////////////////////////////////////////////////////////////////////////// // // // This is a generated file. You can view the original // // source in your browser if your browser supports source maps. // // // // If you are using Chrome, open the Developer Tools and click the gear // // icon in its lower right corner. In the General Settings panel, turn // // on 'Enable source maps'. // // // // If you are using Firefox 23, go to `about:config` and set the // // `devtools.debugger.source-maps-enabled` preference to true. // // (The preference should be on by default in Firefox 24; versions // // older than 23 do not support source maps.) // // // ////////////////////////////////////////////////////////////////////////// (function () { /* Imports */ var Meteor = Package.meteor.Meteor; var $ = Package.jquery.$; var jQuery = Package.jquery.jQuery; var Tracker = Package.tracker.Tracker; var Deps = Package.tracker.Deps; var _ = Package.underscore._; var HTML = Package.htmljs.HTML; var ObserveSequence = Package['observe-sequence'].ObserveSequence; var ReactiveVar = Package['reactive-var'].ReactiveVar; /* Package-scope variables */ var Blaze, UI, Handlebars, AttributeHandler, makeAttributeHandler, ElementAttributesUpdater; (function () { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // // packages/blaze/preamble.js // // // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // /** // 1 * @namespace Blaze // 2 * @summary The namespace for all Blaze-related methods and classes. // 3 */ // 4 Blaze = {}; // 5 // 6 // Utility to HTML-escape a string. Included for legacy reasons. // 7 Blaze._escape = (function() { // 8 var escape_map = { // 9 "<": "<", // 10 ">": ">", // 11 '"': """, // 12 "'": "'", // 13 "`": "`", /* IE allows backtick-delimited attributes?? */ // 14 "&": "&" // 15 }; // 16 var escape_one = function(c) { // 17 return escape_map[c]; // 18 }; // 19 // 20 return function (x) { // 21 return x.replace(/[&<>"'`]/g, escape_one); // 22 }; // 23 })(); // 24 // 25 Blaze._warn = function (msg) { // 26 msg = 'Warning: ' + msg; // 27 // 28 if ((typeof Log !== 'undefined') && Log && Log.warn) // 29 Log.warn(msg); // use Meteor's "logging" package // 30 else if ((typeof console !== 'undefined') && console.log) // 31 console.log(msg); // 32 }; // 33 // 34 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// }).call(this); (function () { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // // packages/blaze/dombackend.js // // // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // var DOMBackend = {}; // 1 Blaze._DOMBackend = DOMBackend; // 2 // 3 var $jq = (typeof jQuery !== 'undefined' ? jQuery : // 4 (typeof Package !== 'undefined' ? // 5 Package.jquery && Package.jquery.jQuery : null)); // 6 if (! $jq) // 7 throw new Error("jQuery not found"); // 8 // 9 DOMBackend._$jq = $jq; // 10 // 11 DOMBackend.parseHTML = function (html) { // 12 // Return an array of nodes. // 13 // // 14 // jQuery does fancy stuff like creating an appropriate // 15 // container element and setting innerHTML on it, as well // 16 // as working around various IE quirks. // 17 return $jq.parseHTML(html) || []; // 18 }; // 19 // 20 DOMBackend.Events = { // 21 // `selector` is non-null. `type` is one type (but // 22 // may be in backend-specific form, e.g. have namespaces). // 23 // Order fired must be order bound. // 24 delegateEvents: function (elem, type, selector, handler) { // 25 $jq(elem).on(type, selector, handler); // 26 }, // 27 // 28 undelegateEvents: function (elem, type, handler) { // 29 $jq(elem).off(type, '**', handler); // 30 }, // 31 // 32 bindEventCapturer: function (elem, type, selector, handler) { // 33 var $elem = $jq(elem); // 34 // 35 var wrapper = function (event) { // 36 event = $jq.event.fix(event); // 37 event.currentTarget = event.target; // 38 // 39 // Note: It might improve jQuery interop if we called into jQuery // 40 // here somehow. Since we don't use jQuery to dispatch the event, // 41 // we don't fire any of jQuery's event hooks or anything. However, // 42 // since jQuery can't bind capturing handlers, it's not clear // 43 // where we would hook in. Internal jQuery functions like `dispatch` // 44 // are too high-level. // 45 var $target = $jq(event.currentTarget); // 46 if ($target.is($elem.find(selector))) // 47 handler.call(elem, event); // 48 }; // 49 // 50 handler._meteorui_wrapper = wrapper; // 51 // 52 type = DOMBackend.Events.parseEventType(type); // 53 // add *capturing* event listener // 54 elem.addEventListener(type, wrapper, true); // 55 }, // 56 // 57 unbindEventCapturer: function (elem, type, handler) { // 58 type = DOMBackend.Events.parseEventType(type); // 59 elem.removeEventListener(type, handler._meteorui_wrapper, true); // 60 }, // 61 // 62 parseEventType: function (type) { // 63 // strip off namespaces // 64 var dotLoc = type.indexOf('.'); // 65 if (dotLoc >= 0) // 66 return type.slice(0, dotLoc); // 67 return type; // 68 } // 69 }; // 70 // 71 // 72 ///// Removal detection and interoperability. // 73 // 74 // For an explanation of this technique, see: // 75 // http://bugs.jquery.com/ticket/12213#comment:23 . // 76 // // 77 // In short, an element is considered "removed" when jQuery // 78 // cleans up its *private* userdata on the element, // 79 // which we can detect using a custom event with a teardown // 80 // hook. // 81 // 82 var NOOP = function () {}; // 83 // 84 // Circular doubly-linked list // 85 var TeardownCallback = function (func) { // 86 this.next = this; // 87 this.prev = this; // 88 this.func = func; // 89 }; // 90 // 91 // Insert newElt before oldElt in the circular list // 92 TeardownCallback.prototype.linkBefore = function(oldElt) { // 93 this.prev = oldElt.prev; // 94 this.next = oldElt; // 95 oldElt.prev.next = this; // 96 oldElt.prev = this; // 97 }; // 98 // 99 TeardownCallback.prototype.unlink = function () { // 100 this.prev.next = this.next; // 101 this.next.prev = this.prev; // 102 }; // 103 // 104 TeardownCallback.prototype.go = function () { // 105 var func = this.func; // 106 func && func(); // 107 }; // 108 // 109 TeardownCallback.prototype.stop = TeardownCallback.prototype.unlink; // 110 // 111 DOMBackend.Teardown = { // 112 _JQUERY_EVENT_NAME: 'blaze_teardown_watcher', // 113 _CB_PROP: '$blaze_teardown_callbacks', // 114 // Registers a callback function to be called when the given element or // 115 // one of its ancestors is removed from the DOM via the backend library. // 116 // The callback function is called at most once, and it receives the element // 117 // in question as an argument. // 118 onElementTeardown: function (elem, func) { // 119 var elt = new TeardownCallback(func); // 120 // 121 var propName = DOMBackend.Teardown._CB_PROP; // 122 if (! elem[propName]) { // 123 // create an empty node that is never unlinked // 124 elem[propName] = new TeardownCallback; // 125 // 126 // Set up the event, only the first time. // 127 $jq(elem).on(DOMBackend.Teardown._JQUERY_EVENT_NAME, NOOP); // 128 } // 129 // 130 elt.linkBefore(elem[propName]); // 131 // 132 return elt; // so caller can call stop() // 133 }, // 134 // Recursively call all teardown hooks, in the backend and registered // 135 // through DOMBackend.onElementTeardown. // 136 tearDownElement: function (elem) { // 137 var elems = []; // 138 // Array.prototype.slice.call doesn't work when given a NodeList in // 139 // IE8 ("JScript object expected"). // 140 var nodeList = elem.getElementsByTagName('*'); // 141 for (var i = 0; i < nodeList.length; i++) { // 142 elems.push(nodeList[i]); // 143 } // 144 elems.push(elem); // 145 $jq.cleanData(elems); // 146 } // 147 }; // 148 // 149 $jq.event.special[DOMBackend.Teardown._JQUERY_EVENT_NAME] = { // 150 setup: function () { // 151 // This "setup" callback is important even though it is empty! // 152 // Without it, jQuery will call addEventListener, which is a // 153 // performance hit, especially with Chrome's async stack trace // 154 // feature enabled. // 155 }, // 156 teardown: function() { // 157 var elem = this; // 158 var callbacks = elem[DOMBackend.Teardown._CB_PROP]; // 159 if (callbacks) { // 160 var elt = callbacks.next; // 161 while (elt !== callbacks) { // 162 elt.go(); // 163 elt = elt.next; // 164 } // 165 callbacks.go(); // 166 // 167 elem[DOMBackend.Teardown._CB_PROP] = null; // 168 } // 169 } // 170 }; // 171 // 172 // 173 // Must use jQuery semantics for `context`, not // 174 // querySelectorAll's. In other words, all the parts // 175 // of `selector` must be found under `context`. // 176 DOMBackend.findBySelector = function (selector, context) { // 177 return $jq(selector, context); // 178 }; // 179 // 180 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// }).call(this); (function () { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // // packages/blaze/domrange.js // // // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // 1 // A constant empty array (frozen if the JS engine supports it). // 2 var _emptyArray = Object.freeze ? Object.freeze([]) : []; // 3 // 4 // `[new] Blaze._DOMRange([nodeAndRangeArray])` // 5 // // 6 // A DOMRange consists of an array of consecutive nodes and DOMRanges, // 7 // which may be replaced at any time with a new array. If the DOMRange // 8 // has been attached to the DOM at some location, then updating // 9 // the array will cause the DOM to be updated at that location. // 10 Blaze._DOMRange = function (nodeAndRangeArray) { // 11 if (! (this instanceof DOMRange)) // 12 // called without `new` // 13 return new DOMRange(nodeAndRangeArray); // 14 // 15 var members = (nodeAndRangeArray || _emptyArray); // 16 if (! (members && (typeof members.length) === 'number')) // 17 throw new Error("Expected array"); // 18 // 19 for (var i = 0; i < members.length; i++) // 20 this._memberIn(members[i]); // 21 // 22 this.members = members; // 23 this.emptyRangePlaceholder = null; // 24 this.attached = false; // 25 this.parentElement = null; // 26 this.parentRange = null; // 27 this.attachedCallbacks = _emptyArray; // 28 }; // 29 var DOMRange = Blaze._DOMRange; // 30 // 31 // In IE 8, don't use empty text nodes as placeholders // 32 // in empty DOMRanges, use comment nodes instead. Using // 33 // empty text nodes in modern browsers is great because // 34 // it doesn't clutter the web inspector. In IE 8, however, // 35 // it seems to lead in some roundabout way to the OAuth // 36 // pop-up crashing the browser completely. In the past, // 37 // we didn't use empty text nodes on IE 8 because they // 38 // don't accept JS properties, so just use the same logic // 39 // even though we don't need to set properties on the // 40 // placeholder anymore. // 41 DOMRange._USE_COMMENT_PLACEHOLDERS = (function () { // 42 var result = false; // 43 var textNode = document.createTextNode(""); // 44 try { // 45 textNode.someProp = true; // 46 } catch (e) { // 47 // IE 8 // 48 result = true; // 49 } // 50 return result; // 51 })(); // 52 // 53 // static methods // 54 DOMRange._insert = function (rangeOrNode, parentElement, nextNode, _isMove) { // 55 var m = rangeOrNode; // 56 if (m instanceof DOMRange) { // 57 m.attach(parentElement, nextNode, _isMove); // 58 } else { // 59 if (_isMove) // 60 DOMRange._moveNodeWithHooks(m, parentElement, nextNode); // 61 else // 62 DOMRange._insertNodeWithHooks(m, parentElement, nextNode); // 63 } // 64 }; // 65 // 66 DOMRange._remove = function (rangeOrNode) { // 67 var m = rangeOrNode; // 68 if (m instanceof DOMRange) { // 69 m.detach(); // 70 } else { // 71 DOMRange._removeNodeWithHooks(m); // 72 } // 73 }; // 74 // 75 DOMRange._removeNodeWithHooks = function (n) { // 76 if (! n.parentNode) // 77 return; // 78 if (n.nodeType === 1 && // 79 n.parentNode._uihooks && n.parentNode._uihooks.removeElement) { // 80 n.parentNode._uihooks.removeElement(n); // 81 } else { // 82 n.parentNode.removeChild(n); // 83 } // 84 }; // 85 // 86 DOMRange._insertNodeWithHooks = function (n, parent, next) { // 87 // `|| null` because IE throws an error if 'next' is undefined // 88 next = next || null; // 89 if (n.nodeType === 1 && // 90 parent._uihooks && parent._uihooks.insertElement) { // 91 parent._uihooks.insertElement(n, next); // 92 } else { // 93 parent.insertBefore(n, next); // 94 } // 95 }; // 96 // 97 DOMRange._moveNodeWithHooks = function (n, parent, next) { // 98 if (n.parentNode !== parent) // 99 return; // 100 // `|| null` because IE throws an error if 'next' is undefined // 101 next = next || null; // 102 if (n.nodeType === 1 && // 103 parent._uihooks && parent._uihooks.moveElement) { // 104 parent._uihooks.moveElement(n, next); // 105 } else { // 106 parent.insertBefore(n, next); // 107 } // 108 }; // 109 // 110 DOMRange.forElement = function (elem) { // 111 if (elem.nodeType !== 1) // 112 throw new Error("Expected element, found: " + elem); // 113 var range = null; // 114 while (elem && ! range) { // 115 range = (elem.$blaze_range || null); // 116 if (! range) // 117 elem = elem.parentNode; // 118 } // 119 return range; // 120 }; // 121 // 122 DOMRange.prototype.attach = function (parentElement, nextNode, _isMove, _isReplace) { // 123 // This method is called to insert the DOMRange into the DOM for // 124 // the first time, but it's also used internally when // 125 // updating the DOM. // 126 // // 127 // If _isMove is true, move this attached range to a different // 128 // location under the same parentElement. // 129 if (_isMove || _isReplace) { // 130 if (! (this.parentElement === parentElement && // 131 this.attached)) // 132 throw new Error("Can only move or replace an attached DOMRange, and only under the same parent element"); // 133 } // 134 // 135 var members = this.members; // 136 if (members.length) { // 137 this.emptyRangePlaceholder = null; // 138 for (var i = 0; i < members.length; i++) { // 139 DOMRange._insert(members[i], parentElement, nextNode, _isMove); // 140 } // 141 } else { // 142 var placeholder = ( // 143 DOMRange._USE_COMMENT_PLACEHOLDERS ? // 144 document.createComment("") : // 145 document.createTextNode("")); // 146 this.emptyRangePlaceholder = placeholder; // 147 parentElement.insertBefore(placeholder, nextNode || null); // 148 } // 149 this.attached = true; // 150 this.parentElement = parentElement; // 151 // 152 if (! (_isMove || _isReplace)) { // 153 for(var i = 0; i < this.attachedCallbacks.length; i++) { // 154 var obj = this.attachedCallbacks[i]; // 155 obj.attached && obj.attached(this, parentElement); // 156 } // 157 } // 158 }; // 159 // 160 DOMRange.prototype.setMembers = function (newNodeAndRangeArray) { // 161 var newMembers = newNodeAndRangeArray; // 162 if (! (newMembers && (typeof newMembers.length) === 'number')) // 163 throw new Error("Expected array"); // 164 // 165 var oldMembers = this.members; // 166 // 167 for (var i = 0; i < oldMembers.length; i++) // 168 this._memberOut(oldMembers[i]); // 169 for (var i = 0; i < newMembers.length; i++) // 170 this._memberIn(newMembers[i]); // 171 // 172 if (! this.attached) { // 173 this.members = newMembers; // 174 } else { // 175 // don't do anything if we're going from empty to empty // 176 if (newMembers.length || oldMembers.length) { // 177 // detach the old members and insert the new members // 178 var nextNode = this.lastNode().nextSibling; // 179 var parentElement = this.parentElement; // 180 // Use detach/attach, but don't fire attached/detached hooks // 181 this.detach(true /*_isReplace*/); // 182 this.members = newMembers; // 183 this.attach(parentElement, nextNode, false, true /*_isReplace*/); // 184 } // 185 } // 186 }; // 187 // 188 DOMRange.prototype.firstNode = function () { // 189 if (! this.attached) // 190 throw new Error("Must be attached"); // 191 // 192 if (! this.members.length) // 193 return this.emptyRangePlaceholder; // 194 // 195 var m = this.members[0]; // 196 return (m instanceof DOMRange) ? m.firstNode() : m; // 197 }; // 198 // 199 DOMRange.prototype.lastNode = function () { // 200 if (! this.attached) // 201 throw new Error("Must be attached"); // 202 // 203 if (! this.members.length) // 204 return this.emptyRangePlaceholder; // 205 // 206 var m = this.members[this.members.length - 1]; // 207 return (m instanceof DOMRange) ? m.lastNode() : m; // 208 }; // 209 // 210 DOMRange.prototype.detach = function (_isReplace) { // 211 if (! this.attached) // 212 throw new Error("Must be attached"); // 213 // 214 var oldParentElement = this.parentElement; // 215 var members = this.members; // 216 if (members.length) { // 217 for (var i = 0; i < members.length; i++) { // 218 DOMRange._remove(members[i]); // 219 } // 220 } else { // 221 var placeholder = this.emptyRangePlaceholder; // 222 this.parentElement.removeChild(placeholder); // 223 this.emptyRangePlaceholder = null; // 224 } // 225 // 226 if (! _isReplace) { // 227 this.attached = false; // 228 this.parentElement = null; // 229 // 230 for(var i = 0; i < this.attachedCallbacks.length; i++) { // 231 var obj = this.attachedCallbacks[i]; // 232 obj.detached && obj.detached(this, oldParentElement); // 233 } // 234 } // 235 }; // 236 // 237 DOMRange.prototype.addMember = function (newMember, atIndex, _isMove) { // 238 var members = this.members; // 239 if (! (atIndex >= 0 && atIndex <= members.length)) // 240 throw new Error("Bad index in range.addMember: " + atIndex); // 241 // 242 if (! _isMove) // 243 this._memberIn(newMember); // 244 // 245 if (! this.attached) { // 246 // currently detached; just updated members // 247 members.splice(atIndex, 0, newMember); // 248 } else if (members.length === 0) { // 249 // empty; use the empty-to-nonempty handling of setMembers // 250 this.setMembers([newMember]); // 251 } else { // 252 var nextNode; // 253 if (atIndex === members.length) { // 254 // insert at end // 255 nextNode = this.lastNode().nextSibling; // 256 } else { // 257 var m = members[atIndex]; // 258 nextNode = (m instanceof DOMRange) ? m.firstNode() : m; // 259 } // 260 members.splice(atIndex, 0, newMember); // 261 DOMRange._insert(newMember, this.parentElement, nextNode, _isMove); // 262 } // 263 }; // 264 // 265 DOMRange.prototype.removeMember = function (atIndex, _isMove) { // 266 var members = this.members; // 267 if (! (atIndex >= 0 && atIndex < members.length)) // 268 throw new Error("Bad index in range.removeMember: " + atIndex); // 269 // 270 if (_isMove) { // 271 members.splice(atIndex, 1); // 272 } else { // 273 var oldMember = members[atIndex]; // 274 this._memberOut(oldMember); // 275 // 276 if (members.length === 1) { // 277 // becoming empty; use the logic in setMembers // 278 this.setMembers(_emptyArray); // 279 } else { // 280 members.splice(atIndex, 1); // 281 if (this.attached) // 282 DOMRange._remove(oldMember); // 283 } // 284 } // 285 }; // 286 // 287 DOMRange.prototype.moveMember = function (oldIndex, newIndex) { // 288 var member = this.members[oldIndex]; // 289 this.removeMember(oldIndex, true /*_isMove*/); // 290 this.addMember(member, newIndex, true /*_isMove*/); // 291 }; // 292 // 293 DOMRange.prototype.getMember = function (atIndex) { // 294 var members = this.members; // 295 if (! (atIndex >= 0 && atIndex < members.length)) // 296 throw new Error("Bad index in range.getMember: " + atIndex); // 297 return this.members[atIndex]; // 298 }; // 299 // 300 DOMRange.prototype._memberIn = function (m) { // 301 if (m instanceof DOMRange) // 302 m.parentRange = this; // 303 else if (m.nodeType === 1) // DOM Element // 304 m.$blaze_range = this; // 305 }; // 306 // 307 DOMRange._destroy = function (m, _skipNodes) { // 308 if (m instanceof DOMRange) { // 309 if (m.view) // 310 Blaze._destroyView(m.view, _skipNodes); // 311 } else if ((! _skipNodes) && m.nodeType === 1) { // 312 // DOM Element // 313 if (m.$blaze_range) { // 314 Blaze._destroyNode(m); // 315 m.$blaze_range = null; // 316 } // 317 } // 318 }; // 319 // 320 DOMRange.prototype._memberOut = DOMRange._destroy; // 321 // 322 // Tear down, but don't remove, the members. Used when chunks // 323 // of DOM are being torn down or replaced. // 324 DOMRange.prototype.destroyMembers = function (_skipNodes) { // 325 var members = this.members; // 326 for (var i = 0; i < members.length; i++) // 327 this._memberOut(members[i], _skipNodes); // 328 }; // 329 // 330 DOMRange.prototype.destroy = function (_skipNodes) { // 331 DOMRange._destroy(this, _skipNodes); // 332 }; // 333 // 334 DOMRange.prototype.containsElement = function (elem) { // 335 if (! this.attached) // 336 throw new Error("Must be attached"); // 337 // 338 // An element is contained in this DOMRange if it's possible to // 339 // reach it by walking parent pointers, first through the DOM and // 340 // then parentRange pointers. In other words, the element or some // 341 // ancestor of it is at our level of the DOM (a child of our // 342 // parentElement), and this element is one of our members or // 343 // is a member of a descendant Range. // 344 // 345 // First check that elem is a descendant of this.parentElement, // 346 // according to the DOM. // 347 if (! Blaze._elementContains(this.parentElement, elem)) // 348 return false; // 349 // 350 // If elem is not an immediate child of this.parentElement, // 351 // walk up to its ancestor that is. // 352 while (elem.parentNode !== this.parentElement) // 353 elem = elem.parentNode; // 354 // 355 var range = elem.$blaze_range; // 356 while (range && range !== this) // 357 range = range.parentRange; // 358 // 359 return range === this; // 360 }; // 361 // 362 DOMRange.prototype.containsRange = function (range) { // 363 if (! this.attached) // 364 throw new Error("Must be attached"); // 365 // 366 if (! range.attached) // 367 return false; // 368 // 369 // A DOMRange is contained in this DOMRange if it's possible // 370 // to reach this range by following parent pointers. If the // 371 // DOMRange has the same parentElement, then it should be // 372 // a member, or a member of a member etc. Otherwise, we must // 373 // contain its parentElement. // 374 // 375 if (range.parentElement !== this.parentElement) // 376 return this.containsElement(range.parentElement); // 377 // 378 if (range === this) // 379 return false; // don't contain self // 380 // 381 while (range && range !== this) // 382 range = range.parentRange; // 383 // 384 return range === this; // 385 }; // 386 // 387 DOMRange.prototype.onAttached = function (attached) { // 388 this.onAttachedDetached({ attached: attached }); // 389 }; // 390 // 391 // callbacks are `attached(range, element)` and // 392 // `detached(range, element)`, and they may // 393 // access the `callbacks` object in `this`. // 394 // The arguments to `detached` are the same // 395 // range and element that were passed to `attached`. // 396 DOMRange.prototype.onAttachedDetached = function (callbacks) { // 397 if (this.attachedCallbacks === _emptyArray) // 398 this.attachedCallbacks = []; // 399 this.attachedCallbacks.push(callbacks); // 400 }; // 401 // 402 DOMRange.prototype.$ = function (selector) { // 403 var self = this; // 404 // 405 var parentNode = this.parentElement; // 406 if (! parentNode) // 407 throw new Error("Can't select in removed DomRange"); // 408 // 409 // Strategy: Find all selector matches under parentNode, // 410 // then filter out the ones that aren't in this DomRange // 411 // using `DOMRange#containsElement`. This is // 412 // asymptotically slow in the presence of O(N) sibling // 413 // content that is under parentNode but not in our range, // 414 // so if performance is an issue, the selector should be // 415 // run on a child element. // 416 // 417 // Since jQuery can't run selectors on a DocumentFragment, // 418 // we don't expect findBySelector to work. // 419 if (parentNode.nodeType === 11 /* DocumentFragment */) // 420 throw new Error("Can't use $ on an offscreen range"); // 421 // 422 var results = Blaze._DOMBackend.findBySelector(selector, parentNode); // 423 // 424 // We don't assume `results` has jQuery API; a plain array // 425 // should do just as well. However, if we do have a jQuery // 426 // array, we want to end up with one also, so we use // 427 // `.filter`. // 428 // 429 // Function that selects only elements that are actually // 430 // in this DomRange, rather than simply descending from // 431 // `parentNode`. // 432 var filterFunc = function (elem) { // 433 // handle jQuery's arguments to filter, where the node // 434 // is in `this` and the index is the first argument. // 435 if (typeof elem === 'number') // 436 elem = this; // 437 // 438 return self.containsElement(elem); // 439 }; // 440 // 441 if (! results.filter) { // 442 // not a jQuery array, and not a browser with // 443 // Array.prototype.filter (e.g. IE <9) // 444 var newResults = []; // 445 for (var i = 0; i < results.length; i++) { // 446 var x = results[i]; // 447 if (filterFunc(x)) // 448 newResults.push(x); // 449 } // 450 results = newResults; // 451 } else { // 452 // `results.filter` is either jQuery's or ECMAScript's `filter` // 453 results = results.filter(filterFunc); // 454 } // 455 // 456 return results; // 457 }; // 458 // 459 // Returns true if element a contains node b and is not node b. // 460 // // 461 // The restriction that `a` be an element (not a document fragment, // 462 // say) is based on what's easy to implement cross-browser. // 463 Blaze._elementContains = function (a, b) { // 464 if (a.nodeType !== 1) // ELEMENT // 465 return false; // 466 if (a === b) // 467 return false; // 468 // 469 if (a.compareDocumentPosition) { // 470 return a.compareDocumentPosition(b) & 0x10; // 471 } else { // 472 // Should be only old IE and maybe other old browsers here. // 473 // Modern Safari has both functions but seems to get contains() wrong. // 474 // IE can't handle b being a text node. We work around this // 475 // by doing a direct parent test now. // 476 b = b.parentNode; // 477 if (! (b && b.nodeType === 1)) // ELEMENT // 478 return false; // 479 if (a === b) // 480 return true; // 481 // 482 return a.contains(b); // 483 } // 484 }; // 485 // 486 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// }).call(this); (function () { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // // packages/blaze/events.js // // // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // var EventSupport = Blaze._EventSupport = {}; // 1 // 2 var DOMBackend = Blaze._DOMBackend; // 3 // 4 // List of events to always delegate, never capture. // 5 // Since jQuery fakes bubbling for certain events in // 6 // certain browsers (like `submit`), we don't want to // 7 // get in its way. // 8 // // 9 // We could list all known bubbling // 10 // events here to avoid creating speculative capturers // 11 // for them, but it would only be an optimization. // 12 var eventsToDelegate = EventSupport.eventsToDelegate = { // 13 blur: 1, change: 1, click: 1, focus: 1, focusin: 1, // 14 focusout: 1, reset: 1, submit: 1 // 15 }; // 16 // 17 var EVENT_MODE = EventSupport.EVENT_MODE = { // 18 TBD: 0, // 19 BUBBLING: 1, // 20 CAPTURING: 2 // 21 }; // 22 // 23 var NEXT_HANDLERREC_ID = 1; // 24 // 25 var HandlerRec = function (elem, type, selector, handler, recipient) { // 26 this.elem = elem; // 27 this.type = type; // 28 this.selector = selector; // 29 this.handler = handler; // 30 this.recipient = recipient; // 31 this.id = (NEXT_HANDLERREC_ID++); // 32 // 33 this.mode = EVENT_MODE.TBD; // 34 // 35 // It's important that delegatedHandler be a different // 36 // instance for each handlerRecord, because its identity // 37 // is used to remove it. // 38 // // 39 // It's also important that the closure have access to // 40 // `this` when it is not called with it set. // 41 this.delegatedHandler = (function (h) { // 42 return function (evt) { // 43 if ((! h.selector) && evt.currentTarget !== evt.target) // 44 // no selector means only fire on target // 45 return; // 46 return h.handler.apply(h.recipient, arguments); // 47 }; // 48 })(this); // 49 // 50 // WHY CAPTURE AND DELEGATE: jQuery can't delegate // 51 // non-bubbling events, because // 52 // event capture doesn't work in IE 8. However, there // 53 // are all sorts of new-fangled non-bubbling events // 54 // like "play" and "touchenter". We delegate these // 55 // events using capture in all browsers except IE 8. // 56 // IE 8 doesn't support these events anyway. // 57 // 58 var tryCapturing = elem.addEventListener && // 59 (! _.has(eventsToDelegate, // 60 DOMBackend.Events.parseEventType(type))); // 61 // 62 if (tryCapturing) { // 63 this.capturingHandler = (function (h) { // 64 return function (evt) { // 65 if (h.mode === EVENT_MODE.TBD) { // 66 // must be first time we're called. // 67 if (evt.bubbles) { // 68 // this type of event bubbles, so don't // 69 // get called again. // 70 h.mode = EVENT_MODE.BUBBLING; // 71 DOMBackend.Events.unbindEventCapturer( // 72 h.elem, h.type, h.capturingHandler); // 73 return; // 74 } else { // 75 // this type of event doesn't bubble, // 76 // so unbind the delegation, preventing // 77 // it from ever firing. // 78 h.mode = EVENT_MODE.CAPTURING; // 79 DOMBackend.Events.undelegateEvents( // 80 h.elem, h.type, h.delegatedHandler); // 81 } // 82 } // 83 // 84 h.delegatedHandler(evt); // 85 }; // 86 })(this); // 87 // 88 } else { // 89 this.mode = EVENT_MODE.BUBBLING; // 90 } // 91 }; // 92 EventSupport.HandlerRec = HandlerRec; // 93 // 94 HandlerRec.prototype.bind = function () { // 95 // `this.mode` may be EVENT_MODE_TBD, in which case we bind both. in // 96 // this case, 'capturingHandler' is in charge of detecting the // 97 // correct mode and turning off one or the other handlers. // 98 if (this.mode !== EVENT_MODE.BUBBLING) { // 99 DOMBackend.Events.bindEventCapturer( // 100 this.elem, this.type, this.selector || '*', // 101 this.capturingHandler); // 102 } // 103 // 104 if (this.mode !== EVENT_MODE.CAPTURING) // 105 DOMBackend.Events.delegateEvents( // 106 this.elem, this.type, // 107 this.selector || '*', this.delegatedHandler); // 108 }; // 109 // 110 HandlerRec.prototype.unbind = function () { // 111 if (this.mode !== EVENT_MODE.BUBBLING) // 112 DOMBackend.Events.unbindEventCapturer(this.elem, this.type, // 113 this.capturingHandler); // 114 // 115 if (this.mode !== EVENT_MODE.CAPTURING) // 116 DOMBackend.Events.undelegateEvents(this.elem, this.type, // 117 this.delegatedHandler); // 118 }; // 119 // 120 EventSupport.listen = function (element, events, selector, handler, recipient, getParentRecipient) { // 121 // 122 // Prevent this method from being JITed by Safari. Due to a // 123 // presumed JIT bug in Safari -- observed in Version 7.0.6 // 124 // (9537.78.2) -- this method may crash the Safari render process if // 125 // it is JITed. // 126 // Repro: https://github.com/dgreensp/public/tree/master/safari-crash // 127 try { element = element; } finally {} // 128 // 129 var eventTypes = []; // 130 events.replace(/[^ /]+/g, function (e) { // 131 eventTypes.push(e); // 132 }); // 133 // 134 var newHandlerRecs = []; // 135 for (var i = 0, N = eventTypes.length; i < N; i++) { // 136 var type = eventTypes[i]; // 137 // 138 var eventDict = element.$blaze_events; // 139 if (! eventDict) // 140 eventDict = (element.$blaze_events = {}); // 141 // 142 var info = eventDict[type]; // 143 if (! info) { // 144 info = eventDict[type] = {}; // 145 info.handlers = []; // 146 } // 147 var handlerList = info.handlers; // 148 var handlerRec = new HandlerRec( // 149 element, type, selector, handler, recipient); // 150 newHandlerRecs.push(handlerRec); // 151 handlerRec.bind(); // 152 handlerList.push(handlerRec); // 153 // Move handlers of enclosing ranges to end, by unbinding and rebinding // 154 // them. In jQuery (or other DOMBackend) this causes them to fire // 155 // later when the backend dispatches event handlers. // 156 if (getParentRecipient) { // 157 for (var r = getParentRecipient(recipient); r; // 158 r = getParentRecipient(r)) { // 159 // r is an enclosing range (recipient) // 160 for (var j = 0, Nj = handlerList.length; // 161 j < Nj; j++) { // 162 var h = handlerList[j]; // 163 if (h.recipient === r) { // 164 h.unbind(); // 165 h.bind(); // 166 handlerList.splice(j, 1); // remove handlerList[j] // 167 handlerList.push(h); // 168 j--; // account for removed handler // 169 Nj--; // don't visit appended handlers // 170 } // 171 } // 172 } // 173 } // 174 } // 175 // 176 return { // 177 // closes over just `element` and `newHandlerRecs` // 178 stop: function () { // 179 var eventDict = element.$blaze_events; // 180 if (! eventDict) // 181 return; // 182 // newHandlerRecs has only one item unless you specify multiple // 183 // event types. If this code is slow, it's because we have to // 184 // iterate over handlerList here. Clearing a whole handlerList // 185 // via stop() methods is O(N^2) in the number of handlers on // 186 // an element. // 187 for (var i = 0; i < newHandlerRecs.length; i++) { // 188 var handlerToRemove = newHandlerRecs[i]; // 189 var info = eventDict[handlerToRemove.type]; // 190 if (! info) // 191 continue; // 192 var handlerList = info.handlers; // 193 for (var j = handlerList.length - 1; j >= 0; j--) { // 194 if (handlerList[j] === handlerToRemove) { // 195 handlerToRemove.unbind(); // 196 handlerList.splice(j, 1); // remove handlerList[j] // 197 } // 198 } // 199 } // 200 newHandlerRecs.length = 0; // 201 } // 202 }; // 203 }; // 204 // 205 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// }).call(this); (function () { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // // packages/blaze/attrs.js // // // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // var jsUrlsAllowed = false; // 1 Blaze._allowJavascriptUrls = function () { // 2 jsUrlsAllowed = true; // 3 }; // 4 Blaze._javascriptUrlsAllowed = function () { // 5 return jsUrlsAllowed; // 6 }; // 7 // 8 // An AttributeHandler object is responsible for updating a particular attribute // 9 // of a particular element. AttributeHandler subclasses implement // 10 // browser-specific logic for dealing with particular attributes across // 11 // different browsers. // 12 // // 13 // To define a new type of AttributeHandler, use // 14 // `var FooHandler = AttributeHandler.extend({ update: function ... })` // 15 // where the `update` function takes arguments `(element, oldValue, value)`. // 16 // The `element` argument is always the same between calls to `update` on // 17 // the same instance. `oldValue` and `value` are each either `null` or // 18 // a Unicode string of the type that might be passed to the value argument // 19 // of `setAttribute` (i.e. not an HTML string with character references). // 20 // When an AttributeHandler is installed, an initial call to `update` is // 21 // always made with `oldValue = null`. The `update` method can access // 22 // `this.name` if the AttributeHandler class is a generic one that applies // 23 // to multiple attribute names. // 24 // // 25 // AttributeHandlers can store custom properties on `this`, as long as they // 26 // don't use the names `element`, `name`, `value`, and `oldValue`. // 27 // // 28 // AttributeHandlers can't influence how attributes appear in rendered HTML, // 29 // only how they are updated after materialization as DOM. // 30 // 31 AttributeHandler = function (name, value) { // 32 this.name = name; // 33 this.value = value; // 34 }; // 35 Blaze._AttributeHandler = AttributeHandler; // 36 // 37 AttributeHandler.prototype.update = function (element, oldValue, value) { // 38 if (value === null) { // 39 if (oldValue !== null) // 40 element.removeAttribute(this.name); // 41 } else { // 42 element.setAttribute(this.name, value); // 43 } // 44 }; // 45 // 46 AttributeHandler.extend = function (options) { // 47 var curType = this; // 48 var subType = function AttributeHandlerSubtype(/*arguments*/) { // 49 AttributeHandler.apply(this, arguments); // 50 }; // 51 subType.prototype = new curType; // 52 subType.extend = curType.extend; // 53 if (options) // 54 _.extend(subType.prototype, options); // 55 return subType; // 56 }; // 57 // 58 /// Apply the diff between the attributes of "oldValue" and "value" to "element." // 59 // // 60 // Each subclass must implement a parseValue method which takes a string // 61 // as an input and returns a dict of attributes. The keys of the dict // 62 // are unique identifiers (ie. css properties in the case of styles), and the // 63 // values are the entire attribute which will be injected into the element. // 64 // // 65 // Extended below to support classes, SVG elements and styles. // 66 // 67 var DiffingAttributeHandler = AttributeHandler.extend({ // 68 update: function (element, oldValue, value) { // 69 if (!this.getCurrentValue || !this.setValue || !this.parseValue) // 70 throw new Error("Missing methods in subclass of 'DiffingAttributeHandler'"); // 71 // 72 var oldAttrsMap = oldValue ? this.parseValue(oldValue) : {}; // 73 var newAttrsMap = value ? this.parseValue(value) : {}; // 74 // 75 // the current attributes on the element, which we will mutate. // 76 // 77 var attrString = this.getCurrentValue(element); // 78 var attrsMap = attrString ? this.parseValue(attrString) : {}; // 79 // 80 _.each(_.keys(oldAttrsMap), function (t) { // 81 if (! (t in newAttrsMap)) // 82 delete attrsMap[t]; // 83 }); // 84 // 85 _.each(_.keys(newAttrsMap), function (t) { // 86 attrsMap[t] = newAttrsMap[t]; // 87 }); // 88 // 89 this.setValue(element, _.values(attrsMap).join(' ')); // 90 } // 91 }); // 92 // 93 var ClassHandler = DiffingAttributeHandler.extend({ // 94 // @param rawValue {String} // 95 getCurrentValue: function (element) { // 96 return element.className; // 97 }, // 98 setValue: function (element, className) { // 99 element.className = className; // 100 }, // 101 parseValue: function (attrString) { // 102 var tokens = {}; // 103 // 104 _.each(attrString.split(' '), function(token) { // 105 if (token) // 106 tokens[token] = token; // 107 }); // 108 return tokens; // 109 } // 110 }); // 111 // 112 var SVGClassHandler = ClassHandler.extend({ // 113 getCurrentValue: function (element) { // 114 return element.className.baseVal; // 115 }, // 116 setValue: function (element, className) { // 117 element.setAttribute('class', className); // 118 } // 119 }); // 120 // 121 var StyleHandler = DiffingAttributeHandler.extend({ // 122 getCurrentValue: function (element) { // 123 return element.getAttribute('style'); // 124 }, // 125 setValue: function (element, style) { // 126 if (style === '') { // 127 element.removeAttribute('style'); // 128 } else { // 129 element.setAttribute('style', style); // 130 } // 131 }, // 132 // 133 // Parse a string to produce a map from property to attribute string. // 134 // // 135 // Example: // 136 // "color:red; foo:12px" produces a token {color: "color:red", foo:"foo:12px"} // 137 parseValue: function (attrString) { // 138 var tokens = {}; // 139 // 140 // Regex for parsing a css attribute declaration, taken from css-parse: // 141 // https://github.com/reworkcss/css-parse/blob/7cef3658d0bba872cde05a85339034b187cb3397/index.js#L219 // 142 var regex = /(\*?[-#\/\*\\\w]+(?:\[[0-9a-z_-]+\])?)\s*:\s*(?:\'(?:\\\'|.)*?\'|"(?:\\"|.)*?"|\([^\)]*?\)|[^};])+[;\s]*/g; var match = regex.exec(attrString); // 144 while (match) { // 145 // match[0] = entire matching string // 146 // match[1] = css property // 147 // Prefix the token to prevent conflicts with existing properties. // 148 // 149 // XXX No `String.trim` on Safari 4. Swap out $.trim if we want to // 150 // remove strong dep on jquery. // 151 tokens[' ' + match[1]] = match[0].trim ? // 152 match[0].trim() : $.trim(match[0]); // 153 // 154 match = regex.exec(attrString); // 155 } // 156 // 157 return tokens; // 158 } // 159 }); // 160 // 161 var BooleanHandler = AttributeHandler.extend({ // 162 update: function (element, oldValue, value) { // 163 var name = this.name; // 164 if (value == null) { // 165 if (oldValue != null) // 166 element[name] = false; // 167 } else { // 168 element[name] = true; // 169 } // 170 } // 171 }); // 172 // 173 var ValueHandler = AttributeHandler.extend({ // 174 update: function (element, oldValue, value) { // 175 if (value !== element.value) // 176 element.value = value; // 177 } // 178 }); // 179 // 180 // attributes of the type 'xlink:something' should be set using // 181 // the correct namespace in order to work // 182 var XlinkHandler = AttributeHandler.extend({ // 183 update: function(element, oldValue, value) { // 184 var NS = 'http://www.w3.org/1999/xlink'; // 185 if (value === null) { // 186 if (oldValue !== null) // 187 element.removeAttributeNS(NS, this.name); // 188 } else { // 189 element.setAttributeNS(NS, this.name, this.value); // 190 } // 191 } // 192 }); // 193 // 194 // cross-browser version of `instanceof SVGElement` // 195 var isSVGElement = function (elem) { // 196 return 'ownerSVGElement' in elem; // 197 }; // 198 // 199 var isUrlAttribute = function (tagName, attrName) { // 200 // Compiled from http://www.w3.org/TR/REC-html40/index/attributes.html // 201 // and // 202 // http://www.w3.org/html/wg/drafts/html/master/index.html#attributes-1 // 203 var urlAttrs = { // 204 FORM: ['action'], // 205 BODY: ['background'], // 206 BLOCKQUOTE: ['cite'], // 207 Q: ['cite'], // 208 DEL: ['cite'], // 209 INS: ['cite'], // 210 OBJECT: ['classid', 'codebase', 'data', 'usemap'], // 211 APPLET: ['codebase'], // 212 A: ['href'], // 213 AREA: ['href'], // 214 LINK: ['href'], // 215 BASE: ['href'], // 216 IMG: ['longdesc', 'src', 'usemap'], // 217 FRAME: ['longdesc', 'src'], // 218 IFRAME: ['longdesc', 'src'], // 219 HEAD: ['profile'], // 220 SCRIPT: ['src'], // 221 INPUT: ['src', 'usemap', 'formaction'], // 222 BUTTON: ['formaction'], // 223 BASE: ['href'], // 224 MENUITEM: ['icon'], // 225 HTML: ['manifest'], // 226 VIDEO: ['poster'] // 227 }; // 228 // 229 if (attrName === 'itemid') { // 230 return true; // 231 } // 232 // 233 var urlAttrNames = urlAttrs[tagName] || []; // 234 return _.contains(urlAttrNames, attrName); // 235 }; // 236 // 237 // To get the protocol for a URL, we let the browser normalize it for // 238 // us, by setting it as the href for an anchor tag and then reading out // 239 // the 'protocol' property. // 240 if (Meteor.isClient) { // 241 var anchorForNormalization = document.createElement('A'); // 242 } // 243 // 244 var getUrlProtocol = function (url) { // 245 if (Meteor.isClient) { // 246 anchorForNormalization.href = url; // 247 return (anchorForNormalization.protocol || "").toLowerCase(); // 248 } else { // 249 throw new Error('getUrlProtocol not implemented on the server'); // 250 } // 251 }; // 252 // 253 // UrlHandler is an attribute handler for all HTML attributes that take // 254 // URL values. It disallows javascript: URLs, unless // 255 // Blaze._allowJavascriptUrls() has been called. To detect javascript: // 256 // urls, we set the attribute on a dummy anchor element and then read // 257 // out the 'protocol' property of the attribute. // 258 var origUpdate = AttributeHandler.prototype.update; // 259 var UrlHandler = AttributeHandler.extend({ // 260 update: function (element, oldValue, value) { // 261 var self = this; // 262 var args = arguments; // 263 // 264 if (Blaze._javascriptUrlsAllowed()) { // 265 origUpdate.apply(self, args); // 266 } else { // 267 var isJavascriptProtocol = (getUrlProtocol(value) === "javascript:"); // 268 if (isJavascriptProtocol) { // 269 Blaze._warn("URLs that use the 'javascript:' protocol are not " + // 270 "allowed in URL attribute values. " + // 271 "Call Blaze._allowJavascriptUrls() " + // 272 "to enable them."); // 273 origUpdate.apply(self, [element, oldValue, null]); // 274 } else { // 275 origUpdate.apply(self, args); // 276 } // 277 } // 278 } // 279 }); // 280 // 281 // XXX make it possible for users to register attribute handlers! // 282 makeAttributeHandler = function (elem, name, value) { // 283 // generally, use setAttribute but certain attributes need to be set // 284 // by directly setting a JavaScript property on the DOM element. // 285 if (name === 'class') { // 286 if (isSVGElement(elem)) { // 287 return new SVGClassHandler(name, value); // 288 } else { // 289 return new ClassHandler(name, value); // 290 } // 291 } else if (name === 'style') { // 292 return new StyleHandler(name, value); // 293 } else if ((elem.tagName === 'OPTION' && name === 'selected') || // 294 (elem.tagName === 'INPUT' && name === 'checked')) { // 295 return new BooleanHandler(name, value); // 296 } else if ((elem.tagName === 'TEXTAREA' || elem.tagName === 'INPUT') // 297 && name === 'value') { // 298 // internally, TEXTAREAs tracks their value in the 'value' // 299 // attribute just like INPUTs. // 300 return new ValueHandler(name, value); // 301 } else if (name.substring(0,6) === 'xlink:') { // 302 return new XlinkHandler(name.substring(6), value); // 303 } else if (isUrlAttribute(elem.tagName, name)) { // 304 return new UrlHandler(name, value); // 305 } else { // 306 return new AttributeHandler(name, value); // 307 } // 308 // 309 // XXX will need one for 'style' on IE, though modern browsers // 310 // seem to handle setAttribute ok. // 311 }; // 312 // 313 // 314 ElementAttributesUpdater = function (elem) { // 315 this.elem = elem; // 316 this.handlers = {}; // 317 }; // 318 // 319 // Update attributes on `elem` to the dictionary `attrs`, whose // 320 // values are strings. // 321 ElementAttributesUpdater.prototype.update = function(newAttrs) { // 322 var elem = this.elem; // 323 var handlers = this.handlers; // 324 // 325 for (var k in handlers) { // 326 if (! _.has(newAttrs, k)) { // 327 // remove attributes (and handlers) for attribute names // 328 // that don't exist as keys of `newAttrs` and so won't // 329 // be visited when traversing it. (Attributes that // 330 // exist in the `newAttrs` object but are `null` // 331 // are handled later.) // 332 var handler = handlers[k]; // 333 var oldValue = handler.value; // 334 handler.value = null; // 335 handler.update(elem, oldValue, null); // 336 delete handlers[k]; // 337 } // 338 } // 339 // 340 for (var k in newAttrs) { // 341 var handler = null; // 342 var oldValue; // 343 var value = newAttrs[k]; // 344 if (! _.has(handlers, k)) { // 345 if (value !== null) { // 346 // make new handler // 347 handler = makeAttributeHandler(elem, k, value); // 348 handlers[k] = handler; // 349 oldValue = null; // 350 } // 351 } else { // 352 handler = handlers[k]; // 353 oldValue = handler.value; // 354 } // 355 if (oldValue !== value) { // 356 handler.value = value; // 357 handler.update(elem, oldValue, value); // 358 if (value === null) // 359 delete handlers[k]; // 360 } // 361 } // 362 }; // 363 // 364 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// }).call(this); (function () { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // // packages/blaze/materializer.js // // // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // Turns HTMLjs into DOM nodes and DOMRanges. // 1 // // 2 // - `htmljs`: the value to materialize, which may be any of the htmljs // 3 // types (Tag, CharRef, Comment, Raw, array, string, boolean, number, // 4 // null, or undefined) or a View or Template (which will be used to // 5 // construct a View). // 6 // - `intoArray`: the array of DOM nodes and DOMRanges to push the output // 7 // into (required) // 8 // - `parentView`: the View we are materializing content for (optional) // 9 // // 10 // Returns `intoArray`, which is especially useful if you pass in `[]`. // 11 Blaze._materializeDOM = function (htmljs, intoArray, parentView) { // 12 // In order to use fewer stack frames, materializeDOMInner can push // 13 // tasks onto `workStack`, and they will be popped off // 14 // and run, last first, after materializeDOMInner returns. The // 15 // reason we use a stack instead of a queue is so that we recurse // 16 // depth-first, doing newer tasks first. // 17 var workStack = []; // 18 materializeDOMInner(htmljs, intoArray, parentView, workStack); // 19 // 20 // A "task" is either an array of arguments to materializeDOM or // 21 // a function to execute. If we only allowed functions as tasks, // 22 // we would have to generate the functions using _.bind or close // 23 // over a loop variable, either of which is a little less efficient. // 24 while (workStack.length) { // 25 // Note that running the workStack task may push new items onto // 26 // the workStack. // 27 var task = workStack.pop(); // 28 if (typeof task === 'function') { // 29 task(); // 30 } else { // 31 // assume array // 32 materializeDOMInner(task[0], task[1], task[2], workStack); // 33 } // 34 } // 35 // 36 return intoArray; // 37 }; // 38 // 39 var materializeDOMInner = function (htmljs, intoArray, parentView, workStack) { // 40 if (htmljs == null) { // 41 // null or undefined // 42 return; // 43 } // 44 // 45 switch (typeof htmljs) { // 46 case 'string': case 'boolean': case 'number': // 47 intoArray.push(document.createTextNode(String(htmljs))); // 48 return; // 49 case 'object': // 50 if (htmljs.htmljsType) { // 51 switch (htmljs.htmljsType) { // 52 case HTML.Tag.htmljsType: // 53 intoArray.push(materializeTag(htmljs, parentView, workStack)); // 54 return; // 55 case HTML.CharRef.htmljsType: // 56 intoArray.push(document.createTextNode(htmljs.str)); // 57 return; // 58 case HTML.Comment.htmljsType: // 59 intoArray.push(document.createComment(htmljs.sanitizedValue)); // 60 return; // 61 case HTML.Raw.htmljsType: // 62 // Get an array of DOM nodes by using the browser's HTML parser // 63 // (like innerHTML). // 64 var nodes = Blaze._DOMBackend.parseHTML(htmljs.value); // 65 for (var i = 0; i < nodes.length; i++) // 66 intoArray.push(nodes[i]); // 67 return; // 68 } // 69 } else if (HTML.isArray(htmljs)) { // 70 for (var i = htmljs.length-1; i >= 0; i--) { // 71 workStack.push([htmljs[i], intoArray, parentView]); // 72 } // 73 return; // 74 } else { // 75 if (htmljs instanceof Blaze.Template) { // 76 htmljs = htmljs.constructView(); // 77 // fall through to Blaze.View case below // 78 } // 79 if (htmljs instanceof Blaze.View) { // 80 Blaze._materializeView(htmljs, parentView, workStack, intoArray); // 81 return; // 82 } // 83 } // 84 } // 85 // 86 throw new Error("Unexpected object in htmljs: " + htmljs); // 87 }; // 88 // 89 var materializeTag = function (tag, parentView, workStack) { // 90 var tagName = tag.tagName; // 91 var elem; // 92 if ((HTML.isKnownSVGElement(tagName) || isSVGAnchor(tag)) // 93 && document.createElementNS) { // 94 // inline SVG // 95 elem = document.createElementNS('http://www.w3.org/2000/svg', tagName); // 96 } else { // 97 // normal elements // 98 elem = document.createElement(tagName); // 99 } // 100 // 101 var rawAttrs = tag.attrs; // 102 var children = tag.children; // 103 if (tagName === 'textarea' && tag.children.length && // 104 ! (rawAttrs && ('value' in rawAttrs))) { // 105 // Provide very limited support for TEXTAREA tags with children // 106 // rather than a "value" attribute. // 107 // Reactivity in the form of Views nested in the tag's children // 108 // won't work. Compilers should compile textarea contents into // 109 // the "value" attribute of the tag, wrapped in a function if there // 110 // is reactivity. // 111 if (typeof rawAttrs === 'function' || // 112 HTML.isArray(rawAttrs)) { // 113 throw new Error("Can't have reactive children of TEXTAREA node; " + // 114 "use the 'value' attribute instead."); // 115 } // 116 rawAttrs = _.extend({}, rawAttrs || null); // 117 rawAttrs.value = Blaze._expand(children, parentView); // 118 children = []; // 119 } // 120 // 121 if (rawAttrs) { // 122 var attrUpdater = new ElementAttributesUpdater(elem); // 123 var updateAttributes = function () { // 124 var expandedAttrs = Blaze._expandAttributes(rawAttrs, parentView); // 125 var flattenedAttrs = HTML.flattenAttributes(expandedAttrs); // 126 var stringAttrs = {}; // 127 for (var attrName in flattenedAttrs) { // 128 stringAttrs[attrName] = Blaze._toText(flattenedAttrs[attrName], // 129 parentView, // 130 HTML.TEXTMODE.STRING); // 131 } // 132 attrUpdater.update(stringAttrs); // 133 }; // 134 var updaterComputation; // 135 if (parentView) { // 136 updaterComputation = // 137 parentView.autorun(updateAttributes, undefined, 'updater'); // 138 } else { // 139 updaterComputation = Tracker.nonreactive(function () { // 140 return Tracker.autorun(function () { // 141 Tracker._withCurrentView(parentView, updateAttributes); // 142 }); // 143 }); // 144 } // 145 Blaze._DOMBackend.Teardown.onElementTeardown(elem, function attrTeardown() { // 146 updaterComputation.stop(); // 147 }); // 148 } // 149 // 150 if (children.length) { // 151 var childNodesAndRanges = []; // 152 // push this function first so that it's done last // 153 workStack.push(function () { // 154 for (var i = 0; i < childNodesAndRanges.length; i++) { // 155 var x = childNodesAndRanges[i]; // 156 if (x instanceof Blaze._DOMRange) // 157 x.attach(elem); // 158 else // 159 elem.appendChild(x); // 160 } // 161 }); // 162 // now push the task that calculates childNodesAndRanges // 163 workStack.push([children, childNodesAndRanges, parentView]); // 164 } // 165 // 166 return elem; // 167 }; // 168 // 169 // 170 var isSVGAnchor = function (node) { // 171 // We generally aren't able to detect SVG elements because // 172 // if "A" were in our list of known svg element names, then all // 173 // nodes would be created using // 174 // `document.createElementNS`. But in the special case of , we can at least detect that attribute and // 176 // create an SVG tag in that case. // 177 // // 178 // However, we still have a general problem of knowing when to // 179 // use document.createElementNS and when to use // 180 // document.createElement; for example, font tags will always // 181 // be created as SVG elements which can cause other // 182 // problems. #1977 // 183 return (node.tagName === "a" && // 184 node.attrs && // 185 node.attrs["xlink:href"] !== undefined); // 186 }; // 187 // 188 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// }).call(this); (function () { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // // packages/blaze/exceptions.js // // // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // var debugFunc; // 1 // 2 // We call into user code in many places, and it's nice to catch exceptions // 3 // propagated from user code immediately so that the whole system doesn't just // 4 // break. Catching exceptions is easy; reporting them is hard. This helper // 5 // reports exceptions. // 6 // // 7 // Usage: // 8 // // 9 // ``` // 10 // try { // 11 // // ... someStuff ... // 12 // } catch (e) { // 13 // reportUIException(e); // 14 // } // 15 // ``` // 16 // // 17 // An optional second argument overrides the default message. // 18 // 19 // Set this to `true` to cause `reportException` to throw // 20 // the next exception rather than reporting it. This is // 21 // useful in unit tests that test error messages. // 22 Blaze._throwNextException = false; // 23 // 24 Blaze._reportException = function (e, msg) { // 25 if (Blaze._throwNextException) { // 26 Blaze._throwNextException = false; // 27 throw e; // 28 } // 29 // 30 if (! debugFunc) // 31 // adapted from Tracker // 32 debugFunc = function () { // 33 return (typeof Meteor !== "undefined" ? Meteor._debug : // 34 ((typeof console !== "undefined") && console.log ? console.log : // 35 function () {})); // 36 }; // 37 // 38 // In Chrome, `e.stack` is a multiline string that starts with the message // 39 // and contains a stack trace. Furthermore, `console.log` makes it clickable. // 40 // `console.log` supplies the space between the two arguments. // 41 debugFunc()(msg || 'Exception caught in template:', e.stack || e.message); // 42 }; // 43 // 44 Blaze._wrapCatchingExceptions = function (f, where) { // 45 if (typeof f !== 'function') // 46 return f; // 47 // 48 return function () { // 49 try { // 50 return f.apply(this, arguments); // 51 } catch (e) { // 52 Blaze._reportException(e, 'Exception in ' + where + ':'); // 53 } // 54 }; // 55 }; // 56 // 57 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// }).call(this); (function () { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // // packages/blaze/view.js // // // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // /// [new] Blaze.View([name], renderMethod) // 1 /// // 2 /// Blaze.View is the building block of reactive DOM. Views have // 3 /// the following features: // 4 /// // 5 /// * lifecycle callbacks - Views are created, rendered, and destroyed, // 6 /// and callbacks can be registered to fire when these things happen. // 7 /// // 8 /// * parent pointer - A View points to its parentView, which is the // 9 /// View that caused it to be rendered. These pointers form a // 10 /// hierarchy or tree of Views. // 11 /// // 12 /// * render() method - A View's render() method specifies the DOM // 13 /// (or HTML) content of the View. If the method establishes // 14 /// reactive dependencies, it may be re-run. // 15 /// // 16 /// * a DOMRange - If a View is rendered to DOM, its position and // 17 /// extent in the DOM are tracked using a DOMRange object. // 18 /// // 19 /// When a View is constructed by calling Blaze.View, the View is // 20 /// not yet considered "created." It doesn't have a parentView yet, // 21 /// and no logic has been run to initialize the View. All real // 22 /// work is deferred until at least creation time, when the onViewCreated // 23 /// callbacks are fired, which happens when the View is "used" in // 24 /// some way that requires it to be rendered. // 25 /// // 26 /// ...more lifecycle stuff // 27 /// // 28 /// `name` is an optional string tag identifying the View. The only // 29 /// time it's used is when looking in the View tree for a View of a // 30 /// particular name; for example, data contexts are stored on Views // 31 /// of name "with". Names are also useful when debugging, so in // 32 /// general it's good for functions that create Views to set the name. // 33 /// Views associated with templates have names of the form "Template.foo". // 34 // 35 /** // 36 * @class // 37 * @summary Constructor for a View, which represents a reactive region of DOM. // 38 * @locus Client // 39 * @param {String} [name] Optional. A name for this type of View. See [`view.name`](#view_name). // 40 * @param {Function} renderFunction A function that returns [*renderable content*](#renderable_content). In this function, `this` is bound to the View. */ // 42 Blaze.View = function (name, render) { // 43 if (! (this instanceof Blaze.View)) // 44 // called without `new` // 45 return new Blaze.View(name, render); // 46 // 47 if (typeof name === 'function') { // 48 // omitted "name" argument // 49 render = name; // 50 name = ''; // 51 } // 52 this.name = name; // 53 this._render = render; // 54 // 55 this._callbacks = { // 56 created: null, // 57 rendered: null, // 58 destroyed: null // 59 }; // 60 // 61 // Setting all properties here is good for readability, // 62 // and also may help Chrome optimize the code by keeping // 63 // the View object from changing shape too much. // 64 this.isCreated = false; // 65 this._isCreatedForExpansion = false; // 66 this.isRendered = false; // 67 this._isAttached = false; // 68 this.isDestroyed = false; // 69 this._isInRender = false; // 70 this.parentView = null; // 71 this._domrange = null; // 72 // This flag is normally set to false except for the cases when view's parent // 73 // was generated as part of expanding some syntactic sugar expressions or // 74 // methods. // 75 // Ex.: Blaze.renderWithData is an equivalent to creating a view with regular // 76 // Blaze.render and wrapping it into {{#with data}}{{/with}} view. Since the // 77 // users don't know anything about these generated parent views, Blaze needs // 78 // this information to be available on views to make smarter decisions. For // 79 // example: removing the generated parent view with the view on Blaze.remove. // 80 this._hasGeneratedParent = false; // 81 // 82 this.renderCount = 0; // 83 }; // 84 // 85 Blaze.View.prototype._render = function () { return null; }; // 86 // 87 Blaze.View.prototype.onViewCreated = function (cb) { // 88 this._callbacks.created = this._callbacks.created || []; // 89 this._callbacks.created.push(cb); // 90 }; // 91 // 92 Blaze.View.prototype._onViewRendered = function (cb) { // 93 this._callbacks.rendered = this._callbacks.rendered || []; // 94 this._callbacks.rendered.push(cb); // 95 }; // 96 // 97 Blaze.View.prototype.onViewReady = function (cb) { // 98 var self = this; // 99 var fire = function () { // 100 Tracker.afterFlush(function () { // 101 if (! self.isDestroyed) { // 102 Blaze._withCurrentView(self, function () { // 103 cb.call(self); // 104 }); // 105 } // 106 }); // 107 }; // 108 self._onViewRendered(function onViewRendered() { // 109 if (self.isDestroyed) // 110 return; // 111 if (! self._domrange.attached) // 112 self._domrange.onAttached(fire); // 113 else // 114 fire(); // 115 }); // 116 }; // 117 // 118 Blaze.View.prototype.onViewDestroyed = function (cb) { // 119 this._callbacks.destroyed = this._callbacks.destroyed || []; // 120 this._callbacks.destroyed.push(cb); // 121 }; // 122 // 123 /// View#autorun(func) // 124 /// // 125 /// Sets up a Tracker autorun that is "scoped" to this View in two // 126 /// important ways: 1) Blaze.currentView is automatically set // 127 /// on every re-run, and 2) the autorun is stopped when the // 128 /// View is destroyed. As with Tracker.autorun, the first run of // 129 /// the function is immediate, and a Computation object that can // 130 /// be used to stop the autorun is returned. // 131 /// // 132 /// View#autorun is meant to be called from View callbacks like // 133 /// onViewCreated, or from outside the rendering process. It may not // 134 /// be called before the onViewCreated callbacks are fired (too early), // 135 /// or from a render() method (too confusing). // 136 /// // 137 /// Typically, autoruns that update the state // 138 /// of the View (as in Blaze.With) should be started from an onViewCreated // 139 /// callback. Autoruns that update the DOM should be started // 140 /// from either onViewCreated (guarded against the absence of // 141 /// view._domrange), or onViewReady. // 142 Blaze.View.prototype.autorun = function (f, _inViewScope, displayName) { // 143 var self = this; // 144 // 145 // The restrictions on when View#autorun can be called are in order // 146 // to avoid bad patterns, like creating a Blaze.View and immediately // 147 // calling autorun on it. A freshly created View is not ready to // 148 // have logic run on it; it doesn't have a parentView, for example. // 149 // It's when the View is materialized or expanded that the onViewCreated // 150 // handlers are fired and the View starts up. // 151 // // 152 // Letting the render() method call `this.autorun()` is problematic // 153 // because of re-render. The best we can do is to stop the old // 154 // autorun and start a new one for each render, but that's a pattern // 155 // we try to avoid internally because it leads to helpers being // 156 // called extra times, in the case where the autorun causes the // 157 // view to re-render (and thus the autorun to be torn down and a // 158 // new one established). // 159 // // 160 // We could lift these restrictions in various ways. One interesting // 161 // idea is to allow you to call `view.autorun` after instantiating // 162 // `view`, and automatically wrap it in `view.onViewCreated`, deferring // 163 // the autorun so that it starts at an appropriate time. However, // 164 // then we can't return the Computation object to the caller, because // 165 // it doesn't exist yet. // 166 if (! self.isCreated) { // 167 throw new Error("View#autorun must be called from the created callback at the earliest"); // 168 } // 169 if (this._isInRender) { // 170 throw new Error("Can't call View#autorun from inside render(); try calling it from the created or rendered callback"); } // 172 if (Tracker.active) { // 173 throw new Error("Can't call View#autorun from a Tracker Computation; try calling it from the created or rendered callback"); } // 175 // 176 // Each local variable allocate additional space on each frame of the // 177 // execution stack. When too many variables are allocated on stack, you can // 178 // run out of memory on stack running a deep recursion (which is typical for // 179 // Blaze functions) and get stackoverlow error. (The size of the stack varies // 180 // between browsers). // 181 // The trick we use here is to allocate only one variable on stack `locals` // 182 // that keeps references to all the rest. Since locals is allocated on heap, // 183 // we don't take up any space on the stack. // 184 var locals = {}; // 185 locals.templateInstanceFunc = Blaze.Template._currentTemplateInstanceFunc; // 186 // 187 locals.f = function viewAutorun(c) { // 188 return Blaze._withCurrentView(_inViewScope || self, function () { // 189 return Blaze.Template._withTemplateInstanceFunc(locals.templateInstanceFunc, function () { // 190 return f.call(self, c); // 191 }); // 192 }); // 193 }; // 194 // 195 // Give the autorun function a better name for debugging and profiling. // 196 // The `displayName` property is not part of the spec but browsers like Chrome // 197 // and Firefox prefer it in debuggers over the name function was declared by. // 198 locals.f.displayName = // 199 (self.name || 'anonymous') + ':' + (displayName || 'anonymous'); // 200 locals.c = Tracker.autorun(locals.f); // 201 // 202 self.onViewDestroyed(function () { locals.c.stop(); }); // 203 // 204 return locals.c; // 205 }; // 206 // 207 Blaze.View.prototype._errorIfShouldntCallSubscribe = function () { // 208 var self = this; // 209 // 210 if (! self.isCreated) { // 211 throw new Error("View#subscribe must be called from the created callback at the earliest"); // 212 } // 213 if (self._isInRender) { // 214 throw new Error("Can't call View#subscribe from inside render(); try calling it from the created or rendered callback"); } // 216 if (self.isDestroyed) { // 217 throw new Error("Can't call View#subscribe from inside the destroyed callback, try calling it inside created or rendered."); } // 219 }; // 220 // 221 /** // 222 * Just like Blaze.View#autorun, but with Meteor.subscribe instead of // 223 * Tracker.autorun. Stop the subscription when the view is destroyed. // 224 * @return {SubscriptionHandle} A handle to the subscription so that you can // 225 * see if it is ready, or stop it manually // 226 */ // 227 Blaze.View.prototype.subscribe = function (args, options) { // 228 var self = this; // 229 options = {} || options; // 230 // 231 self._errorIfShouldntCallSubscribe(); // 232 // 233 var subHandle; // 234 if (options.connection) { // 235 subHandle = options.connection.subscribe.apply(options.connection, args); // 236 } else { // 237 subHandle = Meteor.subscribe.apply(Meteor, args); // 238 } // 239 // 240 self.onViewDestroyed(function () { // 241 subHandle.stop(); // 242 }); // 243 // 244 return subHandle; // 245 }; // 246 // 247 Blaze.View.prototype.firstNode = function () { // 248 if (! this._isAttached) // 249 throw new Error("View must be attached before accessing its DOM"); // 250 // 251 return this._domrange.firstNode(); // 252 }; // 253 // 254 Blaze.View.prototype.lastNode = function () { // 255 if (! this._isAttached) // 256 throw new Error("View must be attached before accessing its DOM"); // 257 // 258 return this._domrange.lastNode(); // 259 }; // 260 // 261 Blaze._fireCallbacks = function (view, which) { // 262 Blaze._withCurrentView(view, function () { // 263 Tracker.nonreactive(function fireCallbacks() { // 264 var cbs = view._callbacks[which]; // 265 for (var i = 0, N = (cbs && cbs.length); i < N; i++) // 266 cbs[i].call(view); // 267 }); // 268 }); // 269 }; // 270 // 271 Blaze._createView = function (view, parentView, forExpansion) { // 272 if (view.isCreated) // 273 throw new Error("Can't render the same View twice"); // 274 // 275 view.parentView = (parentView || null); // 276 view.isCreated = true; // 277 if (forExpansion) // 278 view._isCreatedForExpansion = true; // 279 // 280 Blaze._fireCallbacks(view, 'created'); // 281 }; // 282 // 283 var doFirstRender = function (view, initialContent) { // 284 var domrange = new Blaze._DOMRange(initialContent); // 285 view._domrange = domrange; // 286 domrange.view = view; // 287 view.isRendered = true; // 288 Blaze._fireCallbacks(view, 'rendered'); // 289 // 290 var teardownHook = null; // 291 // 292 domrange.onAttached(function attached(range, element) { // 293 view._isAttached = true; // 294 // 295 teardownHook = Blaze._DOMBackend.Teardown.onElementTeardown( // 296 element, function teardown() { // 297 Blaze._destroyView(view, true /* _skipNodes */); // 298 }); // 299 }); // 300 // 301 // tear down the teardown hook // 302 view.onViewDestroyed(function () { // 303 teardownHook && teardownHook.stop(); // 304 teardownHook = null; // 305 }); // 306 // 307 return domrange; // 308 }; // 309 // 310 // Take an uncreated View `view` and create and render it to DOM, // 311 // setting up the autorun that updates the View. Returns a new // 312 // DOMRange, which has been associated with the View. // 313 // // 314 // The private arguments `_workStack` and `_intoArray` are passed in // 315 // by Blaze._materializeDOM. If provided, then we avoid the mutual // 316 // recursion of calling back into Blaze._materializeDOM so that deep // 317 // View hierarchies don't blow the stack. Instead, we push tasks onto // 318 // workStack for the initial rendering and subsequent setup of the // 319 // View, and they are done after we return. When there is a // 320 // _workStack, we do not return the new DOMRange, but instead push it // 321 // into _intoArray from a _workStack task. // 322 Blaze._materializeView = function (view, parentView, _workStack, _intoArray) { // 323 Blaze._createView(view, parentView); // 324 // 325 var domrange; // 326 var lastHtmljs; // 327 // We don't expect to be called in a Computation, but just in case, // 328 // wrap in Tracker.nonreactive. // 329 Tracker.nonreactive(function () { // 330 view.autorun(function doRender(c) { // 331 // `view.autorun` sets the current view. // 332 view.renderCount++; // 333 view._isInRender = true; // 334 // Any dependencies that should invalidate this Computation come // 335 // from this line: // 336 var htmljs = view._render(); // 337 view._isInRender = false; // 338 // 339 if (! c.firstRun) { // 340 Tracker.nonreactive(function doMaterialize() { // 341 // re-render // 342 var rangesAndNodes = Blaze._materializeDOM(htmljs, [], view); // 343 if (! Blaze._isContentEqual(lastHtmljs, htmljs)) { // 344 domrange.setMembers(rangesAndNodes); // 345 Blaze._fireCallbacks(view, 'rendered'); // 346 } // 347 }); // 348 } // 349 lastHtmljs = htmljs; // 350 // 351 // Causes any nested views to stop immediately, not when we call // 352 // `setMembers` the next time around the autorun. Otherwise, // 353 // helpers in the DOM tree to be replaced might be scheduled // 354 // to re-run before we have a chance to stop them. // 355 Tracker.onInvalidate(function () { // 356 if (domrange) { // 357 domrange.destroyMembers(); // 358 } // 359 }); // 360 }, undefined, 'materialize'); // 361 // 362 // first render. lastHtmljs is the first htmljs. // 363 var initialContents; // 364 if (! _workStack) { // 365 initialContents = Blaze._materializeDOM(lastHtmljs, [], view); // 366 domrange = doFirstRender(view, initialContents); // 367 initialContents = null; // help GC because we close over this scope a lot // 368 } else { // 369 // We're being called from Blaze._materializeDOM, so to avoid // 370 // recursion and save stack space, provide a description of the // 371 // work to be done instead of doing it. Tasks pushed onto // 372 // _workStack will be done in LIFO order after we return. // 373 // The work will still be done within a Tracker.nonreactive, // 374 // because it will be done by some call to Blaze._materializeDOM // 375 // (which is always called in a Tracker.nonreactive). // 376 initialContents = []; // 377 // push this function first so that it happens last // 378 _workStack.push(function () { // 379 domrange = doFirstRender(view, initialContents); // 380 initialContents = null; // help GC because of all the closures here // 381 _intoArray.push(domrange); // 382 }); // 383 // now push the task that calculates initialContents // 384 _workStack.push([lastHtmljs, initialContents, view]); // 385 } // 386 }); // 387 // 388 if (! _workStack) { // 389 return domrange; // 390 } else { // 391 return null; // 392 } // 393 }; // 394 // 395 // Expands a View to HTMLjs, calling `render` recursively on all // 396 // Views and evaluating any dynamic attributes. Calls the `created` // 397 // callback, but not the `materialized` or `rendered` callbacks. // 398 // Destroys the view immediately, unless called in a Tracker Computation, // 399 // in which case the view will be destroyed when the Computation is // 400 // invalidated. If called in a Tracker Computation, the result is a // 401 // reactive string; that is, the Computation will be invalidated // 402 // if any changes are made to the view or subviews that might affect // 403 // the HTML. // 404 Blaze._expandView = function (view, parentView) { // 405 Blaze._createView(view, parentView, true /*forExpansion*/); // 406 // 407 view._isInRender = true; // 408 var htmljs = Blaze._withCurrentView(view, function () { // 409 return view._render(); // 410 }); // 411 view._isInRender = false; // 412 // 413 var result = Blaze._expand(htmljs, view); // 414 // 415 if (Tracker.active) { // 416 Tracker.onInvalidate(function () { // 417 Blaze._destroyView(view); // 418 }); // 419 } else { // 420 Blaze._destroyView(view); // 421 } // 422 // 423 return result; // 424 }; // 425 // 426 // Options: `parentView` // 427 Blaze._HTMLJSExpander = HTML.TransformingVisitor.extend(); // 428 Blaze._HTMLJSExpander.def({ // 429 visitObject: function (x) { // 430 if (x instanceof Blaze.Template) // 431 x = x.constructView(); // 432 if (x instanceof Blaze.View) // 433 return Blaze._expandView(x, this.parentView); // 434 // 435 // this will throw an error; other objects are not allowed! // 436 return HTML.TransformingVisitor.prototype.visitObject.call(this, x); // 437 }, // 438 visitAttributes: function (attrs) { // 439 // expand dynamic attributes // 440 if (typeof attrs === 'function') // 441 attrs = Blaze._withCurrentView(this.parentView, attrs); // 442 // 443 // call super (e.g. for case where `attrs` is an array) // 444 return HTML.TransformingVisitor.prototype.visitAttributes.call(this, attrs); // 445 }, // 446 visitAttribute: function (name, value, tag) { // 447 // expand attribute values that are functions. Any attribute value // 448 // that contains Views must be wrapped in a function. // 449 if (typeof value === 'function') // 450 value = Blaze._withCurrentView(this.parentView, value); // 451 // 452 return HTML.TransformingVisitor.prototype.visitAttribute.call( // 453 this, name, value, tag); // 454 } // 455 }); // 456 // 457 // Return Blaze.currentView, but only if it is being rendered // 458 // (i.e. we are in its render() method). // 459 var currentViewIfRendering = function () { // 460 var view = Blaze.currentView; // 461 return (view && view._isInRender) ? view : null; // 462 }; // 463 // 464 Blaze._expand = function (htmljs, parentView) { // 465 parentView = parentView || currentViewIfRendering(); // 466 return (new Blaze._HTMLJSExpander( // 467 {parentView: parentView})).visit(htmljs); // 468 }; // 469 // 470 Blaze._expandAttributes = function (attrs, parentView) { // 471 parentView = parentView || currentViewIfRendering(); // 472 return (new Blaze._HTMLJSExpander( // 473 {parentView: parentView})).visitAttributes(attrs); // 474 }; // 475 // 476 Blaze._destroyView = function (view, _skipNodes) { // 477 if (view.isDestroyed) // 478 return; // 479 view.isDestroyed = true; // 480 // 481 Blaze._fireCallbacks(view, 'destroyed'); // 482 // 483 // Destroy views and elements recursively. If _skipNodes, // 484 // only recurse up to views, not elements, for the case where // 485 // the backend (jQuery) is recursing over the elements already. // 486 // 487 if (view._domrange) // 488 view._domrange.destroyMembers(_skipNodes); // 489 }; // 490 // 491 Blaze._destroyNode = function (node) { // 492 if (node.nodeType === 1) // 493 Blaze._DOMBackend.Teardown.tearDownElement(node); // 494 }; // 495 // 496 // Are the HTMLjs entities `a` and `b` the same? We could be // 497 // more elaborate here but the point is to catch the most basic // 498 // cases. // 499 Blaze._isContentEqual = function (a, b) { // 500 if (a instanceof HTML.Raw) { // 501 return (b instanceof HTML.Raw) && (a.value === b.value); // 502 } else if (a == null) { // 503 return (b == null); // 504 } else { // 505 return (a === b) && // 506 ((typeof a === 'number') || (typeof a === 'boolean') || // 507 (typeof a === 'string')); // 508 } // 509 }; // 510 // 511 /** // 512 * @summary The View corresponding to the current template helper, event handler, callback, or autorun. If there isn't one, `null`. * @locus Client // 514 * @type {Blaze.View} // 515 */ // 516 Blaze.currentView = null; // 517 // 518 Blaze._withCurrentView = function (view, func) { // 519 var oldView = Blaze.currentView; // 520 try { // 521 Blaze.currentView = view; // 522 return func(); // 523 } finally { // 524 Blaze.currentView = oldView; // 525 } // 526 }; // 527 // 528 // Blaze.render publicly takes a View or a Template. // 529 // Privately, it takes any HTMLJS (extended with Views and Templates) // 530 // except null or undefined, or a function that returns any extended // 531 // HTMLJS. // 532 var checkRenderContent = function (content) { // 533 if (content === null) // 534 throw new Error("Can't render null"); // 535 if (typeof content === 'undefined') // 536 throw new Error("Can't render undefined"); // 537 // 538 if ((content instanceof Blaze.View) || // 539 (content instanceof Blaze.Template) || // 540 (typeof content === 'function')) // 541 return; // 542 // 543 try { // 544 // Throw if content doesn't look like HTMLJS at the top level // 545 // (i.e. verify that this is an HTML.Tag, or an array, // 546 // or a primitive, etc.) // 547 (new HTML.Visitor).visit(content); // 548 } catch (e) { // 549 // Make error message suitable for public API // 550 throw new Error("Expected Template or View"); // 551 } // 552 }; // 553 // 554 // For Blaze.render and Blaze.toHTML, take content and // 555 // wrap it in a View, unless it's a single View or // 556 // Template already. // 557 var contentAsView = function (content) { // 558 checkRenderContent(content); // 559 // 560 if (content instanceof Blaze.Template) { // 561 return content.constructView(); // 562 } else if (content instanceof Blaze.View) { // 563 return content; // 564 } else { // 565 var func = content; // 566 if (typeof func !== 'function') { // 567 func = function () { // 568 return content; // 569 }; // 570 } // 571 return Blaze.View('render', func); // 572 } // 573 }; // 574 // 575 // For Blaze.renderWithData and Blaze.toHTMLWithData, wrap content // 576 // in a function, if necessary, so it can be a content arg to // 577 // a Blaze.With. // 578 var contentAsFunc = function (content) { // 579 checkRenderContent(content); // 580 // 581 if (typeof content !== 'function') { // 582 return function () { // 583 return content; // 584 }; // 585 } else { // 586 return content; // 587 } // 588 }; // 589 // 590 /** // 591 * @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 // 593 * @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. // 595 * @param {DOMNode} [nextNode] Optional. If provided, must be a child of parentNode; 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). */ // 598 Blaze.render = function (content, parentElement, nextNode, parentView) { // 599 if (! parentElement) { // 600 Blaze._warn("Blaze.render without a parent element is deprecated. " + // 601 "You must specify where to insert the rendered content."); // 602 } // 603 // 604 if (nextNode instanceof Blaze.View) { // 605 // handle omitted nextNode // 606 parentView = nextNode; // 607 nextNode = null; // 608 } // 609 // 610 // parentElement must be a DOM node. in particular, can't be the // 611 // result of a call to `$`. Can't check if `parentElement instanceof // 612 // Node` since 'Node' is undefined in IE8. // 613 if (parentElement && typeof parentElement.nodeType !== 'number') // 614 throw new Error("'parentElement' must be a DOM node"); // 615 if (nextNode && typeof nextNode.nodeType !== 'number') // 'nextNode' is optional // 616 throw new Error("'nextNode' must be a DOM node"); // 617 // 618 parentView = parentView || currentViewIfRendering(); // 619 // 620 var view = contentAsView(content); // 621 Blaze._materializeView(view, parentView); // 622 // 623 if (parentElement) { // 624 view._domrange.attach(parentElement, nextNode); // 625 } // 626 // 627 return view; // 628 }; // 629 // 630 Blaze.insert = function (view, parentElement, nextNode) { // 631 Blaze._warn("Blaze.insert has been deprecated. Specify where to insert the " + // 632 "rendered content in the call to Blaze.render."); // 633 // 634 if (! (view && (view._domrange instanceof Blaze._DOMRange))) // 635 throw new Error("Expected template rendered with Blaze.render"); // 636 // 637 view._domrange.attach(parentElement, nextNode); // 638 }; // 639 // 640 /** // 641 * @summary Renders a template or View to DOM nodes with a data context. Otherwise identical to `Blaze.render`. // 642 * @locus Client // 643 * @param {Template|Blaze.View} templateOrView The template (e.g. `Template.myTemplate`) or View object to render. // 644 * @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. // 646 * @param {DOMNode} [nextNode] Optional. If provided, must be a child of parentNode; 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). */ // 649 Blaze.renderWithData = function (content, data, parentElement, nextNode, parentView) { // 650 // We defer the handling of optional arguments to Blaze.render. At this point, // 651 // `nextNode` may actually be `parentView`. // 652 return Blaze.render(Blaze._TemplateWith(data, contentAsFunc(content)), // 653 parentElement, nextNode, parentView); // 654 }; // 655 // 656 /** // 657 * @summary Removes a rendered View from the DOM, stopping all reactive updates and event listeners on it. // 658 * @locus Client // 659 * @param {Blaze.View} renderedView The return value from `Blaze.render` or `Blaze.renderWithData`. // 660 */ // 661 Blaze.remove = function (view) { // 662 if (! (view && (view._domrange instanceof Blaze._DOMRange))) // 663 throw new Error("Expected template rendered with Blaze.render"); // 664 // 665 while (view) { // 666 if (! view.isDestroyed) { // 667 var range = view._domrange; // 668 if (range.attached && ! range.parentRange) // 669 range.detach(); // 670 range.destroy(); // 671 } // 672 // 673 view = view._hasGeneratedParent && view.parentView; // 674 } // 675 }; // 676 // 677 /** // 678 * @summary Renders a template or View to a string of HTML. // 679 * @locus Client // 680 * @param {Template|Blaze.View} templateOrView The template (e.g. `Template.myTemplate`) or View object from which to generate HTML. */ // 682 Blaze.toHTML = function (content, parentView) { // 683 parentView = parentView || currentViewIfRendering(); // 684 // 685 return HTML.toHTML(Blaze._expandView(contentAsView(content), parentView)); // 686 }; // 687 // 688 /** // 689 * @summary Renders a template or View to HTML with a data context. Otherwise identical to `Blaze.toHTML`. // 690 * @locus Client // 691 * @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. // 693 */ // 694 Blaze.toHTMLWithData = function (content, data, parentView) { // 695 parentView = parentView || currentViewIfRendering(); // 696 // 697 return HTML.toHTML(Blaze._expandView(Blaze._TemplateWith( // 698 data, contentAsFunc(content)), parentView)); // 699 }; // 700 // 701 Blaze._toText = function (htmljs, parentView, textMode) { // 702 if (typeof htmljs === 'function') // 703 throw new Error("Blaze._toText doesn't take a function, just HTMLjs"); // 704 // 705 if ((parentView != null) && ! (parentView instanceof Blaze.View)) { // 706 // omitted parentView argument // 707 textMode = parentView; // 708 parentView = null; // 709 } // 710 parentView = parentView || currentViewIfRendering(); // 711 // 712 if (! textMode) // 713 throw new Error("textMode required"); // 714 if (! (textMode === HTML.TEXTMODE.STRING || // 715 textMode === HTML.TEXTMODE.RCDATA || // 716 textMode === HTML.TEXTMODE.ATTRIBUTE)) // 717 throw new Error("Unknown textMode: " + textMode); // 718 // 719 return HTML.toText(Blaze._expand(htmljs, parentView), textMode); // 720 }; // 721 // 722 /** // 723 * @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 // 725 * @param {DOMElement|Blaze.View} [elementOrView] Optional. An element that was rendered by a Meteor, or a View. // 726 */ // 727 Blaze.getData = function (elementOrView) { // 728 var theWith; // 729 // 730 if (! elementOrView) { // 731 theWith = Blaze.getView('with'); // 732 } else if (elementOrView instanceof Blaze.View) { // 733 var view = elementOrView; // 734 theWith = (view.name === 'with' ? view : // 735 Blaze.getView(view, 'with')); // 736 } else if (typeof elementOrView.nodeType === 'number') { // 737 if (elementOrView.nodeType !== 1) // 738 throw new Error("Expected DOM element"); // 739 theWith = Blaze.getView(elementOrView, 'with'); // 740 } else { // 741 throw new Error("Expected DOM element or View"); // 742 } // 743 // 744 return theWith ? theWith.dataVar.get() : null; // 745 }; // 746 // 747 // For back-compat // 748 Blaze.getElementData = function (element) { // 749 Blaze._warn("Blaze.getElementData has been deprecated. Use " + // 750 "Blaze.getData(element) instead."); // 751 // 752 if (element.nodeType !== 1) // 753 throw new Error("Expected DOM element"); // 754 // 755 return Blaze.getData(element); // 756 }; // 757 // 758 // Both arguments are optional. // 759 // 760 /** // 761 * @summary Gets either the current View, or the View enclosing the given DOM element. // 762 * @locus Client // 763 * @param {DOMElement} [element] Optional. If specified, the View enclosing `element` is returned. // 764 */ // 765 Blaze.getView = function (elementOrView, _viewName) { // 766 var viewName = _viewName; // 767 // 768 if ((typeof elementOrView) === 'string') { // 769 // omitted elementOrView; viewName present // 770 viewName = elementOrView; // 771 elementOrView = null; // 772 } // 773 // 774 // We could eventually shorten the code by folding the logic // 775 // from the other methods into this method. // 776 if (! elementOrView) { // 777 return Blaze._getCurrentView(viewName); // 778 } else if (elementOrView instanceof Blaze.View) { // 779 return Blaze._getParentView(elementOrView, viewName); // 780 } else if (typeof elementOrView.nodeType === 'number') { // 781 return Blaze._getElementView(elementOrView, viewName); // 782 } else { // 783 throw new Error("Expected DOM element or View"); // 784 } // 785 }; // 786 // 787 // Gets the current view or its nearest ancestor of name // 788 // `name`. // 789 Blaze._getCurrentView = function (name) { // 790 var view = Blaze.currentView; // 791 // Better to fail in cases where it doesn't make sense // 792 // to use Blaze._getCurrentView(). There will be a current // 793 // view anywhere it does. You can check Blaze.currentView // 794 // if you want to know whether there is one or not. // 795 if (! view) // 796 throw new Error("There is no current view"); // 797 // 798 if (name) { // 799 while (view && view.name !== name) // 800 view = view.parentView; // 801 return view || null; // 802 } else { // 803 // Blaze._getCurrentView() with no arguments just returns // 804 // Blaze.currentView. // 805 return view; // 806 } // 807 }; // 808 // 809 Blaze._getParentView = function (view, name) { // 810 var v = view.parentView; // 811 // 812 if (name) { // 813 while (v && v.name !== name) // 814 v = v.parentView; // 815 } // 816 // 817 return v || null; // 818 }; // 819 // 820 Blaze._getElementView = function (elem, name) { // 821 var range = Blaze._DOMRange.forElement(elem); // 822 var view = null; // 823 while (range && ! view) { // 824 view = (range.view || null); // 825 if (! view) { // 826 if (range.parentRange) // 827 range = range.parentRange; // 828 else // 829 range = Blaze._DOMRange.forElement(range.parentElement); // 830 } // 831 } // 832 // 833 if (name) { // 834 while (view && view.name !== name) // 835 view = view.parentView; // 836 return view || null; // 837 } else { // 838 return view; // 839 } // 840 }; // 841 // 842 Blaze._addEventMap = function (view, eventMap, thisInHandler) { // 843 thisInHandler = (thisInHandler || null); // 844 var handles = []; // 845 // 846 if (! view._domrange) // 847 throw new Error("View must have a DOMRange"); // 848 // 849 view._domrange.onAttached(function attached_eventMaps(range, element) { // 850 _.each(eventMap, function (handler, spec) { // 851 var clauses = spec.split(/,\s+/); // 852 // iterate over clauses of spec, e.g. ['click .foo', 'click .bar'] // 853 _.each(clauses, function (clause) { // 854 var parts = clause.split(/\s+/); // 855 if (parts.length === 0) // 856 return; // 857 // 858 var newEvents = parts.shift(); // 859 var selector = parts.join(' '); // 860 handles.push(Blaze._EventSupport.listen( // 861 element, newEvents, selector, // 862 function (evt) { // 863 if (! range.containsElement(evt.currentTarget)) // 864 return null; // 865 var handlerThis = thisInHandler || this; // 866 var handlerArgs = arguments; // 867 return Blaze._withCurrentView(view, function () { // 868 return handler.apply(handlerThis, handlerArgs); // 869 }); // 870 }, // 871 range, function (r) { // 872 return r.parentRange; // 873 })); // 874 }); // 875 }); // 876 }); // 877 // 878 view.onViewDestroyed(function () { // 879 _.each(handles, function (h) { // 880 h.stop(); // 881 }); // 882 handles.length = 0; // 883 }); // 884 }; // 885 // 886 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// }).call(this); (function () { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // // packages/blaze/builtins.js // // // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Blaze._calculateCondition = function (cond) { // 1 if (cond instanceof Array && cond.length === 0) // 2 cond = false; // 3 return !! cond; // 4 }; // 5 // 6 /** // 7 * @summary Constructs a View that renders content with a data context. // 8 * @locus Client // 9 * @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). // 11 */ // 12 Blaze.With = function (data, contentFunc) { // 13 var view = Blaze.View('with', contentFunc); // 14 // 15 view.dataVar = new ReactiveVar; // 16 // 17 view.onViewCreated(function () { // 18 if (typeof data === 'function') { // 19 // `data` is a reactive function // 20 view.autorun(function () { // 21 view.dataVar.set(data()); // 22 }, view.parentView, 'setData'); // 23 } else { // 24 view.dataVar.set(data); // 25 } // 26 }); // 27 // 28 return view; // 29 }; // 30 // 31 /** // 32 * @summary Constructs a View that renders content conditionally. // 33 * @locus Client // 34 * @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). // 36 * @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. */ // 38 Blaze.If = function (conditionFunc, contentFunc, elseFunc, _not) { // 39 var conditionVar = new ReactiveVar; // 40 // 41 var view = Blaze.View(_not ? 'unless' : 'if', function () { // 42 return conditionVar.get() ? contentFunc() : // 43 (elseFunc ? elseFunc() : null); // 44 }); // 45 view.__conditionVar = conditionVar; // 46 view.onViewCreated(function () { // 47 this.autorun(function () { // 48 var cond = Blaze._calculateCondition(conditionFunc()); // 49 conditionVar.set(_not ? (! cond) : cond); // 50 }, this.parentView, 'condition'); // 51 }); // 52 // 53 return view; // 54 }; // 55 // 56 /** // 57 * @summary An inverted [`Blaze.If`](#blaze_if). // 58 * @locus Client // 59 * @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). // 61 * @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. */ // 63 Blaze.Unless = function (conditionFunc, contentFunc, elseFunc) { // 64 return Blaze.If(conditionFunc, contentFunc, elseFunc, true /*_not*/); // 65 }; // 66 // 67 /** // 68 * @summary Constructs a View that renders `contentFunc` for each item in a sequence. // 69 * @locus Client // 70 * @param {Function} argFunc A function to reactively re-run. The function may return a Cursor, an array, null, or undefined. * @param {Function} contentFunc A Function that returns [*renderable content*](#renderable_content). // 72 * @param {Function} [elseFunc] Optional. A Function that returns [*renderable content*](#renderable_content) to display in the case when there are no items to display. */ // 74 Blaze.Each = function (argFunc, contentFunc, elseFunc) { // 75 var eachView = Blaze.View('each', function () { // 76 var subviews = this.initialSubviews; // 77 this.initialSubviews = null; // 78 if (this._isCreatedForExpansion) { // 79 this.expandedValueDep = new Tracker.Dependency; // 80 this.expandedValueDep.depend(); // 81 } // 82 return subviews; // 83 }); // 84 eachView.initialSubviews = []; // 85 eachView.numItems = 0; // 86 eachView.inElseMode = false; // 87 eachView.stopHandle = null; // 88 eachView.contentFunc = contentFunc; // 89 eachView.elseFunc = elseFunc; // 90 eachView.argVar = new ReactiveVar; // 91 // 92 eachView.onViewCreated(function () { // 93 // We evaluate argFunc in an autorun to make sure // 94 // Blaze.currentView is always set when it runs (rather than // 95 // passing argFunc straight to ObserveSequence). // 96 eachView.autorun(function () { // 97 eachView.argVar.set(argFunc()); // 98 }, eachView.parentView, 'collection'); // 99 // 100 eachView.stopHandle = ObserveSequence.observe(function () { // 101 return eachView.argVar.get(); // 102 }, { // 103 addedAt: function (id, item, index) { // 104 Tracker.nonreactive(function () { // 105 var newItemView = Blaze.With(item, eachView.contentFunc); // 106 eachView.numItems++; // 107 // 108 if (eachView.expandedValueDep) { // 109 eachView.expandedValueDep.changed(); // 110 } else if (eachView._domrange) { // 111 if (eachView.inElseMode) { // 112 eachView._domrange.removeMember(0); // 113 eachView.inElseMode = false; // 114 } // 115 // 116 var range = Blaze._materializeView(newItemView, eachView); // 117 eachView._domrange.addMember(range, index); // 118 } else { // 119 eachView.initialSubviews.splice(index, 0, newItemView); // 120 } // 121 }); // 122 }, // 123 removedAt: function (id, item, index) { // 124 Tracker.nonreactive(function () { // 125 eachView.numItems--; // 126 if (eachView.expandedValueDep) { // 127 eachView.expandedValueDep.changed(); // 128 } else if (eachView._domrange) { // 129 eachView._domrange.removeMember(index); // 130 if (eachView.elseFunc && eachView.numItems === 0) { // 131 eachView.inElseMode = true; // 132 eachView._domrange.addMember( // 133 Blaze._materializeView( // 134 Blaze.View('each_else',eachView.elseFunc), // 135 eachView), 0); // 136 } // 137 } else { // 138 eachView.initialSubviews.splice(index, 1); // 139 } // 140 }); // 141 }, // 142 changedAt: function (id, newItem, oldItem, index) { // 143 Tracker.nonreactive(function () { // 144 if (eachView.expandedValueDep) { // 145 eachView.expandedValueDep.changed(); // 146 } else { // 147 var itemView; // 148 if (eachView._domrange) { // 149 itemView = eachView._domrange.getMember(index).view; // 150 } else { // 151 itemView = eachView.initialSubviews[index]; // 152 } // 153 itemView.dataVar.set(newItem); // 154 } // 155 }); // 156 }, // 157 movedTo: function (id, item, fromIndex, toIndex) { // 158 Tracker.nonreactive(function () { // 159 if (eachView.expandedValueDep) { // 160 eachView.expandedValueDep.changed(); // 161 } else if (eachView._domrange) { // 162 eachView._domrange.moveMember(fromIndex, toIndex); // 163 } else { // 164 var subviews = eachView.initialSubviews; // 165 var itemView = subviews[fromIndex]; // 166 subviews.splice(fromIndex, 1); // 167 subviews.splice(toIndex, 0, itemView); // 168 } // 169 }); // 170 } // 171 }); // 172 // 173 if (eachView.elseFunc && eachView.numItems === 0) { // 174 eachView.inElseMode = true; // 175 eachView.initialSubviews[0] = // 176 Blaze.View('each_else', eachView.elseFunc); // 177 } // 178 }); // 179 // 180 eachView.onViewDestroyed(function () { // 181 if (eachView.stopHandle) // 182 eachView.stopHandle.stop(); // 183 }); // 184 // 185 return eachView; // 186 }; // 187 // 188 Blaze._TemplateWith = function (arg, contentFunc) { // 189 var w; // 190 // 191 var argFunc = arg; // 192 if (typeof arg !== 'function') { // 193 argFunc = function () { // 194 return arg; // 195 }; // 196 } // 197 // 198 // This is a little messy. When we compile `{{> Template.contentBlock}}`, we // 199 // wrap it in Blaze._InOuterTemplateScope in order to skip the intermediate // 200 // parent Views in the current template. However, when there's an argument // 201 // (`{{> Template.contentBlock arg}}`), the argument needs to be evaluated // 202 // in the original scope. There's no good order to nest // 203 // Blaze._InOuterTemplateScope and Spacebars.TemplateWith to achieve this, // 204 // so we wrap argFunc to run it in the "original parentView" of the // 205 // Blaze._InOuterTemplateScope. // 206 // // 207 // To make this better, reconsider _InOuterTemplateScope as a primitive. // 208 // Longer term, evaluate expressions in the proper lexical scope. // 209 var wrappedArgFunc = function () { // 210 var viewToEvaluateArg = null; // 211 if (w.parentView && w.parentView.name === 'InOuterTemplateScope') { // 212 viewToEvaluateArg = w.parentView.originalParentView; // 213 } // 214 if (viewToEvaluateArg) { // 215 return Blaze._withCurrentView(viewToEvaluateArg, argFunc); // 216 } else { // 217 return argFunc(); // 218 } // 219 }; // 220 // 221 var wrappedContentFunc = function () { // 222 var content = contentFunc.call(this); // 223 // 224 // Since we are generating the Blaze._TemplateWith view for the // 225 // user, set the flag on the child view. If `content` is a template, // 226 // construct the View so that we can set the flag. // 227 if (content instanceof Blaze.Template) { // 228 content = content.constructView(); // 229 } // 230 if (content instanceof Blaze.View) { // 231 content._hasGeneratedParent = true; // 232 } // 233 // 234 return content; // 235 }; // 236 // 237 w = Blaze.With(wrappedArgFunc, wrappedContentFunc); // 238 w.__isTemplateWith = true; // 239 return w; // 240 }; // 241 // 242 Blaze._InOuterTemplateScope = function (templateView, contentFunc) { // 243 var view = Blaze.View('InOuterTemplateScope', contentFunc); // 244 var parentView = templateView.parentView; // 245 // 246 // Hack so that if you call `{{> foo bar}}` and it expands into // 247 // `{{#with bar}}{{> foo}}{{/with}}`, and then `foo` is a template // 248 // that inserts `{{> Template.contentBlock}}`, the data context for // 249 // `Template.contentBlock` is not `bar` but the one enclosing that. // 250 if (parentView.__isTemplateWith) // 251 parentView = parentView.parentView; // 252 // 253 view.onViewCreated(function () { // 254 this.originalParentView = this.parentView; // 255 this.parentView = parentView; // 256 }); // 257 return view; // 258 }; // 259 // 260 // XXX COMPAT WITH 0.9.0 // 261 Blaze.InOuterTemplateScope = Blaze._InOuterTemplateScope; // 262 // 263 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// }).call(this); (function () { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // // packages/blaze/lookup.js // // // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Blaze._globalHelpers = {}; // 1 // 2 // Documented as Template.registerHelper. // 3 // This definition also provides back-compat for `UI.registerHelper`. // 4 Blaze.registerHelper = function (name, func) { // 5 Blaze._globalHelpers[name] = func; // 6 }; // 7 // 8 var bindIfIsFunction = function (x, target) { // 9 if (typeof x !== 'function') // 10 return x; // 11 return _.bind(x, target); // 12 }; // 13 // 14 // If `x` is a function, binds the value of `this` for that function // 15 // to the current data context. // 16 var bindDataContext = function (x) { // 17 if (typeof x === 'function') { // 18 return function () { // 19 var data = Blaze.getData(); // 20 if (data == null) // 21 data = {}; // 22 return x.apply(data, arguments); // 23 }; // 24 } // 25 return x; // 26 }; // 27 // 28 Blaze._OLDSTYLE_HELPER = {}; // 29 // 30 var getTemplateHelper = Blaze._getTemplateHelper = function (template, name) { // 31 // XXX COMPAT WITH 0.9.3 // 32 var isKnownOldStyleHelper = false; // 33 // 34 if (template.__helpers.has(name)) { // 35 var helper = template.__helpers.get(name); // 36 if (helper === Blaze._OLDSTYLE_HELPER) { // 37 isKnownOldStyleHelper = true; // 38 } else { // 39 return helper; // 40 } // 41 } // 42 // 43 // old-style helper // 44 if (name in template) { // 45 // Only warn once per helper // 46 if (! isKnownOldStyleHelper) { // 47 template.__helpers.set(name, Blaze._OLDSTYLE_HELPER); // 48 if (! template._NOWARN_OLDSTYLE_HELPERS) { // 49 Blaze._warn('Assigning helper with `' + template.viewName + '.' + // 50 name + ' = ...` is deprecated. Use `' + template.viewName + // 51 '.helpers(...)` instead.'); // 52 } // 53 } // 54 return template[name]; // 55 } // 56 // 57 return null; // 58 }; // 59 // 60 var wrapHelper = function (f, templateFunc) { // 61 if (typeof f !== "function") { // 62 return f; // 63 } // 64 // 65 return function () { // 66 var self = this; // 67 var args = arguments; // 68 // 69 return Blaze.Template._withTemplateInstanceFunc(templateFunc, function () { // 70 return Blaze._wrapCatchingExceptions(f, 'template helper').apply(self, args); // 71 }); // 72 }; // 73 }; // 74 // 75 // Looks up a name, like "foo" or "..", as a helper of the // 76 // current template; a global helper; the name of a template; // 77 // or a property of the data context. Called on the View of // 78 // a template (i.e. a View with a `.template` property, // 79 // where the helpers are). Used for the first name in a // 80 // "path" in a template tag, like "foo" in `{{foo.bar}}` or // 81 // ".." in `{{frobulate ../blah}}`. // 82 // // 83 // Returns a function, a non-function value, or null. If // 84 // a function is found, it is bound appropriately. // 85 // // 86 // NOTE: This function must not establish any reactive // 87 // dependencies itself. If there is any reactivity in the // 88 // value, lookup should return a function. // 89 Blaze.View.prototype.lookup = function (name, _options) { // 90 var template = this.template; // 91 var lookupTemplate = _options && _options.template; // 92 var helper; // 93 var boundTmplInstance; // 94 // 95 if (this.templateInstance) { // 96 boundTmplInstance = _.bind(this.templateInstance, this); // 97 } // 98 // 99 if (/^\./.test(name)) { // 100 // starts with a dot. must be a series of dots which maps to an // 101 // ancestor of the appropriate height. // 102 if (!/^(\.)+$/.test(name)) // 103 throw new Error("id starting with dot must be a series of dots"); // 104 // 105 return Blaze._parentData(name.length - 1, true /*_functionWrapped*/); // 106 // 107 } else if (template && // 108 ((helper = getTemplateHelper(template, name)) != null)) { // 109 return wrapHelper(bindDataContext(helper), boundTmplInstance); // 110 } else if (lookupTemplate && (name in Blaze.Template) && // 111 (Blaze.Template[name] instanceof Blaze.Template)) { // 112 return Blaze.Template[name]; // 113 } else if (Blaze._globalHelpers[name] != null) { // 114 return wrapHelper(bindDataContext(Blaze._globalHelpers[name]), // 115 boundTmplInstance); // 116 } else { // 117 return function () { // 118 var isCalledAsFunction = (arguments.length > 0); // 119 var data = Blaze.getData(); // 120 if (lookupTemplate && ! (data && data[name])) { // 121 throw new Error("No such template: " + name); // 122 } // 123 if (isCalledAsFunction && ! (data && data[name])) { // 124 throw new Error("No such function: " + name); // 125 } // 126 if (! data) // 127 return null; // 128 var x = data[name]; // 129 if (typeof x !== 'function') { // 130 if (isCalledAsFunction) { // 131 throw new Error("Can't call non-function: " + x); // 132 } // 133 return x; // 134 } // 135 return x.apply(data, arguments); // 136 }; // 137 } // 138 return null; // 139 }; // 140 // 141 // Implement Spacebars' {{../..}}. // 142 // @param height {Number} The number of '..'s // 143 Blaze._parentData = function (height, _functionWrapped) { // 144 // If height is null or undefined, we default to 1, the first parent. // 145 if (height == null) { // 146 height = 1; // 147 } // 148 var theWith = Blaze.getView('with'); // 149 for (var i = 0; (i < height) && theWith; i++) { // 150 theWith = Blaze.getView(theWith, 'with'); // 151 } // 152 // 153 if (! theWith) // 154 return null; // 155 if (_functionWrapped) // 156 return function () { return theWith.dataVar.get(); }; // 157 return theWith.dataVar.get(); // 158 }; // 159 // 160 // 161 Blaze.View.prototype.lookupTemplate = function (name) { // 162 return this.lookup(name, {template:true}); // 163 }; // 164 // 165 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// }).call(this); (function () { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // // packages/blaze/template.js // // // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // [new] Blaze.Template([viewName], renderFunction) // 1 // // 2 // `Blaze.Template` is the class of templates, like `Template.foo` in // 3 // Meteor, which is `instanceof Template`. // 4 // // 5 // `viewKind` is a string that looks like "Template.foo" for templates // 6 // defined by the compiler. // 7 // 8 /** // 9 * @class // 10 * @summary Constructor for a Template, which is used to construct Views with particular name and content. // 11 * @locus Client // 12 * @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. */ // 15 Blaze.Template = function (viewName, renderFunction) { // 16 if (! (this instanceof Blaze.Template)) // 17 // called without `new` // 18 return new Blaze.Template(viewName, renderFunction); // 19 // 20 if (typeof viewName === 'function') { // 21 // omitted "viewName" argument // 22 renderFunction = viewName; // 23 viewName = ''; // 24 } // 25 if (typeof viewName !== 'string') // 26 throw new Error("viewName must be a String (or omitted)"); // 27 if (typeof renderFunction !== 'function') // 28 throw new Error("renderFunction must be a function"); // 29 // 30 this.viewName = viewName; // 31 this.renderFunction = renderFunction; // 32 // 33 this.__helpers = new HelperMap; // 34 this.__eventMaps = []; // 35 // 36 this._callbacks = { // 37 created: [], // 38 rendered: [], // 39 destroyed: [] // 40 }; // 41 }; // 42 var Template = Blaze.Template; // 43 // 44 var HelperMap = function () {}; // 45 HelperMap.prototype.get = function (name) { // 46 return this[' '+name]; // 47 }; // 48 HelperMap.prototype.set = function (name, helper) { // 49 this[' '+name] = helper; // 50 }; // 51 HelperMap.prototype.has = function (name) { // 52 return (' '+name) in this; // 53 }; // 54 // 55 /** // 56 * @summary Returns true if `value` is a template object like `Template.myTemplate`. // 57 * @locus Client // 58 * @param {Any} value The value to test. // 59 */ // 60 Blaze.isTemplate = function (t) { // 61 return (t instanceof Blaze.Template); // 62 }; // 63 // 64 /** // 65 * @name onCreated // 66 * @instance // 67 * @memberOf Template // 68 * @summary Register a function to be called when an instance of this template is created. // 69 * @param {Function} callback A function to be added as a callback. // 70 * @locus Client // 71 */ // 72 Template.prototype.onCreated = function (cb) { // 73 this._callbacks.created.push(cb); // 74 }; // 75 // 76 /** // 77 * @name onRendered // 78 * @instance // 79 * @memberOf Template // 80 * @summary Register a function to be called when an instance of this template is inserted into the DOM. // 81 * @param {Function} callback A function to be added as a callback. // 82 * @locus Client // 83 */ // 84 Template.prototype.onRendered = function (cb) { // 85 this._callbacks.rendered.push(cb); // 86 }; // 87 // 88 /** // 89 * @name onDestroyed // 90 * @instance // 91 * @memberOf Template // 92 * @summary Register a function to be called when an instance of this template is removed from the DOM and destroyed. // 93 * @param {Function} callback A function to be added as a callback. // 94 * @locus Client // 95 */ // 96 Template.prototype.onDestroyed = function (cb) { // 97 this._callbacks.destroyed.push(cb); // 98 }; // 99 // 100 Template.prototype._getCallbacks = function (which) { // 101 var self = this; // 102 var callbacks = self[which] ? [self[which]] : []; // 103 // Fire all callbacks added with the new API (Template.onRendered()) // 104 // as well as the old-style callback (e.g. Template.rendered) for // 105 // backwards-compatibility. // 106 callbacks = callbacks.concat(self._callbacks[which]); // 107 return callbacks; // 108 }; // 109 // 110 var fireCallbacks = function (callbacks, template) { // 111 Template._withTemplateInstanceFunc( // 112 function () { return template; }, // 113 function () { // 114 for (var i = 0, N = callbacks.length; i < N; i++) { // 115 callbacks[i].call(template); // 116 } // 117 }); // 118 }; // 119 // 120 Template.prototype.constructView = function (contentFunc, elseFunc) { // 121 var self = this; // 122 var view = Blaze.View(self.viewName, self.renderFunction); // 123 view.template = self; // 124 // 125 view.templateContentBlock = ( // 126 contentFunc ? new Template('(contentBlock)', contentFunc) : null); // 127 view.templateElseBlock = ( // 128 elseFunc ? new Template('(elseBlock)', elseFunc) : null); // 129 // 130 if (self.__eventMaps || typeof self.events === 'object') { // 131 view._onViewRendered(function () { // 132 if (view.renderCount !== 1) // 133 return; // 134 // 135 if (! self.__eventMaps.length && typeof self.events === "object") { // 136 // Provide limited back-compat support for `.events = {...}` // 137 // syntax. Pass `template.events` to the original `.events(...)` // 138 // function. This code must run only once per template, in // 139 // order to not bind the handlers more than once, which is // 140 // ensured by the fact that we only do this when `__eventMaps` // 141 // is falsy, and we cause it to be set now. // 142 Template.prototype.events.call(self, self.events); // 143 } // 144 // 145 _.each(self.__eventMaps, function (m) { // 146 Blaze._addEventMap(view, m, view); // 147 }); // 148 }); // 149 } // 150 // 151 view._templateInstance = new Blaze.TemplateInstance(view); // 152 view.templateInstance = function () { // 153 // Update data, firstNode, and lastNode, and return the TemplateInstance // 154 // object. // 155 var inst = view._templateInstance; // 156 // 157 /** // 158 * @instance // 159 * @memberOf Blaze.TemplateInstance // 160 * @name data // 161 * @summary The data context of this instance's latest invocation. // 162 * @locus Client // 163 */ // 164 inst.data = Blaze.getData(view); // 165 // 166 if (view._domrange && !view.isDestroyed) { // 167 inst.firstNode = view._domrange.firstNode(); // 168 inst.lastNode = view._domrange.lastNode(); // 169 } else { // 170 // on 'created' or 'destroyed' callbacks we don't have a DomRange // 171 inst.firstNode = null; // 172 inst.lastNode = null; // 173 } // 174 // 175 return inst; // 176 }; // 177 // 178 /** // 179 * @name created // 180 * @instance // 181 * @memberOf Template // 182 * @summary Provide a callback when an instance of a template is created. // 183 * @locus Client // 184 * @deprecated in 1.1 // 185 */ // 186 // To avoid situations when new callbacks are added in between view // 187 // instantiation and event being fired, decide on all callbacks to fire // 188 // immediately and then fire them on the event. // 189 var createdCallbacks = self._getCallbacks('created'); // 190 view.onViewCreated(function () { // 191 fireCallbacks(createdCallbacks, view.templateInstance()); // 192 }); // 193 // 194 /** // 195 * @name rendered // 196 * @instance // 197 * @memberOf Template // 198 * @summary Provide a callback when an instance of a template is rendered. // 199 * @locus Client // 200 * @deprecated in 1.1 // 201 */ // 202 var renderedCallbacks = self._getCallbacks('rendered'); // 203 view.onViewReady(function () { // 204 fireCallbacks(renderedCallbacks, view.templateInstance()); // 205 }); // 206 // 207 /** // 208 * @name destroyed // 209 * @instance // 210 * @memberOf Template // 211 * @summary Provide a callback when an instance of a template is destroyed. // 212 * @locus Client // 213 * @deprecated in 1.1 // 214 */ // 215 var destroyedCallbacks = self._getCallbacks('destroyed'); // 216 view.onViewDestroyed(function () { // 217 fireCallbacks(destroyedCallbacks, view.templateInstance()); // 218 }); // 219 // 220 return view; // 221 }; // 222 // 223 /** // 224 * @class // 225 * @summary The class for template instances // 226 * @param {Blaze.View} view // 227 * @instanceName template // 228 */ // 229 Blaze.TemplateInstance = function (view) { // 230 if (! (this instanceof Blaze.TemplateInstance)) // 231 // called without `new` // 232 return new Blaze.TemplateInstance(view); // 233 // 234 if (! (view instanceof Blaze.View)) // 235 throw new Error("View required"); // 236 // 237 view._templateInstance = this; // 238 // 239 /** // 240 * @name view // 241 * @memberOf Blaze.TemplateInstance // 242 * @instance // 243 * @summary The [View](#blaze_view) object for this invocation of the template. // 244 * @locus Client // 245 * @type {Blaze.View} // 246 */ // 247 this.view = view; // 248 this.data = null; // 249 // 250 /** // 251 * @name firstNode // 252 * @memberOf Blaze.TemplateInstance // 253 * @instance // 254 * @summary The first top-level DOM node in this template instance. // 255 * @locus Client // 256 * @type {DOMNode} // 257 */ // 258 this.firstNode = null; // 259 // 260 /** // 261 * @name lastNode // 262 * @memberOf Blaze.TemplateInstance // 263 * @instance // 264 * @summary The last top-level DOM node in this template instance. // 265 * @locus Client // 266 * @type {DOMNode} // 267 */ // 268 this.lastNode = null; // 269 // 270 // This dependency is used to identify state transitions in // 271 // _subscriptionHandles which could cause the result of // 272 // TemplateInstance#subscriptionsReady to change. Basically this is triggered // 273 // whenever a new subscription handle is added or when a subscription handle // 274 // is removed and they are not ready. // 275 this._allSubsReadyDep = new Tracker.Dependency(); // 276 this._allSubsReady = false; // 277 // 278 this._subscriptionHandles = {}; // 279 }; // 280 // 281 /** // 282 * @summary Find all elements matching `selector` in this template instance, and return them as a JQuery object. // 283 * @locus Client // 284 * @param {String} selector The CSS selector to match, scoped to the template contents. // 285 * @returns {DOMNode[]} // 286 */ // 287 Blaze.TemplateInstance.prototype.$ = function (selector) { // 288 var view = this.view; // 289 if (! view._domrange) // 290 throw new Error("Can't use $ on template instance with no DOM"); // 291 return view._domrange.$(selector); // 292 }; // 293 // 294 /** // 295 * @summary Find all elements matching `selector` in this template instance. // 296 * @locus Client // 297 * @param {String} selector The CSS selector to match, scoped to the template contents. // 298 * @returns {DOMElement[]} // 299 */ // 300 Blaze.TemplateInstance.prototype.findAll = function (selector) { // 301 return Array.prototype.slice.call(this.$(selector)); // 302 }; // 303 // 304 /** // 305 * @summary Find one element matching `selector` in this template instance. // 306 * @locus Client // 307 * @param {String} selector The CSS selector to match, scoped to the template contents. // 308 * @returns {DOMElement} // 309 */ // 310 Blaze.TemplateInstance.prototype.find = function (selector) { // 311 var result = this.$(selector); // 312 return result[0] || null; // 313 }; // 314 // 315 /** // 316 * @summary A version of [Tracker.autorun](#tracker_autorun) that is stopped when the template is destroyed. // 317 * @locus Client // 318 * @param {Function} runFunc The function to run. It receives one argument: a Tracker.Computation object. // 319 */ // 320 Blaze.TemplateInstance.prototype.autorun = function (f) { // 321 return this.view.autorun(f); // 322 }; // 323 // 324 /** // 325 * @summary A version of [Meteor.subscribe](#meteor_subscribe) that is stopped // 326 * when the template is destroyed. // 327 * @return {SubscriptionHandle} The subscription handle to the newly made // 328 * subscription. Call `handle.stop()` to manually stop the subscription, or // 329 * `handle.ready()` to find out if this particular subscription has loaded all // 330 * of its inital data. // 331 * @locus Client // 332 * @param {String} name Name of the subscription. Matches the name of the // 333 * server's `publish()` call. // 334 * @param {Any} [arg1,arg2...] Optional arguments passed to publisher function // 335 * on server. // 336 * @param {Function|Object} [callbacks] Optional. May include `onStop` and // 337 * `onReady` callbacks. If a function is passed instead of an object, it is // 338 * interpreted as an `onReady` callback. // 339 */ // 340 Blaze.TemplateInstance.prototype.subscribe = function (/* arguments */) { // 341 var self = this; // 342 // 343 var subHandles = self._subscriptionHandles; // 344 var args = _.toArray(arguments); // 345 // 346 // Duplicate logic from Meteor.subscribe // 347 var callbacks = {}; // 348 if (args.length) { // 349 var lastParam = _.last(args); // 350 if (_.isFunction(lastParam)) { // 351 callbacks.onReady = args.pop(); // 352 } else if (lastParam && // 353 // XXX COMPAT WITH 1.0.3.1 onError used to exist, but now we use // 354 // onStop with an error callback instead. // 355 _.any([lastParam.onReady, lastParam.onError, lastParam.onStop], // 356 _.isFunction)) { // 357 callbacks = args.pop(); // 358 } // 359 } // 360 // 361 var subHandle; // 362 var oldStopped = callbacks.onStop; // 363 callbacks.onStop = function (error) { // 364 // When the subscription is stopped, remove it from the set of tracked // 365 // subscriptions to avoid this list growing without bound // 366 delete subHandles[subHandle.subscriptionId]; // 367 // 368 // Removing a subscription can only change the result of subscriptionsReady // 369 // if we are not ready (that subscription could be the one blocking us being // 370 // ready). // 371 if (! self._allSubsReady) { // 372 self._allSubsReadyDep.changed(); // 373 } // 374 // 375 if (oldStopped) { // 376 oldStopped(error); // 377 } // 378 }; // 379 args.push(callbacks); // 380 // 381 subHandle = self.view.subscribe.call(self.view, args); // 382 // 383 if (! _.has(subHandles, subHandle.subscriptionId)) { // 384 subHandles[subHandle.subscriptionId] = subHandle; // 385 // 386 // Adding a new subscription will always cause us to transition from ready // 387 // to not ready, but if we are already not ready then this can't make us // 388 // ready. // 389 if (self._allSubsReady) { // 390 self._allSubsReadyDep.changed(); // 391 } // 392 } // 393 // 394 return subHandle; // 395 }; // 396 // 397 /** // 398 * @summary A reactive function that returns true when all of the subscriptions // 399 * called with [this.subscribe](#TemplateInstance-subscribe) are ready. // 400 * @return {Boolean} True if all subscriptions on this template instance are // 401 * ready. // 402 */ // 403 Blaze.TemplateInstance.prototype.subscriptionsReady = function () { // 404 this._allSubsReadyDep.depend(); // 405 // 406 this._allSubsReady = _.all(this._subscriptionHandles, function (handle) { // 407 return handle.ready(); // 408 }); // 409 // 410 return this._allSubsReady; // 411 }; // 412 // 413 /** // 414 * @summary Specify template helpers available to this template. // 415 * @locus Client // 416 * @param {Object} helpers Dictionary of helper functions by name. // 417 */ // 418 Template.prototype.helpers = function (dict) { // 419 for (var k in dict) // 420 this.__helpers.set(k, dict[k]); // 421 }; // 422 // 423 // Kind of like Blaze.currentView but for the template instance. // 424 // This is a function, not a value -- so that not all helpers // 425 // are implicitly dependent on the current template instance's `data` property, // 426 // which would make them dependenct on the data context of the template // 427 // inclusion. // 428 Template._currentTemplateInstanceFunc = null; // 429 // 430 Template._withTemplateInstanceFunc = function (templateInstanceFunc, func) { // 431 if (typeof func !== 'function') // 432 throw new Error("Expected function, got: " + func); // 433 var oldTmplInstanceFunc = Template._currentTemplateInstanceFunc; // 434 try { // 435 Template._currentTemplateInstanceFunc = templateInstanceFunc; // 436 return func(); // 437 } finally { // 438 Template._currentTemplateInstanceFunc = oldTmplInstanceFunc; // 439 } // 440 }; // 441 // 442 /** // 443 * @summary Specify event handlers for this template. // 444 * @locus Client // 445 * @param {EventMap} eventMap Event handlers to associate with this template. // 446 */ // 447 Template.prototype.events = function (eventMap) { // 448 var template = this; // 449 var eventMap2 = {}; // 450 for (var k in eventMap) { // 451 eventMap2[k] = (function (k, v) { // 452 return function (event/*, ...*/) { // 453 var view = this; // passed by EventAugmenter // 454 var data = Blaze.getData(event.currentTarget); // 455 if (data == null) // 456 data = {}; // 457 var args = Array.prototype.slice.call(arguments); // 458 var tmplInstanceFunc = _.bind(view.templateInstance, view); // 459 args.splice(1, 0, tmplInstanceFunc()); // 460 // 461 return Template._withTemplateInstanceFunc(tmplInstanceFunc, function () { // 462 return v.apply(data, args); // 463 }); // 464 }; // 465 })(k, eventMap[k]); // 466 } // 467 // 468 template.__eventMaps.push(eventMap2); // 469 }; // 470 // 471 /** // 472 * @function // 473 * @name instance // 474 * @memberOf Template // 475 * @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 // 477 * @returns {Blaze.TemplateInstance} // 478 */ // 479 Template.instance = function () { // 480 return Template._currentTemplateInstanceFunc // 481 && Template._currentTemplateInstanceFunc(); // 482 }; // 483 // 484 // Note: Template.currentData() is documented to take zero arguments, // 485 // while Blaze.getData takes up to one. // 486 // 487 /** // 488 * @summary // 489 * // 490 * - Inside an `onCreated`, `onRendered`, or `onDestroyed` callback, returns // 491 * the data context of the template. // 492 * - Inside an event handler, returns the data context of the template on which // 493 * this event handler was defined. // 494 * - Inside a helper, returns the data context of the DOM node where the helper // 495 * was used. // 496 * // 497 * Establishes a reactive dependency on the result. // 498 * @locus Client // 499 * @function // 500 */ // 501 Template.currentData = Blaze.getData; // 502 // 503 /** // 504 * @summary Accesses other data contexts that enclose the current data context. // 505 * @locus Client // 506 * @function // 507 * @param {Integer} [numLevels] The number of levels beyond the current data context to look. Defaults to 1. // 508 */ // 509 Template.parentData = Blaze._parentData; // 510 // 511 /** // 512 * @summary Defines a [helper function](#template_helpers) which can be used from all templates. // 513 * @locus Client // 514 * @function // 515 * @param {String} name The name of the helper function you are defining. // 516 * @param {Function} function The helper function itself. // 517 */ // 518 Template.registerHelper = Blaze.registerHelper; // 519 // 520 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// }).call(this); (function () { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // // packages/blaze/backcompat.js // // // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // UI = Blaze; // 1 // 2 Blaze.ReactiveVar = ReactiveVar; // 3 UI._templateInstance = Blaze.Template.instance; // 4 // 5 Handlebars = {}; // 6 Handlebars.registerHelper = Blaze.registerHelper; // 7 // 8 Handlebars._escape = Blaze._escape; // 9 // 10 // Return these from {{...}} helpers to achieve the same as returning // 11 // strings from {{{...}}} helpers // 12 Handlebars.SafeString = function(string) { // 13 this.string = string; // 14 }; // 15 Handlebars.SafeString.prototype.toString = function() { // 16 return this.string.toString(); // 17 }; // 18 // 19 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// }).call(this); /* Exports */ if (typeof Package === 'undefined') Package = {}; Package.blaze = { Blaze: Blaze, UI: UI, Handlebars: Handlebars }; })();