UNPKG

9.83 kBJavaScriptView Raw
1/*!
2 * templates <https://github.com/jonschlinkert/templates>
3 *
4 * Copyright (c) 2015, Jon Schlinkert.
5 * Licensed under the MIT License.
6 */
7
8'use strict';
9
10var Base = require('base');
11var debug = require('debug')('base:templates');
12var helpers = require('./lib/helpers/');
13var plugin = require('./lib/plugins/');
14var utils = require('./lib/utils');
15var lib = require('./lib/');
16
17/**
18 * Expose `Templates`
19 */
20
21module.exports = exports = Templates;
22
23/**
24 * This function is the main export of the templates module.
25 * Initialize an instance of `templates` to create your
26 * application.
27 *
28 * ```js
29 * var templates = require('templates');
30 * var app = templates();
31 * ```
32 * @param {Object} `options`
33 * @api public
34 */
35
36function Templates(options) {
37 if (!(this instanceof Templates)) {
38 return new Templates(options);
39 }
40
41 Base.call(this, null, options);
42 this.is('templates');
43 this.define('isApp', true);
44 this.use(utils.option());
45 this.use(utils.plugin());
46 this.initTemplates();
47}
48
49/**
50 * Inherit `Base` and load static plugins
51 */
52
53plugin.static(Base, Templates, 'Templates');
54
55/**
56 * Initialize Templates
57 */
58
59Templates.prototype.initTemplates = function() {
60 debug('initializing <%s>, called from <%s>', __filename, module.parent.id);
61 Templates.emit('templates.preInit', this);
62
63 this.items = {};
64 this.views = {};
65 this.inflections = {};
66
67 // listen for options events
68 this.listen(this);
69
70 this.define('utils', utils);
71 this.use(plugin.init);
72 this.use(plugin.renameKey());
73 this.use(plugin.context);
74 this.use(plugin.lookup);
75 this.use(utils.engines());
76 this.use(utils.helpers());
77 this.use(utils.routes())
78
79 this.use(plugin.item('item', 'Item'));
80 this.use(plugin.item('view', 'View'));
81
82 for (var key in this.options.mixins) {
83 this.mixin(key, this.options.mixins[key]);
84 }
85
86 // create an async `view` helper
87 helpers.view(this);
88
89 // expose constructors on the instance
90 this.expose('Item');
91 this.expose('View');
92 this.expose('List');
93 this.expose('Collection');
94 this.expose('Group');
95 this.expose('Views');
96
97 Templates.setup(this, 'Templates');
98 Templates.emit('templates.postInit', this);
99};
100
101/**
102 * Expose constructors on app instance, allowing them to be
103 * overridden by the user after Templates is instantiated.
104 */
105
106Templates.prototype.expose = function(name) {
107 this.define(name, {
108 configurable: true,
109 enumerable: true,
110 get: function() {
111 return this.options[name] || Templates[name];
112 }
113 });
114};
115
116/**
117 * Listen for events
118 */
119
120Templates.prototype.listen = function(app) {
121 this.on('option', function(key, value) {
122 utils.updateOptions(app, key, value);
123 });
124
125 // ensure that plugins are loaded onto collections
126 // created after the plugins are registered
127 this.on('use', function(fn, app) {
128 if (!fn) return;
129 for (var key in app.views) {
130 if (app.views.hasOwnProperty(key)) {
131 app[key].use(fn);
132 }
133 }
134 });
135};
136
137/**
138 * Create a new list. See the [list docs](docs/lists.md) for more
139 * information about lists.
140 *
141 * ```js
142 * var list = app.list();
143 * list.addItem('abc', {content: '...'});
144 *
145 * // or, create list from a collection
146 * app.create('pages');
147 * var list = app.list(app.pages);
148 * ```
149 * @param {Object} `opts` List options
150 * @return {Object} Returns the `list` instance for chaining.
151 * @api public
152 */
153
154Templates.prototype.list = function(opts) {
155 opts = opts || {};
156
157 if (!opts.isList) {
158 utils.defaults(opts, this.options);
159 }
160
161 var List = opts.List || this.get('List');
162 var list = {};
163
164 if (opts.isList === true || opts instanceof List) {
165 list = opts;
166 } else {
167 opts.Item = opts.Item || opts.View || this.get('Item');
168 list = new List(opts);
169 }
170
171 // customize list items
172 this.extendList(list, opts);
173
174 // emit the list
175 this.emit('list', list, opts);
176 return list;
177};
178
179/**
180 * Create a new collection. Collections are decorated with
181 * special methods for getting and setting items from the
182 * collection. Note that, unlike the [create](#create) method,
183 * collections created with `.collection()` are not cached.
184 *
185 * See the [collection docs](docs/collections.md) for more
186 * information about collections.
187 *
188 * @param {Object} `opts` Collection options
189 * @return {Object} Returns the `collection` instance for chaining.
190 * @api public
191 */
192
193Templates.prototype.collection = function(opts, created) {
194 opts = opts || {};
195
196 if (!opts.isCollection) {
197 utils.defaults(opts, this.options);
198 }
199
200 var Collection = opts.Collection || opts.Views || this.get('Views');
201 var collection = {};
202
203 if (opts.isCollection === true) {
204 collection = opts;
205 } else {
206 opts.Item = opts.Item || opts.View || this.get('View');
207 collection = new Collection(opts);
208 }
209
210 if (created !== true) {
211 // if it's a view collection, prime the viewType(s)
212 if (collection.isViews) {
213 collection.viewType();
214 }
215
216 // run collection plugins
217 this.run(collection);
218
219 // emit the collection
220 this.emit('collection', collection, opts);
221 this.extendViews(collection, opts);
222
223 // add collection and view helpers
224 helpers.singular(this, collection);
225 helpers.plural(this, collection);
226 } else {
227
228 // emit the collection
229 this.emit('collection', collection, opts);
230 }
231 return collection;
232};
233
234/**
235 * Create a new view collection to be stored on the `app.views` object. See
236 * the [create docs](docs/collections.md#create) for more details.
237 *
238 * @param {String} `name` The name of the collection to create. Plural or singular form may be used, as the inflections are automatically resolved when the collection
239 * is created.
240 * @param {Object} `opts` Collection options
241 * @return {Object} Returns the `collection` instance for chaining.
242 * @api public
243 */
244
245Templates.prototype.create = function(name, opts) {
246 debug('creating view collection: "%s"', name);
247 opts = opts || {};
248
249 if (!opts.isCollection) {
250 opts = utils.merge({}, this.options, opts);
251 }
252
253 // emit the collection name and options
254 this.emit('create', name, opts);
255
256 // create the actual collection
257 var collection = this.collection(opts, true);
258 utils.setInstanceNames(collection, name);
259
260 // get the collection inflections, e.g. page/pages
261 var single = utils.single(name);
262 var plural = utils.plural(name);
263
264 // map the inflections for lookups
265 this.inflections[single] = plural;
266
267 // add inflections to collection options
268 collection.option('inflection', single);
269 collection.option('plural', plural);
270
271 // prime the viewType(s) for the collection
272 this.viewType(plural, collection.viewType());
273
274 // add the collection to `app.views`
275 this.views[plural] = collection.items || collection.views;
276
277 // create loader functions for adding views to this collection
278 this.define(plural, function() {
279 return collection.addViews.apply(collection, arguments);
280 });
281 this.define(single, function() {
282 return collection.addView.apply(collection, arguments);
283 });
284
285 /* eslint-disable no-proto */
286 // decorate loader methods with collection methods
287 this[plural].__proto__ = collection;
288 this[single].__proto__ = collection;
289
290 // create aliases on the collection for
291 // addView/addViews to support chaining
292 collection.define(plural, this[plural]);
293 collection.define(single, this[single]);
294
295 // run collection plugins
296 this.run(collection);
297
298 // decorate collection and views in collection
299 // (this is a prototype method to allow overriding behavior)
300 this.extendViews(collection, opts);
301
302 // emit create
303 this.emit('postCreate', collection, opts);
304
305 // add collection and view helpers
306 helpers.singular(this, collection);
307 helpers.plural(this, collection);
308 return collection;
309};
310
311/**
312 * Decorate or override methods on a view created by a collection.
313 */
314
315Templates.prototype.extendView = function(view, options) {
316 plugin.view(this, view, options);
317 return this;
318};
319
320/**
321 * Decorate or override methods on a view collection instance.
322 */
323
324Templates.prototype.extendViews = function(views, options) {
325 plugin.views(this, views, options);
326 return this;
327};
328
329/**
330 * Decorate or override methods on a view collection instance.
331 */
332
333Templates.prototype.extendList = function(views, options) {
334 plugin.list(this, views, options);
335 return this;
336};
337
338/**
339 * Resolve the name of the layout to use for `view`
340 */
341
342Templates.prototype.resolveLayout = function(view) {
343 debug('resolving layout for "%s"', view.key);
344
345 if (!utils.isPartial(view) && typeof view.layout === 'undefined') {
346 if (view.options && view.options.collection) {
347 var collection = this[view.options.collection];
348 var layout = collection.resolveLayout(view);
349 if (typeof layout === 'undefined') {
350 layout = this.option('layout');
351 }
352 return layout;
353 }
354 }
355 return view.layout;
356};
357
358/**
359 * Expose static `setup` method for providing access to an
360 * instance before any other code is run.
361 *
362 * ```js
363 * function App(options) {
364 * Templates.call(this, options);
365 * Templates.setup(this);
366 * }
367 * Templates.extend(App);
368 * ```
369 * @param {Object} `app` Application instance
370 * @param {String} `name` Optionally pass the constructor name to use.
371 * @return {undefined}
372 * @api public
373 */
374
375Templates.setup = function(app, name) {
376 var setup = app.options['init' + name || app.constructor.name];
377 if (typeof setup === 'function') {
378 setup.call(app, app, app.options);
379 }
380};
381
382/**
383 * Expose constructors as static methods.
384 */
385
386Templates.Base = Base;
387Templates.Collection = lib.collection;
388Templates.List = lib.list;
389Templates.Group = lib.group;
390Templates.Views = lib.views;
391Templates.Item = require('vinyl-item');
392Templates.View = require('vinyl-view');
393
394/**
395 * Expose properties for unit tests
396 */
397
398utils.define(Templates, 'utils', utils);
399utils.define(Templates, '_', { lib: lib, plugin: plugin });