UNPKG

9.68 kBJavaScriptView Raw
1/*!
2 * templates <https://github.com/jonschlinkert/templates>
3 *
4 * Copyright (c) 2015-2017, Jon Schlinkert.
5 * Released 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].__proto__.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(options) {
155 options = options || {};
156 var opts = {};
157
158 if (options.isList || options.isViews) {
159 opts = utils.merge({}, this.options, options.options);
160 } else {
161 opts = utils.merge({}, this.options, options);
162 }
163
164 var List = opts.List || this.get('List');
165 var list = {};
166
167 if (!options.isList) {
168 opts.Item = opts.Item || this.get('Item');
169 list = new List(opts);
170
171 if (options.isViews) {
172 list.addItems(options.views);
173 }
174 } else {
175 list = options;
176 }
177
178 // customize list items
179 this.extendList(list, opts);
180
181 // emit the list
182 this.emit('list', list, opts);
183 return list;
184};
185
186/**
187 * Create a new collection. Collections are decorated with
188 * special methods for getting and setting items from the
189 * collection. Note that, unlike the [create](#create) method,
190 * collections created with `.collection()` are not cached.
191 *
192 * See the [collection docs](docs/collections.md) for more
193 * information about collections.
194 *
195 * @param {Object} `opts` Collection options
196 * @return {Object} Returns the `collection` instance for chaining.
197 * @api public
198 */
199
200Templates.prototype.collection = function(options, created) {
201 options = options || {};
202 var opts = {};
203
204 if (options.isList || options.isCollection) {
205 opts = utils.merge({}, this.options, options.options);
206 } else {
207 opts = utils.merge({}, this.options, options);
208 }
209
210 var Collection = opts.Collection || opts.Views || this.get('Views');
211 var collection = {};
212
213 if (options.isCollection) {
214 collection = options;
215
216 } else {
217 opts.Item = opts.Item || this.get('Item');
218 collection = new Collection(opts);
219 }
220
221 if (!options.isCollection) {
222 this.extendViews(collection, opts);
223 }
224
225 this.emit('collection', collection, opts);
226 return collection;
227};
228
229/**
230 * Create a new view collection to be stored on the `app.views` object. See
231 * the [create docs](docs/collections.md#create) for more details.
232 *
233 * @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
234 * is created.
235 * @param {Object} `opts` Collection options
236 * @return {Object} Returns the `collection` instance for chaining.
237 * @api public
238 */
239
240Templates.prototype.create = function(name, options) {
241 debug('creating view collection: "%s"', name);
242 options = options || {};
243
244 // emit the collection name and options
245 this.emit('create', name, options);
246
247 // create the actual collection
248 var collection = this.collection(options, true);
249 utils.setInstanceNames(collection, name);
250
251 // get the collection inflections, e.g. page/pages
252 var single = utils.single(name);
253 var plural = utils.plural(name);
254
255 // map the inflections for lookups
256 this.inflections[single] = plural;
257
258 // add inflections to collection options
259 collection.option('inflection', single);
260 collection.option('plural', plural);
261
262 // prime the viewType(s) for the collection
263 this.viewType(plural, collection.viewType());
264
265 // add a reference the collection's views on `app.views[plural]`
266 this.views[plural] = collection.views;
267
268 // create loader functions for adding views to the collection
269 var parent = Object.create(collection);
270 this.define(plural, function() {
271 return parent.addViews.apply(parent, arguments);
272 });
273 this.define(single, function() {
274 return parent.addView.apply(parent, arguments);
275 });
276
277 Object.setPrototypeOf(this[plural], parent);
278 Object.setPrototypeOf(this[single], parent);
279
280 // create references to the app methods on the collection
281 // itself, so that chaining will work seamlessly
282 collection.define(plural, this[plural]);
283 collection.define(single, this[single]);
284
285 // decorate collection and views in collection
286 // (this is a prototype method to allow overriding behavior)
287 this.extendViews(collection, options);
288
289 // run collection plugins
290 this.run(collection);
291
292 // add collection and view helpers
293 helpers.singular(this, collection);
294 helpers.plural(this, collection);
295
296 // emit create
297 this.emit('postCreate', collection, options);
298 return collection;
299};
300
301/**
302 * Decorate or override methods on a view created by a collection.
303 */
304
305Templates.prototype.extendView = function(view, options) {
306 plugin.view(this, view, options);
307 return this;
308};
309
310/**
311 * Decorate or override methods on a view collection instance.
312 */
313
314Templates.prototype.extendViews = function(views, options) {
315 plugin.views(this, views, options);
316 return this;
317};
318
319/**
320 * Decorate or override methods on a view collection instance.
321 */
322
323Templates.prototype.extendList = function(views, options) {
324 plugin.list(this, views, options);
325 return this;
326};
327
328/**
329 * Resolve the name of the layout to use for `view`
330 */
331
332Templates.prototype.resolveLayout = function(view, options) {
333 debug('resolving layout for "%s"', view.key);
334 var opts = Object.assign({}, options);
335
336 if (!utils.isPartial(view) && typeof view.layout === 'undefined') {
337 if (view.options && view.options.collection) {
338 var collection = this[view.options.collection];
339 var layout = collection.resolveLayout(view);
340 if (typeof layout === 'undefined') {
341 layout = opts.layout || this.option('layout');
342 }
343 return layout;
344 }
345 }
346 return view.layout;
347};
348
349/**
350 * Expose static `setup` method for providing access to an
351 * instance before any other code is run.
352 *
353 * ```js
354 * function App(options) {
355 * Templates.call(this, options);
356 * Templates.setup(this);
357 * }
358 * Templates.extend(App);
359 * ```
360 * @param {Object} `app` Application instance
361 * @param {String} `name` Optionally pass the constructor name to use.
362 * @return {undefined}
363 * @api public
364 */
365
366Templates.setup = function(app, name) {
367 var setup = app.options['init' + name || app.constructor.name];
368 if (typeof setup === 'function') {
369 setup.call(app, app, app.options);
370 }
371};
372
373/**
374 * Expose constructors as static methods.
375 */
376
377Templates.Base = Base;
378Templates.Collection = lib.collection;
379Templates.List = lib.list;
380Templates.Group = lib.group;
381Templates.Views = lib.views;
382Templates.Item = lib.item;
383Templates.View = lib.view;
384
385/**
386 * Expose properties for unit tests
387 */
388
389utils.define(Templates, 'utils', utils);
390utils.define(Templates, '_', { lib: lib, plugin: plugin });