Pro Yearly is on sale from $80 to $50! »

Solving Problems with Modern Tooling

Solving Problems with Modern Tooling

Our job as developers is solving problems. When we use tooling, we're standing on a mountain of solved problems that allows us to focus on the unique task at hand.

In this annotated postmortem, learn how using Craft CMS 3.4's built-in GraphQL combined with the modern frontend tooling Vue.js, Vuex, and Axios allowed us to improve the UX of a client project.

Related articles:

Post-Mortem: Outbreak Database

Using the Craft CMS "headless" with the GraphQL API

5d5677253f5910f6053845178cbf143c?s=128

Andrew Welch

March 04, 2020
Tweet

Transcript

  1. nystudio107 / @nystudio107 by Andrew Welch devMode podcast / devMode.fm

    Solving Problems with Modern Tooling
  2. Problems

  3. Math Problems

  4. Word Problems

  5. Relationship Problems

  6. And yet…

  7. That’s What We Do

  8. We Solve Other People’s Problems

  9. We Solve Other People’s Problems

  10. We Solve Other People’s Problems

  11. Client Problems, AMIRITE?

  12. Standing on a Mountain

  13. Standing on a Mountain of Solved Problems Standing on a

    Mountain
  14. This Old Site

  15. This Old Site

  16. This Old Site Custom website built in Cake PHP

  17. This Old Site Database of food- borne illnesses dating back

    to 1993
  18. This Old Site (They just didn’t want it to look

    like it was from 1993)
  19. None
  20. Project Goals

  21. Project Goals Make the outbreak database easier to maintain for

    the content authors
  22. Project Goals Make the frontend easier to use by researchers

    and journalists
  23. Project Goals Modernize the website underpinnings

  24. Potentially provide an API to allow other parties to access

    the database directly Project Goals
  25. Modern Tooling

  26. Vue.js Modern Tooling

  27. Modern Tooling Vuex

  28. Modern Tooling Axios

  29. GraphQL Modern Tooling

  30. Modern Tooling Tailwind CSS

  31. Modern Tooling Craft CMS

  32. Shiny New Site

  33. None
  34. Eliminate the SERP

  35. // Home page import { OutbreakMixins } from '../mixins/outbreak.js'; import

    '@trevoreyre/autocomplete-vue/dist/style.css' import { createStore } from '../store/store.js'; // App main const main = async() => { // Async load the vue module const [ Vue, VueSmoothScroll ] = await Promise.all([ import(/* webpackChunkName: "vue" */ 'vue'), import(/* webpackChunkName: "vue" */ 'vue2-smooth-scroll'), ]); const store = await createStore(Vue.default); Vue.default.use(VueSmoothScroll.default); // Create our vue instance const vm = new Vue.default({ render: (h) => { return h('search-form'); }, mixins: [OutbreakMixins], store, components: { 'search-form': () => import(/* webpackChunkName: "searchform" */ '../../vue/SearchForm.vue'), }, }); return vm; }; home.js
  36. import * as actions from './actions.js'; import * as mutations

    from './mutations.js'; import * as getters from './getters.js'; // Store main export const createStore = async(Vue) => { const { default: Vuex } = await import(/* webpackChunkName: "vuex" */ 'vuex'); Vue.use(Vuex); return new Vuex.Store({ state: { csrf: null, gqlToken: null, outbreakSlug: null, outbreakDetail: null, states: null, vehicles: null, searchForm: null, organisms: null, outbreaks: null, months: null, countries: null, }, getters, mutations, actions, modules: {} }); }; store.js
  37. Auto-Complete Component

  38. <template> <Autocomplete v-bind="autocompleteProps" v-on="autocompleteEvents" > <template v-slot:result="{ result, props }">

    <li v-bind="props"> {{ result[titleField] }} </li> </template> </Autocomplete> </template> Autocomplete.vue
  39. gql.js // Configure the GraphQL api endpoint export const configureGqlApi

    = (url, token) => ({ baseURL: url, headers: { 'X-Requested-With': 'XMLHttpRequest', ...(token && { 'Authorization': `Bearer ${token}` }), } }); // Execute a GraphQL query by sending an XHR to our api endpoint export const executeGqlQuery = async(api, query, variables, callback) => { // Execute the GQL query try { const response = await api.post('', { query: query, variables: variables }); if (callback && response.data.data) { callback(response.data.data); } // Log any errors if (response.data.errors) { console.error(response.data.errors); } } catch (error) { console.error(error); } };
  40. export const outbreaksQuery = (additionalParams) => { let queryAdditionalParams =

    ''; let entryAdditionalParams = ''; additionalParams.forEach((item) =>{ queryAdditionalParams += `, $${item.fieldName}: [QueryArgument]`; entryAdditionalParams += `, ${item.fieldName}: $${item.fieldName}`; }); return ` query outbreaksQuery($needle: String!${queryAdditionalParams}) { entries(section: "outbreaks", search: $needle${entryAdditionalParams}) { title, url, slug, ...on outbreaks_outbreaks_Entry { summary, beginningDate, vehicles { title }, organisms { title }, tags { title } } } } `; queries.js
  41. None
  42. Live Demo??

  43. devMode podcast / devMode.fm nystudio107.com @nystudio107 SpeakerDeck.com/ nystudio107