diff --git a/examples/vue-router/app.js b/examples/vue-router/app.js index 56a43ea..6a3867c 100644 --- a/examples/vue-router/app.js +++ b/examples/vue-router/app.js @@ -5,7 +5,7 @@ import Router from 'vue-router' Vue.use(Router) Vue.use(VueMeta) -const ChildComponent = () => ({ +const ChildComponent = { name: `child-component`, props: ['page'], template: `

You're looking at the {{ page }} page

`, @@ -14,13 +14,16 @@ const ChildComponent = () => ({ return this.page } } -}) +} +// this wrapper function is not a requirement for vue-router, +// just a demonstration that render-function style components also work. +// See https://github.com/declandewet/vue-meta/issues/9 for more info. function view (page) { return { name: `section-${page}`, render (h) { - return h(ChildComponent(), { + return h(ChildComponent, { props: { page } }) } diff --git a/src/server/inject.js b/src/server/inject.js index 83b9263..bbbf108 100644 --- a/src/server/inject.js +++ b/src/server/inject.js @@ -9,20 +9,8 @@ import generateServerInjector from './generateServerInjector' * @return {Object} - server meta info with `toString` methods */ export default function inject () { - const Vue = this.constructor - // get meta info with sensible defaults - const info = Vue.util.extend({ - title: '', - htmlAttrs: {}, - bodyAttrs: {}, - meta: [], - script: [], - noscript: [], - style: [], - link: [], - base: [] - }, getMetaInfo(this.$root)) + const info = getMetaInfo(this.$root) // generate server injectors for (let key in info) { diff --git a/src/shared/getComponentOption.js b/src/shared/getComponentOption.js index a816082..7c751d8 100644 --- a/src/shared/getComponentOption.js +++ b/src/shared/getComponentOption.js @@ -23,12 +23,6 @@ export default function getComponentOption (opts, result = {}) { const data = $options[option] if (typeof data === 'object') { - // bind context of option methods (if any) to this component - Object.keys(data).forEach((key) => { - const value = data[key] - data[key] = typeof value === 'function' ? value.bind(component) : value - }) - // merge with existing options result = deepmerge(result, data, { clone: true, diff --git a/src/shared/getMetaInfo.js b/src/shared/getMetaInfo.js index 5b22298..a126eab 100644 --- a/src/shared/getMetaInfo.js +++ b/src/shared/getMetaInfo.js @@ -1,5 +1,6 @@ import deepmerge from 'deepmerge' import getComponentOption from './getComponentOption' +import mergeComponentData from './mergeComponentData' /** * Returns the correct meta info for the given component @@ -55,12 +56,6 @@ export default function getMetaInfo (component) { } }) - // if any info options are a function, coerce them to the result of a call - Object.keys(info).forEach((key) => { - const val = info[key] - info[key] = typeof val === 'function' && key !== 'changed' ? val() : val - }) - // backup the title chunk in case user wants access to it if (info.title) { info.titleChunk = info.title @@ -77,5 +72,16 @@ export default function getMetaInfo (component) { info.base = Object.keys(info.base).length ? [info.base] : [] } - return deepmerge(defaultInfo, info) + const metaInfo = deepmerge(defaultInfo, info) + const componentData = mergeComponentData(component) + + // inject component context into functions & call to normalize data + Object.keys(metaInfo).forEach((key) => { + const val = metaInfo[key] + if (typeof val === 'function') { + metaInfo[key] = val.call(componentData) + } + }) + + return metaInfo } diff --git a/src/shared/mergeComponentData.js b/src/shared/mergeComponentData.js new file mode 100644 index 0000000..648fd97 --- /dev/null +++ b/src/shared/mergeComponentData.js @@ -0,0 +1,16 @@ +/** + * Recursively shallow-merges component object with it's children component objects. + * This function is responsible for obtaining the `this` context of metaInfo props when + * declared in function form. + * + * @param {Object} component - the component object + * @return {Object} - the merged data + */ +export default function mergeComponentData (component) { + if (component.$children.length) { + return component.$children.reduce((data, child) => { + return Object.assign({}, data, mergeComponentData(child)) + }, component) + } + return component +} diff --git a/src/shared/plugin.js b/src/shared/plugin.js index 4aeeefe..05630a2 100644 --- a/src/shared/plugin.js +++ b/src/shared/plugin.js @@ -26,9 +26,10 @@ export default function VueMeta (Vue) { requestId = window.requestAnimationFrame(() => { requestId = null + const info = getMetaInfo(this.$root) // update the meta info - updateClientMetaInfo(getMetaInfo(this.$root)) + updateClientMetaInfo(info) }) } }) diff --git a/test/getComponentOption.spec.js b/test/getComponentOption.spec.js index 8a7d5a6..93b548c 100644 --- a/test/getComponentOption.spec.js +++ b/test/getComponentOption.spec.js @@ -19,21 +19,6 @@ describe('getComponentOption', () => { expect(fetchedOption).to.eql('foo') }) - it('binds option method context to the component instance', () => { - component = new Vue({ - data: { - age: 44 - }, - foo: { - bar () { - return this.age - } - } - }) - const fetchedOption = getComponentOption({ component, option: 'foo' }) - expect(fetchedOption.bar()).to.equal(44) - }) - it('fetches deeply nested component options and merges them', () => { Vue.component('merge-child', { template: '
', foo: { bar: 'baz' } })