├── .gitignore
├── .jshintrc
├── .travis.yml
├── .versions
├── LICENSE
├── README.md
├── lib
├── hooks.js
├── template-clear-event-maps.js
├── template-copy-as.js
├── template-for-each-instance.js
├── template-for-each.js
├── template-global-hooks.js
├── template-hooks.js
├── template-inherits-events-from.js
├── template-inherits-helpers-from.js
├── template-inherits-hooks-from.js
├── template-instance-get.js
├── template-instance-parent.js
├── template-instance-set.js
├── template-parent-data-function.js
├── template-register-helpers.js
└── template-replaces.js
├── package.js
├── tests.html
└── tests.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .build*
3 | /packages/
4 | .idea
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "curly": false,
3 | "esnext": true,
4 | "globals": {
5 | "Hooks": true
6 | },
7 | "predef": [
8 | "Blaze",
9 | "EJSON",
10 | "Meteor",
11 | "Package",
12 | "Template",
13 | "Tinytest",
14 | "Tracker",
15 | "_",
16 | "$"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.10"
4 | before_install:
5 | - "curl -L http://git.io/ejPSng | /bin/sh"
--------------------------------------------------------------------------------
/.versions:
--------------------------------------------------------------------------------
1 | aldeed:template-extension@4.1.0
2 | allow-deny@1.0.5
3 | babel-compiler@6.14.1
4 | babel-runtime@1.0.1
5 | base64@1.0.10
6 | binary-heap@1.0.10
7 | blaze@2.1.8
8 | blaze-tools@1.0.9
9 | boilerplate-generator@1.0.11
10 | caching-compiler@1.1.9
11 | caching-html-compiler@1.0.2
12 | callback-hook@1.0.10
13 | check@1.2.4
14 | ddp@1.2.5
15 | ddp-client@1.3.3
16 | ddp-common@1.2.8
17 | ddp-server@1.3.13
18 | deps@1.0.12
19 | diff-sequence@1.0.7
20 | ecmascript@0.6.3
21 | ecmascript-runtime@0.3.15
22 | ejson@1.0.13
23 | geojson-utils@1.0.10
24 | html-tools@1.0.10
25 | htmljs@1.0.10
26 | id-map@1.0.9
27 | jquery@1.11.10
28 | local-test:aldeed:template-extension@4.1.0
29 | logging@1.1.17
30 | meteor@1.6.1
31 | minifiers@1.1.7
32 | minimongo@1.0.20
33 | modules@0.7.9
34 | modules-runtime@0.7.9
35 | mongo@1.1.15
36 | mongo-id@1.0.6
37 | npm-mongo@2.2.16_1
38 | observe-sequence@1.0.15
39 | ordered-dict@1.0.9
40 | promise@0.8.8
41 | random@1.0.10
42 | reactive-var@1.0.11
43 | retry@1.0.9
44 | routepolicy@1.0.12
45 | spacebars@1.0.12
46 | spacebars-compiler@1.0.12
47 | templating@1.1.5
48 | templating-tools@1.0.0
49 | tinytest@1.0.12
50 | tracker@1.1.2
51 | ui@1.0.11
52 | underscore@1.0.10
53 | webapp@1.3.13
54 | webapp-hashing@1.0.9
55 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014-2017 Eric Dobbertin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/aldeed/meteor-template-extension)
2 |
3 | template-extension
4 | =========================
5 |
6 | A smart package for Meteor that allows you to:
7 |
8 | * iterate over all defined templates easily.
9 | * attach multiple created/rendered/destroyed hooks to a template.
10 | * attach a created/rendered/destroyed hook to all templates.
11 | * override a template but keep its helpers and events.
12 | * inherit the helpers from another template.
13 | * inherit the events from another template.
14 | * extend abstract templates and overwrite their events/helpers.
15 | * use `template.parent(numLevels, includeBlockHelpers)` to access a parent template instance.
16 | * use `template.get(propName)` to get the value of the first property named `propName` on the current or an ancestor template instance.
17 | * use `template.set(propName, value)` to set the value of the first property named `propName` on the current or an ancestor template instance.
18 | * pass a function to `Template.parentData(fun)` to get the first data context which passes the test.
19 |
20 |
21 |
22 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
23 |
24 | - [Installation](#installation)
25 | - [Compatibility](#compatibility)
26 | - [Template.forEach(callback)](#templateforeachcallback)
27 | - [Template.forEachCurrentlyRenderedInstance(callback)](#templateforeachcurrentlyrenderedinstancecallback)
28 | - [Template.onCreated / Template.onRendered / Template.onDestroyed](#templateoncreated--templateonrendered--templateondestroyed)
29 | - [hooks(options)](#hooksoptions)
30 | - [replaces(templateName)](#replacestemplatename)
31 | - [inheritsHelpersFrom(templateName), inheritsEventsFrom(templateName), and inheritsHooksFrom(templateName)](#inheritshelpersfromtemplatename-inheritseventsfromtemplatename-and-inheritshooksfromtemplatename)
32 | - [clearEventMaps()](#cleareventmaps)
33 | - [copyAs(newTemplateName)](#copyasnewtemplatename)
34 | - [templateInstance.parent(numLevels, includeBlockHelpers)](#templateinstanceparentnumlevels-includeblockhelpers)
35 | - [templateInstance.parent(selector, includeBlockHelpers)](#templateinstanceparentselector-includeblockhelpers)
36 | - [templateInstance.get(fieldName)](#templateinstancegetfieldname)
37 | - [Template.parentData(fun)](#templateparentdatafun)
38 | - [Contributors](#contributors)
39 |
40 |
41 |
42 | ## Installation
43 |
44 | ```bash
45 | $ meteor add aldeed:template-extension
46 | ```
47 |
48 | ## Compatibility
49 |
50 | - Use a 3.x.x release with Meteor 1.0.x or Meteor 1.1.x
51 | - Use a 4.x.x release with Meteor 1.2+
52 |
53 | ## Template.forEach(callback)
54 |
55 | Call `callback` once for each defined template. Generally, you'll want to call this in a `Meteor.startup` function or sometime after all templates have been loaded.
56 |
57 | ## Template.forEachCurrentlyRenderedInstance(callback)
58 |
59 | Call `callback` once for each template instance that is currently rendered.
60 |
61 | ## Template.onCreated / Template.onRendered / Template.onDestroyed
62 |
63 | Run a function whenever *any* template is created/rendered/destroyed.
64 |
65 | ```js
66 | Template.onRendered(function () {
67 | // Initialize all datepicker inputs whenever any template is rendered
68 | this.$('.datepicker').datepicker();
69 | });
70 | ```
71 |
72 | ## hooks(options)
73 |
74 | An alternative syntax to `onCreated`, `onRendered`, and `onDestroyed`.
75 |
76 | ```js
77 | Template.foo.hooks({
78 | created() {
79 | console.log("foo created");
80 | },
81 | rendered() {
82 | console.log("foo rendered");
83 | },
84 | destroyed() {
85 | console.log("foo destroyed");
86 | },
87 | });
88 | ```
89 |
90 | ## replaces(templateName)
91 |
92 | *html*
93 |
94 | ```html
95 |
96 | {{> foo}}
97 |
98 |
99 |
100 | {{bar}}
101 |
102 |
103 |
104 |
105 | {{bar}} 2
106 |
107 |
108 | ```
109 |
110 | *client.js*
111 |
112 | ```js
113 | Template.foo.helpers({
114 | bar() {
115 | return "TEST";
116 | },
117 | });
118 |
119 | Template.foo.events({
120 | 'click button'(event, template) {
121 | console.log("foo button clicked");
122 | },
123 | });
124 |
125 | Template.foo2.replaces("foo");
126 | ```
127 |
128 | Whenever `{{> foo}}` is used, the contents of the `foo2` template will be shown instead. The `bar` helper defined on "foo" will be used to resolve `{{bar}}`. Clicking the button will still fire the event defined on "foo".
129 |
130 | This is useful when a package you are using defines a template for something and you'd like to adjust some things in that template for your app.
131 |
132 | NOTE: This simply swaps the render function. Helpers, callbacks, and events assigned to `foo2` will not fire when `{{> foo}}` is used. Only the `foo` helpers, callbacks, and events are used.
133 |
134 | ## inheritsHelpersFrom(templateName), inheritsEventsFrom(templateName), and inheritsHooksFrom(templateName)
135 |
136 | *html*
137 |
138 | ```html
139 |
140 | {{> foo}}
141 | {{> foo2}}
142 |
143 |
144 |
145 | {{bar}}
146 |
147 |
148 |
149 |
150 | {{bar}} 2
151 |
152 |
153 | ```
154 |
155 | *client.js*
156 |
157 | ```js
158 | Template.foo.helpers({
159 | bar() {
160 | return 'TEST';
161 | },
162 | });
163 |
164 | Template.foo.events({
165 | 'click button'(event, template) {
166 | console.log('foo button clicked');
167 | },
168 | });
169 |
170 | Template.foo.onCreated(function onCreated() {
171 | console.log('foo created');
172 | });
173 |
174 | Template.foo2.inheritsHelpersFrom('foo');
175 | Template.foo2.inheritsEventsFrom('foo');
176 | Template.foo2.inheritsHooksFrom('foo');
177 | ```
178 |
179 | In this example, both templates are rendered. Both use the `bar` helper defined on "foo" to resolve `{{bar}}`. Both fire the click event defined on "foo". The "foo2" template will inherit the `foo.created` callback and log 'foo' to the console upon creation.
180 |
181 | Additionally, these methods can be called with an array of template names: `Template.foo2.inheritsHooksFrom(['foo', 'bar', 'baz']);`
182 |
183 | Because of the different ways in which helpers, events, and hooks are stored, the ability to override them is different. Helpers are stored internally as a hash, which means that if you inherit a "name" helper and then call `myTemplate.helpers({ name: ... })`, the new "name" helper will overwrite the old helper. By contrast, events and hooks are stored in arrays, which means that you cannot override them easily. You may be able to achieve what you want with `clearEventMaps()` or looping through the events or hooks to somehow remove those you don't want.
184 |
185 | ## clearEventMaps()
186 |
187 | After `Template.foo.events({...})` has been called one or more times, you can remove all the added event handlers by calling `Template.foo.clearEventMaps()`
188 |
189 | ## copyAs(newTemplateName)
190 |
191 | *html*
192 |
193 | ```html
194 |
195 | {{> foo}}
196 | {{> bar}}
197 |
198 |
199 |
200 | {{#each images}}
201 |
202 | {{/each}}
203 |
204 | ```
205 |
206 | *client.js*
207 |
208 | ```js
209 | Template.abstract_foo.helpers({
210 | images() {
211 | return [];
212 | }
213 | });
214 |
215 | Template.abstract_foo.copyAs(['foo', 'bar']);
216 |
217 | Template.foo.helpers({
218 | images() {
219 | return Meteor.call('getFooImages');
220 | }
221 | });
222 |
223 | Template.bar.helpers({
224 | images() {
225 | return Meteor.call('getBarImages');
226 | }
227 | });
228 | ```
229 |
230 | In this example, we defined "foo" and "bar" templates that get their HTML markup, events, and helpers from a base template, `abstract_foo`. We then override the `images` helper for "foo" and "bar" to provide template-specific images provided by different Meteor methods. Template.template.copyAs can accept either single template name (in string form), or an array of template names as shown in the above example.
231 |
232 | Calling `copyAs` is the same as calling `inheritsHelpersFrom`, `inheritsEventsFrom`, and `inheritsHooksFrom` and also copying the render function.
233 |
234 | If copyAs is invoked with a string, it returns the newly created template.
235 |
236 | If copyAs is invoked with an array, it returns an array of newly created templates.
237 |
238 | ## templateInstance.parent(numLevels, includeBlockHelpers)
239 |
240 | On template instances you can now use `parent(numLevels)` method to access a parent template instance.
241 | `numLevels` is the number of levels beyond the current template instance to look. Defaults to 1.
242 | By default block helper template instances are skipped, but if `includeBlockHelpers` is set to true,
243 | they are not.
244 |
245 | ## templateInstance.parent(selector, includeBlockHelpers)
246 |
247 | You can also call `templateInstance.parent` with function as first argument. The function, known as selector, will be
248 | passed the current template being traversed. If it returns true, we return the template that is currently being traversed,
249 | otherwise, we traverse further up. We traverse up until there are no more templates, in which case, we return null.
250 |
251 | ## templateInstance.get(fieldName)
252 |
253 | To not have to hard-code the number of levels when accessing parent template instances you can use
254 | `get(fieldName)` method which returns the value of the first property named `fieldName` on the current
255 | or ancestor template instances, traversed in the hierarchical order. It traverses block helper template
256 | instances as well. This pattern makes it easier to refactor templates without having to worry about
257 | changes to number of levels.
258 |
259 | ## Template.parentData(fun)
260 |
261 | `Template.parentData` now accepts a function which will be used to test each data context when traversing
262 | them in the hierarchical order, returning the first data context for which the test function returns `true`.
263 | This is useful so that you do not have to hard-code the number of levels when accessing parent data contexts,
264 | but you can use a more logic-oriented approach. For example, search for the first data context which contains
265 | a given field. Or:
266 |
267 | ```js
268 | var data = Template.parentData(function (data) {return data instanceof MyDocument;});
269 | ```
270 |
271 | ## Contributing
272 |
273 | When submitting a pull request, please add tests for any new features and make sure that all existing tests still pass.
274 |
275 | ```bash
276 | $ meteor test-packages ./
277 | ```
278 |
279 | ### Contributors
280 |
281 | * @aldeed
282 | * @grabbou
283 | * @mitar
284 | * @jgladch
285 | * @merlinpatt
286 | * @JoeyAndres
287 |
--------------------------------------------------------------------------------
/lib/hooks.js:
--------------------------------------------------------------------------------
1 | Hooks = {
2 | global: {
3 | created: [],
4 | rendered: [],
5 | destroyed: []
6 | },
7 | master: {
8 | created: function () {
9 | Hooks.runGlobal('created', this, arguments);
10 | },
11 | rendered: function () {
12 | Hooks.runGlobal('rendered', this, arguments);
13 | },
14 | destroyed: function () {
15 | Hooks.runGlobal('destroyed', this, arguments);
16 | }
17 | }
18 | };
19 |
20 | Hooks.addGlobal = (template) => {
21 | // For each hookType, define the hooks for this template.
22 | // Since we might call this multiple times from startup code
23 | // and other functions, make sure we do it only once.
24 | // Doing it twice would create an infinite loop of self-calling
25 | // hooks.
26 | if (!template._hasTemplateExtensionMasterHooks) {
27 | template.onCreated(Hooks.master.created);
28 | template.onRendered(Hooks.master.rendered);
29 | template.onDestroyed(Hooks.master.destroyed);
30 |
31 | template._hasTemplateExtensionMasterHooks = true;
32 | }
33 | };
34 |
35 | Hooks.runGlobal = (type, template, args) => {
36 | for (let hook of Hooks.global[type]) {
37 | hook.apply(template, args);
38 | }
39 | };
40 |
--------------------------------------------------------------------------------
/lib/template-clear-event-maps.js:
--------------------------------------------------------------------------------
1 | Template.prototype.clearEventMaps = function clearEventMaps() {
2 | this.__eventMaps = [];
3 | };
4 |
--------------------------------------------------------------------------------
/lib/template-copy-as.js:
--------------------------------------------------------------------------------
1 | Template.prototype.copyAs = function copyAs(newTemplateName) {
2 | var self = this;
3 |
4 | function createNewTemplate(templateName) {
5 | var newTemplate =
6 | Template[templateName] = new Template(`Template.${templateName}`, self.renderFunction);
7 |
8 | newTemplate.inheritsHelpersFrom(self);
9 | newTemplate.inheritsEventsFrom(self);
10 | newTemplate.inheritsHooksFrom(self);
11 |
12 | return newTemplate;
13 | }
14 |
15 | // Check if newTemplateName is an array
16 | if (Array.isArray(newTemplateName)) {
17 | const result = [];
18 | for (let name of newTemplateName) {
19 | result.push(createNewTemplate(name));
20 | }
21 | return result;
22 | }
23 |
24 | // newTemplateName is a string
25 | return createNewTemplate(newTemplateName);
26 | };
27 |
--------------------------------------------------------------------------------
/lib/template-for-each-instance.js:
--------------------------------------------------------------------------------
1 | Template._renderedInstances = [];
2 |
3 | Template.onRendered(function onRendered() {
4 | Template._renderedInstances.push(this);
5 | });
6 |
7 | Template.onDestroyed(function onDestroyed() {
8 | var i = Template._renderedInstances.indexOf(this);
9 | if (i > -1) Template._renderedInstances.splice(i, 1);
10 | });
11 |
12 | Template.forEachCurrentlyRenderedInstance = (func) => {
13 | Template._renderedInstances.forEach(func);
14 | };
15 |
--------------------------------------------------------------------------------
/lib/template-for-each.js:
--------------------------------------------------------------------------------
1 | Template.forEach = (callback) => {
2 | // for some reason we get the "body" template twice when looping, so
3 | // we track that and only call the callback once.
4 | var alreadyDidBody = false;
5 | for (var t in Template) {
6 | if (Template.hasOwnProperty(t)) {
7 | var tmpl = Template[t];
8 | if (Blaze.isTemplate(tmpl)) {
9 | let name = tmpl.viewName;
10 | if (name === 'body') {
11 | if (!alreadyDidBody) {
12 | alreadyDidBody = true;
13 | callback(tmpl);
14 | }
15 | } else if (name !== 'Template.__dynamic' && name !== 'Template.__dynamicWithDataContext') {
16 | callback(tmpl);
17 | }
18 | }
19 | }
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/lib/template-global-hooks.js:
--------------------------------------------------------------------------------
1 | // Setup for multiple hooks support
2 | // We assume that no other code will be directly defining
3 | // a hook once the client has started.
4 | Meteor.startup(() => {
5 | Template.forEach((template) => {
6 | Hooks.addGlobal(template);
7 | });
8 | });
9 |
10 | Template.onCreated = (hook) => {
11 | Hooks.global.created.push(hook);
12 | };
13 |
14 | Template.onRendered = (hook) => {
15 | Hooks.global.rendered.push(hook);
16 | };
17 |
18 | Template.onDestroyed = (hook) => {
19 | Hooks.global.destroyed.push(hook);
20 | };
21 |
--------------------------------------------------------------------------------
/lib/template-hooks.js:
--------------------------------------------------------------------------------
1 | Template.prototype.hooks = function (hooks) {
2 | if (!hooks || typeof hooks !== 'object') {
3 | throw new Error('hooks argument must be an object with created, rendered, and/or destroyed properties, each set to a function');
4 | }
5 |
6 | if (typeof hooks.created === 'function') this.onCreated(hooks.created);
7 | if (typeof hooks.rendered === 'function') this.onRendered(hooks.rendered);
8 | if (typeof hooks.destroyed === 'function') this.onDestroyed(hooks.destroyed);
9 | };
10 |
--------------------------------------------------------------------------------
/lib/template-inherits-events-from.js:
--------------------------------------------------------------------------------
1 | Template.prototype.inheritsEventsFrom = function inheritsEventsFrom(otherTemplate) {
2 | var self = this;
3 |
4 | self.__eventMaps = self.__eventMaps || [];
5 |
6 | function inheritEvents(template) {
7 | // String template names can be provided and template object is looked up
8 | if (typeof template === 'string') template = Template[template];
9 | if (!template) return;
10 |
11 | self.__eventMaps = self.__eventMaps.concat(template.__eventMaps);
12 | }
13 |
14 | // Accept an array as otherTemplate argument
15 | if (Array.isArray(otherTemplate)) {
16 | otherTemplate.forEach(inheritEvents);
17 | return;
18 | }
19 |
20 | // otherTemplate is a string
21 | inheritEvents(otherTemplate);
22 | };
23 |
--------------------------------------------------------------------------------
/lib/template-inherits-helpers-from.js:
--------------------------------------------------------------------------------
1 | Template.prototype.inheritsHelpersFrom = function inheritsHelpersFrom(otherTemplate) {
2 | const inheritHelpers = template => {
3 | // String template names can be provided and template object is looked up
4 | if (typeof template === 'string') template = Template[template];
5 | if (!template) return;
6 |
7 | const inheritedHelpers = {};
8 | for (const name in template.__helpers) {
9 | if (template.__helpers.hasOwnProperty(name)) {
10 | if (name.charAt(0) === ' ') {
11 | inheritedHelpers[name.slice(1)] = template.__helpers[name];
12 | }
13 | }
14 | }
15 |
16 | this.helpers(inheritedHelpers);
17 | };
18 |
19 | // Accept an array as otherTemplate argument
20 | if (Array.isArray(otherTemplate)) {
21 | otherTemplate.forEach(inheritHelpers);
22 | return;
23 | }
24 |
25 | // otherTemplate is a string
26 | inheritHelpers(otherTemplate);
27 | };
28 |
--------------------------------------------------------------------------------
/lib/template-inherits-hooks-from.js:
--------------------------------------------------------------------------------
1 | Template.prototype.inheritsHooksFrom = function inheritsHooksFrom(otherTemplate) {
2 | var self = this;
3 |
4 | function inheritHooks(template) {
5 | // String template names can be provided and template object is looked up
6 | if (typeof template === 'string') template = Template[template];
7 | if (!template) return;
8 |
9 | // For this to work properly, need to ensure that we've defined
10 | // the global hook hook for the other template already.
11 | Hooks.addGlobal(template);
12 |
13 | for (let hook of template._callbacks.created) {
14 | // Don't copy the master hook because every template already has it
15 | if (hook === Hooks.master.created) continue;
16 | self.onCreated(hook);
17 | }
18 |
19 | for (let hook of template._callbacks.rendered) {
20 | // Don't copy the master hook because every template already has it
21 | if (hook === Hooks.master.rendered) continue;
22 | self.onRendered(hook);
23 | }
24 |
25 | for (let hook of template._callbacks.destroyed) {
26 | // Don't copy the master hook because every template already has it
27 | if (hook === Hooks.master.destroyed) continue;
28 | self.onDestroyed(hook);
29 | }
30 | }
31 |
32 | // Accept an array as otherTemplate argument
33 | if (Array.isArray(otherTemplate)) {
34 | otherTemplate.forEach(inheritHooks);
35 | return;
36 | }
37 |
38 | // otherTemplate is a string
39 | inheritHooks(otherTemplate);
40 | };
41 |
--------------------------------------------------------------------------------
/lib/template-instance-get.js:
--------------------------------------------------------------------------------
1 | // Allow easy access to a template instance field when you do not know exactly
2 | // on which instance (this, or parent, or parent's parent, ...) a field is defined.
3 | // This allows easy restructuring of templates in HTML, moving things to included
4 | // templates without having to change everywhere in the code instance levels.
5 | // It also allows different structures of templates, when once template is included
6 | // at one level, and some other time at another. Levels do not matter anymore, just
7 | // that the field exists somewhere.
8 | Blaze.TemplateInstance.prototype.get = function get(fieldName) {
9 | var template = this;
10 |
11 | while (template) {
12 | if (fieldName in template) return template[fieldName];
13 | template = template.parent(1, true);
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/lib/template-instance-parent.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @param selector Can be a height or a function.
3 | * Height. The number of levels beyond the current template instance to look.
4 | * Defaults to 0.
5 | *
6 | * Function that is given a template as we traverse up the template true. It is passed the template
7 | * currently being traversed. If it returns true, then that template is returned, otherwise next is used. This
8 | * is done till we hit a template with no parent, in which case null is returned.
9 | * @param includeBlockHelpers True to include block helpers.
10 | * @returns {*}
11 | */
12 | Blaze.TemplateInstance.prototype.parent = function parent(selector, includeBlockHelpers) {
13 | let template = null;
14 | if (isFinite(selector) || !selector) {
15 | // If height is null, undefined, or 0, we default to 1, the first parent.
16 | const height = !selector ? 1 : selector;
17 | template = parentByHeight.call(this, height, includeBlockHelpers);
18 | } else if (typeof selector === 'function') {
19 | template = parentByHeight.call(this, Number.MAX_VALUE, includeBlockHelpers, selector);
20 | } else {
21 | throw 'template:children Template.parent() is given an invalid selector type.';
22 | }
23 |
24 | return template;
25 | };
26 |
27 | // Access parent template instance. "height" is the number of levels beyond the
28 | // current template instance to look. By default block helper template instances
29 | // are skipped, but if "includeBlockHelpers" is set to true, they are not.
30 | // See https://github.com/meteor/meteor/issues/3071
31 | function parentByHeight(height, includeBlockHelpers, selector) {
32 | var i = 0;
33 | var template = this;
34 | while (i < height && template) {
35 | var view = parentView(template.view, includeBlockHelpers);
36 | // We skip contentBlock views which are injected by Meteor when using
37 | // block helpers (in addition to block helper view). This matches more
38 | // the visual structure of templates and not the internal implementation.
39 | while (view && (!view.template || view.name === '(contentBlock)' || view.name === '(elseBlock)')) {
40 | view = parentView(view, includeBlockHelpers);
41 | }
42 | if (!view) return null;
43 |
44 | // Body view has template field, but not templateInstance,
45 | // which more or less signals that we reached the top.
46 | template = typeof view.templateInstance === 'function' ? view.templateInstance() : null;
47 |
48 | if (!!selector && !!selector(template)) { return template; }
49 |
50 | i++;
51 | }
52 | return template;
53 | }
54 |
55 | function parentView(view, includeBlockHelpers) {
56 | if (includeBlockHelpers) return view.originalParentView || view.parentView;
57 | return view.parentView;
58 | }
59 |
60 |
--------------------------------------------------------------------------------
/lib/template-instance-set.js:
--------------------------------------------------------------------------------
1 | // Allow easy setting of template instance's parent (or ancestor) field
2 |
3 | Blaze.TemplateInstance.prototype.set = function set(fieldName, value) {
4 | var template = this;
5 |
6 | while (template) {
7 | if (fieldName in template) {
8 | var previousValue = template[fieldName];
9 | if (Package['reactive-var'] && previousValue instanceof Package['reactive-var'].ReactiveVar) {
10 | template[fieldName].set(value);
11 | } else {
12 | template[fieldName] = value;
13 | }
14 | return previousValue;
15 | }
16 | template = template.parent(1, true);
17 | }
18 | };
19 |
--------------------------------------------------------------------------------
/lib/template-parent-data-function.js:
--------------------------------------------------------------------------------
1 | // Allow to specify a function to test parent data for at various
2 | // levels, instead of specifying a fixed number of levels to traverse.
3 | var originalParentData = Blaze._parentData;
4 | Blaze._parentData = function (height, _functionWrapped) {
5 | // If height is not a function, simply call original implementation.
6 | if (typeof height !== 'function') return originalParentData(height, _functionWrapped);
7 |
8 | var theWith = Blaze.getView('with');
9 | var test = () => { return height(theWith.dataVar.get()); };
10 | while (theWith) {
11 | if (Tracker.nonreactive(test)) break;
12 | theWith = Blaze.getView(theWith, 'with');
13 | }
14 |
15 | // _functionWrapped is internal and will not be
16 | // specified with non numeric height, so we ignore it.
17 | if (!theWith) return null;
18 | // This registers a Tracker dependency.
19 | return theWith.dataVar.get();
20 | };
21 |
22 | Template.parentData = Blaze._parentData;
23 |
--------------------------------------------------------------------------------
/lib/template-register-helpers.js:
--------------------------------------------------------------------------------
1 | Template.registerHelpers = (helpers) => {
2 | if (!helpers) return;
3 |
4 | for (let name in helpers) {
5 | if (helpers.hasOwnProperty(name)) {
6 | Template.registerHelper(name, helpers[name]);
7 | }
8 | }
9 | };
10 |
--------------------------------------------------------------------------------
/lib/template-replaces.js:
--------------------------------------------------------------------------------
1 | Template.prototype.replaces = function replaces(otherTemplate) {
2 | var self = this;
3 |
4 | function replaceRender(template) {
5 | // String template names can be provided and template object is looked up
6 | if (typeof template === 'string') template = Template[template];
7 | if (!template) return;
8 |
9 | template.renderFunction = self.renderFunction;
10 | }
11 |
12 | // Accept an array as otherTemplate argument
13 | if (Array.isArray(otherTemplate)) {
14 | otherTemplate.forEach(replaceRender);
15 | return;
16 | }
17 |
18 | replaceRender(otherTemplate);
19 | };
20 |
--------------------------------------------------------------------------------
/package.js:
--------------------------------------------------------------------------------
1 | Package.describe({
2 | name: 'aldeed:template-extension',
3 | summary: 'Adds template features currently missing from the templating package',
4 | version: '4.1.0',
5 | git: 'https://github.com/aldeed/meteor-template-extension.git'
6 | });
7 |
8 | Package.onUse(function(api) {
9 | api.versionsFrom('METEOR@1.2');
10 |
11 | api.use([
12 | 'ecmascript',
13 | 'templating',
14 | 'blaze',
15 | 'tracker',
16 | ], 'client');
17 |
18 | api.addFiles([
19 | 'lib/hooks.js',
20 | 'lib/template-for-each.js',
21 | 'lib/template-hooks.js',
22 | 'lib/template-global-hooks.js',
23 | 'lib/template-for-each-instance.js',
24 | 'lib/template-inherits-events-from.js',
25 | 'lib/template-inherits-helpers-from.js',
26 | 'lib/template-inherits-hooks-from.js',
27 | 'lib/template-register-helpers.js',
28 | 'lib/template-replaces.js',
29 | 'lib/template-clear-event-maps.js',
30 | 'lib/template-copy-as.js',
31 | 'lib/template-instance-parent.js',
32 | 'lib/template-instance-get.js',
33 | 'lib/template-instance-set.js',
34 | 'lib/template-parent-data-function.js'
35 | ], 'client');
36 | });
37 |
38 | Package.onTest(function(api) {
39 | api.use([
40 | 'aldeed:template-extension',
41 | 'jquery',
42 | 'templating',
43 | 'tinytest',
44 | 'tracker',
45 | 'ejson',
46 | 'underscore',
47 | 'reactive-var',
48 | ], 'client');
49 |
50 | api.addFiles([
51 | 'tests.html',
52 | 'tests.js'
53 | ], 'client');
54 | });
55 |
--------------------------------------------------------------------------------
/tests.html:
--------------------------------------------------------------------------------
1 |
2 | {{> testTemplate1 data}}
3 |
4 |
5 |
6 | {{#noop}}{{> testTemplate2 data}}{{/noop}}
7 |
8 |
9 |
10 | {{testInstanceGet}}{{testInstanceParent}}{{testData}}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |