Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Decoupling Drupal with Vue.js

Decoupling Drupal with Vue.js

Presented at Blue Conf 2019.

Oliver Davies

June 07, 2019
Tweet

More Decks by Oliver Davies

Other Decks in Programming

Transcript

  1. Decoupling Drupal with Vue.js

    View Slide

  2. My a%empt at...
    Decoupling Drupal with Vue.js

    View Slide

  3. • PHP and Front End Developer
    • System Administrator
    • Senior Engineer at Inviqa
    • Part-!me freelancer
    • Open sourcer
    • @opdavies
    • oliverdavies.uk

    View Slide

  4. Drupal
    Content management
    system, wri!en in PHP

    View Slide

  5. Vue.js
    Progressive framework
    for building user
    interfaces

    View Slide

  6. What do I mean by decoupling?

    View Slide

  7. Drupal is a full stack CMS

    View Slide

  8. Request ➡ Drupal ➡
    Twig ➡ Response

    View Slide

  9. Request ➡ Vue.js ➡ Response

    View Slide

  10. Vue.js ➡ Drupal ➡ Vue.js

    View Slide

  11. Why decouple?

    View Slide

  12. • More flexibility
    • Different development teams
    • Security
    • Exposing data to mul"ple sources
    • Aggrega"ng data from mul"ple sources
    • Back-end applica"on can be swapped out

    View Slide

  13. Example: conference talk
    submission website

    View Slide

  14. Configuring Drupal

    View Slide

  15. drush pm:enable jsonapi

    View Slide

  16. View Slide

  17. Displaying sessions in Vue.js

    View Slide

  18. Configuring CORS in Drupal

    View Slide

  19. Access to XMLH!pRequest at 'h!p:/
    /
    blueconf.docksal/jsonapi/node/session' from
    origin 'h!p:/
    /localhost:8080' has been blocked
    by CORS policy:
    No 'Access-Control-Allow-Origin' header is
    present on the requested resource.

    View Slide

  20. # services.local.yml
    cors.config:
    enabled: false
    # Specify allowed headers, like 'x-allowed-header'.
    allowedHeaders: []
    # Specify allowed request methods, specify ['*'] to allow all possible ones.
    allowedMethods: []
    # Configure requests allowed from specific origins.
    allowedOrigins: ['*']
    # Sets the Access-Control-Expose-Headers header.
    exposedHeaders: false
    # Sets the Access-Control-Max-Age header.
    maxAge: false
    # Sets the Access-Control-Allow-Credentials header.
    supportsCredentials: false

    View Slide

  21. # services.local.yml
    cors.config:
    enabled: true
    allowedHeaders: [
    'x-csrf-token',
    'authorization',
    'content-type',
    'accept',
    'origin',
    'x-requested-with',
    'access-control-allow-origin',
    'x-allowed-header',
    '*'
    ]
    allowedMethods: ['*']
    allowedOrigins: ['http://localhost:8080']
    exposedHeaders: true
    maxAge: false
    supportsCredentials: true

    View Slide

  22. Configuring Vue.js

    View Slide

  23. View Slide

  24. .env
    APP_VUE_DRUPAL_URL=http://blueconf.docksal

    View Slide

  25. src/App.vue
    <br/>import _ from 'lodash'<br/>import AcceptedSessionsList from '@/components/AcceptedSessionsList'<br/>import SessionForm from '@/components/SessionForm'<br/>const axios = require('axios')<br/>export default {<br/>// ...<br/>}<br/>

    View Slide

  26. src/App.vue
    components: {
    AcceptedSessionsList,
    SessionForm
    }

    View Slide

  27. src/App.vue
    data () {
    return {
    loaded: false,
    sessions: []
    }
    }

    View Slide

  28. src/App.vue
    mounted () {
    const baseUrl = process.env.VUE_APP_DRUPAL_URL
    axios.get(`${baseUrl}/jsonapi/node/session`)
    .then(({ data }) => {
    this.loaded = true
    this.sessions = data.data
    })
    }

    View Slide

  29. src/App.vue
    computed: {
    sortedSessions: function () {
    return _(this.sessions)
    .sortBy(({ attributes }) => attributes.title)
    }
    }

    View Slide

  30. src/App.vue








    View Slide

  31. src/components/AcceptedSessionsList.vue
    props: {
    sessions: {
    type: Object,
    required: true
    }
    }

    View Slide

  32. src/components/AcceptedSessionsList.vue
    computed: {
    acceptedSessions: function () {
    return this.sessions
    .filter(session => this.isAccepted(session))
    .value()
    }
    }

    View Slide

  33. src/components/AcceptedSessionsList.vue
    methods: {
    isAccepted: function ({ attributes }) {
    return attributes.field_session_status === 'accepted'
    }
    }

    View Slide

  34. src/components/AcceptedSessionsList.vue


    Sessions


    v-for="{ attributes } in acceptedSessions"
    :key="attributes.drupal_internal__nid"
    class="mb-3"
    >
    {{ attributes.title }}





    View Slide

  35. Crea%ng sessions:
    POSTing back to Drupal

    View Slide

  36. View Slide

  37. View Slide

  38. View Slide

  39. drush user:create api

    View Slide

  40. drush user:role:add
    api_user api

    View Slide

  41. SessionForm.vue




    Title



    Abstract


    class="cursor-pointer bg-blue-500 hover:bg-blue-700 focus:bg-blue-700
    text-gray-100 px-4 py-2 rounded"
    type="submit"
    value="Submit session">



    View Slide

  42. SessionForm.vue
    data () {
    return {
    form: {
    body: '',
    field_session_status: 'accepted',
    field_session_type: 'full',
    title: ''
    }
    }
    },

    View Slide

  43. SessionForm.vue
    methods: {
    submit () {
    const adminUuid = '11dad4c2-baa8-4fb2-97c6-12e1ce925806'
    const apiUuid = '63936126-87cd-4166-9cb4-63b61a210632'
    // ...
    }
    }

    View Slide

  44. SessionForm.vue
    methods: {
    submit () {
    // ...
    const data = {
    type: 'node--session',
    attributes: this.form,
    relationships: {
    'field_speakers': {
    'data': { 'id': adminUuid, 'type': 'user--user' }
    },
    'uid': {
    'data': { 'id': apiUuid, 'type': 'user--user' }
    }
    }
    }
    }
    }

    View Slide

  45. SessionForm.vue
    const baseUrl = process.env.VUE_APP_DRUPAL_URL
    axios({
    method: 'post',
    url: `${baseUrl}/jsonapi/node/session`,
    data: { data },
    headers: {
    'Accept': 'application/vnd.api+json',
    'Authorization': 'Basic YXBpOmFwaQ==',
    'Content-Type': 'application/vnd.api+json'
    }
    })

    View Slide

  46. SessionForm.vue
    // ...
    .then(({ data }) => {
    const title = data.data.attributes.title
    this.messages.push(`Session ${title} has been created.`)
    this.$emit('submitted', data.data)
    this.form.body = ''
    this.form.title = ''
    })

    View Slide

  47. App.vue

    methods: {
    addSession: function (session) {
    this.sessions.push(session)
    }
    }

    View Slide

  48. SessionForm.vue
    // ...
    .catch(({ response: { data } }) => {
    this.errors = _(data.errors).map('detail').value()
    })

    View Slide

  49. Demo

    View Slide

  50. Thanks!
    opdavi.es/drupal-vuejs
    @opdavies
    oliverdavies.uk

    View Slide