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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  5. Vue.js
    Progressive framework
    for building user
    interfaces

    View full-size slide

  6. What do I mean by decoupling?

    View full-size slide

  7. Drupal is a full stack CMS

    View full-size slide

  8. Request ➡ Drupal ➡
    Twig ➡ Response

    View full-size slide

  9. Request ➡ Vue.js ➡ Response

    View full-size slide

  10. Vue.js ➡ Drupal ➡ Vue.js

    View full-size slide

  11. Why decouple?

    View full-size 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 full-size slide

  13. Example: conference talk
    submission website

    View full-size slide

  14. Configuring Drupal

    View full-size slide

  15. drush pm:enable jsonapi

    View full-size slide

  16. Displaying sessions in Vue.js

    View full-size slide

  17. Configuring CORS in Drupal

    View full-size slide

  18. 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 full-size slide

  19. # 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 full-size slide

  20. # 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 full-size slide

  21. Configuring Vue.js

    View full-size slide

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

    View full-size slide

  23. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  26. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  31. src/components/AcceptedSessionsList.vue


    Sessions


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





    View full-size slide

  32. Crea%ng sessions:
    POSTing back to Drupal

    View full-size slide

  33. drush user:create api

    View full-size slide

  34. drush user:role:add
    api_user api

    View full-size slide

  35. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  38. 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 full-size slide

  39. 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 full-size slide

  40. 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 full-size slide

  41. App.vue

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide