'use strict'; var debug = require('debug')('base:templates:layout'); var utils = require('../utils'); module.exports = function(proto) { /** * Apply a layout to the given `view`. * * @name .applyLayout * @param {Object} `view` * @return {Object} Returns a `view` object. */ proto.applyLayout = function(view) { debug('applying layout to view "%s"', view.path); if (view.options.layoutApplied) { return view; } // handle pre-layout middleware this.handleOnce('preLayout', view); // get the name of the first layout var name = this.resolveLayout(view); var str = view.content; var app = this; var opts = {}; utils.extend(opts, this.options); utils.extend(opts, view.options); if (typeof view.context === 'function') { utils.extend(opts, view.context()); } // if no layout is defined, `app.options.layout` if (typeof name === 'undefined' && utils.isRenderable(view)) { name = this.option('layout'); } // if still no layout is defined, bail out if (utils.isFalsey(name)) { return view; } // Handle each layout before it's applied to a view function handleLayout(obj, stats, depth) { var layoutName = obj.layout.basename; debug('applied layout (#%d) "%s", to view "%s"', depth, layoutName, view.path); view.currentLayout = obj.layout; view.define('layoutStack', stats.history); app.handleOnce('onLayout', view); delete view.currentLayout; } try { // build the layout stack var stack = buildStack(this, name, view); // actually apply the layout var res = utils.layouts(str, name, stack, opts, handleLayout); view.enable('layoutApplied'); view.option('layoutStack', res.history); view.contents = new Buffer(res.result); // handle post-layout middleware this.handleOnce('postLayout', view); } catch (err) { if (this.hasListeners('error')) { this.emit('error', err); } else { throw err; } } return view; }; /** * Asynchronously apply a layout to the given `view`. * * @name .applyLayoutAsync * @param {Object} `view` * @param {Function} `callback` */ proto.applyLayoutAsync = function(view, cb) { debug('applying layout to view "%s"', view.path); var app = this; if (view.options.layoutApplied) { cb(null, view); return; } // handle pre-layout middleware this.handleOnce('preLayout', view, function(err, file) { if (err) return cb(err); // get the name of the first layout var name = app.resolveLayout(file); var str = file.content; var opts = {}; utils.extend(opts, app.options); utils.extend(opts, file.options); utils.extend(opts, file.context()); // if no layout is defined, `app.options.layout` if (typeof name === 'undefined' && utils.isRenderable(file)) { name = app.option('layout'); } // if still no layout is defined, bail out if (utils.isFalsey(name)) { cb(null, file); return; } // Handle each layout before it's applied to a file function handleLayout(obj, stats, depth) { var layoutName = obj.layout.basename; debug('applied layout (#%d) "%s", to file "%s"', depth, layoutName, file.path); file.currentLayout = obj.layout; file.define('layoutStack', stats.history); app.handleOnce('onLayout', file); delete file.currentLayout; } try { // build the layout stack var stack = buildStack(app, name, file); // actually apply the layout var res = utils.layouts(str, name, stack, opts, handleLayout); file.enable('layoutApplied'); file.option('layoutStack', res.history); file.contents = new Buffer(res.result); } catch (err) { if (app.hasListeners('error')) { app.emit('error', err); } cb(err); return; } // handle post-layout middleware app.handleOnce('postLayout', file, cb); }); }; /** * Get the layout stack by creating an object from all * collections with the "layout" `viewType` * * @param {Object} `app` * @param {String} `name` The starting layout name * @param {Object} `view` * @return {Object} Returns the layout stack. */ function buildStack(app, name, view) { var layoutExists = false; var registered = 0; var layouts = {}; // get all collections with `viewType` layout var collections = app.viewTypes.layout; var len = collections.length; var idx = -1; while (++idx < len) { var collection = app[collections[idx]]; // detect if at least one of the collections has // our starting layout if (!layoutExists && collection.getView(name)) { layoutExists = true; } // add the collection views to the layouts object for (var key in collection.views) { layouts[key] = collection.views[key]; registered++; } } if (registered === 0) { throw app.formatError('layouts', 'registered', name, view); } if (layoutExists === false) { throw app.formatError('layouts', 'notfound', name, view); } return layouts; } };