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

View File

@ -50,7 +50,7 @@ export default context => {
context.error = _app.$options._nuxt.error.bind(_app) context.error = _app.$options._nuxt.error.bind(_app)
<%= (isDev ? 'const s = isDev && Date.now()' : '') %> <%= (isDev ? 'const s = isDev && Date.now()' : '') %>
const ctx = getContext(context) let ctx = getContext(context)
let Components = getMatchedComponents(context.route) let Components = getMatchedComponents(context.route)
<% if (store) { %> <% if (store) { %>
let promise = (store._actions && store._actions.nuxtServerInit ? store.dispatch('nuxtServerInit', omit(getContext(context), 'redirect', 'error')) : null) 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 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 // 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) => { .then((layout) => {
// Call middleware // Call middleware (layout + pages)
let midd = <%= serialize(router.middleware, { isJSON: true }) %> let midd = []
if (layout.middleware) { if (layout.middleware) {
midd = midd.concat(layout.middleware) midd = midd.concat(layout.middleware)
} }