Dynamic layout feature

This commit is contained in:
Sébastien Chopin 2017-03-17 18:02:58 +01:00
parent fb7b856343
commit b6856928db
2 changed files with 109 additions and 51 deletions

View File

@ -55,22 +55,28 @@ function loadAsyncComponents (to, from, next) {
}
function callMiddleware (Components, context, layout) {
// Call middleware
// if layout is undefined, only call global middleware
let midd = <%= serialize(router.middleware, { isJSON: true }) %>
if (layout.middleware) {
midd = midd.concat(layout.middleware)
}
Components.forEach((Component) => {
if (Component.options.middleware) {
midd = midd.concat(Component.options.middleware)
let unknownMiddleware = false
if (typeof layout !== 'undefined') {
midd = [] // exclude global middleware if layout defined (already called before)
if (layout.middleware) {
midd = midd.concat(layout.middleware)
}
})
Components.forEach((Component) => {
if (Component.options.middleware) {
midd = midd.concat(Component.options.middleware)
}
})
}
midd = midd.map((name) => {
if (typeof middleware[name] !== 'function') {
unknownMiddleware = true
this.error({ statusCode: 500, message: 'Unknown middleware ' + name })
}
return middleware[name]
})
if (unknownMiddleware) return
return promiseSeries(midd, context)
}
@ -81,13 +87,15 @@ function render (to, from, next) {
nextCalled = true
next(path)
}
const context = getContext({ to<%= (store ? ', store' : '') %>, isClient: true, next: _next.bind(this), error: this.error.bind(this) })
let context = getContext({ to<%= (store ? ', store' : '') %>, isClient: true, next: _next.bind(this), error: this.error.bind(this) })
let Components = getMatchedComponents(to)
this._context = context
this._dateLastError = this.$options._nuxt.dateErr
this._hadError = !!this.$options._nuxt.err
if (!Components.length) {
// Default layout
this.loadLayout(NuxtError.layout)
callMiddleware.call(this, Components, context)
.then(() => this.loadLayout(typeof NuxtError.layout === 'function' ? NuxtError.layout(context) : NuxtError.layout))
.then(callMiddleware.bind(this, Components, context))
.then(() => {
this.error({ statusCode: 404, message: 'This page could not be found.' })
@ -105,7 +113,14 @@ function render (to, from, next) {
this.setTransitions(mapTransitions(Components, to, from))
let nextCalled = false
// Set layout
this.loadLayout(Components[0].options.layout)
callMiddleware.call(this, Components, context)
.then(() => {
let layout = Components[0].options.layout
if (typeof layout === 'function') {
layout = layout(context)
}
return this.loadLayout(layout)
})
.then(callMiddleware.bind(this, Components, context))
.then(() => {
// Pass validation?
@ -169,7 +184,11 @@ function render (to, from, next) {
.catch((error) => {
_lastPaths = []
error.statusCode = error.statusCode || error.status || (error.response && error.response.status) || 500
this.loadLayout(NuxtError.layout)
let layout = NuxtError.layout
if (typeof layout === 'function') {
layout = layout(context)
}
this.loadLayout(layout)
.then(() => {
this.error(error)
next(false)
@ -211,7 +230,11 @@ function fixPrepatch (to, ___) {
this.error()
}
// Set layout
this.setLayout(this.$options._nuxt.err ? NuxtError.layout : to.matched[0].components.default.options.layout)
let layout = this.$options._nuxt.err ? NuxtError.layout : to.matched[0].components.default.options.layout
if (typeof layout === 'function') {
layout = layout(this._context)
}
this.setLayout(layout)
// hot reloading
Vue.nextTick(() => hotReloadAPI(this))
})
@ -246,7 +269,8 @@ function addHotReload ($component, depth) {
$component.$vnode.data._hasHotReload = true
var _forceUpdate = $component.$forceUpdate.bind($component.$parent)
$component.$vnode.context.$forceUpdate = () => {
let Component = getMatchedComponents(router.currentRoute)[depth]
let Components = getMatchedComponents(router.currentRoute)
let Component = Components[depth]
if (!Component) return _forceUpdate()
if (typeof Component === 'object' && !Component.options) {
// Updated via vue-router resolveAsyncComponents()
@ -255,41 +279,51 @@ function addHotReload ($component, depth) {
}
this.error()
let promises = []
if (depth === 0) {
// If layout changed
Component.options.layout = Component.options.layout || 'default'
if (this.layoutName !== Component.options.layout) {
let promise = this.loadLayout(Component.options.layout)
promise.then(() => {
this.setLayout(Component.options.layout)
Vue.nextTick(() => hotReloadAPI(this))
})
promises.push(promise)
}
}
const next = function (path) {
<%= (loading ? 'this.$loading.finish && this.$loading.finish()' : '') %>
router.push(path)
}
const context = getContext({ route: router.currentRoute<%= (store ? ', store' : '') %>, isClient: true, next: next.bind(this), error: this.error })
// Call asyncData()
let pAsyncData = promisify(Component.options.asyncData || noopData, context)
pAsyncData.then((asyncDataResult) => {
let data = (typeof Component.options.data === 'function' ? Component.options.data() : noopData())
data = Object.assign(data, asyncDataResult)
Component.options.data = () => data
Component._Ctor.options.data = Component.options.data
<%= (loading ? 'this.$loading.increase && this.$loading.increase(30)' : '') %>
})
promises.push(pAsyncData)
// Call fetch()
Component.options.fetch = Component.options.fetch || noopFetch
let pFetch = Component.options.fetch(context)
if (!(pFetch instanceof Promise)) { pFetch = Promise.resolve(pFetch) }
<%= (loading ? 'pFetch.then(() => this.$loading.increase && this.$loading.increase(30))' : '') %>
promises.push(pFetch)
let context = getContext({ route: router.currentRoute<%= (store ? ', store' : '') %>, isClient: true, next: next.bind(this), error: this.error })
<%= (loading ? 'this.$loading.start && this.$loading.start()' : '') %>
return Promise.all(promises).then(() => {
callMiddleware.call(this, Components, context)
.then(() => {
// If layout changed
if (depth !== 0) return Promise.resolve()
let layout = Component.options.layout || 'default'
if (typeof layout === 'function') {
layout = layout(context)
}
if (this.layoutName === layout) return Promise.resolve()
let promise = this.loadLayout(layout)
promise.then(() => {
this.setLayout(layout)
Vue.nextTick(() => hotReloadAPI(this))
})
return promise
})
.then(() => {
return callMiddleware.call(this, Components, context, this.layout)
})
.then(() => {
// Call asyncData()
let pAsyncData = promisify(Component.options.asyncData || noopData, context)
pAsyncData.then((asyncDataResult) => {
let data = (typeof Component.options.data === 'function' ? Component.options.data() : noopData())
data = Object.assign(data, asyncDataResult)
Component.options.data = () => data
Component._Ctor.options.data = Component.options.data
<%= (loading ? 'this.$loading.increase && this.$loading.increase(30)' : '') %>
})
promises.push(pAsyncData)
// Call fetch()
Component.options.fetch = Component.options.fetch || noopFetch
let pFetch = Component.options.fetch(context)
if (!(pFetch instanceof Promise)) { pFetch = Promise.resolve(pFetch) }
<%= (loading ? 'pFetch.then(() => this.$loading.increase && this.$loading.increase(30))' : '') %>
promises.push(pFetch)
return Promise.all(promises)
})
.then(() => {
<%= (loading ? 'this.$loading.finish && this.$loading.finish()' : '') %>
_forceUpdate()
setTimeout(() => hotReloadAPI(this), 100)
@ -352,10 +386,18 @@ Promise.all(resolveComponents)
.then((Components) => {
const _app = new Vue(app)
let layout = Components.length ? Components[0].options.layout : NuxtError.layout
return _app.loadLayout(layout)
let context = getContext({ to: router.currentRoute, isClient: true })
let layoutName = 'default'
return callMiddleware.call(_app, Components, context)
.then(() => {
_app.setLayout(layout)
layoutName = Components.length ? Components[0].options.layout : NuxtError.layout
if (typeof layoutName === 'function') {
layoutName = layoutName(context)
}
return _app.loadLayout(layoutName)
})
.then(() => {
_app.setLayout(layoutName)
return { _app, Components }
})
})

View File

@ -50,7 +50,7 @@ export default context => {
context.error = _app.$options._nuxt.error.bind(_app)
<%= (isDev ? 'const s = isDev && Date.now()' : '') %>
const ctx = getContext(context)
let ctx = getContext(context)
let Components = getMatchedComponents(context.route)
<% if (store) { %>
let promise = (store._actions && store._actions.nuxtServerInit ? store.dispatch('nuxtServerInit', omit(getContext(context), 'redirect', 'error')) : null)
@ -72,12 +72,28 @@ export default context => {
}
return Component
})
// Call global middleware (nuxt.config.js)
let midd = <%= serialize(router.middleware, { isJSON: true }) %>
midd = midd.map((name) => {
if (typeof middleware[name] !== 'function') {
context.nuxt.error = context.error({ statusCode: 500, message: 'Unknown middleware ' + name })
}
return middleware[name]
})
if (context.nuxt.error) return
return promiseSeries(midd, ctx)
})
.then(() => {
// Set layout
return _app.setLayout(Components.length ? Components[0].options.layout : NuxtError.layout)
let layout = Components.length ? Components[0].options.layout : NuxtError.layout
if (typeof layout === 'function') {
layout = layout(ctx)
}
return _app.setLayout(layout)
})
.then((layout) => {
// Call middleware
let midd = <%= serialize(router.middleware, { isJSON: true }) %>
// Call middleware (layout + pages)
let midd = []
if (layout.middleware) {
midd = midd.concat(layout.middleware)
}