1////////////////////////////////////////////////////////////////////////// 2// // 3// This is a generated file. You can view the original // 4// source in your browser if your browser supports source maps. // 5// Source maps are supported by all recent versions of Chrome, Safari, // 6// and Firefox, and by Internet Explorer 11. // 7// // 8////////////////////////////////////////////////////////////////////////// 9 10 11(function () { 12 13/* Imports */ 14var Meteor = Package.meteor.Meteor; 15var global = Package.meteor.global; 16var meteorEnv = Package.meteor.meteorEnv; 17var $ = Package.jquery.$; 18var jQuery = Package.jquery.jQuery; 19var Tracker = Package.tracker.Tracker; 20var Deps = Package.tracker.Deps; 21var check = Package.check.check; 22var Match = Package.check.Match; 23var _ = Package.underscore._; 24var ObserveSequence = Package['observe-sequence'].ObserveSequence; 25var ReactiveVar = Package['reactive-var'].ReactiveVar; 26var OrderedDict = Package['ordered-dict'].OrderedDict; 27var HTML = Package.htmljs.HTML; 28 29/* Package-scope variables */ 30var Blaze, AttributeHandler, ElementAttributesUpdater, UI, Handlebars; 31 32(function(){ 33 34///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 35// // 36// packages/blaze/preamble.js // 37// // 38///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 39 // 40/** 41 * @namespace Blaze 42 * @summary The namespace for all Blaze-related methods and classes. 43 */ 44Blaze = {}; 45 46// Utility to HTML-escape a string. Included for legacy reasons. 47// TODO: Should be replaced with _.escape once underscore is upgraded to a newer 48// version which escapes ` (backtick) as well. Underscore 1.5.2 does not. 49Blaze._escape = (function() { 50 var escape_map = { 51 "<": "<", 52 ">": ">", 53 '"': """, 54 "'": "'", 55 "/": "/", 56 "`": "`", /* IE allows backtick-delimited attributes?? */ 57 "&": "&" 58 }; 59 var escape_one = function(c) { 60 return escape_map[c]; 61 }; 62 63 return function (x) { 64 return x.replace(/[&<>"'`]/g, escape_one); 65 }; 66})(); 67 68Blaze._warn = function (msg) { 69 msg = 'Warning: ' + msg; 70 71 if ((typeof console !== 'undefined') && console.warn) { 72 console.warn(msg); 73 } 74}; 75 76var nativeBind = Function.prototype.bind; 77 78// An implementation of _.bind which allows better optimization. 79// See: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments 80if (nativeBind) { 81 Blaze._bind = function (func, obj) { 82 if (arguments.length === 2) { 83 return nativeBind.call(func, obj); 84 } 85 86 // Copy the arguments so this function can be optimized. 87 var args = new Array(arguments.length); 88 for (var i = 0; i < args.length; i++) { 89 args[i] = arguments[i]; 90 } 91 92 return nativeBind.apply(func, args.slice(1)); 93 }; 94} 95else { 96 // A slower but backwards compatible version. 97 Blaze._bind = _.bind; 98} 99 100///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 101 102}).call(this); 103 104 105 106 107 108 109(function(){ 110 111///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 112// // 113// packages/blaze/dombackend.js // 114// // 115///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 116 // 117var DOMBackend = {}; 118Blaze._DOMBackend = DOMBackend; 119 120var $jq = (typeof jQuery !== 'undefined' ? jQuery : 121 (typeof Package !== 'undefined' ? 122 Package.jquery && Package.jquery.jQuery : null)); 123if (! $jq) 124 throw new Error("jQuery not found"); 125 126DOMBackend._$jq = $jq; 127 128DOMBackend.parseHTML = function (html) { 129 // Return an array of nodes. 130 // 131 // jQuery does fancy stuff like creating an appropriate 132 // container element and setting innerHTML on it, as well 133 // as working around various IE quirks. 134 return $jq.parseHTML(html) || []; 135}; 136 137DOMBackend.Events = { 138 // `selector` is non-null. `type` is one type (but 139 // may be in backend-specific form, e.g. have namespaces). 140 // Order fired must be order bound. 141 delegateEvents: function (elem, type, selector, handler) { 142 $jq(elem).on(type, selector, handler); 143 }, 144 145 undelegateEvents: function (elem, type, handler) { 146 $jq(elem).off(type, '**', handler); 147 }, 148 149 bindEventCapturer: function (elem, type, selector, handler) { 150 var $elem = $jq(elem); 151 152 var wrapper = function (event) { 153 event = $jq.event.fix(event); 154 event.currentTarget = event.target; 155 156 // Note: It might improve jQuery interop if we called into jQuery 157 // here somehow. Since we don't use jQuery to dispatch the event, 158 // we don't fire any of jQuery's event hooks or anything. However, 159 // since jQuery can't bind capturing handlers, it's not clear 160 // where we would hook in. Internal jQuery functions like `dispatch` 161 // are too high-level. 162 var $target = $jq(event.currentTarget); 163 if ($target.is($elem.find(selector))) 164 handler.call(elem, event); 165 }; 166 167 handler._meteorui_wrapper = wrapper; 168 169 type = DOMBackend.Events.parseEventType(type); 170 // add *capturing* event listener 171 elem.addEventListener(type, wrapper, true); 172 }, 173 174 unbindEventCapturer: function (elem, type, handler) { 175 type = DOMBackend.Events.parseEventType(type); 176 elem.removeEventListener(type, handler._meteorui_wrapper, true); 177 }, 178 179 parseEventType: function (type) { 180 // strip off namespaces 181 var dotLoc = type.indexOf('.'); 182 if (dotLoc >= 0) 183 return type.slice(0, dotLoc); 184 return type; 185 } 186}; 187 188 189///// Removal detection and interoperability. 190 191// For an explanation of this technique, see: 192// http://bugs.jquery.com/ticket/12213#comment:23 . 193// 194// In short, an element is considered "removed" when jQuery 195// cleans up its *private* userdata on the element, 196// which we can detect using a custom event with a teardown 197// hook. 198 199var NOOP = function () {}; 200 201// Circular doubly-linked list 202var TeardownCallback = function (func) { 203 this.next = this; 204 this.prev = this; 205 this.func = func; 206}; 207 208// Insert newElt before oldElt in the circular list 209TeardownCallback.prototype.linkBefore = function(oldElt) { 210 this.prev = oldElt.prev; 211 this.next = oldElt; 212 oldElt.prev.next = this; 213 oldElt.prev = this; 214}; 215 216TeardownCallback.prototype.unlink = function () { 217 this.prev.next = this.next; 218 this.next.prev = this.prev; 219}; 220 221TeardownCallback.prototype.go = function () { 222 var func = this.func; 223 func && func(); 224}; 225 226TeardownCallback.prototype.stop = TeardownCallback.prototype.unlink; 227 228DOMBackend.Teardown = { 229 _JQUERY_EVENT_NAME: 'blaze_teardown_watcher', 230 _CB_PROP: '$blaze_teardown_callbacks', 231 // Registers a callback function to be called when the given element or 232 // one of its ancestors is removed from the DOM via the backend library. 233 // The callback function is called at most once, and it receives the element 234 // in question as an argument. 235 onElementTeardown: function (elem, func) { 236 var elt = new TeardownCallback(func); 237 238 var propName = DOMBackend.Teardown._CB_PROP; 239 if (! elem[propName]) { 240 // create an empty node that is never unlinked 241 elem[propName] = new TeardownCallback; 242 243 // Set up the event, only the first time. 244 $jq(elem).on(DOMBackend.Teardown._JQUERY_EVENT_NAME, NOOP); 245 } 246 247 elt.linkBefore(elem[propName]); 248 249 return elt; // so caller can call stop() 250 }, 251 // Recursively call all teardown hooks, in the backend and registered 252 // through DOMBackend.onElementTeardown. 253 tearDownElement: function (elem) { 254 var elems = []; 255 // Array.prototype.slice.call doesn't work when given a NodeList in 256 // IE8 ("JScript object expected"). 257 var nodeList = elem.getElementsByTagName('*'); 258 for (var i = 0; i < nodeList.length; i++) { 259 elems.push(nodeList[i]); 260 } 261 elems.push(elem); 262 $jq.cleanData(elems); 263 } 264}; 265 266$jq.event.special[DOMBackend.Teardown._JQUERY_EVENT_NAME] = { 267 setup: function () { 268 // This "setup" callback is important even though it is empty! 269 // Without it, jQuery will call addEventListener, which is a 270 // performance hit, especially with Chrome's async stack trace 271 // feature enabled. 272 }, 273 teardown: function() { 274 var elem = this; 275 var callbacks = elem[DOMBackend.Teardown._CB_PROP]; 276 if (callbacks) { 277 var elt = callbacks.next; 278 while (elt !== callbacks) { 279 elt.go(); 280 elt = elt.next; 281 } 282 callbacks.go(); 283 284 elem[DOMBackend.Teardown._CB_PROP] = null; 285 } 286 } 287}; 288 289 290// Must use jQuery semantics for `context`, not
291// querySelectorAll's. In other words, all the parts 292// of `selector` must be found under `context`. 293DOMBackend.findBySelector = function (selector, context) { 294 return $jq(selector, context); 295}; 296 297///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 298 299}).call(this); 300 301 302 303 304 305 306(function(){ 307 308///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 309// // 310// packages/blaze/domrange.js // 311// // 312///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 313 // 314 315// A constant empty array (frozen if the JS engine supports it). 316var _emptyArray = Object.freeze ? Object.freeze([]) : []; 317 318// `[new] Blaze._DOMRange([nodeAndRangeArray])` 319// 320// A DOMRange consists of an array of consecutive nodes and DOMRanges, 321// which may be replaced at any time with a new array. If the DOMRange 322// has been attached to the DOM at some location, then updating 323// the array will cause the DOM to be updated at that location. 324Blaze._DOMRange = function (nodeAndRangeArray) { 325 if (! (this instanceof DOMRange)) 326 // called without `new` 327 return new DOMRange(nodeAndRangeArray); 328 329 var members = (nodeAndRangeArray || _emptyArray); 330 if (! (members && (typeof members.length) === 'number')) 331 throw new Error("Expected array"); 332 333 for (var i = 0; i < members.length; i++) 334 this._memberIn(members[i]); 335 336 this.members = members; 337 this.emptyRangePlaceholder = null; 338 this.attached = false; 339 this.parentElement = null; 340 this.parentRange = null; 341 this.attachedCallbacks = _emptyArray; 342}; 343var DOMRange = Blaze._DOMRange; 344 345// In IE 8, don't use empty text nodes as placeholders 346// in empty DOMRanges, use comment nodes instead. Using 347// empty text nodes in modern browsers is great because 348// it doesn't clutter the web inspector. In IE 8, however, 349// it seems to lead in some roundabout way to the OAuth 350// pop-up crashing the browser completely. In the past, 351// we didn't use empty text nodes on IE 8 because they 352// don't accept JS properties, so just use the same logic 353// even though we don't need to set properties on the 354// placeholder anymore. 355DOMRange._USE_COMMENT_PLACEHOLDERS = (function () { 356 var result = false;
357 var textNode = document.createTextNode(""); 358 try { 359 textNode.someProp = true; 360 } catch (e) { 361 // IE 8 362 result = true; 363 } 364 return result; 365})(); 366 367// static methods 368DOMRange._insert = function (rangeOrNode, parentElement, nextNode, _isMove) { 369 var m = rangeOrNode; 370 if (m instanceof DOMRange) { 371 m.attach(parentElement, nextNode, _isMove); 372 } else { 373 if (_isMove) 374 DOMRange._moveNodeWithHooks(m, parentElement, nextNode); 375 else 376 DOMRange._insertNodeWithHooks(m, parentElement, nextNode); 377 } 378}; 379 380DOMRange._remove = function (rangeOrNode) { 381 var m = rangeOrNode; 382 if (m instanceof DOMRange) { 383 m.detach(); 384 } else { 385 DOMRange._removeNodeWithHooks(m); 386 } 387}; 388 389DOMRange._removeNodeWithHooks = function (n) { 390 if (! n.parentNode) 391 return; 392 if (n.nodeType === 1 && 393 n.parentNode._uihooks && n.parentNode._uihooks.removeElement) { 394 n.parentNode._uihooks.removeElement(n); 395 } else { 396 n.parentNode.removeChild(n); 397 } 398}; 399 400DOMRange._insertNodeWithHooks = function (n, parent, next) { 401 // `|| null` because IE throws an error if 'next' is undefined 402 next = next || null; 403 if (n.nodeType === 1 && 404 parent._uihooks && parent._uihooks.insertElement) { 405 parent._uihooks.insertElement(n, next); 406 } else { 407 parent.insertBefore(n, next); 408 } 409}; 410 411DOMRange._moveNodeWithHooks = function (n, parent, next) { 412 if (n.parentNode !== parent) 413 return; 414 // `|| null` because IE throws an error if 'next' is undefined 415 next = next || null; 416 if (n.nodeType === 1 && 417 parent._uihooks && parent._uihooks.moveElement) { 418 parent._uihooks.moveElement(n, next); 419 } else { 420 parent.insertBefore(n, next); 421 } 422}; 423 424DOMRange.forElement = function (elem) { 425 if (elem.nodeType !== 1) 426 throw new Error("Expected element, found: " + elem); 427 var range = null; 428 while (elem && ! range) { 429 range = (elem.$blaze_range || null); 430 if (! range) 431 elem = elem.parentNode; 432 } 433 return range; 434}; 435 436DOMRange.prototype.attach = function (parentElement, nextNode, _isMove, _isReplace) { 437 // This method is called to insert the DOMRange into the DOM for 438 // the first time, but it's also used internally when 439 // updating the DOM. 440 // 441 // If _isMove is true, move this attached range to a different 442 // location under the same parentElement. 443 if (_isMove || _isReplace) { 444 if (! (this.parentElement === parentElement && 445 this.attached)) 446 throw new Error("Can only move or replace an attached DOMRange, and only under the same parent element"); 447 } 448 449 var members = this.members; 450 if (members.length) { 451 this.emptyRangePlaceholder = null; 452 for (var i = 0; i < members.length; i++) { 453 DOMRange._insert(members[i], parentElement, nextNode, _isMove); 454 } 455 } else { 456 var placeholder = ( 457 DOMRange._USE_COMMENT_PLACEHOLDERS ? 458 document.createComment("") : 459 document.createTextNode("")); 460 this.emptyRangePlaceholder = placeholder; 461 parentElement.insertBefore(placeholder, nextNode || null); 462 } 463 this.attached = true; 464 this.parentElement = parentElement; 465 466 if (! (_isMove || _isReplace)) { 467 for(var i = 0; i < this.attachedCallbacks.length; i++) { 468 var obj = this.attachedCallbacks[i]; 469 obj.attached && obj.attached(this, parentElement); 470 } 471 } 472}; 473 474DOMRange.prototype.setMembers = function (newNodeAndRangeArray) { 475 var newMembers = newNodeAndRangeArray; 476 if (! (newMembers && (typeof newMembers.length) === 'number')) 477 throw new Error("Expected array"); 478 479 var oldMembers = this.members; 480 481 for (var i = 0; i < oldMembers.length; i++) 482 this._memberOut(oldMembers[i]); 483 for (var i = 0; i < newMembers.length; i++) 484 this._memberIn(newMembers[i]); 485 486 if (! this.attached) { 487 this.members = newMembers; 488 } else { 489 // don't do anything if we're going from empty to empty 490 if (newMembers.length || oldMembers.length) { 491 // detach the old members and insert the new members 492 var nextNode = this.lastNode().nextSibling; 493 var parentElement = this.parentElement; 494 // Use detach/attach, but don't fire attached/detached hooks 495 this.detach(true /*_isReplace*/); 496 this.members = newMembers; 497 this.attach(parentElement, nextNode, false, true /*_isReplace*/); 498 } 499 } 500}; 501 502DOMRange.prototype.firstNode = function () { 503 if (! this.attached)
504 throw new Error("Must be attached"); 505 506 if (! this.members.length) 507 return this.emptyRangePlaceholder; 508 509 var m = this.members[0]; 510 return (m instanceof DOMRange) ? m.firstNode() : m; 511}; 512 513DOMRange.prototype.lastNode = function () { 514 if (! this.attached) 515 throw new Error("Must be attached"); 516 517 if (! this.members.length) 518 return this.emptyRangePlaceholder; 519 520 var m = this.members[this.members.length - 1]; 521 return (m instanceof DOMRange) ? m.lastNode() : m; 522}; 523 524DOMRange.prototype.detach = function (_isReplace) { 525 if (! this.attached) 526 throw new Error("Must be attached"); 527 528 var oldParentElement = this.parentElement; 529 var members = this.members; 530 if (members.length) { 531 for (var i = 0; i < members.length; i++) { 532 DOMRange._remove(members[i]); 533 } 534 } else { 535 var placeholder = this.emptyRangePlaceholder; 536 this.parentElement.removeChild(placeholder); 537 this.emptyRangePlaceholder = null; 538 } 539 540 if (! _isReplace) { 541 this.attached = false; 542 this.parentElement = null; 543 544 for(var i = 0; i < this.attachedCallbacks.length; i++) { 545 var obj = this.attachedCallbacks[i]; 546 obj.detached && obj.detached(this, oldParentElement); 547 } 548 } 549}; 550 551DOMRange.prototype.addMember = function (newMember, atIndex, _isMove) { 552 var members = this.members; 553 if (! (atIndex >= 0 && atIndex <= members.length)) 554 throw new Error("Bad index in range.addMember: " + atIndex); 555 556 if (! _isMove) 557 this._memberIn(newMember); 558 559 if (! this.attached) { 560 // currently detached; just updated members 561 members.splice(atIndex, 0, newMember); 562 } else if (members.length === 0) { 563 // empty; use the empty-to-nonempty handling of setMembers 564 this.setMembers([newMember]); 565 } else { 566 var nextNode; 567 if (atIndex === members.length) { 568 // insert at end 569 nextNode = this.lastNode().nextSibling; 570 } else { 571 var m = members[atIndex]; 572 nextNode = (m instanceof DOMRange) ? m.firstNode() : m; 573 } 574 members.splice(atIndex, 0, newMember); 575 DOMRange._insert(newMember, this.parentElement, nextNode, _isMove); 576 } 577}; 578 579DOMRange.prototype.removeMember = function (atIndex, _isMove) { 580 var members = this.members; 581 if (! (atIndex >= 0 && atIndex < members.length)) 582 throw new Error("Bad index in range.removeMember: " + atIndex); 583 584 if (_isMove) { 585 members.splice(atIndex, 1); 586 } else { 587 var oldMember = members[atIndex]; 588 this._memberOut(oldMember); 589 590 if (members.length === 1) { 591 // becoming empty; use the logic in setMembers 592 this.setMembers(_emptyArray); 593 } else { 594 members.splice(atIndex, 1); 595 if (this.attached) 596 DOMRange._remove(oldMember); 597 } 598 } 599}; 600 601DOMRange.prototype.moveMember = function (oldIndex, newIndex) { 602 var member = this.members[oldIndex]; 603 this.removeMember(oldIndex, true /*_isMove*/); 604 this.addMember(member, newIndex, true /*_isMove*/); 605}; 606 607DOMRange.prototype.getMember = function (atIndex) { 608 var members = this.members; 609 if (! (atIndex >= 0 && atIndex < members.length)) 610 throw new Error("Bad index in range.getMember: " + atIndex); 611 return this.members[atIndex]; 612}; 613 614DOMRange.prototype._memberIn = function (m) { 615 if (m instanceof DOMRange) 616 m.parentRange = this; 617 else if (m.nodeType === 1) // DOM Element 618 m.$blaze_range = this; 619}; 620 621DOMRange._destroy = function (m, _skipNodes) { 622 if (m instanceof DOMRange) { 623 if (m.view) 624 Blaze._destroyView(m.view, _skipNodes); 625 } else if ((! _skipNodes) && m.nodeType === 1) { 626 // DOM Element 627 if (m.$blaze_range) { 628 Blaze._destroyNode(m); 629 m.$blaze_range = null; 630 } 631 } 632}; 633 634DOMRange.prototype._memberOut = DOMRange._destroy; 635 636// Tear down, but don't remove, the members. Used when chunks 637// of DOM are being torn down or replaced. 638DOMRange.prototype.destroyMembers = function (_skipNodes) { 639 var members = this.members; 640 for (var i = 0; i < members.length; i++) 641 this._memberOut(members[i], _skipNodes); 642}; 643 644DOMRange.prototype.destroy = function (_skipNodes) { 645 DOMRange._destroy(this, _skipNodes); 646}; 647 648DOMRange.prototype.containsElement = function (elem) { 649 if (! this.attached)
650 throw new Error("Must be attached"); 651 652 // An element is contained in this DOMRange if it's possible to 653 // reach it by walking parent pointers, first through the DOM and 654 // then parentRange pointers. In other words, the element or some 655 // ancestor of it is at our level of the DOM (a child of our 656 // parentElement), and this element is one of our members or 657 // is a member of a descendant Range. 658 659 // First check that elem is a descendant of this.parentElement, 660 // according to the DOM. 661 if (! Blaze._elementContains(this.parentElement, elem)) 662 return false; 663 664 // If elem is not an immediate child of this.parentElement, 665 // walk up to its ancestor that is. 666 while (elem.parentNode !== this.parentElement) 667 elem = elem.parentNode; 668 669 var range = elem.$blaze_range; 670 while (range && range !== this) 671 range = range.parentRange; 672 673 return range === this; 674}; 675 676DOMRange.prototype.containsRange = function (range) { 677 if (! this.attached) 678 throw new Error("Must be attached"); 679 680 if (! range.attached) 681 return false; 682 683 // A DOMRange is contained in this DOMRange if it's possible 684 // to reach this range by following parent pointers. If the 685 // DOMRange has the same parentElement, then it should be 686 // a member, or a member of a member etc. Otherwise, we must 687 // contain its parentElement. 688 689 if (range.parentElement !== this.parentElement) 690 return this.containsElement(range.parentElement); 691 692 if (range === this) 693 return false; // don't contain self 694 695 while (range && range !== this) 696 range = range.parentRange; 697 698 return range === this; 699}; 700 701DOMRange.prototype.onAttached = function (attached) { 702 this.onAttachedDetached({ attached: attached }); 703}; 704 705// callbacks are `attached(range, element)` and 706// `detached(range, element)`, and they may 707// access the `callbacks` object in `this`. 708// The arguments to `detached` are the same 709// range and element that were passed to `attached`. 710DOMRange.prototype.onAttachedDetached = function (callbacks) { 711 if (this.attachedCallbacks === _emptyArray) 712 this.attachedCallbacks = []; 713 this.attachedCallbacks.push(callbacks); 714}; 715 716DOMRange.prototype.$ = function (selector) { 717 var self = this; 718 719 var parentNode = this.parentElement; 720 if (! parentNode) 721 throw new Error("Can't select in removed DomRange"); 722 723 // Strategy: Find all selector matches under parentNode, 724 // then filter out the ones that aren't in this DomRange 725 // using `DOMRange#containsElement`. This is 726 // asymptotically slow in the presence of O(N) sibling 727 // content that is under parentNode but not in our range, 728 // so if performance is an issue, the selector should be 729 // run on a child element. 730 731 // Since jQuery can't run selectors on a DocumentFragment, 732 // we don't expect findBySelector to work. 733 if (parentNode.nodeType === 11 /* DocumentFragment */) 734 throw new Error("Can't use $ on an offscreen range"); 735 736 var results = Blaze._DOMBackend.findBySelector(selector, parentNode); 737 738 // We don't assume `results` has jQuery API; a plain array 739 // should do just as well. However, if we do have a jQuery 740 // array, we want to end up with one also, so we use 741 // `.filter`. 742 743 // Function that selects only elements that are actually 744 // in this DomRange, rather than simply descending from 745 // `parentNode`. 746 var filterFunc = function (elem) { 747 // handle jQuery's arguments to filter, where the node 748 // is in `this` and the index is the first argument. 749 if (typeof elem === 'number') 750 elem = this; 751 752 return self.containsElement(elem); 753 }; 754 755 if (! results.filter) { 756 // not a jQuery array, and not a browser with 757 // Array.prototype.filter (e.g. IE <9) 758 var newResults = []; 759 for (var i = 0; i < results.length; i++) { 760 var x = results[i]; 761 if (filterFunc(x)) 762 newResults.push(x); 763 } 764 results = newResults; 765 } else { 766 // `results.filter` is either jQuery's or ECMAScript's `filter` 767 results = results.filter(filterFunc); 768 } 769 770 return results; 771}; 772 773// Returns true if element a contains node b and is not node b. 774// 775// The restriction that `a` be an element (not a document fragment, 776// say) is based on what's easy to implement cross-browser. 777Blaze._elementContains = function (a, b) { 778 if (a.nodeType !== 1) // ELEMENT 779 return false;
780 if (a === b) 781 return false; 782 783 if (a.compareDocumentPosition) { 784 return a.compareDocumentPosition(b) & 0x10; 785 } else { 786 // Should be only old IE and maybe other old browsers here. 787 // Modern Safari has both functions but seems to get contains() wrong. 788 // IE can't handle b being a text node. We work around this 789 // by doing a direct parent test now. 790 b = b.parentNode; 791 if (! (b && b.nodeType === 1)) // ELEMENT 792 return false; 793 if (a === b) 794 return true; 795 796 return a.contains(b); 797 } 798}; 799 800///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 801 802}).call(this); 803 804 805 806 807 808 809(function(){ 810 811///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 812// // 813// packages/blaze/events.js // 814// // 815///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 816 // 817var EventSupport = Blaze._EventSupport = {}; 818 819var DOMBackend = Blaze._DOMBackend; 820 821// List of events to always delegate, never capture. 822// Since jQuery fakes bubbling for certain events in 823// certain browsers (like `submit`), we don't want to 824// get in its way. 825// 826// We could list all known bubbling 827// events here to avoid creating speculative capturers 828// for them, but it would only be an optimization. 829var eventsToDelegate = EventSupport.eventsToDelegate = { 830 blur: 1, change: 1, click: 1, focus: 1, focusin: 1, 831 focusout: 1, reset: 1, submit: 1 832}; 833 834var EVENT_MODE = EventSupport.EVENT_MODE = { 835 TBD: 0, 836 BUBBLING: 1, 837 CAPTURING: 2 838}; 839 840var NEXT_HANDLERREC_ID = 1; 841 842var HandlerRec = function (elem, type, selector, handler, recipient) { 843 this.elem = elem; 844 this.type = type; 845 this.selector = selector; 846 this.handler = handler; 847 this.recipient = recipient; 848 this.id = (NEXT_HANDLERREC_ID++); 849 850 this.mode = EVENT_MODE.TBD; 851 852 // It's important that delegatedHandler be a different 853 // instance for each handlerRecord, because its identity 854 // is used to remove it. 855 // 856 // It's also important that the closure have access to 857 // `this` when it is not called with it set. 858 this.delegatedHandler = (function (h) { 859 return function (evt) { 860 if ((! h.selector) && evt.currentTarget !== evt.target) 861 // no selector means only fire on target 862 return; 863 return h.handler.apply(h.recipient, arguments); 864 }; 865 })(this); 866 867 // WHY CAPTURE AND DELEGATE: jQuery can't delegate 868 // non-bubbling events, because 869 // event capture doesn't work in IE 8. However, there 870 // are all sorts of new-fangled non-bubbling events 871 // like "play" and "touchenter". We delegate these 872 // events using capture in all browsers except IE 8. 873 // IE 8 doesn't support these events anyway. 874 875 var tryCapturing = elem.addEventListener && 876 (! _.has(eventsToDelegate, 877 DOMBackend.Events.parseEventType(type))); 878 879 if (tryCapturing) { 880 this.capturingHandler = (function (h) { 881 return function (evt) { 882 if (h.mode === EVENT_MODE.TBD) { 883 // must be first time we're called. 884 if (evt.bubbles) { 885 // this type of event bubbles, so don't 886 // get called again. 887 h.mode = EVENT_MODE.BUBBLING; 888 DOMBackend.Events.unbindEventCapturer( 889 h.elem, h.type, h.capturingHandler); 890 return; 891 } else { 892 // this type of event doesn't bubble, 893 // so unbind the delegation, preventing 894 // it from ever firing. 895 h.mode = EVENT_MODE.CAPTURING; 896 DOMBackend.Events.undelegateEvents( 897 h.elem, h.type, h.delegatedHandler); 898 } 899 } 900 901 h.delegatedHandler(evt); 902 }; 903 })(this); 904 905 } else { 906 this.mode = EVENT_MODE.BUBBLING; 907 } 908}; 909EventSupport.HandlerRec = HandlerRec; 910 911HandlerRec.prototype.bind = function () { 912 // `this.mode` may be EVENT_MODE_TBD, in which case we bind both. in 913 // this case, 'capturingHandler' is in charge of detecting the 914 // correct mode and turning off one or the other handlers. 915 if (this.mode !== EVENT_MODE.BUBBLING) { 916 DOMBackend.Events.bindEventCapturer( 917 this.elem, this.type, this.selector || '*', 918 this.capturingHandler); 919 } 920 921 if (this.mode !== EVENT_MODE.CAPTURING) 922 DOMBackend.Events.delegateEvents( 923 this.elem, this.type, 924 this.selector || '*', this.delegatedHandler); 925}; 926 927HandlerRec.prototype.unbind = function () { 928 if (this.mode !== EVENT_MODE.BUBBLING) 929 DOMBackend.Events.unbindEventCapturer(this.elem, this.type, 930 this.capturingHandler); 931 932 if (this.mode !== EVENT_MODE.CAPTURING) 933 DOMBackend.Events.undelegateEvents(this.elem, this.type, 934 this.delegatedHandler); 935}; 936 937EventSupport.listen = function (element, events, selector, handler, recipient, getParentRecipient) { 938 939 // Prevent this method from being JITed by Safari. Due to a 940 // presumed JIT bug in Safari -- observed in Version 7.0.6 941 // (9537.78.2) -- this method may crash the Safari render process if 942 // it is JITed. 943 // Repro: https://github.com/dgreensp/public/tree/master/safari-crash 944 try { element = element; } finally {} 945 946 var eventTypes = []; 947 events.replace(/[^ /]+/g, function (e) { 948 eventTypes.push(e); 949 }); 950 951 var newHandlerRecs = []; 952 for (var i = 0, N = eventTypes.length; i < N; i++) { 953 var type = eventTypes[i]; 954 955 var eventDict = element.$blaze_events; 956 if (! eventDict) 957 eventDict = (element.$blaze_events = {}); 958 959 var info = eventDict[type]; 960 if (! info) { 961 info = eventDict[type] = {}; 962 info.handlers = []; 963 } 964 var handlerList = info.handlers; 965 var handlerRec = new HandlerRec( 966 element, type, selector, handler, recipient); 967 newHandlerRecs.push(handlerRec); 968 handlerRec.bind(); 969 handlerList.push(handlerRec); 970 // Move handlers of enclosing ranges to end, by unbinding and rebinding 971 // them. In jQuery (or other DOMBackend) this causes them to fire 972 // later when the backend dispatches event handlers. 973 if (getParentRecipient) { 974 for (var r = getParentRecipient(recipient); r; 975 r = getParentRecipient(r)) { 976 // r is an enclosing range (recipient) 977 for (var j = 0, Nj = handlerList.length; 978 j < Nj; j++) { 979 var h = handlerList[j]; 980 if (h.recipient === r) { 981 h.unbind(); 982 h.bind(); 983 handlerList.splice(j, 1); // remove handlerList[j] 984 handlerList.push(h); 985 j--; // account for removed handler 986 Nj--; // don't visit appended handlers 987 } 988 } 989 } 990 } 991 } 992 993 return { 994 // closes over just `element` and `newHandlerRecs` 995 stop: function () { 996 var eventDict = element.$blaze_events; 997 if (! eventDict) 998 return; 999 // newHandlerRecs has only one item unless you specify multiple 1000 // event types. If this code is slow, it's because we have to 1001 // iterate over handlerList here. Clearing a whole handlerList 1002 // via stop() methods is O(N^2) in the number of handlers on 1003 // an element. 1004 for (var i = 0; i < newHandlerRecs.length; i++) { 1005 var handlerToRemove = newHandlerRecs[i]; 1006 var info = eventDict[handlerToRemove.type]; 1007 if (! info) 1008 continue; 1009 var handlerList = info.handlers; 1010 for (var j = handlerList.length - 1; j >= 0; j--) { 1011 if (handlerList[j] === handlerToRemove) { 1012 handlerToRemove.unbind(); 1013 handlerList.splice(j, 1); // remove handlerList[j] 1014 } 1015 } 1016 } 1017 newHandlerRecs.length = 0; 1018 } 1019 }; 1020}; 1021 1022///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1023 1024}).call(this); 1025 1026 1027 1028 1029 1030 1031(function(){ 1032 1033///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1034// // 1035// packages/blaze/attrs.js // 1036// // 1037///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1038 // 1039var jsUrlsAllowed = false;
1040Blaze._allowJavascriptUrls = function () { 1041 jsUrlsAllowed = true; 1042}; 1043Blaze._javascriptUrlsAllowed = function () { 1044 return jsUrlsAllowed; 1045}; 1046 1047// An AttributeHandler object is responsible for updating a particular attribute 1048// of a particular element. AttributeHandler subclasses implement 1049// browser-specific logic for dealing with particular attributes across 1050// different browsers. 1051// 1052// To define a new type of AttributeHandler, use 1053// `var FooHandler = AttributeHandler.extend({ update: function ... })` 1054// where the `update` function takes arguments `(element, oldValue, value)`. 1055// The `element` argument is always the same between calls to `update` on 1056// the same instance. `oldValue` and `value` are each either `null` or 1057// a Unicode string of the type that might be passed to the value argument 1058// of `setAttribute` (i.e. not an HTML string with character references). 1059// When an AttributeHandler is installed, an initial call to `update` is 1060// always made with `oldValue = null`. The `update` method can access 1061// `this.name` if the AttributeHandler class is a generic one that applies 1062// to multiple attribute names. 1063// 1064// AttributeHandlers can store custom properties on `this`, as long as they 1065// don't use the names `element`, `name`, `value`, and `oldValue`. 1066// 1067// AttributeHandlers can't influence how attributes appear in rendered HTML, 1068// only how they are updated after materialization as DOM. 1069 1070AttributeHandler = function (name, value) { 1071 this.name = name; 1072 this.value = value; 1073}; 1074Blaze._AttributeHandler = AttributeHandler; 1075 1076AttributeHandler.prototype.update = function (element, oldValue, value) { 1077 if (value === null) { 1078 if (oldValue !== null) 1079 element.removeAttribute(this.name); 1080 } else { 1081 element.setAttribute(this.name, value); 1082 } 1083}; 1084 1085AttributeHandler.extend = function (options) { 1086 var curType = this; 1087 var subType = function AttributeHandlerSubtype(/*arguments*/) { 1088 AttributeHandler.apply(this, arguments); 1089 }; 1090 subType.prototype = new curType; 1091 subType.extend = curType.extend; 1092 if (options) 1093 _.extend(subType.prototype, options); 1094 return subType; 1095}; 1096 1097/// Apply the diff between the attributes of "oldValue" and "value" to "element." 1098// 1099// Each subclass must implement a parseValue method which takes a string 1100// as an input and returns an ordered dict of attributes. The keys of the dict 1101// are unique identifiers (ie. css properties in the case of styles), and the 1102// values are the entire attribute which will be injected into the element. 1103// 1104// Extended below to support classes, SVG elements and styles. 1105 1106Blaze._DiffingAttributeHandler = AttributeHandler.extend({ 1107 update: function (element, oldValue, value) { 1108 if (!this.getCurrentValue || !this.setValue || !this.parseValue || !this.joinValues) 1109 throw new Error("Missing methods in subclass of 'DiffingAttributeHandler'"); 1110 1111 var oldAttrsMap = oldValue ? this.parseValue(oldValue) : new OrderedDict(); 1112 var attrsMap = value ? this.parseValue(value) : new OrderedDict(); 1113 1114 // the current attributes on the element, which we will mutate. 1115 1116 var currentAttrString = this.getCurrentValue(element); 1117 var currentAttrsMap = currentAttrString ? this.parseValue(currentAttrString) : new OrderedDict(); 1118 1119 // Any outside changes to attributes we add at the end. 1120 currentAttrsMap.forEach(function (value, key, i) { 1121 // If the key already exists, we do not use the current value, but the new value. 1122 if (attrsMap.has(key)) { 1123 return; 1124 } 1125 1126 // Key does not already exist, but it existed before. Which means it was explicitly 1127 // removed, so we do not add it. 1128 if (oldAttrsMap.has(key)) { 1129 return; 1130 } 1131 1132 attrsMap.append(key, value); 1133 }); 1134 1135 var values = []; 1136 attrsMap.forEach(function (value, key, i) { 1137 values.push(value); 1138 }); 1139 1140 this.setValue(element, this.joinValues(values)); 1141 } 1142}); 1143 1144var ClassHandler = Blaze._DiffingAttributeHandler.extend({ 1145 // @param rawValue {String} 1146 getCurrentValue: function (element) { 1147 return element.className; 1148 }, 1149 setValue: function (element, className) { 1150 element.className = className; 1151 }, 1152 parseValue: function (attrString) { 1153 var tokens = new OrderedDict(); 1154 1155 _.each(attrString.split(' '), function(token) { 1156 if (token) { 1157 // Ordered dict requires unique keys. 1158 if (! tokens.has(token)) { 1159 tokens.append(token, token); 1160 } 1161 } 1162 }); 1163 return tokens; 1164 }, 1165 joinValues: function (values) { 1166 return values.join(' '); 1167 } 1168}); 1169 1170var SVGClassHandler = ClassHandler.extend({ 1171 getCurrentValue: function (element) { 1172 return element.className.baseVal; 1173 }, 1174 setValue: function (element, className) { 1175 element.setAttribute('class', className); 1176 } 1177}); 1178 1179var StyleHandler = Blaze._DiffingAttributeHandler.extend({ 1180 getCurrentValue: function (element) { 1181 return element.getAttribute('style'); 1182 }, 1183 setValue: function (element, style) { 1184 if (style === '') { 1185 element.removeAttribute('style'); 1186 } else { 1187 element.setAttribute('style', style); 1188 } 1189 }, 1190 1191 // Parse a string to produce a map from property to attribute string. 1192 // 1193 // Example: 1194 // "color:red; foo:12px" produces a token {color: "color:red", foo:"foo:12px"} 1195 parseValue: function (attrString) { 1196 var tokens = new OrderedDict(); 1197 1198 // Regex for parsing a css attribute declaration, taken from css-parse: 1199 // https://github.com/reworkcss/css-parse/blob/7cef3658d0bba872cde05a85339034b187cb3397/index.js#L219 1200 var regex = /(\*?[-#\/\*\\\w]+(?:\[[0-9a-z_-]+\])?)\s*:\s*(?:\'(?:\\\'|.)*?\'|"(?:\\"|.)*?"|\([^\)]*?\)|[^};])+[;\s]*/g; 1201 var match = regex.exec(attrString); 1202 while (match) { 1203 // match[0] = entire matching string 1204 // match[1] = css property 1205 // Prefix the token to prevent conflicts with existing properties. 1206 1207 // We use the last value for the same key. 1208 if (tokens.has(match[1])) { 1209 tokens.remove(match[1]); 1210 } 1211
1212 // XXX No `String.trim` on Safari 4. Swap out $.trim if we want to 1213 // remove strong dep on jquery. 1214 tokens.append(match[1], match[0].trim ? match[0].trim() : $.trim(match[0])); 1215 1216 match = regex.exec(attrString); 1217 } 1218 1219 return tokens; 1220 }, 1221 1222 joinValues: function (values) { 1223 // TODO: Assure that there is always ; between values. But what is an example where it breaks? 1224 return values.join(' '); 1225 } 1226}); 1227 1228var BooleanHandler = AttributeHandler.extend({ 1229 update: function (element, oldValue, value) { 1230 var name = this.name; 1231 if (value == null) { 1232 if (oldValue != null) 1233 element[name] = false; 1234 } else { 1235 element[name] = true; 1236 } 1237 } 1238}); 1239 1240var DOMPropertyHandler = AttributeHandler.extend({ 1241 update: function (element, oldValue, value) { 1242 var name = this.name; 1243 if (value !== element[name]) 1244 element[name] = value; 1245 } 1246}); 1247 1248// attributes of the type 'xlink:something' should be set using 1249// the correct namespace in order to work 1250var XlinkHandler = AttributeHandler.extend({ 1251 update: function(element, oldValue, value) { 1252 var NS = 'http://www.w3.org/1999/xlink'; 1253 if (value === null) { 1254 if (oldValue !== null) 1255 element.removeAttributeNS(NS, this.name); 1256 } else { 1257 element.setAttributeNS(NS, this.name, this.value); 1258 } 1259 } 1260}); 1261 1262// cross-browser version of `instanceof SVGElement` 1263var isSVGElement = function (elem) { 1264 return 'ownerSVGElement' in elem; 1265}; 1266 1267var isUrlAttribute = function (tagName, attrName) { 1268 // Compiled from http://www.w3.org/TR/REC-html40/index/attributes.html 1269 // and 1270 // http://www.w3.org/html/wg/drafts/html/master/index.html#attributes-1 1271 var urlAttrs = { 1272 FORM: ['action'], 1273 BODY: ['background'], 1274 BLOCKQUOTE: ['cite'], 1275 Q: ['cite'], 1276 DEL: ['cite'], 1277 INS: ['cite'], 1278 OBJECT: ['classid', 'codebase', 'data', 'usemap'], 1279 APPLET: ['codebase'], 1280 A: ['href'], 1281 AREA: ['href'], 1282 LINK: ['href'], 1283 BASE: ['href'], 1284 IMG: ['longdesc', 'src', 'usemap'], 1285 FRAME: ['longdesc', 'src'], 1286 IFRAME: ['longdesc', 'src'], 1287 HEAD: ['profile'], 1288 SCRIPT: ['src'], 1289 INPUT: ['src', 'usemap', 'formaction'], 1290 BUTTON: ['formaction'], 1291 BASE: ['href'], 1292 MENUITEM: ['icon'], 1293 HTML: ['manifest'], 1294 VIDEO: ['poster'] 1295 }; 1296 1297 if (attrName === 'itemid') { 1298 return true; 1299 } 1300 1301 var urlAttrNames = urlAttrs[tagName] || []; 1302 return _.contains(urlAttrNames, attrName); 1303}; 1304 1305// To get the protocol for a URL, we let the browser normalize it for 1306// us, by setting it as the href for an anchor tag and then reading out 1307// the 'protocol' property. 1308if (Meteor.isClient) { 1309 var anchorForNormalization = document.createElement('A'); 1310} 1311 1312var getUrlProtocol = function (url) { 1313 if (Meteor.isClient) { 1314 anchorForNormalization.href = url; 1315 return (anchorForNormalization.protocol || "").toLowerCase(); 1316 } else { 1317 throw new Error('getUrlProtocol not implemented on the server'); 1318 } 1319}; 1320 1321// UrlHandler is an attribute handler for all HTML attributes that take 1322// URL values. It disallows javascript: URLs, unless 1323// Blaze._allowJavascriptUrls() has been called. To detect javascript: 1324// urls, we set the attribute on a dummy anchor element and then read 1325// out the 'protocol' property of the attribute. 1326var origUpdate = AttributeHandler.prototype.update; 1327var UrlHandler = AttributeHandler.extend({ 1328 update: function (element, oldValue, value) { 1329 var self = this; 1330 var args = arguments; 1331 1332 if (Blaze._javascriptUrlsAllowed()) { 1333 origUpdate.apply(self, args); 1334 } else { 1335 var isJavascriptProtocol = (getUrlProtocol(value) === "javascript:"); 1336 var isVBScriptProtocol = (getUrlProtocol(value) === "vbscript:"); 1337 if (isJavascriptProtocol || isVBScriptProtocol) { 1338 Blaze._warn("URLs that use the 'javascript:' or 'vbscript:' protocol are not " + 1339 "allowed in URL attribute values. " + 1340 "Call Blaze._allowJavascriptUrls() " + 1341 "to enable them."); 1342 origUpdate.apply(self, [element, oldValue, null]); 1343 } else { 1344 origUpdate.apply(self, args); 1345 } 1346 } 1347 } 1348}); 1349 1350// XXX make it possible for users to register attribute handlers! 1351Blaze._makeAttributeHandler = function (elem, name, value) { 1352 // generally, use setAttribute but certain attributes need to be set 1353 // by directly setting a JavaScript property on the DOM element. 1354 if (name === 'class') { 1355 if (isSVGElement(elem)) { 1356 return new SVGClassHandler(name, value); 1357 } else { 1358 return new ClassHandler(name, value); 1359 } 1360 } else if (name === 'style') { 1361 return new StyleHandler(name, value); 1362 } else if ((elem.tagName === 'OPTION' && name === 'selected') || 1363 (elem.tagName === 'INPUT' && name === 'checked') || 1364 (elem.tagName === 'VIDEO' && name === 'muted')) { 1365 return new BooleanHandler(name, value); 1366 } else if ((elem.tagName === 'TEXTAREA' || elem.tagName === 'INPUT') 1367 && name === 'value') { 1368 // internally, TEXTAREAs tracks their value in the 'value' 1369 // attribute just like INPUTs. 1370 return new DOMPropertyHandler(name, value); 1371 } else if (name.substring(0,6) === 'xlink:') { 1372 return new XlinkHandler(name.substring(6), value); 1373 } else if (isUrlAttribute(elem.tagName, name)) { 1374 return new UrlHandler(name, value); 1375 } else { 1376 return new AttributeHandler(name, value); 1377 } 1378 1379 // XXX will need one for 'style' on IE, though modern browsers 1380 // seem to handle setAttribute ok. 1381}; 1382 1383 1384ElementAttributesUpdater = function (elem) { 1385 this.elem = elem; 1386 this.handlers = {}; 1387}; 1388 1389// Update attributes on `elem` to the dictionary `attrs`, whose 1390// values are strings. 1391ElementAttributesUpdater.prototype.update = function(newAttrs) { 1392 var elem = this.elem; 1393 var handlers = this.handlers; 1394 1395 for (var k in handlers) { 1396 if (! _.has(newAttrs, k)) { 1397 // remove attributes (and handlers) for attribute names 1398 // that don't exist as keys of `newAttrs` and so won't
1399 // be visited when traversing it. (Attributes that 1400 // exist in the `newAttrs` object but are `null` 1401 // are handled later.) 1402 var handler = handlers[k]; 1403 var oldValue = handler.value; 1404 handler.value = null; 1405 handler.update(elem, oldValue, null); 1406 delete handlers[k]; 1407 } 1408 } 1409 1410 for (var k in newAttrs) { 1411 var handler = null; 1412 var oldValue = null; 1413 var value = newAttrs[k]; 1414 if (! _.has(handlers, k)) { 1415 if (value !== null) { 1416 // make new handler 1417 handler = Blaze._makeAttributeHandler(elem, k, value); 1418 handlers[k] = handler; 1419 } 1420 } else { 1421 handler = handlers[k]; 1422 oldValue = handler.value; 1423 } 1424 if (oldValue !== value) { 1425 handler.value = value; 1426 handler.update(elem, oldValue, value); 1427 if (value === null) 1428 delete handlers[k]; 1429 } 1430 } 1431}; 1432 1433///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1434 1435}).call(this); 1436 1437 1438 1439 1440 1441 1442(function(){ 1443 1444///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1445// // 1446// packages/blaze/materializer.js // 1447// // 1448///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1449 // 1450// Turns HTMLjs into DOM nodes and DOMRanges. 1451// 1452// - `htmljs`: the value to materialize, which may be any of the htmljs 1453// types (Tag, CharRef, Comment, Raw, array, string, boolean, number, 1454// null, or undefined) or a View or Template (which will be used to 1455// construct a View). 1456// - `intoArray`: the array of DOM nodes and DOMRanges to push the output 1457// into (required) 1458// - `parentView`: the View we are materializing content for (optional) 1459// - `_existingWorkStack`: optional argument, only used for recursive 1460// calls when there is some other _materializeDOM on the call stack. 1461// If _materializeDOM called your function and passed in a workStack, 1462// pass it back when you call _materializeDOM (such as from a workStack 1463// task). 1464// 1465// Returns `intoArray`, which is especially useful if you pass in `[]`. 1466Blaze._materializeDOM = function (htmljs, intoArray, parentView, 1467 _existingWorkStack) { 1468 // In order to use fewer stack frames, materializeDOMInner can push 1469 // tasks onto `workStack`, and they will be popped off 1470 // and run, last first, after materializeDOMInner returns. The 1471 // reason we use a stack instead of a queue is so that we recurse 1472 // depth-first, doing newer tasks first. 1473 var workStack = (_existingWorkStack || []); 1474 materializeDOMInner(htmljs, intoArray, parentView, workStack); 1475 1476 if (! _existingWorkStack) { 1477 // We created the work stack, so we are responsible for finishing 1478 // the work. Call each "task" function, starting with the top 1479 // of the stack. 1480 while (workStack.length) { 1481 // Note that running task() may push new items onto workStack. 1482 var task = workStack.pop(); 1483 task(); 1484 } 1485 } 1486 1487 return intoArray; 1488}; 1489 1490var materializeDOMInner = function (htmljs, intoArray, parentView, workStack) { 1491 if (htmljs == null) { 1492 // null or undefined 1493 return; 1494 } 1495 1496 switch (typeof htmljs) { 1497 case 'string': case 'boolean': case 'number': 1498 intoArray.push(document.createTextNode(String(htmljs))); 1499 return; 1500 case 'object': 1501 if (htmljs.htmljsType) { 1502 switch (htmljs.htmljsType) { 1503 case HTML.Tag.htmljsType: 1504 intoArray.push(materializeTag(htmljs, parentView, workStack)); 1505 return; 1506 case HTML.CharRef.htmljsType: 1507 intoArray.push(document.createTextNode(htmljs.str)); 1508 return; 1509 case HTML.Comment.htmljsType: 1510 intoArray.push(document.createComment(htmljs.sanitizedValue)); 1511 return; 1512 case HTML.Raw.htmljsType: 1513 // Get an array of DOM nodes by using the browser's HTML parser 1514 // (like innerHTML). 1515 var nodes = Blaze._DOMBackend.parseHTML(htmljs.value); 1516 for (var i = 0; i < nodes.length; i++) 1517 intoArray.push(nodes[i]); 1518 return; 1519 } 1520 } else if (HTML.isArray(htmljs)) { 1521 for (var i = htmljs.length-1; i >= 0; i--) { 1522 workStack.push(Blaze._bind(Blaze._materializeDOM, null, 1523 htmljs[i], intoArray, parentView, workStack)); 1524 } 1525 return; 1526 } else { 1527 if (htmljs instanceof Blaze.Template) { 1528 htmljs = htmljs.constructView(); 1529 // fall through to Blaze.View case below 1530 } 1531 if (htmljs instanceof Blaze.View) { 1532 Blaze._materializeView(htmljs, parentView, workStack, intoArray); 1533 return; 1534 } 1535 } 1536 } 1537 1538 throw new Error("Unexpected object in htmljs: " + htmljs); 1539}; 1540 1541var materializeTag = function (tag, parentView, workStack) { 1542 var tagName = tag.tagName; 1543 var elem; 1544 if ((HTML.isKnownSVGElement(tagName) || isSVGAnchor(tag)) 1545 && document.createElementNS) { 1546 // inline SVG 1547 elem = document.createElementNS('http://www.w3.org/2000/svg', tagName); 1548 } else { 1549 // normal elements 1550 elem = document.createElement(tagName); 1551 } 1552 1553 var rawAttrs = tag.attrs; 1554 var children = tag.children; 1555 if (tagName === 'textarea' && tag.children.length && 1556 ! (rawAttrs && ('value' in rawAttrs))) { 1557 // Provide very limited support for TEXTAREA tags with children 1558 // rather than a "value" attribute. 1559 // Reactivity in the form of Views nested in the tag's children 1560 // won't work. Compilers should compile textarea contents into 1561 // the "value" attribute of the tag, wrapped in a function if there 1562 // is reactivity. 1563 if (typeof rawAttrs === 'function' || 1564 HTML.isArray(rawAttrs)) { 1565 throw new Error("Can't have reactive children of TEXTAREA node; " + 1566 "use the 'value' attribute instead."); 1567 } 1568 rawAttrs = _.extend({}, rawAttrs || null);
1569 rawAttrs.value = Blaze._expand(children, parentView); 1570 children = []; 1571 } 1572 1573 if (rawAttrs) { 1574 var attrUpdater = new ElementAttributesUpdater(elem); 1575 var updateAttributes = function () { 1576 var expandedAttrs = Blaze._expandAttributes(rawAttrs, parentView); 1577 var flattenedAttrs = HTML.flattenAttributes(expandedAttrs); 1578 var stringAttrs = {}; 1579 for (var attrName in flattenedAttrs) { 1580 // map `null`, `undefined`, and `false` to null, which is important 1581 // so that attributes with nully values are considered absent. 1582 // stringify anything else (e.g. strings, booleans, numbers including 0). 1583 if (flattenedAttrs[attrName] == null || flattenedAttrs[attrName] === false) 1584 stringAttrs[attrName] = null; 1585 else 1586 stringAttrs[attrName] = Blaze._toText(flattenedAttrs[attrName], 1587 parentView, 1588 HTML.TEXTMODE.STRING); 1589 } 1590 attrUpdater.update(stringAttrs); 1591 }; 1592 var updaterComputation; 1593 if (parentView) { 1594 updaterComputation = 1595 parentView.autorun(updateAttributes, undefined, 'updater'); 1596 } else { 1597 updaterComputation = Tracker.nonreactive(function () { 1598 return Tracker.autorun(function () { 1599 Tracker._withCurrentView(parentView, updateAttributes); 1600 }); 1601 }); 1602 } 1603 Blaze._DOMBackend.Teardown.onElementTeardown(elem, function attrTeardown() { 1604 updaterComputation.stop(); 1605 }); 1606 } 1607 1608 if (children.length) { 1609 var childNodesAndRanges = []; 1610 // push this function first so that it's done last 1611 workStack.push(function () { 1612 for (var i = 0; i < childNodesAndRanges.length; i++) { 1613 var x = childNodesAndRanges[i]; 1614 if (x instanceof Blaze._DOMRange) 1615 x.attach(elem); 1616 else 1617 elem.appendChild(x); 1618 } 1619 }); 1620 // now push the task that calculates childNodesAndRanges 1621 workStack.push(Blaze._bind(Blaze._materializeDOM, null, 1622 children, childNodesAndRanges, parentView, 1623 workStack)); 1624 } 1625 1626 return elem; 1627}; 1628 1629 1630var isSVGAnchor = function (node) { 1631 // We generally aren't able to detect SVG <a> elements because 1632 // if "A" were in our list of known svg element names, then all 1633 // <a> nodes would be created using 1634 // `document.createElementNS`. But in the special case of <a 1635 // xlink:href="...">, we can at least detect that attribute and 1636 // create an SVG <a> tag in that case. 1637 // 1638 // However, we still have a general problem of knowing when to 1639 // use document.createElementNS and when to use 1640 // document.createElement; for example, font tags will always 1641 // be created as SVG elements which can cause other 1642 // problems. #1977 1643 return (node.tagName === "a" && 1644 node.attrs && 1645 node.attrs["xlink:href"] !== undefined); 1646}; 1647 1648///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1649 1650}).call(this); 1651 1652 1653 1654 1655 1656 1657(function(){ 1658 1659///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1660// // 1661// packages/blaze/exceptions.js // 1662// // 1663///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1664 // 1665var debugFunc; 1666 1667// We call into user code in many places, and it's nice to catch exceptions 1668// propagated from user code immediately so that the whole system doesn't just 1669// break. Catching exceptions is easy; reporting them is hard. This helper 1670// reports exceptions. 1671// 1672// Usage: 1673// 1674// ``` 1675// try { 1676// // ... someStuff ... 1677// } catch (e) { 1678// reportUIException(e); 1679// } 1680// ``` 1681// 1682// An optional second argument overrides the default message. 1683 1684// Set this to `true` to cause `reportException` to throw 1685// the next exception rather than reporting it. This is 1686// useful in unit tests that test error messages. 1687Blaze._throwNextException = false;
1688 1689Blaze._reportException = function (e, msg) { 1690 if (Blaze._throwNextException) { 1691 Blaze._throwNextException = false; 1692 throw e; 1693 } 1694 1695 if (! debugFunc) 1696 // adapted from Tracker 1697 debugFunc = function () { 1698 return (typeof Meteor !== "undefined" ? Meteor._debug : 1699 ((typeof console !== "undefined") && console.log ? console.log : 1700 function () {})); 1701 }; 1702 1703 // In Chrome, `e.stack` is a multiline string that starts with the message 1704 // and contains a stack trace. Furthermore, `console.log` makes it clickable. 1705 // `console.log` supplies the space between the two arguments. 1706 debugFunc()(msg || 'Exception caught in template:', e.stack || e.message || e); 1707}; 1708 1709Blaze._wrapCatchingExceptions = function (f, where) { 1710 if (typeof f !== 'function') 1711 return f; 1712 1713 return function () { 1714 try { 1715 return f.apply(this, arguments); 1716 } catch (e) { 1717 Blaze._reportException(e, 'Exception in ' + where + ':'); 1718 } 1719 }; 1720}; 1721 1722///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1723 1724}).call(this); 1725 1726 1727 1728 1729 1730 1731(function(){ 1732 1733///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1734// // 1735// packages/blaze/view.js // 1736// // 1737///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1738 // 1739/// [new] Blaze.View([name], renderMethod) 1740/// 1741/// Blaze.View is the building block of reactive DOM. Views have 1742/// the following features: 1743/// 1744/// * lifecycle callbacks - Views are created, rendered, and destroyed, 1745/// and callbacks can be registered to fire when these things happen. 1746/// 1747/// * parent pointer - A View points to its parentView, which is the 1748/// View that caused it to be rendered. These pointers form a 1749/// hierarchy or tree of Views. 1750/// 1751/// * render() method - A View's render() method specifies the DOM 1752/// (or HTML) content of the View. If the method establishes 1753/// reactive dependencies, it may be re-run. 1754/// 1755/// * a DOMRange - If a View is rendered to DOM, its position and 1756/// extent in the DOM are tracked using a DOMRange object. 1757/// 1758/// When a View is constructed by calling Blaze.View, the View is 1759/// not yet considered "created." It doesn't have a parentView yet, 1760/// and no logic has been run to initialize the View. All real 1761/// work is deferred until at least creation time, when the onViewCreated 1762/// callbacks are fired, which happens when the View is "used" in 1763/// some way that requires it to be rendered. 1764/// 1765/// ...more lifecycle stuff 1766/// 1767/// `name` is an optional string tag identifying the View. The only 1768/// time it's used is when looking in the View tree for a View of a 1769/// particular name; for example, data contexts are stored on Views 1770/// of name "with". Names are also useful when debugging, so in 1771/// general it's good for functions that create Views to set the name. 1772/// Views associated with templates have names of the form "Template.foo". 1773 1774/** 1775 * @class 1776 * @summary Constructor for a View, which represents a reactive region of DOM. 1777 * @locus Client 1778 * @param {String} [name] Optional. A name for this type of View. See [`view.name`](#view_name). 1779 * @param {Function} renderFunction A function that returns [*renderable content*](#Renderable-Content). In this function, `this` is bound to the View. 1780 */ 1781Blaze.View = function (name, render) { 1782 if (! (this instanceof Blaze.View)) 1783 // called without `new` 1784 return new Blaze.View(name, render); 1785 1786 if (typeof name === 'function') { 1787 // omitted "name" argument 1788 render = name; 1789 name = ''; 1790 } 1791 this.name = name; 1792 this._render = render; 1793 1794 this._callbacks = { 1795 created: null, 1796 rendered: null, 1797 destroyed: null 1798 }; 1799 1800 // Setting all properties here is good for readability, 1801 // and also may help Chrome optimize the code by keeping 1802 // the View object from changing shape too much. 1803 this.isCreated = false;
1804 this._isCreatedForExpansion = false; 1805 this.isRendered = false; 1806 this._isAttached = false; 1807 this.isDestroyed = false; 1808 this._isInRender = false; 1809 this.parentView = null; 1810 this._domrange = null; 1811 // This flag is normally set to false except for the cases when view's parent 1812 // was generated as part of expanding some syntactic sugar expressions or 1813 // methods. 1814 // Ex.: Blaze.renderWithData is an equivalent to creating a view with regular 1815 // Blaze.render and wrapping it into {{#with data}}{{/with}} view. Since the 1816 // users don't know anything about these generated parent views, Blaze needs 1817 // this information to be available on views to make smarter decisions. For 1818 // example: removing the generated parent view with the view on Blaze.remove. 1819 this._hasGeneratedParent = false; 1820 // Bindings accessible to children views (via view.lookup('name')) within the 1821 // closest template view. 1822 this._scopeBindings = {}; 1823 1824 this.renderCount = 0; 1825}; 1826 1827Blaze.View.prototype._render = function () { return null; }; 1828 1829Blaze.View.prototype.onViewCreated = function (cb) { 1830 this._callbacks.created = this._callbacks.created || []; 1831 this._callbacks.created.push(cb); 1832}; 1833 1834Blaze.View.prototype._onViewRendered = function (cb) { 1835 this._callbacks.rendered = this._callbacks.rendered || []; 1836 this._callbacks.rendered.push(cb); 1837}; 1838 1839Blaze.View.prototype.onViewReady = function (cb) { 1840 var self = this; 1841 var fire = function () { 1842 Tracker.afterFlush(function () { 1843 if (! self.isDestroyed) { 1844 Blaze._withCurrentView(self, function () { 1845 cb.call(self); 1846 }); 1847 } 1848 }); 1849 }; 1850 self._onViewRendered(function onViewRendered() { 1851 if (self.isDestroyed) 1852 return; 1853 if (! self._domrange.attached) 1854 self._domrange.onAttached(fire); 1855 else 1856 fire(); 1857 }); 1858}; 1859 1860Blaze.View.prototype.onViewDestroyed = function (cb) { 1861 this._callbacks.destroyed = this._callbacks.destroyed || []; 1862 this._callbacks.destroyed.push(cb); 1863}; 1864Blaze.View.prototype.removeViewDestroyedListener = function (cb) { 1865 var destroyed = this._callbacks.destroyed; 1866 if (! destroyed) 1867 return; 1868 var index = _.lastIndexOf(destroyed, cb); 1869 if (index !== -1) { 1870 // XXX You'd think the right thing to do would be splice, but _fireCallbacks 1871 // gets sad if you remove callbacks while iterating over the list. Should 1872 // change this to use callback-hook or EventEmitter or something else that 1873 // properly supports removal. 1874 destroyed[index] = null; 1875 } 1876}; 1877 1878/// View#autorun(func) 1879/// 1880/// Sets up a Tracker autorun that is "scoped" to this View in two 1881/// important ways: 1) Blaze.currentView is automatically set 1882/// on every re-run, and 2) the autorun is stopped when the 1883/// View is destroyed. As with Tracker.autorun, the first run of 1884/// the function is immediate, and a Computation object that can 1885/// be used to stop the autorun is returned. 1886/// 1887/// View#autorun is meant to be called from View callbacks like 1888/// onViewCreated, or from outside the rendering process. It may not 1889/// be called before the onViewCreated callbacks are fired (too early), 1890/// or from a render() method (too confusing). 1891/// 1892/// Typically, autoruns that update the state 1893/// of the View (as in Blaze.With) should be started from an onViewCreated 1894/// callback. Autoruns that update the DOM should be started 1895/// from either onViewCreated (guarded against the absence of 1896/// view._domrange), or onViewReady. 1897Blaze.View.prototype.autorun = function (f, _inViewScope, displayName) { 1898 var self = this; 1899 1900 // The restrictions on when View#autorun can be called are in order 1901 // to avoid bad patterns, like creating a Blaze.View and immediately 1902 // calling autorun on it. A freshly created View is not ready to 1903 // have logic run on it; it doesn't have a parentView, for example. 1904 // It's when the View is materialized or expanded that the onViewCreated 1905 // handlers are fired and the View starts up. 1906 // 1907 // Letting the render() method call `this.autorun()` is problematic 1908 // because of re-render. The best we can do is to stop the old 1909 // autorun and start a new one for each render, but that's a pattern 1910 // we try to avoid internally because it leads to helpers being 1911 // called extra times, in the case where the autorun causes the 1912 // view to re-render (and thus the autorun to be torn down and a 1913 // new one established). 1914 // 1915 // We could lift these restrictions in various ways. One interesting 1916 // idea is to allow you to call `view.autorun` after instantiating 1917 // `view`, and automatically wrap it in `view.onViewCreated`, deferring 1918 // the autorun so that it starts at an appropriate time. However,
1919 // then we can't return the Computation object to the caller, because 1920 // it doesn't exist yet. 1921 if (! self.isCreated) { 1922 throw new Error("View#autorun must be called from the created callback at the earliest"); 1923 } 1924 if (this._isInRender) { 1925 throw new Error("Can't call View#autorun from inside render(); try calling it from the created or rendered callback"); 1926 } 1927 1928 var templateInstanceFunc = Blaze.Template._currentTemplateInstanceFunc; 1929 1930 var func = function viewAutorun(c) { 1931 return Blaze._withCurrentView(_inViewScope || self, function () { 1932 return Blaze.Template._withTemplateInstanceFunc( 1933 templateInstanceFunc, function () { 1934 return f.call(self, c); 1935 }); 1936 }); 1937 }; 1938 1939 // Give the autorun function a better name for debugging and profiling. 1940 // The `displayName` property is not part of the spec but browsers like Chrome 1941 // and Firefox prefer it in debuggers over the name function was declared by. 1942 func.displayName = 1943 (self.name || 'anonymous') + ':' + (displayName || 'anonymous'); 1944 var comp = Tracker.autorun(func); 1945 1946 var stopComputation = function () { comp.stop(); }; 1947 self.onViewDestroyed(stopComputation); 1948 comp.onStop(function () { 1949 self.removeViewDestroyedListener(stopComputation); 1950 }); 1951 1952 return comp; 1953}; 1954 1955Blaze.View.prototype._errorIfShouldntCallSubscribe = function () { 1956 var self = this; 1957 1958 if (! self.isCreated) { 1959 throw new Error("View#subscribe must be called from the created callback at the earliest"); 1960 } 1961 if (self._isInRender) { 1962 throw new Error("Can't call View#subscribe from inside render(); try calling it from the created or rendered callback"); 1963 } 1964 if (self.isDestroyed) { 1965 throw new Error("Can't call View#subscribe from inside the destroyed callback, try calling it inside created or rendered."); 1966 } 1967}; 1968 1969/** 1970 * Just like Blaze.View#autorun, but with Meteor.subscribe instead of 1971 * Tracker.autorun. Stop the subscription when the view is destroyed. 1972 * @return {SubscriptionHandle} A handle to the subscription so that you can 1973 * see if it is ready, or stop it manually 1974 */ 1975Blaze.View.prototype.subscribe = function (args, options) { 1976 var self = this; 1977 options = options || {}; 1978 1979 self._errorIfShouldntCallSubscribe(); 1980 1981 var subHandle; 1982 if (options.connection) { 1983 subHandle = options.connection.subscribe.apply(options.connection, args); 1984 } else { 1985 subHandle = Meteor.subscribe.apply(Meteor, args); 1986 } 1987 1988 self.onViewDestroyed(function () { 1989 subHandle.stop(); 1990 }); 1991 1992 return subHandle; 1993}; 1994 1995Blaze.View.prototype.firstNode = function () { 1996 if (! this._isAttached) 1997 throw new Error("View must be attached before accessing its DOM"); 1998 1999 return this._domrange.firstNode(); 2000}; 2001 2002Blaze.View.prototype.lastNode = function () { 2003 if (! this._isAttached) 2004 throw new Error("View must be attached before accessing its DOM"); 2005 2006 return this._domrange.lastNode(); 2007}; 2008 2009Blaze._fireCallbacks = function (view, which) { 2010 Blaze._withCurrentView(view, function () { 2011 Tracker.nonreactive(function fireCallbacks() { 2012 var cbs = view._callbacks[which]; 2013 for (var i = 0, N = (cbs && cbs.length); i < N; i++) 2014 cbs[i] && cbs[i].call(view); 2015 }); 2016 }); 2017}; 2018 2019Blaze._createView = function (view, parentView, forExpansion) { 2020 if (view.isCreated) 2021 throw new Error("Can't render the same View twice"); 2022 2023 view.parentView = (parentView || null); 2024 view.isCreated = true; 2025 if (forExpansion) 2026 view._isCreatedForExpansion = true; 2027 2028 Blaze._fireCallbacks(view, 'created'); 2029}; 2030 2031var doFirstRender = function (view, initialContent) { 2032 var domrange = new Blaze._DOMRange(initialContent); 2033 view._domrange = domrange; 2034 domrange.view = view; 2035 view.isRendered = true; 2036 Blaze._fireCallbacks(view, 'rendered'); 2037 2038 var teardownHook = null; 2039 2040 domrange.onAttached(function attached(range, element) { 2041 view._isAttached = true; 2042 2043 teardownHook = Blaze._DOMBackend.Teardown.onElementTeardown( 2044 element, function teardown() { 2045 Blaze._destroyView(view, true /* _skipNodes */); 2046 }); 2047 }); 2048 2049 // tear down the teardown hook 2050 view.onViewDestroyed(function () { 2051 teardownHook && teardownHook.stop(); 2052 teardownHook = null; 2053 }); 2054 2055 return domrange; 2056}; 2057 2058// Take an uncreated View `view` and create and render it to DOM, 2059// setting up the autorun that updates the View. Returns a new 2060// DOMRange, which has been associated with the View. 2061// 2062// The private arguments `_workStack` and `_intoArray` are passed in 2063// by Blaze._materializeDOM and are only present for recursive calls 2064// (when there is some other _materializeView on the stack). If 2065// provided, then we avoid the mutual recursion of calling back into 2066// Blaze._materializeDOM so that deep View hierarchies don't blow the 2067// stack. Instead, we push tasks onto workStack for the initial 2068// rendering and subsequent setup of the View, and they are done after 2069// we return. When there is a _workStack, we do not return the new 2070// DOMRange, but instead push it into _intoArray from a _workStack 2071// task. 2072Blaze._materializeView = function (view, parentView, _workStack, _intoArray) { 2073 Blaze._createView(view, parentView); 2074 2075 var domrange; 2076 var lastHtmljs; 2077 // We don't expect to be called in a Computation, but just in case, 2078 // wrap in Tracker.nonreactive. 2079 Tracker.nonreactive(function () { 2080 view.autorun(function doRender(c) { 2081 // `view.autorun` sets the current view. 2082 view.renderCount++; 2083 view._isInRender = true;
2084 // Any dependencies that should invalidate this Computation come 2085 // from this line: 2086 var htmljs = view._render(); 2087 view._isInRender = false; 2088 2089 if (! c.firstRun && ! Blaze._isContentEqual(lastHtmljs, htmljs)) { 2090 Tracker.nonreactive(function doMaterialize() { 2091 // re-render 2092 var rangesAndNodes = Blaze._materializeDOM(htmljs, [], view); 2093 domrange.setMembers(rangesAndNodes); 2094 Blaze._fireCallbacks(view, 'rendered'); 2095 }); 2096 } 2097 lastHtmljs = htmljs; 2098 2099 // Causes any nested views to stop immediately, not when we call 2100 // `setMembers` the next time around the autorun. Otherwise, 2101 // helpers in the DOM tree to be replaced might be scheduled 2102 // to re-run before we have a chance to stop them. 2103 Tracker.onInvalidate(function () { 2104 if (domrange) { 2105 domrange.destroyMembers(); 2106 } 2107 }); 2108 }, undefined, 'materialize'); 2109 2110 // first render. lastHtmljs is the first htmljs. 2111 var initialContents; 2112 if (! _workStack) { 2113 initialContents = Blaze._materializeDOM(lastHtmljs, [], view); 2114 domrange = doFirstRender(view, initialContents); 2115 initialContents = null; // help GC because we close over this scope a lot 2116 } else { 2117 // We're being called from Blaze._materializeDOM, so to avoid 2118 // recursion and save stack space, provide a description of the 2119 // work to be done instead of doing it. Tasks pushed onto 2120 // _workStack will be done in LIFO order after we return. 2121 // The work will still be done within a Tracker.nonreactive, 2122 // because it will be done by some call to Blaze._materializeDOM 2123 // (which is always called in a Tracker.nonreactive). 2124 initialContents = []; 2125 // push this function first so that it happens last 2126 _workStack.push(function () { 2127 domrange = doFirstRender(view, initialContents); 2128 initialContents = null; // help GC because of all the closures here 2129 _intoArray.push(domrange); 2130 }); 2131 // now push the task that calculates initialContents 2132 _workStack.push(Blaze._bind(Blaze._materializeDOM, null, 2133 lastHtmljs, initialContents, view, _workStack)); 2134 } 2135 }); 2136 2137 if (! _workStack) { 2138 return domrange; 2139 } else { 2140 return null; 2141 } 2142}; 2143 2144// Expands a View to HTMLjs, calling `render` recursively on all 2145// Views and evaluating any dynamic attributes. Calls the `created` 2146// callback, but not the `materialized` or `rendered` callbacks. 2147// Destroys the view immediately, unless called in a Tracker Computation, 2148// in which case the view will be destroyed when the Computation is 2149// invalidated. If called in a Tracker Computation, the result is a 2150// reactive string; that is, the Computation will be invalidated 2151// if any changes are made to the view or subviews that might affect 2152// the HTML. 2153Blaze._expandView = function (view, parentView) { 2154 Blaze._createView(view, parentView, true /*forExpansion*/); 2155 2156 view._isInRender = true; 2157 var htmljs = Blaze._withCurrentView(view, function () { 2158 return view._render(); 2159 }); 2160 view._isInRender = false;
2161 2162 var result = Blaze._expand(htmljs, view); 2163 2164 if (Tracker.active) { 2165 Tracker.onInvalidate(function () { 2166 Blaze._destroyView(view); 2167 }); 2168 } else { 2169 Blaze._destroyView(view); 2170 } 2171 2172 return result; 2173}; 2174 2175// Options: `parentView` 2176Blaze._HTMLJSExpander = HTML.TransformingVisitor.extend(); 2177Blaze._HTMLJSExpander.def({ 2178 visitObject: function (x) { 2179 if (x instanceof Blaze.Template) 2180 x = x.constructView(); 2181 if (x instanceof Blaze.View) 2182 return Blaze._expandView(x, this.parentView); 2183 2184 // this will throw an error; other objects are not allowed! 2185 return HTML.TransformingVisitor.prototype.visitObject.call(this, x); 2186 }, 2187 visitAttributes: function (attrs) { 2188 // expand dynamic attributes 2189 if (typeof attrs === 'function') 2190 attrs = Blaze._withCurrentView(this.parentView, attrs); 2191 2192 // call super (e.g. for case where `attrs` is an array) 2193 return HTML.TransformingVisitor.prototype.visitAttributes.call(this, attrs); 2194 }, 2195 visitAttribute: function (name, value, tag) { 2196 // expand attribute values that are functions. Any attribute value 2197 // that contains Views must be wrapped in a function. 2198 if (typeof value === 'function') 2199 value = Blaze._withCurrentView(this.parentView, value); 2200 2201 return HTML.TransformingVisitor.prototype.visitAttribute.call( 2202 this, name, value, tag); 2203 } 2204}); 2205 2206// Return Blaze.currentView, but only if it is being rendered 2207// (i.e. we are in its render() method). 2208var currentViewIfRendering = function () { 2209 var view = Blaze.currentView; 2210 return (view && view._isInRender) ? view : null; 2211}; 2212 2213Blaze._expand = function (htmljs, parentView) { 2214 parentView = parentView || currentViewIfRendering(); 2215 return (new Blaze._HTMLJSExpander( 2216 {parentView: parentView})).visit(htmljs); 2217}; 2218 2219Blaze._expandAttributes = function (attrs, parentView) { 2220 parentView = parentView || currentViewIfRendering(); 2221 return (new Blaze._HTMLJSExpander( 2222 {parentView: parentView})).visitAttributes(attrs); 2223}; 2224 2225Blaze._destroyView = function (view, _skipNodes) { 2226 if (view.isDestroyed) 2227 return; 2228 view.isDestroyed = true; 2229 2230 Blaze._fireCallbacks(view, 'destroyed'); 2231 2232 // Destroy views and elements recursively. If _skipNodes, 2233 // only recurse up to views, not elements, for the case where 2234 // the backend (jQuery) is recursing over the elements already. 2235 2236 if (view._domrange) 2237 view._domrange.destroyMembers(_skipNodes); 2238}; 2239 2240Blaze._destroyNode = function (node) { 2241 if (node.nodeType === 1) 2242 Blaze._DOMBackend.Teardown.tearDownElement(node); 2243}; 2244 2245// Are the HTMLjs entities `a` and `b` the same? We could be 2246// more elaborate here but the point is to catch the most basic 2247// cases. 2248Blaze._isContentEqual = function (a, b) { 2249 if (a instanceof HTML.Raw) { 2250 return (b instanceof HTML.Raw) && (a.value === b.value); 2251 } else if (a == null) { 2252 return (b == null); 2253 } else { 2254 return (a === b) && 2255 ((typeof a === 'number') || (typeof a === 'boolean') || 2256 (typeof a === 'string')); 2257 } 2258}; 2259 2260/** 2261 * @summary The View corresponding to the current template helper, event handler, callback, or autorun. If there isn't one, `null`. 2262 * @locus Client 2263 * @type {Blaze.View} 2264 */ 2265Blaze.currentView = null; 2266 2267Blaze._withCurrentView = function (view, func) { 2268 var oldView = Blaze.currentView; 2269 try { 2270 Blaze.currentView = view; 2271 return func(); 2272 } finally { 2273 Blaze.currentView = oldView; 2274 } 2275}; 2276 2277// Blaze.render publicly takes a View or a Template. 2278// Privately, it takes any HTMLJS (extended with Views and Templates) 2279// except null or undefined, or a function that returns any extended 2280// HTMLJS. 2281var checkRenderContent = function (content) { 2282 if (content === null) 2283 throw new Error("Can't render null"); 2284 if (typeof content === 'undefined') 2285 throw new Error("Can't render undefined"); 2286 2287 if ((content instanceof Blaze.View) || 2288 (content instanceof Blaze.Template) || 2289 (typeof content === 'function')) 2290 return; 2291 2292 try { 2293 // Throw if content doesn't look like HTMLJS at the top level 2294 // (i.e. verify that this is an HTML.Tag, or an array, 2295 // or a primitive, etc.) 2296 (new HTML.Visitor).visit(content); 2297 } catch (e) { 2298 // Make error message suitable for public API 2299 throw new Error("Expected Template or View"); 2300 } 2301}; 2302 2303// For Blaze.render and Blaze.toHTML, take content and 2304// wrap it in a View, unless it's a single View or 2305// Template already. 2306var contentAsView = function (content) { 2307 checkRenderContent(content); 2308 2309 if (content instanceof Blaze.Template) { 2310 return content.constructView(); 2311 } else if (content instanceof Blaze.View) { 2312 return content; 2313 } else { 2314 var func = content; 2315 if (typeof func !== 'function') { 2316 func = function () { 2317 return content; 2318 }; 2319 } 2320 return Blaze.View('render', func); 2321 } 2322}; 2323 2324// For Blaze.renderWithData and Blaze.toHTMLWithData, wrap content 2325// in a function, if necessary, so it can be a content arg to 2326// a Blaze.With. 2327var contentAsFunc = function (content) { 2328 checkRenderContent(content); 2329 2330 if (typeof content !== 'function') { 2331 return function () { 2332 return content; 2333 }; 2334 } else { 2335 return content; 2336 } 2337}; 2338 2339/** 2340 * @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). 2341 * @locus Client 2342 * @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. 2343 * @param {DOMNode} parentNode The node that will be the parent of the rendered template. It must be an Element node. 2344 * @param {DOMNode} [nextNode] Optional. If provided, must be a child of <em>parentNode</em>; the template will be inserted before this node. If not provided, the template will be inserted as the last child of parentNode. 2345 * @param {Blaze.View} [parentView] Optional. If provided, it will be set as the rendered View's [`parentView`](#view_parentview). 2346 */ 2347Blaze.render = function (content, parentElement, nextNode, parentView) { 2348 if (! parentElement) { 2349 Blaze._warn("Blaze.render without a parent element is deprecated. " + 2350 "You must specify where to insert the rendered content."); 2351 } 2352 2353 if (nextNode instanceof Blaze.View) { 2354 // handle omitted nextNode 2355 parentView = nextNode; 2356 nextNode = null; 2357 } 2358 2359 // parentElement must be a DOM node. in particular, can't be the 2360 // result of a call to `$`. Can't check if `parentElement instanceof 2361 // Node` since 'Node' is undefined in IE8. 2362 if (parentElement && typeof parentElement.nodeType !== 'number') 2363 throw new Error("'parentElement' must be a DOM node"); 2364 if (nextNode && typeof nextNode.nodeType !== 'number') // 'nextNode' is optional 2365 throw new Error("'nextNode' must be a DOM node"); 2366 2367 parentView = parentView || currentViewIfRendering(); 2368 2369 var view = contentAsView(content); 2370 Blaze._materializeView(view, parentView); 2371 2372 if (parentElement) { 2373 view._domrange.attach(parentElement, nextNode); 2374 } 2375 2376 return view; 2377}; 2378 2379Blaze.insert = function (view, parentElement, nextNode) { 2380 Blaze._warn("Blaze.insert has been deprecated. Specify where to insert the " + 2381 "rendered content in the call to Blaze.render."); 2382 2383 if (! (view && (view._domrange instanceof Blaze._DOMRange))) 2384 throw new Error("Expected template rendered with Blaze.render"); 2385 2386 view._domrange.attach(parentElement, nextNode); 2387}; 2388 2389/** 2390 * @summary Renders a template or View to DOM nodes with a data context. Otherwise identical to `Blaze.render`. 2391 * @locus Client 2392 * @param {Template|Blaze.View} templateOrView The template (e.g. `Template.myTemplate`) or View object to render. 2393 * @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. 2394 * @param {DOMNode} parentNode The node that will be the parent of the rendered template. It must be an Element node. 2395 * @param {DOMNode} [nextNode] Optional. If provided, must be a child of <em>parentNode</em>; the template will be inserted before this node. If not provided, the template will be inserted as the last child of parentNode. 2396 * @param {Blaze.View} [parentView] Optional. If provided, it will be set as the rendered View's [`parentView`](#view_parentview). 2397 */ 2398Blaze.renderWithData = function (content, data, parentElement, nextNode, parentView) { 2399 // We defer the handling of optional arguments to Blaze.render. At this point, 2400 // `nextNode` may actually be `parentView`. 2401 return Blaze.render(Blaze._TemplateWith(data, contentAsFunc(content)), 2402 parentElement, nextNode, parentView); 2403}; 2404 2405/** 2406 * @summary Removes a rendered View from the DOM, stopping all reactive updates and event listeners on it. Also destroys the Blaze.Template instance associated with the view. 2407 * @locus Client 2408 * @param {Blaze.View} renderedView The return value from `Blaze.render` or `Blaze.renderWithData`, or the `view` property of a Blaze.Template instance. Calling `Blaze.remove(Template.instance().view)` from within a template event handler will destroy the view as well as that template and trigger the template's `onDestroyed` handlers. 2409 */ 2410Blaze.remove = function (view) { 2411 if (! (view && (view._domrange instanceof Blaze._DOMRange))) 2412 throw new Error("Expected template rendered with Blaze.render"); 2413 2414 while (view) { 2415 if (! view.isDestroyed) { 2416 var range = view._domrange; 2417 if (range.attached && ! range.parentRange) 2418 range.detach(); 2419 range.destroy(); 2420 } 2421 2422 view = view._hasGeneratedParent && view.parentView; 2423 } 2424}; 2425 2426/**
2427 * @summary Renders a template or View to a string of HTML. 2428 * @locus Client 2429 * @param {Template|Blaze.View} templateOrView The template (e.g. `Template.myTemplate`) or View object from which to generate HTML. 2430 */ 2431Blaze.toHTML = function (content, parentView) { 2432 parentView = parentView || currentViewIfRendering(); 2433 2434 return HTML.toHTML(Blaze._expandView(contentAsView(content), parentView)); 2435}; 2436 2437/** 2438 * @summary Renders a template or View to HTML with a data context. Otherwise identical to `Blaze.toHTML`. 2439 * @locus Client 2440 * @param {Template|Blaze.View} templateOrView The template (e.g. `Template.myTemplate`) or View object from which to generate HTML. 2441 * @param {Object|Function} data The data context to use, or a function returning a data context. 2442 */ 2443Blaze.toHTMLWithData = function (content, data, parentView) { 2444 parentView = parentView || currentViewIfRendering(); 2445 2446 return HTML.toHTML(Blaze._expandView(Blaze._TemplateWith( 2447 data, contentAsFunc(content)), parentView)); 2448}; 2449 2450Blaze._toText = function (htmljs, parentView, textMode) { 2451 if (typeof htmljs === 'function') 2452 throw new Error("Blaze._toText doesn't take a function, just HTMLjs"); 2453 2454 if ((parentView != null) && ! (parentView instanceof Blaze.View)) { 2455 // omitted parentView argument 2456 textMode = parentView; 2457 parentView = null; 2458 } 2459 parentView = parentView || currentViewIfRendering(); 2460 2461 if (! textMode) 2462 throw new Error("textMode required"); 2463 if (! (textMode === HTML.TEXTMODE.STRING || 2464 textMode === HTML.TEXTMODE.RCDATA || 2465 textMode === HTML.TEXTMODE.ATTRIBUTE)) 2466 throw new Error("Unknown textMode: " + textMode); 2467 2468 return HTML.toText(Blaze._expand(htmljs, parentView), textMode); 2469}; 2470 2471/** 2472 * @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. 2473 * @locus Client 2474 * @param {DOMElement|Blaze.View} [elementOrView] Optional. An element that was rendered by a Meteor, or a View. 2475 */ 2476Blaze.getData = function (elementOrView) { 2477 var theWith; 2478 2479 if (! elementOrView) { 2480 theWith = Blaze.getView('with'); 2481 } else if (elementOrView instanceof Blaze.View) { 2482 var view = elementOrView; 2483 theWith = (view.name === 'with' ? view : 2484 Blaze.getView(view, 'with')); 2485 } else if (typeof elementOrView.nodeType === 'number') { 2486 if (elementOrView.nodeType !== 1) 2487 throw new Error("Expected DOM element"); 2488 theWith = Blaze.getView(elementOrView, 'with'); 2489 } else { 2490 throw new Error("Expected DOM element or View"); 2491 } 2492 2493 return theWith ? theWith.dataVar.get() : null; 2494}; 2495 2496// For back-compat 2497Blaze.getElementData = function (element) { 2498 Blaze._warn("Blaze.getElementData has been deprecated. Use " + 2499 "Blaze.getData(element) instead."); 2500 2501 if (element.nodeType !== 1) 2502 throw new Error("Expected DOM element"); 2503 2504 return Blaze.getData(element); 2505}; 2506 2507// Both arguments are optional. 2508 2509/** 2510 * @summary Gets either the current View, or the View enclosing the given DOM element. 2511 * @locus Client 2512 * @param {DOMElement} [element] Optional. If specified, the View enclosing `element` is returned. 2513 */ 2514Blaze.getView = function (elementOrView, _viewName) { 2515 var viewName = _viewName; 2516 2517 if ((typeof elementOrView) === 'string') { 2518 // omitted elementOrView; viewName present 2519 viewName = elementOrView; 2520 elementOrView = null; 2521 } 2522 2523 // We could eventually shorten the code by folding the logic 2524 // from the other methods into this method. 2525 if (! elementOrView) { 2526 return Blaze._getCurrentView(viewName); 2527 } else if (elementOrView instanceof Blaze.View) { 2528 return Blaze._getParentView(elementOrView, viewName); 2529 } else if (typeof elementOrView.nodeType === 'number') { 2530 return Blaze._getElementView(elementOrView, viewName); 2531 } else { 2532 throw new Error("Expected DOM element or View"); 2533 } 2534}; 2535 2536// Gets the current view or its nearest ancestor of name 2537// `name`. 2538Blaze._getCurrentView = function (name) { 2539 var view = Blaze.currentView; 2540 // Better to fail in cases where it doesn't make sense 2541 // to use Blaze._getCurrentView(). There will be a current 2542 // view anywhere it does. You can check Blaze.currentView 2543 // if you want to know whether there is one or not. 2544 if (! view) 2545 throw new Error("There is no current view"); 2546 2547 if (name) { 2548 while (view && view.name !== name) 2549 view = view.parentView; 2550 return view || null; 2551 } else { 2552 // Blaze._getCurrentView() with no arguments just returns 2553 // Blaze.currentView. 2554 return view; 2555 } 2556}; 2557 2558Blaze._getParentView = function (view, name) { 2559 var v = view.parentView; 2560 2561 if (name) { 2562 while (v && v.name !== name) 2563 v = v.parentView; 2564 } 2565 2566 return v || null; 2567}; 2568 2569Blaze._getElementView = function (elem, name) { 2570 var range = Blaze._DOMRange.forElement(elem); 2571 var view = null; 2572 while (range && ! view) { 2573 view = (range.view || null); 2574 if (! view) { 2575 if (range.parentRange) 2576 range = range.parentRange; 2577 else 2578 range = Blaze._DOMRange.forElement(range.parentElement); 2579 } 2580 } 2581 2582 if (name) { 2583 while (view && view.name !== name) 2584 view = view.parentView; 2585 return view || null; 2586 } else { 2587 return view; 2588 } 2589}; 2590 2591Blaze._addEventMap = function (view, eventMap, thisInHandler) { 2592 thisInHandler = (thisInHandler || null); 2593 var handles = []; 2594 2595 if (! view._domrange) 2596 throw new Error("View must have a DOMRange"); 2597 2598 view._domrange.onAttached(function attached_eventMaps(range, element) { 2599 _.each(eventMap, function (handler, spec) { 2600 var clauses = spec.split(/,\s+/); 2601 // iterate over clauses of spec, e.g. ['click .foo', 'click .bar'] 2602 _.each(clauses, function (clause) { 2603 var parts = clause.split(/\s+/); 2604 if (parts.length === 0) 2605 return; 2606 2607 var newEvents = parts.shift(); 2608 var selector = parts.join(' '); 2609 handles.push(Blaze._EventSupport.listen( 2610 element, newEvents, selector, 2611 function (evt) { 2612 if (! range.containsElement(evt.currentTarget)) 2613 return null; 2614 var handlerThis = thisInHandler || this; 2615 var handlerArgs = arguments; 2616 return Blaze._withCurrentView(view, function () { 2617 return handler.apply(handlerThis, handlerArgs); 2618 }); 2619 }, 2620 range, function (r) { 2621 return r.parentRange; 2622 })); 2623 }); 2624 }); 2625 }); 2626 2627 view.onViewDestroyed(function () { 2628 _.each(handles, function (h) { 2629 h.stop(); 2630 }); 2631 handles.length = 0; 2632 }); 2633}; 2634 2635///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2636 2637}).call(this); 2638 2639 2640 2641 2642 2643 2644(function(){ 2645 2646///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2647// // 2648// packages/blaze/builtins.js // 2649// // 2650///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2651 // 2652Blaze._calculateCondition = function (cond) { 2653 if (cond instanceof Array && cond.length === 0) 2654 cond = false;
2655 return !! cond; 2656}; 2657 2658/** 2659 * @summary Constructs a View that renders content with a data context. 2660 * @locus Client 2661 * @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. 2662 * @param {Function} contentFunc A Function that returns [*renderable content*](#Renderable-Content). 2663 */ 2664Blaze.With = function (data, contentFunc) { 2665 var view = Blaze.View('with', contentFunc); 2666 2667 view.dataVar = new ReactiveVar; 2668 2669 view.onViewCreated(function () { 2670 if (typeof data === 'function') { 2671 // `data` is a reactive function 2672 view.autorun(function () { 2673 view.dataVar.set(data()); 2674 }, view.parentView, 'setData'); 2675 } else { 2676 view.dataVar.set(data); 2677 } 2678 }); 2679 2680 return view; 2681}; 2682 2683/** 2684 * Attaches bindings to the instantiated view. 2685 * @param {Object} bindings A dictionary of bindings, each binding name 2686 * corresponds to a value or a function that will be reactively re-run. 2687 * @param {View} view The target. 2688 */ 2689Blaze._attachBindingsToView = function (bindings, view) { 2690 view.onViewCreated(function () { 2691 _.each(bindings, function (binding, name) { 2692 view._scopeBindings[name] = new ReactiveVar; 2693 if (typeof binding === 'function') { 2694 view.autorun(function () { 2695 view._scopeBindings[name].set(binding()); 2696 }, view.parentView); 2697 } else { 2698 view._scopeBindings[name].set(binding); 2699 } 2700 }); 2701 }); 2702}; 2703 2704/** 2705 * @summary Constructs a View setting the local lexical scope in the block. 2706 * @param {Function} bindings Dictionary mapping names of bindings to 2707 * values or computations to reactively re-run. 2708 * @param {Function} contentFunc A Function that returns [*renderable content*](#Renderable-Content). 2709 */ 2710Blaze.Let = function (bindings, contentFunc) { 2711 var view = Blaze.View('let', contentFunc); 2712 Blaze._attachBindingsToView(bindings, view); 2713 2714 return view; 2715}; 2716 2717/** 2718 * @summary Constructs a View that renders content conditionally. 2719 * @locus Client 2720 * @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. 2721 * @param {Function} contentFunc A Function that returns [*renderable content*](#Renderable-Content). 2722 * @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. 2723 */ 2724Blaze.If = function (conditionFunc, contentFunc, elseFunc, _not) { 2725 var conditionVar = new ReactiveVar; 2726 2727 var view = Blaze.View(_not ? 'unless' : 'if', function () { 2728 return conditionVar.get() ? contentFunc() : 2729 (elseFunc ? elseFunc() : null); 2730 }); 2731 view.__conditionVar = conditionVar; 2732 view.onViewCreated(function () { 2733 this.autorun(function () { 2734 var cond = Blaze._calculateCondition(conditionFunc()); 2735 conditionVar.set(_not ? (! cond) : cond); 2736 }, this.parentView, 'condition'); 2737 }); 2738 2739 return view; 2740}; 2741 2742/** 2743 * @summary An inverted [`Blaze.If`](#Blaze-If). 2744 * @locus Client 2745 * @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. 2746 * @param {Function} contentFunc A Function that returns [*renderable content*](#Renderable-Content). 2747 * @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. 2748 */ 2749Blaze.Unless = function (conditionFunc, contentFunc, elseFunc) { 2750 return Blaze.If(conditionFunc, contentFunc, elseFunc, true /*_not*/); 2751}; 2752 2753/** 2754 * @summary Constructs a View that renders `contentFunc` for each item in a sequence. 2755 * @locus Client 2756 * @param {Function} argFunc A function to reactively re-run. The function can 2757 * return one of two options: 2758 * 2759 * 1. An object with two fields: '_variable' and '_sequence'. Each iterates over 2760 * '_sequence', it may be a Cursor, an array, null, or undefined. Inside the 2761 * Each body you will be able to get the current item from the sequence using 2762 * the name specified in the '_variable' field. 2763 * 2764 * 2. Just a sequence (Cursor, array, null, or undefined) not wrapped into an 2765 * object. Inside the Each body, the current item will be set as the data 2766 * context. 2767 * @param {Function} contentFunc A Function that returns [*renderable 2768 * content*](#Renderable-Content). 2769 * @param {Function} [elseFunc] A Function that returns [*renderable 2770 * content*](#Renderable-Content) to display in the case when there are no items 2771 * in the sequence. 2772 */
2773Blaze.Each = function (argFunc, contentFunc, elseFunc) { 2774 var eachView = Blaze.View('each', function () { 2775 var subviews = this.initialSubviews; 2776 this.initialSubviews = null; 2777 if (this._isCreatedForExpansion) { 2778 this.expandedValueDep = new Tracker.Dependency; 2779 this.expandedValueDep.depend(); 2780 } 2781 return subviews; 2782 }); 2783 eachView.initialSubviews = []; 2784 eachView.numItems = 0; 2785 eachView.inElseMode = false; 2786 eachView.stopHandle = null; 2787 eachView.contentFunc = contentFunc; 2788 eachView.elseFunc = elseFunc; 2789 eachView.argVar = new ReactiveVar; 2790 eachView.variableName = null; 2791 2792 // update the @index value in the scope of all subviews in the range 2793 var updateIndices = function (from, to) { 2794 if (to === undefined) { 2795 to = eachView.numItems - 1; 2796 } 2797 2798 for (var i = from; i <= to; i++) { 2799 var view = eachView._domrange.members[i].view; 2800 view._scopeBindings['@index'].set(i); 2801 } 2802 }; 2803 2804 eachView.onViewCreated(function () { 2805 // We evaluate argFunc in an autorun to make sure 2806 // Blaze.currentView is always set when it runs (rather than 2807 // passing argFunc straight to ObserveSequence). 2808 eachView.autorun(function () { 2809 // argFunc can return either a sequence as is or a wrapper object with a 2810 // _sequence and _variable fields set. 2811 var arg = argFunc(); 2812 if (_.isObject(arg) && _.has(arg, '_sequence')) { 2813 eachView.variableName = arg._variable || null; 2814 arg = arg._sequence; 2815 } 2816 2817 eachView.argVar.set(arg); 2818 }, eachView.parentView, 'collection'); 2819 2820 eachView.stopHandle = ObserveSequence.observe(function () { 2821 return eachView.argVar.get(); 2822 }, {
2823 addedAt: function (id, item, index) { 2824 Tracker.nonreactive(function () { 2825 var newItemView; 2826 if (eachView.variableName) { 2827 // new-style #each (as in {{#each item in items}}) 2828 // doesn't create a new data context 2829 newItemView = Blaze.View('item', eachView.contentFunc); 2830 } else { 2831 newItemView = Blaze.With(item, eachView.contentFunc); 2832 } 2833 2834 eachView.numItems++; 2835 2836 var bindings = {}; 2837 bindings['@index'] = index; 2838 if (eachView.variableName) { 2839 bindings[eachView.variableName] = item; 2840 } 2841 Blaze._attachBindingsToView(bindings, newItemView); 2842 2843 if (eachView.expandedValueDep) { 2844 eachView.expandedValueDep.changed(); 2845 } else if (eachView._domrange) { 2846 if (eachView.inElseMode) { 2847 eachView._domrange.removeMember(0); 2848 eachView.inElseMode = false; 2849 } 2850 2851 var range = Blaze._materializeView(newItemView, eachView); 2852 eachView._domrange.addMember(range, index); 2853 updateIndices(index); 2854 } else { 2855 eachView.initialSubviews.splice(index, 0, newItemView); 2856 } 2857 }); 2858 }, 2859 removedAt: function (id, item, index) { 2860 Tracker.nonreactive(function () { 2861 eachView.numItems--; 2862 if (eachView.expandedValueDep) { 2863 eachView.expandedValueDep.changed(); 2864 } else if (eachView._domrange) { 2865 eachView._domrange.removeMember(index); 2866 updateIndices(index); 2867 if (eachView.elseFunc && eachView.numItems === 0) { 2868 eachView.inElseMode = true; 2869 eachView._domrange.addMember( 2870 Blaze._materializeView( 2871 Blaze.View('each_else',eachView.elseFunc), 2872 eachView), 0); 2873 } 2874 } else { 2875 eachView.initialSubviews.splice(index, 1); 2876 } 2877 }); 2878 }, 2879 changedAt: function (id, newItem, oldItem, index) { 2880 Tracker.nonreactive(function () { 2881 if (eachView.expandedValueDep) { 2882 eachView.expandedValueDep.changed(); 2883 } else { 2884 var itemView; 2885 if (eachView._domrange) { 2886 itemView = eachView._domrange.getMember(index).view; 2887 } else { 2888 itemView = eachView.initialSubviews[index]; 2889 } 2890 if (eachView.variableName) { 2891 itemView._scopeBindings[eachView.variableName].set(newItem); 2892 } else { 2893 itemView.dataVar.set(newItem); 2894 } 2895 } 2896 }); 2897 }, 2898 movedTo: function (id, item, fromIndex, toIndex) { 2899 Tracker.nonreactive(function () { 2900 if (eachView.expandedValueDep) { 2901 eachView.expandedValueDep.changed(); 2902 } else if (eachView._domrange) { 2903 eachView._domrange.moveMember(fromIndex, toIndex); 2904 updateIndices( 2905 Math.min(fromIndex, toIndex), Math.max(fromIndex, toIndex)); 2906 } else { 2907 var subviews = eachView.initialSubviews; 2908 var itemView = subviews[fromIndex]; 2909 subviews.splice(fromIndex, 1); 2910 subviews.splice(toIndex, 0, itemView); 2911 } 2912 }); 2913 } 2914 }); 2915 2916 if (eachView.elseFunc && eachView.numItems === 0) { 2917 eachView.inElseMode = true; 2918 eachView.initialSubviews[0] = 2919 Blaze.View('each_else', eachView.elseFunc); 2920 } 2921 }); 2922 2923 eachView.onViewDestroyed(function () { 2924 if (eachView.stopHandle) 2925 eachView.stopHandle.stop(); 2926 }); 2927 2928 return eachView; 2929}; 2930 2931Blaze._TemplateWith = function (arg, contentFunc) { 2932 var w; 2933 2934 var argFunc = arg; 2935 if (typeof arg !== 'function') { 2936 argFunc = function () { 2937 return arg; 2938 }; 2939 } 2940 2941 // This is a little messy. When we compile `{{> Template.contentBlock}}`, we 2942 // wrap it in Blaze._InOuterTemplateScope in order to skip the intermediate 2943 // parent Views in the current template. However, when there's an argument 2944 // (`{{> Template.contentBlock arg}}`), the argument needs to be evaluated 2945 // in the original scope. There's no good order to nest 2946 // Blaze._InOuterTemplateScope and Spacebars.TemplateWith to achieve this, 2947 // so we wrap argFunc to run it in the "original parentView" of the 2948 // Blaze._InOuterTemplateScope. 2949 // 2950 // To make this better, reconsider _InOuterTemplateScope as a primitive. 2951 // Longer term, evaluate expressions in the proper lexical scope.
2952 var wrappedArgFunc = function () { 2953 var viewToEvaluateArg = null; 2954 if (w.parentView && w.parentView.name === 'InOuterTemplateScope') { 2955 viewToEvaluateArg = w.parentView.originalParentView; 2956 } 2957 if (viewToEvaluateArg) { 2958 return Blaze._withCurrentView(viewToEvaluateArg, argFunc); 2959 } else { 2960 return argFunc(); 2961 } 2962 }; 2963 2964 var wrappedContentFunc = function () { 2965 var content = contentFunc.call(this); 2966 2967 // Since we are generating the Blaze._TemplateWith view for the 2968 // user, set the flag on the child view. If `content` is a template, 2969 // construct the View so that we can set the flag. 2970 if (content instanceof Blaze.Template) { 2971 content = content.constructView(); 2972 } 2973 if (content instanceof Blaze.View) { 2974 content._hasGeneratedParent = true; 2975 } 2976 2977 return content; 2978 }; 2979 2980 w = Blaze.With(wrappedArgFunc, wrappedContentFunc); 2981 w.__isTemplateWith = true; 2982 return w; 2983}; 2984 2985Blaze._InOuterTemplateScope = function (templateView, contentFunc) { 2986 var view = Blaze.View('InOuterTemplateScope', contentFunc); 2987 var parentView = templateView.parentView; 2988 2989 // Hack so that if you call `{{> foo bar}}` and it expands into 2990 // `{{#with bar}}{{> foo}}{{/with}}`, and then `foo` is a template 2991 // that inserts `{{> Template.contentBlock}}`, the data context for 2992 // `Template.contentBlock` is not `bar` but the one enclosing that. 2993 if (parentView.__isTemplateWith) 2994 parentView = parentView.parentView; 2995 2996 view.onViewCreated(function () { 2997 this.originalParentView = this.parentView; 2998 this.parentView = parentView; 2999 this.__childDoesntStartNewLexicalScope = true; 3000 }); 3001 return view; 3002}; 3003 3004// XXX COMPAT WITH 0.9.0 3005Blaze.InOuterTemplateScope = Blaze._InOuterTemplateScope; 3006 3007///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 3008 3009}).call(this); 3010 3011 3012 3013 3014 3015 3016(function(){ 3017 3018///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 3019// // 3020// packages/blaze/lookup.js // 3021// // 3022///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 3023 // 3024Blaze._globalHelpers = {}; 3025 3026// Documented as Template.registerHelper. 3027// This definition also provides back-compat for `UI.registerHelper`. 3028Blaze.registerHelper = function (name, func) { 3029 Blaze._globalHelpers[name] = func; 3030}; 3031 3032// Also documented as Template.deregisterHelper 3033Blaze.deregisterHelper = function(name) { 3034 delete Blaze._globalHelpers[name]; 3035}; 3036 3037var bindIfIsFunction = function (x, target) { 3038 if (typeof x !== 'function') 3039 return x; 3040 return Blaze._bind(x, target); 3041}; 3042 3043// If `x` is a function, binds the value of `this` for that function 3044// to the current data context. 3045var bindDataContext = function (x) { 3046 if (typeof x === 'function') { 3047 return function () { 3048 var data = Blaze.getData(); 3049 if (data == null) 3050 data = {}; 3051 return x.apply(data, arguments); 3052 }; 3053 } 3054 return x; 3055}; 3056 3057Blaze._OLDSTYLE_HELPER = {}; 3058 3059Blaze._getTemplateHelper = function (template, name, tmplInstanceFunc) { 3060 // XXX COMPAT WITH 0.9.3 3061 var isKnownOldStyleHelper = false; 3062 3063 if (template.__helpers.has(name)) { 3064 var helper = template.__helpers.get(name); 3065 if (helper === Blaze._OLDSTYLE_HELPER) { 3066 isKnownOldStyleHelper = true; 3067 } else if (helper != null) { 3068 return wrapHelper(bindDataContext(helper), tmplInstanceFunc); 3069 } else { 3070 return null; 3071 } 3072 } 3073 3074 // old-style helper 3075 if (name in template) { 3076 // Only warn once per helper 3077 if (! isKnownOldStyleHelper) { 3078 template.__helpers.set(name, Blaze._OLDSTYLE_HELPER); 3079 if (! template._NOWARN_OLDSTYLE_HELPERS) { 3080 Blaze._warn('Assigning helper with `' + template.viewName + '.' + 3081 name + ' = ...` is deprecated. Use `' + template.viewName + 3082 '.helpers(...)` instead.'); 3083 } 3084 } 3085 if (template[name] != null) { 3086 return wrapHelper(bindDataContext(template[name]), tmplInstanceFunc); 3087 } 3088 } 3089 3090 return null; 3091}; 3092 3093var wrapHelper = function (f, templateFunc) { 3094 if (typeof f !== "function") { 3095 return f; 3096 } 3097 3098 return function () { 3099 var self = this; 3100 var args = arguments; 3101 3102 return Blaze.Template._withTemplateInstanceFunc(templateFunc, function () { 3103 return Blaze._wrapCatchingExceptions(f, 'template helper').apply(self, args); 3104 }); 3105 }; 3106}; 3107 3108Blaze._lexicalBindingLookup = function (view, name) { 3109 var currentView = view; 3110 var blockHelpersStack = []; 3111 3112 // walk up the views stopping at a Spacebars.include or Template view that 3113 // doesn't have an InOuterTemplateScope view as a parent 3114 do { 3115 // skip block helpers views 3116 // if we found the binding on the scope, return it 3117 if (_.has(currentView._scopeBindings, name)) { 3118 var bindingReactiveVar = currentView._scopeBindings[name]; 3119 return function () { 3120 return bindingReactiveVar.get(); 3121 }; 3122 } 3123 } while (! (currentView.__startsNewLexicalScope && 3124 ! (currentView.parentView && 3125 currentView.parentView.__childDoesntStartNewLexicalScope)) 3126 && (currentView = currentView.parentView)); 3127 3128 return null; 3129}; 3130 3131// templateInstance argument is provided to be available for possible 3132// alternative implementations of this function by 3rd party packages. 3133Blaze._getTemplate = function (name, templateInstance) { 3134 if ((name in Blaze.Template) && (Blaze.Template[name] instanceof Blaze.Template)) { 3135 return Blaze.Template[name]; 3136 } 3137 return null; 3138}; 3139 3140Blaze._getGlobalHelper = function (name, templateInstance) { 3141 if (Blaze._globalHelpers[name] != null) { 3142 return wrapHelper(bindDataContext(Blaze._globalHelpers[name]), templateInstance); 3143 } 3144 return null; 3145}; 3146 3147// Looks up a name, like "foo" or "..", as a helper of the 3148// current template; the name of a template; a global helper; 3149// or a property of the data context. Called on the View of 3150// a template (i.e. a View with a `.template` property, 3151// where the helpers are). Used for the first name in a 3152// "path" in a template tag, like "foo" in `{{foo.bar}}` or 3153// ".." in `{{frobulate ../blah}}`. 3154// 3155// Returns a function, a non-function value, or null. If 3156// a function is found, it is bound appropriately. 3157// 3158// NOTE: This function must not establish any reactive
3159// dependencies itself. If there is any reactivity in the 3160// value, lookup should return a function. 3161Blaze.View.prototype.lookup = function (name, _options) { 3162 var template = this.template; 3163 var lookupTemplate = _options && _options.template; 3164 var helper; 3165 var binding; 3166 var boundTmplInstance; 3167 var foundTemplate; 3168 3169 if (this.templateInstance) { 3170 boundTmplInstance = Blaze._bind(this.templateInstance, this); 3171 } 3172 3173 // 0. looking up the parent data context with the special "../" syntax 3174 if (/^\./.test(name)) { 3175 // starts with a dot. must be a series of dots which maps to an 3176 // ancestor of the appropriate height. 3177 if (!/^(\.)+$/.test(name)) 3178 throw new Error("id starting with dot must be a series of dots"); 3179 3180 return Blaze._parentData(name.length - 1, true /*_functionWrapped*/); 3181 3182 } 3183 3184 // 1. look up a helper on the current template 3185 if (template && ((helper = Blaze._getTemplateHelper(template, name, boundTmplInstance)) != null)) { 3186 return helper; 3187 } 3188 3189 // 2. look up a binding by traversing the lexical view hierarchy inside the 3190 // current template 3191 if (template && (binding = Blaze._lexicalBindingLookup(Blaze.currentView, name)) != null) { 3192 return binding; 3193 } 3194 3195 // 3. look up a template by name 3196 if (lookupTemplate && ((foundTemplate = Blaze._getTemplate(name, boundTmplInstance)) != null)) { 3197 return foundTemplate; 3198 } 3199 3200 // 4. look up a global helper 3201 if ((helper = Blaze._getGlobalHelper(name, boundTmplInstance)) != null) { 3202 return helper; 3203 } 3204 3205 // 5. look up in a data context 3206 return function () { 3207 var isCalledAsFunction = (arguments.length > 0); 3208 var data = Blaze.getData(); 3209 var x = data && data[name]; 3210 if (! x) { 3211 if (lookupTemplate) { 3212 throw new Error("No such template: " + name); 3213 } else if (isCalledAsFunction) { 3214 throw new Error("No such function: " + name); 3215 } else if (name.charAt(0) === '@' && ((x === null) || 3216 (x === undefined))) { 3217 // Throw an error if the user tries to use a `@directive` 3218 // that doesn't exist. We don't implement all directives 3219 // from Handlebars, so there's a potential for confusion 3220 // if we fail silently. On the other hand, we want to 3221 // throw late in case some app or package wants to provide 3222 // a missing directive. 3223 throw new Error("Unsupported directive: " + name); 3224 } 3225 } 3226 if (! data) { 3227 return null; 3228 } 3229 if (typeof x !== 'function') { 3230 if (isCalledAsFunction) { 3231 throw new Error("Can't call non-function: " + x); 3232 } 3233 return x; 3234 } 3235 return x.apply(data, arguments); 3236 }; 3237}; 3238 3239// Implement Spacebars' {{../..}}. 3240// @param height {Number} The number of '..'s 3241Blaze._parentData = function (height, _functionWrapped) { 3242 // If height is null or undefined, we default to 1, the first parent. 3243 if (height == null) { 3244 height = 1; 3245 } 3246 var theWith = Blaze.getView('with'); 3247 for (var i = 0; (i < height) && theWith; i++) { 3248 theWith = Blaze.getView(theWith, 'with'); 3249 } 3250 3251 if (! theWith) 3252 return null; 3253 if (_functionWrapped) 3254 return function () { return theWith.dataVar.get(); }; 3255 return theWith.dataVar.get(); 3256}; 3257 3258 3259Blaze.View.prototype.lookupTemplate = function (name) { 3260 return this.lookup(name, {template:true}); 3261}; 3262 3263///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 3264 3265}).call(this); 3266 3267 3268 3269 3270 3271 3272(function(){ 3273 3274///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 3275// // 3276// packages/blaze/template.js // 3277// // 3278///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 3279 // 3280// [new] Blaze.Template([viewName], renderFunction) 3281// 3282// `Blaze.Template` is the class of templates, like `Template.foo` in 3283// Meteor, which is `instanceof Template`. 3284// 3285// `viewKind` is a string that looks like "Template.foo" for templates 3286// defined by the compiler. 3287 3288/** 3289 * @class 3290 * @summary Constructor for a Template, which is used to construct Views with particular name and content. 3291 * @locus Client 3292 * @param {String} [viewName] Optional. A name for Views constructed by this Template. See [`view.name`](#view_name). 3293 * @param {Function} renderFunction A function that returns [*renderable content*](#Renderable-Content). This function is used as the `renderFunction` for Views constructed by this Template. 3294 */ 3295Blaze.Template = function (viewName, renderFunction) { 3296 if (! (this instanceof Blaze.Template)) 3297 // called without `new` 3298 return new Blaze.Template(viewName, renderFunction); 3299 3300 if (typeof viewName === 'function') { 3301 // omitted "viewName" argument 3302 renderFunction = viewName; 3303 viewName = ''; 3304 } 3305 if (typeof viewName !== 'string') 3306 throw new Error("viewName must be a String (or omitted)"); 3307 if (typeof renderFunction !== 'function') 3308 throw new Error("renderFunction must be a function"); 3309 3310 this.viewName = viewName; 3311 this.renderFunction = renderFunction; 3312 3313 this.__helpers = new HelperMap; 3314 this.__eventMaps = []; 3315 3316 this._callbacks = { 3317 created: [], 3318 rendered: [], 3319 destroyed: [] 3320 }; 3321}; 3322var Template = Blaze.Template; 3323 3324var HelperMap = function () {}; 3325HelperMap.prototype.get = function (name) { 3326 return this[' '+name]; 3327}; 3328HelperMap.prototype.set = function (name, helper) { 3329 this[' '+name] = helper; 3330}; 3331HelperMap.prototype.has = function (name) { 3332 return (typeof this[' '+name] !== 'undefined'); 3333}; 3334 3335/** 3336 * @summary Returns true if `value` is a template object like `Template.myTemplate`. 3337 * @locus Client 3338 * @param {Any} value The value to test. 3339 */ 3340Blaze.isTemplate = function (t) { 3341 return (t instanceof Blaze.Template); 3342}; 3343 3344/** 3345 * @name onCreated 3346 * @instance 3347 * @memberOf Template
3348 * @summary Register a function to be called when an instance of this template is created. 3349 * @param {Function} callback A function to be added as a callback. 3350 * @locus Client 3351 * @importFromPackage templating 3352 */ 3353Template.prototype.onCreated = function (cb) { 3354 this._callbacks.created.push(cb); 3355}; 3356 3357/** 3358 * @name onRendered 3359 * @instance 3360 * @memberOf Template 3361 * @summary Register a function to be called when an instance of this template is inserted into the DOM. 3362 * @param {Function} callback A function to be added as a callback. 3363 * @locus Client 3364 * @importFromPackage templating 3365 */ 3366Template.prototype.onRendered = function (cb) { 3367 this._callbacks.rendered.push(cb); 3368}; 3369 3370/** 3371 * @name onDestroyed 3372 * @instance 3373 * @memberOf Template 3374 * @summary Register a function to be called when an instance of this template is removed from the DOM and destroyed. 3375 * @param {Function} callback A function to be added as a callback. 3376 * @locus Client 3377 * @importFromPackage templating 3378 */ 3379Template.prototype.onDestroyed = function (cb) { 3380 this._callbacks.destroyed.push(cb); 3381}; 3382 3383Template.prototype._getCallbacks = function (which) { 3384 var self = this; 3385 var callbacks = self[which] ? [self[which]] : []; 3386 // Fire all callbacks added with the new API (Template.onRendered()) 3387 // as well as the old-style callback (e.g. Template.rendered) for 3388 // backwards-compatibility. 3389 callbacks = callbacks.concat(self._callbacks[which]); 3390 return callbacks; 3391}; 3392 3393var fireCallbacks = function (callbacks, template) { 3394 Template._withTemplateInstanceFunc( 3395 function () { return template; }, 3396 function () { 3397 for (var i = 0, N = callbacks.length; i < N; i++) { 3398 callbacks[i].call(template); 3399 } 3400 }); 3401}; 3402 3403Template.prototype.constructView = function (contentFunc, elseFunc) { 3404 var self = this; 3405 var view = Blaze.View(self.viewName, self.renderFunction); 3406 view.template = self; 3407 3408 view.templateContentBlock = ( 3409 contentFunc ? new Template('(contentBlock)', contentFunc) : null); 3410 view.templateElseBlock = ( 3411 elseFunc ? new Template('(elseBlock)', elseFunc) : null); 3412 3413 if (self.__eventMaps || typeof self.events === 'object') { 3414 view._onViewRendered(function () { 3415 if (view.renderCount !== 1) 3416 return; 3417 3418 if (! self.__eventMaps.length && typeof self.events === "object") { 3419 // Provide limited back-compat support for `.events = {...}` 3420 // syntax. Pass `template.events` to the original `.events(...)` 3421 // function. This code must run only once per template, in 3422 // order to not bind the handlers more than once, which is 3423 // ensured by the fact that we only do this when `__eventMaps` 3424 // is falsy, and we cause it to be set now. 3425 Template.prototype.events.call(self, self.events); 3426 } 3427 3428 _.each(self.__eventMaps, function (m) { 3429 Blaze._addEventMap(view, m, view); 3430 }); 3431 }); 3432 } 3433 3434 view._templateInstance = new Blaze.TemplateInstance(view); 3435 view.templateInstance = function () { 3436 // Update data, firstNode, and lastNode, and return the TemplateInstance 3437 // object. 3438 var inst = view._templateInstance; 3439 3440 /** 3441 * @instance 3442 * @memberOf Blaze.TemplateInstance 3443 * @name data 3444 * @summary The data context of this instance's latest invocation. 3445 * @locus Client 3446 */ 3447 inst.data = Blaze.getData(view); 3448 3449 if (view._domrange && !view.isDestroyed) { 3450 inst.firstNode = view._domrange.firstNode(); 3451 inst.lastNode = view._domrange.lastNode(); 3452 } else { 3453 // on 'created' or 'destroyed' callbacks we don't have a DomRange 3454 inst.firstNode = null; 3455 inst.lastNode = null; 3456 } 3457 3458 return inst; 3459 }; 3460 3461 /** 3462 * @name created 3463 * @instance 3464 * @memberOf Template 3465 * @summary Provide a callback when an instance of a template is created. 3466 * @locus Client 3467 * @deprecated in 1.1 3468 */ 3469 // To avoid situations when new callbacks are added in between view 3470 // instantiation and event being fired, decide on all callbacks to fire 3471 // immediately and then fire them on the event. 3472 var createdCallbacks = self._getCallbacks('created'); 3473 view.onViewCreated(function () { 3474 fireCallbacks(createdCallbacks, view.templateInstance()); 3475 }); 3476 3477 /** 3478 * @name rendered 3479 * @instance 3480 * @memberOf Template 3481 * @summary Provide a callback when an instance of a template is rendered. 3482 * @locus Client
3483 * @deprecated in 1.1 3484 */ 3485 var renderedCallbacks = self._getCallbacks('rendered'); 3486 view.onViewReady(function () { 3487 fireCallbacks(renderedCallbacks, view.templateInstance()); 3488 }); 3489 3490 /** 3491 * @name destroyed 3492 * @instance 3493 * @memberOf Template 3494 * @summary Provide a callback when an instance of a template is destroyed. 3495 * @locus Client 3496 * @deprecated in 1.1 3497 */ 3498 var destroyedCallbacks = self._getCallbacks('destroyed'); 3499 view.onViewDestroyed(function () { 3500 fireCallbacks(destroyedCallbacks, view.templateInstance()); 3501 }); 3502 3503 return view; 3504}; 3505 3506/** 3507 * @class 3508 * @summary The class for template instances 3509 * @param {Blaze.View} view 3510 * @instanceName template 3511 */ 3512Blaze.TemplateInstance = function (view) { 3513 if (! (this instanceof Blaze.TemplateInstance)) 3514 // called without `new` 3515 return new Blaze.TemplateInstance(view); 3516 3517 if (! (view instanceof Blaze.View)) 3518 throw new Error("View required"); 3519 3520 view._templateInstance = this; 3521 3522 /** 3523 * @name view 3524 * @memberOf Blaze.TemplateInstance 3525 * @instance 3526 * @summary The [View](../api/blaze.html#Blaze-View) object for this invocation of the template. 3527 * @locus Client 3528 * @type {Blaze.View} 3529 */ 3530 this.view = view; 3531 this.data = null; 3532 3533 /** 3534 * @name firstNode 3535 * @memberOf Blaze.TemplateInstance 3536 * @instance 3537 * @summary The first top-level DOM node in this template instance. 3538 * @locus Client 3539 * @type {DOMNode} 3540 */ 3541 this.firstNode = null; 3542 3543 /** 3544 * @name lastNode 3545 * @memberOf Blaze.TemplateInstance 3546 * @instance 3547 * @summary The last top-level DOM node in this template instance. 3548 * @locus Client 3549 * @type {DOMNode} 3550 */ 3551 this.lastNode = null; 3552 3553 // This dependency is used to identify state transitions in 3554 // _subscriptionHandles which could cause the result of 3555 // TemplateInstance#subscriptionsReady to change. Basically this is triggered 3556 // whenever a new subscription handle is added or when a subscription handle 3557 // is removed and they are not ready. 3558 this._allSubsReadyDep = new Tracker.Dependency(); 3559 this._allSubsReady = false; 3560 3561 this._subscriptionHandles = {}; 3562}; 3563 3564/** 3565 * @summary Find all elements matching `selector` in this template instance, and return them as a JQuery object. 3566 * @locus Client 3567 * @param {String} selector The CSS selector to match, scoped to the template contents. 3568 * @returns {DOMNode[]} 3569 */ 3570Blaze.TemplateInstance.prototype.$ = function (selector) { 3571 var view = this.view; 3572 if (! view._domrange) 3573 throw new Error("Can't use $ on template instance with no DOM"); 3574 return view._domrange.$(selector); 3575}; 3576 3577/** 3578 * @summary Find all elements matching `selector` in this template instance. 3579 * @locus Client 3580 * @param {String} selector The CSS selector to match, scoped to the template contents. 3581 * @returns {DOMElement[]} 3582 */ 3583Blaze.TemplateInstance.prototype.findAll = function (selector) { 3584 return Array.prototype.slice.call(this.$(selector)); 3585}; 3586 3587/** 3588 * @summary Find one element matching `selector` in this template instance. 3589 * @locus Client 3590 * @param {String} selector The CSS selector to match, scoped to the template contents. 3591 * @returns {DOMElement} 3592 */ 3593Blaze.TemplateInstance.prototype.find = function (selector) { 3594 var result = this.$(selector); 3595 return result[0] || null; 3596}; 3597 3598/** 3599 * @summary A version of [Tracker.autorun](https://docs.meteor.com/api/tracker.html#Tracker-autorun) that is stopped when the template is destroyed. 3600 * @locus Client 3601 * @param {Function} runFunc The function to run. It receives one argument: a Tracker.Computation object. 3602 */ 3603Blaze.TemplateInstance.prototype.autorun = function (f) { 3604 return this.view.autorun(f); 3605}; 3606 3607/** 3608 * @summary A version of [Meteor.subscribe](https://docs.meteor.com/api/pubsub.html#Meteor-subscribe) that is stopped 3609 * when the template is destroyed. 3610 * @return {SubscriptionHandle} The subscription handle to the newly made 3611 * subscription. Call `handle.stop()` to manually stop the subscription, or 3612 * `handle.ready()` to find out if this particular subscription has loaded all 3613 * of its inital data. 3614 * @locus Client 3615 * @param {String} name Name of the subscription. Matches the name of the 3616 * server's `publish()` call. 3617 * @param {Any} [arg1,arg2...] Optional arguments passed to publisher function 3618 * on server. 3619 * @param {Function|Object} [options] If a function is passed instead of an 3620 * object, it is interpreted as an `onReady` callback. 3621 * @param {Function} [options.onReady] Passed to [`Meteor.subscribe`](https://docs.meteor.com/api/pubsub.html#Meteor-subscribe). 3622 * @param {Function} [options.onStop] Passed to [`Meteor.subscribe`](https://docs.meteor.com/api/pubsub.html#Meteor-subscribe). 3623 * @param {DDP.Connection} [options.connection] The connection on which to make the 3624 * subscription. 3625 */ 3626Blaze.TemplateInstance.prototype.subscribe = function (/* arguments */) { 3627 var self = this; 3628 3629 var subHandles = self._subscriptionHandles; 3630 var args = _.toArray(arguments); 3631 3632 // Duplicate logic from Meteor.subscribe 3633 var options = {}; 3634 if (args.length) { 3635 var lastParam = _.last(args); 3636 3637 // Match pattern to check if the last arg is an options argument 3638 var lastParamOptionsPattern = { 3639 onReady: Match.Optional(Function), 3640 // XXX COMPAT WITH 1.0.3.1 onError used to exist, but now we use 3641 // onStop with an error callback instead. 3642 onError: Match.Optional(Function), 3643 onStop: Match.Optional(Function), 3644 connection: Match.Optional(Match.Any) 3645 }; 3646 3647 if (_.isFunction(lastParam)) { 3648 options.onReady = args.pop(); 3649 } else if (lastParam && ! _.isEmpty(lastParam) && Match.test(lastParam, lastParamOptionsPattern)) { 3650 options = args.pop(); 3651 } 3652 } 3653 3654 var subHandle; 3655 var oldStopped = options.onStop; 3656 options.onStop = function (error) { 3657 // When the subscription is stopped, remove it from the set of tracked 3658 // subscriptions to avoid this list growing without bound 3659 delete subHandles[subHandle.subscriptionId]; 3660 3661 // Removing a subscription can only change the result of subscriptionsReady 3662 // if we are not ready (that subscription could be the one blocking us being 3663 // ready).
3664 if (! self._allSubsReady) { 3665 self._allSubsReadyDep.changed(); 3666 } 3667 3668 if (oldStopped) { 3669 oldStopped(error); 3670 } 3671 }; 3672 3673 var connection = options.connection; 3674 var callbacks = _.pick(options, ["onReady", "onError", "onStop"]); 3675 3676 // The callbacks are passed as the last item in the arguments array passed to 3677 // View#subscribe 3678 args.push(callbacks); 3679 3680 // View#subscribe takes the connection as one of the options in the last 3681 // argument 3682 subHandle = self.view.subscribe.call(self.view, args, { 3683 connection: connection 3684 }); 3685 3686 if (! _.has(subHandles, subHandle.subscriptionId)) { 3687 subHandles[subHandle.subscriptionId] = subHandle; 3688 3689 // Adding a new subscription will always cause us to transition from ready 3690 // to not ready, but if we are already not ready then this can't make us 3691 // ready. 3692 if (self._allSubsReady) { 3693 self._allSubsReadyDep.changed(); 3694 } 3695 } 3696 3697 return subHandle; 3698}; 3699 3700/** 3701 * @summary A reactive function that returns true when all of the subscriptions 3702 * called with [this.subscribe](#TemplateInstance-subscribe) are ready. 3703 * @return {Boolean} True if all subscriptions on this template instance are 3704 * ready. 3705 */ 3706Blaze.TemplateInstance.prototype.subscriptionsReady = function () { 3707 this._allSubsReadyDep.depend(); 3708 3709 this._allSubsReady = _.all(this._subscriptionHandles, function (handle) { 3710 return handle.ready(); 3711 }); 3712 3713 return this._allSubsReady; 3714}; 3715 3716/** 3717 * @summary Specify template helpers available to this template. 3718 * @locus Client 3719 * @param {Object} helpers Dictionary of helper functions by name. 3720 * @importFromPackage templating 3721 */ 3722Template.prototype.helpers = function (dict) { 3723 if (! _.isObject(dict)) { 3724 throw new Error("Helpers dictionary has to be an object"); 3725 } 3726 3727 for (var k in dict) 3728 this.__helpers.set(k, dict[k]); 3729}; 3730 3731var canUseGetters = function() { 3732 if (Object.defineProperty) { 3733 var obj = {}; 3734 try { 3735 Object.defineProperty(obj, "self", { 3736 get: function () { return obj; } 3737 }); 3738 } catch (e) { 3739 return false; 3740 } 3741 return obj.self === obj; 3742 } 3743 return false; 3744}(); 3745 3746if (canUseGetters) { 3747 // Like Blaze.currentView but for the template instance. A function 3748 // rather than a value so that not all helpers are implicitly dependent 3749 // on the current template instance's `data` property, which would make 3750 // them dependent on the data context of the template inclusion.
3751 var currentTemplateInstanceFunc = null; 3752 3753 // If getters are supported, define this property with a getter function 3754 // to make it effectively read-only, and to work around this bizarre JSC 3755 // bug: https://github.com/meteor/meteor/issues/9926 3756 Object.defineProperty(Template, "_currentTemplateInstanceFunc", { 3757 get: function () { 3758 return currentTemplateInstanceFunc; 3759 } 3760 }); 3761 3762 Template._withTemplateInstanceFunc = function (templateInstanceFunc, func) { 3763 if (typeof func !== 'function') { 3764 throw new Error("Expected function, got: " + func); 3765 } 3766 var oldTmplInstanceFunc = currentTemplateInstanceFunc; 3767 try { 3768 currentTemplateInstanceFunc = templateInstanceFunc; 3769 return func(); 3770 } finally { 3771 currentTemplateInstanceFunc = oldTmplInstanceFunc; 3772 } 3773 }; 3774 3775} else { 3776 // If getters are not supported, just use a normal property. 3777 Template._currentTemplateInstanceFunc = null; 3778 3779 Template._withTemplateInstanceFunc = function (templateInstanceFunc, func) { 3780 if (typeof func !== 'function') { 3781 throw new Error("Expected function, got: " + func); 3782 }
3783 var oldTmplInstanceFunc = Template._currentTemplateInstanceFunc; 3784 try { 3785 Template._currentTemplateInstanceFunc = templateInstanceFunc; 3786 return func(); 3787 } finally { 3788 Template._currentTemplateInstanceFunc = oldTmplInstanceFunc; 3789 } 3790 }; 3791} 3792 3793/** 3794 * @summary Specify event handlers for this template. 3795 * @locus Client 3796 * @param {EventMap} eventMap Event handlers to associate with this template. 3797 * @importFromPackage templating 3798 */ 3799Template.prototype.events = function (eventMap) { 3800 if (! _.isObject(eventMap)) { 3801 throw new Error("Event map has to be an object"); 3802 } 3803 3804 var template = this; 3805 var eventMap2 = {}; 3806 for (var k in eventMap) { 3807 eventMap2[k] = (function (k, v) { 3808 return function (event/*, ...*/) { 3809 var view = this; // passed by EventAugmenter 3810 var data = Blaze.getData(event.currentTarget); 3811 if (data == null) 3812 data = {}; 3813 var args = Array.prototype.slice.call(arguments); 3814 var tmplInstanceFunc = Blaze._bind(view.templateInstance, view); 3815 args.splice(1, 0, tmplInstanceFunc()); 3816 3817 return Template._withTemplateInstanceFunc(tmplInstanceFunc, function () { 3818 return v.apply(data, args); 3819 }); 3820 }; 3821 })(k, eventMap[k]); 3822 } 3823 3824 template.__eventMaps.push(eventMap2); 3825}; 3826 3827/** 3828 * @function 3829 * @name instance 3830 * @memberOf Template
3831 * @summary The [template instance](#Template-instances) corresponding to the current template helper, event handler, callback, or autorun. If there isn't one, `null`. 3832 * @locus Client 3833 * @returns {Blaze.TemplateInstance} 3834 * @importFromPackage templating 3835 */ 3836Template.instance = function () { 3837 return Template._currentTemplateInstanceFunc 3838 && Template._currentTemplateInstanceFunc(); 3839}; 3840 3841// Note: Template.currentData() is documented to take zero arguments, 3842// while Blaze.getData takes up to one. 3843 3844/** 3845 * @summary 3846 * 3847 * - Inside an `onCreated`, `onRendered`, or `onDestroyed` callback, returns 3848 * the data context of the template. 3849 * - Inside an event handler, returns the data context of the template on which 3850 * this event handler was defined. 3851 * - Inside a helper, returns the data context of the DOM node where the helper 3852 * was used. 3853 * 3854 * Establishes a reactive dependency on the result. 3855 * @locus Client 3856 * @function 3857 * @importFromPackage templating 3858 */ 3859Template.currentData = Blaze.getData; 3860 3861/** 3862 * @summary Accesses other data contexts that enclose the current data context. 3863 * @locus Client 3864 * @function 3865 * @param {Integer} [numLevels] The number of levels beyond the current data context to look. Defaults to 1. 3866 * @importFromPackage templating 3867 */ 3868Template.parentData = Blaze._parentData; 3869 3870/** 3871 * @summary Defines a [helper function](#Template-helpers) which can be used from all templates. 3872 * @locus Client 3873 * @function 3874 * @param {String} name The name of the helper function you are defining. 3875 * @param {Function} function The helper function itself. 3876 * @importFromPackage templating 3877 */ 3878Template.registerHelper = Blaze.registerHelper; 3879 3880/** 3881 * @summary Removes a global [helper function](#Template-helpers). 3882 * @locus Client 3883 * @function 3884 * @param {String} name The name of the helper function you are defining. 3885 * @importFromPackage templating 3886 */ 3887Template.deregisterHelper = Blaze.deregisterHelper; 3888 3889///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 3890 3891}).call(this); 3892 3893 3894 3895 3896 3897 3898(function(){ 3899 3900///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 3901// // 3902// packages/blaze/backcompat.js // 3903// // 3904///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 3905 // 3906UI = Blaze; 3907 3908Blaze.ReactiveVar = ReactiveVar; 3909UI._templateInstance = Blaze.Template.instance; 3910 3911Handlebars = {}; 3912Handlebars.registerHelper = Blaze.registerHelper; 3913 3914Handlebars._escape = Blaze._escape; 3915 3916// Return these from {{...}} helpers to achieve the same as returning 3917// strings from {{{...}}} helpers 3918Handlebars.SafeString = function(string) { 3919 this.string = string; 3920}; 3921Handlebars.SafeString.prototype.toString = function() { 3922 return this.string.toString(); 3923}; 3924 3925///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 3926 3927}).call(this); 3928 3929 3930/* Exports */ 3931Package._define("blaze", { 3932 Blaze: Blaze, 3933 UI: UI, 3934 Handlebars: Handlebars 3935}); 3936 3937})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.