3895 lines
130 KiB
JavaScript
3895 lines
130 KiB
JavaScript
//////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// This is a generated file. You can view the original //
|
|
// source in your browser if your browser supports source maps. //
|
|
// Source maps are supported by all recent versions of Chrome, Safari, //
|
|
// and Firefox, and by Internet Explorer 11. //
|
|
// //
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
(function () {
|
|
|
|
/* Imports */
|
|
var Meteor = Package.meteor.Meteor;
|
|
var global = Package.meteor.global;
|
|
var meteorEnv = Package.meteor.meteorEnv;
|
|
var $ = Package.jquery.$;
|
|
var jQuery = Package.jquery.jQuery;
|
|
var Tracker = Package.tracker.Tracker;
|
|
var Deps = Package.tracker.Deps;
|
|
var check = Package.check.check;
|
|
var Match = Package.check.Match;
|
|
var _ = Package.underscore._;
|
|
var ObserveSequence = Package['observe-sequence'].ObserveSequence;
|
|
var ReactiveVar = Package['reactive-var'].ReactiveVar;
|
|
var OrderedDict = Package['ordered-dict'].OrderedDict;
|
|
var HTML = Package.htmljs.HTML;
|
|
|
|
/* Package-scope variables */
|
|
var Blaze, AttributeHandler, ElementAttributesUpdater, UI, Handlebars;
|
|
|
|
(function(){
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// packages/blaze/preamble.js //
|
|
// //
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
/**
|
|
* @namespace Blaze
|
|
* @summary The namespace for all Blaze-related methods and classes.
|
|
*/
|
|
Blaze = {};
|
|
|
|
// Utility to HTML-escape a string. Included for legacy reasons.
|
|
// TODO: Should be replaced with _.escape once underscore is upgraded to a newer
|
|
// version which escapes ` (backtick) as well. Underscore 1.5.2 does not.
|
|
Blaze._escape = (function() {
|
|
var escape_map = {
|
|
"<": "<",
|
|
">": ">",
|
|
'"': """,
|
|
"'": "'",
|
|
"/": "/",
|
|
"`": "`", /* IE allows backtick-delimited attributes?? */
|
|
"&": "&"
|
|
};
|
|
var escape_one = function(c) {
|
|
return escape_map[c];
|
|
};
|
|
|
|
return function (x) {
|
|
return x.replace(/[&<>"'`]/g, escape_one);
|
|
};
|
|
})();
|
|
|
|
Blaze._warn = function (msg) {
|
|
msg = 'Warning: ' + msg;
|
|
|
|
if ((typeof console !== 'undefined') && console.warn) {
|
|
console.warn(msg);
|
|
}
|
|
};
|
|
|
|
var nativeBind = Function.prototype.bind;
|
|
|
|
// An implementation of _.bind which allows better optimization.
|
|
// See: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments
|
|
if (nativeBind) {
|
|
Blaze._bind = function (func, obj) {
|
|
if (arguments.length === 2) {
|
|
return nativeBind.call(func, obj);
|
|
}
|
|
|
|
// Copy the arguments so this function can be optimized.
|
|
var args = new Array(arguments.length);
|
|
for (var i = 0; i < args.length; i++) {
|
|
args[i] = arguments[i];
|
|
}
|
|
|
|
return nativeBind.apply(func, args.slice(1));
|
|
};
|
|
}
|
|
else {
|
|
// A slower but backwards compatible version.
|
|
Blaze._bind = _.bind;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
}).call(this);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(function(){
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// packages/blaze/dombackend.js //
|
|
// //
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
var DOMBackend = {};
|
|
Blaze._DOMBackend = DOMBackend;
|
|
|
|
var $jq = (typeof jQuery !== 'undefined' ? jQuery :
|
|
(typeof Package !== 'undefined' ?
|
|
Package.jquery && Package.jquery.jQuery : null));
|
|
if (! $jq)
|
|
throw new Error("jQuery not found");
|
|
|
|
DOMBackend._$jq = $jq;
|
|
|
|
DOMBackend.parseHTML = function (html) {
|
|
// Return an array of nodes.
|
|
//
|
|
// jQuery does fancy stuff like creating an appropriate
|
|
// container element and setting innerHTML on it, as well
|
|
// as working around various IE quirks.
|
|
return $jq.parseHTML(html) || [];
|
|
};
|
|
|
|
DOMBackend.Events = {
|
|
// `selector` is non-null. `type` is one type (but
|
|
// may be in backend-specific form, e.g. have namespaces).
|
|
// Order fired must be order bound.
|
|
delegateEvents: function (elem, type, selector, handler) {
|
|
$jq(elem).on(type, selector, handler);
|
|
},
|
|
|
|
undelegateEvents: function (elem, type, handler) {
|
|
$jq(elem).off(type, '**', handler);
|
|
},
|
|
|
|
bindEventCapturer: function (elem, type, selector, handler) {
|
|
var $elem = $jq(elem);
|
|
|
|
var wrapper = function (event) {
|
|
event = $jq.event.fix(event);
|
|
event.currentTarget = event.target;
|
|
|
|
// Note: It might improve jQuery interop if we called into jQuery
|
|
// here somehow. Since we don't use jQuery to dispatch the event,
|
|
// we don't fire any of jQuery's event hooks or anything. However,
|
|
// since jQuery can't bind capturing handlers, it's not clear
|
|
// where we would hook in. Internal jQuery functions like `dispatch`
|
|
// are too high-level.
|
|
var $target = $jq(event.currentTarget);
|
|
if ($target.is($elem.find(selector)))
|
|
handler.call(elem, event);
|
|
};
|
|
|
|
handler._meteorui_wrapper = wrapper;
|
|
|
|
type = DOMBackend.Events.parseEventType(type);
|
|
// add *capturing* event listener
|
|
elem.addEventListener(type, wrapper, true);
|
|
},
|
|
|
|
unbindEventCapturer: function (elem, type, handler) {
|
|
type = DOMBackend.Events.parseEventType(type);
|
|
elem.removeEventListener(type, handler._meteorui_wrapper, true);
|
|
},
|
|
|
|
parseEventType: function (type) {
|
|
// strip off namespaces
|
|
var dotLoc = type.indexOf('.');
|
|
if (dotLoc >= 0)
|
|
return type.slice(0, dotLoc);
|
|
return type;
|
|
}
|
|
};
|
|
|
|
|
|
///// Removal detection and interoperability.
|
|
|
|
// For an explanation of this technique, see:
|
|
// http://bugs.jquery.com/ticket/12213#comment:23 .
|
|
//
|
|
// In short, an element is considered "removed" when jQuery
|
|
// cleans up its *private* userdata on the element,
|
|
// which we can detect using a custom event with a teardown
|
|
// hook.
|
|
|
|
var NOOP = function () {};
|
|
|
|
// Circular doubly-linked list
|
|
var TeardownCallback = function (func) {
|
|
this.next = this;
|
|
this.prev = this;
|
|
this.func = func;
|
|
};
|
|
|
|
// Insert newElt before oldElt in the circular list
|
|
TeardownCallback.prototype.linkBefore = function(oldElt) {
|
|
this.prev = oldElt.prev;
|
|
this.next = oldElt;
|
|
oldElt.prev.next = this;
|
|
oldElt.prev = this;
|
|
};
|
|
|
|
TeardownCallback.prototype.unlink = function () {
|
|
this.prev.next = this.next;
|
|
this.next.prev = this.prev;
|
|
};
|
|
|
|
TeardownCallback.prototype.go = function () {
|
|
var func = this.func;
|
|
func && func();
|
|
};
|
|
|
|
TeardownCallback.prototype.stop = TeardownCallback.prototype.unlink;
|
|
|
|
DOMBackend.Teardown = {
|
|
_JQUERY_EVENT_NAME: 'blaze_teardown_watcher',
|
|
_CB_PROP: '$blaze_teardown_callbacks',
|
|
// Registers a callback function to be called when the given element or
|
|
// one of its ancestors is removed from the DOM via the backend library.
|
|
// The callback function is called at most once, and it receives the element
|
|
// in question as an argument.
|
|
onElementTeardown: function (elem, func) {
|
|
var elt = new TeardownCallback(func);
|
|
|
|
var propName = DOMBackend.Teardown._CB_PROP;
|
|
if (! elem[propName]) {
|
|
// create an empty node that is never unlinked
|
|
elem[propName] = new TeardownCallback;
|
|
|
|
// Set up the event, only the first time.
|
|
$jq(elem).on(DOMBackend.Teardown._JQUERY_EVENT_NAME, NOOP);
|
|
}
|
|
|
|
elt.linkBefore(elem[propName]);
|
|
|
|
return elt; // so caller can call stop()
|
|
},
|
|
// Recursively call all teardown hooks, in the backend and registered
|
|
// through DOMBackend.onElementTeardown.
|
|
tearDownElement: function (elem) {
|
|
var elems = [];
|
|
// Array.prototype.slice.call doesn't work when given a NodeList in
|
|
// IE8 ("JScript object expected").
|
|
var nodeList = elem.getElementsByTagName('*');
|
|
for (var i = 0; i < nodeList.length; i++) {
|
|
elems.push(nodeList[i]);
|
|
}
|
|
elems.push(elem);
|
|
$jq.cleanData(elems);
|
|
}
|
|
};
|
|
|
|
$jq.event.special[DOMBackend.Teardown._JQUERY_EVENT_NAME] = {
|
|
setup: function () {
|
|
// This "setup" callback is important even though it is empty!
|
|
// Without it, jQuery will call addEventListener, which is a
|
|
// performance hit, especially with Chrome's async stack trace
|
|
// feature enabled.
|
|
},
|
|
teardown: function() {
|
|
var elem = this;
|
|
var callbacks = elem[DOMBackend.Teardown._CB_PROP];
|
|
if (callbacks) {
|
|
var elt = callbacks.next;
|
|
while (elt !== callbacks) {
|
|
elt.go();
|
|
elt = elt.next;
|
|
}
|
|
callbacks.go();
|
|
|
|
elem[DOMBackend.Teardown._CB_PROP] = null;
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
// Must use jQuery semantics for `context`, not
|
|
// querySelectorAll's. In other words, all the parts
|
|
// of `selector` must be found under `context`.
|
|
DOMBackend.findBySelector = function (selector, context) {
|
|
return $jq(selector, context);
|
|
};
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
}).call(this);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(function(){
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// packages/blaze/domrange.js //
|
|
// //
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
|
|
// A constant empty array (frozen if the JS engine supports it).
|
|
var _emptyArray = Object.freeze ? Object.freeze([]) : [];
|
|
|
|
// `[new] Blaze._DOMRange([nodeAndRangeArray])`
|
|
//
|
|
// A DOMRange consists of an array of consecutive nodes and DOMRanges,
|
|
// which may be replaced at any time with a new array. If the DOMRange
|
|
// has been attached to the DOM at some location, then updating
|
|
// the array will cause the DOM to be updated at that location.
|
|
Blaze._DOMRange = function (nodeAndRangeArray) {
|
|
if (! (this instanceof DOMRange))
|
|
// called without `new`
|
|
return new DOMRange(nodeAndRangeArray);
|
|
|
|
var members = (nodeAndRangeArray || _emptyArray);
|
|
if (! (members && (typeof members.length) === 'number'))
|
|
throw new Error("Expected array");
|
|
|
|
for (var i = 0; i < members.length; i++)
|
|
this._memberIn(members[i]);
|
|
|
|
this.members = members;
|
|
this.emptyRangePlaceholder = null;
|
|
this.attached = false;
|
|
this.parentElement = null;
|
|
this.parentRange = null;
|
|
this.attachedCallbacks = _emptyArray;
|
|
};
|
|
var DOMRange = Blaze._DOMRange;
|
|
|
|
// In IE 8, don't use empty text nodes as placeholders
|
|
// in empty DOMRanges, use comment nodes instead. Using
|
|
// empty text nodes in modern browsers is great because
|
|
// it doesn't clutter the web inspector. In IE 8, however,
|
|
// it seems to lead in some roundabout way to the OAuth
|
|
// pop-up crashing the browser completely. In the past,
|
|
// we didn't use empty text nodes on IE 8 because they
|
|
// don't accept JS properties, so just use the same logic
|
|
// even though we don't need to set properties on the
|
|
// placeholder anymore.
|
|
DOMRange._USE_COMMENT_PLACEHOLDERS = (function () {
|
|
var result = false;
|
|
var textNode = document.createTextNode("");
|
|
try {
|
|
textNode.someProp = true;
|
|
} catch (e) {
|
|
// IE 8
|
|
result = true;
|
|
}
|
|
return result;
|
|
})();
|
|
|
|
// static methods
|
|
DOMRange._insert = function (rangeOrNode, parentElement, nextNode, _isMove) {
|
|
var m = rangeOrNode;
|
|
if (m instanceof DOMRange) {
|
|
m.attach(parentElement, nextNode, _isMove);
|
|
} else {
|
|
if (_isMove)
|
|
DOMRange._moveNodeWithHooks(m, parentElement, nextNode);
|
|
else
|
|
DOMRange._insertNodeWithHooks(m, parentElement, nextNode);
|
|
}
|
|
};
|
|
|
|
DOMRange._remove = function (rangeOrNode) {
|
|
var m = rangeOrNode;
|
|
if (m instanceof DOMRange) {
|
|
m.detach();
|
|
} else {
|
|
DOMRange._removeNodeWithHooks(m);
|
|
}
|
|
};
|
|
|
|
DOMRange._removeNodeWithHooks = function (n) {
|
|
if (! n.parentNode)
|
|
return;
|
|
if (n.nodeType === 1 &&
|
|
n.parentNode._uihooks && n.parentNode._uihooks.removeElement) {
|
|
n.parentNode._uihooks.removeElement(n);
|
|
} else {
|
|
n.parentNode.removeChild(n);
|
|
}
|
|
};
|
|
|
|
DOMRange._insertNodeWithHooks = function (n, parent, next) {
|
|
// `|| null` because IE throws an error if 'next' is undefined
|
|
next = next || null;
|
|
if (n.nodeType === 1 &&
|
|
parent._uihooks && parent._uihooks.insertElement) {
|
|
parent._uihooks.insertElement(n, next);
|
|
} else {
|
|
parent.insertBefore(n, next);
|
|
}
|
|
};
|
|
|
|
DOMRange._moveNodeWithHooks = function (n, parent, next) {
|
|
if (n.parentNode !== parent)
|
|
return;
|
|
// `|| null` because IE throws an error if 'next' is undefined
|
|
next = next || null;
|
|
if (n.nodeType === 1 &&
|
|
parent._uihooks && parent._uihooks.moveElement) {
|
|
parent._uihooks.moveElement(n, next);
|
|
} else {
|
|
parent.insertBefore(n, next);
|
|
}
|
|
};
|
|
|
|
DOMRange.forElement = function (elem) {
|
|
if (elem.nodeType !== 1)
|
|
throw new Error("Expected element, found: " + elem);
|
|
var range = null;
|
|
while (elem && ! range) {
|
|
range = (elem.$blaze_range || null);
|
|
if (! range)
|
|
elem = elem.parentNode;
|
|
}
|
|
return range;
|
|
};
|
|
|
|
DOMRange.prototype.attach = function (parentElement, nextNode, _isMove, _isReplace) {
|
|
// This method is called to insert the DOMRange into the DOM for
|
|
// the first time, but it's also used internally when
|
|
// updating the DOM.
|
|
//
|
|
// If _isMove is true, move this attached range to a different
|
|
// location under the same parentElement.
|
|
if (_isMove || _isReplace) {
|
|
if (! (this.parentElement === parentElement &&
|
|
this.attached))
|
|
throw new Error("Can only move or replace an attached DOMRange, and only under the same parent element");
|
|
}
|
|
|
|
var members = this.members;
|
|
if (members.length) {
|
|
this.emptyRangePlaceholder = null;
|
|
for (var i = 0; i < members.length; i++) {
|
|
DOMRange._insert(members[i], parentElement, nextNode, _isMove);
|
|
}
|
|
} else {
|
|
var placeholder = (
|
|
DOMRange._USE_COMMENT_PLACEHOLDERS ?
|
|
document.createComment("") :
|
|
document.createTextNode(""));
|
|
this.emptyRangePlaceholder = placeholder;
|
|
parentElement.insertBefore(placeholder, nextNode || null);
|
|
}
|
|
this.attached = true;
|
|
this.parentElement = parentElement;
|
|
|
|
if (! (_isMove || _isReplace)) {
|
|
for(var i = 0; i < this.attachedCallbacks.length; i++) {
|
|
var obj = this.attachedCallbacks[i];
|
|
obj.attached && obj.attached(this, parentElement);
|
|
}
|
|
}
|
|
};
|
|
|
|
DOMRange.prototype.setMembers = function (newNodeAndRangeArray) {
|
|
var newMembers = newNodeAndRangeArray;
|
|
if (! (newMembers && (typeof newMembers.length) === 'number'))
|
|
throw new Error("Expected array");
|
|
|
|
var oldMembers = this.members;
|
|
|
|
for (var i = 0; i < oldMembers.length; i++)
|
|
this._memberOut(oldMembers[i]);
|
|
for (var i = 0; i < newMembers.length; i++)
|
|
this._memberIn(newMembers[i]);
|
|
|
|
if (! this.attached) {
|
|
this.members = newMembers;
|
|
} else {
|
|
// don't do anything if we're going from empty to empty
|
|
if (newMembers.length || oldMembers.length) {
|
|
// detach the old members and insert the new members
|
|
var nextNode = this.lastNode().nextSibling;
|
|
var parentElement = this.parentElement;
|
|
// Use detach/attach, but don't fire attached/detached hooks
|
|
this.detach(true /*_isReplace*/);
|
|
this.members = newMembers;
|
|
this.attach(parentElement, nextNode, false, true /*_isReplace*/);
|
|
}
|
|
}
|
|
};
|
|
|
|
DOMRange.prototype.firstNode = function () {
|
|
if (! this.attached)
|
|
throw new Error("Must be attached");
|
|
|
|
if (! this.members.length)
|
|
return this.emptyRangePlaceholder;
|
|
|
|
var m = this.members[0];
|
|
return (m instanceof DOMRange) ? m.firstNode() : m;
|
|
};
|
|
|
|
DOMRange.prototype.lastNode = function () {
|
|
if (! this.attached)
|
|
throw new Error("Must be attached");
|
|
|
|
if (! this.members.length)
|
|
return this.emptyRangePlaceholder;
|
|
|
|
var m = this.members[this.members.length - 1];
|
|
return (m instanceof DOMRange) ? m.lastNode() : m;
|
|
};
|
|
|
|
DOMRange.prototype.detach = function (_isReplace) {
|
|
if (! this.attached)
|
|
throw new Error("Must be attached");
|
|
|
|
var oldParentElement = this.parentElement;
|
|
var members = this.members;
|
|
if (members.length) {
|
|
for (var i = 0; i < members.length; i++) {
|
|
DOMRange._remove(members[i]);
|
|
}
|
|
} else {
|
|
var placeholder = this.emptyRangePlaceholder;
|
|
this.parentElement.removeChild(placeholder);
|
|
this.emptyRangePlaceholder = null;
|
|
}
|
|
|
|
if (! _isReplace) {
|
|
this.attached = false;
|
|
this.parentElement = null;
|
|
|
|
for(var i = 0; i < this.attachedCallbacks.length; i++) {
|
|
var obj = this.attachedCallbacks[i];
|
|
obj.detached && obj.detached(this, oldParentElement);
|
|
}
|
|
}
|
|
};
|
|
|
|
DOMRange.prototype.addMember = function (newMember, atIndex, _isMove) {
|
|
var members = this.members;
|
|
if (! (atIndex >= 0 && atIndex <= members.length))
|
|
throw new Error("Bad index in range.addMember: " + atIndex);
|
|
|
|
if (! _isMove)
|
|
this._memberIn(newMember);
|
|
|
|
if (! this.attached) {
|
|
// currently detached; just updated members
|
|
members.splice(atIndex, 0, newMember);
|
|
} else if (members.length === 0) {
|
|
// empty; use the empty-to-nonempty handling of setMembers
|
|
this.setMembers([newMember]);
|
|
} else {
|
|
var nextNode;
|
|
if (atIndex === members.length) {
|
|
// insert at end
|
|
nextNode = this.lastNode().nextSibling;
|
|
} else {
|
|
var m = members[atIndex];
|
|
nextNode = (m instanceof DOMRange) ? m.firstNode() : m;
|
|
}
|
|
members.splice(atIndex, 0, newMember);
|
|
DOMRange._insert(newMember, this.parentElement, nextNode, _isMove);
|
|
}
|
|
};
|
|
|
|
DOMRange.prototype.removeMember = function (atIndex, _isMove) {
|
|
var members = this.members;
|
|
if (! (atIndex >= 0 && atIndex < members.length))
|
|
throw new Error("Bad index in range.removeMember: " + atIndex);
|
|
|
|
if (_isMove) {
|
|
members.splice(atIndex, 1);
|
|
} else {
|
|
var oldMember = members[atIndex];
|
|
this._memberOut(oldMember);
|
|
|
|
if (members.length === 1) {
|
|
// becoming empty; use the logic in setMembers
|
|
this.setMembers(_emptyArray);
|
|
} else {
|
|
members.splice(atIndex, 1);
|
|
if (this.attached)
|
|
DOMRange._remove(oldMember);
|
|
}
|
|
}
|
|
};
|
|
|
|
DOMRange.prototype.moveMember = function (oldIndex, newIndex) {
|
|
var member = this.members[oldIndex];
|
|
this.removeMember(oldIndex, true /*_isMove*/);
|
|
this.addMember(member, newIndex, true /*_isMove*/);
|
|
};
|
|
|
|
DOMRange.prototype.getMember = function (atIndex) {
|
|
var members = this.members;
|
|
if (! (atIndex >= 0 && atIndex < members.length))
|
|
throw new Error("Bad index in range.getMember: " + atIndex);
|
|
return this.members[atIndex];
|
|
};
|
|
|
|
DOMRange.prototype._memberIn = function (m) {
|
|
if (m instanceof DOMRange)
|
|
m.parentRange = this;
|
|
else if (m.nodeType === 1) // DOM Element
|
|
m.$blaze_range = this;
|
|
};
|
|
|
|
DOMRange._destroy = function (m, _skipNodes) {
|
|
if (m instanceof DOMRange) {
|
|
if (m.view)
|
|
Blaze._destroyView(m.view, _skipNodes);
|
|
} else if ((! _skipNodes) && m.nodeType === 1) {
|
|
// DOM Element
|
|
if (m.$blaze_range) {
|
|
Blaze._destroyNode(m);
|
|
m.$blaze_range = null;
|
|
}
|
|
}
|
|
};
|
|
|
|
DOMRange.prototype._memberOut = DOMRange._destroy;
|
|
|
|
// Tear down, but don't remove, the members. Used when chunks
|
|
// of DOM are being torn down or replaced.
|
|
DOMRange.prototype.destroyMembers = function (_skipNodes) {
|
|
var members = this.members;
|
|
for (var i = 0; i < members.length; i++)
|
|
this._memberOut(members[i], _skipNodes);
|
|
};
|
|
|
|
DOMRange.prototype.destroy = function (_skipNodes) {
|
|
DOMRange._destroy(this, _skipNodes);
|
|
};
|
|
|
|
DOMRange.prototype.containsElement = function (elem) {
|
|
if (! this.attached)
|
|
throw new Error("Must be attached");
|
|
|
|
// An element is contained in this DOMRange if it's possible to
|
|
// reach it by walking parent pointers, first through the DOM and
|
|
// then parentRange pointers. In other words, the element or some
|
|
// ancestor of it is at our level of the DOM (a child of our
|
|
// parentElement), and this element is one of our members or
|
|
// is a member of a descendant Range.
|
|
|
|
// First check that elem is a descendant of this.parentElement,
|
|
// according to the DOM.
|
|
if (! Blaze._elementContains(this.parentElement, elem))
|
|
return false;
|
|
|
|
// If elem is not an immediate child of this.parentElement,
|
|
// walk up to its ancestor that is.
|
|
while (elem.parentNode !== this.parentElement)
|
|
elem = elem.parentNode;
|
|
|
|
var range = elem.$blaze_range;
|
|
while (range && range !== this)
|
|
range = range.parentRange;
|
|
|
|
return range === this;
|
|
};
|
|
|
|
DOMRange.prototype.containsRange = function (range) {
|
|
if (! this.attached)
|
|
throw new Error("Must be attached");
|
|
|
|
if (! range.attached)
|
|
return false;
|
|
|
|
// A DOMRange is contained in this DOMRange if it's possible
|
|
// to reach this range by following parent pointers. If the
|
|
// DOMRange has the same parentElement, then it should be
|
|
// a member, or a member of a member etc. Otherwise, we must
|
|
// contain its parentElement.
|
|
|
|
if (range.parentElement !== this.parentElement)
|
|
return this.containsElement(range.parentElement);
|
|
|
|
if (range === this)
|
|
return false; // don't contain self
|
|
|
|
while (range && range !== this)
|
|
range = range.parentRange;
|
|
|
|
return range === this;
|
|
};
|
|
|
|
DOMRange.prototype.onAttached = function (attached) {
|
|
this.onAttachedDetached({ attached: attached });
|
|
};
|
|
|
|
// callbacks are `attached(range, element)` and
|
|
// `detached(range, element)`, and they may
|
|
// access the `callbacks` object in `this`.
|
|
// The arguments to `detached` are the same
|
|
// range and element that were passed to `attached`.
|
|
DOMRange.prototype.onAttachedDetached = function (callbacks) {
|
|
if (this.attachedCallbacks === _emptyArray)
|
|
this.attachedCallbacks = [];
|
|
this.attachedCallbacks.push(callbacks);
|
|
};
|
|
|
|
DOMRange.prototype.$ = function (selector) {
|
|
var self = this;
|
|
|
|
var parentNode = this.parentElement;
|
|
if (! parentNode)
|
|
throw new Error("Can't select in removed DomRange");
|
|
|
|
// Strategy: Find all selector matches under parentNode,
|
|
// then filter out the ones that aren't in this DomRange
|
|
// using `DOMRange#containsElement`. This is
|
|
// asymptotically slow in the presence of O(N) sibling
|
|
// content that is under parentNode but not in our range,
|
|
// so if performance is an issue, the selector should be
|
|
// run on a child element.
|
|
|
|
// Since jQuery can't run selectors on a DocumentFragment,
|
|
// we don't expect findBySelector to work.
|
|
if (parentNode.nodeType === 11 /* DocumentFragment */)
|
|
throw new Error("Can't use $ on an offscreen range");
|
|
|
|
var results = Blaze._DOMBackend.findBySelector(selector, parentNode);
|
|
|
|
// We don't assume `results` has jQuery API; a plain array
|
|
// should do just as well. However, if we do have a jQuery
|
|
// array, we want to end up with one also, so we use
|
|
// `.filter`.
|
|
|
|
// Function that selects only elements that are actually
|
|
// in this DomRange, rather than simply descending from
|
|
// `parentNode`.
|
|
var filterFunc = function (elem) {
|
|
// handle jQuery's arguments to filter, where the node
|
|
// is in `this` and the index is the first argument.
|
|
if (typeof elem === 'number')
|
|
elem = this;
|
|
|
|
return self.containsElement(elem);
|
|
};
|
|
|
|
if (! results.filter) {
|
|
// not a jQuery array, and not a browser with
|
|
// Array.prototype.filter (e.g. IE <9)
|
|
var newResults = [];
|
|
for (var i = 0; i < results.length; i++) {
|
|
var x = results[i];
|
|
if (filterFunc(x))
|
|
newResults.push(x);
|
|
}
|
|
results = newResults;
|
|
} else {
|
|
// `results.filter` is either jQuery's or ECMAScript's `filter`
|
|
results = results.filter(filterFunc);
|
|
}
|
|
|
|
return results;
|
|
};
|
|
|
|
// Returns true if element a contains node b and is not node b.
|
|
//
|
|
// The restriction that `a` be an element (not a document fragment,
|
|
// say) is based on what's easy to implement cross-browser.
|
|
Blaze._elementContains = function (a, b) {
|
|
if (a.nodeType !== 1) // ELEMENT
|
|
return false;
|
|
if (a === b)
|
|
return false;
|
|
|
|
if (a.compareDocumentPosition) {
|
|
return a.compareDocumentPosition(b) & 0x10;
|
|
} else {
|
|
// Should be only old IE and maybe other old browsers here.
|
|
// Modern Safari has both functions but seems to get contains() wrong.
|
|
// IE can't handle b being a text node. We work around this
|
|
// by doing a direct parent test now.
|
|
b = b.parentNode;
|
|
if (! (b && b.nodeType === 1)) // ELEMENT
|
|
return false;
|
|
if (a === b)
|
|
return true;
|
|
|
|
return a.contains(b);
|
|
}
|
|
};
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
}).call(this);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(function(){
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// packages/blaze/events.js //
|
|
// //
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
var EventSupport = Blaze._EventSupport = {};
|
|
|
|
var DOMBackend = Blaze._DOMBackend;
|
|
|
|
// List of events to always delegate, never capture.
|
|
// Since jQuery fakes bubbling for certain events in
|
|
// certain browsers (like `submit`), we don't want to
|
|
// get in its way.
|
|
//
|
|
// We could list all known bubbling
|
|
// events here to avoid creating speculative capturers
|
|
// for them, but it would only be an optimization.
|
|
var eventsToDelegate = EventSupport.eventsToDelegate = {
|
|
blur: 1, change: 1, click: 1, focus: 1, focusin: 1,
|
|
focusout: 1, reset: 1, submit: 1
|
|
};
|
|
|
|
var EVENT_MODE = EventSupport.EVENT_MODE = {
|
|
TBD: 0,
|
|
BUBBLING: 1,
|
|
CAPTURING: 2
|
|
};
|
|
|
|
var NEXT_HANDLERREC_ID = 1;
|
|
|
|
var HandlerRec = function (elem, type, selector, handler, recipient) {
|
|
this.elem = elem;
|
|
this.type = type;
|
|
this.selector = selector;
|
|
this.handler = handler;
|
|
this.recipient = recipient;
|
|
this.id = (NEXT_HANDLERREC_ID++);
|
|
|
|
this.mode = EVENT_MODE.TBD;
|
|
|
|
// It's important that delegatedHandler be a different
|
|
// instance for each handlerRecord, because its identity
|
|
// is used to remove it.
|
|
//
|
|
// It's also important that the closure have access to
|
|
// `this` when it is not called with it set.
|
|
this.delegatedHandler = (function (h) {
|
|
return function (evt) {
|
|
if ((! h.selector) && evt.currentTarget !== evt.target)
|
|
// no selector means only fire on target
|
|
return;
|
|
return h.handler.apply(h.recipient, arguments);
|
|
};
|
|
})(this);
|
|
|
|
// WHY CAPTURE AND DELEGATE: jQuery can't delegate
|
|
// non-bubbling events, because
|
|
// event capture doesn't work in IE 8. However, there
|
|
// are all sorts of new-fangled non-bubbling events
|
|
// like "play" and "touchenter". We delegate these
|
|
// events using capture in all browsers except IE 8.
|
|
// IE 8 doesn't support these events anyway.
|
|
|
|
var tryCapturing = elem.addEventListener &&
|
|
(! _.has(eventsToDelegate,
|
|
DOMBackend.Events.parseEventType(type)));
|
|
|
|
if (tryCapturing) {
|
|
this.capturingHandler = (function (h) {
|
|
return function (evt) {
|
|
if (h.mode === EVENT_MODE.TBD) {
|
|
// must be first time we're called.
|
|
if (evt.bubbles) {
|
|
// this type of event bubbles, so don't
|
|
// get called again.
|
|
h.mode = EVENT_MODE.BUBBLING;
|
|
DOMBackend.Events.unbindEventCapturer(
|
|
h.elem, h.type, h.capturingHandler);
|
|
return;
|
|
} else {
|
|
// this type of event doesn't bubble,
|
|
// so unbind the delegation, preventing
|
|
// it from ever firing.
|
|
h.mode = EVENT_MODE.CAPTURING;
|
|
DOMBackend.Events.undelegateEvents(
|
|
h.elem, h.type, h.delegatedHandler);
|
|
}
|
|
}
|
|
|
|
h.delegatedHandler(evt);
|
|
};
|
|
})(this);
|
|
|
|
} else {
|
|
this.mode = EVENT_MODE.BUBBLING;
|
|
}
|
|
};
|
|
EventSupport.HandlerRec = HandlerRec;
|
|
|
|
HandlerRec.prototype.bind = function () {
|
|
// `this.mode` may be EVENT_MODE_TBD, in which case we bind both. in
|
|
// this case, 'capturingHandler' is in charge of detecting the
|
|
// correct mode and turning off one or the other handlers.
|
|
if (this.mode !== EVENT_MODE.BUBBLING) {
|
|
DOMBackend.Events.bindEventCapturer(
|
|
this.elem, this.type, this.selector || '*',
|
|
this.capturingHandler);
|
|
}
|
|
|
|
if (this.mode !== EVENT_MODE.CAPTURING)
|
|
DOMBackend.Events.delegateEvents(
|
|
this.elem, this.type,
|
|
this.selector || '*', this.delegatedHandler);
|
|
};
|
|
|
|
HandlerRec.prototype.unbind = function () {
|
|
if (this.mode !== EVENT_MODE.BUBBLING)
|
|
DOMBackend.Events.unbindEventCapturer(this.elem, this.type,
|
|
this.capturingHandler);
|
|
|
|
if (this.mode !== EVENT_MODE.CAPTURING)
|
|
DOMBackend.Events.undelegateEvents(this.elem, this.type,
|
|
this.delegatedHandler);
|
|
};
|
|
|
|
EventSupport.listen = function (element, events, selector, handler, recipient, getParentRecipient) {
|
|
|
|
// Prevent this method from being JITed by Safari. Due to a
|
|
// presumed JIT bug in Safari -- observed in Version 7.0.6
|
|
// (9537.78.2) -- this method may crash the Safari render process if
|
|
// it is JITed.
|
|
// Repro: https://github.com/dgreensp/public/tree/master/safari-crash
|
|
try { element = element; } finally {}
|
|
|
|
var eventTypes = [];
|
|
events.replace(/[^ /]+/g, function (e) {
|
|
eventTypes.push(e);
|
|
});
|
|
|
|
var newHandlerRecs = [];
|
|
for (var i = 0, N = eventTypes.length; i < N; i++) {
|
|
var type = eventTypes[i];
|
|
|
|
var eventDict = element.$blaze_events;
|
|
if (! eventDict)
|
|
eventDict = (element.$blaze_events = {});
|
|
|
|
var info = eventDict[type];
|
|
if (! info) {
|
|
info = eventDict[type] = {};
|
|
info.handlers = [];
|
|
}
|
|
var handlerList = info.handlers;
|
|
var handlerRec = new HandlerRec(
|
|
element, type, selector, handler, recipient);
|
|
newHandlerRecs.push(handlerRec);
|
|
handlerRec.bind();
|
|
handlerList.push(handlerRec);
|
|
// Move handlers of enclosing ranges to end, by unbinding and rebinding
|
|
// them. In jQuery (or other DOMBackend) this causes them to fire
|
|
// later when the backend dispatches event handlers.
|
|
if (getParentRecipient) {
|
|
for (var r = getParentRecipient(recipient); r;
|
|
r = getParentRecipient(r)) {
|
|
// r is an enclosing range (recipient)
|
|
for (var j = 0, Nj = handlerList.length;
|
|
j < Nj; j++) {
|
|
var h = handlerList[j];
|
|
if (h.recipient === r) {
|
|
h.unbind();
|
|
h.bind();
|
|
handlerList.splice(j, 1); // remove handlerList[j]
|
|
handlerList.push(h);
|
|
j--; // account for removed handler
|
|
Nj--; // don't visit appended handlers
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
// closes over just `element` and `newHandlerRecs`
|
|
stop: function () {
|
|
var eventDict = element.$blaze_events;
|
|
if (! eventDict)
|
|
return;
|
|
// newHandlerRecs has only one item unless you specify multiple
|
|
// event types. If this code is slow, it's because we have to
|
|
// iterate over handlerList here. Clearing a whole handlerList
|
|
// via stop() methods is O(N^2) in the number of handlers on
|
|
// an element.
|
|
for (var i = 0; i < newHandlerRecs.length; i++) {
|
|
var handlerToRemove = newHandlerRecs[i];
|
|
var info = eventDict[handlerToRemove.type];
|
|
if (! info)
|
|
continue;
|
|
var handlerList = info.handlers;
|
|
for (var j = handlerList.length - 1; j >= 0; j--) {
|
|
if (handlerList[j] === handlerToRemove) {
|
|
handlerToRemove.unbind();
|
|
handlerList.splice(j, 1); // remove handlerList[j]
|
|
}
|
|
}
|
|
}
|
|
newHandlerRecs.length = 0;
|
|
}
|
|
};
|
|
};
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
}).call(this);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(function(){
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// packages/blaze/attrs.js //
|
|
// //
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
var jsUrlsAllowed = false;
|
|
Blaze._allowJavascriptUrls = function () {
|
|
jsUrlsAllowed = true;
|
|
};
|
|
Blaze._javascriptUrlsAllowed = function () {
|
|
return jsUrlsAllowed;
|
|
};
|
|
|
|
// An AttributeHandler object is responsible for updating a particular attribute
|
|
// of a particular element. AttributeHandler subclasses implement
|
|
// browser-specific logic for dealing with particular attributes across
|
|
// different browsers.
|
|
//
|
|
// To define a new type of AttributeHandler, use
|
|
// `var FooHandler = AttributeHandler.extend({ update: function ... })`
|
|
// where the `update` function takes arguments `(element, oldValue, value)`.
|
|
// The `element` argument is always the same between calls to `update` on
|
|
// the same instance. `oldValue` and `value` are each either `null` or
|
|
// a Unicode string of the type that might be passed to the value argument
|
|
// of `setAttribute` (i.e. not an HTML string with character references).
|
|
// When an AttributeHandler is installed, an initial call to `update` is
|
|
// always made with `oldValue = null`. The `update` method can access
|
|
// `this.name` if the AttributeHandler class is a generic one that applies
|
|
// to multiple attribute names.
|
|
//
|
|
// AttributeHandlers can store custom properties on `this`, as long as they
|
|
// don't use the names `element`, `name`, `value`, and `oldValue`.
|
|
//
|
|
// AttributeHandlers can't influence how attributes appear in rendered HTML,
|
|
// only how they are updated after materialization as DOM.
|
|
|
|
AttributeHandler = function (name, value) {
|
|
this.name = name;
|
|
this.value = value;
|
|
};
|
|
Blaze._AttributeHandler = AttributeHandler;
|
|
|
|
AttributeHandler.prototype.update = function (element, oldValue, value) {
|
|
if (value === null) {
|
|
if (oldValue !== null)
|
|
element.removeAttribute(this.name);
|
|
} else {
|
|
element.setAttribute(this.name, value);
|
|
}
|
|
};
|
|
|
|
AttributeHandler.extend = function (options) {
|
|
var curType = this;
|
|
var subType = function AttributeHandlerSubtype(/*arguments*/) {
|
|
AttributeHandler.apply(this, arguments);
|
|
};
|
|
subType.prototype = new curType;
|
|
subType.extend = curType.extend;
|
|
if (options)
|
|
_.extend(subType.prototype, options);
|
|
return subType;
|
|
};
|
|
|
|
/// Apply the diff between the attributes of "oldValue" and "value" to "element."
|
|
//
|
|
// Each subclass must implement a parseValue method which takes a string
|
|
// as an input and returns an ordered dict of attributes. The keys of the dict
|
|
// are unique identifiers (ie. css properties in the case of styles), and the
|
|
// values are the entire attribute which will be injected into the element.
|
|
//
|
|
// Extended below to support classes, SVG elements and styles.
|
|
|
|
Blaze._DiffingAttributeHandler = AttributeHandler.extend({
|
|
update: function (element, oldValue, value) {
|
|
if (!this.getCurrentValue || !this.setValue || !this.parseValue || !this.joinValues)
|
|
throw new Error("Missing methods in subclass of 'DiffingAttributeHandler'");
|
|
|
|
var oldAttrsMap = oldValue ? this.parseValue(oldValue) : new OrderedDict();
|
|
var attrsMap = value ? this.parseValue(value) : new OrderedDict();
|
|
|
|
// the current attributes on the element, which we will mutate.
|
|
|
|
var currentAttrString = this.getCurrentValue(element);
|
|
var currentAttrsMap = currentAttrString ? this.parseValue(currentAttrString) : new OrderedDict();
|
|
|
|
// Any outside changes to attributes we add at the end.
|
|
currentAttrsMap.forEach(function (value, key, i) {
|
|
// If the key already exists, we do not use the current value, but the new value.
|
|
if (attrsMap.has(key)) {
|
|
return;
|
|
}
|
|
|
|
// Key does not already exist, but it existed before. Which means it was explicitly
|
|
// removed, so we do not add it.
|
|
if (oldAttrsMap.has(key)) {
|
|
return;
|
|
}
|
|
|
|
attrsMap.append(key, value);
|
|
});
|
|
|
|
var values = [];
|
|
attrsMap.forEach(function (value, key, i) {
|
|
values.push(value);
|
|
});
|
|
|
|
this.setValue(element, this.joinValues(values));
|
|
}
|
|
});
|
|
|
|
var ClassHandler = Blaze._DiffingAttributeHandler.extend({
|
|
// @param rawValue {String}
|
|
getCurrentValue: function (element) {
|
|
return element.className;
|
|
},
|
|
setValue: function (element, className) {
|
|
element.className = className;
|
|
},
|
|
parseValue: function (attrString) {
|
|
var tokens = new OrderedDict();
|
|
|
|
_.each(attrString.split(' '), function(token) {
|
|
if (token) {
|
|
// Ordered dict requires unique keys.
|
|
if (! tokens.has(token)) {
|
|
tokens.append(token, token);
|
|
}
|
|
}
|
|
});
|
|
return tokens;
|
|
},
|
|
joinValues: function (values) {
|
|
return values.join(' ');
|
|
}
|
|
});
|
|
|
|
var SVGClassHandler = ClassHandler.extend({
|
|
getCurrentValue: function (element) {
|
|
return element.className.baseVal;
|
|
},
|
|
setValue: function (element, className) {
|
|
element.setAttribute('class', className);
|
|
}
|
|
});
|
|
|
|
var StyleHandler = Blaze._DiffingAttributeHandler.extend({
|
|
getCurrentValue: function (element) {
|
|
return element.getAttribute('style');
|
|
},
|
|
setValue: function (element, style) {
|
|
if (style === '') {
|
|
element.removeAttribute('style');
|
|
} else {
|
|
element.setAttribute('style', style);
|
|
}
|
|
},
|
|
|
|
// Parse a string to produce a map from property to attribute string.
|
|
//
|
|
// Example:
|
|
// "color:red; foo:12px" produces a token {color: "color:red", foo:"foo:12px"}
|
|
parseValue: function (attrString) {
|
|
var tokens = new OrderedDict();
|
|
|
|
// Regex for parsing a css attribute declaration, taken from css-parse:
|
|
// https://github.com/reworkcss/css-parse/blob/7cef3658d0bba872cde05a85339034b187cb3397/index.js#L219
|
|
var regex = /(\*?[-#\/\*\\\w]+(?:\[[0-9a-z_-]+\])?)\s*:\s*(?:\'(?:\\\'|.)*?\'|"(?:\\"|.)*?"|\([^\)]*?\)|[^};])+[;\s]*/g;
|
|
var match = regex.exec(attrString);
|
|
while (match) {
|
|
// match[0] = entire matching string
|
|
// match[1] = css property
|
|
// Prefix the token to prevent conflicts with existing properties.
|
|
|
|
// We use the last value for the same key.
|
|
if (tokens.has(match[1])) {
|
|
tokens.remove(match[1]);
|
|
}
|
|
|
|
// XXX No `String.trim` on Safari 4. Swap out $.trim if we want to
|
|
// remove strong dep on jquery.
|
|
tokens.append(match[1], match[0].trim ? match[0].trim() : $.trim(match[0]));
|
|
|
|
match = regex.exec(attrString);
|
|
}
|
|
|
|
return tokens;
|
|
},
|
|
|
|
joinValues: function (values) {
|
|
// TODO: Assure that there is always ; between values. But what is an example where it breaks?
|
|
return values.join(' ');
|
|
}
|
|
});
|
|
|
|
var BooleanHandler = AttributeHandler.extend({
|
|
update: function (element, oldValue, value) {
|
|
var name = this.name;
|
|
if (value == null) {
|
|
if (oldValue != null)
|
|
element[name] = false;
|
|
} else {
|
|
element[name] = true;
|
|
}
|
|
}
|
|
});
|
|
|
|
var DOMPropertyHandler = AttributeHandler.extend({
|
|
update: function (element, oldValue, value) {
|
|
var name = this.name;
|
|
if (value !== element[name])
|
|
element[name] = value;
|
|
}
|
|
});
|
|
|
|
// attributes of the type 'xlink:something' should be set using
|
|
// the correct namespace in order to work
|
|
var XlinkHandler = AttributeHandler.extend({
|
|
update: function(element, oldValue, value) {
|
|
var NS = 'http://www.w3.org/1999/xlink';
|
|
if (value === null) {
|
|
if (oldValue !== null)
|
|
element.removeAttributeNS(NS, this.name);
|
|
} else {
|
|
element.setAttributeNS(NS, this.name, this.value);
|
|
}
|
|
}
|
|
});
|
|
|
|
// cross-browser version of `instanceof SVGElement`
|
|
var isSVGElement = function (elem) {
|
|
return 'ownerSVGElement' in elem;
|
|
};
|
|
|
|
var isUrlAttribute = function (tagName, attrName) {
|
|
// Compiled from http://www.w3.org/TR/REC-html40/index/attributes.html
|
|
// and
|
|
// http://www.w3.org/html/wg/drafts/html/master/index.html#attributes-1
|
|
var urlAttrs = {
|
|
FORM: ['action'],
|
|
BODY: ['background'],
|
|
BLOCKQUOTE: ['cite'],
|
|
Q: ['cite'],
|
|
DEL: ['cite'],
|
|
INS: ['cite'],
|
|
OBJECT: ['classid', 'codebase', 'data', 'usemap'],
|
|
APPLET: ['codebase'],
|
|
A: ['href'],
|
|
AREA: ['href'],
|
|
LINK: ['href'],
|
|
BASE: ['href'],
|
|
IMG: ['longdesc', 'src', 'usemap'],
|
|
FRAME: ['longdesc', 'src'],
|
|
IFRAME: ['longdesc', 'src'],
|
|
HEAD: ['profile'],
|
|
SCRIPT: ['src'],
|
|
INPUT: ['src', 'usemap', 'formaction'],
|
|
BUTTON: ['formaction'],
|
|
BASE: ['href'],
|
|
MENUITEM: ['icon'],
|
|
HTML: ['manifest'],
|
|
VIDEO: ['poster']
|
|
};
|
|
|
|
if (attrName === 'itemid') {
|
|
return true;
|
|
}
|
|
|
|
var urlAttrNames = urlAttrs[tagName] || [];
|
|
return _.contains(urlAttrNames, attrName);
|
|
};
|
|
|
|
// To get the protocol for a URL, we let the browser normalize it for
|
|
// us, by setting it as the href for an anchor tag and then reading out
|
|
// the 'protocol' property.
|
|
if (Meteor.isClient) {
|
|
var anchorForNormalization = document.createElement('A');
|
|
}
|
|
|
|
var getUrlProtocol = function (url) {
|
|
if (Meteor.isClient) {
|
|
anchorForNormalization.href = url;
|
|
return (anchorForNormalization.protocol || "").toLowerCase();
|
|
} else {
|
|
throw new Error('getUrlProtocol not implemented on the server');
|
|
}
|
|
};
|
|
|
|
// UrlHandler is an attribute handler for all HTML attributes that take
|
|
// URL values. It disallows javascript: URLs, unless
|
|
// Blaze._allowJavascriptUrls() has been called. To detect javascript:
|
|
// urls, we set the attribute on a dummy anchor element and then read
|
|
// out the 'protocol' property of the attribute.
|
|
var origUpdate = AttributeHandler.prototype.update;
|
|
var UrlHandler = AttributeHandler.extend({
|
|
update: function (element, oldValue, value) {
|
|
var self = this;
|
|
var args = arguments;
|
|
|
|
if (Blaze._javascriptUrlsAllowed()) {
|
|
origUpdate.apply(self, args);
|
|
} else {
|
|
var isJavascriptProtocol = (getUrlProtocol(value) === "javascript:");
|
|
var isVBScriptProtocol = (getUrlProtocol(value) === "vbscript:");
|
|
if (isJavascriptProtocol || isVBScriptProtocol) {
|
|
Blaze._warn("URLs that use the 'javascript:' or 'vbscript:' protocol are not " +
|
|
"allowed in URL attribute values. " +
|
|
"Call Blaze._allowJavascriptUrls() " +
|
|
"to enable them.");
|
|
origUpdate.apply(self, [element, oldValue, null]);
|
|
} else {
|
|
origUpdate.apply(self, args);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// XXX make it possible for users to register attribute handlers!
|
|
Blaze._makeAttributeHandler = function (elem, name, value) {
|
|
// generally, use setAttribute but certain attributes need to be set
|
|
// by directly setting a JavaScript property on the DOM element.
|
|
if (name === 'class') {
|
|
if (isSVGElement(elem)) {
|
|
return new SVGClassHandler(name, value);
|
|
} else {
|
|
return new ClassHandler(name, value);
|
|
}
|
|
} else if (name === 'style') {
|
|
return new StyleHandler(name, value);
|
|
} else if ((elem.tagName === 'OPTION' && name === 'selected') ||
|
|
(elem.tagName === 'INPUT' && name === 'checked') ||
|
|
(elem.tagName === 'VIDEO' && name === 'muted')) {
|
|
return new BooleanHandler(name, value);
|
|
} else if ((elem.tagName === 'TEXTAREA' || elem.tagName === 'INPUT')
|
|
&& name === 'value') {
|
|
// internally, TEXTAREAs tracks their value in the 'value'
|
|
// attribute just like INPUTs.
|
|
return new DOMPropertyHandler(name, value);
|
|
} else if (name.substring(0,6) === 'xlink:') {
|
|
return new XlinkHandler(name.substring(6), value);
|
|
} else if (isUrlAttribute(elem.tagName, name)) {
|
|
return new UrlHandler(name, value);
|
|
} else {
|
|
return new AttributeHandler(name, value);
|
|
}
|
|
|
|
// XXX will need one for 'style' on IE, though modern browsers
|
|
// seem to handle setAttribute ok.
|
|
};
|
|
|
|
|
|
ElementAttributesUpdater = function (elem) {
|
|
this.elem = elem;
|
|
this.handlers = {};
|
|
};
|
|
|
|
// Update attributes on `elem` to the dictionary `attrs`, whose
|
|
// values are strings.
|
|
ElementAttributesUpdater.prototype.update = function(newAttrs) {
|
|
var elem = this.elem;
|
|
var handlers = this.handlers;
|
|
|
|
for (var k in handlers) {
|
|
if (! _.has(newAttrs, k)) {
|
|
// remove attributes (and handlers) for attribute names
|
|
// that don't exist as keys of `newAttrs` and so won't
|
|
// be visited when traversing it. (Attributes that
|
|
// exist in the `newAttrs` object but are `null`
|
|
// are handled later.)
|
|
var handler = handlers[k];
|
|
var oldValue = handler.value;
|
|
handler.value = null;
|
|
handler.update(elem, oldValue, null);
|
|
delete handlers[k];
|
|
}
|
|
}
|
|
|
|
for (var k in newAttrs) {
|
|
var handler = null;
|
|
var oldValue = null;
|
|
var value = newAttrs[k];
|
|
if (! _.has(handlers, k)) {
|
|
if (value !== null) {
|
|
// make new handler
|
|
handler = Blaze._makeAttributeHandler(elem, k, value);
|
|
handlers[k] = handler;
|
|
}
|
|
} else {
|
|
handler = handlers[k];
|
|
oldValue = handler.value;
|
|
}
|
|
if (oldValue !== value) {
|
|
handler.value = value;
|
|
handler.update(elem, oldValue, value);
|
|
if (value === null)
|
|
delete handlers[k];
|
|
}
|
|
}
|
|
};
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
}).call(this);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(function(){
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// packages/blaze/materializer.js //
|
|
// //
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Turns HTMLjs into DOM nodes and DOMRanges.
|
|
//
|
|
// - `htmljs`: the value to materialize, which may be any of the htmljs
|
|
// types (Tag, CharRef, Comment, Raw, array, string, boolean, number,
|
|
// null, or undefined) or a View or Template (which will be used to
|
|
// construct a View).
|
|
// - `intoArray`: the array of DOM nodes and DOMRanges to push the output
|
|
// into (required)
|
|
// - `parentView`: the View we are materializing content for (optional)
|
|
// - `_existingWorkStack`: optional argument, only used for recursive
|
|
// calls when there is some other _materializeDOM on the call stack.
|
|
// If _materializeDOM called your function and passed in a workStack,
|
|
// pass it back when you call _materializeDOM (such as from a workStack
|
|
// task).
|
|
//
|
|
// Returns `intoArray`, which is especially useful if you pass in `[]`.
|
|
Blaze._materializeDOM = function (htmljs, intoArray, parentView,
|
|
_existingWorkStack) {
|
|
// In order to use fewer stack frames, materializeDOMInner can push
|
|
// tasks onto `workStack`, and they will be popped off
|
|
// and run, last first, after materializeDOMInner returns. The
|
|
// reason we use a stack instead of a queue is so that we recurse
|
|
// depth-first, doing newer tasks first.
|
|
var workStack = (_existingWorkStack || []);
|
|
materializeDOMInner(htmljs, intoArray, parentView, workStack);
|
|
|
|
if (! _existingWorkStack) {
|
|
// We created the work stack, so we are responsible for finishing
|
|
// the work. Call each "task" function, starting with the top
|
|
// of the stack.
|
|
while (workStack.length) {
|
|
// Note that running task() may push new items onto workStack.
|
|
var task = workStack.pop();
|
|
task();
|
|
}
|
|
}
|
|
|
|
return intoArray;
|
|
};
|
|
|
|
var materializeDOMInner = function (htmljs, intoArray, parentView, workStack) {
|
|
if (htmljs == null) {
|
|
// null or undefined
|
|
return;
|
|
}
|
|
|
|
switch (typeof htmljs) {
|
|
case 'string': case 'boolean': case 'number':
|
|
intoArray.push(document.createTextNode(String(htmljs)));
|
|
return;
|
|
case 'object':
|
|
if (htmljs.htmljsType) {
|
|
switch (htmljs.htmljsType) {
|
|
case HTML.Tag.htmljsType:
|
|
intoArray.push(materializeTag(htmljs, parentView, workStack));
|
|
return;
|
|
case HTML.CharRef.htmljsType:
|
|
intoArray.push(document.createTextNode(htmljs.str));
|
|
return;
|
|
case HTML.Comment.htmljsType:
|
|
intoArray.push(document.createComment(htmljs.sanitizedValue));
|
|
return;
|
|
case HTML.Raw.htmljsType:
|
|
// Get an array of DOM nodes by using the browser's HTML parser
|
|
// (like innerHTML).
|
|
var nodes = Blaze._DOMBackend.parseHTML(htmljs.value);
|
|
for (var i = 0; i < nodes.length; i++)
|
|
intoArray.push(nodes[i]);
|
|
return;
|
|
}
|
|
} else if (HTML.isArray(htmljs)) {
|
|
for (var i = htmljs.length-1; i >= 0; i--) {
|
|
workStack.push(Blaze._bind(Blaze._materializeDOM, null,
|
|
htmljs[i], intoArray, parentView, workStack));
|
|
}
|
|
return;
|
|
} else {
|
|
if (htmljs instanceof Blaze.Template) {
|
|
htmljs = htmljs.constructView();
|
|
// fall through to Blaze.View case below
|
|
}
|
|
if (htmljs instanceof Blaze.View) {
|
|
Blaze._materializeView(htmljs, parentView, workStack, intoArray);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
throw new Error("Unexpected object in htmljs: " + htmljs);
|
|
};
|
|
|
|
var materializeTag = function (tag, parentView, workStack) {
|
|
var tagName = tag.tagName;
|
|
var elem;
|
|
if ((HTML.isKnownSVGElement(tagName) || isSVGAnchor(tag))
|
|
&& document.createElementNS) {
|
|
// inline SVG
|
|
elem = document.createElementNS('http://www.w3.org/2000/svg', tagName);
|
|
} else {
|
|
// normal elements
|
|
elem = document.createElement(tagName);
|
|
}
|
|
|
|
var rawAttrs = tag.attrs;
|
|
var children = tag.children;
|
|
if (tagName === 'textarea' && tag.children.length &&
|
|
! (rawAttrs && ('value' in rawAttrs))) {
|
|
// Provide very limited support for TEXTAREA tags with children
|
|
// rather than a "value" attribute.
|
|
// Reactivity in the form of Views nested in the tag's children
|
|
// won't work. Compilers should compile textarea contents into
|
|
// the "value" attribute of the tag, wrapped in a function if there
|
|
// is reactivity.
|
|
if (typeof rawAttrs === 'function' ||
|
|
HTML.isArray(rawAttrs)) {
|
|
throw new Error("Can't have reactive children of TEXTAREA node; " +
|
|
"use the 'value' attribute instead.");
|
|
}
|
|
rawAttrs = _.extend({}, rawAttrs || null);
|
|
rawAttrs.value = Blaze._expand(children, parentView);
|
|
children = [];
|
|
}
|
|
|
|
if (rawAttrs) {
|
|
var attrUpdater = new ElementAttributesUpdater(elem);
|
|
var updateAttributes = function () {
|
|
var expandedAttrs = Blaze._expandAttributes(rawAttrs, parentView);
|
|
var flattenedAttrs = HTML.flattenAttributes(expandedAttrs);
|
|
var stringAttrs = {};
|
|
for (var attrName in flattenedAttrs) {
|
|
// map `null`, `undefined`, and `false` to null, which is important
|
|
// so that attributes with nully values are considered absent.
|
|
// stringify anything else (e.g. strings, booleans, numbers including 0).
|
|
if (flattenedAttrs[attrName] == null || flattenedAttrs[attrName] === false)
|
|
stringAttrs[attrName] = null;
|
|
else
|
|
stringAttrs[attrName] = Blaze._toText(flattenedAttrs[attrName],
|
|
parentView,
|
|
HTML.TEXTMODE.STRING);
|
|
}
|
|
attrUpdater.update(stringAttrs);
|
|
};
|
|
var updaterComputation;
|
|
if (parentView) {
|
|
updaterComputation =
|
|
parentView.autorun(updateAttributes, undefined, 'updater');
|
|
} else {
|
|
updaterComputation = Tracker.nonreactive(function () {
|
|
return Tracker.autorun(function () {
|
|
Tracker._withCurrentView(parentView, updateAttributes);
|
|
});
|
|
});
|
|
}
|
|
Blaze._DOMBackend.Teardown.onElementTeardown(elem, function attrTeardown() {
|
|
updaterComputation.stop();
|
|
});
|
|
}
|
|
|
|
if (children.length) {
|
|
var childNodesAndRanges = [];
|
|
// push this function first so that it's done last
|
|
workStack.push(function () {
|
|
for (var i = 0; i < childNodesAndRanges.length; i++) {
|
|
var x = childNodesAndRanges[i];
|
|
if (x instanceof Blaze._DOMRange)
|
|
x.attach(elem);
|
|
else
|
|
elem.appendChild(x);
|
|
}
|
|
});
|
|
// now push the task that calculates childNodesAndRanges
|
|
workStack.push(Blaze._bind(Blaze._materializeDOM, null,
|
|
children, childNodesAndRanges, parentView,
|
|
workStack));
|
|
}
|
|
|
|
return elem;
|
|
};
|
|
|
|
|
|
var isSVGAnchor = function (node) {
|
|
// We generally aren't able to detect SVG <a> elements because
|
|
// if "A" were in our list of known svg element names, then all
|
|
// <a> nodes would be created using
|
|
// `document.createElementNS`. But in the special case of <a
|
|
// xlink:href="...">, we can at least detect that attribute and
|
|
// create an SVG <a> tag in that case.
|
|
//
|
|
// However, we still have a general problem of knowing when to
|
|
// use document.createElementNS and when to use
|
|
// document.createElement; for example, font tags will always
|
|
// be created as SVG elements which can cause other
|
|
// problems. #1977
|
|
return (node.tagName === "a" &&
|
|
node.attrs &&
|
|
node.attrs["xlink:href"] !== undefined);
|
|
};
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
}).call(this);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(function(){
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// packages/blaze/exceptions.js //
|
|
// //
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
var debugFunc;
|
|
|
|
// We call into user code in many places, and it's nice to catch exceptions
|
|
// propagated from user code immediately so that the whole system doesn't just
|
|
// break. Catching exceptions is easy; reporting them is hard. This helper
|
|
// reports exceptions.
|
|
//
|
|
// Usage:
|
|
//
|
|
// ```
|
|
// try {
|
|
// // ... someStuff ...
|
|
// } catch (e) {
|
|
// reportUIException(e);
|
|
// }
|
|
// ```
|
|
//
|
|
// An optional second argument overrides the default message.
|
|
|
|
// Set this to `true` to cause `reportException` to throw
|
|
// the next exception rather than reporting it. This is
|
|
// useful in unit tests that test error messages.
|
|
Blaze._throwNextException = false;
|
|
|
|
Blaze._reportException = function (e, msg) {
|
|
if (Blaze._throwNextException) {
|
|
Blaze._throwNextException = false;
|
|
throw e;
|
|
}
|
|
|
|
if (! debugFunc)
|
|
// adapted from Tracker
|
|
debugFunc = function () {
|
|
return (typeof Meteor !== "undefined" ? Meteor._debug :
|
|
((typeof console !== "undefined") && console.log ? console.log :
|
|
function () {}));
|
|
};
|
|
|
|
// In Chrome, `e.stack` is a multiline string that starts with the message
|
|
// and contains a stack trace. Furthermore, `console.log` makes it clickable.
|
|
// `console.log` supplies the space between the two arguments.
|
|
debugFunc()(msg || 'Exception caught in template:', e.stack || e.message || e);
|
|
};
|
|
|
|
Blaze._wrapCatchingExceptions = function (f, where) {
|
|
if (typeof f !== 'function')
|
|
return f;
|
|
|
|
return function () {
|
|
try {
|
|
return f.apply(this, arguments);
|
|
} catch (e) {
|
|
Blaze._reportException(e, 'Exception in ' + where + ':');
|
|
}
|
|
};
|
|
};
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
}).call(this);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(function(){
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// packages/blaze/view.js //
|
|
// //
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
/// [new] Blaze.View([name], renderMethod)
|
|
///
|
|
/// Blaze.View is the building block of reactive DOM. Views have
|
|
/// the following features:
|
|
///
|
|
/// * lifecycle callbacks - Views are created, rendered, and destroyed,
|
|
/// and callbacks can be registered to fire when these things happen.
|
|
///
|
|
/// * parent pointer - A View points to its parentView, which is the
|
|
/// View that caused it to be rendered. These pointers form a
|
|
/// hierarchy or tree of Views.
|
|
///
|
|
/// * render() method - A View's render() method specifies the DOM
|
|
/// (or HTML) content of the View. If the method establishes
|
|
/// reactive dependencies, it may be re-run.
|
|
///
|
|
/// * a DOMRange - If a View is rendered to DOM, its position and
|
|
/// extent in the DOM are tracked using a DOMRange object.
|
|
///
|
|
/// When a View is constructed by calling Blaze.View, the View is
|
|
/// not yet considered "created." It doesn't have a parentView yet,
|
|
/// and no logic has been run to initialize the View. All real
|
|
/// work is deferred until at least creation time, when the onViewCreated
|
|
/// callbacks are fired, which happens when the View is "used" in
|
|
/// some way that requires it to be rendered.
|
|
///
|
|
/// ...more lifecycle stuff
|
|
///
|
|
/// `name` is an optional string tag identifying the View. The only
|
|
/// time it's used is when looking in the View tree for a View of a
|
|
/// particular name; for example, data contexts are stored on Views
|
|
/// of name "with". Names are also useful when debugging, so in
|
|
/// general it's good for functions that create Views to set the name.
|
|
/// Views associated with templates have names of the form "Template.foo".
|
|
|
|
/**
|
|
* @class
|
|
* @summary Constructor for a View, which represents a reactive region of DOM.
|
|
* @locus Client
|
|
* @param {String} [name] Optional. A name for this type of View. See [`view.name`](#view_name).
|
|
* @param {Function} renderFunction A function that returns [*renderable content*](#renderable_content). In this function, `this` is bound to the View.
|
|
*/
|
|
Blaze.View = function (name, render) {
|
|
if (! (this instanceof Blaze.View))
|
|
// called without `new`
|
|
return new Blaze.View(name, render);
|
|
|
|
if (typeof name === 'function') {
|
|
// omitted "name" argument
|
|
render = name;
|
|
name = '';
|
|
}
|
|
this.name = name;
|
|
this._render = render;
|
|
|
|
this._callbacks = {
|
|
created: null,
|
|
rendered: null,
|
|
destroyed: null
|
|
};
|
|
|
|
// Setting all properties here is good for readability,
|
|
// and also may help Chrome optimize the code by keeping
|
|
// the View object from changing shape too much.
|
|
this.isCreated = false;
|
|
this._isCreatedForExpansion = false;
|
|
this.isRendered = false;
|
|
this._isAttached = false;
|
|
this.isDestroyed = false;
|
|
this._isInRender = false;
|
|
this.parentView = null;
|
|
this._domrange = null;
|
|
// This flag is normally set to false except for the cases when view's parent
|
|
// was generated as part of expanding some syntactic sugar expressions or
|
|
// methods.
|
|
// Ex.: Blaze.renderWithData is an equivalent to creating a view with regular
|
|
// Blaze.render and wrapping it into {{#with data}}{{/with}} view. Since the
|
|
// users don't know anything about these generated parent views, Blaze needs
|
|
// this information to be available on views to make smarter decisions. For
|
|
// example: removing the generated parent view with the view on Blaze.remove.
|
|
this._hasGeneratedParent = false;
|
|
// Bindings accessible to children views (via view.lookup('name')) within the
|
|
// closest template view.
|
|
this._scopeBindings = {};
|
|
|
|
this.renderCount = 0;
|
|
};
|
|
|
|
Blaze.View.prototype._render = function () { return null; };
|
|
|
|
Blaze.View.prototype.onViewCreated = function (cb) {
|
|
this._callbacks.created = this._callbacks.created || [];
|
|
this._callbacks.created.push(cb);
|
|
};
|
|
|
|
Blaze.View.prototype._onViewRendered = function (cb) {
|
|
this._callbacks.rendered = this._callbacks.rendered || [];
|
|
this._callbacks.rendered.push(cb);
|
|
};
|
|
|
|
Blaze.View.prototype.onViewReady = function (cb) {
|
|
var self = this;
|
|
var fire = function () {
|
|
Tracker.afterFlush(function () {
|
|
if (! self.isDestroyed) {
|
|
Blaze._withCurrentView(self, function () {
|
|
cb.call(self);
|
|
});
|
|
}
|
|
});
|
|
};
|
|
self._onViewRendered(function onViewRendered() {
|
|
if (self.isDestroyed)
|
|
return;
|
|
if (! self._domrange.attached)
|
|
self._domrange.onAttached(fire);
|
|
else
|
|
fire();
|
|
});
|
|
};
|
|
|
|
Blaze.View.prototype.onViewDestroyed = function (cb) {
|
|
this._callbacks.destroyed = this._callbacks.destroyed || [];
|
|
this._callbacks.destroyed.push(cb);
|
|
};
|
|
Blaze.View.prototype.removeViewDestroyedListener = function (cb) {
|
|
var destroyed = this._callbacks.destroyed;
|
|
if (! destroyed)
|
|
return;
|
|
var index = _.lastIndexOf(destroyed, cb);
|
|
if (index !== -1) {
|
|
// XXX You'd think the right thing to do would be splice, but _fireCallbacks
|
|
// gets sad if you remove callbacks while iterating over the list. Should
|
|
// change this to use callback-hook or EventEmitter or something else that
|
|
// properly supports removal.
|
|
destroyed[index] = null;
|
|
}
|
|
};
|
|
|
|
/// View#autorun(func)
|
|
///
|
|
/// Sets up a Tracker autorun that is "scoped" to this View in two
|
|
/// important ways: 1) Blaze.currentView is automatically set
|
|
/// on every re-run, and 2) the autorun is stopped when the
|
|
/// View is destroyed. As with Tracker.autorun, the first run of
|
|
/// the function is immediate, and a Computation object that can
|
|
/// be used to stop the autorun is returned.
|
|
///
|
|
/// View#autorun is meant to be called from View callbacks like
|
|
/// onViewCreated, or from outside the rendering process. It may not
|
|
/// be called before the onViewCreated callbacks are fired (too early),
|
|
/// or from a render() method (too confusing).
|
|
///
|
|
/// Typically, autoruns that update the state
|
|
/// of the View (as in Blaze.With) should be started from an onViewCreated
|
|
/// callback. Autoruns that update the DOM should be started
|
|
/// from either onViewCreated (guarded against the absence of
|
|
/// view._domrange), or onViewReady.
|
|
Blaze.View.prototype.autorun = function (f, _inViewScope, displayName) {
|
|
var self = this;
|
|
|
|
// The restrictions on when View#autorun can be called are in order
|
|
// to avoid bad patterns, like creating a Blaze.View and immediately
|
|
// calling autorun on it. A freshly created View is not ready to
|
|
// have logic run on it; it doesn't have a parentView, for example.
|
|
// It's when the View is materialized or expanded that the onViewCreated
|
|
// handlers are fired and the View starts up.
|
|
//
|
|
// Letting the render() method call `this.autorun()` is problematic
|
|
// because of re-render. The best we can do is to stop the old
|
|
// autorun and start a new one for each render, but that's a pattern
|
|
// we try to avoid internally because it leads to helpers being
|
|
// called extra times, in the case where the autorun causes the
|
|
// view to re-render (and thus the autorun to be torn down and a
|
|
// new one established).
|
|
//
|
|
// We could lift these restrictions in various ways. One interesting
|
|
// idea is to allow you to call `view.autorun` after instantiating
|
|
// `view`, and automatically wrap it in `view.onViewCreated`, deferring
|
|
// the autorun so that it starts at an appropriate time. However,
|
|
// then we can't return the Computation object to the caller, because
|
|
// it doesn't exist yet.
|
|
if (! self.isCreated) {
|
|
throw new Error("View#autorun must be called from the created callback at the earliest");
|
|
}
|
|
if (this._isInRender) {
|
|
throw new Error("Can't call View#autorun from inside render(); try calling it from the created or rendered callback");
|
|
}
|
|
|
|
var templateInstanceFunc = Blaze.Template._currentTemplateInstanceFunc;
|
|
|
|
var func = function viewAutorun(c) {
|
|
return Blaze._withCurrentView(_inViewScope || self, function () {
|
|
return Blaze.Template._withTemplateInstanceFunc(
|
|
templateInstanceFunc, function () {
|
|
return f.call(self, c);
|
|
});
|
|
});
|
|
};
|
|
|
|
// Give the autorun function a better name for debugging and profiling.
|
|
// The `displayName` property is not part of the spec but browsers like Chrome
|
|
// and Firefox prefer it in debuggers over the name function was declared by.
|
|
func.displayName =
|
|
(self.name || 'anonymous') + ':' + (displayName || 'anonymous');
|
|
var comp = Tracker.autorun(func);
|
|
|
|
var stopComputation = function () { comp.stop(); };
|
|
self.onViewDestroyed(stopComputation);
|
|
comp.onStop(function () {
|
|
self.removeViewDestroyedListener(stopComputation);
|
|
});
|
|
|
|
return comp;
|
|
};
|
|
|
|
Blaze.View.prototype._errorIfShouldntCallSubscribe = function () {
|
|
var self = this;
|
|
|
|
if (! self.isCreated) {
|
|
throw new Error("View#subscribe must be called from the created callback at the earliest");
|
|
}
|
|
if (self._isInRender) {
|
|
throw new Error("Can't call View#subscribe from inside render(); try calling it from the created or rendered callback");
|
|
}
|
|
if (self.isDestroyed) {
|
|
throw new Error("Can't call View#subscribe from inside the destroyed callback, try calling it inside created or rendered.");
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Just like Blaze.View#autorun, but with Meteor.subscribe instead of
|
|
* Tracker.autorun. Stop the subscription when the view is destroyed.
|
|
* @return {SubscriptionHandle} A handle to the subscription so that you can
|
|
* see if it is ready, or stop it manually
|
|
*/
|
|
Blaze.View.prototype.subscribe = function (args, options) {
|
|
var self = this;
|
|
options = options || {};
|
|
|
|
self._errorIfShouldntCallSubscribe();
|
|
|
|
var subHandle;
|
|
if (options.connection) {
|
|
subHandle = options.connection.subscribe.apply(options.connection, args);
|
|
} else {
|
|
subHandle = Meteor.subscribe.apply(Meteor, args);
|
|
}
|
|
|
|
self.onViewDestroyed(function () {
|
|
subHandle.stop();
|
|
});
|
|
|
|
return subHandle;
|
|
};
|
|
|
|
Blaze.View.prototype.firstNode = function () {
|
|
if (! this._isAttached)
|
|
throw new Error("View must be attached before accessing its DOM");
|
|
|
|
return this._domrange.firstNode();
|
|
};
|
|
|
|
Blaze.View.prototype.lastNode = function () {
|
|
if (! this._isAttached)
|
|
throw new Error("View must be attached before accessing its DOM");
|
|
|
|
return this._domrange.lastNode();
|
|
};
|
|
|
|
Blaze._fireCallbacks = function (view, which) {
|
|
Blaze._withCurrentView(view, function () {
|
|
Tracker.nonreactive(function fireCallbacks() {
|
|
var cbs = view._callbacks[which];
|
|
for (var i = 0, N = (cbs && cbs.length); i < N; i++)
|
|
cbs[i] && cbs[i].call(view);
|
|
});
|
|
});
|
|
};
|
|
|
|
Blaze._createView = function (view, parentView, forExpansion) {
|
|
if (view.isCreated)
|
|
throw new Error("Can't render the same View twice");
|
|
|
|
view.parentView = (parentView || null);
|
|
view.isCreated = true;
|
|
if (forExpansion)
|
|
view._isCreatedForExpansion = true;
|
|
|
|
Blaze._fireCallbacks(view, 'created');
|
|
};
|
|
|
|
var doFirstRender = function (view, initialContent) {
|
|
var domrange = new Blaze._DOMRange(initialContent);
|
|
view._domrange = domrange;
|
|
domrange.view = view;
|
|
view.isRendered = true;
|
|
Blaze._fireCallbacks(view, 'rendered');
|
|
|
|
var teardownHook = null;
|
|
|
|
domrange.onAttached(function attached(range, element) {
|
|
view._isAttached = true;
|
|
|
|
teardownHook = Blaze._DOMBackend.Teardown.onElementTeardown(
|
|
element, function teardown() {
|
|
Blaze._destroyView(view, true /* _skipNodes */);
|
|
});
|
|
});
|
|
|
|
// tear down the teardown hook
|
|
view.onViewDestroyed(function () {
|
|
teardownHook && teardownHook.stop();
|
|
teardownHook = null;
|
|
});
|
|
|
|
return domrange;
|
|
};
|
|
|
|
// Take an uncreated View `view` and create and render it to DOM,
|
|
// setting up the autorun that updates the View. Returns a new
|
|
// DOMRange, which has been associated with the View.
|
|
//
|
|
// The private arguments `_workStack` and `_intoArray` are passed in
|
|
// by Blaze._materializeDOM and are only present for recursive calls
|
|
// (when there is some other _materializeView on the stack). If
|
|
// provided, then we avoid the mutual recursion of calling back into
|
|
// Blaze._materializeDOM so that deep View hierarchies don't blow the
|
|
// stack. Instead, we push tasks onto workStack for the initial
|
|
// rendering and subsequent setup of the View, and they are done after
|
|
// we return. When there is a _workStack, we do not return the new
|
|
// DOMRange, but instead push it into _intoArray from a _workStack
|
|
// task.
|
|
Blaze._materializeView = function (view, parentView, _workStack, _intoArray) {
|
|
Blaze._createView(view, parentView);
|
|
|
|
var domrange;
|
|
var lastHtmljs;
|
|
// We don't expect to be called in a Computation, but just in case,
|
|
// wrap in Tracker.nonreactive.
|
|
Tracker.nonreactive(function () {
|
|
view.autorun(function doRender(c) {
|
|
// `view.autorun` sets the current view.
|
|
view.renderCount++;
|
|
view._isInRender = true;
|
|
// Any dependencies that should invalidate this Computation come
|
|
// from this line:
|
|
var htmljs = view._render();
|
|
view._isInRender = false;
|
|
|
|
if (! c.firstRun && ! Blaze._isContentEqual(lastHtmljs, htmljs)) {
|
|
Tracker.nonreactive(function doMaterialize() {
|
|
// re-render
|
|
var rangesAndNodes = Blaze._materializeDOM(htmljs, [], view);
|
|
domrange.setMembers(rangesAndNodes);
|
|
Blaze._fireCallbacks(view, 'rendered');
|
|
});
|
|
}
|
|
lastHtmljs = htmljs;
|
|
|
|
// Causes any nested views to stop immediately, not when we call
|
|
// `setMembers` the next time around the autorun. Otherwise,
|
|
// helpers in the DOM tree to be replaced might be scheduled
|
|
// to re-run before we have a chance to stop them.
|
|
Tracker.onInvalidate(function () {
|
|
if (domrange) {
|
|
domrange.destroyMembers();
|
|
}
|
|
});
|
|
}, undefined, 'materialize');
|
|
|
|
// first render. lastHtmljs is the first htmljs.
|
|
var initialContents;
|
|
if (! _workStack) {
|
|
initialContents = Blaze._materializeDOM(lastHtmljs, [], view);
|
|
domrange = doFirstRender(view, initialContents);
|
|
initialContents = null; // help GC because we close over this scope a lot
|
|
} else {
|
|
// We're being called from Blaze._materializeDOM, so to avoid
|
|
// recursion and save stack space, provide a description of the
|
|
// work to be done instead of doing it. Tasks pushed onto
|
|
// _workStack will be done in LIFO order after we return.
|
|
// The work will still be done within a Tracker.nonreactive,
|
|
// because it will be done by some call to Blaze._materializeDOM
|
|
// (which is always called in a Tracker.nonreactive).
|
|
initialContents = [];
|
|
// push this function first so that it happens last
|
|
_workStack.push(function () {
|
|
domrange = doFirstRender(view, initialContents);
|
|
initialContents = null; // help GC because of all the closures here
|
|
_intoArray.push(domrange);
|
|
});
|
|
// now push the task that calculates initialContents
|
|
_workStack.push(Blaze._bind(Blaze._materializeDOM, null,
|
|
lastHtmljs, initialContents, view, _workStack));
|
|
}
|
|
});
|
|
|
|
if (! _workStack) {
|
|
return domrange;
|
|
} else {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
// Expands a View to HTMLjs, calling `render` recursively on all
|
|
// Views and evaluating any dynamic attributes. Calls the `created`
|
|
// callback, but not the `materialized` or `rendered` callbacks.
|
|
// Destroys the view immediately, unless called in a Tracker Computation,
|
|
// in which case the view will be destroyed when the Computation is
|
|
// invalidated. If called in a Tracker Computation, the result is a
|
|
// reactive string; that is, the Computation will be invalidated
|
|
// if any changes are made to the view or subviews that might affect
|
|
// the HTML.
|
|
Blaze._expandView = function (view, parentView) {
|
|
Blaze._createView(view, parentView, true /*forExpansion*/);
|
|
|
|
view._isInRender = true;
|
|
var htmljs = Blaze._withCurrentView(view, function () {
|
|
return view._render();
|
|
});
|
|
view._isInRender = false;
|
|
|
|
var result = Blaze._expand(htmljs, view);
|
|
|
|
if (Tracker.active) {
|
|
Tracker.onInvalidate(function () {
|
|
Blaze._destroyView(view);
|
|
});
|
|
} else {
|
|
Blaze._destroyView(view);
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
// Options: `parentView`
|
|
Blaze._HTMLJSExpander = HTML.TransformingVisitor.extend();
|
|
Blaze._HTMLJSExpander.def({
|
|
visitObject: function (x) {
|
|
if (x instanceof Blaze.Template)
|
|
x = x.constructView();
|
|
if (x instanceof Blaze.View)
|
|
return Blaze._expandView(x, this.parentView);
|
|
|
|
// this will throw an error; other objects are not allowed!
|
|
return HTML.TransformingVisitor.prototype.visitObject.call(this, x);
|
|
},
|
|
visitAttributes: function (attrs) {
|
|
// expand dynamic attributes
|
|
if (typeof attrs === 'function')
|
|
attrs = Blaze._withCurrentView(this.parentView, attrs);
|
|
|
|
// call super (e.g. for case where `attrs` is an array)
|
|
return HTML.TransformingVisitor.prototype.visitAttributes.call(this, attrs);
|
|
},
|
|
visitAttribute: function (name, value, tag) {
|
|
// expand attribute values that are functions. Any attribute value
|
|
// that contains Views must be wrapped in a function.
|
|
if (typeof value === 'function')
|
|
value = Blaze._withCurrentView(this.parentView, value);
|
|
|
|
return HTML.TransformingVisitor.prototype.visitAttribute.call(
|
|
this, name, value, tag);
|
|
}
|
|
});
|
|
|
|
// Return Blaze.currentView, but only if it is being rendered
|
|
// (i.e. we are in its render() method).
|
|
var currentViewIfRendering = function () {
|
|
var view = Blaze.currentView;
|
|
return (view && view._isInRender) ? view : null;
|
|
};
|
|
|
|
Blaze._expand = function (htmljs, parentView) {
|
|
parentView = parentView || currentViewIfRendering();
|
|
return (new Blaze._HTMLJSExpander(
|
|
{parentView: parentView})).visit(htmljs);
|
|
};
|
|
|
|
Blaze._expandAttributes = function (attrs, parentView) {
|
|
parentView = parentView || currentViewIfRendering();
|
|
return (new Blaze._HTMLJSExpander(
|
|
{parentView: parentView})).visitAttributes(attrs);
|
|
};
|
|
|
|
Blaze._destroyView = function (view, _skipNodes) {
|
|
if (view.isDestroyed)
|
|
return;
|
|
view.isDestroyed = true;
|
|
|
|
Blaze._fireCallbacks(view, 'destroyed');
|
|
|
|
// Destroy views and elements recursively. If _skipNodes,
|
|
// only recurse up to views, not elements, for the case where
|
|
// the backend (jQuery) is recursing over the elements already.
|
|
|
|
if (view._domrange)
|
|
view._domrange.destroyMembers(_skipNodes);
|
|
};
|
|
|
|
Blaze._destroyNode = function (node) {
|
|
if (node.nodeType === 1)
|
|
Blaze._DOMBackend.Teardown.tearDownElement(node);
|
|
};
|
|
|
|
// Are the HTMLjs entities `a` and `b` the same? We could be
|
|
// more elaborate here but the point is to catch the most basic
|
|
// cases.
|
|
Blaze._isContentEqual = function (a, b) {
|
|
if (a instanceof HTML.Raw) {
|
|
return (b instanceof HTML.Raw) && (a.value === b.value);
|
|
} else if (a == null) {
|
|
return (b == null);
|
|
} else {
|
|
return (a === b) &&
|
|
((typeof a === 'number') || (typeof a === 'boolean') ||
|
|
(typeof a === 'string'));
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @summary The View corresponding to the current template helper, event handler, callback, or autorun. If there isn't one, `null`.
|
|
* @locus Client
|
|
* @type {Blaze.View}
|
|
*/
|
|
Blaze.currentView = null;
|
|
|
|
Blaze._withCurrentView = function (view, func) {
|
|
var oldView = Blaze.currentView;
|
|
try {
|
|
Blaze.currentView = view;
|
|
return func();
|
|
} finally {
|
|
Blaze.currentView = oldView;
|
|
}
|
|
};
|
|
|
|
// Blaze.render publicly takes a View or a Template.
|
|
// Privately, it takes any HTMLJS (extended with Views and Templates)
|
|
// except null or undefined, or a function that returns any extended
|
|
// HTMLJS.
|
|
var checkRenderContent = function (content) {
|
|
if (content === null)
|
|
throw new Error("Can't render null");
|
|
if (typeof content === 'undefined')
|
|
throw new Error("Can't render undefined");
|
|
|
|
if ((content instanceof Blaze.View) ||
|
|
(content instanceof Blaze.Template) ||
|
|
(typeof content === 'function'))
|
|
return;
|
|
|
|
try {
|
|
// Throw if content doesn't look like HTMLJS at the top level
|
|
// (i.e. verify that this is an HTML.Tag, or an array,
|
|
// or a primitive, etc.)
|
|
(new HTML.Visitor).visit(content);
|
|
} catch (e) {
|
|
// Make error message suitable for public API
|
|
throw new Error("Expected Template or View");
|
|
}
|
|
};
|
|
|
|
// For Blaze.render and Blaze.toHTML, take content and
|
|
// wrap it in a View, unless it's a single View or
|
|
// Template already.
|
|
var contentAsView = function (content) {
|
|
checkRenderContent(content);
|
|
|
|
if (content instanceof Blaze.Template) {
|
|
return content.constructView();
|
|
} else if (content instanceof Blaze.View) {
|
|
return content;
|
|
} else {
|
|
var func = content;
|
|
if (typeof func !== 'function') {
|
|
func = function () {
|
|
return content;
|
|
};
|
|
}
|
|
return Blaze.View('render', func);
|
|
}
|
|
};
|
|
|
|
// For Blaze.renderWithData and Blaze.toHTMLWithData, wrap content
|
|
// in a function, if necessary, so it can be a content arg to
|
|
// a Blaze.With.
|
|
var contentAsFunc = function (content) {
|
|
checkRenderContent(content);
|
|
|
|
if (typeof content !== 'function') {
|
|
return function () {
|
|
return content;
|
|
};
|
|
} else {
|
|
return content;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @summary Renders a template or View to DOM nodes and inserts it into the DOM, returning a rendered [View](#blaze_view) which can be passed to [`Blaze.remove`](#blaze_remove).
|
|
* @locus Client
|
|
* @param {Template|Blaze.View} templateOrView The template (e.g. `Template.myTemplate`) or View object to render. If a template, a View object is [constructed](#template_constructview). If a View, it must be an unrendered View, which becomes a rendered View and is returned.
|
|
* @param {DOMNode} parentNode The node that will be the parent of the rendered template. It must be an Element node.
|
|
* @param {DOMNode} [nextNode] Optional. If provided, must be a child of <em>parentNode</em>; the template will be inserted before this node. If not provided, the template will be inserted as the last child of parentNode.
|
|
* @param {Blaze.View} [parentView] Optional. If provided, it will be set as the rendered View's [`parentView`](#view_parentview).
|
|
*/
|
|
Blaze.render = function (content, parentElement, nextNode, parentView) {
|
|
if (! parentElement) {
|
|
Blaze._warn("Blaze.render without a parent element is deprecated. " +
|
|
"You must specify where to insert the rendered content.");
|
|
}
|
|
|
|
if (nextNode instanceof Blaze.View) {
|
|
// handle omitted nextNode
|
|
parentView = nextNode;
|
|
nextNode = null;
|
|
}
|
|
|
|
// parentElement must be a DOM node. in particular, can't be the
|
|
// result of a call to `$`. Can't check if `parentElement instanceof
|
|
// Node` since 'Node' is undefined in IE8.
|
|
if (parentElement && typeof parentElement.nodeType !== 'number')
|
|
throw new Error("'parentElement' must be a DOM node");
|
|
if (nextNode && typeof nextNode.nodeType !== 'number') // 'nextNode' is optional
|
|
throw new Error("'nextNode' must be a DOM node");
|
|
|
|
parentView = parentView || currentViewIfRendering();
|
|
|
|
var view = contentAsView(content);
|
|
Blaze._materializeView(view, parentView);
|
|
|
|
if (parentElement) {
|
|
view._domrange.attach(parentElement, nextNode);
|
|
}
|
|
|
|
return view;
|
|
};
|
|
|
|
Blaze.insert = function (view, parentElement, nextNode) {
|
|
Blaze._warn("Blaze.insert has been deprecated. Specify where to insert the " +
|
|
"rendered content in the call to Blaze.render.");
|
|
|
|
if (! (view && (view._domrange instanceof Blaze._DOMRange)))
|
|
throw new Error("Expected template rendered with Blaze.render");
|
|
|
|
view._domrange.attach(parentElement, nextNode);
|
|
};
|
|
|
|
/**
|
|
* @summary Renders a template or View to DOM nodes with a data context. Otherwise identical to `Blaze.render`.
|
|
* @locus Client
|
|
* @param {Template|Blaze.View} templateOrView The template (e.g. `Template.myTemplate`) or View object to render.
|
|
* @param {Object|Function} data The data context to use, or a function returning a data context. If a function is provided, it will be reactively re-run.
|
|
* @param {DOMNode} parentNode The node that will be the parent of the rendered template. It must be an Element node.
|
|
* @param {DOMNode} [nextNode] Optional. If provided, must be a child of <em>parentNode</em>; the template will be inserted before this node. If not provided, the template will be inserted as the last child of parentNode.
|
|
* @param {Blaze.View} [parentView] Optional. If provided, it will be set as the rendered View's [`parentView`](#view_parentview).
|
|
*/
|
|
Blaze.renderWithData = function (content, data, parentElement, nextNode, parentView) {
|
|
// We defer the handling of optional arguments to Blaze.render. At this point,
|
|
// `nextNode` may actually be `parentView`.
|
|
return Blaze.render(Blaze._TemplateWith(data, contentAsFunc(content)),
|
|
parentElement, nextNode, parentView);
|
|
};
|
|
|
|
/**
|
|
* @summary Removes a rendered View from the DOM, stopping all reactive updates and event listeners on it. Also destroys the Blaze.Template instance associated with the view.
|
|
* @locus Client
|
|
* @param {Blaze.View} renderedView The return value from `Blaze.render` or `Blaze.renderWithData`, or the `view` property of a Blaze.Template instance. Calling `Blaze.remove(Template.instance().view)` from within a template event handler will destroy the view as well as that template and trigger the template's `onDestroyed` handlers.
|
|
*/
|
|
Blaze.remove = function (view) {
|
|
if (! (view && (view._domrange instanceof Blaze._DOMRange)))
|
|
throw new Error("Expected template rendered with Blaze.render");
|
|
|
|
while (view) {
|
|
if (! view.isDestroyed) {
|
|
var range = view._domrange;
|
|
if (range.attached && ! range.parentRange)
|
|
range.detach();
|
|
range.destroy();
|
|
}
|
|
|
|
view = view._hasGeneratedParent && view.parentView;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @summary Renders a template or View to a string of HTML.
|
|
* @locus Client
|
|
* @param {Template|Blaze.View} templateOrView The template (e.g. `Template.myTemplate`) or View object from which to generate HTML.
|
|
*/
|
|
Blaze.toHTML = function (content, parentView) {
|
|
parentView = parentView || currentViewIfRendering();
|
|
|
|
return HTML.toHTML(Blaze._expandView(contentAsView(content), parentView));
|
|
};
|
|
|
|
/**
|
|
* @summary Renders a template or View to HTML with a data context. Otherwise identical to `Blaze.toHTML`.
|
|
* @locus Client
|
|
* @param {Template|Blaze.View} templateOrView The template (e.g. `Template.myTemplate`) or View object from which to generate HTML.
|
|
* @param {Object|Function} data The data context to use, or a function returning a data context.
|
|
*/
|
|
Blaze.toHTMLWithData = function (content, data, parentView) {
|
|
parentView = parentView || currentViewIfRendering();
|
|
|
|
return HTML.toHTML(Blaze._expandView(Blaze._TemplateWith(
|
|
data, contentAsFunc(content)), parentView));
|
|
};
|
|
|
|
Blaze._toText = function (htmljs, parentView, textMode) {
|
|
if (typeof htmljs === 'function')
|
|
throw new Error("Blaze._toText doesn't take a function, just HTMLjs");
|
|
|
|
if ((parentView != null) && ! (parentView instanceof Blaze.View)) {
|
|
// omitted parentView argument
|
|
textMode = parentView;
|
|
parentView = null;
|
|
}
|
|
parentView = parentView || currentViewIfRendering();
|
|
|
|
if (! textMode)
|
|
throw new Error("textMode required");
|
|
if (! (textMode === HTML.TEXTMODE.STRING ||
|
|
textMode === HTML.TEXTMODE.RCDATA ||
|
|
textMode === HTML.TEXTMODE.ATTRIBUTE))
|
|
throw new Error("Unknown textMode: " + textMode);
|
|
|
|
return HTML.toText(Blaze._expand(htmljs, parentView), textMode);
|
|
};
|
|
|
|
/**
|
|
* @summary Returns the current data context, or the data context that was used when rendering a particular DOM element or View from a Meteor template.
|
|
* @locus Client
|
|
* @param {DOMElement|Blaze.View} [elementOrView] Optional. An element that was rendered by a Meteor, or a View.
|
|
*/
|
|
Blaze.getData = function (elementOrView) {
|
|
var theWith;
|
|
|
|
if (! elementOrView) {
|
|
theWith = Blaze.getView('with');
|
|
} else if (elementOrView instanceof Blaze.View) {
|
|
var view = elementOrView;
|
|
theWith = (view.name === 'with' ? view :
|
|
Blaze.getView(view, 'with'));
|
|
} else if (typeof elementOrView.nodeType === 'number') {
|
|
if (elementOrView.nodeType !== 1)
|
|
throw new Error("Expected DOM element");
|
|
theWith = Blaze.getView(elementOrView, 'with');
|
|
} else {
|
|
throw new Error("Expected DOM element or View");
|
|
}
|
|
|
|
return theWith ? theWith.dataVar.get() : null;
|
|
};
|
|
|
|
// For back-compat
|
|
Blaze.getElementData = function (element) {
|
|
Blaze._warn("Blaze.getElementData has been deprecated. Use " +
|
|
"Blaze.getData(element) instead.");
|
|
|
|
if (element.nodeType !== 1)
|
|
throw new Error("Expected DOM element");
|
|
|
|
return Blaze.getData(element);
|
|
};
|
|
|
|
// Both arguments are optional.
|
|
|
|
/**
|
|
* @summary Gets either the current View, or the View enclosing the given DOM element.
|
|
* @locus Client
|
|
* @param {DOMElement} [element] Optional. If specified, the View enclosing `element` is returned.
|
|
*/
|
|
Blaze.getView = function (elementOrView, _viewName) {
|
|
var viewName = _viewName;
|
|
|
|
if ((typeof elementOrView) === 'string') {
|
|
// omitted elementOrView; viewName present
|
|
viewName = elementOrView;
|
|
elementOrView = null;
|
|
}
|
|
|
|
// We could eventually shorten the code by folding the logic
|
|
// from the other methods into this method.
|
|
if (! elementOrView) {
|
|
return Blaze._getCurrentView(viewName);
|
|
} else if (elementOrView instanceof Blaze.View) {
|
|
return Blaze._getParentView(elementOrView, viewName);
|
|
} else if (typeof elementOrView.nodeType === 'number') {
|
|
return Blaze._getElementView(elementOrView, viewName);
|
|
} else {
|
|
throw new Error("Expected DOM element or View");
|
|
}
|
|
};
|
|
|
|
// Gets the current view or its nearest ancestor of name
|
|
// `name`.
|
|
Blaze._getCurrentView = function (name) {
|
|
var view = Blaze.currentView;
|
|
// Better to fail in cases where it doesn't make sense
|
|
// to use Blaze._getCurrentView(). There will be a current
|
|
// view anywhere it does. You can check Blaze.currentView
|
|
// if you want to know whether there is one or not.
|
|
if (! view)
|
|
throw new Error("There is no current view");
|
|
|
|
if (name) {
|
|
while (view && view.name !== name)
|
|
view = view.parentView;
|
|
return view || null;
|
|
} else {
|
|
// Blaze._getCurrentView() with no arguments just returns
|
|
// Blaze.currentView.
|
|
return view;
|
|
}
|
|
};
|
|
|
|
Blaze._getParentView = function (view, name) {
|
|
var v = view.parentView;
|
|
|
|
if (name) {
|
|
while (v && v.name !== name)
|
|
v = v.parentView;
|
|
}
|
|
|
|
return v || null;
|
|
};
|
|
|
|
Blaze._getElementView = function (elem, name) {
|
|
var range = Blaze._DOMRange.forElement(elem);
|
|
var view = null;
|
|
while (range && ! view) {
|
|
view = (range.view || null);
|
|
if (! view) {
|
|
if (range.parentRange)
|
|
range = range.parentRange;
|
|
else
|
|
range = Blaze._DOMRange.forElement(range.parentElement);
|
|
}
|
|
}
|
|
|
|
if (name) {
|
|
while (view && view.name !== name)
|
|
view = view.parentView;
|
|
return view || null;
|
|
} else {
|
|
return view;
|
|
}
|
|
};
|
|
|
|
Blaze._addEventMap = function (view, eventMap, thisInHandler) {
|
|
thisInHandler = (thisInHandler || null);
|
|
var handles = [];
|
|
|
|
if (! view._domrange)
|
|
throw new Error("View must have a DOMRange");
|
|
|
|
view._domrange.onAttached(function attached_eventMaps(range, element) {
|
|
_.each(eventMap, function (handler, spec) {
|
|
var clauses = spec.split(/,\s+/);
|
|
// iterate over clauses of spec, e.g. ['click .foo', 'click .bar']
|
|
_.each(clauses, function (clause) {
|
|
var parts = clause.split(/\s+/);
|
|
if (parts.length === 0)
|
|
return;
|
|
|
|
var newEvents = parts.shift();
|
|
var selector = parts.join(' ');
|
|
handles.push(Blaze._EventSupport.listen(
|
|
element, newEvents, selector,
|
|
function (evt) {
|
|
if (! range.containsElement(evt.currentTarget))
|
|
return null;
|
|
var handlerThis = thisInHandler || this;
|
|
var handlerArgs = arguments;
|
|
return Blaze._withCurrentView(view, function () {
|
|
return handler.apply(handlerThis, handlerArgs);
|
|
});
|
|
},
|
|
range, function (r) {
|
|
return r.parentRange;
|
|
}));
|
|
});
|
|
});
|
|
});
|
|
|
|
view.onViewDestroyed(function () {
|
|
_.each(handles, function (h) {
|
|
h.stop();
|
|
});
|
|
handles.length = 0;
|
|
});
|
|
};
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
}).call(this);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(function(){
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// packages/blaze/builtins.js //
|
|
// //
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
Blaze._calculateCondition = function (cond) {
|
|
if (cond instanceof Array && cond.length === 0)
|
|
cond = false;
|
|
return !! cond;
|
|
};
|
|
|
|
/**
|
|
* @summary Constructs a View that renders content with a data context.
|
|
* @locus Client
|
|
* @param {Object|Function} data An object to use as the data context, or a function returning such an object. If a function is provided, it will be reactively re-run.
|
|
* @param {Function} contentFunc A Function that returns [*renderable content*](#renderable_content).
|
|
*/
|
|
Blaze.With = function (data, contentFunc) {
|
|
var view = Blaze.View('with', contentFunc);
|
|
|
|
view.dataVar = new ReactiveVar;
|
|
|
|
view.onViewCreated(function () {
|
|
if (typeof data === 'function') {
|
|
// `data` is a reactive function
|
|
view.autorun(function () {
|
|
view.dataVar.set(data());
|
|
}, view.parentView, 'setData');
|
|
} else {
|
|
view.dataVar.set(data);
|
|
}
|
|
});
|
|
|
|
return view;
|
|
};
|
|
|
|
/**
|
|
* Attaches bindings to the instantiated view.
|
|
* @param {Object} bindings A dictionary of bindings, each binding name
|
|
* corresponds to a value or a function that will be reactively re-run.
|
|
* @param {View} view The target.
|
|
*/
|
|
Blaze._attachBindingsToView = function (bindings, view) {
|
|
view.onViewCreated(function () {
|
|
_.each(bindings, function (binding, name) {
|
|
view._scopeBindings[name] = new ReactiveVar;
|
|
if (typeof binding === 'function') {
|
|
view.autorun(function () {
|
|
view._scopeBindings[name].set(binding());
|
|
}, view.parentView);
|
|
} else {
|
|
view._scopeBindings[name].set(binding);
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
/**
|
|
* @summary Constructs a View setting the local lexical scope in the block.
|
|
* @param {Function} bindings Dictionary mapping names of bindings to
|
|
* values or computations to reactively re-run.
|
|
* @param {Function} contentFunc A Function that returns [*renderable content*](#renderable_content).
|
|
*/
|
|
Blaze.Let = function (bindings, contentFunc) {
|
|
var view = Blaze.View('let', contentFunc);
|
|
Blaze._attachBindingsToView(bindings, view);
|
|
|
|
return view;
|
|
};
|
|
|
|
/**
|
|
* @summary Constructs a View that renders content conditionally.
|
|
* @locus Client
|
|
* @param {Function} conditionFunc A function to reactively re-run. Whether the result is truthy or falsy determines whether `contentFunc` or `elseFunc` is shown. An empty array is considered falsy.
|
|
* @param {Function} contentFunc A Function that returns [*renderable content*](#renderable_content).
|
|
* @param {Function} [elseFunc] Optional. A Function that returns [*renderable content*](#renderable_content). If no `elseFunc` is supplied, no content is shown in the "else" case.
|
|
*/
|
|
Blaze.If = function (conditionFunc, contentFunc, elseFunc, _not) {
|
|
var conditionVar = new ReactiveVar;
|
|
|
|
var view = Blaze.View(_not ? 'unless' : 'if', function () {
|
|
return conditionVar.get() ? contentFunc() :
|
|
(elseFunc ? elseFunc() : null);
|
|
});
|
|
view.__conditionVar = conditionVar;
|
|
view.onViewCreated(function () {
|
|
this.autorun(function () {
|
|
var cond = Blaze._calculateCondition(conditionFunc());
|
|
conditionVar.set(_not ? (! cond) : cond);
|
|
}, this.parentView, 'condition');
|
|
});
|
|
|
|
return view;
|
|
};
|
|
|
|
/**
|
|
* @summary An inverted [`Blaze.If`](#blaze_if).
|
|
* @locus Client
|
|
* @param {Function} conditionFunc A function to reactively re-run. If the result is falsy, `contentFunc` is shown, otherwise `elseFunc` is shown. An empty array is considered falsy.
|
|
* @param {Function} contentFunc A Function that returns [*renderable content*](#renderable_content).
|
|
* @param {Function} [elseFunc] Optional. A Function that returns [*renderable content*](#renderable_content). If no `elseFunc` is supplied, no content is shown in the "else" case.
|
|
*/
|
|
Blaze.Unless = function (conditionFunc, contentFunc, elseFunc) {
|
|
return Blaze.If(conditionFunc, contentFunc, elseFunc, true /*_not*/);
|
|
};
|
|
|
|
/**
|
|
* @summary Constructs a View that renders `contentFunc` for each item in a sequence.
|
|
* @locus Client
|
|
* @param {Function} argFunc A function to reactively re-run. The function can
|
|
* return one of two options:
|
|
*
|
|
* 1. An object with two fields: '_variable' and '_sequence'. Each iterates over
|
|
* '_sequence', it may be a Cursor, an array, null, or undefined. Inside the
|
|
* Each body you will be able to get the current item from the sequence using
|
|
* the name specified in the '_variable' field.
|
|
*
|
|
* 2. Just a sequence (Cursor, array, null, or undefined) not wrapped into an
|
|
* object. Inside the Each body, the current item will be set as the data
|
|
* context.
|
|
* @param {Function} contentFunc A Function that returns [*renderable
|
|
* content*](#renderable_content).
|
|
* @param {Function} [elseFunc] A Function that returns [*renderable
|
|
* content*](#renderable_content) to display in the case when there are no items
|
|
* in the sequence.
|
|
*/
|
|
Blaze.Each = function (argFunc, contentFunc, elseFunc) {
|
|
var eachView = Blaze.View('each', function () {
|
|
var subviews = this.initialSubviews;
|
|
this.initialSubviews = null;
|
|
if (this._isCreatedForExpansion) {
|
|
this.expandedValueDep = new Tracker.Dependency;
|
|
this.expandedValueDep.depend();
|
|
}
|
|
return subviews;
|
|
});
|
|
eachView.initialSubviews = [];
|
|
eachView.numItems = 0;
|
|
eachView.inElseMode = false;
|
|
eachView.stopHandle = null;
|
|
eachView.contentFunc = contentFunc;
|
|
eachView.elseFunc = elseFunc;
|
|
eachView.argVar = new ReactiveVar;
|
|
eachView.variableName = null;
|
|
|
|
// update the @index value in the scope of all subviews in the range
|
|
var updateIndices = function (from, to) {
|
|
if (to === undefined) {
|
|
to = eachView.numItems - 1;
|
|
}
|
|
|
|
for (var i = from; i <= to; i++) {
|
|
var view = eachView._domrange.members[i].view;
|
|
view._scopeBindings['@index'].set(i);
|
|
}
|
|
};
|
|
|
|
eachView.onViewCreated(function () {
|
|
// We evaluate argFunc in an autorun to make sure
|
|
// Blaze.currentView is always set when it runs (rather than
|
|
// passing argFunc straight to ObserveSequence).
|
|
eachView.autorun(function () {
|
|
// argFunc can return either a sequence as is or a wrapper object with a
|
|
// _sequence and _variable fields set.
|
|
var arg = argFunc();
|
|
if (_.isObject(arg) && _.has(arg, '_sequence')) {
|
|
eachView.variableName = arg._variable || null;
|
|
arg = arg._sequence;
|
|
}
|
|
|
|
eachView.argVar.set(arg);
|
|
}, eachView.parentView, 'collection');
|
|
|
|
eachView.stopHandle = ObserveSequence.observe(function () {
|
|
return eachView.argVar.get();
|
|
}, {
|
|
addedAt: function (id, item, index) {
|
|
Tracker.nonreactive(function () {
|
|
var newItemView;
|
|
if (eachView.variableName) {
|
|
// new-style #each (as in {{#each item in items}})
|
|
// doesn't create a new data context
|
|
newItemView = Blaze.View('item', eachView.contentFunc);
|
|
} else {
|
|
newItemView = Blaze.With(item, eachView.contentFunc);
|
|
}
|
|
|
|
eachView.numItems++;
|
|
|
|
var bindings = {};
|
|
bindings['@index'] = index;
|
|
if (eachView.variableName) {
|
|
bindings[eachView.variableName] = item;
|
|
}
|
|
Blaze._attachBindingsToView(bindings, newItemView);
|
|
|
|
if (eachView.expandedValueDep) {
|
|
eachView.expandedValueDep.changed();
|
|
} else if (eachView._domrange) {
|
|
if (eachView.inElseMode) {
|
|
eachView._domrange.removeMember(0);
|
|
eachView.inElseMode = false;
|
|
}
|
|
|
|
var range = Blaze._materializeView(newItemView, eachView);
|
|
eachView._domrange.addMember(range, index);
|
|
updateIndices(index);
|
|
} else {
|
|
eachView.initialSubviews.splice(index, 0, newItemView);
|
|
}
|
|
});
|
|
},
|
|
removedAt: function (id, item, index) {
|
|
Tracker.nonreactive(function () {
|
|
eachView.numItems--;
|
|
if (eachView.expandedValueDep) {
|
|
eachView.expandedValueDep.changed();
|
|
} else if (eachView._domrange) {
|
|
eachView._domrange.removeMember(index);
|
|
updateIndices(index);
|
|
if (eachView.elseFunc && eachView.numItems === 0) {
|
|
eachView.inElseMode = true;
|
|
eachView._domrange.addMember(
|
|
Blaze._materializeView(
|
|
Blaze.View('each_else',eachView.elseFunc),
|
|
eachView), 0);
|
|
}
|
|
} else {
|
|
eachView.initialSubviews.splice(index, 1);
|
|
}
|
|
});
|
|
},
|
|
changedAt: function (id, newItem, oldItem, index) {
|
|
Tracker.nonreactive(function () {
|
|
if (eachView.expandedValueDep) {
|
|
eachView.expandedValueDep.changed();
|
|
} else {
|
|
var itemView;
|
|
if (eachView._domrange) {
|
|
itemView = eachView._domrange.getMember(index).view;
|
|
} else {
|
|
itemView = eachView.initialSubviews[index];
|
|
}
|
|
if (eachView.variableName) {
|
|
itemView._scopeBindings[eachView.variableName].set(newItem);
|
|
} else {
|
|
itemView.dataVar.set(newItem);
|
|
}
|
|
}
|
|
});
|
|
},
|
|
movedTo: function (id, item, fromIndex, toIndex) {
|
|
Tracker.nonreactive(function () {
|
|
if (eachView.expandedValueDep) {
|
|
eachView.expandedValueDep.changed();
|
|
} else if (eachView._domrange) {
|
|
eachView._domrange.moveMember(fromIndex, toIndex);
|
|
updateIndices(
|
|
Math.min(fromIndex, toIndex), Math.max(fromIndex, toIndex));
|
|
} else {
|
|
var subviews = eachView.initialSubviews;
|
|
var itemView = subviews[fromIndex];
|
|
subviews.splice(fromIndex, 1);
|
|
subviews.splice(toIndex, 0, itemView);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
if (eachView.elseFunc && eachView.numItems === 0) {
|
|
eachView.inElseMode = true;
|
|
eachView.initialSubviews[0] =
|
|
Blaze.View('each_else', eachView.elseFunc);
|
|
}
|
|
});
|
|
|
|
eachView.onViewDestroyed(function () {
|
|
if (eachView.stopHandle)
|
|
eachView.stopHandle.stop();
|
|
});
|
|
|
|
return eachView;
|
|
};
|
|
|
|
Blaze._TemplateWith = function (arg, contentFunc) {
|
|
var w;
|
|
|
|
var argFunc = arg;
|
|
if (typeof arg !== 'function') {
|
|
argFunc = function () {
|
|
return arg;
|
|
};
|
|
}
|
|
|
|
// This is a little messy. When we compile `{{> Template.contentBlock}}`, we
|
|
// wrap it in Blaze._InOuterTemplateScope in order to skip the intermediate
|
|
// parent Views in the current template. However, when there's an argument
|
|
// (`{{> Template.contentBlock arg}}`), the argument needs to be evaluated
|
|
// in the original scope. There's no good order to nest
|
|
// Blaze._InOuterTemplateScope and Spacebars.TemplateWith to achieve this,
|
|
// so we wrap argFunc to run it in the "original parentView" of the
|
|
// Blaze._InOuterTemplateScope.
|
|
//
|
|
// To make this better, reconsider _InOuterTemplateScope as a primitive.
|
|
// Longer term, evaluate expressions in the proper lexical scope.
|
|
var wrappedArgFunc = function () {
|
|
var viewToEvaluateArg = null;
|
|
if (w.parentView && w.parentView.name === 'InOuterTemplateScope') {
|
|
viewToEvaluateArg = w.parentView.originalParentView;
|
|
}
|
|
if (viewToEvaluateArg) {
|
|
return Blaze._withCurrentView(viewToEvaluateArg, argFunc);
|
|
} else {
|
|
return argFunc();
|
|
}
|
|
};
|
|
|
|
var wrappedContentFunc = function () {
|
|
var content = contentFunc.call(this);
|
|
|
|
// Since we are generating the Blaze._TemplateWith view for the
|
|
// user, set the flag on the child view. If `content` is a template,
|
|
// construct the View so that we can set the flag.
|
|
if (content instanceof Blaze.Template) {
|
|
content = content.constructView();
|
|
}
|
|
if (content instanceof Blaze.View) {
|
|
content._hasGeneratedParent = true;
|
|
}
|
|
|
|
return content;
|
|
};
|
|
|
|
w = Blaze.With(wrappedArgFunc, wrappedContentFunc);
|
|
w.__isTemplateWith = true;
|
|
return w;
|
|
};
|
|
|
|
Blaze._InOuterTemplateScope = function (templateView, contentFunc) {
|
|
var view = Blaze.View('InOuterTemplateScope', contentFunc);
|
|
var parentView = templateView.parentView;
|
|
|
|
// Hack so that if you call `{{> foo bar}}` and it expands into
|
|
// `{{#with bar}}{{> foo}}{{/with}}`, and then `foo` is a template
|
|
// that inserts `{{> Template.contentBlock}}`, the data context for
|
|
// `Template.contentBlock` is not `bar` but the one enclosing that.
|
|
if (parentView.__isTemplateWith)
|
|
parentView = parentView.parentView;
|
|
|
|
view.onViewCreated(function () {
|
|
this.originalParentView = this.parentView;
|
|
this.parentView = parentView;
|
|
this.__childDoesntStartNewLexicalScope = true;
|
|
});
|
|
return view;
|
|
};
|
|
|
|
// XXX COMPAT WITH 0.9.0
|
|
Blaze.InOuterTemplateScope = Blaze._InOuterTemplateScope;
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
}).call(this);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(function(){
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// packages/blaze/lookup.js //
|
|
// //
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
Blaze._globalHelpers = {};
|
|
|
|
// Documented as Template.registerHelper.
|
|
// This definition also provides back-compat for `UI.registerHelper`.
|
|
Blaze.registerHelper = function (name, func) {
|
|
Blaze._globalHelpers[name] = func;
|
|
};
|
|
|
|
// Also documented as Template.deregisterHelper
|
|
Blaze.deregisterHelper = function(name) {
|
|
delete Blaze._globalHelpers[name];
|
|
};
|
|
|
|
var bindIfIsFunction = function (x, target) {
|
|
if (typeof x !== 'function')
|
|
return x;
|
|
return Blaze._bind(x, target);
|
|
};
|
|
|
|
// If `x` is a function, binds the value of `this` for that function
|
|
// to the current data context.
|
|
var bindDataContext = function (x) {
|
|
if (typeof x === 'function') {
|
|
return function () {
|
|
var data = Blaze.getData();
|
|
if (data == null)
|
|
data = {};
|
|
return x.apply(data, arguments);
|
|
};
|
|
}
|
|
return x;
|
|
};
|
|
|
|
Blaze._OLDSTYLE_HELPER = {};
|
|
|
|
Blaze._getTemplateHelper = function (template, name, tmplInstanceFunc) {
|
|
// XXX COMPAT WITH 0.9.3
|
|
var isKnownOldStyleHelper = false;
|
|
|
|
if (template.__helpers.has(name)) {
|
|
var helper = template.__helpers.get(name);
|
|
if (helper === Blaze._OLDSTYLE_HELPER) {
|
|
isKnownOldStyleHelper = true;
|
|
} else if (helper != null) {
|
|
return wrapHelper(bindDataContext(helper), tmplInstanceFunc);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// old-style helper
|
|
if (name in template) {
|
|
// Only warn once per helper
|
|
if (! isKnownOldStyleHelper) {
|
|
template.__helpers.set(name, Blaze._OLDSTYLE_HELPER);
|
|
if (! template._NOWARN_OLDSTYLE_HELPERS) {
|
|
Blaze._warn('Assigning helper with `' + template.viewName + '.' +
|
|
name + ' = ...` is deprecated. Use `' + template.viewName +
|
|
'.helpers(...)` instead.');
|
|
}
|
|
}
|
|
if (template[name] != null) {
|
|
return wrapHelper(bindDataContext(template[name]), tmplInstanceFunc);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
var wrapHelper = function (f, templateFunc) {
|
|
if (typeof f !== "function") {
|
|
return f;
|
|
}
|
|
|
|
return function () {
|
|
var self = this;
|
|
var args = arguments;
|
|
|
|
return Blaze.Template._withTemplateInstanceFunc(templateFunc, function () {
|
|
return Blaze._wrapCatchingExceptions(f, 'template helper').apply(self, args);
|
|
});
|
|
};
|
|
};
|
|
|
|
Blaze._lexicalBindingLookup = function (view, name) {
|
|
var currentView = view;
|
|
var blockHelpersStack = [];
|
|
|
|
// walk up the views stopping at a Spacebars.include or Template view that
|
|
// doesn't have an InOuterTemplateScope view as a parent
|
|
do {
|
|
// skip block helpers views
|
|
// if we found the binding on the scope, return it
|
|
if (_.has(currentView._scopeBindings, name)) {
|
|
var bindingReactiveVar = currentView._scopeBindings[name];
|
|
return function () {
|
|
return bindingReactiveVar.get();
|
|
};
|
|
}
|
|
} while (! (currentView.__startsNewLexicalScope &&
|
|
! (currentView.parentView &&
|
|
currentView.parentView.__childDoesntStartNewLexicalScope))
|
|
&& (currentView = currentView.parentView));
|
|
|
|
return null;
|
|
};
|
|
|
|
// templateInstance argument is provided to be available for possible
|
|
// alternative implementations of this function by 3rd party packages.
|
|
Blaze._getTemplate = function (name, templateInstance) {
|
|
if ((name in Blaze.Template) && (Blaze.Template[name] instanceof Blaze.Template)) {
|
|
return Blaze.Template[name];
|
|
}
|
|
return null;
|
|
};
|
|
|
|
Blaze._getGlobalHelper = function (name, templateInstance) {
|
|
if (Blaze._globalHelpers[name] != null) {
|
|
return wrapHelper(bindDataContext(Blaze._globalHelpers[name]), templateInstance);
|
|
}
|
|
return null;
|
|
};
|
|
|
|
// Looks up a name, like "foo" or "..", as a helper of the
|
|
// current template; the name of a template; a global helper;
|
|
// or a property of the data context. Called on the View of
|
|
// a template (i.e. a View with a `.template` property,
|
|
// where the helpers are). Used for the first name in a
|
|
// "path" in a template tag, like "foo" in `{{foo.bar}}` or
|
|
// ".." in `{{frobulate ../blah}}`.
|
|
//
|
|
// Returns a function, a non-function value, or null. If
|
|
// a function is found, it is bound appropriately.
|
|
//
|
|
// NOTE: This function must not establish any reactive
|
|
// dependencies itself. If there is any reactivity in the
|
|
// value, lookup should return a function.
|
|
Blaze.View.prototype.lookup = function (name, _options) {
|
|
var template = this.template;
|
|
var lookupTemplate = _options && _options.template;
|
|
var helper;
|
|
var binding;
|
|
var boundTmplInstance;
|
|
var foundTemplate;
|
|
|
|
if (this.templateInstance) {
|
|
boundTmplInstance = Blaze._bind(this.templateInstance, this);
|
|
}
|
|
|
|
// 0. looking up the parent data context with the special "../" syntax
|
|
if (/^\./.test(name)) {
|
|
// starts with a dot. must be a series of dots which maps to an
|
|
// ancestor of the appropriate height.
|
|
if (!/^(\.)+$/.test(name))
|
|
throw new Error("id starting with dot must be a series of dots");
|
|
|
|
return Blaze._parentData(name.length - 1, true /*_functionWrapped*/);
|
|
|
|
}
|
|
|
|
// 1. look up a helper on the current template
|
|
if (template && ((helper = Blaze._getTemplateHelper(template, name, boundTmplInstance)) != null)) {
|
|
return helper;
|
|
}
|
|
|
|
// 2. look up a binding by traversing the lexical view hierarchy inside the
|
|
// current template
|
|
if (template && (binding = Blaze._lexicalBindingLookup(Blaze.currentView, name)) != null) {
|
|
return binding;
|
|
}
|
|
|
|
// 3. look up a template by name
|
|
if (lookupTemplate && ((foundTemplate = Blaze._getTemplate(name, boundTmplInstance)) != null)) {
|
|
return foundTemplate;
|
|
}
|
|
|
|
// 4. look up a global helper
|
|
if ((helper = Blaze._getGlobalHelper(name, boundTmplInstance)) != null) {
|
|
return helper;
|
|
}
|
|
|
|
// 5. look up in a data context
|
|
return function () {
|
|
var isCalledAsFunction = (arguments.length > 0);
|
|
var data = Blaze.getData();
|
|
var x = data && data[name];
|
|
if (! x) {
|
|
if (lookupTemplate) {
|
|
throw new Error("No such template: " + name);
|
|
} else if (isCalledAsFunction) {
|
|
throw new Error("No such function: " + name);
|
|
} else if (name.charAt(0) === '@' && ((x === null) ||
|
|
(x === undefined))) {
|
|
// Throw an error if the user tries to use a `@directive`
|
|
// that doesn't exist. We don't implement all directives
|
|
// from Handlebars, so there's a potential for confusion
|
|
// if we fail silently. On the other hand, we want to
|
|
// throw late in case some app or package wants to provide
|
|
// a missing directive.
|
|
throw new Error("Unsupported directive: " + name);
|
|
}
|
|
}
|
|
if (! data) {
|
|
return null;
|
|
}
|
|
if (typeof x !== 'function') {
|
|
if (isCalledAsFunction) {
|
|
throw new Error("Can't call non-function: " + x);
|
|
}
|
|
return x;
|
|
}
|
|
return x.apply(data, arguments);
|
|
};
|
|
};
|
|
|
|
// Implement Spacebars' {{../..}}.
|
|
// @param height {Number} The number of '..'s
|
|
Blaze._parentData = function (height, _functionWrapped) {
|
|
// If height is null or undefined, we default to 1, the first parent.
|
|
if (height == null) {
|
|
height = 1;
|
|
}
|
|
var theWith = Blaze.getView('with');
|
|
for (var i = 0; (i < height) && theWith; i++) {
|
|
theWith = Blaze.getView(theWith, 'with');
|
|
}
|
|
|
|
if (! theWith)
|
|
return null;
|
|
if (_functionWrapped)
|
|
return function () { return theWith.dataVar.get(); };
|
|
return theWith.dataVar.get();
|
|
};
|
|
|
|
|
|
Blaze.View.prototype.lookupTemplate = function (name) {
|
|
return this.lookup(name, {template:true});
|
|
};
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
}).call(this);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(function(){
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// packages/blaze/template.js //
|
|
// //
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// [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
|
|
* @importFromPackage templating
|
|
*/
|
|
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
|
|
* @importFromPackage templating
|
|
*/
|
|
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
|
|
* @importFromPackage templating
|
|
*/
|
|
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} [options] If a function is passed instead of an
|
|
* object, it is interpreted as an `onReady` callback.
|
|
* @param {Function} [options.onReady] Passed to [`Meteor.subscribe`](#meteor_subscribe).
|
|
* @param {Function} [options.onStop] Passed to [`Meteor.subscribe`](#meteor_subscribe).
|
|
* @param {DDP.Connection} [options.connection] The connection on which to make the
|
|
* subscription.
|
|
*/
|
|
Blaze.TemplateInstance.prototype.subscribe = function (/* arguments */) {
|
|
var self = this;
|
|
|
|
var subHandles = self._subscriptionHandles;
|
|
var args = _.toArray(arguments);
|
|
|
|
// Duplicate logic from Meteor.subscribe
|
|
var options = {};
|
|
if (args.length) {
|
|
var lastParam = _.last(args);
|
|
|
|
// Match pattern to check if the last arg is an options argument
|
|
var lastParamOptionsPattern = {
|
|
onReady: Match.Optional(Function),
|
|
// XXX COMPAT WITH 1.0.3.1 onError used to exist, but now we use
|
|
// onStop with an error callback instead.
|
|
onError: Match.Optional(Function),
|
|
onStop: Match.Optional(Function),
|
|
connection: Match.Optional(Match.Any)
|
|
};
|
|
|
|
if (_.isFunction(lastParam)) {
|
|
options.onReady = args.pop();
|
|
} else if (lastParam && ! _.isEmpty(lastParam) && Match.test(lastParam, lastParamOptionsPattern)) {
|
|
options = args.pop();
|
|
}
|
|
}
|
|
|
|
var subHandle;
|
|
var oldStopped = options.onStop;
|
|
options.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);
|
|
}
|
|
};
|
|
|
|
var connection = options.connection;
|
|
var callbacks = _.pick(options, ["onReady", "onError", "onStop"]);
|
|
|
|
// The callbacks are passed as the last item in the arguments array passed to
|
|
// View#subscribe
|
|
args.push(callbacks);
|
|
|
|
// View#subscribe takes the connection as one of the options in the last
|
|
// argument
|
|
subHandle = self.view.subscribe.call(self.view, args, {
|
|
connection: connection
|
|
});
|
|
|
|
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.
|
|
* @importFromPackage templating
|
|
*/
|
|
Template.prototype.helpers = function (dict) {
|
|
if (! _.isObject(dict)) {
|
|
throw new Error("Helpers dictionary has to be an object");
|
|
}
|
|
|
|
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.
|
|
* @importFromPackage templating
|
|
*/
|
|
Template.prototype.events = function (eventMap) {
|
|
if (! _.isObject(eventMap)) {
|
|
throw new Error("Event map has to be an object");
|
|
}
|
|
|
|
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 = Blaze._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}
|
|
* @importFromPackage templating
|
|
*/
|
|
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 an event handler, returns the data context of the template on which
|
|
* this event handler was defined.
|
|
* - Inside a helper, returns the data context of the DOM node where the helper
|
|
* was used.
|
|
*
|
|
* Establishes a reactive dependency on the result.
|
|
* @locus Client
|
|
* @function
|
|
* @importFromPackage templating
|
|
*/
|
|
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.
|
|
* @importFromPackage templating
|
|
*/
|
|
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.
|
|
* @importFromPackage templating
|
|
*/
|
|
Template.registerHelper = Blaze.registerHelper;
|
|
|
|
/**
|
|
* @summary Removes a global [helper function](#template_helpers).
|
|
* @locus Client
|
|
* @function
|
|
* @param {String} name The name of the helper function you are defining.
|
|
* @importFromPackage templating
|
|
*/
|
|
Template.deregisterHelper = Blaze.deregisterHelper;
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
}).call(this);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(function(){
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// packages/blaze/backcompat.js //
|
|
// //
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
UI = Blaze;
|
|
|
|
Blaze.ReactiveVar = ReactiveVar;
|
|
UI._templateInstance = Blaze.Template.instance;
|
|
|
|
Handlebars = {};
|
|
Handlebars.registerHelper = Blaze.registerHelper;
|
|
|
|
Handlebars._escape = Blaze._escape;
|
|
|
|
// Return these from {{...}} helpers to achieve the same as returning
|
|
// strings from {{{...}}} helpers
|
|
Handlebars.SafeString = function(string) {
|
|
this.string = string;
|
|
};
|
|
Handlebars.SafeString.prototype.toString = function() {
|
|
return this.string.toString();
|
|
};
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
}).call(this);
|
|
|
|
|
|
/* Exports */
|
|
Package._define("blaze", {
|
|
Blaze: Blaze,
|
|
UI: UI,
|
|
Handlebars: Handlebars
|
|
});
|
|
|
|
})();
|