Skip to content

Commit

Permalink
feat(#312): unwrap template injected components (#351)
Browse files Browse the repository at this point in the history
* feat(#312): unwrap template injected components

* chore(#312): update theming guide

* chore(#312): add wrappers example

* chore(#312): update wrapper ignore logic
  • Loading branch information
Decipher committed Oct 31, 2021
1 parent e7b1533 commit c4457e1
Show file tree
Hide file tree
Showing 10 changed files with 206 additions and 17 deletions.
5 changes: 5 additions & 0 deletions .changeset/twenty-fireants-collect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"druxt": minor
---

Changed template injected module components to not use a DruxtWrapper component by default.
14 changes: 12 additions & 2 deletions docs/content/guide/theming.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,24 @@ If there are no matching component names, a default `DruxtWrapper` component wil

Most Druxt modules can have the default template overridden, allowing for full control of the default slot rendering.

The available data provided to the template scope is deteremined by the relevant module.
The available data provided to the template scope is determined by the relevant module.

```vue
<DruxtEntity>
<DruxtEntity v-bind="props">
<template #default="{ entity }">
<div>
<h1>{{ entity.attributes.title }}</h1>
</div>
</template>
</DruxtEntity>
```

By default, a component using the default template will not be wrapped by a DruxtWrapper component. It is possible to enable the DruxtWrapper system by setting the `wrapper` property to `true`:

```vue
<DruxtBlock v-bind="props" :wrapper="true">
<template #default="{ block }">
// This will be wrapped by a DruxtBlock Wrapper component.
</template>
</DruxtBlock>
````
10 changes: 10 additions & 0 deletions examples/nuxt/wrappers/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# wrappers

This directory contains exaples of how to use Wrapper components and the wrapper property.


## Setup

```
yarn && yarn dev
```
16 changes: 16 additions & 0 deletions examples/nuxt/wrappers/components/druxt/entity/node/Page.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<template>
<div>
<h3>This is a theme component: DruxtEntityNodePage.vue</h3>
<slot />
</div>
</template>

<script>
export default {
druxt: {
query: {
schema: true
}
}
}
</script>
3 changes: 3 additions & 0 deletions examples/nuxt/wrappers/nuxt.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
buildModules: [['druxt-entity', { baseUrl: 'https://demo-api.druxtjs.org' }]]
}
11 changes: 11 additions & 0 deletions examples/nuxt/wrappers/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "wrappers",
"scripts": {
"dev": "nuxt dev"
},
"packageManager": "yarn@3.0.0",
"dependencies": {
"druxt-entity": "link:../../../packages/druxt-entity",
"nuxt": "latest"
}
}
93 changes: 93 additions & 0 deletions examples/nuxt/wrappers/pages/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<template>
<div v-if="!$fetchState.pending">
<h1>DruxtWrapper examples</h1>
<blockquote>
The DruxtWrapper is a theming component that wraps the DruxtModule output.
<br />
The following examples demonstrate different ways it can be used.
</blockquote>

<hr />

<h2>DruxtEntity default</h2>
<p>This is the default behaviour, renders a theme component.</p>
<pre><code>&lt;DruxtEntity :type="type" :uuid="uuid" /&gt;</code></pre>
<details>
<summary><strong>Output</strong></summary>
<DruxtEntity :type="type" :uuid="uuid" />
</details>

<hr />

<h2>DruxtEntity with wrapper disabled</h2>
<p>
This will ignore any available wrapper components and renders the default
output of the DruxtEntity module.
</p>
<pre><code>&lt;DruxtEntity :type="type" :uuid="uuid" :wrapper="false" /&gt;</code></pre>
<details>
<summary><strong>Output</strong></summary>
<DruxtEntity :type="type" :uuid="uuid" :wrapper="false" />
</details>

<hr />

<h2>DruxtEntity using template injection, default wrapper</h2>
<p>
The value of the template will be the output of the component, wrapper
component is ignored.
</p>
<pre><code>&lt;DruxtEntity :type="type" :uuid="uuid"&gt;
&lt;template #default="{ entity }"&gt;
...
&lt;/template&gt;
&lt;/DruxtEntity&gt;</code></pre>
<details>
<summary><strong>Output</strong></summary>
<DruxtEntity :type="type" :uuid="uuid">
<template #default="{ entity }">
<pre><code>{{ JSON.stringify(entity, null, ' ') }}</code></pre>
</template>
</DruxtEntity>
</details>

<hr />

<h2>DruxtEntity using template injection with wrapper enabled</h2>
<p>
The value of the template will be the output of the default slot in a
wrapper component.
</p>
<pre><code>&lt;DruxtEntity :type="type" :uuid="uuid" :wrapper="true"&gt;
&lt;template #default="{ entity }"&gt;
...
&lt;/template&gt;
&lt;/DruxtEntity&gt;</code></pre>
<details>
<summary><strong>Output</strong></summary>
<DruxtEntity :type="type" :uuid="uuid" :wrapper="true">
<template #default="{ entity }">
<pre><code>{{ JSON.stringify(entity, null, ' ') }}</code></pre>
</template>
</DruxtEntity>
</details>
</div>

<div v-else>Loading...</div>
</template>

<script>
import { DrupalJsonApiParams } from 'drupal-jsonapi-params'
export default {
async fetch() {
const query = new DrupalJsonApiParams().addPageLimit(1).addFields(this.type, ['id'])
this.uuid = (await this.$store.dispatch('druxt/getCollection', { type: this.type, query })).data[0].id
},
data: () => ({
uuid: null,
type: 'node--page'
})
}
</script>
Empty file.
60 changes: 46 additions & 14 deletions packages/druxt/src/components/DruxtModule.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,25 @@ export default {
default: null,
},
/**
* The wrapper component configuration.
*
* Used to set the wrapper component, class, style and propsData.
*
* @example
* <DruxtModule
* :wrapper="{
* component: 'MyWrapper',
* class: 'wrapper',
* propsData: { foo: 'bar' }
* }"
* />
*
* @type {(Boolean|Object)}
*/
wrapper: {
type: Object,
default: () => ({
component: 'div',
propsData: {},
})
type: [Boolean, Object],
default: () => undefined
},
},
Expand Down Expand Up @@ -97,7 +110,17 @@ export default {
}
// Build wrapper component object.
const options = this.getModuleComponents()
let options = []
const hasDefaultTemplate = !!(this.$vnode.data.scopedSlots || {}).default
// Load wrapper components if:
if (
// No default template and wrapper isn't false OR
(!hasDefaultTemplate && this.wrapper !== false) ||
// Default tempalte and wrapper is set
(hasDefaultTemplate && this.wrapper)
) {
options = this.getModuleComponents()
}
let component = {
is: (((options.filter(o => o.global) || [])[0] || {}).name || 'DruxtWrapper'),
options: options.map(o => o.name) || [],
Expand Down Expand Up @@ -154,12 +177,14 @@ export default {
* @returns {Components}
*/
getModuleComponents() {
// Ensure that the Druxt module component has `druxt.componentOptions`.
if (!(this.$options.druxt || {}).componentOptions) {
return []
}
const options = this.$options.druxt.componentOptions.call(this, this)
if (!options || !options.length) {
// Ensure that there available component options are returned.
if (!(options || []).length) {
return []
}
Expand Down Expand Up @@ -304,20 +329,27 @@ export default {
const self = this
const wrapperData = {
class: this.wrapper.class || undefined,
style: this.wrapper.style || undefined,
props: this.wrapper.propsData,
class: (this.wrapper || {}).class || undefined,
style: (this.wrapper || {}).style || undefined,
props: (this.wrapper || {}).propsData || undefined,
}
// Return only wrapper if fetch state is still pending.
if (this.$fetchState.pending) {
return h(this.wrapper.component, wrapperData)
return h((this.wrapper || {}).component || 'div', wrapperData)
}
// Return wrapped component.
// Prepare attributes.
const attrs = { ...this.component.$attrs, ...this.$attrs }
delete attrs['data-fetch-key']
return h(this.wrapper.component, wrapperData, [
// Unwrap default template based component if required.
if ((this.$scopedSlots.default && !this.wrapper) || this.wrapper === false) {
this.component.is = 'DruxtWrapper'
}
// Return component.
return h((this.wrapper || {}).component || 'div', wrapperData, [
h(this.component.is, {
attrs,
on: {
Expand Down
11 changes: 10 additions & 1 deletion packages/druxt/test/components/DruxtModule.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe('DruxtModule component', () => {
mocks = {
$createElement: jest.fn(),
$fetchState: { pending: false },
$options: { druxt: {} },
$nuxt: {
context: {
isDev: false,
Expand Down Expand Up @@ -199,11 +200,19 @@ describe('DruxtModule component', () => {
})

test('custom default slot', async () => {
const customModule = {
extends: DruxtModule,
druxt: {}
}

const scopedSlots = { default: jest.fn() }
const wrapper = mount(DruxtModule, { localVue, mocks, scopedSlots })
const wrapper = mount(customModule, { localVue, mocks, scopedSlots })
await wrapper.vm.$options.fetch.call(wrapper.vm)

wrapper.vm.getScopedSlots().default.call(wrapper.vm)
expect(scopedSlots.default).toHaveBeenCalled()

await wrapper.setProps({ wrapper: true })
await wrapper.vm.$options.fetch.call(wrapper.vm)
})
})

0 comments on commit c4457e1

Please sign in to comment.