mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
520 lines
16 KiB
JavaScript
520 lines
16 KiB
JavaScript
// [new] Blaze.Template([viewName], renderFunction)
|
|
//
|
|
// `Blaze.Template` is the class of templates, like `Template.foo` in
|
|
// Meteor, which is `instanceof Template`.
|
|
//
|
|
// `viewKind` is a string that looks like "Template.foo" for templates
|
|
// defined by the compiler.
|
|
|
|
/**
|
|
* @class
|
|
* @summary Constructor for a Template, which is used to construct Views with particular name and content.
|
|
* @locus Client
|
|
* @param {String} [viewName] Optional. A name for Views constructed by this Template. See [`view.name`](#view_name).
|
|
* @param {Function} renderFunction A function that returns [*renderable content*](#renderable_content). This function is used as the `renderFunction` for Views constructed by this Template.
|
|
*/
|
|
Blaze.Template = function (viewName, renderFunction) {
|
|
if (! (this instanceof Blaze.Template))
|
|
// called without `new`
|
|
return new Blaze.Template(viewName, renderFunction);
|
|
|
|
if (typeof viewName === 'function') {
|
|
// omitted "viewName" argument
|
|
renderFunction = viewName;
|
|
viewName = '';
|
|
}
|
|
if (typeof viewName !== 'string')
|
|
throw new Error("viewName must be a String (or omitted)");
|
|
if (typeof renderFunction !== 'function')
|
|
throw new Error("renderFunction must be a function");
|
|
|
|
this.viewName = viewName;
|
|
this.renderFunction = renderFunction;
|
|
|
|
this.__helpers = new HelperMap;
|
|
this.__eventMaps = [];
|
|
|
|
this._callbacks = {
|
|
created: [],
|
|
rendered: [],
|
|
destroyed: []
|
|
};
|
|
};
|
|
var Template = Blaze.Template;
|
|
|
|
var HelperMap = function () {};
|
|
HelperMap.prototype.get = function (name) {
|
|
return this[' '+name];
|
|
};
|
|
HelperMap.prototype.set = function (name, helper) {
|
|
this[' '+name] = helper;
|
|
};
|
|
HelperMap.prototype.has = function (name) {
|
|
return (' '+name) in this;
|
|
};
|
|
|
|
/**
|
|
* @summary Returns true if `value` is a template object like `Template.myTemplate`.
|
|
* @locus Client
|
|
* @param {Any} value The value to test.
|
|
*/
|
|
Blaze.isTemplate = function (t) {
|
|
return (t instanceof Blaze.Template);
|
|
};
|
|
|
|
/**
|
|
* @name onCreated
|
|
* @instance
|
|
* @memberOf Template
|
|
* @summary Register a function to be called when an instance of this template is created.
|
|
* @param {Function} callback A function to be added as a callback.
|
|
* @locus Client
|
|
*/
|
|
Template.prototype.onCreated = function (cb) {
|
|
this._callbacks.created.push(cb);
|
|
};
|
|
|
|
/**
|
|
* @name onRendered
|
|
* @instance
|
|
* @memberOf Template
|
|
* @summary Register a function to be called when an instance of this template is inserted into the DOM.
|
|
* @param {Function} callback A function to be added as a callback.
|
|
* @locus Client
|
|
*/
|
|
Template.prototype.onRendered = function (cb) {
|
|
this._callbacks.rendered.push(cb);
|
|
};
|
|
|
|
/**
|
|
* @name onDestroyed
|
|
* @instance
|
|
* @memberOf Template
|
|
* @summary Register a function to be called when an instance of this template is removed from the DOM and destroyed.
|
|
* @param {Function} callback A function to be added as a callback.
|
|
* @locus Client
|
|
*/
|
|
Template.prototype.onDestroyed = function (cb) {
|
|
this._callbacks.destroyed.push(cb);
|
|
};
|
|
|
|
Template.prototype._getCallbacks = function (which) {
|
|
var self = this;
|
|
var callbacks = self[which] ? [self[which]] : [];
|
|
// Fire all callbacks added with the new API (Template.onRendered())
|
|
// as well as the old-style callback (e.g. Template.rendered) for
|
|
// backwards-compatibility.
|
|
callbacks = callbacks.concat(self._callbacks[which]);
|
|
return callbacks;
|
|
};
|
|
|
|
var fireCallbacks = function (callbacks, template) {
|
|
Template._withTemplateInstanceFunc(
|
|
function () { return template; },
|
|
function () {
|
|
for (var i = 0, N = callbacks.length; i < N; i++) {
|
|
callbacks[i].call(template);
|
|
}
|
|
});
|
|
};
|
|
|
|
Template.prototype.constructView = function (contentFunc, elseFunc) {
|
|
var self = this;
|
|
var view = Blaze.View(self.viewName, self.renderFunction);
|
|
view.template = self;
|
|
|
|
view.templateContentBlock = (
|
|
contentFunc ? new Template('(contentBlock)', contentFunc) : null);
|
|
view.templateElseBlock = (
|
|
elseFunc ? new Template('(elseBlock)', elseFunc) : null);
|
|
|
|
if (self.__eventMaps || typeof self.events === 'object') {
|
|
view._onViewRendered(function () {
|
|
if (view.renderCount !== 1)
|
|
return;
|
|
|
|
if (! self.__eventMaps.length && typeof self.events === "object") {
|
|
// Provide limited back-compat support for `.events = {...}`
|
|
// syntax. Pass `template.events` to the original `.events(...)`
|
|
// function. This code must run only once per template, in
|
|
// order to not bind the handlers more than once, which is
|
|
// ensured by the fact that we only do this when `__eventMaps`
|
|
// is falsy, and we cause it to be set now.
|
|
Template.prototype.events.call(self, self.events);
|
|
}
|
|
|
|
_.each(self.__eventMaps, function (m) {
|
|
Blaze._addEventMap(view, m, view);
|
|
});
|
|
});
|
|
}
|
|
|
|
view._templateInstance = new Blaze.TemplateInstance(view);
|
|
view.templateInstance = function () {
|
|
// Update data, firstNode, and lastNode, and return the TemplateInstance
|
|
// object.
|
|
var inst = view._templateInstance;
|
|
|
|
/**
|
|
* @instance
|
|
* @memberOf Blaze.TemplateInstance
|
|
* @name data
|
|
* @summary The data context of this instance's latest invocation.
|
|
* @locus Client
|
|
*/
|
|
inst.data = Blaze.getData(view);
|
|
|
|
if (view._domrange && !view.isDestroyed) {
|
|
inst.firstNode = view._domrange.firstNode();
|
|
inst.lastNode = view._domrange.lastNode();
|
|
} else {
|
|
// on 'created' or 'destroyed' callbacks we don't have a DomRange
|
|
inst.firstNode = null;
|
|
inst.lastNode = null;
|
|
}
|
|
|
|
return inst;
|
|
};
|
|
|
|
/**
|
|
* @name created
|
|
* @instance
|
|
* @memberOf Template
|
|
* @summary Provide a callback when an instance of a template is created.
|
|
* @locus Client
|
|
* @deprecated in 1.1
|
|
*/
|
|
// To avoid situations when new callbacks are added in between view
|
|
// instantiation and event being fired, decide on all callbacks to fire
|
|
// immediately and then fire them on the event.
|
|
var createdCallbacks = self._getCallbacks('created');
|
|
view.onViewCreated(function () {
|
|
fireCallbacks(createdCallbacks, view.templateInstance());
|
|
});
|
|
|
|
/**
|
|
* @name rendered
|
|
* @instance
|
|
* @memberOf Template
|
|
* @summary Provide a callback when an instance of a template is rendered.
|
|
* @locus Client
|
|
* @deprecated in 1.1
|
|
*/
|
|
var renderedCallbacks = self._getCallbacks('rendered');
|
|
view.onViewReady(function () {
|
|
fireCallbacks(renderedCallbacks, view.templateInstance());
|
|
});
|
|
|
|
/**
|
|
* @name destroyed
|
|
* @instance
|
|
* @memberOf Template
|
|
* @summary Provide a callback when an instance of a template is destroyed.
|
|
* @locus Client
|
|
* @deprecated in 1.1
|
|
*/
|
|
var destroyedCallbacks = self._getCallbacks('destroyed');
|
|
view.onViewDestroyed(function () {
|
|
fireCallbacks(destroyedCallbacks, view.templateInstance());
|
|
});
|
|
|
|
return view;
|
|
};
|
|
|
|
/**
|
|
* @class
|
|
* @summary The class for template instances
|
|
* @param {Blaze.View} view
|
|
* @instanceName template
|
|
*/
|
|
Blaze.TemplateInstance = function (view) {
|
|
if (! (this instanceof Blaze.TemplateInstance))
|
|
// called without `new`
|
|
return new Blaze.TemplateInstance(view);
|
|
|
|
if (! (view instanceof Blaze.View))
|
|
throw new Error("View required");
|
|
|
|
view._templateInstance = this;
|
|
|
|
/**
|
|
* @name view
|
|
* @memberOf Blaze.TemplateInstance
|
|
* @instance
|
|
* @summary The [View](#blaze_view) object for this invocation of the template.
|
|
* @locus Client
|
|
* @type {Blaze.View}
|
|
*/
|
|
this.view = view;
|
|
this.data = null;
|
|
|
|
/**
|
|
* @name firstNode
|
|
* @memberOf Blaze.TemplateInstance
|
|
* @instance
|
|
* @summary The first top-level DOM node in this template instance.
|
|
* @locus Client
|
|
* @type {DOMNode}
|
|
*/
|
|
this.firstNode = null;
|
|
|
|
/**
|
|
* @name lastNode
|
|
* @memberOf Blaze.TemplateInstance
|
|
* @instance
|
|
* @summary The last top-level DOM node in this template instance.
|
|
* @locus Client
|
|
* @type {DOMNode}
|
|
*/
|
|
this.lastNode = null;
|
|
|
|
// This dependency is used to identify state transitions in
|
|
// _subscriptionHandles which could cause the result of
|
|
// TemplateInstance#subscriptionsReady to change. Basically this is triggered
|
|
// whenever a new subscription handle is added or when a subscription handle
|
|
// is removed and they are not ready.
|
|
this._allSubsReadyDep = new Tracker.Dependency();
|
|
this._allSubsReady = false;
|
|
|
|
this._subscriptionHandles = {};
|
|
};
|
|
|
|
/**
|
|
* @summary Find all elements matching `selector` in this template instance, and return them as a JQuery object.
|
|
* @locus Client
|
|
* @param {String} selector The CSS selector to match, scoped to the template contents.
|
|
* @returns {DOMNode[]}
|
|
*/
|
|
Blaze.TemplateInstance.prototype.$ = function (selector) {
|
|
var view = this.view;
|
|
if (! view._domrange)
|
|
throw new Error("Can't use $ on template instance with no DOM");
|
|
return view._domrange.$(selector);
|
|
};
|
|
|
|
/**
|
|
* @summary Find all elements matching `selector` in this template instance.
|
|
* @locus Client
|
|
* @param {String} selector The CSS selector to match, scoped to the template contents.
|
|
* @returns {DOMElement[]}
|
|
*/
|
|
Blaze.TemplateInstance.prototype.findAll = function (selector) {
|
|
return Array.prototype.slice.call(this.$(selector));
|
|
};
|
|
|
|
/**
|
|
* @summary Find one element matching `selector` in this template instance.
|
|
* @locus Client
|
|
* @param {String} selector The CSS selector to match, scoped to the template contents.
|
|
* @returns {DOMElement}
|
|
*/
|
|
Blaze.TemplateInstance.prototype.find = function (selector) {
|
|
var result = this.$(selector);
|
|
return result[0] || null;
|
|
};
|
|
|
|
/**
|
|
* @summary A version of [Tracker.autorun](#tracker_autorun) that is stopped when the template is destroyed.
|
|
* @locus Client
|
|
* @param {Function} runFunc The function to run. It receives one argument: a Tracker.Computation object.
|
|
*/
|
|
Blaze.TemplateInstance.prototype.autorun = function (f) {
|
|
return this.view.autorun(f);
|
|
};
|
|
|
|
/**
|
|
* @summary A version of [Meteor.subscribe](#meteor_subscribe) that is stopped
|
|
* when the template is destroyed.
|
|
* @return {SubscriptionHandle} The subscription handle to the newly made
|
|
* subscription. Call `handle.stop()` to manually stop the subscription, or
|
|
* `handle.ready()` to find out if this particular subscription has loaded all
|
|
* of its inital data.
|
|
* @locus Client
|
|
* @param {String} name Name of the subscription. Matches the name of the
|
|
* server's `publish()` call.
|
|
* @param {Any} [arg1,arg2...] Optional arguments passed to publisher function
|
|
* on server.
|
|
* @param {Function|Object} [callbacks] Optional. May include `onError` and
|
|
* `onReady` callbacks. If a function is passed instead of an object, it is
|
|
* interpreted as an `onReady` callback.
|
|
*/
|
|
Blaze.TemplateInstance.prototype.subscribe = function (/* arguments */) {
|
|
var self = this;
|
|
|
|
var subHandles = self._subscriptionHandles;
|
|
var args = _.toArray(arguments);
|
|
|
|
// Duplicate logic from Meteor.subscribe
|
|
var callbacks = {};
|
|
if (args.length) {
|
|
var lastParam = _.last(args);
|
|
if (_.isFunction(lastParam)) {
|
|
callbacks.onReady = args.pop();
|
|
} else if (lastParam &&
|
|
// XXX COMPAT WITH 1.0.3.1 onError used to exist, but now we use
|
|
// onStop with an error callback instead.
|
|
_.any([lastParam.onReady, lastParam.onError, lastParam.onStop],
|
|
_.isFunction)) {
|
|
callbacks = args.pop();
|
|
}
|
|
}
|
|
|
|
var subHandle;
|
|
var oldStopped = callbacks.onStop;
|
|
callbacks.onStop = function (error) {
|
|
// When the subscription is stopped, remove it from the set of tracked
|
|
// subscriptions to avoid this list growing without bound
|
|
delete subHandles[subHandle.subscriptionId];
|
|
|
|
// Removing a subscription can only change the result of subscriptionsReady
|
|
// if we are not ready (that subscription could be the one blocking us being
|
|
// ready).
|
|
if (! self._allSubsReady) {
|
|
self._allSubsReadyDep.changed();
|
|
}
|
|
|
|
if (oldStopped) {
|
|
oldStopped(error);
|
|
}
|
|
};
|
|
args.push(callbacks);
|
|
|
|
subHandle = self.view.subscribe.call(self.view, args);
|
|
|
|
if (! _.has(subHandles, subHandle.subscriptionId)) {
|
|
subHandles[subHandle.subscriptionId] = subHandle;
|
|
|
|
// Adding a new subscription will always cause us to transition from ready
|
|
// to not ready, but if we are already not ready then this can't make us
|
|
// ready.
|
|
if (self._allSubsReady) {
|
|
self._allSubsReadyDep.changed();
|
|
}
|
|
}
|
|
|
|
return subHandle;
|
|
};
|
|
|
|
/**
|
|
* @summary A reactive function that returns true when all of the subscriptions
|
|
* called with [this.subscribe](#TemplateInstance-subscribe) are ready.
|
|
* @return {Boolean} True if all subscriptions on this template instance are
|
|
* ready.
|
|
*/
|
|
Blaze.TemplateInstance.prototype.subscriptionsReady = function () {
|
|
this._allSubsReadyDep.depend();
|
|
|
|
this._allSubsReady = _.all(this._subscriptionHandles, function (handle) {
|
|
return handle.ready();
|
|
});
|
|
|
|
return this._allSubsReady;
|
|
};
|
|
|
|
/**
|
|
* @summary Specify template helpers available to this template.
|
|
* @locus Client
|
|
* @param {Object} helpers Dictionary of helper functions by name.
|
|
*/
|
|
Template.prototype.helpers = function (dict) {
|
|
for (var k in dict)
|
|
this.__helpers.set(k, dict[k]);
|
|
};
|
|
|
|
// Kind of like Blaze.currentView but for the template instance.
|
|
// This is a function, not a value -- so that not all helpers
|
|
// are implicitly dependent on the current template instance's `data` property,
|
|
// which would make them dependenct on the data context of the template
|
|
// inclusion.
|
|
Template._currentTemplateInstanceFunc = null;
|
|
|
|
Template._withTemplateInstanceFunc = function (templateInstanceFunc, func) {
|
|
if (typeof func !== 'function')
|
|
throw new Error("Expected function, got: " + func);
|
|
var oldTmplInstanceFunc = Template._currentTemplateInstanceFunc;
|
|
try {
|
|
Template._currentTemplateInstanceFunc = templateInstanceFunc;
|
|
return func();
|
|
} finally {
|
|
Template._currentTemplateInstanceFunc = oldTmplInstanceFunc;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @summary Specify event handlers for this template.
|
|
* @locus Client
|
|
* @param {EventMap} eventMap Event handlers to associate with this template.
|
|
*/
|
|
Template.prototype.events = function (eventMap) {
|
|
var template = this;
|
|
var eventMap2 = {};
|
|
for (var k in eventMap) {
|
|
eventMap2[k] = (function (k, v) {
|
|
return function (event/*, ...*/) {
|
|
var view = this; // passed by EventAugmenter
|
|
var data = Blaze.getData(event.currentTarget);
|
|
if (data == null)
|
|
data = {};
|
|
var args = Array.prototype.slice.call(arguments);
|
|
var tmplInstanceFunc = _.bind(view.templateInstance, view);
|
|
args.splice(1, 0, tmplInstanceFunc());
|
|
|
|
return Template._withTemplateInstanceFunc(tmplInstanceFunc, function () {
|
|
return v.apply(data, args);
|
|
});
|
|
};
|
|
})(k, eventMap[k]);
|
|
}
|
|
|
|
template.__eventMaps.push(eventMap2);
|
|
};
|
|
|
|
/**
|
|
* @function
|
|
* @name instance
|
|
* @memberOf Template
|
|
* @summary The [template instance](#template_inst) corresponding to the current template helper, event handler, callback, or autorun. If there isn't one, `null`.
|
|
* @locus Client
|
|
* @returns Blaze.TemplateInstance
|
|
*/
|
|
Template.instance = function () {
|
|
return Template._currentTemplateInstanceFunc
|
|
&& Template._currentTemplateInstanceFunc();
|
|
};
|
|
|
|
// Note: Template.currentData() is documented to take zero arguments,
|
|
// while Blaze.getData takes up to one.
|
|
|
|
/**
|
|
* @summary
|
|
*
|
|
* - Inside an `onCreated`, `onRendered`, or `onDestroyed` callback, returns
|
|
* the data context of the template.
|
|
* - Inside a helper, returns the data context of the DOM node where the helper
|
|
* was used.
|
|
* - Inside an event handler, returns the data context of the element that fired
|
|
* the event.
|
|
*
|
|
* Establishes a reactive dependency on the result.
|
|
* @locus Client
|
|
* @function
|
|
*/
|
|
Template.currentData = Blaze.getData;
|
|
|
|
/**
|
|
* @summary Accesses other data contexts that enclose the current data context.
|
|
* @locus Client
|
|
* @function
|
|
* @param {Integer} [numLevels] The number of levels beyond the current data context to look. Defaults to 1.
|
|
*/
|
|
Template.parentData = Blaze._parentData;
|
|
|
|
/**
|
|
* @summary Defines a [helper function](#template_helpers) which can be used from all templates.
|
|
* @locus Client
|
|
* @function
|
|
* @param {String} name The name of the helper function you are defining.
|
|
* @param {Function} function The helper function itself.
|
|
*/
|
|
Template.registerHelper = Blaze.registerHelper;
|