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.2k
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
Taking Flight with Tailwind CSS
opdavies
0
4.3k
TDD - Test Driven Drupal
opdavies
0
3k
Building "Build Configs"
opdavies
0
250
Communities and contribution
opdavies
0
72
Working without Workspace
opdavies
0
150
Things you should know about PHP
opdavies
1
640
An Introduction to Mob Programming
opdavies
0
170
Building Static Websites with Sculpin
opdavies
0
1.2k
Deploying PHP applications with Ansible, Ansible Vault and Ansistrano
opdavies
0
5.7k
Other Decks in Programming
See All in Programming
Balkan Ruby 2024 — How and why to run SQLite on Rails in production
fractaledmind
0
110
Ruby on Fails - effective error handling with Rails conventions
talyssonoc
0
260
RailsConf 2024: Riffing on Rails: sketch your way to better designed code
kaspth
0
190
Productivity is Messing Around and Having Fun
hollycummins
1
160
Direct Style Effect Systems The Print[A] ExampleA Comprehension Aid
philipschwarz
PRO
0
390
PHPはいつから死んでいるかの調査
chiroruxx
2
440
デフォルトにして至高、RubyMineの大好きな所
ruzia
0
1.3k
検証も兼ねて個人開発でHonoとかと向き合った話
hanetsuki
1
1.4k
AppRouter Panel Talk
yosuke_furukawa
PRO
1
520
Fragment Composition of GraphQL
quramy
14
1.6k
CDKコントリビュートの最初の壁を越えよう! -簡単issueの見つけ方-
badmintoncryer
3
400
仕様と実装で学ぶOpenTelemetry
drumato
2
180
Featured
See All Featured
StorybookのUI Testing Handbookを読んだ
zakiyama
13
4.6k
Facilitating Awesome Meetings
lara
43
5.6k
A Philosophy of Restraint
colly
197
16k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
34
8.9k
Unsuck your backbone
ammeep
664
57k
The Cult of Friendly URLs
andyhume
74
5.7k
The Pragmatic Product Professional
lauravandoore
26
5.9k
The Brand Is Dead. Long Live the Brand.
mthomps
49
30k
Principles of Awesome APIs and How to Build Them.
keavy
121
16k
A better future with KSS
kneath
231
16k
Writing Fast Ruby
sferik
622
60k
Why Our Code Smells
bkeepers
PRO
331
56k
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