Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Decoupling Drupal with Vue.js
Search
Oliver Davies
June 07, 2019
Programming
0
2.6k
Decoupling Drupal with Vue.js
Presented at Blue Conf 2019.
Oliver Davies
June 07, 2019
Tweet
Share
More Decks by Oliver Davies
See All by Oliver Davies
Building Static Websites with Sculpin
opdavies
0
1.7k
Taking Flight with Tailwind CSS
opdavies
0
5.3k
TDD - Test Driven Drupal
opdavies
0
4.1k
Building "Build Configs"
opdavies
0
490
Communities and contribution
opdavies
0
240
Working without Workspace
opdavies
0
290
Things you should know about PHP
opdavies
1
830
An Introduction to Mob Programming
opdavies
0
340
Deploying PHP applications with Ansible, Ansible Vault and Ansistrano
opdavies
0
6.4k
Other Decks in Programming
See All in Programming
AI Coding Meetup #3 - 導入セッション / ai-coding-meetup-3
izumin5210
0
3.4k
Devoxx BE - Local Development in the AI Era
kdubois
0
130
One Enishi After Another
snoozer05
PRO
0
120
Foundation Modelsを実装日本語学習アプリを作ってみた!
hypebeans
0
120
Server Side Kotlin Meetup vol.16: 内部動作を理解して ハイパフォーマンスなサーバサイド Kotlin アプリケーションを書こう
ternbusty
3
220
Software Architecture
hschwentner
6
2.3k
AIと人間の共創開発!OSSで試行錯誤した開発スタイル
mae616
1
690
いま中途半端なSwift 6対応をするより、Default ActorやApproachable Concurrencyを有効にしてからでいいんじゃない?
yimajo
2
440
Swift Concurrency - 状態監視の罠
objectiveaudio
2
550
The Past, Present, and Future of Enterprise Java
ivargrimstad
0
280
その面倒な作業、「Dart」にやらせませんか? Flutter開発者のための業務効率化
yordgenome03
1
130
Devoxx BE 2025 Loom lab
josepaumard
0
100
Featured
See All Featured
XXLCSS - How to scale CSS and keep your sanity
sugarenia
248
1.3M
[RailsConf 2023] Rails as a piece of cake
palkan
57
5.9k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
35
3.2k
A Tale of Four Properties
chriscoyier
161
23k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
230
22k
Mobile First: as difficult as doing things right
swwweet
225
10k
Agile that works and the tools we love
rasmusluckow
331
21k
A better future with KSS
kneath
239
18k
Measuring & Analyzing Core Web Vitals
bluesmoon
9
620
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
9
910
Producing Creativity
orderedlist
PRO
347
40k
Building Adaptive Systems
keathley
44
2.8k
Transcript
Decoupling Drupal with Vue.js
My a%empt at... Decoupling Drupal with Vue.js
• PHP and Front End Developer • System Administrator •
Senior Engineer at Inviqa • Part-!me freelancer • Open sourcer • @opdavies • oliverdavies.uk
Drupal Content management system, wri!en in PHP
Vue.js Progressive framework for building user interfaces
What do I mean by decoupling?
Drupal is a full stack CMS
Request ➡ Drupal ➡ Twig ➡ Response
Request ➡ Vue.js ➡ Response
Vue.js ➡ Drupal ➡ Vue.js
Why decouple?
• 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
Example: conference talk submission website
Configuring Drupal
drush pm:enable jsonapi
None
Displaying sessions in Vue.js
Configuring CORS in Drupal
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.
# 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
# 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
Configuring Vue.js
None
.env APP_VUE_DRUPAL_URL=http://blueconf.docksal
src/App.vue <script> import _ from 'lodash' import AcceptedSessionsList from '@/components/AcceptedSessionsList'
import SessionForm from '@/components/SessionForm' const axios = require('axios') export default { // ... } </script>
src/App.vue components: { AcceptedSessionsList, SessionForm }
src/App.vue data () { return { loaded: false, sessions: []
} }
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 }) }
src/App.vue computed: { sortedSessions: function () { return _(this.sessions) .sortBy(({
attributes }) => attributes.title) } }
src/App.vue <template> <div id="app" class="antialiased min-h-screen font-sans bg-gray-100 text-black p-12">
<div class="w-full max-w-2xl mx-auto"> <accepted-sessions-list :sessions="sortedSessions" /> <session-form /> </div> </div> </template>
src/components/AcceptedSessionsList.vue props: { sessions: { type: Object, required: true }
}
src/components/AcceptedSessionsList.vue computed: { acceptedSessions: function () { return this.sessions .filter(session
=> this.isAccepted(session)) .value() } }
src/components/AcceptedSessionsList.vue methods: { isAccepted: function ({ attributes }) { return
attributes.field_session_status === 'accepted' } }
src/components/AcceptedSessionsList.vue <template> <div> <h1 class="text-4xl font-semibold mb-2">Sessions</h1> <div v-if="acceptedSessions.length >
0" class="bg-white p-6 rounded-lg border"> <ul class="-mb-3"> <li v-for="{ attributes } in acceptedSessions" :key="attributes.drupal_internal__nid" class="mb-3" > {{ attributes.title }} </li> </ul> </div> </div> </template>
Crea%ng sessions: POSTing back to Drupal
None
None
None
drush user:create api
drush user:role:add api_user api
SessionForm.vue <template> <section class="mt-8"> <form @submit.prevent="submit"> <label class="block mb-4"> Title
<input name="title" type="text" v-model="form.title" required /> </label> <label class="block mb-4"> Abstract <textarea name="title" rows="5" v-model="form.body" required /> </label> <input 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"> </form> </section> </template>
SessionForm.vue data () { return { form: { body: '',
field_session_status: 'accepted', field_session_type: 'full', title: '' } } },
SessionForm.vue methods: { submit () { const adminUuid = '11dad4c2-baa8-4fb2-97c6-12e1ce925806'
const apiUuid = '63936126-87cd-4166-9cb4-63b61a210632' // ... } }
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' } } } } } }
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' } })
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 = '' })
App.vue <session-form @submitted="addSession($event)" /> methods: { addSession: function (session) {
this.sessions.push(session) } }
SessionForm.vue // ... .catch(({ response: { data } }) =>
{ this.errors = _(data.errors).map('detail').value() })
Demo
Thanks! opdavi.es/drupal-vuejs @opdavies oliverdavies.uk