1/* 2 Ractive.js v0.7.3 3 Sat Apr 25 2015 13:52:38 GMT-0400 (EDT) - commit da40f81c660ba2f09c45a09a9c20fdd34ee36d80 4 5 http://ractivejs.org 6 http://twitter.com/RactiveJS 7 8 Released under the MIT License. 9*/ 10 11(function (global, factory) { 12 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 13 typeof define === 'function' && define.amd ? define(factory) : 14 global.Ractive = factory() 15}(this, function () { 'use strict'; 16 17 var TEMPLATE_VERSION = 3; 18 19 var defaultOptions = { 20 21 // render placement: 22 el: void 0, 23 append: false, 24 25 // template: 26 template: { v: TEMPLATE_VERSION, t: [] }, 27 28 // parse: // TODO static delimiters? 29 preserveWhitespace: false, 30 sanitize: false, 31 stripComments: true, 32 delimiters: ["{{", "}}"], 33 tripleDelimiters: ["{{{", "}}}"], 34 interpolate: false, 35 36 // data & binding: 37 data: {}, 38 computed: {}, 39 magic: false, 40 modifyArrays: true, 41 adapt: [], 42 isolated: false, 43 twoway: true, 44 lazy: false, 45 46 // transitions: 47 noIntro: false, 48 transitionsEnabled: true, 49 complete: void 0, 50 51 // css: 52 css: null, 53 noCssTransform: false 54 }; 55 56 var config_defaults = defaultOptions; 57 58 // These are a subset of the easing equations found at 59 // https://raw.github.com/danro/easing-js - license info 60 // follows: 61 62 // -------------------------------------------------- 63 // easing.js v0.5.4 64 // Generic set of easing functions with AMD support 65 // https://github.com/danro/easing-js 66 // This code may be freely distributed under the MIT license 67 // http://danro.mit-license.org/ 68 // -------------------------------------------------- 69 // All functions adapted from Thomas Fuchs & Jeremy Kahn 70 // Easing Equations (c) 2003 Robert Penner, BSD license 71 // https://raw.github.com/danro/easing-js/master/LICENSE 72 // -------------------------------------------------- 73 74 // In that library, the functions named easeIn, easeOut, and 75 // easeInOut below are named easeInCubic, easeOutCubic, and 76 // (you guessed it) easeInOutCubic. 77 // 78 // You can add additional easing functions to this list, and they 79 // will be globally available. 80 81 var static_easing = { 82 linear: function (pos) { 83 return pos; 84 }, 85 easeIn: function (pos) { 86 return Math.pow(pos, 3); 87 }, 88 easeOut: function (pos) { 89 return Math.pow(pos - 1, 3) + 1; 90 }, 91 easeInOut: function (pos) { 92 if ((pos /= 0.5) < 1) { 93 return 0.5 * Math.pow(pos, 3); 94 } 95 return 0.5 * (Math.pow(pos - 2, 3) + 2); 96 } 97 }; 98 99 /*global console, navigator */ 100 var isClient, isJsdom, hasConsole, environment__magic, namespaces, svg, vendors; 101 102 isClient = typeof document === "object"; 103 104 isJsdom = typeof navigator !== "undefined" && /jsDom/.test(navigator.appName); 105 106 hasConsole = typeof console !== "undefined" && typeof console.warn === "function" && typeof console.warn.apply === "function"; 107 108 try { 109 Object.defineProperty({}, "test", { value: 0 }); 110 environment__magic = true; 111 } catch (e) { 112 environment__magic = false; 113 } 114 115 namespaces = { 116 html: "http://www.w3.org/1999/xhtml", 117 mathml: "http://www.w3.org/1998/Math/MathML", 118 svg: "http://www.w3.org/2000/svg", 119 xlink: "http://www.w3.org/1999/xlink", 120 xml: "http://www.w3.org/XML/1998/namespace", 121 xmlns: "http://www.w3.org/2000/xmlns/" 122 }; 123 124 if (typeof document === "undefined") { 125 svg = false; 126 } else { 127 svg = document && document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"); 128 } 129 130 vendors = ["o", "ms", "moz", "webkit"]; 131 132 var createElement, matches, dom__div, methodNames, unprefixed, prefixed, dom__i, j, makeFunction; 133 134 // Test for SVG support 135 if (!svg) { 136 createElement = function (type, ns) { 137 if (ns && ns !== namespaces.html) { 138 throw "This browser does not support namespaces other than http://www.w3.org/1999/xhtml. The most likely cause of this error is that you're trying to render SVG in an older browser. See http://docs.ractivejs.org/latest/svg-and-older-browsers for more information"; 139 } 140 141 return document.createElement(type); 142 }; 143 } else { 144 createElement = function (type, ns) { 145 if (!ns || ns === namespaces.html) { 146 return document.createElement(type); 147 } 148 149 return document.createElementNS(ns, type); 150 }; 151 } 152 153 function getElement(input) { 154 var output; 155 156 if (!input || typeof input === "boolean") { 157 return; 158 } 159 160 if (typeof window === "undefined" || !document || !input) { 161 return null; 162 } 163 164 // We already have a DOM node - no work to do. (Duck typing alert!) 165 if (input.nodeType) { 166 return input; 167 } 168 169 // Get node from string 170 if (typeof input === "string") { 171 // try ID first 172 output = document.getElementById(input); 173 174 // then as selector, if possible 175 if (!output && document.querySelector) { 176 output = document.querySelector(input); 177 } 178 179 // did it work? 180 if (output && output.nodeType) { 181 return output; 182 } 183 } 184 185 // If we've been given a collection (jQuery, Zepto etc), extract the first item 186 if (input[0] && input[0].nodeType) { 187 return input[0]; 188 } 189 190 return null; 191 } 192 193 if (!isClient) { 194 matches = null; 195 } else { 196 dom__div = createElement("div"); 197 methodNames = ["matches", "matchesSelector"]; 198 199 makeFunction = function (methodName) { 200 return function (node, selector) { 201 return node[methodName](selector); 202 }; 203 }; 204 205 dom__i = methodNames.length; 206 207 while (dom__i-- && !matches) { 208 unprefixed = methodNames[dom__i]; 209 210 if (dom__div[unprefixed]) { 211 matches = makeFunction(unprefixed); 212 } else {
213 j = vendors.length; 214 while (j--) { 215 prefixed = vendors[dom__i] + unprefixed.substr(0, 1).toUpperCase() + unprefixed.substring(1); 216 217 if (dom__div[prefixed]) { 218 matches = makeFunction(prefixed); 219 break; 220 } 221 } 222 } 223 } 224 225 // IE8... 226 if (!matches) { 227 matches = function (node, selector) { 228 var nodes, parentNode, i; 229 230 parentNode = node.parentNode; 231 232 if (!parentNode) { 233 // empty dummy <div> 234 dom__div.innerHTML = ""; 235 236 parentNode = dom__div; 237 node = node.cloneNode(); 238 239 dom__div.appendChild(node); 240 } 241 242 nodes = parentNode.querySelectorAll(selector); 243 244 i = nodes.length; 245 while (i--) { 246 if (nodes[i] === node) { 247 return true; 248 } 249 } 250 251 return false; 252 }; 253 } 254 } 255 256 function detachNode(node) { 257 if (node && typeof node.parentNode !== "unknown" && node.parentNode) { 258 node.parentNode.removeChild(node); 259 } 260 261 return node; 262 } 263 264 function safeToStringValue(value) { 265 return value == null || !value.toString ? "" : value; 266 } 267 268 var legacy = null; 269 270 var create, defineProperty, defineProperties; 271 272 try { 273 Object.defineProperty({}, "test", { value: 0 }); 274 275 if (isClient) { 276 Object.defineProperty(document.createElement("div"), "test", { value: 0 }); 277 } 278 279 defineProperty = Object.defineProperty; 280 } catch (err) { 281 // Object.defineProperty doesn't exist, or we're in IE8 where you can 282 // only use it with DOM objects (what were you smoking, MSFT?) 283 defineProperty = function (obj, prop, desc) { 284 obj[prop] = desc.value; 285 }; 286 } 287 288 try { 289 try { 290 Object.defineProperties({}, { test: { value: 0 } }); 291 } catch (err) { 292 // TODO how do we account for this? noMagic = true; 293 throw err; 294 } 295 296 if (isClient) { 297 Object.defineProperties(createElement("div"), { test: { value: 0 } }); 298 } 299 300 defineProperties = Object.defineProperties; 301 } catch (err) { 302 defineProperties = function (obj, props) { 303 var prop; 304 305 for (prop in props) { 306 if (props.hasOwnProperty(prop)) { 307 defineProperty(obj, prop, props[prop]); 308 } 309 } 310 }; 311 } 312 313 try { 314 Object.create(null); 315 316 create = Object.create; 317 } catch (err) { 318 // sigh 319 create = (function () { 320 var F = function () {}; 321 322 return function (proto, props) { 323 var obj; 324 325 if (proto === null) { 326 return {}; 327 } 328 329 F.prototype = proto; 330 obj = new F(); 331 332 if (props) { 333 Object.defineProperties(obj, props); 334 } 335 336 return obj; 337 }; 338 })(); 339 } 340 341 function utils_object__extend(target) { 342 for (var _len = arguments.length, sources = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 343 sources[_key - 1] = arguments[_key]; 344 } 345 346 var prop, source; 347 348 while (source = sources.shift()) { 349 for (prop in source) { 350 if (hasOwn.call(source, prop)) { 351 target[prop] = source[prop]; 352 } 353 } 354 } 355 356 return target; 357 } 358 359 function fillGaps(target) { 360 for (var _len = arguments.length, sources = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 361 sources[_key - 1] = arguments[_key]; 362 } 363 364 sources.forEach(function (s) { 365 for (var key in s) { 366 if (s.hasOwnProperty(key) && !(key in target)) { 367 target[key] = s[key]; 368 } 369 } 370 }); 371 372 return target; 373 } 374 375 var hasOwn = Object.prototype.hasOwnProperty; 376 377 // thanks, http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/ 378 var is__toString = Object.prototype.toString, 379 arrayLikePattern = /^\[object (?:Array|FileList)\]$/; 380 function isArray(thing) { 381 return is__toString.call(thing) === "[object Array]"; 382 } 383 384 function isArrayLike(obj) { 385 return arrayLikePattern.test(is__toString.call(obj)); 386 } 387 388 function isEqual(a, b) { 389 if (a === null && b === null) { 390 return true; 391 } 392 393 if (typeof a === "object" || typeof b === "object") { 394 return false; 395 } 396 397 return a === b; 398 } 399 400 function is__isNumeric(thing) { 401 return !isNaN(parseFloat(thing)) && isFinite(thing); 402 } 403 404 function isObject(thing) { 405 return thing && is__toString.call(thing) === "[object Object]"; 406 } 407 408 var noop = function () {}; 409 410 /* global console */ 411 var alreadyWarned = {}, 412 log, 413 printWarning, 414 welcome; 415 416 if (hasConsole) { 417 (function () { 418 var welcomeIntro = ["%cRactive.js %c0.7.3 %cin debug mode, %cmore...", "color: rgb(114, 157, 52); font-weight: normal;", "color: rgb(85, 85, 85); font-weight: normal;", "color: rgb(85, 85, 85); font-weight: normal;", "color: rgb(82, 140, 224); font-weight: normal; text-decoration: underline;"]; 419 var welcomeMessage = "You're running Ractive 0.7.3 in debug mode - messages will be printed to the console to help you fix problems and optimise your application.\n\nTo disable debug mode, add this line at the start of your app:\n Ractive.DEBUG = false;
419\n\nTo disable debug mode when your app is minified, add this snippet:\n Ractive.DEBUG = /unminified/.test(function(){/*unminified*/});\n\nGet help and support:\n http://docs.ractivejs.org\n http://stackoverflow.com/questions/tagged/ractivejs\n http://groups.google.com/forum/#!forum/ractive-js\n http://twitter.com/ractivejs\n\nFound a bug? Raise an issue:\n https://github.com/ractivejs/ractive/issues\n\n"; 420 421 welcome = function () { 422 var hasGroup = !!console.groupCollapsed; 423 console[hasGroup ? "groupCollapsed" : "log"].apply(console, welcomeIntro); 424 console.log(welcomeMessage); 425 if (hasGroup) { 426 console.groupEnd(welcomeIntro); 427 } 428 429 welcome = noop; 430 }; 431 432 printWarning = function (message, args) { 433 welcome(); 434 435 // extract information about the instance this message pertains to, if applicable 436 if (typeof args[args.length - 1] === "object") { 437 var options = args.pop(); 438 var ractive = options ? options.ractive : null; 439 440 if (ractive) { 441 // if this is an instance of a component that we know the name of, add 442 // it to the message 443 var _name = undefined; 444 if (ractive.component && (_name = ractive.component.name)) { 445 message = "<" + _name + "> " + message; 446 } 447 448 var node = undefined; 449 if (node = options.node || ractive.fragment && ractive.fragment.rendered && ractive.find("*")) { 450 args.push(node); 451 } 452 } 453 } 454 455 console.warn.apply(console, ["%cRactive.js: %c" + message, "color: rgb(114, 157, 52);", "color: rgb(85, 85, 85);"].concat(args)); 456 }; 457 458 log = function () { 459 console.log.apply(console, arguments); 460 }; 461 })(); 462 } else { 463 printWarning = log = welcome = noop; 464 } 465 466 function format(message, args) { 467 return message.replace(/%s/g, function () { 468 return args.shift(); 469 }); 470 } 471 472 function fatal(message) { 473 for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 474 args[_key - 1] = arguments[_key]; 475 } 476 477 message = format(message, args); 478 throw new Error(message); 479 } 480 481 function logIfDebug() { 482 if (_Ractive.DEBUG) { 483 log.apply(null, arguments); 484 } 485 } 486 487 function warn(message) { 488 for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 489 args[_key - 1] = arguments[_key]; 490 } 491 492 message = format(message, args); 493 printWarning(message, args); 494 } 495 496 function warnOnce(message) { 497 for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 498 args[_key - 1] = arguments[_key]; 499 } 500 501 message = format(message, args); 502 503 if (alreadyWarned[message]) { 504 return; 505 } 506 507 alreadyWarned[message] = true; 508 printWarning(message, args); 509 } 510 511 function warnIfDebug() { 512 if (_Ractive.DEBUG) { 513 warn.apply(null, arguments); 514 } 515 } 516 517 function warnOnceIfDebug() { 518 if (_Ractive.DEBUG) { 519 warnOnce.apply(null, arguments); 520 } 521 } 522 523 // Error messages that are used (or could be) in multiple places 524 var badArguments = "Bad arguments"; 525 var noRegistryFunctionReturn = "A function was specified for \"%s\" %s, but no %s was returned"; 526 var missingPlugin = function (name, type) { 527 return "Missing \"" + name + "\" " + type + " plugin. You may need to download a plugin via http://docs.ractivejs.org/latest/plugins#" + type + "s"; 528 }; 529 530 function findInViewHierarchy(registryName, ractive, name) { 531 var instance = findInstance(registryName, ractive, name); 532 return instance ? instance[registryName][name] : null; 533 } 534 535 function findInstance(registryName, ractive, name) { 536 while (ractive) { 537 if (name in ractive[registryName]) { 538 return ractive; 539 } 540 541 if (ractive.isolated) { 542 return null; 543 } 544 545 ractive = ractive.parent; 546 } 547 } 548 549 var interpolate = function (from, to, ractive, type) { 550 if (from === to) { 551 return snap(to); 552 } 553 554 if (type) { 555 556 var interpol = findInViewHierarchy("interpolators", ractive, type); 557 if (interpol) { 558 return interpol(from, to) || snap(to); 559 } 560 561 fatal(missingPlugin(type, "interpolator")); 562 } 563 564 return static_interpolators.number(from, to) || static_interpolators.array(from, to) || static_interpolators.object(from, to) || snap(to); 565 }; 566
567 var shared_interpolate = interpolate; 568 569 function snap(to) { 570 return function () { 571 return to; 572 }; 573 } 574 575 var interpolators = { 576 number: function (from, to) { 577 var delta; 578 579 if (!is__isNumeric(from) || !is__isNumeric(to)) { 580 return null; 581 } 582 583 from = +from; 584 to = +to; 585 586 delta = to - from; 587 588 if (!delta) { 589 return function () { 590 return from; 591 }; 592 } 593 594 return function (t) { 595 return from + t * delta; 596 }; 597 }, 598 599 array: function (from, to) { 600 var intermediate, interpolators, len, i; 601 602 if (!isArray(from) || !isArray(to)) { 603 return null; 604 } 605 606 intermediate = []; 607 interpolators = []; 608 609 i = len = Math.min(from.length, to.length); 610 while (i--) { 611 interpolators[i] = shared_interpolate(from[i], to[i]); 612 } 613 614 // surplus values - don't interpolate, but don't exclude them either 615 for (i = len; i < from.length; i += 1) { 616 intermediate[i] = from[i]; 617 } 618 619 for (i = len; i < to.length; i += 1) { 620 intermediate[i] = to[i]; 621 } 622 623 return function (t) { 624 var i = len; 625 626 while (i--) { 627 intermediate[i] = interpolators[i](t); 628 } 629 630 return intermediate; 631 }; 632 }, 633 634 object: function (from, to) { 635 var properties, len, interpolators, intermediate, prop; 636 637 if (!isObject(from) || !isObject(to)) { 638 return null; 639 } 640 641 properties = []; 642 intermediate = {}; 643 interpolators = {}; 644 645 for (prop in from) { 646 if (hasOwn.call(from, prop)) { 647 if (hasOwn.call(to, prop)) { 648 properties.push(prop); 649 interpolators[prop] = shared_interpolate(from[prop], to[prop]); 650 } else { 651 intermediate[prop] = from[prop]; 652 } 653 } 654 } 655 656 for (prop in to) { 657 if (hasOwn.call(to, prop) && !hasOwn.call(from, prop)) { 658 intermediate[prop] = to[prop]; 659 } 660 } 661 662 len = properties.length; 663 664 return function (t) { 665 var i = len, 666 prop; 667 668 while (i--) { 669 prop = properties[i]; 670 671 intermediate[prop] = interpolators[prop](t); 672 } 673 674 return intermediate; 675 }; 676 } 677 }; 678 679 var static_interpolators = interpolators; 680 681 // This function takes a keypath such as 'foo.bar.baz', and returns 682 // all the variants of that keypath that include a wildcard in place 683 // of a key, such as 'foo.bar.*', 'foo.*.baz', 'foo.*.*' and so on. 684 // These are then checked against the dependants map (ractive.viewmodel.depsMap) 685 // to see if any pattern observers are downstream of one or more of 686 // these wildcard keypaths (e.g. 'foo.bar.*.status') 687 var utils_getPotentialWildcardMatches = getPotentialWildcardMatches; 688 689 var starMaps = {}; 690 function getPotentialWildcardMatches(keypath) { 691 var keys, starMap, mapper, i, result, wildcardKeypath; 692 693 keys = keypath.split("."); 694 if (!(starMap = starMaps[keys.length])) { 695 starMap = getStarMap(keys.length); 696 } 697 698 result = []; 699 700 mapper = function (star, i) { 701 return star ? "*" : keys[i]; 702 }; 703 704 i = starMap.length; 705 while (i--) { 706 wildcardKeypath = starMap[i].map(mapper).join("."); 707 708 if (!result.hasOwnProperty(wildcardKeypath)) { 709 result.push(wildcardKeypath); 710 result[wildcardKeypath] = true; 711 } 712 } 713 714 return result; 715 } 716 717 // This function returns all the possible true/false combinations for 718 // a given number - e.g. for two, the possible combinations are 719 // [ true, true ], [ true, false ], [ false, true ], [ false, false ]. 720 // It does so by getting all the binary values between 0 and e.g. 11 721 function getStarMap(num) { 722 var ones = "", 723 max, 724 binary, 725 starMap, 726 mapper, 727 i, 728 j, 729 l, 730 map; 731 732 if (!starMaps[num]) { 733 starMap = []; 734 735 while (ones.length < num) { 736 ones += 1; 737 } 738 739 max = parseInt(ones, 2); 740 741 mapper = function (digit) { 742 return digit === "1"; 743 }; 744 745 for (i = 0; i <= max; i += 1) { 746 binary = i.toString(2); 747 while (binary.length < num) { 748 binary = "0" + binary; 749 } 750 751 map = []; 752 l = binary.length; 753 for (j = 0; j < l; j++) { 754 map.push(mapper(binary[j])); 755 } 756 starMap[i] = map; 757 } 758 759 starMaps[num] = starMap; 760 } 761 762 return starMaps[num]; 763 } 764 765 var refPattern = /\[\s*(\*|[0-9]|[1-9][0-9]+)\s*\]/g; 766 var patternPattern = /\*/; 767 var keypathCache = {}; 768 769 var Keypath = function (str) { 770 var keys = str.split("."); 771 772 this.str = str; 773 774 if (str[0] === "@") { 775 this.isSpecial = true; 776 this.value = decodeKeypath(str); 777 } 778 779 this.firstKey = keys[0]; 780 this.lastKey = keys.pop(); 781 782 this.isPattern = patternPattern.test(str); 783 784 this.parent = str === "" ? null : getKeypath(keys.join(".")); 785 this.isRoot = !str; 786 }; 787 788 Keypath.prototype = { 789 equalsOrStartsWith: function (keypath) { 790 return keypath === this || this.startsWith(keypath); 791 }, 792 793 join: function (str) { 794 return getKeypath(this.isRoot ? String(str) : this.str + "." + str); 795 }, 796 797 replace: function (oldKeypath, newKeypath) { 798 if (this === oldKeypath) { 799 return newKeypath; 800 } 801 802 if (this.startsWith(oldKeypath)) { 803 return newKeypath === null ? newKeypath : getKeypath(this.str.replace(oldKeypath.str + ".", newKeypath.str + ".")); 804 } 805 }, 806 807 startsWith: function (keypath) { 808 if (!keypath) { 809 // TODO under what circumstances does this happen? 810 return false;
811 } 812 813 return keypath && this.str.substr(0, keypath.str.length + 1) === keypath.str + "."; 814 }, 815 816 toString: function () { 817 throw new Error("Bad coercion"); 818 }, 819 820 valueOf: function () { 821 throw new Error("Bad coercion"); 822 }, 823 824 wildcardMatches: function () { 825 return this._wildcardMatches || (this._wildcardMatches = utils_getPotentialWildcardMatches(this.str)); 826 } 827 }; 828 function assignNewKeypath(target, property, oldKeypath, newKeypath) { 829 var existingKeypath = target[property]; 830 831 if (existingKeypath && (existingKeypath.equalsOrStartsWith(newKeypath) || !existingKeypath.equalsOrStartsWith(oldKeypath))) { 832 return; 833 } 834 835 target[property] = existingKeypath ? existingKeypath.replace(oldKeypath, newKeypath) : newKeypath; 836 return true; 837 } 838 839 function decodeKeypath(keypath) { 840 var value = keypath.slice(2); 841 842 if (keypath[1] === "i") { 843 return is__isNumeric(value) ? +value : value; 844 } else { 845 return value; 846 } 847 } 848 849 function getKeypath(str) { 850 if (str == null) { 851 return str; 852 } 853 854 // TODO it *may* be worth having two versions of this function - one where 855 // keypathCache inherits from null, and one for IE8. Depends on how 856 // much of an overhead hasOwnProperty is - probably negligible 857 if (!keypathCache.hasOwnProperty(str)) { 858 keypathCache[str] = new Keypath(str); 859 } 860 861 return keypathCache[str]; 862 } 863 864 function getMatchingKeypaths(ractive, keypath) { 865 var keys, key, matchingKeypaths; 866 867 keys = keypath.str.split("."); 868 matchingKeypaths = [rootKeypath]; 869 870 while (key = keys.shift()) { 871 if (key === "*") { 872 // expand to find all valid child keypaths 873 matchingKeypaths = matchingKeypaths.reduce(expand, []); 874 } else { 875 if (matchingKeypaths[0] === rootKeypath) { 876 // first key 877 matchingKeypaths[0] = getKeypath(key); 878 } else { 879 matchingKeypaths = matchingKeypaths.map(concatenate(key)); 880 } 881 } 882 } 883 884 return matchingKeypaths; 885 886 function expand(matchingKeypaths, keypath) { 887 var wrapper, value, keys; 888 889 if (keypath.isRoot) { 890 keys = [].concat(Object.keys(ractive.viewmodel.data), Object.keys(ractive.viewmodel.mappings), Object.keys(ractive.viewmodel.computations)); 891 } else { 892 wrapper = ractive.viewmodel.wrapped[keypath.str]; 893 value = wrapper ? wrapper.get() : ractive.viewmodel.get(keypath); 894 895 keys = value ? Object.keys(value) : null; 896 } 897 898 if (keys) { 899 keys.forEach(function (key) { 900 if (key !== "_ractive" || !isArray(value)) { 901 matchingKeypaths.push(keypath.join(key)); 902 } 903 }); 904 } 905 906 return matchingKeypaths; 907 } 908 } 909 910 function concatenate(key) { 911 return function (keypath) { 912 return keypath.join(key); 913 }; 914 } 915 function normalise(ref) { 916 return ref ? ref.replace(refPattern, ".$1") : ""; 917 } 918 919 var rootKeypath = getKeypath(""); 920 921 var shared_add = add; 922 var shared_add__errorMessage = "Cannot add to a non-numeric value"; 923 function add(root, keypath, d) { 924 if (typeof keypath !== "string" || !is__isNumeric(d)) { 925 throw new Error("Bad arguments"); 926 } 927 928 var value = undefined, 929 changes = undefined; 930 931 if (/\*/.test(keypath)) { 932 changes = {}; 933 934 getMatchingKeypaths(root, getKeypath(normalise(keypath))).forEach(function (keypath) { 935 var value = root.viewmodel.get(keypath); 936 937 if (!is__isNumeric(value)) { 938 throw new Error(shared_add__errorMessage); 939 } 940 941 changes[keypath.str] = value + d; 942 }); 943 944 return root.set(changes); 945 } 946 947 value = root.get(keypath); 948 949 if (!is__isNumeric(value)) { 950 throw new Error(shared_add__errorMessage); 951 } 952 953 return root.set(keypath, +value + d); 954 } 955 956 var prototype_add = Ractive$add; 957 function Ractive$add(keypath, d) { 958 return shared_add(this, keypath, d === undefined ? 1 : +d); 959 } 960 961 var requestAnimationFrame; 962 963 // If window doesn't exist, we don't need requestAnimationFrame 964 if (typeof window === "undefined") { 965 requestAnimationFrame = null; 966 } else { 967 // https://gist.github.com/paulirish/1579671 968 (function (vendors, lastTime, window) { 969 970 var x, setTimeout; 971 972 if (window.requestAnimationFrame) { 973 return; 974 } 975 976 for (x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 977 window.requestAnimationFrame = window[vendors[x] + "RequestAnimationFrame"]; 978 } 979 980 if (!window.requestAnimationFrame) { 981 setTimeout = window.setTimeout; 982 983 window.requestAnimationFrame = function (callback) { 984 var currTime, timeToCall, id; 985 986 currTime = Date.now(); 987 timeToCall = Math.max(0, 16 - (currTime - lastTime)); 988 id = setTimeout(function () { 989 callback(currTime + timeToCall); 990 }, timeToCall); 991 992 lastTime = currTime + timeToCall; 993 return id; 994 }; 995 } 996 })(vendors, 0, window); 997 998 requestAnimationFrame = window.requestAnimationFrame; 999 } 1000 1001 var rAF = requestAnimationFrame; 1002 1003 var getTime; 1004 1005 if (typeof window !== "undefined" && window.performance && typeof window.performance.now === "function") { 1006 getTime = function () { 1007 return window.performance.now(); 1008 }; 1009 } else { 1010 getTime = function () { 1011 return Date.now(); 1012 }; 1013 } 1014 1015 var utils_getTime = getTime; 1016 1017 var deprecations = { 1018 construct: { 1019 deprecated: "beforeInit", 1020 replacement: "onconstruct" 1021 }, 1022 render: { 1023 deprecated: "init", 1024 message: "The \"init\" method has been deprecated " + "and will likely be removed in a future release. " + "You can either use the \"oninit\" method which will fire " + "only once prior to, and regardless of, any eventual ractive " + "instance being rendered, or if you need to access the " + "rendered DOM, use \"onrender\" instead. " + "See http://docs.ractivejs.org/latest/migrating for more information." 1025 }, 1026 complete: { 1027 deprecated: "complete", 1028 replacement: "oncomplete" 1029 } 1030 }; 1031 1032 function Hook(event) { 1033 this.event = event; 1034 this.method = "on" + event; 1035 this.deprecate = deprecations[event]; 1036 } 1037 1038 Hook.prototype.fire = function (ractive, arg) { 1039 function call(method) { 1040 if (ractive[method]) { 1041 arg ? ractive[method](arg) : ractive[method](); 1042 return true; 1043 } 1044 } 1045 1046 call(this.method); 1047 1048 if (!ractive[this.method] && this.deprecate && call(this.deprecate.deprecated)) { 1049 if (this.deprecate.message) { 1050 warnIfDebug(this.deprecate.message); 1051 } else {
1052 warnIfDebug("The method \"%s\" has been deprecated in favor of \"%s\" and will likely be removed in a future release. See http://docs.ractivejs.org/latest/migrating for more information.", this.deprecate.deprecated, this.deprecate.replacement); 1053 } 1054 } 1055 1056 arg ? ractive.fire(this.event, arg) : ractive.fire(this.event); 1057 }; 1058 1059 var hooks_Hook = Hook; 1060 1061 function addToArray(array, value) { 1062 var index = array.indexOf(value); 1063 1064 if (index === -1) { 1065 array.push(value); 1066 } 1067 } 1068 1069 function arrayContains(array, value) { 1070 for (var i = 0, c = array.length; i < c; i++) { 1071 if (array[i] == value) { 1072 return true; 1073 } 1074 } 1075 1076 return false; 1077 } 1078 1079 function arrayContentsMatch(a, b) { 1080 var i; 1081 1082 if (!isArray(a) || !isArray(b)) { 1083 return false; 1084 } 1085 1086 if (a.length !== b.length) { 1087 return false; 1088 } 1089 1090 i = a.length; 1091 while (i--) { 1092 if (a[i] !== b[i]) { 1093 return false; 1094 } 1095 } 1096 1097 return true; 1098 } 1099 1100 function ensureArray(x) { 1101 if (typeof x === "string") { 1102 return [x]; 1103 } 1104 1105 if (x === undefined) { 1106 return []; 1107 } 1108 1109 return x; 1110 } 1111 1112 function lastItem(array) { 1113 return array[array.length - 1]; 1114 } 1115 1116 function removeFromArray(array, member) { 1117 var index = array.indexOf(member); 1118 1119 if (index !== -1) { 1120 array.splice(index, 1); 1121 } 1122 } 1123 1124 function toArray(arrayLike) { 1125 var array = [], 1126 i = arrayLike.length; 1127 while (i--) { 1128 array[i] = arrayLike[i]; 1129 } 1130 1131 return array; 1132 } 1133 1134 var _Promise, 1135 PENDING = {}, 1136 FULFILLED = {}, 1137 REJECTED = {}; 1138 1139 if (typeof Promise === "function") { 1140 // use native Promise 1141 _Promise = Promise; 1142 } else { 1143 _Promise = function (callback) { 1144 var fulfilledHandlers = [], 1145 rejectedHandlers = [], 1146 state = PENDING, 1147 result, 1148 dispatchHandlers, 1149 makeResolver, 1150 fulfil, 1151 reject, 1152 promise; 1153 1154 makeResolver = function (newState) { 1155 return function (value) { 1156 if (state !== PENDING) { 1157 return; 1158 } 1159 1160 result = value; 1161 state = newState; 1162 1163 dispatchHandlers = makeDispatcher(state === FULFILLED ? fulfilledHandlers : rejectedHandlers, result); 1164 1165 // dispatch onFulfilled and onRejected handlers asynchronously 1166 wait(dispatchHandlers); 1167 }; 1168 }; 1169 1170 fulfil = makeResolver(FULFILLED); 1171 reject = makeResolver(REJECTED); 1172 1173 try { 1174 callback(fulfil, reject); 1175 } catch (err) { 1176 reject(err); 1177 } 1178 1179 promise = { 1180 // `then()` returns a Promise - 2.2.7 1181 then: function (onFulfilled, onRejected) { 1182 var promise2 = new _Promise(function (fulfil, reject) { 1183 1184 var processResolutionHandler = function (handler, handlers, forward) { 1185 1186 // 2.2.1.1 1187 if (typeof handler === "function") { 1188 handlers.push(function (p1result) { 1189 var x; 1190 1191 try { 1192 x = handler(p1result); 1193 utils_Promise__resolve(promise2, x, fulfil, reject); 1194 } catch (err) { 1195 reject(err); 1196 } 1197 }); 1198 } else { 1199 // Forward the result of promise1 to promise2, if resolution handlers 1200 // are not given 1201 handlers.push(forward); 1202 } 1203 }; 1204 1205 // 2.2 1206 processResolutionHandler(onFulfilled, fulfilledHandlers, fulfil); 1207 processResolutionHandler(onRejected, rejectedHandlers, reject); 1208 1209 if (state !== PENDING) { 1210 // If the promise has resolved already, dispatch the appropriate handlers asynchronously 1211 wait(dispatchHandlers); 1212 } 1213 }); 1214 1215 return promise2; 1216 } 1217 }; 1218 1219 promise["catch"] = function (onRejected) { 1220 return this.then(null, onRejected); 1221 }; 1222 1223 return promise; 1224 }; 1225 1226 _Promise.all = function (promises) { 1227 return new _Promise(function (fulfil, reject) { 1228 var result = [], 1229 pending, 1230 i, 1231 processPromise; 1232 1233 if (!promises.length) { 1234 fulfil(result); 1235 return; 1236 } 1237 1238 processPromise = function (promise, i) { 1239 if (promise && typeof promise.then === "function") { 1240 promise.then(function (value) { 1241 result[i] = value; 1242 --pending || fulfil(result); 1243 }, reject); 1244 } else { 1245 result[i] = promise; 1246 --pending || fulfil(result); 1247 } 1248 }; 1249 1250 pending = i = promises.length; 1251 while (i--) { 1252 processPromise(promises[i], i); 1253 } 1254 }); 1255 }; 1256 1257 _Promise.resolve = function (value) { 1258 return new _Promise(function (fulfil) { 1259 fulfil(value); 1260 }); 1261 }; 1262 1263 _Promise.reject = function (reason) { 1264 return new _Promise(function (fulfil, reject) { 1265 reject(reason); 1266 }); 1267 }; 1268 } 1269 1270 var utils_Promise = _Promise; 1271 1272 // TODO use MutationObservers or something to simulate setImmediate 1273 function wait(callback) { 1274 setTimeout(callback, 0); 1275 } 1276 1277 function makeDispatcher(handlers, result) { 1278 return function () { 1279 var handler; 1280 1281 while (handler = handlers.shift()) { 1282 handler(result); 1283 } 1284 }; 1285 } 1286 1287 function utils_Promise__resolve(promise, x, fulfil, reject) { 1288 // Promise Resolution Procedure 1289 var then; 1290 1291 // 2.3.1 1292 if (x === promise) { 1293 throw new TypeError("A promise's fulfillment handler cannot return the same promise"); 1294 } 1295 1296 // 2.3.2 1297 if (x instanceof _Promise) { 1298 x.then(fulfil, reject); 1299 } 1300 1301 // 2.3.3 1302 else if (x && (typeof x === "object" || typeof x === "function")) {
1303 try { 1304 then = x.then; // 2.3.3.1 1305 } catch (e) { 1306 reject(e); // 2.3.3.2 1307 return; 1308 } 1309 1310 // 2.3.3.3 1311 if (typeof then === "function") { 1312 var called, resolvePromise, rejectPromise; 1313 1314 resolvePromise = function (y) { 1315 if (called) { 1316 return; 1317 } 1318 called = true; 1319 utils_Promise__resolve(promise, y, fulfil, reject); 1320 }; 1321 1322 rejectPromise = function (r) { 1323 if (called) { 1324 return; 1325 } 1326 called = true; 1327 reject(r); 1328 }; 1329 1330 try { 1331 then.call(x, resolvePromise, rejectPromise); 1332 } catch (e) { 1333 if (!called) { 1334 // 2.3.3.3.4.1 1335 reject(e); // 2.3.3.3.4.2 1336 called = true; 1337 return; 1338 } 1339 } 1340 } else { 1341 fulfil(x); 1342 } 1343 } else { 1344 fulfil(x); 1345 } 1346 } 1347 1348 var getInnerContext = function (fragment) { 1349 do { 1350 if (fragment.context !== undefined) { 1351 return fragment.context; 1352 } 1353 } while (fragment = fragment.parent); 1354 1355 return rootKeypath; 1356 }; 1357 1358 var shared_resolveRef = resolveRef; 1359 1360 function resolveRef(ractive, ref, fragment) { 1361 var keypath; 1362 1363 ref = normalise(ref); 1364 1365 // If a reference begins '~/', it's a top-level reference 1366 if (ref.substr(0, 2) === "~/") { 1367 keypath = getKeypath(ref.substring(2)); 1368 createMappingIfNecessary(ractive, keypath.firstKey, fragment); 1369 } 1370 1371 // If a reference begins with '.', it's either a restricted reference or 1372 // an ancestor reference... 1373 else if (ref[0] === ".") { 1374 keypath = resolveAncestorRef(getInnerContext(fragment), ref); 1375 1376 if (keypath) { 1377 createMappingIfNecessary(ractive, keypath.firstKey, fragment); 1378 } 1379 } 1380 1381 // ...otherwise we need to figure out the keypath based on context 1382 else { 1383 keypath = resolveAmbiguousReference(ractive, getKeypath(ref), fragment); 1384 } 1385 1386 return keypath; 1387 } 1388 1389 function resolveAncestorRef(baseContext, ref) { 1390 var contextKeys; 1391 1392 // TODO... 1393 if (baseContext != undefined && typeof baseContext !== "string") { 1394 baseContext = baseContext.str; 1395 } 1396 1397 // {{.}} means 'current context' 1398 if (ref === ".") return getKeypath(baseContext); 1399 1400 contextKeys = baseContext ? baseContext.split(".") : []; 1401 1402 // ancestor references (starting "../") go up the tree 1403 if (ref.substr(0, 3) === "../") { 1404 while (ref.substr(0, 3) === "../") { 1405 if (!contextKeys.length) { 1406 throw new Error("Could not resolve reference - too many \"../\" prefixes"); 1407 } 1408 1409 contextKeys.pop(); 1410 ref = ref.substring(3); 1411 } 1412 1413 contextKeys.push(ref); 1414 return getKeypath(contextKeys.join(".")); 1415 } 1416 1417 // not an ancestor reference - must be a restricted reference (prepended with "." or "./") 1418 if (!baseContext) { 1419 return getKeypath(ref.replace(/^\.\/?/, "")); 1420 } 1421 1422 return getKeypath(baseContext + ref.replace(/^\.\//, ".")); 1423 } 1424 1425 function resolveAmbiguousReference(ractive, ref, fragment, isParentLookup) { 1426 var context, key, parentValue, hasContextChain, parentKeypath; 1427 1428 if (ref.isRoot) { 1429 return ref; 1430 } 1431 1432 key = ref.firstKey; 1433 1434 while (fragment) { 1435 context = fragment.context; 1436 fragment = fragment.parent; 1437 1438 if (!context) { 1439 continue; 1440 } 1441 1442 hasContextChain = true; 1443 parentValue = ractive.viewmodel.get(context); 1444 1445 if (parentValue && (typeof parentValue === "object" || typeof parentValue === "function") && key in parentValue) { 1446 return context.join(ref.str); 1447 } 1448 } 1449 1450 // Root/computed/mapped property? 1451 if (isRootProperty(ractive.viewmodel, key)) { 1452 return ref; 1453 } 1454 1455 // If this is an inline component, and it's not isolated, we 1456 // can try going up the scope chain 1457 if (ractive.parent && !ractive.isolated) { 1458 hasContextChain = true; 1459 fragment = ractive.component.parentFragment; 1460 1461 key = getKeypath(key); 1462 1463 if (parentKeypath = resolveAmbiguousReference(ractive.parent, key, fragment, true)) { 1464 // We need to create an inter-component binding 1465 ractive.viewmodel.map(key, { 1466 origin: ractive.parent.viewmodel, 1467 keypath: parentKeypath 1468 }); 1469 1470 return ref; 1471 } 1472 } 1473 1474 // If there's no context chain, and the instance is either a) isolated or 1475 // b) an orphan, then we know that the keypath is identical to the reference 1476 if (!isParentLookup && !hasContextChain) { 1477 // the data object needs to have a property by this name, 1478 // to prevent future failed lookups 1479 ractive.viewmodel.set(ref, undefined); 1480 return ref; 1481 } 1482 } 1483 1484 function createMappingIfNecessary(ractive, key) { 1485 var parentKeypath; 1486 1487 if (!ractive.parent || ractive.isolated || isRootProperty(ractive.viewmodel, key)) { 1488 return; 1489 } 1490 1491 key = getKeypath(key); 1492
1493 if (parentKeypath = resolveAmbiguousReference(ractive.parent, key, ractive.component.parentFragment, true)) { 1494 ractive.viewmodel.map(key, { 1495 origin: ractive.parent.viewmodel, 1496 keypath: parentKeypath 1497 }); 1498 } 1499 } 1500 1501 function isRootProperty(viewmodel, key) { 1502 // special case for reference to root 1503 return key === "" || key in viewmodel.data || key in viewmodel.computations || key in viewmodel.mappings; 1504 } 1505 1506 function teardown(x) { 1507 x.teardown(); 1508 } 1509 1510 function methodCallers__unbind(x) { 1511 x.unbind(); 1512 } 1513 1514 function methodCallers__unrender(x) { 1515 x.unrender(); 1516 } 1517 1518 function cancel(x) { 1519 x.cancel(); 1520 } 1521 1522 var TransitionManager = function (callback, parent) { 1523 this.callback = callback; 1524 this.parent = parent; 1525 1526 this.intros = []; 1527 this.outros = []; 1528 1529 this.children = []; 1530 this.totalChildren = this.outroChildren = 0; 1531 1532 this.detachQueue = []; 1533 this.decoratorQueue = []; 1534 this.outrosComplete = false; 1535 1536 if (parent) { 1537 parent.addChild(this); 1538 } 1539 }; 1540 1541 TransitionManager.prototype = { 1542 addChild: function (child) { 1543 this.children.push(child); 1544 1545 this.totalChildren += 1; 1546 this.outroChildren += 1; 1547 }, 1548 1549 decrementOutros: function () { 1550 this.outroChildren -= 1; 1551 check(this); 1552 }, 1553 1554 decrementTotal: function () { 1555 this.totalChildren -= 1; 1556 check(this); 1557 }, 1558 1559 add: function (transition) { 1560 var list = transition.isIntro ? this.intros : this.outros; 1561 list.push(transition); 1562 }, 1563 1564 addDecorator: function (decorator) { 1565 this.decoratorQueue.push(decorator); 1566 }, 1567 1568 remove: function (transition) { 1569 var list = transition.isIntro ? this.intros : this.outros; 1570 removeFromArray(list, transition); 1571 check(this); 1572 }, 1573 1574 init: function () { 1575 this.ready = true; 1576 check(this); 1577 }, 1578 1579 detachNodes: function () { 1580 this.decoratorQueue.forEach(teardown); 1581 this.detachQueue.forEach(detach); 1582 this.children.forEach(detachNodes); 1583 } 1584 }; 1585 1586 function detach(element) { 1587 element.detach(); 1588 } 1589 1590 function detachNodes(tm) { 1591 tm.detachNodes(); 1592 } 1593 1594 function check(tm) { 1595 if (!tm.ready || tm.outros.length || tm.outroChildren) return; 1596 1597 // If all outros are complete, and we haven't already done this, 1598 // we notify the parent if there is one, otherwise 1599 // start detaching nodes 1600 if (!tm.outrosComplete) { 1601 if (tm.parent) { 1602 tm.parent.decrementOutros(tm); 1603 } else { 1604 tm.detachNodes(); 1605 } 1606 1607 tm.outrosComplete = true; 1608 } 1609 1610 // Once everything is done, we can notify parent transition 1611 // manager and call the callback 1612 if (!tm.intros.length && !tm.totalChildren) { 1613 if (typeof tm.callback === "function") { 1614 tm.callback(); 1615 } 1616 1617 if (tm.parent) { 1618 tm.parent.decrementTotal(); 1619 } 1620 } 1621 } 1622 1623 var global_TransitionManager = TransitionManager; 1624 1625 var batch, 1626 runloop, 1627 unresolved = [], 1628 changeHook = new hooks_Hook("change"); 1629 1630 runloop = { 1631 start: function (instance, returnPromise) { 1632 var promise, fulfilPromise; 1633 1634 if (returnPromise) { 1635 promise = new utils_Promise(function (f) { 1636 return fulfilPromise = f; 1637 }); 1638 } 1639 1640 batch = { 1641 previousBatch: batch, 1642 transitionManager: new global_TransitionManager(fulfilPromise, batch && batch.transitionManager), 1643 views: [], 1644 tasks: [], 1645 ractives: [], 1646 instance: instance 1647 }; 1648 1649 if (instance) { 1650 batch.ractives.push(instance); 1651 } 1652 1653 return promise; 1654 }, 1655 1656 end: function () { 1657 flushChanges(); 1658 1659 batch.transitionManager.init(); 1660 if (!batch.previousBatch && !!batch.instance) batch.instance.viewmodel.changes = []; 1661 batch = batch.previousBatch; 1662 }, 1663 1664 addRactive: function (ractive) { 1665 if (batch) { 1666 addToArray(batch.ractives, ractive); 1667 } 1668 }, 1669 1670 registerTransition: function (transition) { 1671 transition._manager = batch.transitionManager; 1672 batch.transitionManager.add(transition); 1673 }, 1674 1675 registerDecorator: function (decorator) { 1676 batch.transitionManager.addDecorator(decorator); 1677 }, 1678 1679 addView: function (view) { 1680 batch.views.push(view); 1681 }, 1682 1683 addUnresolved: function (thing) { 1684 unresolved.push(thing); 1685 }, 1686 1687 removeUnresolved: function (thing) { 1688 removeFromArray(unresolved, thing); 1689 }, 1690 1691 // synchronise node detachments with transition ends 1692 detachWhenReady: function (thing) { 1693 batch.transitionManager.detachQueue.push(thing); 1694 }, 1695 1696 scheduleTask: function (task, postRender) { 1697 var _batch; 1698 1699 if (!batch) { 1700 task(); 1701 } else { 1702 _batch = batch; 1703 while (postRender && _batch.previousBatch) { 1704 // this can't happen until the DOM has been fully updated 1705 // otherwise in some situations (with components inside elements) 1706 // transitions and decorators will initialise prematurely 1707 _batch = _batch.previousBatch; 1708 } 1709 1710 _batch.tasks.push(task); 1711 } 1712 } 1713 }; 1714 1715 var global_runloop = runloop; 1716 1717 function flushChanges() { 1718 var i, thing, changeHash; 1719 1720 while (batch.ractives.length) { 1721 thing = batch.ractives.pop(); 1722 changeHash = thing.viewmodel.applyChanges(); 1723 1724 if (changeHash) { 1725 changeHook.fire(thing, changeHash); 1726 } 1727 } 1728
1729 attemptKeypathResolution(); 1730 1731 // Now that changes have been fully propagated, we can update the DOM 1732 // and complete other tasks 1733 for (i = 0; i < batch.views.length; i += 1) { 1734 batch.views[i].update(); 1735 } 1736 batch.views.length = 0; 1737 1738 for (i = 0; i < batch.tasks.length; i += 1) { 1739 batch.tasks[i](); 1740 } 1741 batch.tasks.length = 0; 1742 1743 // If updating the view caused some model blowback - e.g. a triple 1744 // containing <option> elements caused the binding on the <select> 1745 // to update - then we start over 1746 if (batch.ractives.length) return flushChanges(); 1747 } 1748 1749 function attemptKeypathResolution() { 1750 var i, item, keypath, resolved; 1751 1752 i = unresolved.length; 1753 1754 // see if we can resolve any unresolved references 1755 while (i--) { 1756 item = unresolved[i]; 1757 1758 if (item.keypath) { 1759 // it resolved some other way. TODO how? two-way binding? Seems 1760 // weird that we'd still end up here 1761 unresolved.splice(i, 1); 1762 continue; // avoid removing the wrong thing should the next condition be true 1763 } 1764 1765 if (keypath = shared_resolveRef(item.root, item.ref, item.parentFragment)) { 1766 (resolved || (resolved = [])).push({ 1767 item: item, 1768 keypath: keypath 1769 }); 1770 1771 unresolved.splice(i, 1); 1772 } 1773 } 1774 1775 if (resolved) { 1776 resolved.forEach(global_runloop__resolve); 1777 } 1778 } 1779 1780 function global_runloop__resolve(resolved) { 1781 resolved.item.resolve(resolved.keypath); 1782 } 1783 1784 var queue = []; 1785 1786 var animations = { 1787 tick: function () { 1788 var i, animation, now; 1789 1790 now = utils_getTime(); 1791 1792 global_runloop.start(); 1793 1794 for (i = 0; i < queue.length; i += 1) { 1795 animation = queue[i]; 1796 1797 if (!animation.tick(now)) { 1798 // animation is complete, remove it from the stack, and decrement i so we don't miss one 1799 queue.splice(i--, 1); 1800 } 1801 } 1802 1803 global_runloop.end(); 1804 1805 if (queue.length) { 1806 rAF(animations.tick); 1807 } else { 1808 animations.running = false; 1809 } 1810 }, 1811 1812 add: function (animation) { 1813 queue.push(animation); 1814 1815 if (!animations.running) { 1816 animations.running = true; 1817 rAF(animations.tick); 1818 } 1819 }, 1820 1821 // TODO optimise this 1822 abort: function (keypath, root) { 1823 var i = queue.length, 1824 animation; 1825 1826 while (i--) { 1827 animation = queue[i]; 1828 1829 if (animation.root === root && animation.keypath === keypath) { 1830 animation.stop(); 1831 } 1832 } 1833 } 1834 }; 1835 1836 var shared_animations = animations; 1837 1838 var Animation = function (options) { 1839 var key; 1840 1841 this.startTime = Date.now(); 1842 1843 // from and to 1844 for (key in options) { 1845 if (options.hasOwnProperty(key)) { 1846 this[key] = options[key]; 1847 } 1848 } 1849 1850 this.interpolator = shared_interpolate(this.from, this.to, this.root, this.interpolator); 1851 this.running = true; 1852 1853 this.tick(); 1854 }; 1855 1856 Animation.prototype = { 1857 tick: function () { 1858 var elapsed, t, value, timeNow, index, keypath; 1859 1860 keypath = this.keypath; 1861 1862 if (this.running) { 1863 timeNow = Date.now(); 1864 elapsed = timeNow - this.startTime; 1865 1866 if (elapsed >= this.duration) { 1867 if (keypath !== null) { 1868 global_runloop.start(this.root); 1869 this.root.viewmodel.set(keypath, this.to); 1870 global_runloop.end(); 1871 } 1872 1873 if (this.step) { 1874 this.step(1, this.to); 1875 } 1876 1877 this.complete(this.to); 1878 1879 index = this.root._animations.indexOf(this); 1880 1881 // TODO investigate why this happens 1882 if (index === -1) { 1883 warnIfDebug("Animation was not found"); 1884 } 1885 1886 this.root._animations.splice(index, 1); 1887 1888 this.running = false; 1889 return false; // remove from the stack 1890 } 1891 1892 t = this.easing ? this.easing(elapsed / this.duration) : elapsed / this.duration; 1893 1894 if (keypath !== null) { 1895 value = this.interpolator(t); 1896 global_runloop.start(this.root); 1897 this.root.viewmodel.set(keypath, value); 1898 global_runloop.end(); 1899 } 1900 1901 if (this.step) { 1902 this.step(t, value); 1903 } 1904 1905 return true; // keep in the stack 1906 } 1907 1908 return false; // remove from the stack 1909 }, 1910 1911 stop: function () { 1912 var index; 1913 1914 this.running = false; 1915 1916 index = this.root._animations.indexOf(this); 1917 1918 // TODO investigate why this happens 1919 if (index === -1) { 1920 warnIfDebug("Animation was not found"); 1921 } 1922 1923 this.root._animations.splice(index, 1); 1924 } 1925 }; 1926 1927 var animate_Animation = Animation; 1928 1929 var prototype_animate = Ractive$animate; 1930 1931 var noAnimation = { stop: noop }; 1932 function Ractive$animate(keypath, to, options) { 1933 var promise, fulfilPromise, k, animation, animations, easing, duration, step, complete, makeValueCollector, currentValues, collectValue, dummy, dummyOptions; 1934 1935 promise = new utils_Promise(function (fulfil) { 1936 return fulfilPromise = fulfil; 1937 }); 1938 1939 // animate multiple keypaths 1940 if (typeof keypath === "object") { 1941 options = to || {}; 1942 easing = options.easing; 1943 duration = options.duration; 1944 1945 animations = []; 1946 1947 // we don't want to pass the `step` and `complete` handlers, as they will 1948 // run for each animation! So instead we'll store the handlers and create 1949 // our own... 1950 step = options.step; 1951 complete = options.complete; 1952 1953 if (step || complete) { 1954 currentValues = {}; 1955 1956 options.step = null; 1957 options.complete = null; 1958 1959 makeValueCollector = function (keypath) { 1960 return function (t, value) { 1961 currentValues[keypath] = value; 1962 }; 1963 }; 1964 } 1965 1966 for (k in keypath) { 1967 if (keypath.hasOwnProperty(k)) { 1968 if (step || complete) { 1969 collectValue = makeValueCollector(k); 1970 options = { easing: easing, duration: duration }; 1971 1972 if (step) { 1973 options.step = collectValue; 1974 } 1975 } 1976 1977 options.complete = complete ? collectValue : noop; 1978 animations.push(animate(this, k, keypath[k], options)); 1979 } 1980 } 1981 1982 // Create a dummy animation, to facilitate step/complete 1983 // callbacks, and Promise fulfilment 1984 dummyOptions = { easing: easing, duration: duration }; 1985 1986 if (step) { 1987 dummyOptions.step = function (t) { 1988 return step(t, currentValues); 1989 }; 1990 } 1991 1992 if (complete) { 1993 promise.then(function (t) { 1994 return complete(t, currentValues); 1995 }); 1996 } 1997 1998 dummyOptions.complete = fulfilPromise; 1999 2000 dummy = animate(this, null, null, dummyOptions); 2001 animations.push(dummy); 2002 2003 promise.stop = function () { 2004 var animation; 2005 2006 while (animation = animations.pop()) { 2007 animation.stop(); 2008 } 2009 2010 if (dummy) { 2011 dummy.stop(); 2012 } 2013 }; 2014 2015 return promise; 2016 } 2017 2018 // animate a single keypath 2019 options = options || {}; 2020 2021 if (options.complete) { 2022 promise.then(options.complete); 2023 } 2024 2025 options.complete = fulfilPromise; 2026 animation = animate(this, keypath, to, options); 2027 2028 promise.stop = function () { 2029 return animation.stop(); 2030 }; 2031 return promise; 2032 } 2033 2034 function animate(root, keypath, to, options) { 2035 var easing, duration, animation, from; 2036 2037 if (keypath) { 2038 keypath = getKeypath(normalise(keypath)); 2039 } 2040 2041 if (keypath !== null) { 2042 from = root.viewmodel.get(keypath); 2043 } 2044 2045 // cancel any existing animation 2046 // TODO what about upstream/downstream keypaths? 2047 shared_animations.abort(keypath, root); 2048 2049 // don't bother animating values that stay the same 2050 if (isEqual(from, to)) { 2051 if (options.complete) { 2052 options.complete(options.to); 2053 } 2054 2055 return noAnimation; 2056 } 2057 2058 // easing function 2059 if (options.easing) { 2060 if (typeof options.easing === "function") { 2061 easing = options.easing; 2062 } else { 2063 easing = root.easing[options.easing]; 2064 } 2065 2066 if (typeof easing !== "function") { 2067 easing = null; 2068 } 2069 } 2070 2071 // duration 2072 duration = options.duration === undefined ? 400 : options.duration; 2073 2074 // TODO store keys, use an internal set method 2075 animation = new animate_Animation({ 2076 keypath: keypath, 2077 from: from, 2078 to: to, 2079 root: root, 2080 duration: duration, 2081 easing: easing, 2082 interpolator: options.interpolator, 2083 2084 // TODO wrap callbacks if necessary, to use instance as context 2085 step: options.step, 2086 complete: options.complete 2087 }); 2088 2089 shared_animations.add(animation); 2090 root._animations.push(animation); 2091 2092 return animation; 2093 } 2094 2095 var prototype_detach = Ractive$detach; 2096 var prototype_detach__detachHook = new hooks_Hook("detach"); 2097 function Ractive$detach() { 2098 if (this.detached) { 2099 return this.detached; 2100 } 2101 2102 if (this.el) { 2103 removeFromArray(this.el.__ractive_instances__, this); 2104 } 2105 this.detached = this.fragment.detach(); 2106 prototype_detach__detachHook.fire(this); 2107 return this.detached; 2108 } 2109 2110 var prototype_find = Ractive$find; 2111 2112 function Ractive$find(selector) { 2113 if (!this.el) { 2114 return null; 2115 } 2116 2117 return this.fragment.find(selector); 2118 } 2119 2120 var test = Query$test; 2121 function Query$test(item, noDirty) { 2122 var itemMatches; 2123 2124 if (this._isComponentQuery) { 2125 itemMatches = !this.selector || item.name === this.selector; 2126 } else { 2127 itemMatches = item.node ? matches(item.node, this.selector) : null; 2128 } 2129 2130 if (itemMatches) { 2131 this.push(item.node || item.instance); 2132
2133 if (!noDirty) { 2134 this._makeDirty(); 2135 } 2136 2137 return true; 2138 } 2139 } 2140 2141 var makeQuery_cancel = function () { 2142 var liveQueries, selector, index; 2143 2144 liveQueries = this._root[this._isComponentQuery ? "liveComponentQueries" : "liveQueries"]; 2145 selector = this.selector; 2146 2147 index = liveQueries.indexOf(selector); 2148 2149 if (index !== -1) { 2150 liveQueries.splice(index, 1); 2151 liveQueries[selector] = null; 2152 } 2153 }; 2154 2155 var sortByItemPosition = function (a, b) { 2156 var ancestryA, ancestryB, oldestA, oldestB, mutualAncestor, indexA, indexB, fragments, fragmentA, fragmentB; 2157 2158 ancestryA = getAncestry(a.component || a._ractive.proxy); 2159 ancestryB = getAncestry(b.component || b._ractive.proxy); 2160 2161 oldestA = lastItem(ancestryA); 2162 oldestB = lastItem(ancestryB); 2163 2164 // remove items from the end of both ancestries as long as they are identical 2165 // - the final one removed is the closest mutual ancestor 2166 while (oldestA && oldestA === oldestB) { 2167 ancestryA.pop(); 2168 ancestryB.pop(); 2169 2170 mutualAncestor = oldestA; 2171 2172 oldestA = lastItem(ancestryA); 2173 oldestB = lastItem(ancestryB); 2174 } 2175 2176 // now that we have the mutual ancestor, we can find which is earliest 2177 oldestA = oldestA.component || oldestA; 2178 oldestB = oldestB.component || oldestB; 2179 2180 fragmentA = oldestA.parentFragment; 2181 fragmentB = oldestB.parentFragment; 2182 2183 // if both items share a parent fragment, our job is easy 2184 if (fragmentA === fragmentB) { 2185 indexA = fragmentA.items.indexOf(oldestA); 2186 indexB = fragmentB.items.indexOf(oldestB); 2187 2188 // if it's the same index, it means one contains the other, 2189 // so we see which has the longest ancestry 2190 return indexA - indexB || ancestryA.length - ancestryB.length; 2191 } 2192 2193 // if mutual ancestor is a section, we first test to see which section 2194 // fragment comes first 2195 if (fragments = mutualAncestor.fragments) { 2196 indexA = fragments.indexOf(fragmentA); 2197 indexB = fragments.indexOf(fragmentB); 2198 2199 return indexA - indexB || ancestryA.length - ancestryB.length; 2200 } 2201 2202 throw new Error("An unexpected condition was met while comparing the position of two components. Please file an issue at https://github.com/RactiveJS/Ractive/issues - thanks!"); 2203 }; 2204 2205 function getParent(item) { 2206 var parentFragment; 2207 2208 if (parentFragment = item.parentFragment) { 2209 return parentFragment.owner; 2210 } 2211 2212 if (item.component && (parentFragment = item.component.parentFragment)) { 2213 return parentFragment.owner; 2214 } 2215 } 2216 2217 function getAncestry(item) { 2218 var ancestry, ancestor; 2219 2220 ancestry = [item]; 2221 2222 ancestor = getParent(item); 2223 2224 while (ancestor) { 2225 ancestry.push(ancestor); 2226 ancestor = getParent(ancestor); 2227 } 2228 2229 return ancestry; 2230 } 2231 2232 var sortByDocumentPosition = function (node, otherNode) { 2233 var bitmask; 2234 2235 if (node.compareDocumentPosition) { 2236 bitmask = node.compareDocumentPosition(otherNode); 2237 return bitmask & 2 ? 1 : -1; 2238 } 2239 2240 // In old IE, we can piggy back on the mechanism for 2241 // comparing component positions 2242 return sortByItemPosition(node, otherNode); 2243 }; 2244 2245 var sort = function () { 2246 this.sort(this._isComponentQuery ? sortByItemPosition : sortByDocumentPosition); 2247 this._dirty = false; 2248 }; 2249 2250 var makeQuery_dirty = function () { 2251 var _this = this; 2252 2253 if (!this._dirty) { 2254 this._dirty = true; 2255 2256 // Once the DOM has been updated, ensure the query 2257 // is correctly ordered 2258 global_runloop.scheduleTask(function () { 2259 _this._sort(); 2260 }); 2261 } 2262 }; 2263 2264 var remove = function (nodeOrComponent) { 2265 var index = this.indexOf(this._isComponentQuery ? nodeOrComponent.instance : nodeOrComponent); 2266 2267 if (index !== -1) { 2268 this.splice(index, 1); 2269 } 2270 }; 2271 2272 var _makeQuery = makeQuery; 2273 function makeQuery(ractive, selector, live, isComponentQuery) { 2274 var query = []; 2275 2276 defineProperties(query, { 2277 selector: { value: selector }, 2278 live: { value: live }, 2279 2280 _isComponentQuery: { value: isComponentQuery }, 2281 _test: { value: test } 2282 }); 2283 2284 if (!live) { 2285 return query; 2286 } 2287 2288 defineProperties(query, { 2289 cancel: { value: makeQuery_cancel }, 2290 2291 _root: { value: ractive }, 2292 _sort: { value: sort }, 2293 _makeDirty: { value: makeQuery_dirty }, 2294 _remove: { value: remove }, 2295 2296 _dirty: { value: false, writable: true } 2297 }); 2298 2299 return query; 2300 } 2301 2302 var prototype_findAll = Ractive$findAll; 2303 function Ractive$findAll(selector, options) { 2304 var liveQueries, query; 2305 2306 if (!this.el) { 2307 return []; 2308 } 2309 2310 options = options || {}; 2311 liveQueries = this._liveQueries; 2312 2313 // Shortcut: if we're maintaining a live query with this 2314 // selector, we don't need to traverse the parallel DOM 2315 if (query = liveQueries[selector]) { 2316 2317 // Either return the exact same query, or (if not live) a snapshot 2318 return options && options.live ? query : query.slice(); 2319 } 2320 2321 query = _makeQuery(this, selector, !!options.live, false); 2322 2323 // Add this to the list of live queries Ractive needs to maintain, 2324 // if applicable 2325 if (query.live) { 2326 liveQueries.push(selector); 2327 liveQueries["_" + selector] = query; 2328 } 2329 2330 this.fragment.findAll(selector, query); 2331 return query; 2332 } 2333 2334 var prototype_findAllComponents = Ractive$findAllComponents; 2335 function Ractive$findAllComponents(selector, options) { 2336 var liveQueries, query; 2337 2338 options = options || {}; 2339 liveQueries = this._liveComponentQueries; 2340 2341 // Shortcut: if we're maintaining a live query with this 2342 // selector, we don't need to traverse the parallel DOM 2343 if (query = liveQueries[selector]) { 2344 2345 // Either return the exact same query, or (if not live) a snapshot 2346 return options && options.live ? query : query.slice(); 2347 } 2348 2349 query = _makeQuery(this, selector, !!options.live, true); 2350 2351 // Add this to the list of live queries Ractive needs to maintain, 2352 // if applicable 2353 if (query.live) { 2354 liveQueries.push(selector); 2355 liveQueries["_" + selector] = query; 2356 } 2357 2358 this.fragment.findAllComponents(selector, query); 2359 return query; 2360 } 2361 2362 var prototype_findComponent = Ractive$findComponent; 2363 2364 function Ractive$findComponent(selector) { 2365 return this.fragment.findComponent(selector); 2366 } 2367 2368 var findContainer = Ractive$findContainer; 2369 2370 function Ractive$findContainer(selector) { 2371 if (this.container) { 2372 if (this.container.component && this.container.component.name === selector) { 2373 return this.container; 2374 } else { 2375 return this.container.findContainer(selector); 2376 } 2377 } 2378 2379 return null; 2380 } 2381 2382 var findParent = Ractive$findParent; 2383 2384 function Ractive$findParent(selector) { 2385 2386 if (this.parent) { 2387 if (this.parent.component && this.parent.component.name === selector) { 2388 return this.parent; 2389 } else { 2390 return this.parent.findParent(selector); 2391 } 2392 } 2393 2394 return null; 2395 } 2396 2397 var eventStack = { 2398 enqueue: function (ractive, event) { 2399 if (ractive.event) { 2400 ractive._eventQueue = ractive._eventQueue || []; 2401 ractive._eventQueue.push(ractive.event); 2402 } 2403 ractive.event = event; 2404 }, 2405 dequeue: function (ractive) { 2406 if (ractive._eventQueue && ractive._eventQueue.length) { 2407 ractive.event = ractive._eventQueue.pop(); 2408 } else { 2409 delete ractive.event; 2410 } 2411 } 2412 }; 2413 2414 var shared_eventStack = eventStack; 2415 2416 var shared_fireEvent = fireEvent; 2417 2418 function fireEvent(ractive, eventName) { 2419 var options = arguments[2] === undefined ? {} : arguments[2]; 2420 2421 if (!eventName) { 2422 return; 2423 } 2424 2425 if (!options.event) { 2426 options.event = { 2427 name: eventName, 2428 // until event not included as argument default 2429 _noArg: true 2430 }; 2431 } else { 2432 options.event.name = eventName; 2433 } 2434 2435 var eventNames = getKeypath(eventName).wildcardMatches(); 2436 fireEventAs(ractive, eventNames, options.event, options.args, true); 2437 } 2438 2439 function fireEventAs(ractive, eventNames, event, args) { 2440 var initialFire = arguments[4] === undefined ? false : arguments[4]; 2441 2442 var subscribers, 2443 i, 2444 bubble = true; 2445 2446 shared_eventStack.enqueue(ractive, event); 2447 2448 for (i = eventNames.length; i >= 0; i--) { 2449 subscribers = ractive._subs[eventNames[i]]; 2450 2451 if (subscribers) { 2452 bubble = notifySubscribers(ractive, subscribers, event, args) && bubble; 2453 } 2454 } 2455 2456 shared_eventStack.dequeue(ractive); 2457 2458 if (ractive.parent && bubble) { 2459 2460 if (initialFire && ractive.component) { 2461 var fullName = ractive.component.name + "." + eventNames[eventNames.length - 1]; 2462 eventNames = getKeypath(fullName).wildcardMatches(); 2463 2464 if (event) { 2465 event.component = ractive; 2466 } 2467 } 2468 2469 fireEventAs(ractive.parent, eventNames, event, args); 2470 } 2471 } 2472 2473 function notifySubscribers(ractive, subscribers, event, args) { 2474 var originalEvent = null, 2475 stopEvent = false;
2476 2477 if (event && !event._noArg) { 2478 args = [event].concat(args); 2479 } 2480 2481 // subscribers can be modified inflight, e.g. "once" functionality 2482 // so we need to copy to make sure everyone gets called 2483 subscribers = subscribers.slice(); 2484 2485 for (var i = 0, len = subscribers.length; i < len; i += 1) { 2486 if (subscribers[i].apply(ractive, args) === false) { 2487 stopEvent = true; 2488 } 2489 } 2490 2491 if (event && !event._noArg && stopEvent && (originalEvent = event.original)) { 2492 originalEvent.preventDefault && originalEvent.preventDefault(); 2493 originalEvent.stopPropagation && originalEvent.stopPropagation(); 2494 } 2495 2496 return !stopEvent; 2497 } 2498 2499 var prototype_fire = Ractive$fire; 2500 function Ractive$fire(eventName) { 2501 2502 var options = { 2503 args: Array.prototype.slice.call(arguments, 1) 2504 }; 2505 2506 shared_fireEvent(this, eventName, options); 2507 } 2508 2509 var prototype_get = Ractive$get; 2510 var options = { 2511 capture: true, // top-level calls should be intercepted 2512 noUnwrap: true, // wrapped values should NOT be unwrapped 2513 fullRootGet: true // root get should return mappings 2514 }; 2515 function Ractive$get(keypath) { 2516 var value; 2517 2518 keypath = getKeypath(normalise(keypath)); 2519 value = this.viewmodel.get(keypath, options); 2520 2521 // Create inter-component binding, if necessary 2522 if (value === undefined && this.parent && !this.isolated) { 2523 if (shared_resolveRef(this, keypath.str, this.component.parentFragment)) { 2524 // creates binding as side-effect, if appropriate 2525 value = this.viewmodel.get(keypath); 2526 } 2527 } 2528 2529 return value; 2530 } 2531 2532 var insert = Ractive$insert; 2533 2534 var insertHook = new hooks_Hook("insert"); 2535 function Ractive$insert(target, anchor) { 2536 if (!this.fragment.rendered) { 2537 // TODO create, and link to, documentation explaining this 2538 throw new Error("The API has changed - you must call `ractive.render(target[, anchor])` to render your Ractive instance. Once rendered you can use `ractive.insert()`."); 2539 } 2540 2541 target = getElement(target); 2542 anchor = getElement(anchor) || null; 2543 2544 if (!target) { 2545 throw new Error("You must specify a valid target to insert into"); 2546 } 2547 2548 target.insertBefore(this.detach(), anchor); 2549 this.el = target; 2550 2551 (target.__ractive_instances__ || (target.__ractive_instances__ = [])).push(this); 2552 this.detached = null; 2553 2554 fireInsertHook(this); 2555 } 2556 2557 function fireInsertHook(ractive) { 2558 insertHook.fire(ractive); 2559 2560 ractive.findAllComponents("*").forEach(function (child) { 2561 fireInsertHook(child.instance); 2562 }); 2563 } 2564 2565 var prototype_merge = Ractive$merge; 2566 function Ractive$merge(keypath, array, options) { 2567 var currentArray, promise; 2568 2569 keypath = getKeypath(normalise(keypath)); 2570 currentArray = this.viewmodel.get(keypath); 2571 2572 // If either the existing value or the new value isn't an 2573 // array, just do a regular set 2574 if (!isArray(currentArray) || !isArray(array)) { 2575 return this.set(keypath, array, options && options.complete); 2576 } 2577 2578 // Manage transitions 2579 promise = global_runloop.start(this, true); 2580 this.viewmodel.merge(keypath, currentArray, array, options); 2581 global_runloop.end(); 2582 2583 return promise; 2584 } 2585 2586 var Observer = function (ractive, keypath, callback, options) { 2587 this.root = ractive; 2588 this.keypath = keypath; 2589 this.callback = callback; 2590 this.defer = options.defer; 2591 2592 // default to root as context, but allow it to be overridden 2593 this.context = options && options.context ? options.context : ractive; 2594 }; 2595 2596 Observer.prototype = { 2597 init: function (immediate) { 2598 this.value = this.root.get(this.keypath.str); 2599 2600 if (immediate !== false) { 2601 this.update(); 2602 } else { 2603 this.oldValue = this.value; 2604 } 2605 }, 2606 2607 setValue: function (value) { 2608 var _this = this; 2609 2610 if (!isEqual(value, this.value)) { 2611 this.value = value; 2612 2613 if (this.defer && this.ready) { 2614 global_runloop.scheduleTask(function () { 2615 return _this.update(); 2616 }); 2617 } else { 2618 this.update(); 2619 } 2620 } 2621 }, 2622 2623 update: function () { 2624 // Prevent infinite loops 2625 if (this.updating) { 2626 return; 2627 } 2628 2629 this.updating = true; 2630 2631 this.callback.call(this.context, this.value, this.oldValue, this.keypath.str); 2632 this.oldValue = this.value; 2633 2634 this.updating = false;
2635 } 2636 }; 2637 2638 var observe_Observer = Observer; 2639 2640 var observe_getPattern = getPattern; 2641 function getPattern(ractive, pattern) { 2642 var matchingKeypaths, values; 2643 2644 matchingKeypaths = getMatchingKeypaths(ractive, pattern); 2645 2646 values = {}; 2647 matchingKeypaths.forEach(function (keypath) { 2648 values[keypath.str] = ractive.get(keypath.str); 2649 }); 2650 2651 return values; 2652 } 2653 2654 var PatternObserver, 2655 slice = Array.prototype.slice; 2656 2657 PatternObserver = function (ractive, keypath, callback, options) { 2658 this.root = ractive; 2659 2660 this.callback = callback; 2661 this.defer = options.defer; 2662 2663 this.keypath = keypath; 2664 this.regex = new RegExp("^" + keypath.str.replace(/\./g, "\\.").replace(/\*/g, "([^\\.]+)") + "$"); 2665 this.values = {}; 2666 2667 if (this.defer) { 2668 this.proxies = []; 2669 } 2670 2671 // default to root as context, but allow it to be overridden 2672 this.context = options && options.context ? options.context : ractive; 2673 }; 2674 2675 PatternObserver.prototype = { 2676 init: function (immediate) { 2677 var values, keypath; 2678 2679 values = observe_getPattern(this.root, this.keypath); 2680 2681 if (immediate !== false) { 2682 for (keypath in values) { 2683 if (values.hasOwnProperty(keypath)) { 2684 this.update(getKeypath(keypath)); 2685 } 2686 } 2687 } else { 2688 this.values = values; 2689 } 2690 }, 2691 2692 update: function (keypath) { 2693 var _this = this; 2694 2695 var values; 2696 2697 if (keypath.isPattern) { 2698 values = observe_getPattern(this.root, keypath); 2699 2700 for (keypath in values) { 2701 if (values.hasOwnProperty(keypath)) { 2702 this.update(getKeypath(keypath)); 2703 } 2704 } 2705 2706 return; 2707 } 2708 2709 // special case - array mutation should not trigger `array.*` 2710 // pattern observer with `array.length` 2711 if (this.root.viewmodel.implicitChanges[keypath.str]) { 2712 return; 2713 } 2714 2715 if (this.defer && this.ready) { 2716 global_runloop.scheduleTask(function () { 2717 return _this.getProxy(keypath).update(); 2718 }); 2719 return; 2720 } 2721 2722 this.reallyUpdate(keypath); 2723 }, 2724 2725 reallyUpdate: function (keypath) { 2726 var keypathStr, value, keys, args; 2727 2728 keypathStr = keypath.str; 2729 value = this.root.viewmodel.get(keypath); 2730 2731 // Prevent infinite loops 2732 if (this.updating) { 2733 this.values[keypathStr] = value; 2734 return; 2735 } 2736 2737 this.updating = true; 2738 2739 if (!isEqual(value, this.values[keypathStr]) || !this.ready) { 2740 keys = slice.call(this.regex.exec(keypathStr), 1); 2741 args = [value, this.values[keypathStr], keypathStr].concat(keys); 2742 2743 this.values[keypathStr] = value; 2744 this.callback.apply(this.context, args); 2745 } 2746 2747 this.updating = false; 2748 }, 2749 2750 getProxy: function (keypath) { 2751 var _this = this; 2752 2753 if (!this.proxies[keypath.str]) { 2754 this.proxies[keypath.str] = { 2755 update: function () { 2756 return _this.reallyUpdate(keypath); 2757 } 2758 }; 2759 } 2760 2761 return this.proxies[keypath.str]; 2762 } 2763 }; 2764 2765 var observe_PatternObserver = PatternObserver; 2766 2767 var observe_getObserverFacade = getObserverFacade; 2768 var emptyObject = {}; 2769 function getObserverFacade(ractive, keypath, callback, options) { 2770 var observer, isPatternObserver, cancelled; 2771 2772 keypath = getKeypath(normalise(keypath)); 2773 options = options || emptyObject; 2774 2775 // pattern observers are treated differently 2776 if (keypath.isPattern) { 2777 observer = new observe_PatternObserver(ractive, keypath, callback, options); 2778 ractive.viewmodel.patternObservers.push(observer); 2779 isPatternObserver = true; 2780 } else { 2781 observer = new observe_Observer(ractive, keypath, callback, options); 2782 } 2783 2784 observer.init(options.init); 2785 ractive.viewmodel.register(keypath, observer, isPatternObserver ? "patternObservers" : "observers"); 2786 2787 // This flag allows observers to initialise even with undefined values 2788 observer.ready = true; 2789 2790 var facade = { 2791 cancel: function () { 2792 var index; 2793 2794 if (cancelled) { 2795 return; 2796 } 2797 2798 if (isPatternObserver) { 2799 index = ractive.viewmodel.patternObservers.indexOf(observer); 2800 2801 ractive.viewmodel.patternObservers.splice(index, 1); 2802 ractive.viewmodel.unregister(keypath, observer, "patternObservers"); 2803 } else { 2804 ractive.viewmodel.unregister(keypath, observer, "observers"); 2805 } 2806 cancelled = true; 2807 } 2808 }; 2809 2810 ractive._observers.push(facade); 2811 return facade; 2812 } 2813 2814 var observe = Ractive$observe; 2815 function Ractive$observe(keypath, callback, options) { 2816 2817 var observers, map, keypaths, i; 2818 2819 // Allow a map of keypaths to handlers 2820 if (isObject(keypath)) { 2821 options = callback; 2822 map = keypath; 2823 2824 observers = []; 2825 2826 for (keypath in map) { 2827 if (map.hasOwnProperty(keypath)) { 2828 callback = map[keypath]; 2829 observers.push(this.observe(keypath, callback, options)); 2830 } 2831 } 2832 2833 return { 2834 cancel: function () { 2835 while (observers.length) { 2836 observers.pop().cancel(); 2837 } 2838 } 2839 }; 2840 } 2841 2842 // Allow `ractive.observe( callback )` - i.e. observe entire model 2843 if (typeof keypath === "function") { 2844 options = callback; 2845 callback = keypath; 2846 keypath = ""; 2847 2848 return observe_getObserverFacade(this, keypath, callback, options); 2849 } 2850 2851 keypaths = keypath.split(" "); 2852 2853 // Single keypath 2854 if (keypaths.length === 1) { 2855 return observe_getObserverFacade(this, keypath, callback, options); 2856 } 2857 2858 // Multiple space-separated keypaths 2859 observers = []; 2860 2861 i = keypaths.length; 2862 while (i--) { 2863 keypath = keypaths[i]; 2864 2865 if (keypath) { 2866 observers.push(observe_getObserverFacade(this, keypath, callback, options)); 2867 } 2868 } 2869 2870 return { 2871 cancel: function () { 2872 while (observers.length) { 2873 observers.pop().cancel(); 2874 } 2875 } 2876 }; 2877 } 2878 2879 var observeOnce = Ractive$observeOnce; 2880 2881 function Ractive$observeOnce(property, callback, options) { 2882 2883 var observer = this.observe(property, function () { 2884 callback.apply(this, arguments); 2885 observer.cancel(); 2886 }, { init: false, defer: options && options.defer }); 2887 2888 return observer; 2889 } 2890 2891 var shared_trim = function (str) { 2892 return str.trim(); 2893 }; 2894 2895 var notEmptyString = function (str) { 2896 return str !== ""; 2897 }; 2898 2899 var off = Ractive$off; 2900 function Ractive$off(eventName, callback) { 2901 var _this = this; 2902 2903 var eventNames; 2904 2905 // if no arguments specified, remove all callbacks 2906 if (!eventName) { 2907 // TODO use this code instead, once the following issue has been resolved 2908 // in PhantomJS (tests are unpassable otherwise!) 2909 // https://github.com/ariya/phantomjs/issues/11856 2910 // defineProperty( this, '_subs', { value: create( null ), configurable: true }); 2911 for (eventName in this._subs) { 2912 delete this._subs[eventName]; 2913 } 2914 } else { 2915 // Handle multiple space-separated event names 2916 eventNames = eventName.split(" ").map(shared_trim).filter(notEmptyString); 2917
2918 eventNames.forEach(function (eventName) { 2919 var subscribers, index; 2920 2921 // If we have subscribers for this event... 2922 if (subscribers = _this._subs[eventName]) { 2923 // ...if a callback was specified, only remove that 2924 if (callback) { 2925 index = subscribers.indexOf(callback); 2926 if (index !== -1) { 2927 subscribers.splice(index, 1); 2928 } 2929 } 2930 2931 // ...otherwise remove all callbacks 2932 else { 2933 _this._subs[eventName] = []; 2934 } 2935 } 2936 }); 2937 } 2938 2939 return this; 2940 } 2941 2942 var on = Ractive$on; 2943 function Ractive$on(eventName, callback) { 2944 var _this = this; 2945 2946 var listeners, n, eventNames; 2947 2948 // allow mutliple listeners to be bound in one go 2949 if (typeof eventName === "object") { 2950 listeners = []; 2951 2952 for (n in eventName) { 2953 if (eventName.hasOwnProperty(n)) { 2954 listeners.push(this.on(n, eventName[n])); 2955 } 2956 } 2957 2958 return { 2959 cancel: function () { 2960 var listener; 2961 2962 while (listener = listeners.pop()) { 2963 listener.cancel(); 2964 } 2965 } 2966 }; 2967 } 2968 2969 // Handle multiple space-separated event names 2970 eventNames = eventName.split(" ").map(shared_trim).filter(notEmptyString); 2971 2972 eventNames.forEach(function (eventName) { 2973 (_this._subs[eventName] || (_this._subs[eventName] = [])).push(callback); 2974 }); 2975 2976 return { 2977 cancel: function () { 2978 return _this.off(eventName, callback); 2979 } 2980 }; 2981 } 2982 2983 var once = Ractive$once; 2984 2985 function Ractive$once(eventName, handler) { 2986 2987 var listener = this.on(eventName, function () { 2988 handler.apply(this, arguments); 2989 listener.cancel(); 2990 }); 2991 2992 // so we can still do listener.cancel() manually 2993 return listener; 2994 } 2995 2996 // This function takes an array, the name of a mutator method, and the 2997 // arguments to call that mutator method with, and returns an array that 2998 // maps the old indices to their new indices. 2999 3000 // So if you had something like this... 3001 // 3002 // array = [ 'a', 'b', 'c', 'd' ]; 3003 // array.push( 'e' ); 3004 // 3005 // ...you'd get `[ 0, 1, 2, 3 ]` - in other words, none of the old indices 3006 // have changed. If you then did this... 3007 // 3008 // array.unshift( 'z' ); 3009 // 3010 // ...the indices would be `[ 1, 2, 3, 4, 5 ]` - every item has been moved 3011 // one higher to make room for the 'z'. If you removed an item, the new index 3012 // would be -1... 3013 // 3014 // array.splice( 2, 2 ); 3015 // 3016 // ...this would result in [ 0, 1, -1, -1, 2, 3 ]. 3017 // 3018 // This information is used to enable fast, non-destructive shuffling of list 3019 // sections when you do e.g. `ractive.splice( 'items', 2, 2 ); 3020 3021 var shared_getNewIndices = getNewIndices; 3022 3023 function getNewIndices(array, methodName, args) { 3024 var spliceArguments, 3025 len, 3026 newIndices = [], 3027 removeStart, 3028 removeEnd, 3029 balance, 3030 i; 3031 3032 spliceArguments = getSpliceEquivalent(array, methodName, args); 3033 3034 if (!spliceArguments) { 3035 return null; // TODO support reverse and sort? 3036 } 3037 3038 len = array.length; 3039 balance = spliceArguments.length - 2 - spliceArguments[1]; 3040 3041 removeStart = Math.min(len, spliceArguments[0]); 3042 removeEnd = removeStart + spliceArguments[1]; 3043 3044 for (i = 0; i < removeStart; i += 1) { 3045 newIndices.push(i); 3046 } 3047 3048 for (; i < removeEnd; i += 1) { 3049 newIndices.push(-1); 3050 } 3051 3052 for (; i < len; i += 1) { 3053 newIndices.push(i + balance); 3054 } 3055 3056 // there is a net shift for the rest of the array starting with index + balance 3057 if (balance !== 0) { 3058 newIndices.touchedFrom = spliceArguments[0]; 3059 } else { 3060 newIndices.touchedFrom = array.length; 3061 } 3062 3063 return newIndices; 3064 } 3065 3066 // The pop, push, shift an unshift methods can all be represented 3067 // as an equivalent splice 3068 function getSpliceEquivalent(array, methodName, args) { 3069 switch (methodName) { 3070 case "splice": 3071 if (args[0] !== undefined && args[0] < 0) { 3072 args[0] = array.length + Math.max(args[0], -array.length); 3073 } 3074 3075 while (args.length < 2) { 3076 args.push(0); 3077 } 3078 3079 // ensure we only remove elements that exist 3080 args[1] = Math.min(args[1], array.length - args[0]); 3081 3082 return args; 3083 3084 case "sort": 3085 case "reverse": 3086 return null; 3087 3088 case "pop": 3089 if (array.length) { 3090 return [array.length - 1, 1]; 3091 } 3092 return [0, 0]; 3093 3094 case "push": 3095 return [array.length, 0].concat(args); 3096 3097 case "shift": 3098 return [0, array.length ? 1 : 0]; 3099 3100 case "unshift": 3101 return [0, 0].concat(args); 3102 } 3103 } 3104 3105 var arrayProto = Array.prototype; 3106 3107 var makeArrayMethod = function (methodName) { 3108 return function (keypath) { 3109 for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 3110 args[_key - 1] = arguments[_key]; 3111 } 3112 3113 var array, 3114 newIndices = [], 3115 len, 3116 promise, 3117 result; 3118 3119 keypath = getKeypath(normalise(keypath)); 3120 3121 array = this.viewmodel.get(keypath); 3122 len = array.length; 3123 3124 if (!isArray(array)) { 3125 throw new Error("Called ractive." + methodName + "('" + keypath.str + "'), but '" + keypath.str + "' does not refer to an array"); 3126 } 3127 3128 newIndices = shared_getNewIndices(array, methodName, args); 3129 3130 result = arrayProto[methodName].apply(array, args); 3131 promise = global_runloop.start(this, true).then(function () { 3132 return result; 3133 }); 3134 3135 if (!!newIndices) { 3136 this.viewmodel.smartUpdate(keypath, array, newIndices); 3137 } else { 3138 this.viewmodel.mark(keypath); 3139 } 3140 3141 global_runloop.end(); 3142 3143 return promise; 3144 }; 3145 }; 3146 3147 var pop = makeArrayMethod("pop"); 3148 3149 var push = makeArrayMethod("push"); 3150 3151 var css, 3152 update, 3153 styleElement, 3154 head, 3155 styleSheet, 3156 inDom, 3157 global_css__prefix = "/* Ractive.js component styles */\n", 3158 styles = [], 3159 dirty = false;
3160 3161 if (!isClient) { 3162 // TODO handle encapsulated CSS in server-rendered HTML! 3163 css = { 3164 add: noop, 3165 apply: noop 3166 }; 3167 } else { 3168 styleElement = document.createElement("style"); 3169 styleElement.type = "text/css"; 3170 3171 head = document.getElementsByTagName("head")[0]; 3172 3173 inDom = false; 3174 3175 // Internet Exploder won't let you use styleSheet.innerHTML - we have to 3176 // use styleSheet.cssText instead 3177 styleSheet = styleElement.styleSheet; 3178 3179 update = function () { 3180 var css = global_css__prefix + styles.map(function (s) { 3181 return "\n/* {" + s.id + "} */\n" + s.styles; 3182 }).join("\n"); 3183 3184 if (styleSheet) { 3185 styleSheet.cssText = css; 3186 } else { 3187 styleElement.innerHTML = css; 3188 } 3189 3190 if (!inDom) { 3191 head.appendChild(styleElement); 3192 inDom = true; 3193 } 3194 }; 3195 3196 css = { 3197 add: function (s) { 3198 styles.push(s); 3199 dirty = true; 3200 }, 3201 3202 apply: function () { 3203 if (dirty) { 3204 update(); 3205 dirty = false; 3206 } 3207 } 3208 }; 3209 } 3210 3211 var global_css = css; 3212 3213 var prototype_render = Ractive$render; 3214 3215 var renderHook = new hooks_Hook("render"), 3216 completeHook = new hooks_Hook("complete"); 3217 function Ractive$render(target, anchor) { 3218 var _this = this; 3219 3220 var promise, instances, transitionsEnabled; 3221 3222 // if `noIntro` is `true`, temporarily disable transitions 3223 transitionsEnabled = this.transitionsEnabled; 3224 if (this.noIntro) { 3225 this.transitionsEnabled = false; 3226 } 3227 3228 promise = global_runloop.start(this, true); 3229 global_runloop.scheduleTask(function () { 3230 return renderHook.fire(_this); 3231 }, true); 3232 3233 if (this.fragment.rendered) { 3234 throw new Error("You cannot call ractive.render() on an already rendered instance! Call ractive.unrender() first"); 3235 } 3236 3237 target = getElement(target) || this.el; 3238 anchor = getElement(anchor) || this.anchor; 3239 3240 this.el = target; 3241 this.anchor = anchor; 3242 3243 if (!this.append && target) { 3244 // Teardown any existing instances *before* trying to set up the new one - 3245 // avoids certain weird bugs 3246 var others = target.__ractive_instances__; 3247 if (others && others.length) { 3248 removeOtherInstances(others); 3249 } 3250 3251 // make sure we are the only occupants 3252 target.innerHTML = ""; // TODO is this quicker than removeChild? Initial research inconclusive 3253 } 3254 3255 if (this.cssId) { 3256 // ensure encapsulated CSS is up-to-date 3257 global_css.apply(); 3258 } 3259 3260 if (target) { 3261 if (!(instances = target.__ractive_instances__)) { 3262 target.__ractive_instances__ = [this]; 3263 } else { 3264 instances.push(this); 3265 } 3266 3267 if (anchor) { 3268 target.insertBefore(this.fragment.render(), anchor); 3269 } else { 3270 target.appendChild(this.fragment.render()); 3271 } 3272 } 3273 3274 global_runloop.end(); 3275 3276 this.transitionsEnabled = transitionsEnabled; 3277 3278 return promise.then(function () { 3279 return completeHook.fire(_this); 3280 }); 3281 } 3282 3283 function removeOtherInstances(others) { 3284 others.splice(0, others.length).forEach(teardown); 3285 } 3286 3287 var adaptConfigurator = { 3288 extend: function (Parent, proto, options) { 3289 proto.adapt = custom_adapt__combine(proto.adapt, ensureArray(options.adapt)); 3290 }, 3291 3292 init: function () {} 3293 }; 3294 3295 var custom_adapt = adaptConfigurator; 3296 3297 function custom_adapt__combine(a, b) { 3298 var c = a.slice(), 3299 i = b.length; 3300 3301 while (i--) { 3302 if (! ~c.indexOf(b[i])) { 3303 c.push(b[i]); 3304 } 3305 } 3306 3307 return c; 3308 } 3309 3310 var transform = transformCss; 3311 3312 var selectorsPattern = /(?:^|\})?\s*([^\{\}]+)\s*\{/g, 3313 commentsPattern = /\/\*.*?\*\//g, 3314 selectorUnitPattern = /((?:(?:\[[^\]+]\])|(?:[^\s\+\>\~:]))+)((?::[^\s\+\>\~\(]+(?:\([^\)]+\))?)?\s*[\s\+\>\~]?)\s*/g, 3315 mediaQueryPattern = /^@media/, 3316 dataRvcGuidPattern = /\[data-ractive-css~="\{[a-z0-9-]+\}"]/g; 3317 function transformCss(css, id) { 3318 var transformed, dataAttr, addGuid; 3319 3320 dataAttr = "[data-ractive-css~=\"{" + id + "}\"]"; 3321 3322 addGuid = function (selector) { 3323 var selectorUnits, 3324 match, 3325 unit, 3326 base, 3327 prepended, 3328 appended, 3329 i, 3330 transformed = []; 3331 3332 selectorUnits = []; 3333 3334 while (match = selectorUnitPattern.exec(selector)) { 3335 selectorUnits.push({ 3336 str: match[0], 3337 base: match[1], 3338 modifiers: match[2] 3339 }); 3340 } 3341 3342 // For each simple selector within the selector, we need to create a version
3343 // that a) combines with the id, and b) is inside the id 3344 base = selectorUnits.map(extractString); 3345 3346 i = selectorUnits.length; 3347 while (i--) { 3348 appended = base.slice(); 3349 3350 // Pseudo-selectors should go after the attribute selector 3351 unit = selectorUnits[i]; 3352 appended[i] = unit.base + dataAttr + unit.modifiers || ""; 3353 3354 prepended = base.slice(); 3355 prepended[i] = dataAttr + " " + prepended[i]; 3356 3357 transformed.push(appended.join(" "), prepended.join(" ")); 3358 } 3359 3360 return transformed.join(", "); 3361 }; 3362 3363 if (dataRvcGuidPattern.test(css)) { 3364 transformed = css.replace(dataRvcGuidPattern, dataAttr); 3365 } else { 3366 transformed = css.replace(commentsPattern, "").replace(selectorsPattern, function (match, $1) { 3367 var selectors, transformed; 3368 3369 // don't transform media queries! 3370 if (mediaQueryPattern.test($1)) return match; 3371 3372 selectors = $1.split(",").map(trim); 3373 transformed = selectors.map(addGuid).join(", ") + " "; 3374 3375 return match.replace($1, transformed); 3376 }); 3377 } 3378 3379 return transformed; 3380 } 3381 3382 function trim(str) { 3383 if (str.trim) { 3384 return str.trim(); 3385 } 3386 3387 return str.replace(/^\s+/, "").replace(/\s+$/, ""); 3388 } 3389 3390 function extractString(unit) { 3391 return unit.str; 3392 } 3393 3394 var css_css__uid = 1; 3395 3396 var cssConfigurator = { 3397 name: "css", 3398 3399 extend: function (Parent, proto, options) { 3400 if (options.css) { 3401 var id = css_css__uid++; 3402 var styles = options.noCssTransform ? options.css : transform(options.css, id); 3403 3404 proto.cssId = id; 3405 global_css.add({ id: id, styles: styles }); 3406 } 3407 }, 3408 3409 init: function () {} 3410 }; 3411 3412 var css_css = cssConfigurator; 3413 3414 function validate(data) { 3415 // Warn if userOptions.data is a non-POJO 3416 if (data && data.constructor !== Object) { 3417 if (typeof data === "function") {} else if (typeof data !== "object") { 3418 fatal("data option must be an object or a function, `" + data + "` is not valid"); 3419 } else { 3420 warnIfDebug("If supplied, options.data should be a plain JavaScript object - using a non-POJO as the root object may work, but is discouraged"); 3421 } 3422 } 3423 } 3424 3425 var dataConfigurator = { 3426 name: "data", 3427 3428 extend: function (Parent, proto, options) { 3429 var key = undefined, 3430 value = undefined; 3431 3432 // check for non-primitives, which could cause mutation-related bugs 3433 if (options.data && isObject(options.data)) { 3434 for (key in options.data) { 3435 value = options.data[key]; 3436 3437 if (value && typeof value === "object") { 3438 if (isObject(value) || isArray(value)) { 3439 warnIfDebug("Passing a `data` option with object and array properties to Ractive.extend() is discouraged, as mutating them is likely to cause bugs. Consider using a data function instead:\n\n // this...\n data: function () {\n return {\n myObject: {}\n };\n })\n\n // instead of this:\n data: {\n myObject: {}\n }"); 3440 } 3441 } 3442 } 3443 } 3444 3445 proto.data = custom_data__combine(proto.data, options.data); 3446 }, 3447 3448 init: function (Parent, ractive, options) { 3449 var result = custom_data__combine(Parent.prototype.data, options.data); 3450 3451 if (typeof result === "function") { 3452 result = result.call(ractive); 3453 } 3454 3455 return result || {}; 3456 }, 3457 3458 reset: function (ractive) { 3459 var result = this.init(ractive.constructor, ractive, ractive.viewmodel); 3460 3461 ractive.viewmodel.reset(result); 3462 return true; 3463 } 3464 }; 3465 3466 var custom_data = dataConfigurator; 3467 3468 function custom_data__combine(parentValue, childValue) { 3469 validate(childValue); 3470 3471 var parentIsFn = typeof parentValue === "function"; 3472 var childIsFn = typeof childValue === "function"; 3473 3474 // Very important, otherwise child instance can become 3475 // the default data object on Ractive or a component. 3476 // then ractive.set() ends up setting on the prototype! 3477 if (!childValue && !parentIsFn) { 3478 childValue = {}; 3479 } 3480 3481 // Fast path, where we just need to copy properties from 3482 // parent to child 3483 if (!parentIsFn && !childIsFn) { 3484 return fromProperties(childValue, parentValue); 3485 } 3486 3487 return function () { 3488 var child = childIsFn ? callDataFunction(childValue, this) : childValue; 3489 var parent = parentIsFn ? callDataFunction(parentValue, this) : parentValue; 3490 3491 return fromProperties(child, parent); 3492 }; 3493 } 3494 3495 function callDataFunction(fn, context) {
3496 var data = fn.call(context); 3497 3498 if (!data) return; 3499 3500 if (typeof data !== "object") { 3501 fatal("Data function must return an object"); 3502 } 3503 3504 if (data.constructor !== Object) { 3505 warnOnceIfDebug("Data function returned something other than a plain JavaScript object. This might work, but is strongly discouraged"); 3506 } 3507 3508 return data; 3509 } 3510 3511 function fromProperties(primary, secondary) { 3512 if (primary && secondary) { 3513 for (var key in secondary) { 3514 if (!(key in primary)) { 3515 primary[key] = secondary[key]; 3516 } 3517 } 3518 3519 return primary; 3520 } 3521 3522 return primary || secondary; 3523 } 3524 3525 // TODO do we need to support this in the new Ractive() case? 3526 3527 var Parser, 3528 ParseError, 3529 parse_Parser__leadingWhitespace = /^\s+/; 3530 3531 ParseError = function (message) { 3532 this.name = "ParseError"; 3533 this.message = message; 3534 try { 3535 throw new Error(message); 3536 } catch (e) { 3537 this.stack = e.stack; 3538 } 3539 }; 3540 3541 ParseError.prototype = Error.prototype; 3542 3543 Parser = function (str, options) { 3544 var items, 3545 item, 3546 lineStart = 0; 3547 3548 this.str = str; 3549 this.options = options || {}; 3550 this.pos = 0; 3551 3552 this.lines = this.str.split("\n"); 3553 this.lineEnds = this.lines.map(function (line) { 3554 var lineEnd = lineStart + line.length + 1; // +1 for the newline 3555 3556 lineStart = lineEnd; 3557 return lineEnd; 3558 }, 0); 3559 3560 // Custom init logic 3561 if (this.init) this.init(str, options); 3562 3563 items = []; 3564 3565 while (this.pos < this.str.length && (item = this.read())) { 3566 items.push(item); 3567 } 3568 3569 this.leftover = this.remaining(); 3570 this.result = this.postProcess ? this.postProcess(items, options) : items; 3571 }; 3572 3573 Parser.prototype = { 3574 read: function (converters) { 3575 var pos, i, len, item; 3576 3577 if (!converters) converters = this.converters; 3578 3579 pos = this.pos; 3580 3581 len = converters.length; 3582 for (i = 0; i < len; i += 1) { 3583 this.pos = pos; // reset for each attempt 3584 3585 if (item = converters[i](this)) { 3586 return item; 3587 } 3588 } 3589 3590 return null; 3591 }, 3592 3593 getLinePos: function (char) { 3594 var lineNum = 0, 3595 lineStart = 0, 3596 columnNum; 3597 3598 while (char >= this.lineEnds[lineNum]) { 3599 lineStart = this.lineEnds[lineNum]; 3600 lineNum += 1; 3601 } 3602 3603 columnNum = char - lineStart; 3604 return [lineNum + 1, columnNum + 1, char]; // line/col should be one-based, not zero-based! 3605 }, 3606 3607 error: function (message) { 3608 var pos = this.getLinePos(this.pos); 3609 var lineNum = pos[0]; 3610 var columnNum = pos[1]; 3611 3612 var line = this.lines[pos[0] - 1]; 3613 var numTabs = 0; 3614 var annotation = line.replace(/\t/g, function (match, char) { 3615 if (char < pos[1]) { 3616 numTabs += 1; 3617 } 3618 3619 return " "; 3620 }) + "\n" + new Array(pos[1] + numTabs).join(" ") + "^----"; 3621 3622 var error = new ParseError("" + message + " at line " + lineNum + " character " + columnNum + ":\n" + annotation); 3623 3624 error.line = pos[0]; 3625 error.character = pos[1]; 3626 error.shortMessage = message; 3627 3628 throw error; 3629 }, 3630 3631 matchString: function (string) { 3632 if (this.str.substr(this.pos, string.length) === string) { 3633 this.pos += string.length; 3634 return string; 3635 } 3636 }, 3637 3638 matchPattern: function (pattern) { 3639 var match; 3640 3641 if (match = pattern.exec(this.remaining())) { 3642 this.pos += match[0].length; 3643 return match[1] || match[0]; 3644 } 3645 }, 3646 3647 allowWhitespace: function () { 3648 this.matchPattern(parse_Parser__leadingWhitespace); 3649 }, 3650 3651 remaining: function () { 3652 return this.str.substring(this.pos); 3653 }, 3654 3655 nextChar: function () { 3656 return this.str.charAt(this.pos); 3657 } 3658 }; 3659 3660 Parser.extend = function (proto) { 3661 var Parent = this, 3662 Child, 3663 key; 3664 3665 Child = function (str, options) { 3666 Parser.call(this, str, options); 3667 }; 3668 3669 Child.prototype = create(Parent.prototype); 3670 3671 for (key in proto) { 3672 if (hasOwn.call(proto, key)) { 3673 Child.prototype[key] = proto[key]; 3674 } 3675 } 3676 3677 Child.extend = Parser.extend; 3678 return Child; 3679 }; 3680 3681 var parse_Parser = Parser; 3682 3683 var TEXT = 1; 3684 var INTERPOLATOR = 2; 3685 var TRIPLE = 3; 3686 var SECTION = 4; 3687 var INVERTED = 5; 3688 var CLOSING = 6; 3689 var ELEMENT = 7; 3690 var PARTIAL = 8; 3691 var COMMENT = 9; 3692 var DELIMCHANGE = 10; 3693 var ATTRIBUTE = 13; 3694 var CLOSING_TAG = 14; 3695 var COMPONENT = 15; 3696 var YIELDER = 16; 3697 var INLINE_PARTIAL = 17; 3698 var DOCTYPE = 18; 3699 3700 var NUMBER_LITERAL = 20; 3701 var STRING_LITERAL = 21; 3702 var ARRAY_LITERAL = 22; 3703 var OBJECT_LITERAL = 23; 3704 var BOOLEAN_LITERAL = 24; 3705 var REGEXP_LITERAL = 25; 3706 3707 var GLOBAL = 26; 3708 var KEY_VALUE_PAIR = 27; 3709 3710 var REFERENCE = 30; 3711 var REFINEMENT = 31; 3712 var MEMBER = 32; 3713 var PREFIX_OPERATOR = 33; 3714 var BRACKETED = 34; 3715 var CONDITIONAL = 35; 3716 var INFIX_OPERATOR = 36; 3717 3718 var INVOCATION = 40; 3719 3720 var SECTION_IF = 50; 3721 var SECTION_UNLESS = 51; 3722 var SECTION_EACH = 52; 3723 var SECTION_WITH = 53; 3724 var SECTION_IF_WITH = 54; 3725 3726 var ELSE = 60; 3727 var ELSEIF = 61; 3728 3729 var mustache_readDelimiterChange = readDelimiterChange; 3730 var delimiterChangePattern = /^[^\s=]+/, 3731 whitespacePattern = /^\s+/; 3732 function readDelimiterChange(parser) { 3733 var start, opening, closing; 3734 3735 if (!parser.matchString("=")) { 3736 return null; 3737 } 3738 3739 start = parser.pos; 3740 3741 // allow whitespace before new opening delimiter 3742 parser.allowWhitespace(); 3743 3744 opening = parser.matchPattern(delimiterChangePattern); 3745 if (!opening) { 3746 parser.pos = start; 3747 return null; 3748 } 3749 3750 // allow whitespace (in fact, it's necessary...) 3751 if (!parser.matchPattern(whitespacePattern)) { 3752 return null; 3753 } 3754 3755 closing = parser.matchPattern(delimiterChangePattern); 3756 if (!closing) { 3757 parser.pos = start; 3758 return null; 3759 } 3760 3761 // allow whitespace before closing '=' 3762 parser.allowWhitespace(); 3763 3764 if (!parser.matchString("=")) { 3765 parser.pos = start; 3766 return null; 3767 } 3768 3769 return [opening, closing]; 3770 } 3771 3772 var readRegexpLiteral = readRegexpLiteral__readNumberLiteral; 3773 var regexpPattern = /^(\/(?:[^\n\r\u2028\u2029/\\[]|\\.|\[(?:[^\n\r\u2028\u2029\]\\]|\\.)*])+\/(?:([gimuy])(?![a-z]*\2))*(?![a-zA-Z_$0-9]))/; 3774 function readRegexpLiteral__readNumberLiteral(parser) { 3775 var result; 3776 3777 if (result = parser.matchPattern(regexpPattern)) { 3778 return { 3779 t: REGEXP_LITERAL, 3780 v: result 3781 }; 3782 } 3783 3784 return null; 3785 } 3786 3787 var converters_readMustache = readMustache; 3788 3789 var delimiterChangeToken = { t: DELIMCHANGE, exclude: true }; 3790 function readMustache(parser) { 3791 var mustache, i; 3792 3793 // If we're inside a <script> or <style> tag, and we're not 3794 // interpolating, bug out 3795 if (parser.interpolate[parser.inside] === false) { 3796 return null; 3797 } 3798 3799 for (i = 0; i < parser.tags.length; i += 1) { 3800 if (mustache = readMustacheOfType(parser, parser.tags[i])) { 3801 return mustache; 3802 } 3803 } 3804 } 3805 3806 function readMustacheOfType(parser, tag) { 3807 var start, mustache, reader, i; 3808 3809 start = parser.pos; 3810 3811 if (parser.matchString("\\" + tag.open)) { 3812 if (start === 0 || parser.str[start - 1] !== "\\") { 3813 return tag.open; 3814 } 3815 } else if (!parser.matchString(tag.open)) { 3816 return null; 3817 } 3818 3819 // delimiter change? 3820 if (mustache = mustache_readDelimiterChange(parser)) { 3821 // find closing delimiter or abort... 3822 if (!parser.matchString(tag.close)) { 3823 return null; 3824 } 3825 3826 // ...then make the switch 3827 tag.open = mustache[0]; 3828 tag.close = mustache[1]; 3829 parser.sortMustacheTags(); 3830 3831 return delimiterChangeToken; 3832 } 3833 3834 parser.allowWhitespace(); 3835 3836 // illegal section closer 3837 if (parser.matchString("/")) { 3838 parser.pos -= 1; 3839 var rewind = parser.pos; 3840 if (!readRegexpLiteral(parser)) { 3841 parser.pos = rewind - tag.close.length; 3842 parser.error("Attempted to close a section that wasn't open"); 3843 } else { 3844 parser.pos = rewind; 3845 } 3846 } 3847 3848 for (i = 0; i < tag.readers.length; i += 1) { 3849 reader = tag.readers[i]; 3850 3851 if (mustache = reader(parser, tag)) { 3852 if (tag.isStatic) { 3853 mustache.s = true; // TODO make this `1` instead - more compact 3854 } 3855 3856 if (parser.includeLinePositions) { 3857 mustache.p = parser.getLinePos(start); 3858 } 3859 3860 return mustache; 3861 } 3862 } 3863 3864 parser.pos = start; 3865 return null; 3866 } 3867
3868 var expectedExpression = "Expected a JavaScript expression"; 3869 var expectedParen = "Expected closing paren"; 3870 3871 var literal_readNumberLiteral = literal_readNumberLiteral__readNumberLiteral; 3872 var literal_readNumberLiteral__numberPattern = /^(?:[+-]?)0*(?:(?:(?:[1-9]\d*)?\.\d+)|(?:(?:0|[1-9]\d*)\.)|(?:0|[1-9]\d*))(?:[eE][+-]?\d+)?/; 3873 function literal_readNumberLiteral__readNumberLiteral(parser) { 3874 var result; 3875 3876 if (result = parser.matchPattern(literal_readNumberLiteral__numberPattern)) { 3877 return { 3878 t: NUMBER_LITERAL, 3879 v: result 3880 }; 3881 } 3882 3883 return null; 3884 } 3885 3886 var literal_readBooleanLiteral = readBooleanLiteral; 3887 function readBooleanLiteral(parser) { 3888 var remaining = parser.remaining(); 3889 3890 if (remaining.substr(0, 4) === "true") { 3891 parser.pos += 4; 3892 return { 3893 t: BOOLEAN_LITERAL, 3894 v: "true" 3895 }; 3896 } 3897 3898 if (remaining.substr(0, 5) === "false") { 3899 parser.pos += 5; 3900 return { 3901 t: BOOLEAN_LITERAL, 3902 v: "false" 3903 }; 3904 } 3905 3906 return null; 3907 } 3908 3909 var stringMiddlePattern, escapeSequencePattern, lineContinuationPattern; 3910 3911 // Match one or more characters until: ", ', \, or EOL/EOF. 3912 // EOL/EOF is written as (?!.) (meaning there's no non-newline char next). 3913 stringMiddlePattern = /^(?=.)[^"'\\]+?(?:(?!.)|(?=["'\\]))/; 3914 3915 // Match one escape sequence, including the backslash. 3916 escapeSequencePattern = /^\\(?:['"\\bfnrt]|0(?![0-9])|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|(?=.)[^ux0-9])/; 3917 3918 // Match one ES5 line continuation (backslash + line terminator). 3919 lineContinuationPattern = /^\\(?:\r\n|[\u000A\u000D\u2028\u2029])/; 3920 3921 // Helper for defining getDoubleQuotedString and getSingleQuotedString. 3922 var makeQuotedStringMatcher = function (okQuote) { 3923 return function (parser) { 3924 var start, literal, done, next; 3925 3926 start = parser.pos; 3927 literal = "\""; 3928 done = false; 3929 3930 while (!done) { 3931 next = parser.matchPattern(stringMiddlePattern) || parser.matchPattern(escapeSequencePattern) || parser.matchString(okQuote); 3932 if (next) { 3933 if (next === "\"") { 3934 literal += "\\\""; 3935 } else if (next === "\\'") { 3936 literal += "'"; 3937 } else { 3938 literal += next; 3939 } 3940 } else { 3941 next = parser.matchPattern(lineContinuationPattern); 3942 if (next) { 3943 // convert \(newline-like) into a \u escape, which is allowed in JSON 3944 literal += "\\u" + ("000" + next.charCodeAt(1).toString(16)).slice(-4); 3945 } else { 3946 done = true; 3947 } 3948 } 3949 } 3950 3951 literal += "\""; 3952 3953 // use JSON.parse to interpret escapes 3954 return JSON.parse(literal); 3955 }; 3956 }; 3957 3958 var getSingleQuotedString = makeQuotedStringMatcher("\""); 3959 var getDoubleQuotedString = makeQuotedStringMatcher("'"); 3960 3961 var readStringLiteral = function (parser) { 3962 var start, string; 3963 3964 start = parser.pos; 3965 3966 if (parser.matchString("\"")) { 3967 string = getDoubleQuotedString(parser); 3968 3969 if (!parser.matchString("\"")) { 3970 parser.pos = start; 3971 return null; 3972 } 3973 3974 return { 3975 t: STRING_LITERAL, 3976 v: string 3977 }; 3978 } 3979 3980 if (parser.matchString("'")) { 3981 string = getSingleQuotedString(parser); 3982 3983 if (!parser.matchString("'")) { 3984 parser.pos = start; 3985 return null; 3986 } 3987 3988 return { 3989 t: STRING_LITERAL, 3990 v: string 3991 }; 3992 } 3993 3994 return null; 3995 }; 3996 3997 var patterns__name = /^[a-zA-Z_$][a-zA-Z_$0-9]*/; 3998 3999 // http://mathiasbynens.be/notes/javascript-properties 4000 // can be any name, string literal, or number literal 4001 var shared_readKey = readKey; 4002 var identifier = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/; 4003 function readKey(parser) { 4004 var token; 4005 4006 if (token = readStringLiteral(parser)) { 4007 return identifier.test(token.v) ? token.v : "\"" + token.v.replace(/"/g, "\\\"") + "\""; 4008 } 4009 4010 if (token = literal_readNumberLiteral(parser)) { 4011 return token.v; 4012 } 4013 4014 if (token = parser.matchPattern(patterns__name)) { 4015 return token; 4016 } 4017 } 4018 4019 var keyValuePair = readKeyValuePair; 4020 function readKeyValuePair(parser) { 4021 var start, key, value; 4022 4023 start = parser.pos; 4024 4025 // allow whitespace between '{' and key 4026 parser.allowWhitespace(); 4027 4028 key = shared_readKey(parser); 4029 if (key === null) { 4030 parser.pos = start; 4031 return null; 4032 } 4033 4034 // allow whitespace between key and ':' 4035 parser.allowWhitespace(); 4036 4037 // next character must be ':' 4038 if (!parser.matchString(":")) { 4039 parser.pos = start; 4040 return null; 4041 } 4042 4043 // allow whitespace between ':' and value 4044 parser.allowWhitespace(); 4045 4046 // next expression must be a, well... expression 4047 value = converters_readExpression(parser); 4048 if (value === null) { 4049 parser.pos = start; 4050 return null; 4051 } 4052 4053 return { 4054 t: KEY_VALUE_PAIR, 4055 k: key, 4056 v: value 4057 }; 4058 } 4059 4060 var objectLiteral_keyValuePairs = readKeyValuePairs; 4061 function readKeyValuePairs(parser) { 4062 var start, pairs, pair, keyValuePairs; 4063 4064 start = parser.pos; 4065 4066 pair = keyValuePair(parser); 4067 if (pair === null) { 4068 return null; 4069 } 4070 4071 pairs = [pair]; 4072 4073 if (parser.matchString(",")) { 4074 keyValuePairs = readKeyValuePairs(parser); 4075 4076 if (!keyValuePairs) { 4077 parser.pos = start; 4078 return null; 4079 } 4080
4081 return pairs.concat(keyValuePairs); 4082 } 4083 4084 return pairs; 4085 } 4086 4087 var readObjectLiteral = function (parser) { 4088 var start, keyValuePairs; 4089 4090 start = parser.pos; 4091 4092 // allow whitespace 4093 parser.allowWhitespace(); 4094 4095 if (!parser.matchString("{")) { 4096 parser.pos = start; 4097 return null; 4098 } 4099 4100 keyValuePairs = objectLiteral_keyValuePairs(parser); 4101 4102 // allow whitespace between final value and '}' 4103 parser.allowWhitespace(); 4104 4105 if (!parser.matchString("}")) { 4106 parser.pos = start; 4107 return null; 4108 } 4109 4110 return { 4111 t: OBJECT_LITERAL, 4112 m: keyValuePairs 4113 }; 4114 }; 4115 4116 var shared_readExpressionList = readExpressionList; 4117 function readExpressionList(parser) { 4118 var start, expressions, expr, next; 4119 4120 start = parser.pos; 4121 4122 parser.allowWhitespace(); 4123 4124 expr = converters_readExpression(parser); 4125 4126 if (expr === null) { 4127 return null; 4128 } 4129 4130 expressions = [expr]; 4131 4132 // allow whitespace between expression and ',' 4133 parser.allowWhitespace(); 4134 4135 if (parser.matchString(",")) { 4136 next = readExpressionList(parser); 4137 if (next === null) { 4138 parser.error(expectedExpression); 4139 } 4140 4141 next.forEach(append); 4142 } 4143 4144 function append(expression) { 4145 expressions.push(expression); 4146 } 4147 4148 return expressions; 4149 } 4150 4151 var readArrayLiteral = function (parser) { 4152 var start, expressionList; 4153 4154 start = parser.pos; 4155 4156 // allow whitespace before '[' 4157 parser.allowWhitespace(); 4158 4159 if (!parser.matchString("[")) { 4160 parser.pos = start; 4161 return null; 4162 } 4163 4164 expressionList = shared_readExpressionList(parser); 4165 4166 if (!parser.matchString("]")) { 4167 parser.pos = start; 4168 return null; 4169 } 4170 4171 return { 4172 t: ARRAY_LITERAL, 4173 m: expressionList 4174 }; 4175 }; 4176 4177 var primary_readLiteral = readLiteral; 4178 function readLiteral(parser) { 4179 return literal_readNumberLiteral(parser) || literal_readBooleanLiteral(parser) || readStringLiteral(parser) || readObjectLiteral(parser) || readArrayLiteral(parser) || readRegexpLiteral(parser); 4180 } 4181 4182 var primary_readReference = readReference; 4183 var prefixPattern = /^(?:~\/|(?:\.\.\/)+|\.\/(?:\.\.\/)*|\.)/, 4184 globals, 4185 keywords; 4186 4187 // if a reference is a browser global, we don't deference it later, so it needs special treatment 4188 globals = /^(?:Array|console|Date|RegExp|decodeURIComponent|decodeURI|encodeURIComponent|encodeURI|isFinite|isNaN|parseFloat|parseInt|JSON|Math|NaN|undefined|null)\b/; 4189 4190 // keywords are not valid references, with the exception of `this` 4191 keywords = /^(?:break|case|catch|continue|debugger|default|delete|do|else|finally|for|function|if|in|instanceof|new|return|switch|throw|try|typeof|var|void|while|with)$/; 4192 4193 var legalReference = /^[a-zA-Z$_0-9]+(?:(?:\.[a-zA-Z$_0-9]+)|(?:\[[0-9]+\]))*/; 4194 var relaxedName = /^[a-zA-Z_$][-a-zA-Z_$0-9]*/; 4195 function readReference(parser) { 4196 var startPos, prefix, name, global, reference, lastDotIndex; 4197 4198 startPos = parser.pos; 4199 4200 name = parser.matchPattern(/^@(?:keypath|index|key)/); 4201 4202 if (!name) { 4203 prefix = parser.matchPattern(prefixPattern) || ""; 4204 name = !prefix && parser.relaxedNames && parser.matchPattern(relaxedName) || parser.matchPattern(legalReference); 4205 4206 if (!name && prefix === ".") { 4207 prefix = ""; 4208 name = "."; 4209 } 4210 } 4211 4212 if (!name) { 4213 return null; 4214 } 4215 4216 // bug out if it's a keyword (exception for ancestor/restricted refs - see https://github.com/ractivejs/ractive/issues/1497) 4217 if (!prefix && !parser.relaxedNames && keywords.test(name)) { 4218 parser.pos = startPos; 4219 return null; 4220 } 4221 4222 // if this is a browser global, stop here 4223 if (!prefix && globals.test(name)) { 4224 global = globals.exec(name)[0]; 4225 parser.pos = startPos + global.length; 4226 4227 return { 4228 t: GLOBAL, 4229 v: global 4230 }; 4231 } 4232 4233 reference = (prefix || "") + normalise(name); 4234 4235 if (parser.matchString("(")) { 4236 // if this is a method invocation (as opposed to a function) we need 4237 // to strip the method name from the reference combo, else the context 4238 // will be wrong 4239 lastDotIndex = reference.lastIndexOf("."); 4240 if (lastDotIndex !== -1) { 4241 reference = reference.substr(0, lastDotIndex); 4242 parser.pos = startPos + reference.length; 4243 } else { 4244 parser.pos -= 1; 4245 } 4246 } 4247 4248 return { 4249 t: REFERENCE, 4250 n: reference.replace(/^this\./, "./").replace(/^this$/, ".") 4251 }; 4252 } 4253 4254 var primary_readBracketedExpression = readBracketedExpression; 4255 function readBracketedExpression(parser) { 4256 var start, expr; 4257 4258 start = parser.pos; 4259 4260 if (!parser.matchString("(")) { 4261 return null; 4262 } 4263 4264 parser.allowWhitespace(); 4265 4266 expr = converters_readExpression(parser); 4267 if (!expr) { 4268 parser.error(expectedExpression); 4269 } 4270 4271 parser.allowWhitespace(); 4272 4273 if (!parser.matchString(")")) { 4274 parser.error(expectedParen); 4275 } 4276 4277 return { 4278 t: BRACKETED, 4279 x: expr 4280 }; 4281 } 4282 4283 var readPrimary = function (parser) { 4284 return primary_readLiteral(parser) || primary_readReference(parser) || primary_readBracketedExpression(parser); 4285 }; 4286 4287 var shared_readRefinement = readRefinement; 4288 function readRefinement(parser) { 4289 var start, name, expr; 4290 4291 start = parser.pos; 4292 4293 parser.allowWhitespace(); 4294 4295 // "." name 4296 if (parser.matchString(".")) { 4297 parser.allowWhitespace(); 4298 4299 if (name = parser.matchPattern(patterns__name)) { 4300 return { 4301 t: REFINEMENT, 4302 n: name 4303 }; 4304 } 4305 4306 parser.error("Expected a property name"); 4307 } 4308 4309 // "[" expression "]" 4310 if (parser.matchString("[")) { 4311 parser.allowWhitespace(); 4312 4313 expr = converters_readExpression(parser); 4314 if (!expr) { 4315 parser.error(expectedExpression); 4316 } 4317 4318 parser.allowWhitespace(); 4319 4320 if (!parser.matchString("]")) { 4321 parser.error("Expected ']'"); 4322 } 4323 4324 return { 4325 t: REFINEMENT, 4326 x: expr 4327 }; 4328 } 4329 4330 return null; 4331 } 4332 4333 var readMemberOrInvocation = function (parser) { 4334 var current, expression, refinement, expressionList; 4335 4336 expression = readPrimary(parser); 4337 4338 if (!expression) { 4339 return null; 4340 } 4341 4342 while (expression) { 4343 current = parser.pos; 4344 4345 if (refinement = shared_readRefinement(parser)) { 4346 expression = { 4347 t: MEMBER, 4348 x: expression, 4349 r: refinement 4350 }; 4351 } else if (parser.matchString("(")) { 4352 parser.allowWhitespace(); 4353 expressionList = shared_readExpressionList(parser); 4354 4355 parser.allowWhitespace(); 4356 4357 if (!parser.matchString(")")) { 4358 parser.error(expectedParen); 4359 } 4360 4361 expression = { 4362 t: INVOCATION, 4363 x: expression 4364 }; 4365 4366 if (expressionList) { 4367 expression.o = expressionList; 4368 } 4369 } else { 4370 break; 4371 } 4372 } 4373 4374 return expression; 4375 }; 4376 4377 var readTypeOf, makePrefixSequenceMatcher; 4378 4379 makePrefixSequenceMatcher = function (symbol, fallthrough) { 4380 return function (parser) { 4381 var expression; 4382 4383 if (expression = fallthrough(parser)) { 4384 return expression; 4385 } 4386 4387 if (!parser.matchString(symbol)) { 4388 return null; 4389 } 4390 4391 parser.allowWhitespace(); 4392 4393 expression = converters_readExpression(parser); 4394 if (!expression) { 4395 parser.error(expectedExpression); 4396 } 4397 4398 return { 4399 s: symbol, 4400 o: expression, 4401 t: PREFIX_OPERATOR 4402 }; 4403 }; 4404 }; 4405 4406 // create all prefix sequence matchers, return readTypeOf 4407 (function () { 4408 var i, len, matcher, prefixOperators, fallthrough; 4409 4410 prefixOperators = "! ~ + - typeof".split(" "); 4411 4412 fallthrough = readMemberOrInvocation; 4413 for (i = 0, len = prefixOperators.length; i < len; i += 1) { 4414 matcher = makePrefixSequenceMatcher(prefixOperators[i], fallthrough); 4415 fallthrough = matcher; 4416 } 4417 4418 // typeof operator is higher precedence than multiplication, so provides the 4419 // fallthrough for the multiplication sequence matcher we're about to create 4420 // (we're skipping void and delete) 4421 readTypeOf = fallthrough; 4422 })(); 4423 4424 var readTypeof = readTypeOf; 4425 4426 var readLogicalOr, makeInfixSequenceMatcher; 4427 4428 makeInfixSequenceMatcher = function (symbol, fallthrough) { 4429 return function (parser) { 4430 var start, left, right; 4431 4432 left = fallthrough(parser); 4433 if (!left) { 4434 return null; 4435 } 4436 4437 // Loop to handle left-recursion in a case like `a * b * c` and produce 4438 // left association, i.e. `(a * b) * c`. The matcher can't call itself 4439 // to parse `left` because that would be infinite regress. 4440 while (true) { 4441 start = parser.pos; 4442 4443 parser.allowWhitespace(); 4444 4445 if (!parser.matchString(symbol)) { 4446 parser.pos = start; 4447 return left; 4448 } 4449 4450 // special case - in operator must not be followed by [a-zA-Z_$0-9] 4451 if (symbol === "in" && /[a-zA-Z_$0-9]/.test(parser.remaining().charAt(0))) { 4452 parser.pos = start; 4453 return left; 4454 } 4455 4456 parser.allowWhitespace(); 4457 4458 // right operand must also consist of only higher-precedence operators 4459 right = fallthrough(parser); 4460 if (!right) { 4461 parser.pos = start; 4462 return left; 4463 } 4464 4465 left = { 4466 t: INFIX_OPERATOR, 4467 s: symbol, 4468 o: [left, right] 4469 }; 4470 4471 // Loop back around. If we don't see another occurrence of the symbol, 4472 // we'll return left. 4473 } 4474 }; 4475 }; 4476 4477 // create all infix sequence matchers, and return readLogicalOr 4478 (function () { 4479 var i, len, matcher, infixOperators, fallthrough; 4480 4481 // All the infix operators on order of precedence (source: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/Operator_Precedence) 4482 // Each sequence matcher will initially fall through to its higher precedence 4483 // neighbour, and only attempt to match if one of the higher precedence operators 4484 // (or, ultimately, a literal, reference, or bracketed expression) already matched 4485 infixOperators = "* / % + - << >> >>> < <= > >= in instanceof == != === !== & ^ | && ||".split(" "); 4486 4487 // A typeof operator is higher precedence than multiplication 4488 fallthrough = readTypeof; 4489 for (i = 0, len = infixOperators.length; i < len; i += 1) { 4490 matcher = makeInfixSequenceMatcher(infixOperators[i], fallthrough); 4491 fallthrough = matcher; 4492 } 4493 4494 // Logical OR is the fallthrough for the conditional matcher 4495 readLogicalOr = fallthrough; 4496 })(); 4497 4498 var expressions_readLogicalOr = readLogicalOr; 4499 4500 // The conditional operator is the lowest precedence operator, so we start here
4501 var readConditional = getConditional; 4502 function getConditional(parser) { 4503 var start, expression, ifTrue, ifFalse; 4504 4505 expression = expressions_readLogicalOr(parser); 4506 if (!expression) { 4507 return null; 4508 } 4509 4510 start = parser.pos; 4511 4512 parser.allowWhitespace(); 4513 4514 if (!parser.matchString("?")) { 4515 parser.pos = start; 4516 return expression; 4517 } 4518 4519 parser.allowWhitespace(); 4520 4521 ifTrue = converters_readExpression(parser); 4522 if (!ifTrue) { 4523 parser.error(expectedExpression); 4524 } 4525 4526 parser.allowWhitespace(); 4527 4528 if (!parser.matchString(":")) { 4529 parser.error("Expected \":\""); 4530 } 4531 4532 parser.allowWhitespace(); 4533 4534 ifFalse = converters_readExpression(parser); 4535 if (!ifFalse) { 4536 parser.error(expectedExpression); 4537 } 4538 4539 return { 4540 t: CONDITIONAL, 4541 o: [expression, ifTrue, ifFalse] 4542 }; 4543 } 4544 4545 var converters_readExpression = readExpression; 4546 function readExpression(parser) { 4547 // The conditional operator is the lowest precedence operator (except yield, 4548 // assignment operators, and commas, none of which are supported), so we 4549 // start there. If it doesn't match, it 'falls through' to progressively 4550 // higher precedence operators, until it eventually matches (or fails to 4551 // match) a 'primary' - a literal or a reference. This way, the abstract syntax 4552 // tree has everything in its proper place, i.e. 2 + 3 * 4 === 14, not 20. 4553 return readConditional(parser); 4554 } 4555 4556 var utils_flattenExpression = flattenExpression; 4557 4558 function flattenExpression(expression) { 4559 var refs; 4560 4561 extractRefs(expression, refs = []); 4562 4563 return { 4564 r: refs, 4565 s: stringify(expression) 4566 }; 4567 4568 function stringify(node) { 4569 switch (node.t) { 4570 case BOOLEAN_LITERAL: 4571 case GLOBAL: 4572 case NUMBER_LITERAL: 4573 case REGEXP_LITERAL: 4574 return node.v; 4575 4576 case STRING_LITERAL: 4577 return JSON.stringify(String(node.v)); 4578 4579 case ARRAY_LITERAL: 4580 return "[" + (node.m ? node.m.map(stringify).join(",") : "") + "]"; 4581 4582 case OBJECT_LITERAL: 4583 return "{" + (node.m ? node.m.map(stringify).join(",") : "") + "}"; 4584 4585 case KEY_VALUE_PAIR: 4586 return node.k + ":" + stringify(node.v); 4587 4588 case PREFIX_OPERATOR: 4589 return (node.s === "typeof" ? "typeof " : node.s) + stringify(node.o); 4590 4591 case INFIX_OPERATOR: 4592 return stringify(node.o[0]) + (node.s.substr(0, 2) === "in" ? " " + node.s + " " : node.s) + stringify(node.o[1]); 4593 4594 case INVOCATION: 4595 return stringify(node.x) + "(" + (node.o ? node.o.map(stringify).join(",") : "") + ")"; 4596 4597 case BRACKETED: 4598 return "(" + stringify(node.x) + ")"; 4599 4600 case MEMBER: 4601 return stringify(node.x) + stringify(node.r); 4602 4603 case REFINEMENT: 4604 return node.n ? "." + node.n : "[" + stringify(node.x) + "]"; 4605 4606 case CONDITIONAL: 4607 return stringify(node.o[0]) + "?" + stringify(node.o[1]) + ":" + stringify(node.o[2]); 4608 4609 case REFERENCE: 4610 return "_" + refs.indexOf(node.n); 4611 4612 default: 4613 throw new Error("Expected legal JavaScript"); 4614 } 4615 } 4616 } 4617 4618 // TODO maybe refactor this? 4619 function extractRefs(node, refs) { 4620 var i, list; 4621 4622 if (node.t === REFERENCE) { 4623 if (refs.indexOf(node.n) === -1) { 4624 refs.unshift(node.n); 4625 } 4626 } 4627 4628 list = node.o || node.m; 4629 if (list) { 4630 if (isObject(list)) { 4631 extractRefs(list, refs); 4632 } else { 4633 i = list.length; 4634 while (i--) { 4635 extractRefs(list[i], refs); 4636 } 4637 } 4638 } 4639 4640 if (node.x) { 4641 extractRefs(node.x, refs); 4642 } 4643 4644 if (node.r) { 4645 extractRefs(node.r, refs); 4646 } 4647 4648 if (node.v) { 4649 extractRefs(node.v, refs); 4650 } 4651 } 4652 4653 var utils_refineExpression = refineExpression; 4654 4655 var arrayMemberPattern = /^[0-9][1-9]*$/; 4656 function refineExpression(expression, mustache) { 4657 var referenceExpression; 4658 4659 if (expression) { 4660 while (expression.t === BRACKETED && expression.x) { 4661 expression = expression.x; 4662 } 4663 4664 // special case - integers should be treated as array members references, 4665 // rather than as expressions in their own right 4666 if (expression.t === REFERENCE) { 4667 mustache.r = expression.n; 4668 } else { 4669 if (expression.t === NUMBER_LITERAL && arrayMemberPattern.test(expression.v)) { 4670 mustache.r = expression.v; 4671 } else if (referenceExpression = getReferenceExpression(expression)) { 4672 mustache.rx = referenceExpression; 4673 } else { 4674 mustache.x = utils_flattenExpression(expression); 4675 } 4676 } 4677 4678 return mustache; 4679 } 4680 } 4681 4682 // TODO refactor this! it's bewildering 4683 function getReferenceExpression(expression) { 4684 var members = [], 4685 refinement; 4686 4687 while (expression.t === MEMBER && expression.r.t === REFINEMENT) { 4688 refinement = expression.r; 4689 4690 if (refinement.x) { 4691 if (refinement.x.t === REFERENCE) { 4692 members.unshift(refinement.x); 4693 } else { 4694 members.unshift(utils_flattenExpression(refinement.x)); 4695 } 4696 } else { 4697 members.unshift(refinement.n); 4698 } 4699 4700 expression = expression.x; 4701 } 4702 4703 if (expression.t !== REFERENCE) { 4704 return null; 4705 } 4706 4707 return { 4708 r: expression.n, 4709 m: members 4710 }; 4711 } 4712 4713 var mustache_readTriple = readTriple; 4714 function readTriple(parser, tag) { 4715 var expression = converters_readExpression(parser), 4716 triple; 4717 4718 if (!expression) { 4719 return null; 4720 } 4721 4722 if (!parser.matchString(tag.close)) { 4723 parser.error("Expected closing delimiter '" + tag.close + "'"); 4724 } 4725 4726 triple = { t: TRIPLE }; 4727 utils_refineExpression(expression, triple); // TODO handle this differently - it's mysterious 4728 4729 return triple; 4730 } 4731 4732 var mustache_readUnescaped = readUnescaped; 4733 function readUnescaped(parser, tag) { 4734 var expression, triple; 4735 4736 if (!parser.matchString("&")) { 4737 return null; 4738 } 4739 4740 parser.allowWhitespace(); 4741 4742 expression = converters_readExpression(parser); 4743 4744 if (!expression) { 4745 return null; 4746 } 4747 4748 if (!parser.matchString(tag.close)) { 4749 parser.error("Expected closing delimiter '" + tag.close + "'"); 4750 } 4751 4752 triple = { t: TRIPLE }; 4753 utils_refineExpression(expression, triple); // TODO handle this differently - it's mysterious 4754 4755 return triple; 4756 } 4757 4758 var mustache_readPartial = readPartial; 4759 function readPartial(parser, tag) { 4760 var start, nameStart, expression, context, partial; 4761 4762 start = parser.pos; 4763 4764 if (!parser.matchString(">")) { 4765 return null; 4766 } 4767 4768 parser.allowWhitespace(); 4769 nameStart = parser.pos; 4770 4771 // Partial names can include hyphens, so we can't use readExpression 4772 // blindly. Instead, we use the `relaxedNames` flag to indicate that 4773 // `foo-bar` should be read as a single name, rather than 'subtract 4774 // bar from foo' 4775 parser.relaxedNames = true; 4776 expression = converters_readExpression(parser); 4777 parser.relaxedNames = false;
4778 4779 parser.allowWhitespace(); 4780 context = converters_readExpression(parser); 4781 parser.allowWhitespace(); 4782 4783 if (!expression) { 4784 return null; 4785 } 4786 4787 partial = { t: PARTIAL }; 4788 utils_refineExpression(expression, partial); // TODO... 4789 4790 parser.allowWhitespace(); 4791 4792 // if we have another expression - e.g. `{{>foo bar}}` - then 4793 // we turn it into `{{#with bar}}{{>foo}}{{/with}}` 4794 if (context) { 4795 partial = { 4796 t: SECTION, 4797 n: SECTION_WITH, 4798 f: [partial] 4799 }; 4800 4801 utils_refineExpression(context, partial); 4802 } 4803 4804 if (!parser.matchString(tag.close)) { 4805 parser.error("Expected closing delimiter '" + tag.close + "'"); 4806 } 4807 4808 return partial; 4809 } 4810 4811 var readMustacheComment = readComment; 4812 function readComment(parser, tag) { 4813 var index; 4814 4815 if (!parser.matchString("!")) { 4816 return null; 4817 } 4818 4819 index = parser.remaining().indexOf(tag.close); 4820 4821 if (index !== -1) { 4822 parser.pos += index + tag.close.length; 4823 return { t: COMMENT }; 4824 } 4825 } 4826 4827 var converters_readExpressionOrReference = readExpressionOrReference; 4828 function readExpressionOrReference(parser, expectedFollowers) { 4829 var start, expression, i; 4830 4831 start = parser.pos; 4832 expression = converters_readExpression(parser); 4833 4834 if (!expression) { 4835 return null; 4836 } 4837 4838 for (i = 0; i < expectedFollowers.length; i += 1) { 4839 if (parser.remaining().substr(0, expectedFollowers[i].length) === expectedFollowers[i]) { 4840 return expression; 4841 } 4842 } 4843 4844 parser.pos = start; 4845 return primary_readReference(parser); 4846 } 4847 4848 var mustache_readInterpolator = readInterpolator; 4849 function readInterpolator(parser, tag) { 4850 var start, expression, interpolator, err; 4851 4852 start = parser.pos; 4853 4854 // TODO would be good for perf if we could do away with the try-catch 4855 try { 4856 expression = converters_readExpressionOrReference(parser, [tag.close]); 4857 } catch (e) { 4858 err = e; 4859 } 4860 4861 if (!expression) { 4862 if (parser.str.charAt(start) === "!") { 4863 // special case - comment 4864 parser.pos = start; 4865 return null; 4866 } 4867 4868 if (err) { 4869 throw err; 4870 } 4871 } 4872 4873 if (!parser.matchString(tag.close)) { 4874 parser.error("Expected closing delimiter '" + tag.close + "' after reference"); 4875 4876 if (!expression) { 4877 // special case - comment 4878 if (parser.nextChar() === "!") { 4879 return null; 4880 } 4881 4882 parser.error("Expected expression or legal reference"); 4883 } 4884 } 4885 4886 interpolator = { t: INTERPOLATOR }; 4887 utils_refineExpression(expression, interpolator); // TODO handle this differently - it's mysterious 4888 4889 return interpolator; 4890 } 4891 4892 var mustache_readYielder = readYielder; 4893 var yieldPattern = /^yield\s*/; 4894 function readYielder(parser, tag) { 4895 var start, name, yielder; 4896 4897 if (!parser.matchPattern(yieldPattern)) { 4898 return null; 4899 } 4900 4901 start = parser.pos; 4902 name = parser.matchPattern(/^[a-zA-Z_$][a-zA-Z_$0-9\-]*/); 4903 4904 parser.allowWhitespace(); 4905 4906 if (!parser.matchString(tag.close)) { 4907 parser.error("expected legal partial name"); 4908 } 4909 4910 yielder = { t: YIELDER }; 4911 4912 if (name) { 4913 yielder.n = name; 4914 } 4915 4916 return yielder; 4917 } 4918 4919 var section_readClosing = readClosing; 4920 function readClosing(parser, tag) { 4921 var start, remaining, index, closing; 4922 4923 start = parser.pos; 4924 4925 if (!parser.matchString(tag.open)) { 4926 return null; 4927 } 4928 4929 parser.allowWhitespace(); 4930 4931 if (!parser.matchString("/")) { 4932 parser.pos = start; 4933 return null; 4934 } 4935 4936 parser.allowWhitespace(); 4937 4938 remaining = parser.remaining(); 4939 index = remaining.indexOf(tag.close); 4940 4941 if (index !== -1) { 4942 closing = { 4943 t: CLOSING, 4944 r: remaining.substr(0, index).split(" ")[0] 4945 }; 4946 4947 parser.pos += index; 4948 4949 if (!parser.matchString(tag.close)) { 4950 parser.error("Expected closing delimiter '" + tag.close + "'"); 4951 } 4952 4953 return closing; 4954 } 4955 4956 parser.pos = start; 4957 return null; 4958 } 4959 4960 var section_readElse = section_readElse__readElse; 4961 var section_readElse__elsePattern = /^\s*else\s*/; 4962 function section_readElse__readElse(parser, tag) { 4963 var start = parser.pos; 4964 4965 if (!parser.matchString(tag.open)) { 4966 return null; 4967 } 4968 4969 if (!parser.matchPattern(section_readElse__elsePattern)) { 4970 parser.pos = start; 4971 return null; 4972 } 4973 4974 if (!parser.matchString(tag.close)) { 4975 parser.error("Expected closing delimiter '" + tag.close + "'"); 4976 } 4977 4978 return { 4979 t: ELSE 4980 }; 4981 } 4982 4983 var readElseIf = readElseIf__readElse; 4984 var readElseIf__elsePattern = /^\s*elseif\s+/; 4985 function readElseIf__readElse(parser, tag) { 4986 var start = parser.pos, 4987 expression; 4988 4989 if (!parser.matchString(tag.open)) { 4990 return null; 4991 } 4992 4993 if (!parser.matchPattern(readElseIf__elsePattern)) { 4994 parser.pos = start; 4995 return null; 4996 } 4997 4998 expression = converters_readExpression(parser); 4999 5000 if (!parser.matchString(tag.close)) { 5001 parser.error("Expected closing delimiter '" + tag.close + "'"); 5002 } 5003 5004 return { 5005 t: ELSEIF, 5006 x: expression 5007 }; 5008 } 5009 5010 var handlebarsBlockCodes = { 5011 each: SECTION_EACH, 5012 "if": SECTION_IF, 5013 "if-with": SECTION_IF_WITH, 5014 "with": SECTION_WITH, 5015 unless: SECTION_UNLESS 5016 }; 5017 5018 var mustache_readSection = readSection; 5019 5020 var indexRefPattern = /^\s*:\s*([a-zA-Z_$][a-zA-Z_$0-9]*)/,
5021 keyIndexRefPattern = /^\s*,\s*([a-zA-Z_$][a-zA-Z_$0-9]*)/, 5022 handlebarsBlockPattern = new RegExp("^(" + Object.keys(handlebarsBlockCodes).join("|") + ")\\b"); 5023 function readSection(parser, tag) { 5024 var start, expression, section, child, children, hasElse, block, unlessBlock, conditions, closed, i, expectedClose; 5025 5026 start = parser.pos; 5027 5028 if (parser.matchString("^")) { 5029 section = { t: SECTION, f: [], n: SECTION_UNLESS }; 5030 } else if (parser.matchString("#")) { 5031 section = { t: SECTION, f: [] }; 5032 5033 if (parser.matchString("partial")) { 5034 parser.pos = start - parser.standardDelimiters[0].length; 5035 parser.error("Partial definitions can only be at the top level of the template, or immediately inside components"); 5036 } 5037 5038 if (block = parser.matchPattern(handlebarsBlockPattern)) { 5039 expectedClose = block; 5040 section.n = handlebarsBlockCodes[block]; 5041 } 5042 } else { 5043 return null; 5044 } 5045 5046 parser.allowWhitespace(); 5047 5048 expression = converters_readExpression(parser); 5049 5050 if (!expression) { 5051 parser.error("Expected expression"); 5052 } 5053 5054 // optional index and key references 5055 if (i = parser.matchPattern(indexRefPattern)) { 5056 var extra = undefined; 5057 5058 if (extra = parser.matchPattern(keyIndexRefPattern)) { 5059 section.i = i + "," + extra; 5060 } else { 5061 section.i = i; 5062 } 5063 } 5064 5065 parser.allowWhitespace(); 5066 5067 if (!parser.matchString(tag.close)) { 5068 parser.error("Expected closing delimiter '" + tag.close + "'"); 5069 } 5070 5071 parser.sectionDepth += 1; 5072 children = section.f; 5073 5074 conditions = []; 5075 5076 do { 5077 if (child = section_readClosing(parser, tag)) { 5078 if (expectedClose && child.r !== expectedClose) { 5079 parser.error("Expected " + tag.open + "/" + expectedClose + "" + tag.close); 5080 } 5081 5082 parser.sectionDepth -= 1; 5083 closed = true; 5084 } else if (child = readElseIf(parser, tag)) { 5085 if (section.n === SECTION_UNLESS) { 5086 parser.error("{{else}} not allowed in {{#unless}}"); 5087 } 5088 5089 if (hasElse) { 5090 parser.error("illegal {{elseif...}} after {{else}}"); 5091 } 5092 5093 if (!unlessBlock) { 5094 unlessBlock = createUnlessBlock(expression, section.n); 5095 } 5096 5097 unlessBlock.f.push({ 5098 t: SECTION, 5099 n: SECTION_IF, 5100 x: utils_flattenExpression(mustache_readSection__combine(conditions.concat(child.x))), 5101 f: children = [] 5102 }); 5103 5104 conditions.push(invert(child.x)); 5105 } else if (child = section_readElse(parser, tag)) { 5106 if (section.n === SECTION_UNLESS) { 5107 parser.error("{{else}} not allowed in {{#unless}}"); 5108 } 5109 5110 if (hasElse) { 5111 parser.error("there can only be one {{else}} block, at the end of a section"); 5112 } 5113 5114 hasElse = true; 5115 5116 // use an unless block if there's no elseif 5117 if (!unlessBlock) { 5118 unlessBlock = createUnlessBlock(expression, section.n); 5119 children = unlessBlock.f; 5120 } else { 5121 unlessBlock.f.push({ 5122 t: SECTION, 5123 n: SECTION_IF, 5124 x: utils_flattenExpression(mustache_readSection__combine(conditions)), 5125 f: children = [] 5126 }); 5127 } 5128 } else { 5129 child = parser.read(READERS); 5130 5131 if (!child) { 5132 break; 5133 } 5134 5135 children.push(child); 5136 } 5137 } while (!closed); 5138 5139 if (unlessBlock) { 5140 // special case - `with` should become `if-with` (TODO is this right? 5141 // seems to me that `with` ought to behave consistently, regardless 5142 // of the presence/absence of `else`. In other words should always 5143 // be `if-with` 5144 if (section.n === SECTION_WITH) { 5145 section.n = SECTION_IF_WITH; 5146 } 5147 5148 section.l = unlessBlock; 5149 } 5150 5151 utils_refineExpression(expression, section); 5152 5153 // TODO if a section is empty it should be discarded. Don't do 5154 // that here though - we need to clean everything up first, as 5155 // it may contain removeable whitespace. As a temporary measure, 5156 // to pass the existing tests, remove empty `f` arrays 5157 if (!section.f.length) { 5158 delete section.f; 5159 } 5160 5161 return section; 5162 } 5163 5164 function createUnlessBlock(expression, sectionType) { 5165 var unlessBlock; 5166 5167 if (sectionType === SECTION_WITH) { 5168 // special case - a `{{#with foo}}` section will render if `foo` is 5169 // truthy, so the `{{else}}` section needs to render if `foo` is falsy, 5170 // rather than adhering to the normal `{{#unless foo}}` logic (which 5171 // treats empty arrays/objects as falsy) 5172 unlessBlock = { 5173 t: SECTION, 5174 n: SECTION_IF, 5175 f: [] 5176 }; 5177
5178 utils_refineExpression(invert(expression), unlessBlock); 5179 } else { 5180 unlessBlock = { 5181 t: SECTION, 5182 n: SECTION_UNLESS, 5183 f: [] 5184 }; 5185 5186 utils_refineExpression(expression, unlessBlock); 5187 } 5188 5189 return unlessBlock; 5190 } 5191 5192 function invert(expression) { 5193 if (expression.t === PREFIX_OPERATOR && expression.s === "!") { 5194 return expression.o; 5195 } 5196 5197 return { 5198 t: PREFIX_OPERATOR, 5199 s: "!", 5200 o: parensIfNecessary(expression) 5201 }; 5202 } 5203 5204 function mustache_readSection__combine(expressions) { 5205 if (expressions.length === 1) { 5206 return expressions[0]; 5207 } 5208 5209 return { 5210 t: INFIX_OPERATOR, 5211 s: "&&", 5212 o: [parensIfNecessary(expressions[0]), parensIfNecessary(mustache_readSection__combine(expressions.slice(1)))] 5213 }; 5214 } 5215 5216 function parensIfNecessary(expression) { 5217 // TODO only wrap if necessary 5218 return { 5219 t: BRACKETED, 5220 x: expression 5221 }; 5222 } 5223 5224 var converters_readHtmlComment = readHtmlComment; 5225 var OPEN_COMMENT = "<!--", 5226 CLOSE_COMMENT = "-->"; 5227 function readHtmlComment(parser) { 5228 var start, content, remaining, endIndex, comment; 5229 5230 start = parser.pos; 5231 5232 if (!parser.matchString(OPEN_COMMENT)) { 5233 return null; 5234 } 5235 5236 remaining = parser.remaining(); 5237 endIndex = remaining.indexOf(CLOSE_COMMENT); 5238 5239 if (endIndex === -1) { 5240 parser.error("Illegal HTML - expected closing comment sequence ('-->')"); 5241 } 5242 5243 content = remaining.substr(0, endIndex); 5244 parser.pos += endIndex + 3; 5245 5246 comment = { 5247 t: COMMENT, 5248 c: content 5249 }; 5250 5251 if (parser.includeLinePositions) { 5252 comment.p = parser.getLinePos(start); 5253 } 5254 5255 return comment; 5256 } 5257 5258 var booleanAttributes, voidElementNames, htmlEntities, controlCharacters, entityPattern, lessThan, greaterThan, amp; 5259 5260 // https://github.com/kangax/html-minifier/issues/63#issuecomment-37763316 5261 booleanAttributes = /^(allowFullscreen|async|autofocus|autoplay|checked|compact|controls|declare|default|defaultChecked|defaultMuted|defaultSelected|defer|disabled|enabled|formNoValidate|hidden|indeterminate|inert|isMap|itemScope|loop|multiple|muted|noHref|noResize|noShade|noValidate|noWrap|open|pauseOnExit|readOnly|required|reversed|scoped|seamless|selected|sortable|translate|trueSpeed|typeMustMatch|visible)$/i; 5262 voidElementNames = /^(?:area|base|br|col|command|doctype|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/i; 5263 5264 htmlEntities = { quot: 34, amp: 38, apos: 39, lt: 60, gt: 62, nbsp: 160, iexcl: 161, cent: 162, pound: 163, curren: 164, yen: 165, brvbar: 166, sect: 167, uml: 168, copy: 169, ordf: 170, laquo: 171, not: 172, shy: 173, reg: 174, macr: 175, deg: 176, plusmn: 177, sup2: 178, sup3: 179, acute: 180, micro: 181, para: 182, middot: 183, cedil: 184, sup1: 185, ordm: 186, raquo: 187, frac14: 188, frac12: 189, frac34: 190, iquest: 191, Agrave: 192, Aacute: 193, Acirc: 194, Atilde: 195, Auml: 196, Aring: 197, AElig: 198, Ccedil: 199, Egrave: 200, Eacute: 201, Ecirc: 202, Euml: 203, Igrave: 204, Iacute: 205, Icirc: 206, Iuml: 207, ETH: 208, Ntilde: 209, Ograve: 210, Oacute: 211, Ocirc: 212, Otilde: 213, Ouml: 214, times: 215, Oslash: 216, Ugrave: 217, Uacute: 218, Ucirc: 219, Uuml: 220, Yacute: 221, THORN: 222, szlig: 223, agrave: 224, aacute: 225, acirc: 226, atilde: 227, auml: 228, aring: 229, aelig: 230, ccedil: 231, egrave: 232, eacute: 233, ecirc: 234, euml: 235, igrave: 236, iacute: 237, icirc: 238, iuml: 239, eth: 240, ntilde: 241, ograve: 242, oacute: 243, ocirc: 244, otilde: 245, ouml: 246, divide: 247, oslash: 248, ugrave: 249, uacute: 250, ucirc: 251, uuml: 252, yacute: 253, thorn: 254, yuml: 255, OElig: 338, oelig: 339, Scaron: 352, scaron: 353, Yuml: 376, fnof: 402, circ: 710, tilde: 732, Alpha: 913, Beta: 914, Gamma: 915, Delta: 916, Epsilon: 917, Zeta: 918, Eta: 919, Theta: 920, Iota: 921, Kappa: 922, Lambda: 923, Mu: 924, Nu: 925, Xi: 926, Omicron: 927, Pi: 928, Rho: 929, Sigma: 931, Tau: 932, Upsilon: 933, Phi: 934, Chi: 935, Psi: 936, Omega: 937, alpha: 945, beta: 946, gamma: 947, delta: 948, epsilon: 949, zeta: 950, eta: 951, theta: 952, iota: 953, kappa: 954, lambda: 955, mu: 956, nu: 957, xi: 958, omicron: 959, pi: 960, rho: 961, sigmaf: 962, sigma: 963, tau: 964, upsilon: 965, phi: 966, chi: 967, psi: 968, omega: 969, thetasym: 977, upsih: 978, piv: 982, ensp: 8194, emsp: 8195, thinsp: 8201, zwnj: 8204, zwj: 8205, lrm: 8206, rlm: 8207, ndash: 8211, mdash: 8212, lsquo: 8216, rsquo: 8217, sbquo: 8218, ldquo: 8220, rdquo: 8221, bdquo: 8222, dagger: 8224, Dagger: 8225, bull: 8226, hellip: 8230, permil: 8240, prime: 8242, Prime: 8243, lsaquo: 8249, rsaquo: 8250, oline: 8254, frasl: 8260, euro: 8364, image: 8465, weierp: 8472, real: 8476, trade: 8482, alefsym: 8501, larr: 8592, uarr: 8593, rarr: 8594, darr: 8595, harr: 8596, crarr: 8629, lArr: 8656, uArr: 8657, rArr: 8658, dArr: 8659, hArr: 8660, forall: 8704, part: 8706, exist: 8707, empty: 8709, nabla: 8711, isin: 8712, notin: 8713, ni: 8715, prod: 8719, sum: 8721, minus: 8722, lowast: 8727, radic: 8730, prop: 8733, infin: 8734, ang: 8736, and: 8743, or: 8744, cap: 8745, cup: 8746, int: 8747, there4
5264: 8756, sim: 8764, cong: 8773, asymp: 8776, ne: 8800, equiv: 8801, le: 8804, ge: 8805, sub: 8834, sup: 8835, nsub: 8836, sube: 8838, supe: 8839, oplus: 8853, otimes: 8855, perp: 8869, sdot: 8901, lceil: 8968, rceil: 8969, lfloor: 8970, rfloor: 8971, lang: 9001, rang: 9002, loz: 9674, spades: 9824, clubs: 9827, hearts: 9829, diams: 9830 }; 5265 controlCharacters = [8364, 129, 8218, 402, 8222, 8230, 8224, 8225, 710, 8240, 352, 8249, 338, 141, 381, 143, 144, 8216, 8217, 8220, 8221, 8226, 8211, 8212, 732, 8482, 353, 8250, 339, 157, 382, 376]; 5266 entityPattern = new RegExp("&(#?(?:x[\\w\\d]+|\\d+|" + Object.keys(htmlEntities).join("|") + "));?", "g"); 5267 5268 function decodeCharacterReferences(html) { 5269 return html.replace(entityPattern, function (match, entity) { 5270 var code; 5271 5272 // Handle named entities 5273 if (entity[0] !== "#") { 5274 code = htmlEntities[entity]; 5275 } else if (entity[1] === "x") { 5276 code = parseInt(entity.substring(2), 16); 5277 } else { 5278 code = parseInt(entity.substring(1), 10); 5279 } 5280 5281 if (!code) { 5282 return match; 5283 } 5284 5285 return String.fromCharCode(validateCode(code)); 5286 }); 5287 } 5288 5289 // some code points are verboten. If we were inserting HTML, the browser would replace the illegal 5290 // code points with alternatives in some cases - since we're bypassing that mechanism, we need 5291 // to replace them ourselves 5292 // 5293 // Source: http://en.wikipedia.org/wiki/Character_encodings_in_HTML#Illegal_characters 5294 function validateCode(code) { 5295 if (!code) { 5296 return 65533; 5297 } 5298 5299 // line feed becomes generic whitespace 5300 if (code === 10) { 5301 return 32; 5302 } 5303 5304 // ASCII range. (Why someone would use HTML entities for ASCII characters I don't know, but...) 5305 if (code < 128) { 5306 return code; 5307 } 5308 5309 // code points 128-159 are dealt with leniently by browsers, but they're incorrect. We need 5310 // to correct the mistake or we'll end up with missing € signs and so on 5311 if (code <= 159) { 5312 return controlCharacters[code - 128]; 5313 } 5314 5315 // basic multilingual plane 5316 if (code < 55296) { 5317 return code; 5318 } 5319 5320 // UTF-16 surrogate halves 5321 if (code <= 57343) { 5322 return 65533; 5323 } 5324 5325 // rest of the basic multilingual plane 5326 if (code <= 65535) { 5327 return code; 5328 } 5329 5330 return 65533; 5331 } 5332 5333 lessThan = /</g; 5334 greaterThan = />/g; 5335 amp = /&/g; 5336 5337 function escapeHtml(str) { 5338 return str.replace(amp, "&").replace(lessThan, "<").replace(greaterThan, ">"); 5339 } 5340 5341 var leadingLinebreak = /^\s*\r?\n/, 5342 trailingLinebreak = /\r?\n\s*$/; 5343 5344 var stripStandalones = function (items) { 5345 var i, current, backOne, backTwo, lastSectionItem; 5346 5347 for (i = 1; i < items.length; i += 1) { 5348 current = items[i]; 5349 backOne = items[i - 1]; 5350 backTwo = items[i - 2]; 5351 5352 // if we're at the end of a [text][comment][text] sequence... 5353 if (isString(current) && isComment(backOne) && isString(backTwo)) { 5354 5355 // ... and the comment is a standalone (i.e. line breaks either side)... 5356 if (trailingLinebreak.test(backTwo) && leadingLinebreak.test(current)) { 5357 5358 // ... then we want to remove the whitespace after the first line break 5359 items[i - 2] = backTwo.replace(trailingLinebreak, "\n"); 5360 5361 // and the leading line break of the second text token 5362 items[i] = current.replace(leadingLinebreak, ""); 5363 } 5364 } 5365 5366 // if the current item is a section, and it is preceded by a linebreak, and 5367 // its first item is a linebreak... 5368 if (isSection(current) && isString(backOne)) { 5369 if (trailingLinebreak.test(backOne) && isString(current.f[0]) && leadingLinebreak.test(current.f[0])) { 5370 items[i - 1] = backOne.replace(trailingLinebreak, "\n"); 5371 current.f[0] = current.f[0].replace(leadingLinebreak, ""); 5372 } 5373 } 5374 5375 // if the last item was a section, and it is followed by a linebreak, and 5376 // its last item is a linebreak... 5377 if (isString(current) && isSection(backOne)) { 5378 lastSectionItem = lastItem(backOne.f); 5379 5380 if (isString(lastSectionItem) && trailingLinebreak.test(lastSectionItem) && leadingLinebreak.test(current)) { 5381 backOne.f[backOne.f.length - 1] = lastSectionItem.replace(trailingLinebreak, "\n"); 5382 items[i] = current.replace(leadingLinebreak, ""); 5383 } 5384 } 5385 } 5386 5387 return items; 5388 }; 5389 5390 function isString(item) { 5391 return typeof item === "string"; 5392 } 5393 5394 function isComment(item) { 5395 return item.t === COMMENT || item.t === DELIMCHANGE; 5396 } 5397 5398 function isSection(item) { 5399 return (item.t === SECTION || item.t === INVERTED) && item.f; 5400 } 5401 5402 var trimWhitespace = function (items, leadingPattern, trailingPattern) { 5403 var item; 5404 5405 if (leadingPattern) { 5406 item = items[0]; 5407 if (typeof item === "string") { 5408 item = item.replace(leadingPattern, ""); 5409 5410 if (!item) { 5411 items.shift(); 5412 } else { 5413 items[0] = item; 5414 } 5415 } 5416 } 5417 5418 if (trailingPattern) { 5419 item = lastItem(items); 5420 if (typeof item === "string") { 5421 item = item.replace(trailingPattern, ""); 5422 5423 if (!item) { 5424 items.pop(); 5425 } else { 5426 items[items.length - 1] = item; 5427 } 5428 } 5429 } 5430 }; 5431 5432 var utils_cleanup = cleanup; 5433 var contiguousWhitespace = /[ \t\f\r\n]+/g; 5434 var preserveWhitespaceElements = /^(?:pre|script|style|textarea)$/i; 5435 var utils_cleanup__leadingWhitespace = /^[ \t\f\r\n]+/; 5436 var trailingWhitespace = /[ \t\f\r\n]+$/; 5437 var leadingNewLine = /^(?:\r\n|\r|\n)/; 5438 var trailingNewLine = /(?:\r\n|\r|\n)$/; 5439 function cleanup(items, stripComments, preserveWhitespace, removeLeadingWhitespace, removeTrailingWhitespace) { 5440 var i, item, previousItem, nextItem, preserveWhitespaceInsideFragment, removeLeadingWhitespaceInsideFragment, removeTrailingWhitespaceInsideFragment, key; 5441 5442 // First pass - remove standalones and comments etc 5443 stripStandalones(items); 5444 5445 i = items.length; 5446 while (i--) { 5447 item = items[i]; 5448 5449 // Remove delimiter changes, unsafe elements etc 5450 if (item.exclude) { 5451 items.splice(i, 1); 5452 } 5453
5454 // Remove comments, unless we want to keep them 5455 else if (stripComments && item.t === COMMENT) { 5456 items.splice(i, 1); 5457 } 5458 } 5459 5460 // If necessary, remove leading and trailing whitespace 5461 trimWhitespace(items, removeLeadingWhitespace ? utils_cleanup__leadingWhitespace : null, removeTrailingWhitespace ? trailingWhitespace : null); 5462 5463 i = items.length; 5464 while (i--) { 5465 item = items[i]; 5466 5467 // Recurse 5468 if (item.f) { 5469 var isPreserveWhitespaceElement = item.t === ELEMENT && preserveWhitespaceElements.test(item.e); 5470 preserveWhitespaceInsideFragment = preserveWhitespace || isPreserveWhitespaceElement; 5471 5472 if (!preserveWhitespace && isPreserveWhitespaceElement) { 5473 trimWhitespace(item.f, leadingNewLine, trailingNewLine); 5474 } 5475 5476 if (!preserveWhitespaceInsideFragment) { 5477 previousItem = items[i - 1]; 5478 nextItem = items[i + 1]; 5479 5480 // if the previous item was a text item with trailing whitespace, 5481 // remove leading whitespace inside the fragment 5482 if (!previousItem || typeof previousItem === "string" && trailingWhitespace.test(previousItem)) { 5483 removeLeadingWhitespaceInsideFragment = true; 5484 } 5485 5486 // and vice versa 5487 if (!nextItem || typeof nextItem === "string" && utils_cleanup__leadingWhitespace.test(nextItem)) { 5488 removeTrailingWhitespaceInsideFragment = true; 5489 } 5490 } 5491 5492 cleanup(item.f, stripComments, preserveWhitespaceInsideFragment, removeLeadingWhitespaceInsideFragment, removeTrailingWhitespaceInsideFragment); 5493 } 5494 5495 // Split if-else blocks into two (an if, and an unless) 5496 if (item.l) { 5497 cleanup(item.l.f, stripComments, preserveWhitespace, removeLeadingWhitespaceInsideFragment, removeTrailingWhitespaceInsideFragment); 5498 5499 items.splice(i + 1, 0, item.l); 5500 delete item.l; // TODO would be nice if there was a way around this 5501 } 5502 5503 // Clean up element attributes 5504 if (item.a) { 5505 for (key in item.a) { 5506 if (item.a.hasOwnProperty(key) && typeof item.a[key] !== "string") { 5507 cleanup(item.a[key], stripComments, preserveWhitespace, removeLeadingWhitespaceInsideFragment, removeTrailingWhitespaceInsideFragment); 5508 } 5509 } 5510 } 5511 5512 // Clean up conditional attributes 5513 if (item.m) { 5514 cleanup(item.m, stripComments, preserveWhitespace, removeLeadingWhitespaceInsideFragment, removeTrailingWhitespaceInsideFragment); 5515 } 5516 5517 // Clean up event handlers 5518 if (item.v) { 5519 for (key in item.v) { 5520 if (item.v.hasOwnProperty(key)) { 5521 // clean up names 5522 if (isArray(item.v[key].n)) {
5523 cleanup(item.v[key].n, stripComments, preserveWhitespace, removeLeadingWhitespaceInsideFragment, removeTrailingWhitespaceInsideFragment); 5524 } 5525 5526 // clean up params 5527 if (isArray(item.v[key].d)) { 5528 cleanup(item.v[key].d, stripComments, preserveWhitespace, removeLeadingWhitespaceInsideFragment, removeTrailingWhitespaceInsideFragment); 5529 } 5530 } 5531 } 5532 } 5533 } 5534 5535 // final pass - fuse text nodes together 5536 i = items.length; 5537 while (i--) { 5538 if (typeof items[i] === "string") { 5539 if (typeof items[i + 1] === "string") { 5540 items[i] = items[i] + items[i + 1]; 5541 items.splice(i + 1, 1); 5542 } 5543 5544 if (!preserveWhitespace) { 5545 items[i] = items[i].replace(contiguousWhitespace, " "); 5546 } 5547 5548 if (items[i] === "") { 5549 items.splice(i, 1); 5550 } 5551 } 5552 } 5553 } 5554 5555 var element_readClosingTag = readClosingTag; 5556 var closingTagPattern = /^([a-zA-Z]{1,}:?[a-zA-Z0-9\-]*)\s*\>/; 5557 function readClosingTag(parser) { 5558 var start, tag; 5559 5560 start = parser.pos; 5561 5562 // are we looking at a closing tag? 5563 if (!parser.matchString("</")) { 5564 return null; 5565 } 5566 5567 if (tag = parser.matchPattern(closingTagPattern)) { 5568 if (parser.inside && tag !== parser.inside) { 5569 parser.pos = start; 5570 return null; 5571 } 5572 5573 return { 5574 t: CLOSING_TAG, 5575 e: tag 5576 }; 5577 } 5578 5579 // We have an illegal closing tag, report it 5580 parser.pos -= 2; 5581 parser.error("Illegal closing tag"); 5582 } 5583 5584 var getLowestIndex = function (haystack, needles) { 5585 var i, index, lowest; 5586 5587 i = needles.length; 5588 while (i--) { 5589 index = haystack.indexOf(needles[i]); 5590 5591 // short circuit 5592 if (!index) { 5593 return 0; 5594 } 5595 5596 if (index === -1) { 5597 continue; 5598 } 5599 5600 if (!lowest || index < lowest) { 5601 lowest = index; 5602 } 5603 } 5604 5605 return lowest || -1; 5606 }; 5607 5608 var element_readAttribute = readAttribute; 5609 5610 var attributeNamePattern = /^[^\s"'>\/=]+/, 5611 unquotedAttributeValueTextPattern = /^[^\s"'=<>`]+/; 5612 function readAttribute(parser) { 5613 var attr, name, value; 5614 5615 parser.allowWhitespace(); 5616 5617 name = parser.matchPattern(attributeNamePattern); 5618 if (!name) { 5619 return null; 5620 } 5621 5622 attr = { name: name }; 5623 5624 value = readAttributeValue(parser); 5625 if (value != null) { 5626 // not null/undefined 5627 attr.value = value; 5628 } 5629 5630 return attr; 5631 } 5632 5633 function readAttributeValue(parser) { 5634 var start, valueStart, startDepth, value; 5635 5636 start = parser.pos; 5637 5638 // next character must be `=`, `/`, `>` or whitespace 5639 if (!/[=\/>\s]/.test(parser.nextChar())) { 5640 parser.error("Expected `=`, `/`, `>` or whitespace"); 5641 } 5642 5643 parser.allowWhitespace(); 5644 5645 if (!parser.matchString("=")) { 5646 parser.pos = start; 5647 return null; 5648 } 5649 5650 parser.allowWhitespace(); 5651 5652 valueStart = parser.pos; 5653 startDepth = parser.sectionDepth; 5654 5655 value = readQuotedAttributeValue(parser, "'") || readQuotedAttributeValue(parser, "\"") || readUnquotedAttributeValue(parser); 5656 5657 if (value === null) { 5658 parser.error("Expected valid attribute value"); 5659 } 5660 5661 if (parser.sectionDepth !== startDepth) { 5662 parser.pos = valueStart; 5663 parser.error("An attribute value must contain as many opening section tags as closing section tags"); 5664 } 5665 5666 if (!value.length) { 5667 return ""; 5668 } 5669 5670 if (value.length === 1 && typeof value[0] === "string") { 5671 return decodeCharacterReferences(value[0]); 5672 } 5673 5674 return value; 5675 } 5676 5677 function readUnquotedAttributeValueToken(parser) { 5678 var start, text, haystack, needles, index; 5679 5680 start = parser.pos; 5681 5682 text = parser.matchPattern(unquotedAttributeValueTextPattern); 5683 5684 if (!text) { 5685 return null; 5686 } 5687 5688 haystack = text; 5689 needles = parser.tags.map(function (t) { 5690 return t.open; 5691 }); // TODO refactor... we do this in readText.js as well 5692 5693 if ((index = getLowestIndex(haystack, needles)) !== -1) { 5694 text = text.substr(0, index); 5695 parser.pos = start + text.length; 5696 } 5697 5698 return text; 5699 } 5700 5701 function readUnquotedAttributeValue(parser) { 5702 var tokens, token; 5703 5704 parser.inAttribute = true; 5705 5706 tokens = []; 5707 5708 token = converters_readMustache(parser) || readUnquotedAttributeValueToken(parser); 5709 while (token !== null) { 5710 tokens.push(token); 5711 token = converters_readMustache(parser) || readUnquotedAttributeValueToken(parser); 5712 } 5713 5714 if (!tokens.length) { 5715 return null; 5716 } 5717 5718 parser.inAttribute = false;
5719 return tokens; 5720 } 5721 5722 function readQuotedAttributeValue(parser, quoteMark) { 5723 var start, tokens, token; 5724 5725 start = parser.pos; 5726 5727 if (!parser.matchString(quoteMark)) { 5728 return null; 5729 } 5730 5731 parser.inAttribute = quoteMark; 5732 5733 tokens = []; 5734 5735 token = converters_readMustache(parser) || readQuotedStringToken(parser, quoteMark); 5736 while (token !== null) { 5737 tokens.push(token); 5738 token = converters_readMustache(parser) || readQuotedStringToken(parser, quoteMark); 5739 } 5740 5741 if (!parser.matchString(quoteMark)) { 5742 parser.pos = start; 5743 return null; 5744 } 5745 5746 parser.inAttribute = false; 5747 5748 return tokens; 5749 } 5750 5751 function readQuotedStringToken(parser, quoteMark) { 5752 var start, index, haystack, needles; 5753 5754 start = parser.pos; 5755 haystack = parser.remaining(); 5756 5757 needles = parser.tags.map(function (t) { 5758 return t.open; 5759 }); // TODO refactor... we do this in readText.js as well 5760 needles.push(quoteMark); 5761 5762 index = getLowestIndex(haystack, needles); 5763 5764 if (index === -1) { 5765 parser.error("Quoted attribute value must have a closing quote"); 5766 } 5767 5768 if (!index) { 5769 return null; 5770 } 5771 5772 parser.pos += index; 5773 return haystack.substr(0, index); 5774 } 5775 5776 var JsonParser, specials, specialsPattern, parseJSON__numberPattern, placeholderPattern, placeholderAtStartPattern, onlyWhitespace; 5777 5778 specials = { 5779 "true": true, 5780 "false": false, 5781 undefined: undefined, 5782 "null": null 5783 }; 5784 5785 specialsPattern = new RegExp("^(?:" + Object.keys(specials).join("|") + ")"); 5786 parseJSON__numberPattern = /^(?:[+-]?)(?:(?:(?:0|[1-9]\d*)?\.\d+)|(?:(?:0|[1-9]\d*)\.)|(?:0|[1-9]\d*))(?:[eE][+-]?\d+)?/; 5787 placeholderPattern = /\$\{([^\}]+)\}/g; 5788 placeholderAtStartPattern = /^\$\{([^\}]+)\}/; 5789 onlyWhitespace = /^\s*$/; 5790 5791 JsonParser = parse_Parser.extend({ 5792 init: function (str, options) { 5793 this.values = options.values; 5794 this.allowWhitespace(); 5795 }, 5796 5797 postProcess: function (result) { 5798 if (result.length !== 1 || !onlyWhitespace.test(this.leftover)) { 5799 return null; 5800 } 5801 5802 return { value: result[0].v }; 5803 }, 5804 5805 converters: [function getPlaceholder(parser) { 5806 var placeholder; 5807 5808 if (!parser.values) { 5809 return null; 5810 } 5811 5812 placeholder = parser.matchPattern(placeholderAtStartPattern); 5813 5814 if (placeholder && parser.values.hasOwnProperty(placeholder)) { 5815 return { v: parser.values[placeholder] }; 5816 } 5817 }, function getSpecial(parser) { 5818 var special; 5819 5820 if (special = parser.matchPattern(specialsPattern)) { 5821 return { v: specials[special] }; 5822 } 5823 }, function getNumber(parser) { 5824 var number; 5825 5826 if (number = parser.matchPattern(parseJSON__numberPattern)) { 5827 return { v: +number }; 5828 } 5829 }, function getString(parser) { 5830 var stringLiteral = readStringLiteral(parser), 5831 values; 5832 5833 if (stringLiteral && (values = parser.values)) { 5834 return { 5835 v: stringLiteral.v.replace(placeholderPattern, function (match, $1) { 5836 return $1 in values ? values[$1] : $1; 5837 }) 5838 }; 5839 } 5840 5841 return stringLiteral; 5842 }, function getObject(parser) { 5843 var result, pair; 5844 5845 if (!parser.matchString("{")) { 5846 return null; 5847 } 5848 5849 result = {}; 5850 5851 parser.allowWhitespace(); 5852 5853 if (parser.matchString("}")) { 5854 return { v: result }; 5855 } 5856 5857 while (pair = getKeyValuePair(parser)) { 5858 result[pair.key] = pair.value; 5859 5860 parser.allowWhitespace(); 5861 5862 if (parser.matchString("}")) { 5863 return { v: result }; 5864 } 5865 5866 if (!parser.matchString(",")) { 5867 return null; 5868 } 5869 } 5870 5871 return null; 5872 }, function getArray(parser) { 5873 var result, valueToken; 5874 5875 if (!parser.matchString("[")) { 5876 return null; 5877 } 5878 5879 result = []; 5880 5881 parser.allowWhitespace(); 5882 5883 if (parser.matchString("]")) { 5884 return { v: result }; 5885 } 5886 5887 while (valueToken = parser.read()) { 5888 result.push(valueToken.v); 5889 5890 parser.allowWhitespace(); 5891 5892 if (parser.matchString("]")) { 5893 return { v: result }; 5894 } 5895 5896 if (!parser.matchString(",")) { 5897 return null; 5898 } 5899 5900 parser.allowWhitespace(); 5901 } 5902 5903 return null; 5904 }] 5905 }); 5906 5907 function getKeyValuePair(parser) { 5908 var key, valueToken, pair; 5909 5910 parser.allowWhitespace(); 5911 5912 key = shared_readKey(parser); 5913 5914 if (!key) { 5915 return null; 5916 } 5917 5918 pair = { key: key }; 5919 5920 parser.allowWhitespace(); 5921 if (!parser.matchString(":")) { 5922 return null; 5923 } 5924 parser.allowWhitespace(); 5925 5926 valueToken = parser.read(); 5927 if (!valueToken) { 5928 return null; 5929 } 5930 5931 pair.value = valueToken.v; 5932 5933 return pair; 5934 } 5935 5936 var parseJSON = function (str, values) { 5937 var parser = new JsonParser(str, { 5938 values: values 5939 }); 5940 5941 return parser.result; 5942 }; 5943 5944 // TODO clean this up, it's shocking 5945 var element_processDirective = processDirective; 5946 var methodCallPattern = /^([a-zA-Z_$][a-zA-Z_$0-9]*)\(/, 5947 methodCallExcessPattern = /\)\s*$/, 5948 ExpressionParser; 5949 5950 ExpressionParser = parse_Parser.extend({ 5951 converters: [converters_readExpression] 5952 }); 5953 function processDirective(tokens, parentParser) { 5954 var result, match, parser, args, token, colonIndex, directiveName, directiveArgs, parsed; 5955 5956 if (typeof tokens === "string") { 5957 if (match = methodCallPattern.exec(tokens)) { 5958 var end = tokens.lastIndexOf(")"); 5959 5960 // check for invalid method calls 5961 if (!methodCallExcessPattern.test(tokens)) { 5962 parentParser.error("Invalid input after method call expression '" + tokens.slice(end + 1) + "'"); 5963 } 5964 5965 result = { m: match[1] }; 5966 args = "[" + tokens.slice(result.m.length + 1, end) + "]"; 5967 5968 parser = new ExpressionParser(args); 5969 result.a = utils_flattenExpression(parser.result[0]); 5970 5971 return result; 5972 } 5973 5974 if (tokens.indexOf(":") === -1) { 5975 return tokens.trim(); 5976 } 5977 5978 tokens = [tokens]; 5979 } 5980 5981 result = {}; 5982 5983 directiveName = []; 5984 directiveArgs = []; 5985 5986 if (tokens) { 5987 while (tokens.length) { 5988 token = tokens.shift(); 5989 5990 if (typeof token === "string") { 5991 colonIndex = token.indexOf(":"); 5992 5993 if (colonIndex === -1) { 5994 directiveName.push(token); 5995 } else { 5996 5997 // is the colon the first character? 5998 if (colonIndex) { 5999 // no 6000 directiveName.push(token.substr(0, colonIndex)); 6001 } 6002 6003 // if there is anything after the colon in this token, treat 6004 // it as the first token of the directiveArgs fragment 6005 if (token.length > colonIndex + 1) { 6006 directiveArgs[0] = token.substring(colonIndex + 1); 6007 } 6008 6009 break; 6010 } 6011 } else { 6012 directiveName.push(token); 6013 } 6014 } 6015 6016 directiveArgs = directiveArgs.concat(tokens); 6017 } 6018 6019 if (!directiveName.length) { 6020 result = ""; 6021 } else if (directiveArgs.length || typeof directiveName !== "string") { 6022 result = { 6023 // TODO is this really necessary? just use the array 6024 n: directiveName.length === 1 && typeof directiveName[0] === "string" ? directiveName[0] : directiveName 6025 }; 6026 6027 if (directiveArgs.length === 1 && typeof directiveArgs[0] === "string") { 6028 parsed = parseJSON("[" + directiveArgs[0] + "]"); 6029 result.a = parsed ? parsed.value : directiveArgs[0].trim(); 6030 } else { 6031 result.d = directiveArgs; 6032 } 6033 } else { 6034 result = directiveName; 6035 } 6036 6037 return result; 6038 } 6039 6040 var tagNamePattern = /^[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/, 6041 validTagNameFollower = /^[\s\n\/>]/, 6042 onPattern = /^on/, 6043 proxyEventPattern = /^on-([a-zA-Z\\*\\.$_][a-zA-Z\\*\\.$_0-9\-]+)$/, 6044 reservedEventNames = /^(?:change|reset|teardown|update|construct|config|init|render|unrender|detach|insert)$/, 6045 directives = { "intro-outro": "t0", intro: "t1", outro: "t2", decorator: "o" }, 6046 exclude = { exclude: true }, 6047 disallowedContents; 6048 6049 // based on http://developers.whatwg.org/syntax.html#syntax-tag-omission 6050 disallowedContents = { 6051 li: ["li"], 6052 dt: ["dt", "dd"], 6053 dd: ["dt", "dd"], 6054 p: "address article aside blockquote div dl fieldset footer form h1 h2 h3 h4 h5 h6 header hgroup hr main menu nav ol p pre section table ul".split(" "), 6055 rt: ["rt", "rp"], 6056 rp: ["rt", "rp"], 6057 optgroup: ["optgroup"], 6058 option: ["option", "optgroup"], 6059 thead: ["tbody", "tfoot"], 6060 tbody: ["tbody", "tfoot"], 6061 tfoot: ["tbody"], 6062 tr: ["tr", "tbody"], 6063 td: ["td", "th", "tr"], 6064 th: ["td", "th", "tr"] 6065 }; 6066 6067 var converters_readElement = readElement; 6068 6069 function readElement(parser) { 6070 var start, element, directiveName, match, addProxyEvent, attribute, directive, selfClosing, children, partials, hasPartials, child, closed, pos, remaining, closingTag; 6071 6072 start = parser.pos; 6073 6074 if (parser.inside || parser.inAttribute) { 6075 return null; 6076 } 6077 6078 if (!parser.matchString("<")) { 6079 return null; 6080 } 6081 6082 // if this is a closing tag, abort straight away 6083 if (parser.nextChar() === "/") { 6084 return null; 6085 } 6086 6087 element = {}; 6088 if (parser.includeLinePositions) { 6089 element.p = parser.getLinePos(start); 6090 } 6091 6092 if (parser.matchString("!")) { 6093 element.t = DOCTYPE; 6094 if (!parser.matchPattern(/^doctype/i)) { 6095 parser.error("Expected DOCTYPE declaration"); 6096 } 6097 6098 element.a = parser.matchPattern(/^(.+?)>/); 6099 return element; 6100 } 6101 6102 element.t = ELEMENT; 6103 6104 // element name 6105 element.e = parser.matchPattern(tagNamePattern); 6106 if (!element.e) { 6107 return null; 6108 } 6109 6110 // next character must be whitespace, closing solidus or '>' 6111 if (!validTagNameFollower.test(parser.nextChar())) { 6112 parser.error("Illegal tag name"); 6113 } 6114 6115 addProxyEvent = function (name, directive) { 6116 var directiveName = directive.n || directive; 6117 6118 if (reservedEventNames.test(directiveName)) { 6119 parser.pos -= directiveName.length; 6120 parser.error("Cannot use reserved event names (change, reset, teardown, update, construct, config, init, render, unrender, detach, insert)"); 6121 } 6122 6123 element.v[name] = directive; 6124 }; 6125 6126 parser.allowWhitespace(); 6127 6128 // directives and attributes 6129 while (attribute = converters_readMustache(parser) || element_readAttribute(parser)) { 6130 // regular attributes 6131 if (attribute.name) { 6132 // intro, outro, decorator 6133 if (directiveName = directives[attribute.name]) { 6134 element[directiveName] = element_processDirective(attribute.value, parser); 6135 } 6136 6137 // on-click etc 6138 else if (match = proxyEventPattern.exec(attribute.name)) { 6139 if (!element.v) element.v = {}; 6140 directive = element_processDirective(attribute.value, parser); 6141 addProxyEvent(match[1], directive); 6142 } else { 6143 if (!parser.sanitizeEventAttributes || !onPattern.test(attribute.name)) { 6144 if (!element.a) element.a = {}; 6145 element.a[attribute.name] = attribute.value || (attribute.value === "" ? "" : 0); 6146 } 6147 } 6148 } 6149 6150 // {{#if foo}}class='foo'{{/if}} 6151 else { 6152 if (!element.m) element.m = []; 6153 element.m.push(attribute); 6154 } 6155 6156 parser.allowWhitespace(); 6157 } 6158 6159 // allow whitespace before closing solidus 6160 parser.allowWhitespace(); 6161 6162 // self-closing solidus? 6163 if (parser.matchString("/")) { 6164 selfClosing = true; 6165 } 6166 6167 // closing angle bracket 6168 if (!parser.matchString(">")) { 6169 return null; 6170 } 6171 6172 var lowerCaseName = element.e.toLowerCase(); 6173 var preserveWhitespace = parser.preserveWhitespace; 6174 6175 if (!selfClosing && !voidElementNames.test(element.e)) { 6176 parser.elementStack.push(lowerCaseName); 6177 6178 // Special case - if we open a script element, further tags should 6179 // be ignored unless they're a closing script element 6180 if (lowerCaseName === "script" || lowerCaseName === "style") { 6181 parser.inside = lowerCaseName; 6182 } 6183 6184 children = []; 6185 partials = create(null); 6186 6187 do { 6188 pos = parser.pos; 6189 remaining = parser.remaining(); 6190 6191 // if for example we're in an <li> element, and we see another 6192 // <li> tag, close the first so they become siblings 6193 if (!canContain(lowerCaseName, remaining)) { 6194 closed = true; 6195 } 6196 6197 // closing tag 6198 else if (closingTag = element_readClosingTag(parser)) { 6199 closed = true; 6200 6201 var closingTagName = closingTag.e.toLowerCase(); 6202 6203 // if this *isn't* the closing tag for the current element... 6204 if (closingTagName !== lowerCaseName) { 6205 // rewind parser 6206 parser.pos = pos; 6207 6208 // if it doesn't close a parent tag, error 6209 if (! ~parser.elementStack.indexOf(closingTagName)) { 6210 var errorMessage = "Unexpected closing tag"; 6211 6212 // add additional help for void elements, since component names 6213 // might clash with them 6214 if (voidElementNames.test(closingTagName)) {
6215 errorMessage += " (<" + closingTagName + "> is a void element - it cannot contain children)"; 6216 } 6217 6218 parser.error(errorMessage); 6219 } 6220 } 6221 } 6222 6223 // implicit close by closing section tag. TODO clean this up 6224 else if (child = section_readClosing(parser, { open: parser.standardDelimiters[0], close: parser.standardDelimiters[1] })) { 6225 closed = true; 6226 parser.pos = pos; 6227 } else { 6228 if (child = parser.read(PARTIAL_READERS)) { 6229 if (partials[child.n]) { 6230 parser.pos = pos; 6231 parser.error("Duplicate partial definition"); 6232 } 6233 6234 utils_cleanup(child.f, parser.stripComments, preserveWhitespace, !preserveWhitespace, !preserveWhitespace); 6235 6236 partials[child.n] = child.f; 6237 hasPartials = true; 6238 } else { 6239 if (child = parser.read(READERS)) { 6240 children.push(child); 6241 } else { 6242 closed = true; 6243 } 6244 } 6245 } 6246 } while (!closed); 6247 6248 if (children.length) { 6249 element.f = children; 6250 } 6251 6252 if (hasPartials) { 6253 element.p = partials; 6254 } 6255 6256 parser.elementStack.pop(); 6257 } 6258 6259 parser.inside = null; 6260 6261 if (parser.sanitizeElements && parser.sanitizeElements.indexOf(lowerCaseName) !== -1) { 6262 return exclude; 6263 } 6264 6265 return element; 6266 } 6267 6268 function canContain(name, remaining) { 6269 var match, disallowed; 6270 6271 match = /^<([a-zA-Z][a-zA-Z0-9]*)/.exec(remaining); 6272 disallowed = disallowedContents[name]; 6273 6274 if (!match || !disallowed) { 6275 return true; 6276 } 6277 6278 return ! ~disallowed.indexOf(match[1].toLowerCase()); 6279 } 6280 6281 var converters_readText = readText; 6282 function readText(parser) { 6283 var index, remaining, disallowed, barrier; 6284 6285 remaining = parser.remaining(); 6286 6287 barrier = parser.inside ? "</" + parser.inside : "<"; 6288 6289 if (parser.inside && !parser.interpolate[parser.inside]) { 6290 index = remaining.indexOf(barrier); 6291 } else { 6292 disallowed = parser.tags.map(function (t) { 6293 return t.open; 6294 }); 6295 disallowed = disallowed.concat(parser.tags.map(function (t) { 6296 return "\\" + t.open; 6297 })); 6298 6299 // http://developers.whatwg.org/syntax.html#syntax-attributes 6300 if (parser.inAttribute === true) { 6301 // we're inside an unquoted attribute value 6302 disallowed.push("\"", "'", "=", "<", ">", "`"); 6303 } else if (parser.inAttribute) { 6304 // quoted attribute value 6305 disallowed.push(parser.inAttribute); 6306 } else { 6307 disallowed.push(barrier); 6308 } 6309 6310 index = getLowestIndex(remaining, disallowed); 6311 } 6312 6313 if (!index) { 6314 return null; 6315 } 6316 6317 if (index === -1) { 6318 index = remaining.length; 6319 } 6320 6321 parser.pos += index; 6322 6323 return parser.inside ? remaining.substr(0, index) : decodeCharacterReferences(remaining.substr(0, index)); 6324 } 6325 6326 var utils_escapeRegExp = escapeRegExp; 6327 var utils_escapeRegExp__pattern = /[-/\\^$*+?.()|[\]{}]/g; 6328 function escapeRegExp(str) { 6329 return str.replace(utils_escapeRegExp__pattern, "\\$&"); 6330 } 6331 6332 var converters_readPartialDefinitionComment = readPartialDefinitionComment; 6333 6334 var startPattern = /^<!--\s*/, 6335 namePattern = /s*>\s*([a-zA-Z_$][-a-zA-Z_$0-9]*)\s*/, 6336 finishPattern = /\s*-->/, 6337 child; 6338 6339 function readPartialDefinitionComment(parser) { 6340 var firstPos = parser.pos, 6341 open = parser.standardDelimiters[0], 6342 close = parser.standardDelimiters[1], 6343 content = undefined, 6344 closed = undefined; 6345 6346 if (!parser.matchPattern(startPattern) || !parser.matchString(open)) { 6347 parser.pos = firstPos; 6348 return null; 6349 } 6350 6351 var name = parser.matchPattern(namePattern); 6352 6353 warnOnceIfDebug("Inline partial comments are deprecated.\nUse this...\n {{#partial " + name + "}} ... {{/partial}}\n\n...instead of this:\n <!-- {{>" + name + "}} --> ... <!-- {{/" + name + "}} -->'"); 6354 6355 // make sure the rest of the comment is in the correct place 6356 if (!parser.matchString(close) || !parser.matchPattern(finishPattern)) { 6357 parser.pos = firstPos; 6358 return null; 6359 } 6360 6361 content = []; 6362 6363 var endPattern = new RegExp("^<!--\\s*" + utils_escapeRegExp(open) + "\\s*\\/\\s*" + name + "\\s*" + utils_escapeRegExp(close) + "\\s*-->"); 6364 6365 do { 6366 if (parser.matchPattern(endPattern)) { 6367 closed = true; 6368 } else { 6369 child = parser.read(READERS); 6370 if (!child) { 6371 parser.error("expected closing comment ('<!-- " + open + "/" + name + "" + close + " -->')"); 6372 } 6373 6374 content.push(child); 6375 } 6376 } while (!closed); 6377 6378 return { 6379 t: INLINE_PARTIAL, 6380 f: content, 6381 n: name 6382 }; 6383 } 6384 6385 var converters_readPartialDefinitionSection = readPartialDefinitionSection; 6386 var partialDefinitionSectionPattern = /^#\s*partial\s+/; 6387 function readPartialDefinitionSection(parser) { 6388 var start, name, content, child, closed; 6389 6390 start = parser.pos; 6391 6392 var delimiters = parser.standardDelimiters; 6393 6394 if (!parser.matchString(delimiters[0])) { 6395 return null; 6396 } 6397 6398 if (!parser.matchPattern(partialDefinitionSectionPattern)) { 6399 parser.pos = start; 6400 return null; 6401 } 6402 6403 name = parser.matchPattern(/^[a-zA-Z_$][a-zA-Z_$0-9\-]*/); 6404 6405 if (!name) { 6406 parser.error("expected legal partial name"); 6407 } 6408 6409 if (!parser.matchString(delimiters[1])) { 6410 parser.error("Expected closing delimiter '" + delimiters[1] + "'"); 6411 } 6412 6413 content = []; 6414 6415 do { 6416 // TODO clean this up 6417 if (child = section_readClosing(parser, { open: parser.standardDelimiters[0], close: parser.standardDelimiters[1] })) { 6418 if (!child.r === "partial") { 6419 parser.error("Expected " + delimiters[0] + "/partial" + delimiters[1]); 6420 } 6421 6422 closed = true; 6423 } else { 6424 child = parser.read(READERS); 6425 6426 if (!child) { 6427 parser.error("Expected " + delimiters[0] + "/partial" + delimiters[1]); 6428 } 6429 6430 content.push(child); 6431 } 6432 } while (!closed); 6433 6434 return { 6435 t: INLINE_PARTIAL, 6436 n: name, 6437 f: content 6438 }; 6439 } 6440 6441 var converters_readTemplate = readTemplate; 6442 function readTemplate(parser) { 6443 var fragment = []; 6444 var partials = create(null); 6445 var hasPartials = false;
6446 6447 var preserveWhitespace = parser.preserveWhitespace; 6448 6449 while (parser.pos < parser.str.length) { 6450 var pos = parser.pos, 6451 item = undefined, 6452 partial = undefined; 6453 6454 if (partial = parser.read(PARTIAL_READERS)) { 6455 if (partials[partial.n]) { 6456 parser.pos = pos; 6457 parser.error("Duplicated partial definition"); 6458 } 6459 6460 utils_cleanup(partial.f, parser.stripComments, preserveWhitespace, !preserveWhitespace, !preserveWhitespace); 6461 6462 partials[partial.n] = partial.f; 6463 hasPartials = true; 6464 } else if (item = parser.read(READERS)) { 6465 fragment.push(item); 6466 } else { 6467 parser.error("Unexpected template content"); 6468 } 6469 } 6470 6471 var result = { 6472 v: TEMPLATE_VERSION, 6473 t: fragment 6474 }; 6475 6476 if (hasPartials) { 6477 result.p = partials; 6478 } 6479 6480 return result; 6481 } 6482 6483 var _parse = parse; 6484 6485 var STANDARD_READERS = [mustache_readPartial, mustache_readUnescaped, mustache_readSection, mustache_readYielder, mustache_readInterpolator, readMustacheComment]; 6486 var TRIPLE_READERS = [mustache_readTriple]; 6487 var STATIC_READERS = [mustache_readUnescaped, mustache_readSection, mustache_readInterpolator]; // TODO does it make sense to have a static section? 6488 6489 var StandardParser = undefined; 6490 function parse(template, options) { 6491 return new StandardParser(template, options || {}).result; 6492 } 6493 6494 var READERS = [converters_readMustache, converters_readHtmlComment, converters_readElement, converters_readText]; 6495 var PARTIAL_READERS = [converters_readPartialDefinitionComment, converters_readPartialDefinitionSection]; 6496 6497 StandardParser = parse_Parser.extend({ 6498 init: function (str, options) { 6499 var tripleDelimiters = options.tripleDelimiters || ["{{{", "}}}"], 6500 staticDelimiters = options.staticDelimiters || ["[[", "]]"], 6501 staticTripleDelimiters = options.staticTripleDelimiters || ["[[[", "]]]"]; 6502 6503 this.standardDelimiters = options.delimiters || ["{{", "}}"]; 6504 6505 this.tags = [{ isStatic: false, isTriple: false, open: this.standardDelimiters[0], close: this.standardDelimiters[1], readers: STANDARD_READERS }, { isStatic: false, isTriple: true, open: tripleDelimiters[0], close: tripleDelimiters[1], readers: TRIPLE_READERS }, { isStatic: true, isTriple: false, open: staticDelimiters[0], close: staticDelimiters[1], readers: STATIC_READERS }, { isStatic: true, isTriple: true, open: staticTripleDelimiters[0], close: staticTripleDelimiters[1], readers: TRIPLE_READERS }]; 6506 6507 this.sortMustacheTags(); 6508 6509 this.sectionDepth = 0; 6510 this.elementStack = []; 6511 6512 this.interpolate = { 6513 script: !options.interpolate || options.interpolate.script !== false, 6514 style: !options.interpolate || options.interpolate.style !== false 6515 }; 6516 6517 if (options.sanitize === true) { 6518 options.sanitize = { 6519 // blacklist from https://code.google.com/p/google-caja/source/browse/trunk/src/com/google/caja/lang/html/html4-elements-whitelist.json 6520 elements: "applet base basefont body frame frameset head html isindex link meta noframes noscript object param script style title".split(" "), 6521 eventAttributes: true 6522 }; 6523 } 6524 6525 this.stripComments = options.stripComments !== false; 6526 this.preserveWhitespace = options.preserveWhitespace; 6527 this.sanitizeElements = options.sanitize && options.sanitize.elements; 6528 this.sanitizeEventAttributes = options.sanitize && options.sanitize.eventAttributes; 6529 this.includeLinePositions = options.includeLinePositions; 6530 }, 6531 6532 postProcess: function (result) { 6533 // special case - empty string 6534 if (!result.length) { 6535 return { t: [], v: TEMPLATE_VERSION }; 6536 } 6537 6538 if (this.sectionDepth > 0) { 6539 this.error("A section was left open"); 6540 } 6541 6542 utils_cleanup(result[0].t, this.stripComments, this.preserveWhitespace, !this.preserveWhitespace, !this.preserveWhitespace); 6543 6544 return result[0]; 6545 }, 6546 6547 converters: [converters_readTemplate], 6548 6549 sortMustacheTags: function () { 6550 // Sort in order of descending opening delimiter length (longer first), 6551 // to protect against opening delimiters being substrings of each other 6552 this.tags.sort(function (a, b) { 6553 return b.open.length - a.open.length; 6554 }); 6555 } 6556 }); 6557
6558 var parseOptions = ["preserveWhitespace", "sanitize", "stripComments", "delimiters", "tripleDelimiters", "interpolate"]; 6559 6560 var parser = { 6561 fromId: fromId, isHashedId: isHashedId, isParsed: isParsed, getParseOptions: getParseOptions, createHelper: template_parser__createHelper, 6562 parse: doParse 6563 }; 6564 6565 function template_parser__createHelper(parseOptions) { 6566 var helper = create(parser); 6567 helper.parse = function (template, options) { 6568 return doParse(template, options || parseOptions); 6569 }; 6570 return helper; 6571 } 6572 6573 function doParse(template, parseOptions) { 6574 if (!_parse) { 6575 throw new Error("Missing Ractive.parse - cannot parse template. Either preparse or use the version that includes the parser"); 6576 } 6577 6578 return _parse(template, parseOptions || this.options); 6579 } 6580 6581 function fromId(id, options) { 6582 var template; 6583 6584 if (!isClient) { 6585 if (options && options.noThrow) { 6586 return; 6587 } 6588 throw new Error("Cannot retrieve template #" + id + " as Ractive is not running in a browser."); 6589 } 6590 6591 if (isHashedId(id)) { 6592 id = id.substring(1); 6593 } 6594 6595 if (!(template = document.getElementById(id))) { 6596 if (options && options.noThrow) { 6597 return; 6598 } 6599 throw new Error("Could not find template element with id #" + id); 6600 } 6601 6602 if (template.tagName.toUpperCase() !== "SCRIPT") { 6603 if (options && options.noThrow) { 6604 return; 6605 } 6606 throw new Error("Template element with id #" + id + ", must be a <script> element"); 6607 } 6608 6609 return "textContent" in template ? template.textContent : template.innerHTML; 6610 } 6611 6612 function isHashedId(id) { 6613 return id && id[0] === "#"; 6614 } 6615 6616 function isParsed(template) { 6617 return !(typeof template === "string"); 6618 } 6619 6620 function getParseOptions(ractive) { 6621 // Could be Ractive or a Component 6622 if (ractive.defaults) { 6623 ractive = ractive.defaults; 6624 } 6625 6626 return parseOptions.reduce(function (val, key) { 6627 val[key] = ractive[key]; 6628 return val; 6629 }, {}); 6630 } 6631 6632 var template_parser = parser; 6633 6634 var templateConfigurator = { 6635 name: "template", 6636 6637 extend: function extend(Parent, proto, options) { 6638 var template; 6639 6640 // only assign if exists 6641 if ("template" in options) { 6642 template = options.template; 6643 6644 if (typeof template === "function") { 6645 proto.template = template; 6646 } else { 6647 proto.template = parseIfString(template, proto); 6648 } 6649 } 6650 }, 6651 6652 init: function init(Parent, ractive, options) { 6653 var template, fn; 6654 6655 // TODO because of prototypal inheritance, we might just be able to use 6656 // ractive.template, and not bother passing through the Parent object. 6657 // At present that breaks the test mocks' expectations 6658 template = "template" in options ? options.template : Parent.prototype.template; 6659 6660 if (typeof template === "function") { 6661 fn = template; 6662 template = getDynamicTemplate(ractive, fn); 6663 6664 ractive._config.template = { 6665 fn: fn, 6666 result: template 6667 }; 6668 } 6669 6670 template = parseIfString(template, ractive); 6671 6672 // TODO the naming of this is confusing - ractive.template refers to [...], 6673 // but Component.prototype.template refers to {v:1,t:[],p:[]}... 6674 // it's unnecessary, because the developer never needs to access 6675 // ractive.template 6676 ractive.template = template.t; 6677 6678 if (template.p) { 6679 extendPartials(ractive.partials, template.p); 6680 } 6681 }, 6682 6683 reset: function (ractive) { 6684 var result = resetValue(ractive), 6685 parsed; 6686 6687 if (result) { 6688 parsed = parseIfString(result, ractive); 6689 6690 ractive.template = parsed.t; 6691 extendPartials(ractive.partials, parsed.p, true); 6692 6693 return true; 6694 } 6695 } 6696 }; 6697 6698 function resetValue(ractive) { 6699 var initial = ractive._config.template, 6700 result; 6701 6702 // If this isn't a dynamic template, there's nothing to do 6703 if (!initial || !initial.fn) { 6704 return; 6705 } 6706 6707 result = getDynamicTemplate(ractive, initial.fn); 6708 6709 // TODO deep equality check to prevent unnecessary re-rendering 6710 // in the case of already-parsed templates 6711 if (result !== initial.result) { 6712 initial.result = result; 6713 result = parseIfString(result, ractive); 6714 return result; 6715 } 6716 } 6717 6718 function getDynamicTemplate(ractive, fn) { 6719 var helper = template_template__createHelper(template_parser.getParseOptions(ractive));
6720 return fn.call(ractive, helper); 6721 } 6722 6723 function template_template__createHelper(parseOptions) { 6724 var helper = create(template_parser); 6725 helper.parse = function (template, options) { 6726 return template_parser.parse(template, options || parseOptions); 6727 }; 6728 return helper; 6729 } 6730 6731 function parseIfString(template, ractive) { 6732 if (typeof template === "string") { 6733 // ID of an element containing the template? 6734 if (template[0] === "#") { 6735 template = template_parser.fromId(template); 6736 } 6737 6738 template = _parse(template, template_parser.getParseOptions(ractive)); 6739 } 6740 6741 // Check that the template even exists 6742 else if (template == undefined) { 6743 throw new Error("The template cannot be " + template + "."); 6744 } 6745 6746 // Check the parsed template has a version at all 6747 else if (typeof template.v !== "number") { 6748 throw new Error("The template parser was passed a non-string template, but the template doesn't have a version. Make sure you're passing in the template you think you are."); 6749 } 6750 6751 // Check we're using the correct version 6752 else if (template.v !== TEMPLATE_VERSION) { 6753 throw new Error("Mismatched template version (expected " + TEMPLATE_VERSION + ", got " + template.v + ") Please ensure you are using the latest version of Ractive.js in your build process as well as in your app"); 6754 } 6755 6756 return template; 6757 } 6758 6759 function extendPartials(existingPartials, newPartials, overwrite) { 6760 if (!newPartials) return; 6761 6762 // TODO there's an ambiguity here - we need to overwrite in the `reset()` 6763 // case, but not initially... 6764 6765 for (var key in newPartials) { 6766 if (overwrite || !existingPartials.hasOwnProperty(key)) { 6767 existingPartials[key] = newPartials[key]; 6768 } 6769 } 6770 } 6771 6772 var template_template = templateConfigurator; 6773 6774 var config_registries__registryNames, Registry, registries; 6775 6776 config_registries__registryNames = ["adaptors", "components", "computed", "decorators", "easing", "events", "interpolators", "partials", "transitions"]; 6777 6778 Registry = function (name, useDefaults) { 6779 this.name = name; 6780 this.useDefaults = useDefaults; 6781 }; 6782 6783 Registry.prototype = { 6784 constructor: Registry, 6785 6786 extend: function (Parent, proto, options) { 6787 this.configure(this.useDefaults ? Parent.defaults : Parent, this.useDefaults ? proto : proto.constructor, options); 6788 }, 6789 6790 init: function () {}, 6791 6792 configure: function (Parent, target, options) { 6793 var name = this.name, 6794 option = options[name], 6795 registry; 6796 6797 registry = create(Parent[name]); 6798 6799 for (var key in option) { 6800 registry[key] = option[key]; 6801 } 6802 6803 target[name] = registry; 6804 }, 6805 6806 reset: function (ractive) { 6807 var registry = ractive[this.name]; 6808 var changed = false; 6809 Object.keys(registry).forEach(function (key) { 6810 var item = registry[key]; 6811 if (item._fn) { 6812 if (item._fn.isOwner) { 6813 registry[key] = item._fn; 6814 } else { 6815 delete registry[key]; 6816 } 6817 changed = true; 6818 } 6819 }); 6820 return changed; 6821 } 6822 }; 6823 6824 registries = config_registries__registryNames.map(function (name) { 6825 return new Registry(name, name === "computed"); 6826 }); 6827 6828 var config_registries = registries; 6829 6830 /*this.configure( 6831 this.useDefaults ? Parent.defaults : Parent, 6832 ractive, 6833 options );*/ 6834 6835 var wrapPrototype = wrap; 6836 6837 function wrap(parent, name, method) { 6838 if (!/_super/.test(method)) { 6839 return method; 6840 } 6841 6842 var wrapper = function wrapSuper() { 6843 var superMethod = getSuperMethod(wrapper._parent, name), 6844 hasSuper = ("_super" in this), 6845 oldSuper = this._super, 6846 result; 6847 6848 this._super = superMethod; 6849 6850 result = method.apply(this, arguments); 6851 6852 if (hasSuper) { 6853 this._super = oldSuper; 6854 } else { 6855 delete this._super; 6856 } 6857 6858 return result; 6859 }; 6860 6861 wrapper._parent = parent; 6862 wrapper._method = method; 6863 6864 return wrapper; 6865 } 6866 6867 function getSuperMethod(parent, name) { 6868 var value, method; 6869 6870 if (name in parent) { 6871 value = parent[name]; 6872 6873 if (typeof value === "function") { 6874 method = value; 6875 } else { 6876 method = function returnValue() { 6877 return value; 6878 }; 6879 } 6880 } else { 6881 method = noop; 6882 } 6883 6884 return method; 6885 } 6886 6887 var config_deprecate = deprecate;
6888 function getMessage(deprecated, correct, isError) { 6889 return "options." + deprecated + " has been deprecated in favour of options." + correct + "." + (isError ? " You cannot specify both options, please use options." + correct + "." : ""); 6890 } 6891 6892 function deprecateOption(options, deprecatedOption, correct) { 6893 if (deprecatedOption in options) { 6894 if (!(correct in options)) { 6895 warnIfDebug(getMessage(deprecatedOption, correct)); 6896 options[correct] = options[deprecatedOption]; 6897 } else { 6898 throw new Error(getMessage(deprecatedOption, correct, true)); 6899 } 6900 } 6901 } 6902 function deprecate(options) { 6903 deprecateOption(options, "beforeInit", "onconstruct"); 6904 deprecateOption(options, "init", "onrender"); 6905 deprecateOption(options, "complete", "oncomplete"); 6906 deprecateOption(options, "eventDefinitions", "events"); 6907 6908 // Using extend with Component instead of options, 6909 // like Human.extend( Spider ) means adaptors as a registry 6910 // gets copied to options. So we have to check if actually an array 6911 if (isArray(options.adaptors)) { 6912 deprecateOption(options, "adaptors", "adapt"); 6913 } 6914 } 6915 6916 var config, order, defaultKeys, custom, isBlacklisted, isStandardKey; 6917 6918 custom = { 6919 adapt: custom_adapt, 6920 css: css_css, 6921 data: custom_data, 6922 template: template_template 6923 }; 6924 6925 defaultKeys = Object.keys(config_defaults); 6926 6927 isStandardKey = makeObj(defaultKeys.filter(function (key) { 6928 return !custom[key]; 6929 })); 6930 6931 // blacklisted keys that we don't double extend 6932 isBlacklisted = makeObj(defaultKeys.concat(config_registries.map(function (r) { 6933 return r.name; 6934 }))); 6935 6936 order = [].concat(defaultKeys.filter(function (key) { 6937 return !config_registries[key] && !custom[key]; 6938 }), config_registries, custom.data, custom.template, custom.css); 6939 6940 config = { 6941 extend: function (Parent, proto, options) { 6942 return configure("extend", Parent, proto, options); 6943 }, 6944 6945 init: function (Parent, ractive, options) { 6946 return configure("init", Parent, ractive, options); 6947 }, 6948 6949 reset: function (ractive) { 6950 return order.filter(function (c) { 6951 return c.reset && c.reset(ractive); 6952 }).map(function (c) { 6953 return c.name; 6954 }); 6955 }, 6956 6957 // this defines the order. TODO this isn't used anywhere in the codebase, 6958 // only in the test suite - should get rid of it 6959 order: order }; 6960 6961 function configure(method, Parent, target, options) { 6962 config_deprecate(options); 6963 6964 for (var key in options) { 6965 if (isStandardKey.hasOwnProperty(key)) { 6966 var value = options[key]; 6967 6968 // warn the developer if they passed a function and ignore its value 6969 6970 // NOTE: we allow some functions on "el" because we duck type element lists 6971 // and some libraries or ef'ed-up virtual browsers (phantomJS) return a 6972 // function object as the result of querySelector methods 6973 if (key !== "el" && typeof value === "function") { 6974 warnIfDebug("" + key + " is a Ractive option that does not expect a function and will be ignored", method === "init" ? target : null); 6975 } else { 6976 target[key] = value; 6977 } 6978 } 6979 } 6980 6981 config_registries.forEach(function (registry) { 6982 registry[method](Parent, target, options); 6983 }); 6984 6985 custom_adapt[method](Parent, target, options); 6986 template_template[method](Parent, target, options); 6987 css_css[method](Parent, target, options); 6988 6989 extendOtherMethods(Parent.prototype, target, options); 6990 } 6991 6992 function extendOtherMethods(parent, target, options) { 6993 for (var key in options) { 6994 if (!isBlacklisted[key] && options.hasOwnProperty(key)) { 6995 var member = options[key]; 6996 6997 // if this is a method that overwrites a method, wrap it: 6998 if (typeof member === "function") { 6999 member = wrapPrototype(parent, key, member); 7000 } 7001 7002 target[key] = member; 7003 } 7004 } 7005 } 7006 7007 function makeObj(array) { 7008 var obj = {}; 7009 array.forEach(function (x) { 7010 return obj[x] = true; 7011 }); 7012 return obj; 7013 } 7014 7015 var config_config = config; 7016 7017 var prototype_bubble = Fragment$bubble; 7018 7019 function Fragment$bubble() { 7020 this.dirtyValue = this.dirtyArgs = true; 7021 7022 if (this.bound && typeof this.owner.bubble === "function") { 7023 this.owner.bubble(); 7024 } 7025 } 7026 7027 var Fragment_prototype_detach = Fragment$detach; 7028 7029 function Fragment$detach() { 7030 var docFrag; 7031 7032 if (this.items.length === 1) { 7033 return this.items[0].detach(); 7034 } 7035 7036 docFrag = document.createDocumentFragment(); 7037
7038 this.items.forEach(function (item) { 7039 var node = item.detach(); 7040 7041 // TODO The if {...} wasn't previously required - it is now, because we're 7042 // forcibly detaching everything to reorder sections after an update. That's 7043 // a non-ideal brute force approach, implemented to get all the tests to pass 7044 // - as soon as it's replaced with something more elegant, this should 7045 // revert to `docFrag.appendChild( item.detach() )` 7046 if (node) { 7047 docFrag.appendChild(node); 7048 } 7049 }); 7050 7051 return docFrag; 7052 } 7053 7054 var Fragment_prototype_find = Fragment$find; 7055 7056 function Fragment$find(selector) { 7057 var i, len, item, queryResult; 7058 7059 if (this.items) { 7060 len = this.items.length; 7061 for (i = 0; i < len; i += 1) { 7062 item = this.items[i]; 7063 7064 if (item.find && (queryResult = item.find(selector))) { 7065 return queryResult; 7066 } 7067 } 7068 7069 return null; 7070 } 7071 } 7072 7073 var Fragment_prototype_findAll = Fragment$findAll; 7074 7075 function Fragment$findAll(selector, query) { 7076 var i, len, item; 7077 7078 if (this.items) { 7079 len = this.items.length; 7080 for (i = 0; i < len; i += 1) { 7081 item = this.items[i]; 7082 7083 if (item.findAll) { 7084 item.findAll(selector, query); 7085 } 7086 } 7087 } 7088 7089 return query; 7090 } 7091 7092 var Fragment_prototype_findAllComponents = Fragment$findAllComponents; 7093 7094 function Fragment$findAllComponents(selector, query) { 7095 var i, len, item; 7096 7097 if (this.items) { 7098 len = this.items.length; 7099 for (i = 0; i < len; i += 1) { 7100 item = this.items[i]; 7101 7102 if (item.findAllComponents) { 7103 item.findAllComponents(selector, query); 7104 } 7105 } 7106 } 7107 7108 return query; 7109 } 7110 7111 var Fragment_prototype_findComponent = Fragment$findComponent; 7112 7113 function Fragment$findComponent(selector) { 7114 var len, i, item, queryResult; 7115 7116 if (this.items) { 7117 len = this.items.length; 7118 for (i = 0; i < len; i += 1) { 7119 item = this.items[i]; 7120 7121 if (item.findComponent && (queryResult = item.findComponent(selector))) { 7122 return queryResult; 7123 } 7124 } 7125 7126 return null; 7127 } 7128 } 7129 7130 var prototype_findNextNode = Fragment$findNextNode; 7131 7132 function Fragment$findNextNode(item) { 7133 var index = item.index, 7134 node; 7135 7136 if (this.items[index + 1]) { 7137 node = this.items[index + 1].firstNode(); 7138 } 7139 7140 // if this is the root fragment, and there are no more items, 7141 // it means we're at the end... 7142 else if (this.owner === this.root) { 7143 if (!this.owner.component) { 7144 // TODO but something else could have been appended to 7145 // this.root.el, no? 7146 node = null; 7147 } 7148 7149 // ...unless this is a component 7150 else { 7151 node = this.owner.component.findNextNode(); 7152 } 7153 } else { 7154 node = this.owner.findNextNode(this); 7155 } 7156 7157 return node; 7158 } 7159 7160 var prototype_firstNode = Fragment$firstNode; 7161 7162 function Fragment$firstNode() { 7163 if (this.items && this.items[0]) { 7164 return this.items[0].firstNode(); 7165 } 7166 7167 return null; 7168 } 7169 7170 var shared_processItems = processItems; 7171 7172 function processItems(items, values, guid, counter) { 7173 counter = counter || 0; 7174 7175 return items.map(function (item) { 7176 var placeholderId, wrapped, value; 7177 7178 if (item.text) { 7179 return item.text; 7180 } 7181 7182 if (item.fragments) { 7183 return item.fragments.map(function (fragment) { 7184 return processItems(fragment.items, values, guid, counter); 7185 }).join(""); 7186 } 7187 7188 placeholderId = guid + "-" + counter++; 7189 7190 if (item.keypath && (wrapped = item.root.viewmodel.wrapped[item.keypath.str])) { 7191 value = wrapped.value; 7192 } else { 7193 value = item.getValue(); 7194 } 7195 7196 values[placeholderId] = value; 7197 7198 return "${" + placeholderId + "}"; 7199 }).join(""); 7200 } 7201 7202 var getArgsList = Fragment$getArgsList; 7203 function Fragment$getArgsList() { 7204 var values, source, parsed, result; 7205 7206 if (this.dirtyArgs) { 7207 source = shared_processItems(this.items, values = {}, this.root._guid); 7208 parsed = parseJSON("[" + source + "]", values); 7209 7210 if (!parsed) { 7211 result = [this.toString()]; 7212 } else { 7213 result = parsed.value; 7214 } 7215 7216 this.argsList = result; 7217 this.dirtyArgs = false; 7218 } 7219 7220 return this.argsList; 7221 } 7222 7223 var getNode = Fragment$getNode;
7224 7225 function Fragment$getNode() { 7226 var fragment = this; 7227 7228 do { 7229 if (fragment.pElement) { 7230 return fragment.pElement.node; 7231 } 7232 } while (fragment = fragment.parent); 7233 7234 return this.root.detached || this.root.el; 7235 } 7236 7237 var prototype_getValue = Fragment$getValue; 7238 function Fragment$getValue() { 7239 var values, source, parsed, result; 7240 7241 if (this.dirtyValue) { 7242 source = shared_processItems(this.items, values = {}, this.root._guid); 7243 parsed = parseJSON(source, values); 7244 7245 if (!parsed) { 7246 result = this.toString(); 7247 } else { 7248 result = parsed.value; 7249 } 7250 7251 this.value = result; 7252 this.dirtyValue = false; 7253 } 7254 7255 return this.value; 7256 } 7257 7258 var shared_detach = function () { 7259 return detachNode(this.node); 7260 }; 7261 7262 var Text = function (options) { 7263 this.type = TEXT; 7264 this.text = options.template; 7265 }; 7266 7267 Text.prototype = { 7268 detach: shared_detach, 7269 7270 firstNode: function () { 7271 return this.node; 7272 }, 7273 7274 render: function () { 7275 if (!this.node) { 7276 this.node = document.createTextNode(this.text); 7277 } 7278 7279 return this.node; 7280 }, 7281 7282 toString: function (escape) { 7283 return escape ? escapeHtml(this.text) : this.text; 7284 }, 7285 7286 unrender: function (shouldDestroy) { 7287 if (shouldDestroy) { 7288 return this.detach(); 7289 } 7290 } 7291 }; 7292 7293 var items_Text = Text; 7294 7295 var shared_unbind = shared_unbind__unbind; 7296 7297 function shared_unbind__unbind() { 7298 if (this.registered) { 7299 // this was registered as a dependant 7300 this.root.viewmodel.unregister(this.keypath, this); 7301 } 7302 7303 if (this.resolver) { 7304 this.resolver.unbind(); 7305 } 7306 } 7307 7308 var Mustache_getValue = Mustache$getValue; 7309 7310 function Mustache$getValue() { 7311 return this.value; 7312 } 7313 7314 var ReferenceResolver = function (owner, ref, callback) { 7315 var keypath; 7316 7317 this.ref = ref; 7318 this.resolved = false; 7319 7320 this.root = owner.root; 7321 this.parentFragment = owner.parentFragment; 7322 this.callback = callback; 7323 7324 keypath = shared_resolveRef(owner.root, ref, owner.parentFragment); 7325 if (keypath != undefined) { 7326 this.resolve(keypath); 7327 } else { 7328 global_runloop.addUnresolved(this); 7329 } 7330 }; 7331 7332 ReferenceResolver.prototype = { 7333 resolve: function (keypath) { 7334 if (this.keypath && !keypath) { 7335 // it was resolved, and now it's not. Can happen if e.g. `bar` in 7336 // `{{foo[bar]}}` becomes undefined 7337 global_runloop.addUnresolved(this); 7338 } 7339 7340 this.resolved = true; 7341 7342 this.keypath = keypath; 7343 this.callback(keypath); 7344 }, 7345 7346 forceResolution: function () { 7347 this.resolve(getKeypath(this.ref)); 7348 }, 7349 7350 rebind: function (oldKeypath, newKeypath) { 7351 var keypath; 7352 7353 if (this.keypath != undefined) { 7354 keypath = this.keypath.replace(oldKeypath, newKeypath); 7355 // was a new keypath created? 7356 if (keypath !== undefined) { 7357 // resolve it 7358 this.resolve(keypath); 7359 } 7360 } 7361 }, 7362 7363 unbind: function () { 7364 if (!this.resolved) { 7365 global_runloop.removeUnresolved(this); 7366 } 7367 } 7368 }; 7369 7370 var Resolvers_ReferenceResolver = ReferenceResolver; 7371 7372 var SpecialResolver = function (owner, ref, callback) { 7373 this.parentFragment = owner.parentFragment; 7374 this.ref = ref; 7375 this.callback = callback; 7376 7377 this.rebind(); 7378 }; 7379 7380 var props = { 7381 "@keypath": { prefix: "c", prop: ["context"] }, 7382 "@index": { prefix: "i", prop: ["index"] }, 7383 "@key": { prefix: "k", prop: ["key", "index"] } 7384 }; 7385 7386 function getProp(target, prop) { 7387 var value; 7388 for (var i = 0; i < prop.prop.length; i++) { 7389 if ((value = target[prop.prop[i]]) !== undefined) { 7390 return value; 7391 } 7392 } 7393 } 7394 7395 SpecialResolver.prototype = { 7396 rebind: function () { 7397 var ref = this.ref, 7398 fragment = this.parentFragment, 7399 prop = props[ref], 7400 value; 7401 7402 if (!prop) { 7403 throw new Error("Unknown special reference \"" + ref + "\" - valid references are @index, @key and @keypath"); 7404 } 7405 7406 // have we already found the nearest parent? 7407 if (this.cached) { 7408 return this.callback(getKeypath("@" + prop.prefix + getProp(this.cached, prop))); 7409 } 7410 7411 // special case for indices, which may cross component boundaries 7412 if (prop.prop.indexOf("index") !== -1 || prop.prop.indexOf("key") !== -1) { 7413 while (fragment) { 7414 if (fragment.owner.currentSubtype === SECTION_EACH && (value = getProp(fragment, prop)) !== undefined) { 7415 this.cached = fragment; 7416 7417 fragment.registerIndexRef(this); 7418 7419 return this.callback(getKeypath("@" + prop.prefix + value)); 7420 } 7421 7422 // watch for component boundaries 7423 if (!fragment.parent && fragment.owner && fragment.owner.component && fragment.owner.component.parentFragment && !fragment.owner.component.instance.isolated) { 7424 fragment = fragment.owner.component.parentFragment; 7425 } else { 7426 fragment = fragment.parent; 7427 } 7428 } 7429 } else { 7430 while (fragment) { 7431 if ((value = getProp(fragment, prop)) !== undefined) { 7432 return this.callback(getKeypath("@" + prop.prefix + value.str)); 7433 } 7434 7435 fragment = fragment.parent; 7436 } 7437 } 7438 }, 7439 7440 unbind: function () { 7441 if (this.cached) { 7442 this.cached.unregisterIndexRef(this); 7443 } 7444 } 7445 }; 7446 7447 var Resolvers_SpecialResolver = SpecialResolver; 7448 7449 var IndexResolver = function (owner, ref, callback) { 7450 this.parentFragment = owner.parentFragment; 7451 this.ref = ref; 7452 this.callback = callback; 7453 7454 ref.ref.fragment.registerIndexRef(this); 7455 7456 this.rebind(); 7457 }; 7458 7459 IndexResolver.prototype = { 7460 rebind: function () { 7461 var index, 7462 ref = this.ref.ref; 7463 7464 if (ref.ref.t === "k") { 7465 index = "k" + ref.fragment.key; 7466 } else { 7467 index = "i" + ref.fragment.index; 7468 } 7469 7470 if (index !== undefined) { 7471 this.callback(getKeypath("@" + index)); 7472 } 7473 }, 7474 7475 unbind: function () { 7476 this.ref.ref.fragment.unregisterIndexRef(this); 7477 } 7478 }; 7479 7480 var Resolvers_IndexResolver = IndexResolver; 7481 7482 var Resolvers_findIndexRefs = findIndexRefs; 7483 7484 function findIndexRefs(fragment, refName) { 7485 var result = {}, 7486 refs, 7487 fragRefs, 7488 ref, 7489 i, 7490 owner, 7491 hit = false;
7492 7493 if (!refName) { 7494 result.refs = refs = {}; 7495 } 7496 7497 while (fragment) { 7498 if ((owner = fragment.owner) && (fragRefs = owner.indexRefs)) { 7499 7500 // we're looking for a particular ref, and it's here 7501 if (refName && (ref = owner.getIndexRef(refName))) { 7502 result.ref = { 7503 fragment: fragment, 7504 ref: ref 7505 }; 7506 return result; 7507 } 7508 7509 // we're collecting refs up-tree 7510 else if (!refName) { 7511 for (i in fragRefs) { 7512 ref = fragRefs[i]; 7513 7514 // don't overwrite existing refs - they should shadow parents 7515 if (!refs[ref.n]) { 7516 hit = true; 7517 refs[ref.n] = { 7518 fragment: fragment, 7519 ref: ref 7520 }; 7521 } 7522 } 7523 } 7524 } 7525 7526 // watch for component boundaries 7527 if (!fragment.parent && fragment.owner && fragment.owner.component && fragment.owner.component.parentFragment && !fragment.owner.component.instance.isolated) { 7528 result.componentBoundary = true; 7529 fragment = fragment.owner.component.parentFragment; 7530 } else { 7531 fragment = fragment.parent; 7532 } 7533 } 7534 7535 if (!hit) { 7536 return undefined; 7537 } else { 7538 return result; 7539 } 7540 } 7541 7542 findIndexRefs.resolve = function resolve(indices) { 7543 var refs = {}, 7544 k, 7545 ref; 7546 7547 for (k in indices.refs) { 7548 ref = indices.refs[k]; 7549 refs[ref.ref.n] = ref.ref.t === "k" ? ref.fragment.key : ref.fragment.index; 7550 } 7551 7552 return refs; 7553 }; 7554 7555 var Resolvers_createReferenceResolver = createReferenceResolver; 7556 function createReferenceResolver(owner, ref, callback) { 7557 var indexRef; 7558 7559 if (ref.charAt(0) === "@") { 7560 return new Resolvers_SpecialResolver(owner, ref, callback); 7561 } 7562 7563 if (indexRef = Resolvers_findIndexRefs(owner.parentFragment, ref)) { 7564 return new Resolvers_IndexResolver(owner, indexRef, callback); 7565 } 7566 7567 return new Resolvers_ReferenceResolver(owner, ref, callback); 7568 } 7569 7570 var shared_getFunctionFromString = getFunctionFromString; 7571 var cache = {}; 7572 function getFunctionFromString(str, i) { 7573 var fn, args; 7574 7575 if (cache[str]) { 7576 return cache[str]; 7577 } 7578 7579 args = []; 7580 while (i--) { 7581 args[i] = "_" + i; 7582 } 7583 7584 fn = new Function(args.join(","), "return(" + str + ")"); 7585 7586 cache[str] = fn; 7587 return fn; 7588 } 7589 7590 var ExpressionResolver, 7591 Resolvers_ExpressionResolver__bind = Function.prototype.bind; 7592 7593 ExpressionResolver = function (owner, parentFragment, expression, callback) { 7594 var _this = this; 7595 7596 var ractive; 7597 7598 ractive = owner.root; 7599 7600 this.root = ractive; 7601 this.parentFragment = parentFragment; 7602 this.callback = callback; 7603 this.owner = owner; 7604 this.str = expression.s; 7605 this.keypaths = []; 7606 7607 // Create resolvers for each reference 7608 this.pending = expression.r.length; 7609 this.refResolvers = expression.r.map(function (ref, i) { 7610 return Resolvers_createReferenceResolver(_this, ref, function (keypath) { 7611 _this.resolve(i, keypath); 7612 }); 7613 }); 7614 7615 this.ready = true; 7616 this.bubble(); 7617 }; 7618 7619 ExpressionResolver.prototype = { 7620 bubble: function () { 7621 if (!this.ready) { 7622 return; 7623 } 7624 7625 this.uniqueString = getUniqueString(this.str, this.keypaths); 7626 this.keypath = createExpressionKeypath(this.uniqueString); 7627 7628 this.createEvaluator(); 7629 this.callback(this.keypath); 7630 }, 7631 7632 unbind: function () { 7633 var resolver; 7634 7635 while (resolver = this.refResolvers.pop()) { 7636 resolver.unbind(); 7637 } 7638 }, 7639 7640 resolve: function (index, keypath) { 7641 this.keypaths[index] = keypath; 7642 this.bubble(); 7643 }, 7644 7645 createEvaluator: function () { 7646 var _this = this; 7647 7648 var computation, valueGetters, signature, keypath, fn; 7649 7650 keypath = this.keypath; 7651 computation = this.root.viewmodel.computations[keypath.str]; 7652 7653 // only if it doesn't exist yet! 7654 if (!computation) { 7655 fn = shared_getFunctionFromString(this.str, this.refResolvers.length); 7656 7657 valueGetters = this.keypaths.map(function (keypath) { 7658 var value; 7659 7660 if (keypath === "undefined") { 7661 return function () { 7662 return undefined; 7663 }; 7664 } 7665 7666 // 'special' keypaths encode a value 7667 if (keypath.isSpecial) { 7668 value = keypath.value; 7669 return function () { 7670 return value; 7671 }; 7672 } 7673 7674 return function () { 7675 var value = _this.root.viewmodel.get(keypath, { noUnwrap: true, fullRootGet: true }); 7676 if (typeof value === "function") { 7677 value = wrapFunction(value, _this.root); 7678 } 7679 return value; 7680 }; 7681 }); 7682 7683 signature = { 7684 deps: this.keypaths.filter(isValidDependency), 7685 getter: function () { 7686 var args = valueGetters.map(call);
7687 return fn.apply(null, args); 7688 } 7689 }; 7690 7691 computation = this.root.viewmodel.compute(keypath, signature); 7692 } else { 7693 this.root.viewmodel.mark(keypath); 7694 } 7695 }, 7696 7697 rebind: function (oldKeypath, newKeypath) { 7698 // TODO only bubble once, no matter how many references are affected by the rebind 7699 this.refResolvers.forEach(function (r) { 7700 return r.rebind(oldKeypath, newKeypath); 7701 }); 7702 } 7703 }; 7704 7705 var Resolvers_ExpressionResolver = ExpressionResolver; 7706 7707 function call(value) { 7708 return value.call(); 7709 } 7710 7711 function getUniqueString(str, keypaths) { 7712 // get string that is unique to this expression 7713 return str.replace(/_([0-9]+)/g, function (match, $1) { 7714 var keypath, value; 7715 7716 // make sure we're not replacing a non-keypath _[0-9] 7717 if (+$1 >= keypaths.length) { 7718 return "_" + $1; 7719 } 7720 7721 keypath = keypaths[$1]; 7722 7723 if (keypath === undefined) { 7724 return "undefined"; 7725 } 7726 7727 if (keypath.isSpecial) { 7728 value = keypath.value; 7729 return typeof value === "number" ? value : "\"" + value + "\""; 7730 } 7731 7732 return keypath.str; 7733 }); 7734 } 7735 7736 function createExpressionKeypath(uniqueString) { 7737 // Sanitize by removing any periods or square brackets. Otherwise 7738 // we can't split the keypath into keys! 7739 // Remove asterisks too, since they mess with pattern observers 7740 return getKeypath("${" + uniqueString.replace(/[\.\[\]]/g, "-").replace(/\*/, "#MUL#") + "}"); 7741 } 7742 7743 function isValidDependency(keypath) { 7744 return keypath !== undefined && keypath[0] !== "@"; 7745 } 7746 7747 function wrapFunction(fn, ractive) { 7748 var wrapped, prop, key; 7749 7750 if (fn.__ractive_nowrap) { 7751 return fn; 7752 } 7753 7754 prop = "__ractive_" + ractive._guid; 7755 wrapped = fn[prop]; 7756 7757 if (wrapped) { 7758 return wrapped; 7759 } else if (/this/.test(fn.toString())) { 7760 defineProperty(fn, prop, { 7761 value: Resolvers_ExpressionResolver__bind.call(fn, ractive), 7762 configurable: true 7763 }); 7764 7765 // Add properties/methods to wrapped function 7766 for (key in fn) { 7767 if (fn.hasOwnProperty(key)) { 7768 fn[prop][key] = fn[key]; 7769 } 7770 } 7771 7772 ractive._boundFunctions.push({ 7773 fn: fn, 7774 prop: prop 7775 }); 7776 7777 return fn[prop]; 7778 } 7779 7780 defineProperty(fn, "__ractive_nowrap", { 7781 value: fn 7782 }); 7783 7784 return fn.__ractive_nowrap; 7785 } 7786 7787 var MemberResolver = function (template, resolver, parentFragment) { 7788 var _this = this; 7789 7790 this.resolver = resolver; 7791 this.root = resolver.root; 7792 this.parentFragment = parentFragment; 7793 this.viewmodel = resolver.root.viewmodel; 7794 7795 if (typeof template === "string") { 7796 this.value = template; 7797 } 7798 7799 // Simple reference? 7800 else if (template.t === REFERENCE) { 7801 this.refResolver = Resolvers_createReferenceResolver(this, template.n, function (keypath) { 7802 _this.resolve(keypath); 7803 }); 7804 } 7805 7806 // Otherwise we have an expression in its own right 7807 else { 7808 new Resolvers_ExpressionResolver(resolver, parentFragment, template, function (keypath) { 7809 _this.resolve(keypath); 7810 }); 7811 } 7812 }; 7813 7814 MemberResolver.prototype = { 7815 resolve: function (keypath) { 7816 if (this.keypath) { 7817 this.viewmodel.unregister(this.keypath, this); 7818 } 7819 7820 this.keypath = keypath; 7821 this.value = this.viewmodel.get(keypath); 7822 7823 this.bind(); 7824 7825 this.resolver.bubble(); 7826 }, 7827 7828 bind: function () { 7829 this.viewmodel.register(this.keypath, this); 7830 }, 7831 7832 rebind: function (oldKeypath, newKeypath) { 7833 if (this.refResolver) { 7834 this.refResolver.rebind(oldKeypath, newKeypath); 7835 } 7836 }, 7837 7838 setValue: function (value) { 7839 this.value = value; 7840 this.resolver.bubble(); 7841 }, 7842 7843 unbind: function () { 7844 if (this.keypath) { 7845 this.viewmodel.unregister(this.keypath, this); 7846 } 7847 7848 if (this.refResolver) { 7849 this.refResolver.unbind(); 7850 } 7851 }, 7852 7853 forceResolution: function () { 7854 if (this.refResolver) { 7855 this.refResolver.forceResolution(); 7856 } 7857 } 7858 }; 7859 7860 var ReferenceExpressionResolver_MemberResolver = MemberResolver; 7861 7862 var ReferenceExpressionResolver = function (mustache, template, callback) { 7863 var _this = this; 7864 7865 var ractive, ref, keypath, parentFragment; 7866 7867 this.parentFragment = parentFragment = mustache.parentFragment; 7868 this.root = ractive = mustache.root; 7869 this.mustache = mustache; 7870 7871 this.ref = ref = template.r; 7872 this.callback = callback; 7873 7874 this.unresolved = []; 7875 7876 // Find base keypath 7877 if (keypath = shared_resolveRef(ractive, ref, parentFragment)) { 7878 this.base = keypath; 7879 } else { 7880 this.baseResolver = new Resolvers_ReferenceResolver(this, ref, function (keypath) { 7881 _this.base = keypath; 7882 _this.baseResolver = null; 7883 _this.bubble(); 7884 }); 7885 } 7886 7887 // Find values for members, or mark them as unresolved 7888 this.members = template.m.map(function (template) { 7889 return new ReferenceExpressionResolver_MemberResolver(template, _this, parentFragment); 7890 }); 7891 7892 this.ready = true; 7893 this.bubble(); // trigger initial resolution if possible 7894 }; 7895 7896 ReferenceExpressionResolver.prototype = { 7897 getKeypath: function () { 7898 var values = this.members.map(ReferenceExpressionResolver_ReferenceExpressionResolver__getValue); 7899 7900 if (!values.every(isDefined) || this.baseResolver) { 7901 return null; 7902 } 7903 7904 return this.base.join(values.join(".")); 7905 }, 7906 7907 bubble: function () { 7908 if (!this.ready || this.baseResolver) { 7909 return; 7910 } 7911 7912 this.callback(this.getKeypath()); 7913 }, 7914 7915 unbind: function () {
7916 this.members.forEach(methodCallers__unbind); 7917 }, 7918 7919 rebind: function (oldKeypath, newKeypath) { 7920 var changed; 7921 7922 if (this.base) { 7923 var newBase = this.base.replace(oldKeypath, newKeypath); 7924 if (newBase && newBase !== this.base) { 7925 this.base = newBase; 7926 changed = true; 7927 } 7928 } 7929 7930 this.members.forEach(function (members) { 7931 if (members.rebind(oldKeypath, newKeypath)) { 7932 changed = true; 7933 } 7934 }); 7935 7936 if (changed) { 7937 this.bubble(); 7938 } 7939 }, 7940 7941 forceResolution: function () { 7942 if (this.baseResolver) { 7943 this.base = getKeypath(this.ref); 7944 7945 this.baseResolver.unbind(); 7946 this.baseResolver = null; 7947 } 7948 7949 this.members.forEach(forceResolution); 7950 this.bubble(); 7951 } 7952 }; 7953 7954 function ReferenceExpressionResolver_ReferenceExpressionResolver__getValue(member) { 7955 return member.value; 7956 } 7957 7958 function isDefined(value) { 7959 return value != undefined; 7960 } 7961 7962 function forceResolution(member) { 7963 member.forceResolution(); 7964 } 7965 7966 var ReferenceExpressionResolver_ReferenceExpressionResolver = ReferenceExpressionResolver; 7967 7968 var Mustache_initialise = Mustache$init; 7969 function Mustache$init(mustache, options) { 7970 7971 var ref, parentFragment, template; 7972 7973 parentFragment = options.parentFragment; 7974 template = options.template; 7975 7976 mustache.root = parentFragment.root; 7977 mustache.parentFragment = parentFragment; 7978 mustache.pElement = parentFragment.pElement; 7979 7980 mustache.template = options.template; 7981 mustache.index = options.index || 0; 7982 mustache.isStatic = options.template.s; 7983 7984 mustache.type = options.template.t; 7985 7986 mustache.registered = false; 7987 7988 // if this is a simple mustache, with a reference, we just need to resolve 7989 // the reference to a keypath 7990 if (ref = template.r) { 7991 mustache.resolver = Resolvers_createReferenceResolver(mustache, ref, resolve); 7992 } 7993 7994 // if it's an expression, we have a bit more work to do 7995 if (options.template.x) { 7996 mustache.resolver = new Resolvers_ExpressionResolver(mustache, parentFragment, options.template.x, resolveAndRebindChildren); 7997 } 7998 7999 if (options.template.rx) { 8000 mustache.resolver = new ReferenceExpressionResolver_ReferenceExpressionResolver(mustache, options.template.rx, resolveAndRebindChildren); 8001 } 8002 8003 // Special case - inverted sections 8004 if (mustache.template.n === SECTION_UNLESS && !mustache.hasOwnProperty("value")) { 8005 mustache.setValue(undefined); 8006 } 8007 8008 function resolve(keypath) { 8009 mustache.resolve(keypath); 8010 } 8011 8012 function resolveAndRebindChildren(newKeypath) { 8013 var oldKeypath = mustache.keypath; 8014 8015 if (newKeypath != oldKeypath) { 8016 mustache.resolve(newKeypath); 8017 8018 if (oldKeypath !== undefined) { 8019 mustache.fragments && mustache.fragments.forEach(function (f) { 8020 f.rebind(oldKeypath, newKeypath); 8021 }); 8022 } 8023 } 8024 } 8025 } 8026 8027 var Mustache_resolve = Mustache$resolve; 8028 8029 function Mustache$resolve(keypath) { 8030 var wasResolved, value, twowayBinding; 8031 8032 // 'Special' keypaths, e.g. @foo or @7, encode a value 8033 if (keypath && keypath.isSpecial) { 8034 this.keypath = keypath; 8035 this.setValue(keypath.value); 8036 return; 8037 } 8038 8039 // If we resolved previously, we need to unregister 8040 if (this.registered) { 8041 // undefined or null 8042 this.root.viewmodel.unregister(this.keypath, this); 8043 this.registered = false; 8044 8045 wasResolved = true; 8046 } 8047 8048 this.keypath = keypath; 8049 8050 // If the new keypath exists, we need to register 8051 // with the viewmodel 8052 if (keypath != undefined) { 8053 // undefined or null 8054 value = this.root.viewmodel.get(keypath); 8055 this.root.viewmodel.register(keypath, this); 8056 8057 this.registered = true; 8058 } 8059 8060 // Either way we need to queue up a render (`value` 8061 // will be `undefined` if there's no keypath) 8062 this.setValue(value); 8063 8064 // Two-way bindings need to point to their new target keypath 8065 if (wasResolved && (twowayBinding = this.twowayBinding)) { 8066 twowayBinding.rebound(); 8067 } 8068 } 8069 8070 var Mustache_rebind = Mustache$rebind; 8071 8072 function Mustache$rebind(oldKeypath, newKeypath) { 8073 // Children first 8074 if (this.fragments) {
8075 this.fragments.forEach(function (f) { 8076 return f.rebind(oldKeypath, newKeypath); 8077 }); 8078 } 8079 8080 // Expression mustache? 8081 if (this.resolver) { 8082 this.resolver.rebind(oldKeypath, newKeypath); 8083 } 8084 } 8085 8086 var Mustache = { 8087 getValue: Mustache_getValue, 8088 init: Mustache_initialise, 8089 resolve: Mustache_resolve, 8090 rebind: Mustache_rebind 8091 }; 8092 8093 var Interpolator = function (options) { 8094 this.type = INTERPOLATOR; 8095 Mustache.init(this, options); 8096 }; 8097 8098 Interpolator.prototype = { 8099 update: function () { 8100 this.node.data = this.value == undefined ? "" : this.value; 8101 }, 8102 resolve: Mustache.resolve, 8103 rebind: Mustache.rebind, 8104 detach: shared_detach, 8105 8106 unbind: shared_unbind, 8107 8108 render: function () { 8109 if (!this.node) { 8110 this.node = document.createTextNode(safeToStringValue(this.value)); 8111 } 8112 8113 return this.node; 8114 }, 8115 8116 unrender: function (shouldDestroy) { 8117 if (shouldDestroy) { 8118 detachNode(this.node); 8119 } 8120 }, 8121 8122 getValue: Mustache.getValue, 8123 8124 // TEMP 8125 setValue: function (value) { 8126 var wrapper; 8127 8128 // TODO is there a better way to approach this? 8129 if (this.keypath && (wrapper = this.root.viewmodel.wrapped[this.keypath.str])) { 8130 value = wrapper.get(); 8131 } 8132 8133 if (!isEqual(value, this.value)) { 8134 this.value = value; 8135 this.parentFragment.bubble(); 8136 8137 if (this.node) { 8138 global_runloop.addView(this); 8139 } 8140 } 8141 }, 8142 8143 firstNode: function () { 8144 return this.node; 8145 }, 8146 8147 toString: function (escape) { 8148 var string = "" + safeToStringValue(this.value); 8149 return escape ? escapeHtml(string) : string; 8150 } 8151 }; 8152 8153 var items_Interpolator = Interpolator; 8154 8155 var Section_prototype_bubble = Section$bubble; 8156 8157 function Section$bubble() { 8158 this.parentFragment.bubble(); 8159 } 8160 8161 var Section_prototype_detach = Section$detach; 8162 8163 function Section$detach() { 8164 var docFrag; 8165 8166 if (this.fragments.length === 1) { 8167 return this.fragments[0].detach(); 8168 } 8169 8170 docFrag = document.createDocumentFragment(); 8171 8172 this.fragments.forEach(function (item) { 8173 docFrag.appendChild(item.detach()); 8174 }); 8175 8176 return docFrag; 8177 } 8178 8179 var find = Section$find; 8180 8181 function Section$find(selector) { 8182 var i, len, queryResult; 8183 8184 len = this.fragments.length; 8185 for (i = 0; i < len; i += 1) { 8186 if (queryResult = this.fragments[i].find(selector)) { 8187 return queryResult; 8188 } 8189 } 8190 8191 return null; 8192 } 8193 8194 var findAll = Section$findAll; 8195 8196 function Section$findAll(selector, query) { 8197 var i, len; 8198 8199 len = this.fragments.length; 8200 for (i = 0; i < len; i += 1) { 8201 this.fragments[i].findAll(selector, query); 8202 } 8203 } 8204 8205 var findAllComponents = Section$findAllComponents; 8206 8207 function Section$findAllComponents(selector, query) { 8208 var i, len; 8209 8210 len = this.fragments.length; 8211 for (i = 0; i < len; i += 1) { 8212 this.fragments[i].findAllComponents(selector, query); 8213 } 8214 } 8215 8216 var findComponent = Section$findComponent; 8217 8218 function Section$findComponent(selector) { 8219 var i, len, queryResult; 8220 8221 len = this.fragments.length; 8222 for (i = 0; i < len; i += 1) { 8223 if (queryResult = this.fragments[i].findComponent(selector)) { 8224 return queryResult; 8225 } 8226 } 8227 8228 return null; 8229 } 8230 8231 var findNextNode = Section$findNextNode; 8232 8233 function Section$findNextNode(fragment) { 8234 if (this.fragments[fragment.index + 1]) { 8235 return this.fragments[fragment.index + 1].firstNode(); 8236 } 8237 8238 return this.parentFragment.findNextNode(this); 8239 } 8240 8241 var firstNode = Section$firstNode; 8242 8243 function Section$firstNode() { 8244 var len, i, node; 8245 8246 if (len = this.fragments.length) { 8247 for (i = 0; i < len; i += 1) { 8248 if (node = this.fragments[i].firstNode()) { 8249 return node; 8250 } 8251 } 8252 } 8253 8254 return this.parentFragment.findNextNode(this); 8255 } 8256 8257 var shuffle = Section$shuffle; 8258 8259 function Section$shuffle(newIndices) { 8260 var _this = this; 8261 8262 var parentFragment, firstChange, i, newLength, reboundFragments, fragmentOptions, fragment; 8263 8264 // short circuit any double-updates, and ensure that this isn't applied to 8265 // non-list sections 8266 if (this.shuffling || this.unbound || this.currentSubtype !== SECTION_EACH) { 8267 return; 8268 } 8269 8270 this.shuffling = true; 8271 global_runloop.scheduleTask(function () { 8272 return _this.shuffling = false;
8273 }); 8274 8275 parentFragment = this.parentFragment; 8276 8277 reboundFragments = []; 8278 8279 // TODO: need to update this 8280 // first, rebind existing fragments 8281 newIndices.forEach(function (newIndex, oldIndex) { 8282 var fragment, by, oldKeypath, newKeypath, deps; 8283 8284 if (newIndex === oldIndex) { 8285 reboundFragments[newIndex] = _this.fragments[oldIndex]; 8286 return; 8287 } 8288 8289 fragment = _this.fragments[oldIndex]; 8290 8291 if (firstChange === undefined) { 8292 firstChange = oldIndex; 8293 } 8294 8295 // does this fragment need to be torn down? 8296 if (newIndex === -1) { 8297 _this.fragmentsToUnrender.push(fragment); 8298 fragment.unbind(); 8299 return; 8300 } 8301 8302 // Otherwise, it needs to be rebound to a new index 8303 by = newIndex - oldIndex; 8304 oldKeypath = _this.keypath.join(oldIndex); 8305 newKeypath = _this.keypath.join(newIndex); 8306 8307 fragment.index = newIndex; 8308 8309 // notify any registered index refs directly 8310 if (deps = fragment.registeredIndexRefs) { 8311 deps.forEach(shuffle__blindRebind); 8312 } 8313 8314 fragment.rebind(oldKeypath, newKeypath); 8315 reboundFragments[newIndex] = fragment; 8316 }); 8317 8318 newLength = this.root.viewmodel.get(this.keypath).length; 8319 8320 // If nothing changed with the existing fragments, then we start adding 8321 // new fragments at the end... 8322 if (firstChange === undefined) { 8323 // ...unless there are no new fragments to add 8324 if (this.length === newLength) { 8325 return; 8326 } 8327 8328 firstChange = this.length; 8329 } 8330 8331 this.length = this.fragments.length = newLength; 8332 8333 if (this.rendered) { 8334 global_runloop.addView(this); 8335 } 8336 8337 // Prepare new fragment options 8338 fragmentOptions = { 8339 template: this.template.f, 8340 root: this.root, 8341 owner: this 8342 }; 8343 8344 // Add as many new fragments as we need to, or add back existing 8345 // (detached) fragments 8346 for (i = firstChange; i < newLength; i += 1) { 8347 fragment = reboundFragments[i]; 8348 8349 if (!fragment) { 8350 this.fragmentsToCreate.push(i); 8351 } 8352 8353 this.fragments[i] = fragment; 8354 } 8355 } 8356 8357 function shuffle__blindRebind(dep) { 8358 // the keypath doesn't actually matter here as it won't have changed 8359 dep.rebind("", ""); 8360 } 8361 8362 var prototype_rebind = function (oldKeypath, newKeypath) { 8363 Mustache.rebind.call(this, oldKeypath, newKeypath); 8364 }; 8365 8366 var Section_prototype_render = Section$render; 8367 8368 function Section$render() { 8369 var _this = this; 8370 8371 this.docFrag = document.createDocumentFragment(); 8372 8373 this.fragments.forEach(function (f) { 8374 return _this.docFrag.appendChild(f.render()); 8375 }); 8376 8377 this.renderedFragments = this.fragments.slice(); 8378 this.fragmentsToRender = []; 8379 8380 this.rendered = true; 8381 return this.docFrag; 8382 } 8383 8384 var setValue = Section$setValue; 8385 8386 function Section$setValue(value) { 8387 var _this = this; 8388 8389 var wrapper, fragmentOptions; 8390 8391 if (this.updating) { 8392 // If a child of this section causes a re-evaluation - for example, an 8393 // expression refers to a function that mutates the array that this 8394 // section depends on - we'll end up with a double rendering bug (see 8395 // https://github.com/ractivejs/ractive/issues/748). This prevents it. 8396 return; 8397 } 8398 8399 this.updating = true; 8400 8401 // with sections, we need to get the fake value if we have a wrapped object 8402 if (this.keypath && (wrapper = this.root.viewmodel.wrapped[this.keypath.str])) { 8403 value = wrapper.get(); 8404 } 8405 8406 // If any fragments are awaiting creation after a splice, 8407 // this is the place to do it 8408 if (this.fragmentsToCreate.length) { 8409 fragmentOptions = { 8410 template: this.template.f || [], 8411 root: this.root, 8412 pElement: this.pElement, 8413 owner: this 8414 }; 8415 8416 this.fragmentsToCreate.forEach(function (index) { 8417 var fragment; 8418 8419 fragmentOptions.context = _this.keypath.join(index); 8420 fragmentOptions.index = index; 8421 8422 fragment = new virtualdom_Fragment(fragmentOptions); 8423 _this.fragmentsToRender.push(_this.fragments[index] = fragment); 8424 }); 8425 8426 this.fragmentsToCreate.length = 0; 8427 } else if (reevaluateSection(this, value)) { 8428 this.bubble(); 8429 8430 if (this.rendered) { 8431 global_runloop.addView(this); 8432 } 8433 } 8434 8435 this.value = value; 8436 this.updating = false;
8437 } 8438 8439 function changeCurrentSubtype(section, value, obj) { 8440 if (value === SECTION_EACH) { 8441 // make sure ref type is up to date for key or value indices 8442 if (section.indexRefs && section.indexRefs[0]) { 8443 var ref = section.indexRefs[0]; 8444 8445 // when switching flavors, make sure the section gets updated 8446 if (obj && ref.t === "i" || !obj && ref.t === "k") { 8447 // if switching from object to list, unbind all of the old fragments 8448 if (!obj) { 8449 section.length = 0; 8450 section.fragmentsToUnrender = section.fragments.slice(0); 8451 section.fragmentsToUnrender.forEach(function (f) { 8452 return f.unbind(); 8453 }); 8454 } 8455 } 8456 8457 ref.t = obj ? "k" : "i"; 8458 } 8459 } 8460 8461 section.currentSubtype = value; 8462 } 8463 8464 function reevaluateSection(section, value) { 8465 var fragmentOptions = { 8466 template: section.template.f || [], 8467 root: section.root, 8468 pElement: section.parentFragment.pElement, 8469 owner: section 8470 }; 8471 8472 section.hasContext = true; 8473 8474 // If we already know the section type, great 8475 // TODO can this be optimised? i.e. pick an reevaluateSection function during init 8476 // and avoid doing this each time? 8477 if (section.subtype) { 8478 switch (section.subtype) { 8479 case SECTION_IF: 8480 section.hasContext = false; 8481 return reevaluateConditionalSection(section, value, false, fragmentOptions); 8482 8483 case SECTION_UNLESS: 8484 section.hasContext = false; 8485 return reevaluateConditionalSection(section, value, true, fragmentOptions); 8486 8487 case SECTION_WITH: 8488 return reevaluateContextSection(section, fragmentOptions); 8489 8490 case SECTION_IF_WITH: 8491 return reevaluateConditionalContextSection(section, value, fragmentOptions); 8492 8493 case SECTION_EACH: 8494 if (isObject(value)) { 8495 changeCurrentSubtype(section, section.subtype, true); 8496 return reevaluateListObjectSection(section, value, fragmentOptions); 8497 } 8498 8499 // Fallthrough - if it's a conditional or an array we need to continue 8500 } 8501 } 8502 8503 // Otherwise we need to work out what sort of section we're dealing with 8504 section.ordered = !!isArrayLike(value); 8505 8506 // Ordered list section 8507 if (section.ordered) { 8508 changeCurrentSubtype(section, SECTION_EACH, false); 8509 return reevaluateListSection(section, value, fragmentOptions); 8510 } 8511 8512 // Unordered list, or context 8513 if (isObject(value) || typeof value === "function") { 8514 // Index reference indicates section should be treated as a list 8515 if (section.template.i) { 8516 changeCurrentSubtype(section, SECTION_EACH, true); 8517 return reevaluateListObjectSection(section, value, fragmentOptions); 8518 } 8519 8520 // Otherwise, object provides context for contents 8521 changeCurrentSubtype(section, SECTION_WITH, false); 8522 return reevaluateContextSection(section, fragmentOptions); 8523 } 8524 8525 // Conditional section 8526 changeCurrentSubtype(section, SECTION_IF, false); 8527 section.hasContext = false; 8528 return reevaluateConditionalSection(section, value, false, fragmentOptions); 8529 } 8530 8531 function reevaluateListSection(section, value, fragmentOptions) { 8532 var i, length, fragment; 8533 8534 length = value.length; 8535 8536 if (length === section.length) { 8537 // Nothing to do 8538 return false; 8539 } 8540 8541 // if the array is shorter than it was previously, remove items 8542 if (length < section.length) { 8543 section.fragmentsToUnrender = section.fragments.splice(length, section.length - length); 8544 section.fragmentsToUnrender.forEach(methodCallers__unbind); 8545 } 8546 8547 // otherwise... 8548 else { 8549 if (length > section.length) { 8550 // add any new ones 8551 for (i = section.length; i < length; i += 1) { 8552 // append list item to context stack 8553 fragmentOptions.context = section.keypath.join(i); 8554 fragmentOptions.index = i; 8555 8556 fragment = new virtualdom_Fragment(fragmentOptions); 8557 section.fragmentsToRender.push(section.fragments[i] = fragment); 8558 } 8559 } 8560 } 8561 8562 section.length = length; 8563 return true; 8564 } 8565 8566 function reevaluateListObjectSection(section, value, fragmentOptions) { 8567 var id, i, hasKey, fragment, changed, deps; 8568 8569 hasKey = section.hasKey || (section.hasKey = {}); 8570 8571 // remove any fragments that should no longer exist 8572 i = section.fragments.length; 8573 while (i--) { 8574 fragment = section.fragments[i]; 8575 8576 if (!(fragment.key in value)) { 8577 changed = true; 8578 8579 fragment.unbind(); 8580 section.fragmentsToUnrender.push(fragment); 8581 section.fragments.splice(i, 1); 8582 8583 hasKey[fragment.key] = false;
8584 } 8585 } 8586 8587 // notify any dependents about changed indices 8588 i = section.fragments.length; 8589 while (i--) { 8590 fragment = section.fragments[i]; 8591 8592 if (fragment.index !== i) { 8593 fragment.index = i; 8594 if (deps = fragment.registeredIndexRefs) { 8595 deps.forEach(setValue__blindRebind); 8596 } 8597 } 8598 } 8599 8600 // add any that haven't been created yet 8601 i = section.fragments.length; 8602 for (id in value) { 8603 if (!hasKey[id]) { 8604 changed = true; 8605 8606 fragmentOptions.context = section.keypath.join(id); 8607 fragmentOptions.key = id; 8608 fragmentOptions.index = i++; 8609 8610 fragment = new virtualdom_Fragment(fragmentOptions); 8611 8612 section.fragmentsToRender.push(fragment); 8613 section.fragments.push(fragment); 8614 hasKey[id] = true; 8615 } 8616 } 8617 8618 section.length = section.fragments.length; 8619 return changed; 8620 } 8621 8622 function reevaluateConditionalContextSection(section, value, fragmentOptions) { 8623 if (value) { 8624 return reevaluateContextSection(section, fragmentOptions); 8625 } else { 8626 return removeSectionFragments(section); 8627 } 8628 } 8629 8630 function reevaluateContextSection(section, fragmentOptions) { 8631 var fragment; 8632 8633 // ...then if it isn't rendered, render it, adding section.keypath to the context stack 8634 // (if it is already rendered, then any children dependent on the context stack 8635 // will update themselves without any prompting) 8636 if (!section.length) { 8637 // append this section to the context stack 8638 fragmentOptions.context = section.keypath; 8639 fragmentOptions.index = 0; 8640 8641 fragment = new virtualdom_Fragment(fragmentOptions); 8642 8643 section.fragmentsToRender.push(section.fragments[0] = fragment); 8644 section.length = 1; 8645 8646 return true; 8647 } 8648 } 8649 8650 function reevaluateConditionalSection(section, value, inverted, fragmentOptions) { 8651 var doRender, emptyArray, emptyObject, fragment, name; 8652 8653 emptyArray = isArrayLike(value) && value.length === 0; 8654 emptyObject = false; 8655 if (!isArrayLike(value) && isObject(value)) { 8656 emptyObject = true; 8657 for (name in value) { 8658 emptyObject = false; 8659 break; 8660 } 8661 } 8662 8663 if (inverted) { 8664 doRender = emptyArray || emptyObject || !value; 8665 } else { 8666 doRender = value && !emptyArray && !emptyObject; 8667 } 8668 8669 if (doRender) { 8670 if (!section.length) { 8671 // no change to context stack 8672 fragmentOptions.index = 0; 8673 8674 fragment = new virtualdom_Fragment(fragmentOptions); 8675 section.fragmentsToRender.push(section.fragments[0] = fragment); 8676 section.length = 1; 8677 8678 return true; 8679 } 8680 8681 if (section.length > 1) { 8682 section.fragmentsToUnrender = section.fragments.splice(1); 8683 section.fragmentsToUnrender.forEach(methodCallers__unbind); 8684 8685 return true; 8686 } 8687 } else { 8688 return removeSectionFragments(section); 8689 } 8690 } 8691 8692 function removeSectionFragments(section) { 8693 if (section.length) { 8694 section.fragmentsToUnrender = section.fragments.splice(0, section.fragments.length).filter(isRendered); 8695 section.fragmentsToUnrender.forEach(methodCallers__unbind); 8696 section.length = section.fragmentsToRender.length = 0; 8697 return true; 8698 } 8699 } 8700 8701 function isRendered(fragment) { 8702 return fragment.rendered; 8703 } 8704 8705 function setValue__blindRebind(dep) { 8706 // the keypath doesn't actually matter here as it won't have changed 8707 dep.rebind("", ""); 8708 } 8709 8710 var prototype_toString = Section$toString; 8711 8712 function Section$toString(escape) { 8713 var str, i, len; 8714 8715 str = ""; 8716 8717 i = 0; 8718 len = this.length; 8719 8720 for (i = 0; i < len; i += 1) { 8721 str += this.fragments[i].toString(escape); 8722 } 8723 8724 return str; 8725 } 8726 8727 var prototype_unbind = Section$unbind; 8728 function Section$unbind() { 8729 var _this = this; 8730 8731 this.fragments.forEach(methodCallers__unbind); 8732 this.fragmentsToRender.forEach(function (f) { 8733 return removeFromArray(_this.fragments, f); 8734 }); 8735 this.fragmentsToRender = []; 8736 shared_unbind.call(this); 8737 8738 this.length = 0; 8739 this.unbound = true; 8740 } 8741 8742 var prototype_unrender = Section$unrender; 8743 8744 function Section$unrender(shouldDestroy) { 8745 this.fragments.forEach(shouldDestroy ? unrenderAndDestroy : prototype_unrender__unrender); 8746 this.renderedFragments = []; 8747 this.rendered = false;
8748 } 8749 8750 function unrenderAndDestroy(fragment) { 8751 fragment.unrender(true); 8752 } 8753 8754 function prototype_unrender__unrender(fragment) { 8755 fragment.unrender(false); 8756 } 8757 8758 var prototype_update = Section$update; 8759 8760 function Section$update() { 8761 var fragment, renderIndex, renderedFragments, anchor, target, i, len; 8762 8763 // `this.renderedFragments` is in the order of the previous render. 8764 // If fragments have shuffled about, this allows us to quickly 8765 // reinsert them in the correct place 8766 renderedFragments = this.renderedFragments; 8767 8768 // Remove fragments that have been marked for destruction 8769 while (fragment = this.fragmentsToUnrender.pop()) { 8770 fragment.unrender(true); 8771 renderedFragments.splice(renderedFragments.indexOf(fragment), 1); 8772 } 8773 8774 // Render new fragments (but don't insert them yet) 8775 while (fragment = this.fragmentsToRender.shift()) { 8776 fragment.render(); 8777 } 8778 8779 if (this.rendered) { 8780 target = this.parentFragment.getNode(); 8781 } 8782 8783 len = this.fragments.length; 8784 for (i = 0; i < len; i += 1) { 8785 fragment = this.fragments[i]; 8786 renderIndex = renderedFragments.indexOf(fragment, i); // search from current index - it's guaranteed to be the same or higher 8787 8788 if (renderIndex === i) { 8789 // already in the right place. insert accumulated nodes (if any) and carry on 8790 if (this.docFrag.childNodes.length) { 8791 anchor = fragment.firstNode(); 8792 target.insertBefore(this.docFrag, anchor); 8793 } 8794 8795 continue; 8796 } 8797 8798 this.docFrag.appendChild(fragment.detach()); 8799 8800 // update renderedFragments 8801 if (renderIndex !== -1) { 8802 renderedFragments.splice(renderIndex, 1); 8803 } 8804 renderedFragments.splice(i, 0, fragment); 8805 } 8806 8807 if (this.rendered && this.docFrag.childNodes.length) { 8808 anchor = this.parentFragment.findNextNode(this); 8809 target.insertBefore(this.docFrag, anchor); 8810 } 8811 8812 // Save the rendering order for next time 8813 this.renderedFragments = this.fragments.slice(); 8814 } 8815 8816 var Section = function (options) { 8817 this.type = SECTION; 8818 this.subtype = this.currentSubtype = options.template.n; 8819 this.inverted = this.subtype === SECTION_UNLESS; 8820 8821 this.pElement = options.pElement; 8822 8823 this.fragments = []; 8824 this.fragmentsToCreate = []; 8825 this.fragmentsToRender = []; 8826 this.fragmentsToUnrender = []; 8827 8828 if (options.template.i) { 8829 this.indexRefs = options.template.i.split(",").map(function (k, i) { 8830 return { n: k, t: i === 0 ? "k" : "i" }; 8831 }); 8832 } 8833 8834 this.renderedFragments = []; 8835 8836 this.length = 0; // number of times this section is rendered 8837 8838 Mustache.init(this, options); 8839 }; 8840 8841 Section.prototype = { 8842 bubble: Section_prototype_bubble, 8843 detach: Section_prototype_detach, 8844 find: find, 8845 findAll: findAll, 8846 findAllComponents: findAllComponents, 8847 findComponent: findComponent, 8848 findNextNode: findNextNode, 8849 firstNode: firstNode, 8850 getIndexRef: function (name) { 8851 if (this.indexRefs) { 8852 var i = this.indexRefs.length; 8853 while (i--) { 8854 var ref = this.indexRefs[i]; 8855 if (ref.n === name) { 8856 return ref; 8857 } 8858 } 8859 } 8860 }, 8861 getValue: Mustache.getValue, 8862 shuffle: shuffle, 8863 rebind: prototype_rebind, 8864 render: Section_prototype_render, 8865 resolve: Mustache.resolve, 8866 setValue: setValue, 8867 toString: prototype_toString, 8868 unbind: prototype_unbind, 8869 unrender: prototype_unrender, 8870 update: prototype_update 8871 }; 8872 8873 var _Section = Section; 8874 8875 var Triple_prototype_detach = Triple$detach; 8876 8877 function Triple$detach() { 8878 var len, i; 8879 8880 if (this.docFrag) { 8881 len = this.nodes.length; 8882 for (i = 0; i < len; i += 1) { 8883 this.docFrag.appendChild(this.nodes[i]); 8884 } 8885 8886 return this.docFrag; 8887 } 8888 } 8889 8890 var Triple_prototype_find = Triple$find; 8891 function Triple$find(selector) { 8892 var i, len, node, queryResult; 8893 8894 len = this.nodes.length; 8895 for (i = 0; i < len; i += 1) { 8896 node = this.nodes[i]; 8897 8898 if (node.nodeType !== 1) { 8899 continue; 8900 } 8901 8902 if (matches(node, selector)) { 8903 return node; 8904 } 8905 8906 if (queryResult = node.querySelector(selector)) { 8907 return queryResult; 8908 } 8909 } 8910 8911 return null; 8912 } 8913 8914 var Triple_prototype_findAll = Triple$findAll; 8915 function Triple$findAll(selector, queryResult) { 8916 var i, len, node, queryAllResult, numNodes, j; 8917 8918 len = this.nodes.length; 8919 for (i = 0; i < len; i += 1) { 8920 node = this.nodes[i]; 8921 8922 if (node.nodeType !== 1) { 8923 continue; 8924 } 8925 8926 if (matches(node, selector)) { 8927 queryResult.push(node); 8928 } 8929 8930 if (queryAllResult = node.querySelectorAll(selector)) { 8931 numNodes = queryAllResult.length; 8932 for (j = 0; j < numNodes; j += 1) { 8933 queryResult.push(queryAllResult[j]); 8934 } 8935 } 8936 } 8937 } 8938 8939 var Triple_prototype_firstNode = Triple$firstNode; 8940 8941 function Triple$firstNode() { 8942 if (this.rendered && this.nodes[0]) { 8943 return this.nodes[0]; 8944 } 8945 8946 return this.parentFragment.findNextNode(this); 8947 } 8948 8949 var elementCache = {}, 8950 ieBug, 8951 ieBlacklist; 8952 8953 try { 8954 createElement("table").innerHTML = "foo"; 8955 } catch (err) { 8956 ieBug = true; 8957 8958 ieBlacklist = { 8959 TABLE: ["<table class=\"x\">", "</table>"], 8960 THEAD: ["<table><thead class=\"x\">", "</thead></table>"], 8961 TBODY: ["<table><tbody class=\"x\">", "</tbody></table>"], 8962 TR: ["<table><tr class=\"x\">", "</tr></table>"], 8963 SELECT: ["<select class=\"x\">", "</select>"] 8964 }; 8965 } 8966 8967 var insertHtml = function (html, node, docFrag) { 8968 var container, 8969 nodes = [], 8970 wrapper, 8971 selectedOption, 8972 child, 8973 i; 8974 8975 // render 0 and false 8976 if (html != null && html !== "") { 8977 if (ieBug && (wrapper = ieBlacklist[node.tagName])) { 8978 container = element("DIV"); 8979 container.innerHTML = wrapper[0] + html + wrapper[1]; 8980 container = container.querySelector(".x"); 8981 8982 if (container.tagName === "SELECT") { 8983 selectedOption = container.options[container.selectedIndex]; 8984 } 8985 } else if (node.namespaceURI === namespaces.svg) { 8986 container = element("DIV"); 8987 container.innerHTML = "<svg class=\"x\">" + html + "</svg>"; 8988 container = container.querySelector(".x"); 8989 } else { 8990 container = element(node.tagName); 8991 container.innerHTML = html; 8992 8993 if (container.tagName === "SELECT") { 8994 selectedOption = container.options[container.selectedIndex]; 8995 } 8996 } 8997 8998 while (child = container.firstChild) { 8999 nodes.push(child); 9000 docFrag.appendChild(child); 9001 } 9002 9003 // This is really annoying. Extracting <option> nodes from the 9004 // temporary container <select> causes the remaining ones to 9005 // become selected. So now we have to deselect them. IE8, you 9006 // amaze me. You really do 9007 // ...and now Chrome too 9008 if (node.tagName === "SELECT") { 9009 i = nodes.length; 9010 while (i--) { 9011 if (nodes[i] !== selectedOption) { 9012 nodes[i].selected = false;
9013 } 9014 } 9015 } 9016 } 9017 9018 return nodes; 9019 }; 9020 9021 function element(tagName) { 9022 return elementCache[tagName] || (elementCache[tagName] = createElement(tagName)); 9023 } 9024 9025 var helpers_updateSelect = updateSelect; 9026 9027 function updateSelect(parentElement) { 9028 var selectedOptions, option, value; 9029 9030 if (!parentElement || parentElement.name !== "select" || !parentElement.binding) { 9031 return; 9032 } 9033 9034 selectedOptions = toArray(parentElement.node.options).filter(isSelected); 9035 9036 // If one of them had a `selected` attribute, we need to sync 9037 // the model to the view 9038 if (parentElement.getAttribute("multiple")) { 9039 value = selectedOptions.map(function (o) { 9040 return o.value; 9041 }); 9042 } else if (option = selectedOptions[0]) { 9043 value = option.value; 9044 } 9045 9046 if (value !== undefined) { 9047 parentElement.binding.setValue(value); 9048 } 9049 9050 parentElement.bubble(); 9051 } 9052 9053 function isSelected(option) { 9054 return option.selected; 9055 } 9056 9057 var Triple_prototype_render = Triple$render; 9058 function Triple$render() { 9059 if (this.rendered) { 9060 throw new Error("Attempted to render an item that was already rendered"); 9061 } 9062 9063 this.docFrag = document.createDocumentFragment(); 9064 this.nodes = insertHtml(this.value, this.parentFragment.getNode(), this.docFrag); 9065 9066 // Special case - we're inserting the contents of a <select> 9067 helpers_updateSelect(this.pElement); 9068 9069 this.rendered = true; 9070 return this.docFrag; 9071 } 9072 9073 var prototype_setValue = Triple$setValue; 9074 function Triple$setValue(value) { 9075 var wrapper; 9076 9077 // TODO is there a better way to approach this? 9078 if (wrapper = this.root.viewmodel.wrapped[this.keypath.str]) { 9079 value = wrapper.get(); 9080 } 9081 9082 if (value !== this.value) { 9083 this.value = value; 9084 this.parentFragment.bubble(); 9085 9086 if (this.rendered) { 9087 global_runloop.addView(this); 9088 } 9089 } 9090 } 9091 9092 var Triple_prototype_toString = Triple$toString; 9093 function Triple$toString() { 9094 return this.value != undefined ? decodeCharacterReferences("" + this.value) : ""; 9095 } 9096 9097 var Triple_prototype_unrender = Triple$unrender; 9098 function Triple$unrender(shouldDestroy) { 9099 if (this.rendered && shouldDestroy) { 9100 this.nodes.forEach(detachNode); 9101 this.rendered = false; 9102 } 9103 9104 // TODO update live queries 9105 } 9106 9107 var Triple_prototype_update = Triple$update; 9108 function Triple$update() { 9109 var node, parentNode; 9110 9111 if (!this.rendered) { 9112 return; 9113 } 9114 9115 // Remove existing nodes 9116 while (this.nodes && this.nodes.length) { 9117 node = this.nodes.pop(); 9118 node.parentNode.removeChild(node); 9119 } 9120 9121 // Insert new nodes 9122 parentNode = this.parentFragment.getNode(); 9123 9124 this.nodes = insertHtml(this.value, parentNode, this.docFrag); 9125 parentNode.insertBefore(this.docFrag, this.parentFragment.findNextNode(this)); 9126 9127 // Special case - we're inserting the contents of a <select> 9128 helpers_updateSelect(this.pElement); 9129 } 9130 9131 var Triple = function (options) { 9132 this.type = TRIPLE; 9133 Mustache.init(this, options); 9134 }; 9135 9136 Triple.prototype = { 9137 detach: Triple_prototype_detach, 9138 find: Triple_prototype_find, 9139 findAll: Triple_prototype_findAll, 9140 firstNode: Triple_prototype_firstNode, 9141 getValue: Mustache.getValue, 9142 rebind: Mustache.rebind, 9143 render: Triple_prototype_render, 9144 resolve: Mustache.resolve, 9145 setValue: prototype_setValue, 9146 toString: Triple_prototype_toString, 9147 unbind: shared_unbind, 9148 unrender: Triple_prototype_unrender, 9149 update: Triple_prototype_update 9150 }; 9151 9152 var _Triple = Triple; 9153 9154 var Element_prototype_bubble = function () { 9155 this.parentFragment.bubble(); 9156 }; 9157 9158 var Element_prototype_detach = Element$detach; 9159 9160 function Element$detach() { 9161 var node = this.node, 9162 parentNode; 9163 9164 if (node) { 9165 // need to check for parent node - DOM may have been altered 9166 // by something other than Ractive! e.g. jQuery UI... 9167 if (parentNode = node.parentNode) { 9168 parentNode.removeChild(node); 9169 } 9170 9171 return node; 9172 } 9173 } 9174 9175 var Element_prototype_find = function (selector) { 9176 if (!this.node) { 9177 // this element hasn't been rendered yet 9178 return null; 9179 } 9180 9181 if (matches(this.node, selector)) { 9182 return this.node; 9183 } 9184 9185 if (this.fragment && this.fragment.find) { 9186 return this.fragment.find(selector); 9187 } 9188 }; 9189 9190 var Element_prototype_findAll = function (selector, query) { 9191 // Add this node to the query, if applicable, and register the 9192 // query on this element 9193 if (query._test(this, true) && query.live) { 9194 (this.liveQueries || (this.liveQueries = [])).push(query); 9195 } 9196 9197 if (this.fragment) { 9198 this.fragment.findAll(selector, query); 9199 } 9200 }; 9201 9202 var Element_prototype_findAllComponents = function (selector, query) { 9203 if (this.fragment) { 9204 this.fragment.findAllComponents(selector, query); 9205 } 9206 }; 9207 9208 var Element_prototype_findComponent = function (selector) { 9209 if (this.fragment) { 9210 return this.fragment.findComponent(selector); 9211 } 9212 }; 9213 9214 var Element_prototype_findNextNode = Element$findNextNode; 9215 9216 function Element$findNextNode() { 9217 return null; 9218 } 9219 9220 var Element_prototype_firstNode = Element$firstNode; 9221 9222 function Element$firstNode() { 9223 return this.node; 9224 } 9225 9226 var getAttribute = Element$getAttribute; 9227 9228 function Element$getAttribute(name) { 9229 if (!this.attributes || !this.attributes[name]) { 9230 return; 9231 } 9232 9233 return this.attributes[name].value; 9234 } 9235 9236 var truthy = /^true|on|yes|1$/i; 9237 var processBindingAttributes__isNumeric = /^[0-9]+$/; 9238 9239 var processBindingAttributes = function (element, template) { 9240 var val, attrs, attributes; 9241 9242 attributes = template.a || {}; 9243 attrs = {}; 9244 9245 // attributes that are present but don't have a value (=) 9246 // will be set to the number 0, which we condider to be true 9247 // the string '0', however is false 9248 9249 val = attributes.twoway; 9250 if (val !== undefined) { 9251 attrs.twoway = val === 0 || truthy.test(val); 9252 } 9253 9254 val = attributes.lazy; 9255 if (val !== undefined) { 9256 // check for timeout value 9257 if (val !== 0 && processBindingAttributes__isNumeric.test(val)) { 9258 attrs.lazy = parseInt(val); 9259 } else { 9260 attrs.lazy = val === 0 || truthy.test(val); 9261 } 9262 } 9263 9264 return attrs; 9265 }; 9266 9267 var Attribute_prototype_bubble = Attribute$bubble; 9268 function Attribute$bubble() { 9269 var value = this.useProperty || !this.rendered ? this.fragment.getValue() : this.fragment.toString(); 9270 9271 // TODO this can register the attribute multiple times (see render test 9272 // 'Attribute with nested mustaches') 9273 if (!isEqual(value, this.value)) { 9274 9275 // Need to clear old id from ractive.nodes
9276 if (this.name === "id" && this.value) { 9277 delete this.root.nodes[this.value]; 9278 } 9279 9280 this.value = value; 9281 9282 if (this.name === "value" && this.node) { 9283 // We need to store the value on the DOM like this so we 9284 // can retrieve it later without it being coerced to a string 9285 this.node._ractive.value = value; 9286 } 9287 9288 if (this.rendered) { 9289 global_runloop.addView(this); 9290 } 9291 } 9292 } 9293 9294 var svgCamelCaseElements, svgCamelCaseAttributes, createMap, map; 9295 svgCamelCaseElements = "altGlyph altGlyphDef altGlyphItem animateColor animateMotion animateTransform clipPath feBlend feColorMatrix feComponentTransfer feComposite feConvolveMatrix feDiffuseLighting feDisplacementMap feDistantLight feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur feImage feMerge feMergeNode feMorphology feOffset fePointLight feSpecularLighting feSpotLight feTile feTurbulence foreignObject glyphRef linearGradient radialGradient textPath vkern".split(" "); 9296 svgCamelCaseAttributes = "attributeName attributeType baseFrequency baseProfile calcMode clipPathUnits contentScriptType contentStyleType diffuseConstant edgeMode externalResourcesRequired filterRes filterUnits glyphRef gradientTransform gradientUnits kernelMatrix kernelUnitLength keyPoints keySplines keyTimes lengthAdjust limitingConeAngle markerHeight markerUnits markerWidth maskContentUnits maskUnits numOctaves pathLength patternContentUnits patternTransform patternUnits pointsAtX pointsAtY pointsAtZ preserveAlpha preserveAspectRatio primitiveUnits refX refY repeatCount repeatDur requiredExtensions requiredFeatures specularConstant specularExponent spreadMethod startOffset stdDeviation stitchTiles surfaceScale systemLanguage tableValues targetX targetY textLength viewBox viewTarget xChannelSelector yChannelSelector zoomAndPan".split(" "); 9297 9298 createMap = function (items) { 9299 var map = {}, 9300 i = items.length; 9301 while (i--) { 9302 map[items[i].toLowerCase()] = items[i]; 9303 } 9304 return map; 9305 }; 9306 9307 map = createMap(svgCamelCaseElements.concat(svgCamelCaseAttributes)); 9308 9309 var enforceCase = function (elementName) { 9310 var lowerCaseElementName = elementName.toLowerCase(); 9311 return map[lowerCaseElementName] || lowerCaseElementName; 9312 }; 9313 9314 var determineNameAndNamespace = function (attribute, name) { 9315 var colonIndex, namespacePrefix; 9316 9317 // are we dealing with a namespaced attribute, e.g. xlink:href? 9318 colonIndex = name.indexOf(":"); 9319 if (colonIndex !== -1) { 9320 9321 // looks like we are, yes... 9322 namespacePrefix = name.substr(0, colonIndex); 9323 9324 // ...unless it's a namespace *declaration*, which we ignore (on the assumption 9325 // that only valid namespaces will be used) 9326 if (namespacePrefix !== "xmlns") { 9327 name = name.substring(colonIndex + 1); 9328 9329 attribute.name = enforceCase(name); 9330 attribute.namespace = namespaces[namespacePrefix.toLowerCase()]; 9331 attribute.namespacePrefix = namespacePrefix; 9332 9333 if (!attribute.namespace) { 9334 throw "Unknown namespace (\"" + namespacePrefix + "\")"; 9335 } 9336 9337 return; 9338 } 9339 } 9340 9341 // SVG attribute names are case sensitive 9342 attribute.name = attribute.element.namespace !== namespaces.html ? enforceCase(name) : name; 9343 }; 9344 9345 var helpers_getInterpolator = getInterpolator; 9346 function getInterpolator(attribute) { 9347 var items = attribute.fragment.items; 9348 9349 if (items.length !== 1) { 9350 return; 9351 } 9352 9353 if (items[0].type === INTERPOLATOR) { 9354 return items[0]; 9355 } 9356 } 9357 9358 var prototype_init = Attribute$init; 9359 function Attribute$init(options) { 9360 this.type = ATTRIBUTE; 9361 this.element = options.element; 9362 this.root = options.root; 9363 9364 determineNameAndNamespace(this, options.name); 9365 this.isBoolean = booleanAttributes.test(this.name); 9366 9367 // if it's an empty attribute, or just a straight key-value pair, with no 9368 // mustache shenanigans, set the attribute accordingly and go home 9369 if (!options.value || typeof options.value === "string") { 9370 this.value = this.isBoolean ? true : options.value || ""; 9371 return; 9372 } 9373 9374 // otherwise we need to do some work 9375 9376 // share parentFragment with parent element 9377 this.parentFragment = this.element.parentFragment; 9378 9379 this.fragment = new virtualdom_Fragment({ 9380 template: options.value, 9381 root: this.root, 9382 owner: this 9383 }); 9384 9385 // TODO can we use this.fragment.toString() in some cases? It's quicker 9386 this.value = this.fragment.getValue(); 9387 9388 // Store a reference to this attribute's interpolator, if its fragment 9389 // takes the form `{{foo}}`. This is necessary for two-way binding and 9390 // for correctly rendering HTML later 9391 this.interpolator = helpers_getInterpolator(this); 9392 this.isBindable = !!this.interpolator && !this.interpolator.isStatic; 9393 9394 // mark as ready 9395 this.ready = true; 9396 } 9397 9398 var Attribute_prototype_rebind = Attribute$rebind; 9399 9400 function Attribute$rebind(oldKeypath, newKeypath) { 9401 if (this.fragment) { 9402 this.fragment.rebind(oldKeypath, newKeypath); 9403 } 9404 } 9405 9406 var Attribute_prototype_render = Attribute$render; 9407 var propertyNames = { 9408 "accept-charset": "acceptCharset", 9409 accesskey: "accessKey", 9410 bgcolor: "bgColor", 9411 "class": "className", 9412 codebase: "codeBase",
9413 colspan: "colSpan", 9414 contenteditable: "contentEditable", 9415 datetime: "dateTime", 9416 dirname: "dirName", 9417 "for": "htmlFor", 9418 "http-equiv": "httpEquiv", 9419 ismap: "isMap", 9420 maxlength: "maxLength", 9421 novalidate: "noValidate", 9422 pubdate: "pubDate", 9423 readonly: "readOnly", 9424 rowspan: "rowSpan", 9425 tabindex: "tabIndex", 9426 usemap: "useMap" 9427 }; 9428 function Attribute$render(node) { 9429 var propertyName; 9430 9431 this.node = node; 9432 9433 // should we use direct property access, or setAttribute? 9434 if (!node.namespaceURI || node.namespaceURI === namespaces.html) { 9435 propertyName = propertyNames[this.name] || this.name; 9436 9437 if (node[propertyName] !== undefined) { 9438 this.propertyName = propertyName; 9439 } 9440 9441 // is attribute a boolean attribute or 'value'? If so we're better off doing e.g. 9442 // node.selected = true rather than node.setAttribute( 'selected', '' ) 9443 if (this.isBoolean || this.isTwoway) { 9444 this.useProperty = true; 9445 } 9446 9447 if (propertyName === "value") { 9448 node._ractive.value = this.value; 9449 } 9450 } 9451 9452 this.rendered = true; 9453 this.update(); 9454 } 9455 9456 var Attribute_prototype_toString = Attribute$toString; 9457 9458 function Attribute$toString() { 9459 var _ref = this; 9460 9461 var name = _ref.name; 9462 var namespacePrefix = _ref.namespacePrefix; 9463 var value = _ref.value; 9464 var interpolator = _ref.interpolator; 9465 var fragment = _ref.fragment; 9466 9467 // Special case - select and textarea values (should not be stringified) 9468 if (name === "value" && (this.element.name === "select" || this.element.name === "textarea")) { 9469 return; 9470 } 9471 9472 // Special case - content editable 9473 if (name === "value" && this.element.getAttribute("contenteditable") !== undefined) { 9474 return; 9475 } 9476 9477 // Special case - radio names 9478 if (name === "name" && this.element.name === "input" && interpolator) { 9479 return "name={{" + (interpolator.keypath.str || interpolator.ref) + "}}"; 9480 } 9481 9482 // Boolean attributes 9483 if (this.isBoolean) { 9484 return value ? name : ""; 9485 } 9486 9487 if (fragment) { 9488 // special case - this catches undefined/null values (#1211) 9489 if (fragment.items.length === 1 && fragment.items[0].value == null) { 9490 return ""; 9491 } 9492 9493 value = fragment.toString(); 9494 } 9495 9496 if (namespacePrefix) { 9497 name = namespacePrefix + ":" + name; 9498 } 9499 9500 return value ? name + "=\"" + Attribute_prototype_toString__escape(value) + "\"" : name; 9501 } 9502 9503 function Attribute_prototype_toString__escape(value) { 9504 return value.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'"); 9505 } 9506 9507 var Attribute_prototype_unbind = Attribute$unbind; 9508 9509 function Attribute$unbind() { 9510 // ignore non-dynamic attributes 9511 if (this.fragment) { 9512 this.fragment.unbind(); 9513 } 9514 9515 if (this.name === "id") { 9516 delete this.root.nodes[this.value]; 9517 } 9518 } 9519 9520 var updateSelectValue = Attribute$updateSelect; 9521 9522 function Attribute$updateSelect() { 9523 var value = this.value, 9524 options, 9525 option, 9526 optionValue, 9527 i; 9528 9529 if (!this.locked) { 9530 this.node._ractive.value = value; 9531 9532 options = this.node.options; 9533 i = options.length; 9534 9535 while (i--) { 9536 option = options[i]; 9537 optionValue = option._ractive ? option._ractive.value : option.value; // options inserted via a triple don't have _ractive 9538 9539 if (optionValue == value) { 9540 // double equals as we may be comparing numbers with strings 9541 option.selected = true; 9542 break; 9543 } 9544 } 9545 } 9546 9547 // if we're still here, it means the new value didn't match any of the options... 9548 // TODO figure out what to do in this situation 9549 } 9550 9551 var updateMultipleSelectValue = Attribute$updateMultipleSelect; 9552 function Attribute$updateMultipleSelect() { 9553 var value = this.value, 9554 options, 9555 i, 9556 option, 9557 optionValue; 9558 9559 if (!isArray(value)) { 9560 value = [value]; 9561 } 9562 9563 options = this.node.options; 9564 i = options.length; 9565 9566 while (i--) { 9567 option = options[i]; 9568 optionValue = option._ractive ? option._ractive.value : option.value; // options inserted via a triple don't have _ractive 9569 option.selected = arrayContains(value, optionValue); 9570 } 9571 } 9572 9573 var updateRadioName = Attribute$updateRadioName; 9574 9575 function Attribute$updateRadioName() { 9576 var _ref = this; 9577 9578 var node = _ref.node; 9579 var value = _ref.value; 9580 9581 node.checked = value == node._ractive.value; 9582 } 9583 9584 var updateRadioValue = Attribute$updateRadioValue; 9585 function Attribute$updateRadioValue() { 9586 var wasChecked, 9587 node = this.node, 9588 binding, 9589 bindings, 9590 i; 9591 9592 wasChecked = node.checked; 9593 9594 node.value = this.element.getAttribute("value"); 9595 node.checked = this.element.getAttribute("value") === this.element.getAttribute("name"); 9596 9597 // This is a special case - if the input was checked, and the value 9598 // changed so that it's no longer checked, the twoway binding is 9599 // most likely out of date. To fix it we have to jump through some 9600 // hoops... this is a little kludgy but it works 9601 if (wasChecked && !node.checked && this.element.binding) { 9602 bindings = this.element.binding.siblings; 9603 9604 if (i = bindings.length) { 9605 while (i--) { 9606 binding = bindings[i]; 9607 9608 if (!binding.element.node) { 9609 // this is the initial render, siblings are still rendering! 9610 // we'll come back later... 9611 return; 9612 } 9613 9614 if (binding.element.node.checked) { 9615 global_runloop.addRactive(binding.root); 9616 return binding.handleChange(); 9617 } 9618 } 9619 9620 this.root.viewmodel.set(binding.keypath, undefined); 9621 } 9622 } 9623 } 9624 9625 var updateCheckboxName = Attribute$updateCheckboxName; 9626 function Attribute$updateCheckboxName() { 9627 var _ref = this; 9628 9629 var element = _ref.element; 9630 var node = _ref.node; 9631 var value = _ref.value;var binding = element.binding;var valueAttribute;var i; 9632 9633 valueAttribute = element.getAttribute("value"); 9634 9635 if (!isArray(value)) { 9636 binding.isChecked = node.checked = value == valueAttribute; 9637 } else { 9638 i = value.length; 9639 while (i--) { 9640 if (valueAttribute == value[i]) { 9641 binding.isChecked = node.checked = true; 9642 return; 9643 } 9644 } 9645 binding.isChecked = node.checked = false;
9646 } 9647 } 9648 9649 var updateClassName = Attribute$updateClassName; 9650 function Attribute$updateClassName() { 9651 this.node.className = safeToStringValue(this.value); 9652 } 9653 9654 var updateIdAttribute = Attribute$updateIdAttribute; 9655 9656 function Attribute$updateIdAttribute() { 9657 var _ref = this; 9658 9659 var node = _ref.node; 9660 var value = _ref.value; 9661 9662 this.root.nodes[value] = node; 9663 node.id = value; 9664 } 9665 9666 var updateIEStyleAttribute = Attribute$updateIEStyleAttribute; 9667 9668 function Attribute$updateIEStyleAttribute() { 9669 var node, value; 9670 9671 node = this.node; 9672 value = this.value; 9673 9674 if (value === undefined) { 9675 value = ""; 9676 } 9677 9678 node.style.setAttribute("cssText", value); 9679 } 9680 9681 var updateContentEditableValue = Attribute$updateContentEditableValue; 9682 9683 function Attribute$updateContentEditableValue() { 9684 var value = this.value; 9685 9686 if (value === undefined) { 9687 value = ""; 9688 } 9689 9690 if (!this.locked) { 9691 this.node.innerHTML = value; 9692 } 9693 } 9694 9695 var updateValue = Attribute$updateValue; 9696 9697 function Attribute$updateValue() { 9698 var _ref = this; 9699 9700 var node = _ref.node; 9701 var value = _ref.value; 9702 9703 // store actual value, so it doesn't get coerced to a string 9704 node._ractive.value = value; 9705 9706 // with two-way binding, only update if the change wasn't initiated by the user 9707 // otherwise the cursor will often be sent to the wrong place 9708 if (!this.locked) { 9709 node.value = value == undefined ? "" : value; 9710 } 9711 } 9712 9713 var updateBoolean = Attribute$updateBooleanAttribute; 9714 9715 function Attribute$updateBooleanAttribute() { 9716 // with two-way binding, only update if the change wasn't initiated by the user 9717 // otherwise the cursor will often be sent to the wrong place 9718 if (!this.locked) { 9719 this.node[this.propertyName] = this.value; 9720 } 9721 } 9722 9723 var updateEverythingElse = Attribute$updateEverythingElse; 9724 9725 function Attribute$updateEverythingElse() { 9726 var _ref = this; 9727 9728 var node = _ref.node; 9729 var namespace = _ref.namespace; 9730 var name = _ref.name; 9731 var value = _ref.value; 9732 var fragment = _ref.fragment; 9733 9734 if (namespace) { 9735 node.setAttributeNS(namespace, name, (fragment || value).toString()); 9736 } else if (!this.isBoolean) { 9737 if (value == null) { 9738 node.removeAttribute(name); 9739 } else { 9740 node.setAttribute(name, (fragment || value).toString()); 9741 } 9742 } 9743 9744 // Boolean attributes - truthy becomes '', falsy means 'remove attribute' 9745 else { 9746 if (value) { 9747 node.setAttribute(name, ""); 9748 } else { 9749 node.removeAttribute(name); 9750 } 9751 } 9752 } 9753 9754 // There are a few special cases when it comes to updating attributes. For this reason, 9755 // the prototype .update() method points to this method, which waits until the 9756 // attribute has finished initialising, then replaces the prototype method with a more 9757 // suitable one. That way, we save ourselves doing a bunch of tests on each call 9758 var Attribute_prototype_update = Attribute$update; 9759 function Attribute$update() { 9760 var _ref = this; 9761 9762 var name = _ref.name; 9763 var element = _ref.element; 9764 var node = _ref.node;var type;var updateMethod; 9765 9766 if (name === "id") { 9767 updateMethod = updateIdAttribute; 9768 } else if (name === "value") { 9769 // special case - selects 9770 if (element.name === "select" && name === "value") { 9771 updateMethod = element.getAttribute("multiple") ? updateMultipleSelectValue : updateSelectValue; 9772 } else if (element.name === "textarea") { 9773 updateMethod = updateValue; 9774 } 9775 9776 // special case - contenteditable 9777 else if (element.getAttribute("contenteditable") != null) { 9778 updateMethod = updateContentEditableValue; 9779 } 9780 9781 // special case - <input> 9782 else if (element.name === "input") { 9783 type = element.getAttribute("type"); 9784 9785 // type='file' value='{{fileList}}'> 9786 if (type === "file") { 9787 updateMethod = noop; // read-only 9788 } 9789 9790 // type='radio' name='{{twoway}}' 9791 else if (type === "radio" && element.binding && element.binding.name === "name") { 9792 updateMethod = updateRadioValue; 9793 } else { 9794 updateMethod = updateValue; 9795 } 9796 } 9797 } 9798 9799 // special case - <input type='radio' name='{{twoway}}' value='foo'> 9800 else if (this.isTwoway && name === "name") { 9801 if (node.type === "radio") { 9802 updateMethod = updateRadioName; 9803 } else if (node.type === "checkbox") { 9804 updateMethod = updateCheckboxName; 9805 } 9806 } 9807 9808 // special case - style attributes in Internet Exploder 9809 else if (name === "style" && node.style.setAttribute) { 9810 updateMethod = updateIEStyleAttribute; 9811 } 9812
9813 // special case - class names. IE fucks things up, again 9814 else if (name === "class" && (!node.namespaceURI || node.namespaceURI === namespaces.html)) { 9815 updateMethod = updateClassName; 9816 } else if (this.useProperty) { 9817 updateMethod = updateBoolean; 9818 } 9819 9820 if (!updateMethod) { 9821 updateMethod = updateEverythingElse; 9822 } 9823 9824 this.update = updateMethod; 9825 this.update(); 9826 } 9827 9828 var Attribute = function (options) { 9829 this.init(options); 9830 }; 9831 9832 Attribute.prototype = { 9833 bubble: Attribute_prototype_bubble, 9834 init: prototype_init, 9835 rebind: Attribute_prototype_rebind, 9836 render: Attribute_prototype_render, 9837 toString: Attribute_prototype_toString, 9838 unbind: Attribute_prototype_unbind, 9839 update: Attribute_prototype_update 9840 }; 9841 9842 var _Attribute = Attribute; 9843 9844 var createAttributes = function (element, attributes) { 9845 var name, 9846 attribute, 9847 result = []; 9848 9849 for (name in attributes) { 9850 // skip binding attributes 9851 if (name === "twoway" || name === "lazy") { 9852 continue; 9853 } 9854 9855 if (attributes.hasOwnProperty(name)) { 9856 attribute = new _Attribute({ 9857 element: element, 9858 name: name, 9859 value: attributes[name], 9860 root: element.root 9861 }); 9862 9863 result[name] = attribute; 9864 9865 if (name !== "value") { 9866 result.push(attribute); 9867 } 9868 } 9869 } 9870 9871 // value attribute goes last. This is because it 9872 // may get clamped on render otherwise, e.g. in 9873 // `<input type='range' value='999' min='0' max='1000'>` 9874 // since default max is 100 9875 if (attribute = result.value) { 9876 result.push(attribute); 9877 } 9878 9879 return result; 9880 }; 9881 9882 var _ConditionalAttribute__div; 9883 9884 if (typeof document !== "undefined") { 9885 _ConditionalAttribute__div = createElement("div"); 9886 } 9887 9888 var ConditionalAttribute = function (element, template) { 9889 this.element = element; 9890 this.root = element.root; 9891 this.parentFragment = element.parentFragment; 9892 9893 this.attributes = []; 9894 9895 this.fragment = new virtualdom_Fragment({ 9896 root: element.root, 9897 owner: this, 9898 template: [template] 9899 }); 9900 }; 9901 9902 ConditionalAttribute.prototype = { 9903 bubble: function () { 9904 if (this.node) { 9905 this.update(); 9906 } 9907 9908 this.element.bubble(); 9909 }, 9910 9911 rebind: function (oldKeypath, newKeypath) { 9912 this.fragment.rebind(oldKeypath, newKeypath); 9913 }, 9914 9915 render: function (node) { 9916 this.node = node; 9917 this.isSvg = node.namespaceURI === namespaces.svg; 9918 9919 this.update(); 9920 }, 9921 9922 unbind: function () { 9923 this.fragment.unbind(); 9924 }, 9925 9926 update: function () { 9927 var _this = this; 9928 9929 var str, attrs; 9930 9931 str = this.fragment.toString(); 9932 attrs = parseAttributes(str, this.isSvg); 9933 9934 // any attributes that previously existed but no longer do 9935 // must be removed 9936 this.attributes.filter(function (a) { 9937 return notIn(attrs, a); 9938 }
9938).forEach(function (a) { 9939 _this.node.removeAttribute(a.name); 9940 }); 9941 9942 attrs.forEach(function (a) { 9943 _this.node.setAttribute(a.name, a.value); 9944 }); 9945 9946 this.attributes = attrs; 9947 }, 9948 9949 toString: function () { 9950 return this.fragment.toString(); 9951 } 9952 }; 9953 9954 var _ConditionalAttribute = ConditionalAttribute; 9955 9956 function parseAttributes(str, isSvg) { 9957 var tag = isSvg ? "svg" : "div"; 9958 _ConditionalAttribute__div.innerHTML = "<" + tag + " " + str + "></" + tag + ">"; 9959 9960 return toArray(_ConditionalAttribute__div.childNodes[0].attributes); 9961 } 9962 9963 function notIn(haystack, needle) { 9964 var i = haystack.length; 9965 9966 while (i--) { 9967 if (haystack[i].name === needle.name) { 9968 return false; 9969 } 9970 } 9971 9972 return true; 9973 } 9974 9975 var createConditionalAttributes = function (element, attributes) { 9976 if (!attributes) { 9977 return []; 9978 } 9979 9980 return attributes.map(function (a) { 9981 return new _ConditionalAttribute(element, a); 9982 }); 9983 }; 9984 9985 var Binding = function (element) { 9986 var interpolator, keypath, value, parentForm; 9987 9988 this.element = element; 9989 this.root = element.root; 9990 this.attribute = element.attributes[this.name || "value"]; 9991 9992 interpolator = this.attribute.interpolator; 9993 interpolator.twowayBinding = this; 9994 9995 if (keypath = interpolator.keypath) { 9996 if (keypath.str.slice(-1) === "}") { 9997 warnOnceIfDebug("Two-way binding does not work with expressions (`%s` on <%s>)", interpolator.resolver.uniqueString, element.name, { ractive: this.root }); 9998 return false; 9999 } 10000 10001 if (keypath.isSpecial) { 10002 warnOnceIfDebug("Two-way binding does not work with %s", interpolator.resolver.ref, { ractive: this.root }); 10003 return false; 10004 } 10005 } else { 10006 // A mustache may be *ambiguous*. Let's say we were given 10007 // `value="{{bar}}"`. If the context was `foo`, and `foo.bar` 10008 // *wasn't* `undefined`, the keypath would be `foo.bar`. 10009 // Then, any user input would result in `foo.bar` being updated. 10010 // 10011 // If, however, `foo.bar` *was* undefined, and so was `bar`, we would be 10012 // left with an unresolved partial keypath - so we are forced to make an 10013 // assumption. That assumption is that the input in question should 10014 // be forced to resolve to `bar`, and any user input would affect `bar` 10015 // and not `foo.bar`. 10016 // 10017 // Did that make any sense? No? Oh. Sorry. Well the moral of the story is 10018 // be explicit when using two-way data-binding about what keypath you're 10019 // updating. Using it in lists is probably a recipe for confusion... 10020 var ref = interpolator.template.r ? "'" + interpolator.template.r + "' reference" : "expression"; 10021 warnIfDebug("The %s being used for two-way binding is ambiguous, and may cause unexpected results. Consider initialising your data to eliminate the ambiguity", ref, { ractive: this.root }); 10022 interpolator.resolver.forceResolution(); 10023 keypath = interpolator.keypath; 10024 } 10025 10026 this.attribute.isTwoway = true; 10027 this.keypath = keypath; 10028 10029 // initialise value, if it's undefined 10030 value = this.root.viewmodel.get(keypath); 10031 10032 if (value === undefined && this.getInitialValue) { 10033 value = this.getInitialValue(); 10034 10035 if (value !== undefined) { 10036 this.root.viewmodel.set(keypath, value); 10037 } 10038 } 10039 10040 if (parentForm = findParentForm(element)) { 10041 this.resetValue = value; 10042 parentForm.formBindings.push(this); 10043 } 10044 }; 10045 10046 Binding.prototype = { 10047 handleChange: function () { 10048 var _this = this; 10049 10050 global_runloop.start(this.root); 10051 this.attribute.locked = true; 10052 this.root.viewmodel.set(this.keypath, this.getValue()); 10053 global_runloop.scheduleTask(function () { 10054 return _this.attribute.locked = false; 10055 }); 10056 global_runloop.end(); 10057 }, 10058 10059 rebound: function () { 10060 var bindings, oldKeypath, newKeypath; 10061 10062 oldKeypath = this.keypath; 10063 newKeypath = this.attribute.interpolator.keypath; 10064 10065 // The attribute this binding is linked to has already done the work 10066 if (oldKeypath === newKeypath) { 10067 return; 10068 } 10069 10070 removeFromArray(this.root._twowayBindings[oldKeypath.str], this); 10071 10072 this.keypath = newKeypath; 10073 10074 bindings = this.root._twowayBindings[newKeypath.str] || (this.root._twowayBindings[newKeypath.str] = []); 10075 bindings.push(this); 10076 }, 10077 10078 unbind: function () {} 10079 }; 10080 10081 Binding.extend = function (properties) { 10082 var Parent = this, 10083 SpecialisedBinding; 10084 10085 SpecialisedBinding = function (element) { 10086 Binding.call(this, element); 10087 10088 if (this.init) { 10089 this.init(); 10090 } 10091 }; 10092 10093 SpecialisedBinding.prototype = create(Parent.prototype); 10094 utils_object__extend(SpecialisedBinding.prototype, properties); 10095 10096 SpecialisedBinding.extend = Binding.extend; 10097 10098 return SpecialisedBinding; 10099 }; 10100 10101 var Binding_Binding = Binding; 10102 10103 function findParentForm(element) { 10104 while (element = element.parent) { 10105 if (element.name === "form") { 10106 return element; 10107 } 10108 } 10109 } 10110 10111 // this is called when the element is unbound. 10112 // Specialised bindings can override it 10113 10114 // This is the handler for DOM events that would lead to a change in the model 10115 // (i.e. change, sometimes, input, and occasionally click and keyup) 10116 var handleDomEvent = handleChange; 10117 10118 function handleChange() { 10119 this._ractive.binding.handleChange(); 10120 } 10121 10122 var GenericBinding; 10123 10124 GenericBinding = Binding_Binding.extend({ 10125 getInitialValue: function () { 10126 return ""; 10127 }, 10128 10129 getValue: function () { 10130 return this.element.node.value; 10131 }, 10132 10133 render: function () { 10134 var node = this.element.node, 10135 lazy, 10136 timeout = false;
10137 this.rendered = true; 10138 10139 // any lazy setting for this element overrides the root 10140 // if the value is a number, it's a timeout 10141 lazy = this.root.lazy; 10142 if (this.element.lazy === true) { 10143 lazy = true; 10144 } else if (this.element.lazy === false) { 10145 lazy = false; 10146 } else if (is__isNumeric(this.element.lazy)) { 10147 lazy = false; 10148 timeout = +this.element.lazy; 10149 } else if (is__isNumeric(lazy || "")) { 10150 timeout = +lazy; 10151 lazy = false; 10152 10153 // make sure the timeout is available to the handler 10154 this.element.lazy = timeout; 10155 } 10156 10157 this.handler = timeout ? handleDelay : handleDomEvent; 10158 10159 node.addEventListener("change", handleDomEvent, false); 10160 10161 if (!lazy) { 10162 node.addEventListener("input", this.handler, false); 10163 10164 if (node.attachEvent) { 10165 node.addEventListener("keyup", this.handler, false); 10166 } 10167 } 10168 10169 node.addEventListener("blur", handleBlur, false); 10170 }, 10171 10172 unrender: function () { 10173 var node = this.element.node; 10174 this.rendered = false; 10175 10176 node.removeEventListener("change", handleDomEvent, false); 10177 node.removeEventListener("input", this.handler, false); 10178 node.removeEventListener("keyup", this.handler, false); 10179 node.removeEventListener("blur", handleBlur, false); 10180 } 10181 }); 10182 10183 var Binding_GenericBinding = GenericBinding; 10184 10185 function handleBlur() { 10186 var value; 10187 10188 handleDomEvent.call(this); 10189 10190 value = this._ractive.root.viewmodel.get(this._ractive.binding.keypath); 10191 this.value = value == undefined ? "" : value; 10192 } 10193 10194 function handleDelay() { 10195 var binding = this._ractive.binding, 10196 el = this; 10197 10198 if (!!binding._timeout) clearTimeout(binding._timeout); 10199 10200 binding._timeout = setTimeout(function () { 10201 if (binding.rendered) handleDomEvent.call(el); 10202 binding._timeout = undefined; 10203 }, binding.element.lazy); 10204 } 10205 10206 var ContentEditableBinding = Binding_GenericBinding.extend({ 10207 getInitialValue: function () { 10208 return this.element.fragment ? this.element.fragment.toString() : ""; 10209 }, 10210 10211 getValue: function () { 10212 return this.element.node.innerHTML; 10213 } 10214 }); 10215 10216 var Binding_ContentEditableBinding = ContentEditableBinding; 10217 10218 var shared_getSiblings = getSiblings; 10219 var sets = {}; 10220 function getSiblings(id, group, keypath) { 10221 var hash = id + group + keypath; 10222 return sets[hash] || (sets[hash] = []); 10223 } 10224 10225 var RadioBinding = Binding_Binding.extend({ 10226 name: "checked", 10227 10228 init: function () { 10229 this.siblings = shared_getSiblings(this.root._guid, "radio", this.element.getAttribute("name")); 10230 this.siblings.push(this); 10231 }, 10232 10233 render: function () { 10234 var node = this.element.node; 10235 10236 node.addEventListener("change", handleDomEvent, false); 10237 10238 if (node.attachEvent) { 10239 node.addEventListener("click", handleDomEvent, false); 10240 } 10241 }, 10242 10243 unrender: function () { 10244 var node = this.element.node; 10245 10246 node.removeEventListener("change", handleDomEvent, false); 10247 node.removeEventListener("click", handleDomEvent, false); 10248 }, 10249 10250 handleChange: function () { 10251 global_runloop.start(this.root); 10252 10253 this.siblings.forEach(function (binding) { 10254 binding.root.viewmodel.set(binding.keypath, binding.getValue()); 10255 }); 10256 10257 global_runloop.end(); 10258 }, 10259 10260 getValue: function () { 10261 return this.element.node.checked; 10262 }, 10263 10264 unbind: function () { 10265 removeFromArray(this.siblings, this); 10266 } 10267 }); 10268 10269 var Binding_RadioBinding = RadioBinding; 10270 10271 var RadioNameBinding = Binding_Binding.extend({ 10272 name: "name", 10273 10274 init: function () { 10275 this.siblings = shared_getSiblings(this.root._guid, "radioname", this.keypath.str); 10276 this.siblings.push(this); 10277 10278 this.radioName = true; // so that ractive.updateModel() knows what to do with this 10279 }, 10280 10281 getInitialValue: function () { 10282 if (this.element.getAttribute("checked")) { 10283 return this.element.getAttribute("value"); 10284 } 10285 }, 10286 10287 render: function () { 10288 var node = this.element.node; 10289 10290 node.name = "{{" + this.keypath.str + "}}"; 10291 node.checked = this.root.viewmodel.get(this.keypath) == this.element.getAttribute("value"); 10292 10293 node.addEventListener("change", handleDomEvent, false); 10294 10295 if (node.attachEvent) { 10296 node.addEventListener("click", handleDomEvent, false); 10297 } 10298 }, 10299 10300 unrender: function () { 10301 var node = this.element.node; 10302 10303 node.removeEventListener("change", handleDomEvent, false); 10304 node.removeEventListener("click", handleDomEvent, false); 10305 }, 10306 10307 getValue: function () { 10308 var node = this.element.node; 10309 return node._ractive ? node._ractive.value : node.value; 10310 }, 10311 10312 handleChange: function () { 10313 // If this <input> is the one that's checked, then the value of its 10314 // `name` keypath gets set to its value 10315 if (this.element.node.checked) { 10316 Binding_Binding.prototype.handleChange.call(this); 10317 } 10318 }, 10319 10320 rebound: function (oldKeypath, newKeypath) { 10321 var node; 10322 10323 Binding_Binding.prototype.rebound.call(this, oldKeypath, newKeypath); 10324 10325 if (node = this.element.node) { 10326 node.name = "{{" + this.keypath.str + "}}"; 10327 } 10328 }, 10329 10330 unbind: function () { 10331 removeFromArray(this.siblings, this); 10332 } 10333 }); 10334 10335 var Binding_RadioNameBinding = RadioNameBinding; 10336 10337 var CheckboxNameBinding = Binding_Binding.extend({ 10338 name: "name", 10339 10340 getInitialValue: function () { 10341 // This only gets called once per group (of inputs that
10342 // share a name), because it only gets called if there 10343 // isn't an initial value. By the same token, we can make 10344 // a note of that fact that there was no initial value, 10345 // and populate it using any `checked` attributes that 10346 // exist (which users should avoid, but which we should 10347 // support anyway to avoid breaking expectations) 10348 this.noInitialValue = true; 10349 return []; 10350 }, 10351 10352 init: function () { 10353 var existingValue, bindingValue; 10354 10355 this.checkboxName = true; // so that ractive.updateModel() knows what to do with this 10356 10357 // Each input has a reference to an array containing it and its 10358 // siblings, as two-way binding depends on being able to ascertain 10359 // the status of all inputs within the group 10360 this.siblings = shared_getSiblings(this.root._guid, "checkboxes", this.keypath.str); 10361 this.siblings.push(this); 10362 10363 if (this.noInitialValue) { 10364 this.siblings.noInitialValue = true; 10365 } 10366 10367 // If no initial value was set, and this input is checked, we 10368 // update the model 10369 if (this.siblings.noInitialValue && this.element.getAttribute("checked")) { 10370 existingValue = this.root.viewmodel.get(this.keypath); 10371 bindingValue = this.element.getAttribute("value"); 10372 10373 existingValue.push(bindingValue); 10374 } 10375 }, 10376 10377 unbind: function () { 10378 removeFromArray(this.siblings, this); 10379 }, 10380 10381 render: function () { 10382 var node = this.element.node, 10383 existingValue, 10384 bindingValue; 10385 10386 existingValue = this.root.viewmodel.get(this.keypath); 10387 bindingValue = this.element.getAttribute("value"); 10388 10389 if (isArray(existingValue)) { 10390 this.isChecked = arrayContains(existingValue, bindingValue); 10391 } else { 10392 this.isChecked = existingValue == bindingValue; 10393 } 10394 10395 node.name = "{{" + this.keypath.str + "}}"; 10396 node.checked = this.isChecked; 10397 10398 node.addEventListener("change", handleDomEvent, false); 10399 10400 // in case of IE emergency, bind to click event as well 10401 if (node.attachEvent) { 10402 node.addEventListener("click", handleDomEvent, false); 10403 } 10404 }, 10405 10406 unrender: function () { 10407 var node = this.element.node; 10408 10409 node.removeEventListener("change", handleDomEvent, false); 10410 node.removeEventListener("click", handleDomEvent, false); 10411 }, 10412 10413 changed: function () { 10414 var wasChecked = !!this.isChecked; 10415 this.isChecked = this.element.node.checked; 10416 return this.isChecked === wasChecked; 10417 }, 10418 10419 handleChange: function () { 10420 this.isChecked = this.element.node.checked; 10421 Binding_Binding.prototype.handleChange.call(this); 10422 }, 10423 10424 getValue: function () { 10425 return this.siblings.filter(isChecked).map(Binding_CheckboxNameBinding__getValue); 10426 } 10427 }); 10428 10429 function isChecked(binding) { 10430 return binding.isChecked; 10431 } 10432 10433 function Binding_CheckboxNameBinding__getValue(binding) { 10434 return binding.element.getAttribute("value"); 10435 } 10436 10437 var Binding_CheckboxNameBinding = CheckboxNameBinding; 10438 10439 var CheckboxBinding = Binding_Binding.extend({ 10440 name: "checked", 10441 10442 render: function () { 10443 var node = this.element.node; 10444 10445 node.addEventListener("change", handleDomEvent, false); 10446 10447 if (node.attachEvent) { 10448 node.addEventListener("click", handleDomEvent, false); 10449 } 10450 }, 10451 10452 unrender: function () { 10453 var node = this.element.node; 10454 10455 node.removeEventListener("change", handleDomEvent, false); 10456 node.removeEventListener("click", handleDomEvent, false); 10457 }, 10458 10459 getValue: function () { 10460 return this.element.node.checked; 10461 } 10462 }); 10463 10464 var Binding_CheckboxBinding = CheckboxBinding; 10465 10466 var SelectBinding = Binding_Binding.extend({ 10467 getInitialValue: function () { 10468 var options = this.element.options, 10469 len, 10470 i, 10471 value, 10472 optionWasSelected; 10473 10474 if (this.element.getAttribute("value") !== undefined) { 10475 return; 10476 } 10477 10478 i = len = options.length; 10479 10480 if (!len) { 10481 return; 10482 } 10483 10484 // take the final selected option... 10485 while (i--) { 10486 if (options[i].getAttribute("selected")) { 10487 value = options[i].getAttribute("value"); 10488 optionWasSelected = true; 10489 break; 10490 } 10491 } 10492 10493 // or the first non-disabled option, if none are selected 10494 if (!optionWasSelected) { 10495 while (++i < len) { 10496 if (!options[i].getAttribute("disabled")) { 10497 value = options[i].getAttribute("value"); 10498 break; 10499 } 10500 } 10501 } 10502 10503 // This is an optimisation (aka hack) that allows us to forgo some 10504 // other more expensive work 10505 if (value !== undefined) { 10506 this.element.attributes.value.value = value; 10507 } 10508 10509 return value; 10510 }, 10511 10512 render: function () { 10513 this.element.node.addEventListener("change", handleDomEvent, false); 10514 }, 10515 10516 unrender: function () { 10517 this.element.node.removeEventListener("change", handleDomEvent, false); 10518 }, 10519 10520 // TODO this method is an anomaly... is it necessary? 10521 setValue: function (value) { 10522 this.root.viewmodel.set(this.keypath, value); 10523 }, 10524 10525 getValue: function () { 10526 var options, i, len, option, optionValue; 10527 10528 options = this.element.node.options; 10529 len = options.length; 10530 10531 for (i = 0; i < len; i += 1) { 10532 option = options[i]; 10533 10534 if (options[i].selected) { 10535 optionValue = option._ractive ? option._ractive.value : option.value; 10536 return optionValue; 10537 } 10538 } 10539 }, 10540 10541 forceUpdate: function () { 10542 var _this = this; 10543 10544 var value = this.getValue(); 10545 10546 if (value !== undefined) { 10547 this.attribute.locked = true; 10548 global_runloop.scheduleTask(function () { 10549 return _this.attribute.locked = false;
10550 }); 10551 this.root.viewmodel.set(this.keypath, value); 10552 } 10553 } 10554 }); 10555 10556 var Binding_SelectBinding = SelectBinding; 10557 10558 var MultipleSelectBinding = Binding_SelectBinding.extend({ 10559 getInitialValue: function () { 10560 return this.element.options.filter(function (option) { 10561 return option.getAttribute("selected"); 10562 }).map(function (option) { 10563 return option.getAttribute("value"); 10564 }); 10565 }, 10566 10567 render: function () { 10568 var valueFromModel; 10569 10570 this.element.node.addEventListener("change", handleDomEvent, false); 10571 10572 valueFromModel = this.root.viewmodel.get(this.keypath); 10573 10574 if (valueFromModel === undefined) { 10575 // get value from DOM, if possible 10576 this.handleChange(); 10577 } 10578 }, 10579 10580 unrender: function () { 10581 this.element.node.removeEventListener("change", handleDomEvent, false); 10582 }, 10583 10584 setValue: function () { 10585 throw new Error("TODO not implemented yet"); 10586 }, 10587 10588 getValue: function () { 10589 var selectedValues, options, i, len, option, optionValue; 10590 10591 selectedValues = []; 10592 options = this.element.node.options; 10593 len = options.length; 10594 10595 for (i = 0; i < len; i += 1) { 10596 option = options[i]; 10597 10598 if (option.selected) { 10599 optionValue = option._ractive ? option._ractive.value : option.value; 10600 selectedValues.push(optionValue); 10601 } 10602 } 10603 10604 return selectedValues; 10605 }, 10606 10607 handleChange: function () { 10608 var attribute, previousValue, value; 10609 10610 attribute = this.attribute; 10611 previousValue = attribute.value; 10612 10613 value = this.getValue(); 10614 10615 if (previousValue === undefined || !arrayContentsMatch(value, previousValue)) { 10616 Binding_SelectBinding.prototype.handleChange.call(this); 10617 } 10618 10619 return this; 10620 }, 10621 10622 forceUpdate: function () { 10623 var _this = this; 10624 10625 var value = this.getValue(); 10626 10627 if (value !== undefined) { 10628 this.attribute.locked = true; 10629 global_runloop.scheduleTask(function () { 10630 return _this.attribute.locked = false; 10631 }); 10632 this.root.viewmodel.set(this.keypath, value); 10633 } 10634 }, 10635 10636 updateModel: function () { 10637 if (this.attribute.value === undefined || !this.attribute.value.length) { 10638 this.root.viewmodel.set(this.keypath, this.initialValue); 10639 } 10640 } 10641 }); 10642 10643 var Binding_MultipleSelectBinding = MultipleSelectBinding; 10644 10645 var FileListBinding = Binding_Binding.extend({ 10646 render: function () { 10647 this.element.node.addEventListener("change", handleDomEvent, false); 10648 }, 10649 10650 unrender: function () { 10651 this.element.node.removeEventListener("change", handleDomEvent, false); 10652 }, 10653 10654 getValue: function () { 10655 return this.element.node.files; 10656 } 10657 }); 10658 10659 var Binding_FileListBinding = FileListBinding; 10660 10661 var NumericBinding = Binding_GenericBinding.extend({ 10662 getInitialValue: function () { 10663 return undefined; 10664 }, 10665 10666 getValue: function () { 10667 var value = parseFloat(this.element.node.value); 10668 return isNaN(value) ? undefined : value; 10669 } 10670 }); 10671 10672 var init_createTwowayBinding = createTwowayBinding; 10673 10674 function createTwowayBinding(element) { 10675 var attributes = element.attributes, 10676 type, 10677 Binding, 10678 bindName, 10679 bindChecked, 10680 binding; 10681 10682 // if this is a late binding, and there's already one, it 10683 // needs to be torn down 10684 if (element.binding) { 10685 element.binding.teardown(); 10686 element.binding = null; 10687 } 10688 10689 // contenteditable 10690 if ( 10691 // if the contenteditable attribute is true or is bindable and may thus become true 10692 (element.getAttribute("contenteditable") || !!attributes.contenteditable && isBindable(attributes.contenteditable)) && isBindable(attributes.value)) { 10693 Binding = Binding_ContentEditableBinding; 10694 } 10695 10696 // <input> 10697 else if (element.name === "input") { 10698 type = element.getAttribute("type"); 10699 10700 if (type === "radio" || type === "checkbox") { 10701 bindName = isBindable(attributes.name); 10702 bindChecked = isBindable(attributes.checked); 10703 10704 // we can either bind the name attribute, or the checked attribute - not both 10705 if (bindName && bindChecked) { 10706 warnIfDebug("A radio input can have two-way binding on its name attribute, or its checked attribute - not both", { ractive: element.root }); 10707 } 10708 10709 if (bindName) { 10710 Binding = type === "radio" ? Binding_RadioNameBinding : Binding_CheckboxNameBinding; 10711 } else if (bindChecked) { 10712 Binding = type === "radio" ? Binding_RadioBinding : Binding_CheckboxBinding; 10713 } 10714 } else if (type === "file" && isBindable(attributes.value)) { 10715 Binding = Binding_FileListBinding; 10716 } else if (isBindable(attributes.value)) { 10717 Binding = type === "number" || type === "range" ? NumericBinding : Binding_GenericBinding; 10718 } 10719 } 10720 10721 // <select> 10722 else if (element.name === "select" && isBindable(attributes.value)) { 10723 Binding = element.getAttribute("multiple") ? Binding_MultipleSelectBinding : Binding_SelectBinding; 10724 } 10725 10726 // <textarea> 10727 else if (element.name === "textarea" && isBindable(attributes.value)) { 10728 Binding = Binding_GenericBinding; 10729 } 10730 10731 if (Binding && (binding = new Binding(element)) && binding.keypath) { 10732 return binding; 10733 } 10734 } 10735 10736 function isBindable(attribute) { 10737 return attribute && attribute.isBindable; 10738 } 10739 10740 // and this element also has a value attribute to bind 10741 10742 var EventHandler_prototype_bubble = EventHandler$bubble; 10743 10744 function EventHandler$bubble() { 10745 var hasAction = this.getAction(); 10746 10747 if (hasAction && !this.hasListener) { 10748 this.listen(); 10749 } else if (!hasAction && this.hasListener) { 10750 this.unrender(); 10751 } 10752 } 10753
10754 // This function may be overwritten, if the event directive 10755 // includes parameters 10756 var EventHandler_prototype_fire = EventHandler$fire; 10757 function EventHandler$fire(event) { 10758 shared_fireEvent(this.root, this.getAction(), { event: event }); 10759 } 10760 10761 var getAction = EventHandler$getAction; 10762 10763 function EventHandler$getAction() { 10764 return this.action.toString().trim(); 10765 } 10766 10767 var EventHandler_prototype_init = EventHandler$init; 10768 10769 var eventPattern = /^event(?:\.(.+))?/; 10770 function EventHandler$init(element, name, template) { 10771 var _this = this; 10772 10773 var action, refs, ractive; 10774 10775 this.element = element; 10776 this.root = element.root; 10777 this.parentFragment = element.parentFragment; 10778 this.name = name; 10779 10780 if (name.indexOf("*") !== -1) { 10781 fatal("Only component proxy-events may contain \"*\" wildcards, <%s on-%s=\"...\"/> is not valid", element.name, name); 10782 this.invalid = true; 10783 } 10784 10785 if (template.m) { 10786 refs = template.a.r; 10787 10788 // This is a method call 10789 this.method = template.m; 10790 this.keypaths = []; 10791 this.fn = shared_getFunctionFromString(template.a.s, refs.length); 10792 10793 this.parentFragment = element.parentFragment; 10794 ractive = this.root; 10795 10796 // Create resolvers for each reference 10797 this.refResolvers = []; 10798 refs.forEach(function (ref, i) { 10799 var match = undefined; 10800 10801 // special case - the `event` object 10802 if (match = eventPattern.exec(ref)) { 10803 _this.keypaths[i] = { 10804 eventObject: true, 10805 refinements: match[1] ? match[1].split(".") : [] 10806 }; 10807 } else { 10808 _this.refResolvers.push(Resolvers_createReferenceResolver(_this, ref, function (keypath) { 10809 return _this.resolve(i, keypath); 10810 })); 10811 } 10812 }); 10813 10814 this.fire = fireMethodCall; 10815 } else { 10816 // Get action ('foo' in 'on-click='foo') 10817 action = template.n || template; 10818 if (typeof action !== "string") { 10819 action = new virtualdom_Fragment({ 10820 template: action, 10821 root: this.root, 10822 owner: this 10823 }); 10824 } 10825 10826 this.action = action; 10827 10828 // Get parameters 10829 if (template.d) { 10830 this.dynamicParams = new virtualdom_Fragment({ 10831 template: template.d, 10832 root: this.root, 10833 owner: this.element 10834 }); 10835 10836 this.fire = fireEventWithDynamicParams; 10837 } else if (template.a) { 10838 this.params = template.a; 10839 this.fire = fireEventWithParams; 10840 } 10841 } 10842 } 10843 10844 function fireMethodCall(event) { 10845 var ractive, values, args; 10846 10847 ractive = this.root; 10848 10849 if (typeof ractive[this.method] !== "function") { 10850 throw new Error("Attempted to call a non-existent method (\"" + this.method + "\")"); 10851 } 10852 10853 values = this.keypaths.map(function (keypath) { 10854 var value, len, i; 10855 10856 if (keypath === undefined) { 10857 // not yet resolved 10858 return undefined; 10859 } 10860 10861 // TODO the refinements stuff would be better handled at parse time 10862 if (keypath.eventObject) { 10863 value = event; 10864 10865 if (len = keypath.refinements.length) { 10866 for (i = 0; i < len; i += 1) { 10867 value = value[keypath.refinements[i]]; 10868 } 10869 } 10870 } else { 10871 value = ractive.viewmodel.get(keypath); 10872 } 10873 10874 return value; 10875 }); 10876 10877 shared_eventStack.enqueue(ractive, event); 10878 10879 args = this.fn.apply(null, values); 10880 ractive[this.method].apply(ractive, args); 10881 10882 shared_eventStack.dequeue(ractive); 10883 } 10884 10885 function fireEventWithParams(event) { 10886 shared_fireEvent(this.root, this.getAction(), { event: event, args: this.params }); 10887 } 10888 10889 function fireEventWithDynamicParams(event) { 10890 var args = this.dynamicParams.getArgsList(); 10891 10892 // need to strip [] from ends if a string! 10893 if (typeof args === "string") { 10894 args = args.substr(1, args.length - 2); 10895 } 10896 10897 shared_fireEvent(this.root, this.getAction(), { event: event, args: args }); 10898 } 10899 10900 var shared_genericHandler = genericHandler; 10901 function genericHandler(event) { 10902 var storage, 10903 handler, 10904 indices, 10905 index = {}; 10906 10907 storage = this._ractive; 10908 handler = storage.events[event.type]; 10909 10910 if (indices = Resolvers_findIndexRefs(handler.element.parentFragment)) { 10911 index = Resolvers_findIndexRefs.resolve(indices); 10912 } 10913 10914 handler.fire({ 10915 node: this, 10916 original: event, 10917 index: index, 10918 keypath: storage.keypath.str, 10919 context: storage.root.viewmodel.get(storage.keypath) 10920 }); 10921 } 10922 10923 var listen = EventHandler$listen; 10924 10925 var customHandlers = {}, 10926 touchEvents = { 10927 touchstart: true, 10928 touchmove: true, 10929 touchend: true, 10930 touchcancel: true, 10931 //not w3c, but supported in some browsers 10932 touchleave: true 10933 }; 10934 function EventHandler$listen() { 10935 var definition, 10936 name = this.name; 10937 10938 if (this.invalid) { 10939 return; 10940 } 10941 10942 if (definition = findInViewHierarchy("events", this.root, name)) { 10943 this.custom = definition(this.node, getCustomHandler(name)); 10944 } else { 10945 // Looks like we're dealing with a standard DOM event... but let's check
10946 if (!("on" + name in this.node) && !(window && "on" + name in window) && !isJsdom) { 10947 10948 // okay to use touch events if this browser doesn't support them 10949 if (!touchEvents[name]) { 10950 warnOnceIfDebug(missingPlugin(name, "event"), { node: this.node }); 10951 } 10952 10953 return; 10954 } 10955 10956 this.node.addEventListener(name, shared_genericHandler, false); 10957 } 10958 10959 this.hasListener = true; 10960 } 10961 10962 function getCustomHandler(name) { 10963 if (!customHandlers[name]) { 10964 customHandlers[name] = function (event) { 10965 var storage = event.node._ractive; 10966 10967 event.index = storage.index; 10968 event.keypath = storage.keypath.str; 10969 event.context = storage.root.viewmodel.get(storage.keypath); 10970 10971 storage.events[name].fire(event); 10972 }; 10973 } 10974 10975 return customHandlers[name]; 10976 } 10977 10978 var EventHandler_prototype_rebind = EventHandler$rebind; 10979 10980 function EventHandler$rebind(oldKeypath, newKeypath) { 10981 var fragment; 10982 if (this.method) { 10983 fragment = this.element.parentFragment; 10984 this.refResolvers.forEach(rebind); 10985 10986 return; 10987 } 10988 10989 if (typeof this.action !== "string") { 10990 rebind(this.action); 10991 } 10992 10993 if (this.dynamicParams) { 10994 rebind(this.dynamicParams); 10995 } 10996 10997 function rebind(thing) { 10998 thing && thing.rebind(oldKeypath, newKeypath); 10999 } 11000 } 11001 11002 var EventHandler_prototype_render = EventHandler$render; 11003 11004 function EventHandler$render() { 11005 this.node = this.element.node; 11006 // store this on the node itself, so it can be retrieved by a 11007 // universal handler 11008 this.node._ractive.events[this.name] = this; 11009 11010 if (this.method || this.getAction()) { 11011 this.listen(); 11012 } 11013 } 11014 11015 var prototype_resolve = EventHandler$resolve; 11016 11017 function EventHandler$resolve(index, keypath) { 11018 this.keypaths[index] = keypath; 11019 } 11020 11021 var EventHandler_prototype_unbind = EventHandler$unbind; 11022 function EventHandler$unbind() { 11023 if (this.method) { 11024 this.refResolvers.forEach(methodCallers__unbind); 11025 return; 11026 } 11027 11028 // Tear down dynamic name 11029 if (typeof this.action !== "string") { 11030 this.action.unbind(); 11031 } 11032 11033 // Tear down dynamic parameters 11034 if (this.dynamicParams) { 11035 this.dynamicParams.unbind(); 11036 } 11037 } 11038 11039 var EventHandler_prototype_unrender = EventHandler$unrender; 11040 function EventHandler$unrender() { 11041 11042 if (this.custom) { 11043 this.custom.teardown(); 11044 } else { 11045 this.node.removeEventListener(this.name, shared_genericHandler, false); 11046 } 11047 11048 this.hasListener = false;
11049 } 11050 11051 var EventHandler = function (element, name, template) { 11052 this.init(element, name, template); 11053 }; 11054 11055 EventHandler.prototype = { 11056 bubble: EventHandler_prototype_bubble, 11057 fire: EventHandler_prototype_fire, 11058 getAction: getAction, 11059 init: EventHandler_prototype_init, 11060 listen: listen, 11061 rebind: EventHandler_prototype_rebind, 11062 render: EventHandler_prototype_render, 11063 resolve: prototype_resolve, 11064 unbind: EventHandler_prototype_unbind, 11065 unrender: EventHandler_prototype_unrender 11066 }; 11067 11068 var _EventHandler = EventHandler; 11069 11070 var createEventHandlers = function (element, template) { 11071 var i, 11072 name, 11073 names, 11074 handler, 11075 result = []; 11076 11077 for (name in template) { 11078 if (template.hasOwnProperty(name)) { 11079 names = name.split("-"); 11080 i = names.length; 11081 11082 while (i--) { 11083 handler = new _EventHandler(element, names[i], template[name]); 11084 result.push(handler); 11085 } 11086 } 11087 } 11088 11089 return result; 11090 }; 11091 11092 var Decorator = function (element, template) { 11093 var self = this, 11094 ractive, 11095 name, 11096 fragment; 11097 11098 this.element = element; 11099 this.root = ractive = element.root; 11100 11101 name = template.n || template; 11102 11103 if (typeof name !== "string") { 11104 fragment = new virtualdom_Fragment({ 11105 template: name, 11106 root: ractive, 11107 owner: element 11108 }); 11109 11110 name = fragment.toString(); 11111 fragment.unbind(); 11112 11113 if (name === "") { 11114 // empty string okay, just no decorator 11115 return; 11116 } 11117 } 11118 11119 if (template.a) { 11120 this.params = template.a; 11121 } else if (template.d) { 11122 this.fragment = new virtualdom_Fragment({ 11123 template: template.d, 11124 root: ractive, 11125 owner: element 11126 }); 11127 11128 this.params = this.fragment.getArgsList(); 11129 11130 this.fragment.bubble = function () { 11131 this.dirtyArgs = this.dirtyValue = true; 11132 self.params = this.getArgsList(); 11133 11134 if (self.ready) { 11135 self.update(); 11136 } 11137 }; 11138 } 11139 11140 this.fn = findInViewHierarchy("decorators", ractive, name); 11141 11142 if (!this.fn) { 11143 fatal(missingPlugin(name, "decorator")); 11144 } 11145 }; 11146 11147 Decorator.prototype = { 11148 init: function () { 11149 var node, result, args; 11150 11151 node = this.element.node; 11152 11153 if (this.params) { 11154 args = [node].concat(this.params); 11155 result = this.fn.apply(this.root, args); 11156 } else { 11157 result = this.fn.call(this.root, node); 11158 } 11159 11160 if (!result || !result.teardown) { 11161 throw new Error("Decorator definition must return an object with a teardown method"); 11162 } 11163 11164 // TODO does this make sense? 11165 this.actual = result; 11166 this.ready = true; 11167 }, 11168 11169 update: function () { 11170 if (this.actual.update) { 11171 this.actual.update.apply(this.root, this.params); 11172 } else { 11173 this.actual.teardown(true); 11174 this.init(); 11175 } 11176 }, 11177 11178 rebind: function (oldKeypath, newKeypath) { 11179 if (this.fragment) { 11180 this.fragment.rebind(oldKeypath, newKeypath); 11181 } 11182 }, 11183 11184 teardown: function (updating) { 11185 this.torndown = true; 11186 if (this.ready) { 11187 this.actual.teardown(); 11188 } 11189 11190 if (!updating && this.fragment) { 11191 this.fragment.unbind(); 11192 } 11193 } 11194 }; 11195 11196 var _Decorator = Decorator; 11197 11198 function select__bubble() { 11199 var _this = this; 11200 11201 if (!this.dirty) { 11202 this.dirty = true; 11203 11204 global_runloop.scheduleTask(function () { 11205 sync(_this); 11206 _this.dirty = false; 11207 }); 11208 } 11209 11210 this.parentFragment.bubble(); // default behaviour 11211 } 11212 11213 function sync(selectElement) { 11214 var selectNode, selectValue, isMultiple, options, optionWasSelected; 11215 11216 selectNode = selectElement.node; 11217 11218 if (!selectNode) { 11219 return; 11220 } 11221 11222 options = toArray(selectNode.options); 11223 11224 selectValue = selectElement.getAttribute("value"); 11225 isMultiple = selectElement.getAttribute("multiple"); 11226 11227 // If the <select> has a specified value, that should override 11228 // these options 11229 if (selectValue !== undefined) { 11230 options.forEach(function (o) { 11231 var optionValue, shouldSelect; 11232 11233 optionValue = o._ractive ? o._ractive.value : o.value; 11234 shouldSelect = isMultiple ? valueContains(selectValue, optionValue) : selectValue == optionValue; 11235 11236 if (shouldSelect) { 11237 optionWasSelected = true; 11238 } 11239 11240 o.selected = shouldSelect; 11241 }); 11242 11243 if (!optionWasSelected) { 11244 if (options[0]) { 11245 options[0].selected = true; 11246 } 11247 11248 if (selectElement.binding) { 11249 selectElement.binding.forceUpdate(); 11250 } 11251 } 11252 } 11253 11254 // Otherwise the value should be initialised according to which 11255 // <option>
11255 element is selected, if twoway binding is in effect 11256 else if (selectElement.binding) { 11257 selectElement.binding.forceUpdate(); 11258 } 11259 } 11260 11261 function valueContains(selectValue, optionValue) { 11262 var i = selectValue.length; 11263 while (i--) { 11264 if (selectValue[i] == optionValue) { 11265 return true; 11266 } 11267 } 11268 } 11269 11270 function special_option__init(option, template) { 11271 option.select = findParentSelect(option.parent); 11272 11273 // we might be inside a <datalist> element 11274 if (!option.select) { 11275 return; 11276 } 11277 11278 option.select.options.push(option); 11279 11280 // If the value attribute is missing, use the element's content 11281 if (!template.a) { 11282 template.a = {}; 11283 } 11284 11285 // ...as long as it isn't disabled 11286 if (template.a.value === undefined && !template.a.hasOwnProperty("disabled")) { 11287 template.a.value = template.f; 11288 } 11289 11290 // If there is a `selected` attribute, but the <select> 11291 // already has a value, delete it 11292 if ("selected" in template.a && option.select.getAttribute("value") !== undefined) { 11293 delete template.a.selected; 11294 } 11295 } 11296 11297 function special_option__unbind(option) { 11298 if (option.select) { 11299 removeFromArray(option.select.options, option); 11300 } 11301 } 11302 11303 function findParentSelect(element) { 11304 if (!element) { 11305 return; 11306 } 11307 11308 do { 11309 if (element.name === "select") { 11310 return element; 11311 } 11312 } while (element = element.parent); 11313 } 11314 11315 var Element_prototype_init = Element$init; 11316 function Element$init(options) { 11317 var parentFragment, template, ractive, binding, bindings, twoway, bindingAttrs; 11318 11319 this.type = ELEMENT; 11320 11321 // stuff we'll need later 11322 parentFragment = this.parentFragment = options.parentFragment; 11323 template = this.template = options.template; 11324 11325 this.parent = options.pElement || parentFragment.pElement; 11326 11327 this.root = ractive = parentFragment.root; 11328 this.index = options.index; 11329 this.key = options.key; 11330 11331 this.name = enforceCase(template.e); 11332 11333 // Special case - <option> elements 11334 if (this.name === "option") { 11335 special_option__init(this, template); 11336 } 11337 11338 // Special case - <select> elements 11339 if (this.name === "select") { 11340 this.options = []; 11341 this.bubble = select__bubble; // TODO this is a kludge 11342 } 11343 11344 // Special case - <form> elements 11345 if (this.name === "form") { 11346 this.formBindings = []; 11347 } 11348 11349 // handle binding attributes first (twoway, lazy) 11350 bindingAttrs = processBindingAttributes(this, template); 11351 11352 // create attributes 11353 this.attributes = createAttributes(this, template.a); 11354 this.conditionalAttributes = createConditionalAttributes(this, template.m); 11355 11356 // append children, if there are any 11357 if (template.f) { 11358 this.fragment = new virtualdom_Fragment({ 11359 template: template.f, 11360 root: ractive, 11361 owner: this, 11362 pElement: this, 11363 cssIds: null 11364 }); 11365 } 11366 11367 // the element setting should override the ractive setting 11368 twoway = ractive.twoway; 11369 if (bindingAttrs.twoway === false) twoway = false;else if (bindingAttrs.twoway === true) twoway = true; 11370 11371 this.twoway = twoway; 11372 this.lazy = bindingAttrs.lazy; 11373 11374 // create twoway binding 11375 if (twoway && (binding = init_createTwowayBinding(this, template.a))) { 11376 this.binding = binding; 11377 11378 // register this with the root, so that we can do ractive.updateModel() 11379 bindings = this.root._twowayBindings[binding.keypath.str] || (this.root._twowayBindings[binding.keypath.str] = []); 11380 bindings.push(binding); 11381 } 11382 11383 // create event proxies 11384 if (template.v) { 11385 this.eventHandlers = createEventHandlers(this, template.v); 11386 } 11387 11388 // create decorator 11389 if (template.o) { 11390 this.decorator = new _Decorator(this, template.o); 11391 } 11392 11393 // create transitions 11394 this.intro = template.t0 || template.t1; 11395 this.outro = template.t0 || template.t2; 11396 } 11397 11398 var Element_prototype_rebind = Element$rebind; 11399 function Element$rebind(oldKeypath, newKeypath) { 11400 var i, storage, liveQueries, ractive; 11401 11402 if (this.attributes) { 11403 this.attributes.forEach(rebind); 11404 } 11405 11406 if (this.conditionalAttributes) { 11407 this.conditionalAttributes.forEach(rebind); 11408 } 11409 11410 if (this.eventHandlers) {
11411 this.eventHandlers.forEach(rebind); 11412 } 11413 11414 if (this.decorator) { 11415 rebind(this.decorator); 11416 } 11417 11418 // rebind children 11419 if (this.fragment) { 11420 rebind(this.fragment); 11421 } 11422 11423 // Update live queries, if necessary 11424 if (liveQueries = this.liveQueries) { 11425 ractive = this.root; 11426 11427 i = liveQueries.length; 11428 while (i--) { 11429 liveQueries[i]._makeDirty(); 11430 } 11431 } 11432 11433 if (this.node && (storage = this.node._ractive)) { 11434 11435 // adjust keypath if needed 11436 assignNewKeypath(storage, "keypath", oldKeypath, newKeypath); 11437 } 11438 11439 function rebind(thing) { 11440 thing.rebind(oldKeypath, newKeypath); 11441 } 11442 } 11443 11444 function special_img__render(img) { 11445 var loadHandler; 11446 11447 // if this is an <img>, and we're in a crap browser, we may need to prevent it 11448 // from overriding width and height when it loads the src 11449 if (img.attributes.width || img.attributes.height) { 11450 img.node.addEventListener("load", loadHandler = function () { 11451 var width = img.getAttribute("width"), 11452 height = img.getAttribute("height"); 11453 11454 if (width !== undefined) { 11455 img.node.setAttribute("width", width); 11456 } 11457 11458 if (height !== undefined) { 11459 img.node.setAttribute("height", height); 11460 } 11461 11462 img.node.removeEventListener("load", loadHandler, false); 11463 }, false); 11464 } 11465 } 11466 11467 function form__render(element) { 11468 element.node.addEventListener("reset", handleReset, false); 11469 } 11470 11471 function form__unrender(element) { 11472 element.node.removeEventListener("reset", handleReset, false); 11473 } 11474 11475 function handleReset() { 11476 var element = this._ractive.proxy; 11477 11478 global_runloop.start();
11479 element.formBindings.forEach(updateModel); 11480 global_runloop.end(); 11481 } 11482 11483 function updateModel(binding) { 11484 binding.root.viewmodel.set(binding.keypath, binding.resetValue); 11485 } 11486 11487 var Transition_prototype_init = Transition$init; 11488 function Transition$init(element, template, isIntro) { 11489 var ractive, name, fragment; 11490 11491 this.element = element; 11492 this.root = ractive = element.root; 11493 this.isIntro = isIntro; 11494 11495 name = template.n || template; 11496 11497 if (typeof name !== "string") { 11498 fragment = new virtualdom_Fragment({ 11499 template: name, 11500 root: ractive, 11501 owner: element 11502 }); 11503 11504 name = fragment.toString(); 11505 fragment.unbind(); 11506 11507 if (name === "") { 11508 // empty string okay, just no transition 11509 return; 11510 } 11511 } 11512 11513 this.name = name; 11514 11515 if (template.a) { 11516 this.params = template.a; 11517 } else if (template.d) { 11518 // TODO is there a way to interpret dynamic arguments without all the 11519 // 'dependency thrashing'? 11520 fragment = new virtualdom_Fragment({ 11521 template: template.d, 11522 root: ractive, 11523 owner: element 11524 }); 11525 11526 this.params = fragment.getArgsList(); 11527 fragment.unbind(); 11528 } 11529 11530 this._fn = findInViewHierarchy("transitions", ractive, name); 11531 11532 if (!this._fn) { 11533 warnOnceIfDebug(missingPlugin(name, "transition"), { ractive: this.root }); 11534 } 11535 } 11536 11537 var camelCase = function (hyphenatedStr) { 11538 return hyphenatedStr.replace(/-([a-zA-Z])/g, function (match, $1) { 11539 return $1.toUpperCase(); 11540 }); 11541 }; 11542 11543 var helpers_prefix__prefix, prefixCache, helpers_prefix__testStyle; 11544 11545 if (!isClient) { 11546 helpers_prefix__prefix = null; 11547 } else { 11548 prefixCache = {}; 11549 helpers_prefix__testStyle = createElement("div").style; 11550 11551 helpers_prefix__prefix = function (prop) { 11552 var i, vendor, capped; 11553 11554 prop = camelCase(prop); 11555 11556 if (!prefixCache[prop]) { 11557 if (helpers_prefix__testStyle[prop] !== undefined) { 11558 prefixCache[prop] = prop; 11559 } else { 11560 // test vendors... 11561 capped = prop.charAt(0).toUpperCase() + prop.substring(1); 11562 11563 i = vendors.length; 11564 while (i--) { 11565 vendor = vendors[i]; 11566 if (helpers_prefix__testStyle[vendor + capped] !== undefined) { 11567 prefixCache[prop] = vendor + capped; 11568 break; 11569 } 11570 } 11571 } 11572 } 11573 11574 return prefixCache[prop]; 11575 }; 11576 } 11577 11578 var helpers_prefix = helpers_prefix__prefix; 11579 11580 var getStyle, prototype_getStyle__getComputedStyle; 11581 11582 if (!isClient) { 11583 getStyle = null; 11584 } else { 11585 prototype_getStyle__getComputedStyle = window.getComputedStyle || legacy.getComputedStyle; 11586 11587 getStyle = function (props) { 11588 var computedStyle, styles, i, prop, value; 11589 11590 computedStyle = prototype_getStyle__getComputedStyle(this.node); 11591 11592 if (typeof props === "string") { 11593 value = computedStyle[helpers_prefix(props)]; 11594 if (value === "0px") { 11595 value = 0; 11596 } 11597 return value; 11598 } 11599 11600 if (!isArray(props)) { 11601 throw new Error("Transition$getStyle must be passed a string, or an array of strings representing CSS properties"); 11602 } 11603 11604 styles = {}; 11605 11606 i = props.length; 11607 while (i--) { 11608 prop = props[i]; 11609 value = computedStyle[helpers_prefix(prop)]; 11610 if (value === "0px") { 11611 value = 0; 11612 } 11613 styles[prop] = value; 11614 } 11615 11616 return styles; 11617 }; 11618 } 11619 11620 var prototype_getStyle = getStyle; 11621 11622 var setStyle = function (style, value) { 11623 var prop; 11624 11625 if (typeof style === "string") { 11626 this.node.style[helpers_prefix(style)] = value; 11627 } else { 11628 for (prop in style) { 11629 if (style.hasOwnProperty(prop)) { 11630 this.node.style[helpers_prefix(prop)] = style[prop]; 11631 } 11632 } 11633 } 11634 11635 return this; 11636 }; 11637 11638 var Ticker = function (options) { 11639 var easing; 11640 11641 this.duration = options.duration; 11642 this.step = options.step; 11643 this.complete = options.complete; 11644 11645 // easing 11646 if (typeof options.easing === "string") { 11647 easing = options.root.easing[options.easing]; 11648 11649 if (!easing) { 11650 warnOnceIfDebug(missingPlugin(options.easing, "easing")); 11651 easing = linear; 11652 } 11653 } else if (typeof options.easing === "function") { 11654 easing = options.easing; 11655 } else { 11656 easing = linear; 11657 } 11658 11659 this.easing = easing; 11660 11661 this.start = utils_getTime(); 11662 this.end = this.start + this.duration; 11663 11664 this.running = true; 11665 shared_animations.add(this); 11666 }; 11667 11668 Ticker.prototype = { 11669 tick: function (now) { 11670 var elapsed, eased; 11671 11672 if (!this.running) { 11673 return false;
11674 } 11675 11676 if (now > this.end) { 11677 if (this.step) { 11678 this.step(1); 11679 } 11680 11681 if (this.complete) { 11682 this.complete(1); 11683 } 11684 11685 return false; 11686 } 11687 11688 elapsed = now - this.start; 11689 eased = this.easing(elapsed / this.duration); 11690 11691 if (this.step) { 11692 this.step(eased); 11693 } 11694 11695 return true; 11696 }, 11697 11698 stop: function () { 11699 if (this.abort) { 11700 this.abort(); 11701 } 11702 11703 this.running = false; 11704 } 11705 }; 11706 11707 var shared_Ticker = Ticker; 11708 function linear(t) { 11709 return t; 11710 } 11711 11712 var unprefixPattern = new RegExp("^-(?:" + vendors.join("|") + ")-"); 11713 11714 var unprefix = function (prop) { 11715 return prop.replace(unprefixPattern, ""); 11716 }; 11717 11718 var vendorPattern = new RegExp("^(?:" + vendors.join("|") + ")([A-Z])"); 11719 11720 var hyphenate = function (str) { 11721 var hyphenated; 11722 11723 if (!str) { 11724 return ""; // edge case 11725 } 11726 11727 if (vendorPattern.test(str)) { 11728 str = "-" + str; 11729 } 11730 11731 hyphenated = str.replace(/[A-Z]/g, function (match) { 11732 return "-" + match.toLowerCase(); 11733 }); 11734 11735 return hyphenated; 11736 }; 11737 11738 var createTransitions, 11739 animateStyle_createTransitions__testStyle, 11740 TRANSITION, 11741 TRANSITIONEND, 11742 CSS_TRANSITIONS_ENABLED, 11743 TRANSITION_DURATION, 11744 TRANSITION_PROPERTY, 11745 TRANSITION_TIMING_FUNCTION, 11746 canUseCssTransitions = {}, 11747 cannotUseCssTransitions = {}; 11748 11749 if (!isClient) { 11750 createTransitions = null; 11751 } else { 11752 animateStyle_createTransitions__testStyle = createElement("div").style; 11753 11754 // determine some facts about our environment 11755 (function () { 11756 if (animateStyle_createTransitions__testStyle.transition !== undefined) { 11757 TRANSITION = "transition"; 11758 TRANSITIONEND = "transitionend"; 11759 CSS_TRANSITIONS_ENABLED = true; 11760 } else if (animateStyle_createTransitions__testStyle.webkitTransition !== undefined) { 11761 TRANSITION = "webkitTransition"; 11762 TRANSITIONEND = "webkitTransitionEnd"; 11763 CSS_TRANSITIONS_ENABLED = true; 11764 } else { 11765 CSS_TRANSITIONS_ENABLED = false; 11766 } 11767 })(); 11768 11769 if (TRANSITION) { 11770 TRANSITION_DURATION = TRANSITION + "Duration"; 11771 TRANSITION_PROPERTY = TRANSITION + "Property"; 11772 TRANSITION_TIMING_FUNCTION = TRANSITION + "TimingFunction"; 11773 } 11774 11775 createTransitions = function (t, to, options, changedProperties, resolve) { 11776 11777 // Wait a beat (otherwise the target styles will be applied immediately) 11778 // TODO use a fastdom-style mechanism? 11779 setTimeout(function () { 11780 11781 var hashPrefix, jsTransitionsComplete, cssTransitionsComplete, checkComplete, transitionEndHandler; 11782 11783 checkComplete = function () { 11784 if (jsTransitionsComplete && cssTransitionsComplete) { 11785 // will changes to events and fire have an unexpected consequence here? 11786 t.root.fire(t.name + ":end", t.node, t.isIntro); 11787 resolve(); 11788 } 11789 }; 11790 11791 // this is used to keep track of which elements can use CSS to animate 11792 // which properties 11793 hashPrefix = (t.node.namespaceURI || "") + t.node.tagName; 11794 11795 t.node.style[TRANSITION_PROPERTY] = changedProperties.map(helpers_prefix).map(hyphenate).join(","); 11796 t.node.style[TRANSITION_TIMING_FUNCTION] = hyphenate(options.easing || "linear"); 11797 t.node.style[TRANSITION_DURATION] = options.duration / 1000 + "s"; 11798 11799 transitionEndHandler = function (event) { 11800 var index; 11801 11802 index = changedProperties.indexOf(camelCase(unprefix(event.propertyName))); 11803 if (index !== -1) { 11804 changedProperties.splice(index, 1); 11805 } 11806 11807 if (changedProperties.length) { 11808 // still transitioning... 11809 return; 11810 } 11811 11812 t.node.removeEventListener(TRANSITIONEND, transitionEndHandler, false); 11813 11814 cssTransitionsComplete = true; 11815 checkComplete(); 11816 }; 11817 11818 t.node.addEventListener(TRANSITIONEND, transitionEndHandler, false); 11819 11820 setTimeout(function () { 11821 var i = changedProperties.length, 11822 hash, 11823 originalValue, 11824 index, 11825 propertiesToTransitionInJs = [], 11826 prop, 11827 suffix; 11828 11829 while (i--) { 11830 prop = changedProperties[i]; 11831 hash = hashPrefix + prop; 11832 11833 if (CSS_TRANSITIONS_ENABLED && !cannotUseCssTransitions[hash]) { 11834 t.node.style[helpers_prefix(prop)] = to[prop]; 11835 11836 // If we're not sure if CSS transitions are supported for 11837 // this tag/property combo, find out now 11838 if (!canUseCssTransitions[hash]) { 11839 originalValue = t.getStyle(prop); 11840 11841 // if this property is transitionable in this browser, 11842 // the current style will be different from the target style 11843 canUseCssTransitions[hash] = t.getStyle(prop) != to[prop]; 11844 cannotUseCssTransitions[hash] = !canUseCssTransitions[hash]; 11845 11846 // Reset, if we're going to use timers after all 11847 if (cannotUseCssTransitions[hash]) { 11848 t.node.style[helpers_prefix(prop)] = originalValue; 11849 } 11850 } 11851 } 11852
11853 if (!CSS_TRANSITIONS_ENABLED || cannotUseCssTransitions[hash]) { 11854 // we need to fall back to timer-based stuff 11855 if (originalValue === undefined) { 11856 originalValue = t.getStyle(prop); 11857 } 11858 11859 // need to remove this from changedProperties, otherwise transitionEndHandler 11860 // will get confused 11861 index = changedProperties.indexOf(prop); 11862 if (index === -1) { 11863 warnIfDebug("Something very strange happened with transitions. Please raise an issue at https://github.com/ractivejs/ractive/issues - thanks!", { node: t.node }); 11864 } else { 11865 changedProperties.splice(index, 1); 11866 } 11867 11868 // TODO Determine whether this property is animatable at all 11869 11870 suffix = /[^\d]*$/.exec(to[prop])[0]; 11871 11872 // ...then kick off a timer-based transition 11873 propertiesToTransitionInJs.push({ 11874 name: helpers_prefix(prop), 11875 interpolator: shared_interpolate(parseFloat(originalValue), parseFloat(to[prop])), 11876 suffix: suffix 11877 }); 11878 } 11879 } 11880 11881 // javascript transitions 11882 if (propertiesToTransitionInJs.length) { 11883 new shared_Ticker({ 11884 root: t.root, 11885 duration: options.duration, 11886 easing: camelCase(options.easing || ""), 11887 step: function (pos) { 11888 var prop, i; 11889 11890 i = propertiesToTransitionInJs.length; 11891 while (i--) { 11892 prop = propertiesToTransitionInJs[i]; 11893 t.node.style[prop.name] = prop.interpolator(pos) + prop.suffix; 11894 } 11895 }, 11896 complete: function () { 11897 jsTransitionsComplete = true; 11898 checkComplete(); 11899 } 11900 }); 11901 } else { 11902 jsTransitionsComplete = true; 11903 } 11904 11905 if (!changedProperties.length) { 11906 // We need to cancel the transitionEndHandler, and deal with 11907 // the fact that it will never fire 11908 t.node.removeEventListener(TRANSITIONEND, transitionEndHandler, false); 11909 cssTransitionsComplete = true; 11910 checkComplete(); 11911 } 11912 }, 0); 11913 }, options.delay || 0); 11914 }; 11915 } 11916 11917 var animateStyle_createTransitions = createTransitions; 11918 11919 var hidden, vendor, animateStyle_visibility__prefix, animateStyle_visibility__i, visibility; 11920 11921 if (typeof document !== "undefined") { 11922 hidden = "hidden"; 11923 11924 visibility = {}; 11925 11926 if (hidden in document) { 11927 animateStyle_visibility__prefix = ""; 11928 } else { 11929 animateStyle_visibility__i = vendors.length; 11930 while (animateStyle_visibility__i--) { 11931 vendor = vendors[animateStyle_visibility__i]; 11932 hidden = vendor + "Hidden"; 11933 11934 if (hidden in document) { 11935 animateStyle_visibility__prefix = vendor; 11936 } 11937 } 11938 } 11939 11940 if (animateStyle_visibility__prefix !== undefined) { 11941 document.addEventListener(animateStyle_visibility__prefix + "visibilitychange", onChange); 11942 11943 // initialise 11944 onChange(); 11945 } else { 11946 // gah, we're in an old browser 11947 if ("onfocusout" in document) { 11948 document.addEventListener("focusout", onHide); 11949 document.addEventListener("focusin", onShow); 11950 } else { 11951 window.addEventListener("pagehide", onHide); 11952 window.addEventListener("blur", onHide); 11953 11954 window.addEventListener("pageshow", onShow); 11955 window.addEventListener("focus", onShow); 11956 } 11957 11958 visibility.hidden = false; // until proven otherwise. Not ideal but hey 11959 } 11960 } 11961 11962 function onChange() { 11963 visibility.hidden = document[hidden]; 11964 } 11965 11966 function onHide() { 11967 visibility.hidden = true; 11968 } 11969 11970 function onShow() { 11971 visibility.hidden = false; 11972 } 11973 11974 var animateStyle_visibility = visibility; 11975 11976 var animateStyle, _animateStyle__getComputedStyle, resolved; 11977 11978 if (!isClient) { 11979 animateStyle = null; 11980 } else { 11981 _animateStyle__getComputedStyle = window.getComputedStyle || legacy.getComputedStyle; 11982 11983 animateStyle = function (style, value, options) { 11984 var _this = this; 11985 11986 var to; 11987 11988 if (arguments.length === 4) { 11989 throw new Error("t.animateStyle() returns a promise - use .then() instead of passing a callback"); 11990 } 11991 11992 // Special case - page isn't visible. Don't animate anything, because 11993 // that way you'll never get CSS transitionend events 11994 if (animateStyle_visibility.hidden) { 11995 this.setStyle(style, value); 11996 return resolved || (resolved = utils_Promise.resolve()); 11997 } 11998 11999 if (typeof style === "string") { 12000 to = {}; 12001 to[style] = value; 12002 } else { 12003 to = style; 12004 12005 // shuffle arguments 12006 options = value; 12007 } 12008 12009 // As of 0.3.9, transition authors should supply an `option` object with 12010 // `duration` and `easing` properties (and optional `delay`), plus a 12011 // callback function that gets called after the animation completes 12012 12013 // TODO remove this check in a future version 12014 if (!options) { 12015 warnOnceIfDebug("The \"%s\" transition does not supply an options object to `t.animateStyle()`. This will break in a future version of Ractive. For more info see https://github.com/RactiveJS/Ractive/issues/340", this.name); 12016 options = this; 12017 } 12018 12019 var promise = new utils_Promise(function (resolve) { 12020 var propertyNames, changedProperties, computedStyle, current, from, i, prop; 12021 12022 // Edge case - if duration is zero, set style synchronously and complete 12023 if (!options.duration) { 12024 _this.setStyle(to); 12025 resolve(); 12026 return; 12027 } 12028 12029 // Get a list of the properties we're animating 12030 propertyNames = Object.keys(to); 12031 changedProperties = []; 12032 12033 // Store the current styles 12034 computedStyle = _animateStyle__getComputedStyle(_this.node); 12035 12036 from = {}; 12037 i = propertyNames.length; 12038 while (i--) { 12039 prop = propertyNames[i]; 12040 current = computedStyle[helpers_prefix(prop)]; 12041 12042 if (current === "0px") { 12043 current = 0; 12044 } 12045 12046 // we need to know if we're actually changing anything 12047 if (current != to[prop]) {
12048 // use != instead of !==, so we can compare strings with numbers 12049 changedProperties.push(prop); 12050 12051 // make the computed style explicit, so we can animate where 12052 // e.g. height='auto' 12053 _this.node.style[helpers_prefix(prop)] = current; 12054 } 12055 } 12056 12057 // If we're not actually changing anything, the transitionend event 12058 // will never fire! So we complete early 12059 if (!changedProperties.length) { 12060 resolve(); 12061 return; 12062 } 12063 12064 animateStyle_createTransitions(_this, to, options, changedProperties, resolve); 12065 }); 12066 12067 return promise; 12068 }; 12069 } 12070 12071 var _animateStyle = animateStyle; 12072 12073 var processParams = function (params, defaults) { 12074 if (typeof params === "number") { 12075 params = { duration: params }; 12076 } else if (typeof params === "string") { 12077 if (params === "slow") { 12078 params = { duration: 600 }; 12079 } else if (params === "fast") { 12080 params = { duration: 200 }; 12081 } else { 12082 params = { duration: 400 }; 12083 } 12084 } else if (!params) { 12085 params = {}; 12086 } 12087 12088 return fillGaps({}, params, defaults); 12089 }; 12090 12091 var prototype_start = Transition$start; 12092 12093 function Transition$start() { 12094 var _this = this; 12095 12096 var node, originalStyle, completed; 12097 12098 node = this.node = this.element.node; 12099 originalStyle = node.getAttribute("style"); 12100 12101 // create t.complete() - we don't want this on the prototype, 12102 // because we don't want `this` silliness when passing it as 12103 // an argument 12104 this.complete = function (noReset) { 12105 if (completed) { 12106 return; 12107 } 12108 12109 if (!noReset && _this.isIntro) { 12110 resetStyle(node, originalStyle); 12111 } 12112 12113 node._ractive.transition = null; 12114 _this._manager.remove(_this); 12115 12116 completed = true; 12117 }; 12118 12119 // If the transition function doesn't exist, abort 12120 if (!this._fn) { 12121 this.complete(); 12122 return; 12123 } 12124 12125 this._fn.apply(this.root, [this].concat(this.params)); 12126 } 12127 12128 function resetStyle(node, style) { 12129 if (style) { 12130 node.setAttribute("style", style); 12131 } else { 12132 12133 // Next line is necessary, to remove empty style attribute! 12134 // See http://stackoverflow.com/a/7167553 12135 node.getAttribute("style"); 12136 node.removeAttribute("style"); 12137 } 12138 } 12139 12140 var Transition = function (owner, template, isIntro) { 12141 this.init(owner, template, isIntro); 12142 }; 12143 12144 Transition.prototype = { 12145 init: Transition_prototype_init, 12146 start: prototype_start, 12147 getStyle: prototype_getStyle, 12148 setStyle: setStyle, 12149 animateStyle: _animateStyle, 12150 processParams: processParams 12151 }; 12152 12153 var _Transition = Transition; 12154 12155 var Element_prototype_render = Element$render; 12156 12157 var updateCss, updateScript; 12158 12159 updateCss = function () { 12160 var node = this.node, 12161 content = this.fragment.toString(false); 12162 12163 // IE8 has no styleSheet unless there's a type text/css 12164 if (window && window.appearsToBeIELessEqual8) { 12165 node.type = "text/css"; 12166 } 12167 12168 if (node.styleSheet) { 12169 node.styleSheet.cssText = content; 12170 } else { 12171 12172 while (node.hasChildNodes()) { 12173 node.removeChild(node.firstChild); 12174 } 12175 12176 node.appendChild(document.createTextNode(content)); 12177 } 12178 }; 12179 12180 updateScript = function () { 12181 if (!this.node.type || this.node.type === "text/javascript") { 12182 warnIfDebug("Script tag was updated. This does not cause the code to be re-evaluated!", { ractive: this.root }); 12183 // As it happens, we ARE in a position to re-evaluate the code if we wanted 12184 // to - we could eval() it, or insert it into a fresh (temporary) script tag. 12185 // But this would be a terrible idea with unpredictable results, so let's not. 12186 } 12187 12188 this.node.text = this.fragment.toString(false); 12189 }; 12190 function Element$render() { 12191 var _this = this; 12192 12193 var root = this.root, 12194 namespace, 12195 node, 12196 transition; 12197 12198 namespace = getNamespace(this); 12199 node = this.node = createElement(this.name, namespace); 12200 12201 // Is this a top-level node of a component? If so, we may need to add 12202 // a data-ractive-css attribute, for CSS encapsulation 12203 if (this.parentFragment.cssIds) { 12204 this.node.setAttribute("data-ractive-css", this.parentFragment.cssIds.map(function (x) { 12205 return "{" + x + "}"; 12206 }).join(" ")); 12207 } 12208 12209 // Add _ractive property to the node - we use this object to store stuff 12210 // related to proxy events, two-way bindings etc 12211 defineProperty(this.node, "_ractive", { 12212 value: { 12213 proxy: this, 12214 keypath: getInnerContext(this.parentFragment), 12215 events: create(null), 12216 root: root 12217 } 12218 }); 12219 12220 // Render attributes
12221 this.attributes.forEach(function (a) { 12222 return a.render(node); 12223 }); 12224 this.conditionalAttributes.forEach(function (a) { 12225 return a.render(node); 12226 }); 12227 12228 // Render children 12229 if (this.fragment) { 12230 // Special case - <script> element 12231 if (this.name === "script") { 12232 this.bubble = updateScript; 12233 this.node.text = this.fragment.toString(false); // bypass warning initially 12234 this.fragment.unrender = noop; // TODO this is a kludge 12235 } 12236 12237 // Special case - <style> element 12238 else if (this.name === "style") { 12239 this.bubble = updateCss; 12240 this.bubble(); 12241 this.fragment.unrender = noop; 12242 } 12243 12244 // Special case - contenteditable 12245 else if (this.binding && this.getAttribute("contenteditable")) { 12246 this.fragment.unrender = noop; 12247 } else { 12248 this.node.appendChild(this.fragment.render()); 12249 } 12250 } 12251 12252 // deal with two-way bindings 12253 if (this.binding) { 12254 this.binding.render(); 12255 this.node._ractive.binding = this.binding; 12256 } 12257 12258 // Add proxy event handlers 12259 if (this.eventHandlers) { 12260 this.eventHandlers.forEach(function (h) { 12261 return h.render(); 12262 }); 12263 } 12264 12265 if (this.name === "option") { 12266 processOption(this); 12267 } 12268 12269 // Special cases 12270 if (this.name === "img") { 12271 // if this is an <img>, and we're in a crap browser, we may 12272 // need to prevent it from overriding width and height when 12273 // it loads the src 12274 special_img__render(this); 12275 } else if (this.name === "form") { 12276 // forms need to keep track of their bindings, in case of reset 12277 form__render(this); 12278 } else if (this.name === "input" || this.name === "textarea") { 12279 // inputs and textareas should store their initial value as 12280 // `defaultValue` in case of reset 12281 this.node.defaultValue = this.node.value; 12282 } else if (this.name === "option") { 12283 // similarly for option nodes 12284 this.node.defaultSelected = this.node.selected; 12285 } 12286 12287 // apply decorator(s) 12288 if (this.decorator && this.decorator.fn) { 12289 global_runloop.scheduleTask(function () { 12290 if (!_this.decorator.torndown) { 12291 _this.decorator.init(); 12292 } 12293 }, true); 12294 } 12295 12296 // trigger intro transition 12297 if (root.transitionsEnabled && this.intro) { 12298 transition = new _Transition(this, this.intro, true); 12299 global_runloop.registerTransition(transition); 12300 global_runloop.scheduleTask(function () { 12301 return transition.start(); 12302 }, true); 12303 12304 this.transition = transition; 12305 } 12306 12307 if (this.node.autofocus) { 12308 // Special case. Some browsers (*cough* Firefix *cough*) have a problem 12309 // with dynamically-generated elements having autofocus, and they won't 12310 // allow you to programmatically focus the element until it's in the DOM 12311 global_runloop.scheduleTask(function () { 12312 return _this.node.focus(); 12313 }, true); 12314 } 12315 12316 updateLiveQueries(this); 12317 return this.node; 12318 } 12319 12320 function getNamespace(element) { 12321 var namespace, xmlns, parent; 12322 12323 // Use specified namespace... 12324 if (xmlns = element.getAttribute("xmlns")) { 12325 namespace = xmlns; 12326 } 12327 12328 // ...or SVG namespace, if this is an <svg> element 12329 else if (element.name === "svg") { 12330 namespace = namespaces.svg; 12331 } else if (parent = element.parent) { 12332 // ...or HTML, if the parent is a <foreignObject> 12333 if (parent.name === "foreignObject") { 12334 namespace = namespaces.html; 12335 } 12336 12337 // ...or inherit from the parent node 12338 else { 12339 namespace = parent.node.namespaceURI; 12340 } 12341 } else { 12342 namespace = element.root.el.namespaceURI; 12343 } 12344 12345 return namespace; 12346 } 12347 12348 function processOption(option) { 12349 var optionValue, selectValue, i; 12350 12351 if (!option.select) { 12352 return; 12353 } 12354 12355 selectValue = option.select.getAttribute("value"); 12356 if (selectValue === undefined) { 12357 return; 12358 } 12359 12360 optionValue = option.getAttribute("value"); 12361 12362 if (option.select.node.multiple && isArray(selectValue)) { 12363 i = selectValue.length; 12364 while (i--) { 12365 if (optionValue == selectValue[i]) { 12366 option.node.selected = true; 12367 break; 12368 } 12369 } 12370 } else { 12371 option.node.selected = optionValue == selectValue; 12372 } 12373 } 12374 12375 function updateLiveQueries(element) { 12376 var instance, liveQueries, i, selector, query; 12377 12378 // Does this need to be added to any live queries? 12379 instance = element.root; 12380 12381 do { 12382 liveQueries = instance._liveQueries; 12383 12384 i = liveQueries.length; 12385 while (i--) { 12386 selector = liveQueries[i]; 12387 query = liveQueries["_" + selector]; 12388 12389 if (query._test(element)) { 12390 // keep register of applicable selectors, for when we teardown 12391 (element.liveQueries || (element.liveQueries = [])).push(query); 12392 } 12393 } 12394 } while (instance = instance.parent); 12395 } 12396 12397 var Element_prototype_toString = function () { 12398 var str, escape; 12399 12400 if (this.template.y) { 12401 // DOCTYPE declaration 12402 return "<!DOCTYPE" + this.template.dd + ">"; 12403 } 12404 12405 str = "<" + this.template.e; 12406 12407 str += this.attributes.map(stringifyAttribute).join("") + this.conditionalAttributes.map(stringifyAttribute).join(""); 12408 12409 // Special case - selected options 12410 if (this.name === "option" && optionIsSelected(this)) { 12411 str += " selected"; 12412 } 12413 12414 // Special case - two-way radio name bindings 12415 if (this.name === "input" && inputIsCheckedRadio(this)) { 12416 str += " checked"; 12417 } 12418 12419 str += ">"; 12420 12421 // Special case - textarea 12422 if (this.name === "textarea" && this.getAttribute("value") !== undefined) { 12423 str += escapeHtml(this.getAttribute("value")); 12424 } 12425 12426 // Special case - contenteditable 12427 else if (this.getAttribute("contenteditable") !== undefined) { 12428 str += this.getAttribute("value") || ""; 12429 } 12430 12431 if (this.fragment) { 12432 escape = this.name !== "script" && this.name !== "style"; 12433 str += this.fragment.toString(escape); 12434 } 12435 12436 // add a closing tag if this isn't a void element 12437 if (!voidElementNames.test(this.template.e)) { 12438 str += "</" + this.template.e + ">"; 12439 } 12440 12441 return str; 12442 }; 12443 12444 function optionIsSelected(element) { 12445 var optionValue, selectValue, i; 12446 12447 optionValue = element.getAttribute("value"); 12448 12449 if (optionValue === undefined || !element.select) { 12450 return false;
12451 } 12452 12453 selectValue = element.select.getAttribute("value"); 12454 12455 if (selectValue == optionValue) { 12456 return true; 12457 } 12458 12459 if (element.select.getAttribute("multiple") && isArray(selectValue)) { 12460 i = selectValue.length; 12461 while (i--) { 12462 if (selectValue[i] == optionValue) { 12463 return true; 12464 } 12465 } 12466 } 12467 } 12468 12469 function inputIsCheckedRadio(element) { 12470 var attributes, typeAttribute, valueAttribute, nameAttribute; 12471 12472 attributes = element.attributes; 12473 12474 typeAttribute = attributes.type; 12475 valueAttribute = attributes.value; 12476 nameAttribute = attributes.name; 12477 12478 if (!typeAttribute || typeAttribute.value !== "radio" || !valueAttribute || !nameAttribute.interpolator) { 12479 return; 12480 } 12481 12482 if (valueAttribute.value === nameAttribute.interpolator.value) { 12483 return true; 12484 } 12485 } 12486 12487 function stringifyAttribute(attribute) { 12488 var str = attribute.toString(); 12489 return str ? " " + str : ""; 12490 } 12491 12492 var Element_prototype_unbind = Element$unbind; 12493 function Element$unbind() { 12494 if (this.fragment) { 12495 this.fragment.unbind(); 12496 } 12497 12498 if (this.binding) { 12499 this.binding.unbind(); 12500 } 12501 12502 if (this.eventHandlers) { 12503 this.eventHandlers.forEach(methodCallers__unbind); 12504 } 12505 12506 // Special case - <option> 12507 if (this.name === "option") { 12508 special_option__unbind(this); 12509 } 12510 12511 this.attributes.forEach(methodCallers__unbind); 12512 this.conditionalAttributes.forEach(methodCallers__unbind); 12513 } 12514 12515 var Element_prototype_unrender = Element$unrender; 12516 12517 function Element$unrender(shouldDestroy) { 12518 var binding, bindings, transition; 12519 12520 if (transition = this.transition) { 12521 transition.complete(); 12522 } 12523
12524 // Detach as soon as we can 12525 if (this.name === "option") { 12526 // <option> elements detach immediately, so that 12527 // their parent <select> element syncs correctly, and 12528 // since option elements can't have transitions anyway 12529 this.detach(); 12530 } else if (shouldDestroy) { 12531 global_runloop.detachWhenReady(this); 12532 } 12533 12534 // Children first. that way, any transitions on child elements will be 12535 // handled by the current transitionManager 12536 if (this.fragment) { 12537 this.fragment.unrender(false); 12538 } 12539 12540 if (binding = this.binding) { 12541 this.binding.unrender(); 12542 12543 this.node._ractive.binding = null; 12544 bindings = this.root._twowayBindings[binding.keypath.str]; 12545 bindings.splice(bindings.indexOf(binding), 1); 12546 } 12547 12548 // Remove event handlers 12549 if (this.eventHandlers) { 12550 this.eventHandlers.forEach(methodCallers__unrender); 12551 } 12552 12553 if (this.decorator) { 12554 global_runloop.registerDecorator(this.decorator); 12555 } 12556 12557 // trigger outro transition if necessary 12558 if (this.root.transitionsEnabled && this.outro) { 12559 transition = new _Transition(this, this.outro, false); 12560 global_runloop.registerTransition(transition); 12561 global_runloop.scheduleTask(function () { 12562 return transition.start(); 12563 }); 12564 } 12565 12566 // Remove this node from any live queries 12567 if (this.liveQueries) { 12568 removeFromLiveQueries(this); 12569 } 12570 12571 if (this.name === "form") { 12572 form__unrender(this); 12573 } 12574 } 12575 12576 function removeFromLiveQueries(element) { 12577 var query, selector, i; 12578 12579 i = element.liveQueries.length; 12580 while (i--) { 12581 query = element.liveQueries[i]; 12582 selector = query.selector; 12583 12584 query._remove(element.node); 12585 } 12586 } 12587 12588 var Element = function (options) { 12589 this.init(options); 12590 }; 12591 12592 Element.prototype = { 12593 bubble: Element_prototype_bubble, 12594 detach: Element_prototype_detach, 12595 find: Element_prototype_find, 12596 findAll: Element_prototype_findAll, 12597 findAllComponents: Element_prototype_findAllComponents, 12598 findComponent: Element_prototype_findComponent, 12599 findNextNode: Element_prototype_findNextNode, 12600 firstNode: Element_prototype_firstNode, 12601 getAttribute: getAttribute, 12602 init: Element_prototype_init, 12603 rebind: Element_prototype_rebind, 12604 render: Element_prototype_render, 12605 toString: Element_prototype_toString, 12606 unbind: Element_prototype_unbind, 12607 unrender: Element_prototype_unrender 12608 }; 12609 12610 var _Element = Element; 12611 12612 var deIndent__empty = /^\s*$/, 12613 deIndent__leadingWhitespace = /^\s*/; 12614 12615 var deIndent = function (str) { 12616 var lines, firstLine, lastLine, minIndent; 12617 12618 lines = str.split("\n"); 12619 12620 // remove first and last line, if they only contain whitespace 12621 firstLine = lines[0]; 12622 if (firstLine !== undefined && deIndent__empty.test(firstLine)) { 12623 lines.shift(); 12624 } 12625 12626 lastLine = lastItem(lines); 12627 if (lastLine !== undefined && deIndent__empty.test(lastLine)) { 12628 lines.pop(); 12629 } 12630 12631 minIndent = lines.reduce(reducer, null); 12632 12633 if (minIndent) { 12634 str = lines.map(function (line) { 12635 return line.replace(minIndent, ""); 12636 }).join("\n"); 12637 } 12638 12639 return str; 12640 }; 12641 12642 function reducer(previous, line) { 12643 var lineIndent = deIndent__leadingWhitespace.exec(line)[0]; 12644 12645 if (previous === null || lineIndent.length < previous.length) { 12646 return lineIndent; 12647 } 12648 12649 return previous; 12650 } 12651 12652 var Partial_getPartialTemplate = getPartialTemplate; 12653 12654 function getPartialTemplate(ractive, name, parentFragment) { 12655 var partial; 12656 12657 // If the partial in instance or view heirarchy instances, great 12658 if (partial = getPartialFromRegistry(ractive, name, parentFragment || {})) { 12659 return partial; 12660 } 12661 12662 // Does it exist on the page as a script tag? 12663 partial = template_parser.fromId(name, { noThrow: true }); 12664 12665 if (partial) { 12666 // is this necessary? 12667 partial = deIndent(partial); 12668 12669 // parse and register to this ractive instance 12670 var parsed = template_parser.parse(partial, template_parser.getParseOptions(ractive)); 12671 12672 // register (and return main partial if there are others in the template) 12673 return ractive.partials[name] = parsed.t; 12674 } 12675 } 12676 12677 function getPartialFromRegistry(ractive, name, parentFragment) { 12678 var fn = undefined, 12679 partial = findParentPartial(name, parentFragment.owner); 12680 12681 // if there was an instance up-hierarchy, cool 12682 if (partial) return partial; 12683 12684 // find first instance in the ractive or view hierarchy that has this partial
12685 var instance = findInstance("partials", ractive, name); 12686 12687 if (!instance) { 12688 return; 12689 } 12690 12691 partial = instance.partials[name]; 12692 12693 // partial is a function? 12694 if (typeof partial === "function") { 12695 fn = partial.bind(instance); 12696 fn.isOwner = instance.partials.hasOwnProperty(name); 12697 partial = fn.call(ractive, template_parser); 12698 } 12699 12700 if (!partial && partial !== "") { 12701 warnIfDebug(noRegistryFunctionReturn, name, "partial", "partial", { ractive: ractive }); 12702 return; 12703 } 12704 12705 // If this was added manually to the registry, 12706 // but hasn't been parsed, parse it now 12707 if (!template_parser.isParsed(partial)) { 12708 12709 // use the parseOptions of the ractive instance on which it was found 12710 var parsed = template_parser.parse(partial, template_parser.getParseOptions(instance)); 12711 12712 // Partials cannot contain nested partials! 12713 // TODO add a test for this 12714 if (parsed.p) { 12715 warnIfDebug("Partials ({{>%s}}) cannot contain nested inline partials", name, { ractive: ractive }); 12716 } 12717 12718 // if fn, use instance to store result, otherwise needs to go 12719 // in the correct point in prototype chain on instance or constructor 12720 var target = fn ? instance : findOwner(instance, name); 12721 12722 // may be a template with partials, which need to be registered and main template extracted 12723 target.partials[name] = partial = parsed.t; 12724 } 12725 12726 // store for reset 12727 if (fn) { 12728 partial._fn = fn; 12729 } 12730 12731 return partial.v ? partial.t : partial; 12732 } 12733 12734 function findOwner(ractive, key) { 12735 return ractive.partials.hasOwnProperty(key) ? ractive : findConstructor(ractive.constructor, key); 12736 } 12737 12738 function findConstructor(constructor, key) { 12739 if (!constructor) { 12740 return; 12741 } 12742 return constructor.partials.hasOwnProperty(key) ? constructor : findConstructor(constructor._Parent, key); 12743 } 12744 12745 function findParentPartial(name, parent) { 12746 if (parent) { 12747 if (parent.template && parent.template.p && parent.template.p[name]) { 12748 return parent.template.p[name]; 12749 } else if (parent.parentFragment && parent.parentFragment.owner) { 12750 return findParentPartial(name, parent.parentFragment.owner); 12751 } 12752 } 12753 } 12754 12755 var applyIndent = function (string, indent) { 12756 var indented; 12757 12758 if (!indent) { 12759 return string; 12760 } 12761 12762 indented = string.split("\n").map(function (line, notFirstLine) { 12763 return notFirstLine ? indent + line : line; 12764 }).join("\n"); 12765 12766 return indented; 12767 }; 12768 12769 var missingPartialMessage = "Could not find template for partial \"%s\""; 12770 12771 var Partial = function (options) { 12772 var parentFragment, template; 12773 12774 parentFragment = this.parentFragment = options.parentFragment; 12775 12776 this.root = parentFragment.root; 12777 this.type = PARTIAL; 12778 this.index = options.index; 12779 this.name = options.template.r; 12780 this.rendered = false; 12781 12782 this.fragment = this.fragmentToRender = this.fragmentToUnrender = null; 12783 12784 Mustache.init(this, options); 12785 12786 // If this didn't resolve, it most likely means we have a named partial 12787 // (i.e. `{{>foo}}` means 'use the foo partial', not 'use the partial 12788 // whose name is the value of `foo`') 12789 if (!this.keypath) { 12790 if (template = Partial_getPartialTemplate(this.root, this.name, parentFragment)) { 12791 shared_unbind.call(this); // prevent any further changes 12792 this.isNamed = true; 12793 this.setTemplate(template); 12794 } else { 12795 warnOnceIfDebug(missingPartialMessage, this.name); 12796 } 12797 } 12798 }; 12799 12800 Partial.prototype = { 12801 bubble: function () { 12802 this.parentFragment.bubble(); 12803 }, 12804 12805 detach: function () { 12806 return this.fragment.detach(); 12807 }, 12808 12809 find: function (selector) { 12810 return this.fragment.find(selector); 12811 }, 12812 12813 findAll: function (selector, query) { 12814 return this.fragment.findAll(selector, query); 12815 }, 12816 12817 findComponent: function (selector) { 12818 return this.fragment.findComponent(selector); 12819 }, 12820 12821 findAllComponents: function (selector, query) { 12822 return this.fragment.findAllComponents(selector, query); 12823 }, 12824 12825 firstNode: function () { 12826 return this.fragment.firstNode(); 12827 }, 12828 12829 findNextNode: function () { 12830 return this.parentFragment.findNextNode(this); 12831 }, 12832 12833 getPartialName: function () { 12834 if (this.isNamed && this.name) return this.name;else if (this.value === undefined) return this.name;else return this.value; 12835 }, 12836 12837 getValue: function () { 12838 return this.fragment.getValue(); 12839 }, 12840 12841 rebind: function (oldKeypath, newKeypath) { 12842 // named partials aren't bound, so don't rebind 12843 if (!this.isNamed) { 12844 Mustache_rebind.call(this, oldKeypath, newKeypath); 12845 } 12846 12847 if (this.fragment) { 12848 this.fragment.rebind(oldKeypath, newKeypath); 12849 } 12850 }, 12851 12852 render: function () { 12853 this.docFrag = document.createDocumentFragment(); 12854 this.update(); 12855 12856 this.rendered = true; 12857 return this.docFrag; 12858 }, 12859 12860 resolve: Mustache.resolve, 12861 12862 setValue: function (value) { 12863 var template; 12864 12865 if (value !== undefined && value === this.value) { 12866 // nothing has changed, so no work to be done 12867 return; 12868 } 12869 12870 if (value !== undefined) { 12871 template = Partial_getPartialTemplate(this.root, "" + value, this.parentFragment); 12872 } 12873 12874 // we may be here if we have a partial like `{{>foo}}` and `foo` is the 12875 // name of both a data property (whose value ISN'T the name of a partial) 12876 // and a partial. In those cases, this becomes a named partial 12877 if (!template && this.name && (template = Partial_getPartialTemplate(this.root, this.name, this.parentFragment))) { 12878 shared_unbind.call(this); 12879 this.isNamed = true; 12880 } 12881 12882 if (!template) {
12883 warnOnceIfDebug(missingPartialMessage, this.name, { ractive: this.root }); 12884 } 12885 12886 this.value = value; 12887 12888 this.setTemplate(template || []); 12889 12890 this.bubble(); 12891 12892 if (this.rendered) { 12893 global_runloop.addView(this); 12894 } 12895 }, 12896 12897 setTemplate: function (template) { 12898 if (this.fragment) { 12899 this.fragment.unbind(); 12900 if (this.rendered) { 12901 this.fragmentToUnrender = this.fragment; 12902 } 12903 } 12904 12905 this.fragment = new virtualdom_Fragment({ 12906 template: template, 12907 root: this.root, 12908 owner: this, 12909 pElement: this.parentFragment.pElement 12910 }); 12911 12912 this.fragmentToRender = this.fragment; 12913 }, 12914 12915 toString: function (toString) { 12916 var string, previousItem, lastLine, match; 12917 12918 string = this.fragment.toString(toString); 12919 12920 previousItem = this.parentFragment.items[this.index - 1]; 12921 12922 if (!previousItem || previousItem.type !== TEXT) { 12923 return string; 12924 } 12925 12926 lastLine = previousItem.text.split("\n").pop(); 12927 12928 if (match = /^\s+$/.exec(lastLine)) { 12929 return applyIndent(string, match[0]); 12930 } 12931 12932 return string; 12933 }, 12934 12935 unbind: function () { 12936 if (!this.isNamed) { 12937 // dynamic partial - need to unbind self 12938 shared_unbind.call(this); 12939 } 12940 12941 if (this.fragment) { 12942 this.fragment.unbind(); 12943 } 12944 }, 12945 12946 unrender: function (shouldDestroy) { 12947 if (this.rendered) { 12948 if (this.fragment) { 12949 this.fragment.unrender(shouldDestroy); 12950 } 12951 this.rendered = false; 12952 } 12953 }, 12954 12955 update: function () { 12956 var target, anchor; 12957 12958 if (this.fragmentToUnrender) { 12959 this.fragmentToUnrender.unrender(true); 12960 this.fragmentToUnrender = null; 12961 } 12962 12963 if (this.fragmentToRender) { 12964 this.docFrag.appendChild(this.fragmentToRender.render()); 12965 this.fragmentToRender = null; 12966 } 12967 12968 if (this.rendered) { 12969 target = this.parentFragment.getNode(); 12970 anchor = this.parentFragment.findNextNode(this); 12971 target.insertBefore(this.docFrag, anchor); 12972 } 12973 } 12974 }; 12975 12976 var _Partial = Partial; 12977 12978 // finds the component constructor in the registry or view hierarchy registries 12979 12980 var Component_getComponent = getComponent; 12981 function getComponent(ractive, name) { 12982 12983 var Component, 12984 instance = findInstance("components", ractive, name); 12985 12986 if (instance) { 12987 Component = instance.components[name]; 12988 12989 // best test we have for not Ractive.extend 12990 if (!Component._Parent) { 12991 // function option, execute and store for reset 12992 var fn = Component.bind(instance); 12993 fn.isOwner = instance.components.hasOwnProperty(name); 12994 Component = fn(); 12995 12996 if (!Component) { 12997 warnIfDebug(noRegistryFunctionReturn, name, "component", "component", { ractive: ractive }); 12998 12999 return; 13000 } 13001 13002 if (typeof Component === "string") { 13003 // allow string lookup 13004 Component = getComponent(ractive, Component); 13005 } 13006 13007 Component._fn = fn; 13008 instance.components[name] = Component; 13009 } 13010 } 13011 13012 return Component; 13013 } 13014 13015 var Component_prototype_detach = Component$detach; 13016 var Component_prototype_detach__detachHook = new hooks_Hook("detach"); 13017 function Component$detach() { 13018 var detached = this.instance.fragment.detach(); 13019 Component_prototype_detach__detachHook.fire(this.instance); 13020 return detached; 13021 } 13022 13023 var Component_prototype_find = Component$find; 13024 13025 function Component$find(selector) { 13026 return this.instance.fragment.find(selector); 13027 } 13028 13029 var Component_prototype_findAll = Component$findAll; 13030 13031 function Component$findAll(selector, query) { 13032 return this.instance.fragment.findAll(selector, query); 13033 } 13034 13035 var Component_prototype_findAllComponents = Component$findAllComponents; 13036 13037 function Component$findAllComponents(selector, query) { 13038 query._test(this, true); 13039 13040 if (this.instance.fragment) { 13041 this.instance.fragment.findAllComponents(selector, query); 13042 } 13043 } 13044 13045 var Component_prototype_findComponent = Component$findComponent; 13046 13047 function Component$findComponent(selector) { 13048 if (!selector || selector === this.name) { 13049 return this.instance; 13050 } 13051 13052 if (this.instance.fragment) { 13053 return this.instance.fragment.findComponent(selector); 13054 } 13055 13056 return null; 13057 } 13058 13059 var Component_prototype_findNextNode = Component$findNextNode; 13060 13061 function Component$findNextNode() { 13062 return this.parentFragment.findNextNode(this); 13063 } 13064 13065 var Component_prototype_firstNode = Component$firstNode; 13066 13067 function Component$firstNode() { 13068 if (this.rendered) { 13069 return this.instance.fragment.firstNode(); 13070 } 13071 13072 return null; 13073 } 13074 13075 var processWrapper = function (wrapper, array, methodName, newIndices) { 13076 var root = wrapper.root; 13077 var keypath = wrapper.keypath; 13078 13079 if (!!newIndices) { 13080 root.viewmodel.smartUpdate(keypath, array, newIndices); 13081 } else { 13082 // If this is a sort or reverse, we just do root.set()... 13083 // TODO use merge logic? 13084 root.viewmodel.mark(keypath); 13085 } 13086 }; 13087 13088 var patchedArrayProto = [], 13089 mutatorMethods = ["pop", "push", "reverse", "shift", "sort", "splice", "unshift"], 13090 testObj, 13091 patchArrayMethods, 13092 unpatchArrayMethods; 13093 13094 mutatorMethods.forEach(function (methodName) { 13095 var method = function () { 13096 for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { 13097 args[_key] = arguments[_key]; 13098 } 13099 13100 var newIndices, result, wrapper, i; 13101 13102 newIndices = shared_getNewIndices(this, methodName, args); 13103 13104 // apply the underlying method 13105 result = Array.prototype[methodName].apply(this, arguments); 13106 13107 // trigger changes 13108 global_runloop.start(); 13109 13110 this._ractive.setting = true; 13111 i = this._ractive.wrappers.length; 13112 while (i--) { 13113 wrapper = this._ractive.wrappers[i]; 13114 13115 global_runloop.addRactive(wrapper.root); 13116 processWrapper(wrapper, this, methodName, newIndices); 13117 } 13118 13119 global_runloop.end(); 13120 13121 this._ractive.setting = false;
13122 return result; 13123 }; 13124 13125 defineProperty(patchedArrayProto, methodName, { 13126 value: method 13127 }); 13128 }); 13129 13130 // can we use prototype chain injection? 13131 // http://perfectionkills.com/how-ecmascript-5-still-does-not-allow-to-subclass-an-array/#wrappers_prototype_chain_injection 13132 testObj = {}; 13133 13134 if (testObj.__proto__) { 13135 // yes, we can 13136 patchArrayMethods = function (array) { 13137 array.__proto__ = patchedArrayProto; 13138 }; 13139 13140 unpatchArrayMethods = function (array) { 13141 array.__proto__ = Array.prototype; 13142 }; 13143 } else { 13144 // no, we can't 13145 patchArrayMethods = function (array) { 13146 var i, methodName; 13147 13148 i = mutatorMethods.length; 13149 while (i--) { 13150 methodName = mutatorMethods[i]; 13151 defineProperty(array, methodName, { 13152 value: patchedArrayProto[methodName], 13153 configurable: true 13154 }); 13155 } 13156 }; 13157 13158 unpatchArrayMethods = function (array) { 13159 var i; 13160 13161 i = mutatorMethods.length; 13162 while (i--) { 13163 delete array[mutatorMethods[i]]; 13164 } 13165 }; 13166 } 13167 13168 patchArrayMethods.unpatch = unpatchArrayMethods; 13169 var patch = patchArrayMethods; 13170 13171 var arrayAdaptor, 13172 13173 // helpers 13174 ArrayWrapper, array_index__errorMessage; 13175 13176 arrayAdaptor = { 13177 filter: function (object) { 13178 // wrap the array if a) b) it's an array, and b) either it hasn't been wrapped already, 13179 // or the array didn't trigger the get() itself 13180 return isArray(object) && (!object._ractive || !object._ractive.setting); 13181 }, 13182 wrap: function (ractive, array, keypath) { 13183 return new ArrayWrapper(ractive, array, keypath); 13184 } 13185 }; 13186 13187 ArrayWrapper = function (ractive, array, keypath) { 13188 this.root = ractive; 13189 this.value = array; 13190 this.keypath = getKeypath(keypath); 13191 13192 // if this array hasn't already been ractified, ractify it 13193 if (!array._ractive) { 13194 13195 // define a non-enumerable _ractive property to store the wrappers 13196 defineProperty(array, "_ractive", { 13197 value: { 13198 wrappers: [], 13199 instances: [], 13200 setting: false 13201 }, 13202 configurable: true 13203 }); 13204 13205 patch(array); 13206 } 13207 13208 // store the ractive instance, so we can handle transitions later 13209 if (!array._ractive.instances[ractive._guid]) { 13210 array._ractive.instances[ractive._guid] = 0; 13211 array._ractive.instances.push(ractive); 13212 } 13213 13214 array._ractive.instances[ractive._guid] += 1; 13215 array._ractive.wrappers.push(this); 13216 }; 13217 13218 ArrayWrapper.prototype = { 13219 get: function () { 13220 return this.value; 13221 }, 13222 teardown: function () { 13223 var array, storage, wrappers, instances, index; 13224 13225 array = this.value; 13226 storage = array._ractive; 13227 wrappers = storage.wrappers; 13228 instances = storage.instances; 13229 13230 // if teardown() was invoked because we're clearing the cache as a result of 13231 // a change that the array itself triggered, we can save ourselves the teardown 13232 // and immediate setup 13233 if (storage.setting) { 13234 return false; // so that we don't remove it from this.root.viewmodel.wrapped 13235 } 13236 13237 index = wrappers.indexOf(this); 13238 if (index === -1) { 13239 throw new Error(array_index__errorMessage); 13240 } 13241 13242 wrappers.splice(index, 1); 13243 13244 // if nothing else depends on this array, we can revert it to its 13245 // natural state 13246 if (!wrappers.length) { 13247 delete array._ractive; 13248 patch.unpatch(this.value); 13249 } else { 13250 // remove ractive instance if possible 13251 instances[this.root._guid] -= 1; 13252 if (!instances[this.root._guid]) { 13253 index = instances.indexOf(this.root); 13254 13255 if (index === -1) { 13256 throw new Error(array_index__errorMessage); 13257 } 13258 13259 instances.splice(index, 1); 13260 } 13261 } 13262 } 13263 }; 13264 13265 array_index__errorMessage = "Something went wrong in a rather interesting way"; 13266 var array_index = arrayAdaptor; 13267 13268 var numeric = /^\s*[0-9]+\s*$/; 13269 13270 var createBranch = function (key) { 13271 return numeric.test(key) ? [] : {}; 13272 }; 13273 13274 var magicAdaptor, MagicWrapper; 13275 13276 try { 13277 Object.defineProperty({}, "test", { value: 0 }); 13278 13279 magicAdaptor = { 13280 filter: function (object, keypath, ractive) { 13281 var parentWrapper, parentValue; 13282 13283 if (!keypath) { 13284 return false; 13285 } 13286 13287 keypath = getKeypath(keypath); 13288 13289 // If the parent value is a wrapper, other than a magic wrapper, 13290 // we shouldn't wrap this property 13291 if ((parentWrapper = ractive.viewmodel.wrapped[keypath.parent.str]) && !parentWrapper.magic) { 13292 return false;
13293 } 13294 13295 parentValue = ractive.viewmodel.get(keypath.parent); 13296 13297 // if parentValue is an array that doesn't include this member, 13298 // we should return false otherwise lengths will get messed up 13299 if (isArray(parentValue) && /^[0-9]+$/.test(keypath.lastKey)) { 13300 return false; 13301 } 13302 13303 return parentValue && (typeof parentValue === "object" || typeof parentValue === "function"); 13304 }, 13305 wrap: function (ractive, property, keypath) { 13306 return new MagicWrapper(ractive, property, keypath); 13307 } 13308 }; 13309 13310 MagicWrapper = function (ractive, value, keypath) { 13311 var objKeypath, template, siblings; 13312 13313 keypath = getKeypath(keypath); 13314 13315 this.magic = true; 13316 13317 this.ractive = ractive; 13318 this.keypath = keypath; 13319 this.value = value; 13320 13321 this.prop = keypath.lastKey; 13322 13323 objKeypath = keypath.parent; 13324 this.obj = objKeypath.isRoot ? ractive.viewmodel.data : ractive.viewmodel.get(objKeypath); 13325 13326 template = this.originalDescriptor = Object.getOwnPropertyDescriptor(this.obj, this.prop); 13327 13328 // Has this property already been wrapped? 13329 if (template && template.set && (siblings = template.set._ractiveWrappers)) { 13330 13331 // Yes. Register this wrapper to this property, if it hasn't been already 13332 if (siblings.indexOf(this) === -1) { 13333 siblings.push(this); 13334 } 13335 13336 return; // already wrapped 13337 } 13338 13339 // No, it hasn't been wrapped 13340 createAccessors(this, value, template); 13341 }; 13342 13343 MagicWrapper.prototype = { 13344 get: function () { 13345 return this.value; 13346 }, 13347 reset: function (value) { 13348 if (this.updating) { 13349 return; 13350 } 13351 13352 this.updating = true; 13353 this.obj[this.prop] = value; // trigger set() accessor 13354 global_runloop.addRactive(this.ractive); 13355 this.ractive.viewmodel.mark(this.keypath, { keepExistingWrapper: true }); 13356 this.updating = false; 13357 return true; 13358 }, 13359 set: function (key, value) { 13360 if (this.updating) { 13361 return; 13362 } 13363 13364 if (!this.obj[this.prop]) { 13365 this.updating = true; 13366 this.obj[this.prop] = createBranch(key); 13367 this.updating = false; 13368 } 13369 13370 this.obj[this.prop][key] = value; 13371 }, 13372 teardown: function () { 13373 var template, set, value, wrappers, index; 13374 13375 // If this method was called because the cache was being cleared as a 13376 // result of a set()/update() call made by this wrapper, we return false 13377 // so that it doesn't get torn down 13378 if (this.updating) { 13379 return false; 13380 } 13381 13382 template = Object.getOwnPropertyDescriptor(this.obj, this.prop); 13383 set = template && template.set; 13384 13385 if (!set) { 13386 // most likely, this was an array member that was spliced out 13387 return; 13388 } 13389 13390 wrappers = set._ractiveWrappers; 13391 13392 index = wrappers.indexOf(this); 13393 if (index !== -1) { 13394 wrappers.splice(index, 1); 13395 } 13396 13397 // Last one out, turn off the lights 13398 if (!wrappers.length) { 13399 value = this.obj[this.prop]; 13400 13401 Object.defineProperty(this.obj, this.prop, this.originalDescriptor || { 13402 writable: true, 13403 enumerable: true, 13404 configurable: true 13405 }); 13406 13407 this.obj[this.prop] = value; 13408 } 13409 } 13410 }; 13411 } catch (err) { 13412 magicAdaptor = false; // no magic in this browser 13413 } 13414 13415 var adaptors_magic = magicAdaptor; 13416 13417 function createAccessors(originalWrapper, value, template) { 13418 13419 var object, property, oldGet, oldSet, get, set; 13420 13421 object = originalWrapper.obj; 13422 property = originalWrapper.prop; 13423 13424 // Is this template configurable? 13425 if (template && !template.configurable) { 13426 // Special case - array length 13427 if (property === "length") { 13428 return; 13429 } 13430 13431 throw new Error("Cannot use magic mode with property \"" + property + "\" - object is not configurable"); 13432 } 13433 13434 // Time to wrap this property 13435 if (template) { 13436 oldGet = template.get; 13437 oldSet = template.set; 13438 } 13439 13440 get = oldGet || function () { 13441 return value; 13442 }; 13443 13444 set = function (v) { 13445 if (oldSet) { 13446 oldSet(v); 13447 } 13448 13449 value = oldGet ? oldGet() : v; 13450 set._ractiveWrappers.forEach(updateWrapper); 13451 }; 13452 13453 function updateWrapper(wrapper) { 13454 var keypath, ractive; 13455 13456 wrapper.value = value; 13457 13458 if (wrapper.updating) { 13459 return; 13460 } 13461 13462 ractive = wrapper.ractive; 13463 keypath = wrapper.keypath; 13464 13465 wrapper.updating = true; 13466 global_runloop.start(ractive); 13467 13468 ractive.viewmodel.mark(keypath); 13469 13470 global_runloop.end(); 13471 wrapper.updating = false;
13472 } 13473 13474 // Create an array of wrappers, in case other keypaths/ractives depend on this property. 13475 // Handily, we can store them as a property of the set function. Yay JavaScript. 13476 set._ractiveWrappers = [originalWrapper]; 13477 Object.defineProperty(object, property, { get: get, set: set, enumerable: true, configurable: true }); 13478 } 13479 13480 var magicArrayAdaptor, MagicArrayWrapper; 13481 13482 if (adaptors_magic) { 13483 magicArrayAdaptor = { 13484 filter: function (object, keypath, ractive) { 13485 return adaptors_magic.filter(object, keypath, ractive) && array_index.filter(object); 13486 }, 13487 13488 wrap: function (ractive, array, keypath) { 13489 return new MagicArrayWrapper(ractive, array, keypath); 13490 } 13491 }; 13492 13493 MagicArrayWrapper = function (ractive, array, keypath) { 13494 this.value = array; 13495 13496 this.magic = true; 13497 13498 this.magicWrapper = adaptors_magic.wrap(ractive, array, keypath); 13499 this.arrayWrapper = array_index.wrap(ractive, array, keypath); 13500 }; 13501 13502 MagicArrayWrapper.prototype = { 13503 get: function () { 13504 return this.value; 13505 }, 13506 teardown: function () { 13507 this.arrayWrapper.teardown(); 13508 this.magicWrapper.teardown(); 13509 }, 13510 reset: function (value) { 13511 return this.magicWrapper.reset(value); 13512 } 13513 }; 13514 } 13515 13516 var magicArray = magicArrayAdaptor; 13517 13518 var prototype_adapt = Viewmodel$adapt; 13519 13520 var prefixers = {}; 13521 function Viewmodel$adapt(keypath, value) { 13522 var len, i, adaptor, wrapped; 13523 13524 if (!this.adaptors) return; 13525 13526 // Do we have an adaptor for this value? 13527 len = this.adaptors.length; 13528 for (i = 0; i < len; i += 1) { 13529 adaptor = this.adaptors[i]; 13530 13531 if (adaptor.filter(value, keypath, this.ractive)) { 13532 wrapped = this.wrapped[keypath] = adaptor.wrap(this.ractive, value, keypath, getPrefixer(keypath)); 13533 wrapped.value = value; 13534 return; 13535 } 13536 } 13537 } 13538 13539 function prefixKeypath(obj, prefix) { 13540 var prefixed = {}, 13541 key; 13542 13543 if (!prefix) { 13544 return obj; 13545 } 13546 13547 prefix += "."; 13548 13549 for (key in obj) { 13550 if (obj.hasOwnProperty(key)) { 13551 prefixed[prefix + key] = obj[key]; 13552 } 13553 } 13554 13555 return prefixed; 13556 } 13557 13558 function getPrefixer(rootKeypath) { 13559 var rootDot; 13560 13561 if (!prefixers[rootKeypath]) { 13562 rootDot = rootKeypath ? rootKeypath + "." : ""; 13563 13564 prefixers[rootKeypath] = function (relativeKeypath, value) { 13565 var obj; 13566 13567 if (typeof relativeKeypath === "string") { 13568 obj = {}; 13569 obj[rootDot + relativeKeypath] = value; 13570 return obj; 13571 } 13572 13573 if (typeof relativeKeypath === "object") { 13574 // 'relativeKeypath' is in fact a hash, not a keypath 13575 return rootDot ? prefixKeypath(relativeKeypath, rootKeypath) : relativeKeypath; 13576 } 13577 }; 13578 } 13579 13580 return prefixers[rootKeypath]; 13581 } 13582 13583 // TEMP 13584 13585 var helpers_getUpstreamChanges = getUpstreamChanges; 13586 function getUpstreamChanges(changes) { 13587 var upstreamChanges = [rootKeypath], 13588 i, 13589 keypath; 13590 13591 i = changes.length; 13592 while (i--) { 13593 keypath = changes[i].parent; 13594 13595 while (keypath && !keypath.isRoot) { 13596 if (changes.indexOf(keypath) === -1) { 13597 addToArray(upstreamChanges, keypath); 13598 } 13599 keypath = keypath.parent; 13600 } 13601 } 13602 13603 return upstreamChanges; 13604 } 13605 13606 var applyChanges_notifyPatternObservers = notifyPatternObservers; 13607 13608 function notifyPatternObservers(viewmodel, keypath, onlyDirect) { 13609 var potentialWildcardMatches; 13610 13611 updateMatchingPatternObservers(viewmodel, keypath); 13612 13613 if (onlyDirect) { 13614 return; 13615 } 13616 13617 potentialWildcardMatches = keypath.wildcardMatches(); 13618 potentialWildcardMatches.forEach(function (upstreamPattern) { 13619 cascade(viewmodel, upstreamPattern, keypath); 13620 }); 13621 } 13622 13623 function cascade(viewmodel, upstreamPattern, keypath) { 13624 var group, map, actualChildKeypath; 13625 13626 // TODO should be one or the other 13627 upstreamPattern = upstreamPattern.str || upstreamPattern; 13628 13629 group = viewmodel.depsMap.patternObservers; 13630 map = group && group[upstreamPattern]; 13631 13632 if (!map) { 13633 return; 13634 } 13635 13636 map.forEach(function (childKeypath) { 13637 actualChildKeypath = keypath.join(childKeypath.lastKey); // 'foo.bar.baz' 13638 13639 updateMatchingPatternObservers(viewmodel, actualChildKeypath); 13640 cascade(viewmodel, childKeypath, actualChildKeypath); 13641 }); 13642 } 13643 13644 function updateMatchingPatternObservers(viewmodel, keypath) {
13645 viewmodel.patternObservers.forEach(function (observer) { 13646 if (observer.regex.test(keypath.str)) { 13647 observer.update(keypath); 13648 } 13649 }); 13650 } 13651 13652 var applyChanges = Viewmodel$applyChanges; 13653 13654 function Viewmodel$applyChanges() { 13655 var _this = this; 13656 13657 var self = this, 13658 changes, 13659 upstreamChanges, 13660 hash = {}, 13661 bindings; 13662 13663 changes = this.changes; 13664 13665 if (!changes.length) { 13666 // TODO we end up here on initial render. Perhaps we shouldn't? 13667 return; 13668 } 13669 13670 function invalidateComputation(computation) { 13671 var key = computation.key; 13672 13673 if (computation.viewmodel === self) { 13674 self.clearCache(key.str); 13675 computation.invalidate(); 13676 13677 changes.push(key); 13678 cascade(key); 13679 } else { 13680 computation.viewmodel.mark(key); 13681 } 13682 } 13683 13684 function cascade(keypath) { 13685 var map, computations; 13686 13687 if (self.noCascade.hasOwnProperty(keypath.str)) { 13688 return; 13689 } 13690 13691 if (computations = self.deps.computed[keypath.str]) { 13692 computations.forEach(invalidateComputation); 13693 } 13694 13695 if (map = self.depsMap.computed[keypath.str]) { 13696 map.forEach(cascade); 13697 } 13698 } 13699 13700 changes.slice().forEach(cascade); 13701 13702 upstreamChanges = helpers_getUpstreamChanges(changes);
13703 upstreamChanges.forEach(function (keypath) { 13704 var computations; 13705 13706 // make sure we haven't already been down this particular keypath in this turn 13707 if (changes.indexOf(keypath) === -1 && (computations = self.deps.computed[keypath.str])) { 13708 computations.forEach(invalidateComputation); 13709 } 13710 }); 13711 13712 this.changes = []; 13713 13714 // Pattern observers are a weird special case 13715 if (this.patternObservers.length) { 13716 upstreamChanges.forEach(function (keypath) { 13717 return applyChanges_notifyPatternObservers(_this, keypath, true); 13718 }); 13719 changes.forEach(function (keypath) { 13720 return applyChanges_notifyPatternObservers(_this, keypath); 13721 }); 13722 } 13723 13724 if (this.deps.observers) { 13725 upstreamChanges.forEach(function (keypath) { 13726 return notifyUpstreamDependants(_this, null, keypath, "observers"); 13727 }); 13728 notifyAllDependants(this, changes, "observers"); 13729 } 13730 13731 if (this.deps["default"]) { 13732 bindings = []; 13733 upstreamChanges.forEach(function (keypath) { 13734 return notifyUpstreamDependants(_this, bindings, keypath, "default"); 13735 }); 13736 13737 if (bindings.length) { 13738 notifyBindings(this, bindings, changes); 13739 } 13740 13741 notifyAllDependants(this, changes, "default"); 13742 } 13743 13744 // Return a hash of keypaths to updated values
13745 changes.forEach(function (keypath) { 13746 hash[keypath.str] = _this.get(keypath); 13747 }); 13748 13749 this.implicitChanges = {}; 13750 this.noCascade = {}; 13751 13752 return hash; 13753 } 13754 13755 function notifyUpstreamDependants(viewmodel, bindings, keypath, groupName) { 13756 var dependants, value; 13757 13758 if (dependants = findDependants(viewmodel, keypath, groupName)) { 13759 value = viewmodel.get(keypath); 13760 13761 dependants.forEach(function (d) { 13762 // don't "set" the parent value, refine it 13763 // i.e. not data = value, but data[foo] = fooValue 13764 if (bindings && d.refineValue) { 13765 bindings.push(d); 13766 } else { 13767 d.setValue(value); 13768 } 13769 }); 13770 } 13771 } 13772 13773 function notifyBindings(viewmodel, bindings, changes) { 13774 13775 bindings.forEach(function (binding) { 13776 var useSet = false, 13777 i = 0, 13778 length = changes.length, 13779 refinements = []; 13780 13781 while (i < length) { 13782 var keypath = changes[i]; 13783 13784 if (keypath === binding.keypath) { 13785 useSet = true; 13786 break; 13787 } 13788 13789 if (keypath.slice(0, binding.keypath.length) === binding.keypath) { 13790 refinements.push(keypath); 13791 } 13792 13793 i++; 13794 } 13795 13796 if (useSet) { 13797 binding.setValue(viewmodel.get(binding.keypath)); 13798 } 13799 13800 if (refinements.length) { 13801 binding.refineValue(refinements); 13802 } 13803 }); 13804 } 13805 13806 function notifyAllDependants(viewmodel, keypaths, groupName) { 13807 var queue = []; 13808 13809 addKeypaths(keypaths); 13810 queue.forEach(dispatch); 13811 13812 function addKeypaths(keypaths) { 13813 keypaths.forEach(addKeypath); 13814 keypaths.forEach(cascade); 13815 } 13816 13817 function addKeypath(keypath) { 13818 var deps = findDependants(viewmodel, keypath, groupName); 13819 13820 if (deps) { 13821 queue.push({ 13822 keypath: keypath, 13823 deps: deps 13824 }); 13825 } 13826 } 13827 13828 function cascade(keypath) { 13829 var childDeps; 13830 13831 if (childDeps = viewmodel.depsMap[groupName][keypath.str]) { 13832 addKeypaths(childDeps); 13833 } 13834 } 13835 13836 function dispatch(set) { 13837 var value = viewmodel.get(set.keypath); 13838 set.deps.forEach(function (d) { 13839 return d.setValue(value); 13840 }); 13841 } 13842 } 13843 13844 function findDependants(viewmodel, keypath, groupName) { 13845 var group = viewmodel.deps[groupName]; 13846 return group ? group[keypath.str] : null; 13847 } 13848 13849 var capture = Viewmodel$capture; 13850 13851 function Viewmodel$capture() { 13852 this.captureGroups.push([]); 13853 } 13854 13855 var clearCache = Viewmodel$clearCache; 13856 13857 function Viewmodel$clearCache(keypath, keepExistingWrapper) { 13858 var cacheMap, wrapper; 13859 13860 if (!keepExistingWrapper) { 13861 // Is there a wrapped property at this keypath? 13862 if (wrapper = this.wrapped[keypath]) { 13863 // Did we unwrap it? 13864 if (wrapper.teardown() !== false) { 13865 // Is this right? 13866 // What's the meaning of returning false from teardown? 13867 // Could there be a GC ramification if this is a "real" ractive.teardown()? 13868 this.wrapped[keypath] = null; 13869 } 13870 } 13871 } 13872 13873 this.cache[keypath] = undefined; 13874 13875 if (cacheMap = this.cacheMap[keypath]) { 13876 while (cacheMap.length) { 13877 this.clearCache(cacheMap.pop()); 13878 } 13879 } 13880 } 13881 13882 var UnresolvedDependency = function (computation, ref) { 13883 this.computation = computation; 13884 this.viewmodel = computation.viewmodel; 13885 this.ref = ref; 13886 13887 // TODO this seems like a red flag! 13888 this.root = this.viewmodel.ractive; 13889 this.parentFragment = this.root.component && this.root.component.parentFragment; 13890 }; 13891 13892 UnresolvedDependency.prototype = { 13893 resolve: function (keypath) { 13894 this.computation.softDeps.push(keypath); 13895 this.computation.unresolvedDeps[keypath.str] = null; 13896 this.viewmodel.register(keypath, this.computation, "computed"); 13897 } 13898 }; 13899 13900 var Computation_UnresolvedDependency = UnresolvedDependency; 13901 13902 var Computation = function (key, signature) { 13903 this.key = key; 13904 13905 this.getter = signature.getter; 13906 this.setter = signature.setter; 13907 13908 this.hardDeps = signature.deps || []; 13909 this.softDeps = []; 13910 this.unresolvedDeps = {}; 13911 13912 this.depValues = {}; 13913 13914 this._dirty = this._firstRun = true; 13915 }; 13916 13917 Computation.prototype = { 13918 constructor: Computation, 13919 13920 init: function (viewmodel) { 13921 var _this = this; 13922 13923 var initial; 13924 13925 this.viewmodel = viewmodel; 13926 this.bypass = true; 13927 13928 initial = viewmodel.get(this.key); 13929 viewmodel.clearCache(this.key.str); 13930 13931 this.bypass = false;
13932 13933 if (this.setter && initial !== undefined) { 13934 this.set(initial); 13935 } 13936 13937 if (this.hardDeps) { 13938 this.hardDeps.forEach(function (d) { 13939 return viewmodel.register(d, _this, "computed"); 13940 }); 13941 } 13942 }, 13943 13944 invalidate: function () { 13945 this._dirty = true; 13946 }, 13947 13948 get: function () { 13949 var _this = this; 13950 13951 var newDeps, 13952 dependenciesChanged, 13953 dependencyValuesChanged = false; 13954 13955 if (this.getting) { 13956 // prevent double-computation (e.g. caused by array mutation inside computation) 13957 var msg = "The " + this.key.str + " computation indirectly called itself. This probably indicates a bug in the computation. It is commonly caused by `array.sort(...)` - if that's the case, clone the array first with `array.slice().sort(...)`"; 13958 warnOnce(msg); 13959 return this.value; 13960 } 13961 13962 this.getting = true; 13963 13964 if (this._dirty) { 13965 // determine whether the inputs have changed, in case this depends on 13966 // other computed values 13967 if (this._firstRun || !this.hardDeps.length && !this.softDeps.length) { 13968 dependencyValuesChanged = true; 13969 } else { 13970 [this.hardDeps, this.softDeps].forEach(function (deps) { 13971 var keypath, value, i; 13972 13973 if (dependencyValuesChanged) { 13974 return; 13975 } 13976 13977 i = deps.length; 13978 while (i--) { 13979 keypath = deps[i]; 13980 value = _this.viewmodel.get(keypath); 13981 13982 if (!isEqual(value, _this.depValues[keypath.str])) { 13983 _this.depValues[keypath.str] = value; 13984 dependencyValuesChanged = true; 13985 13986 return; 13987 } 13988 } 13989 }); 13990 } 13991 13992 if (dependencyValuesChanged) { 13993 this.viewmodel.capture(); 13994 13995 try { 13996 this.value = this.getter(); 13997 } catch (err) { 13998 warnIfDebug("Failed to compute \"%s\"", this.key.str); 13999 logIfDebug(err.stack || err); 14000 14001 this.value = void 0; 14002 } 14003 14004 newDeps = this.viewmodel.release(); 14005 dependenciesChanged = this.updateDependencies(newDeps); 14006 14007 if (dependenciesChanged) { 14008 [this.hardDeps, this.softDeps].forEach(function (deps) { 14009 deps.forEach(function (keypath) { 14010 _this.depValues[keypath.str] = _this.viewmodel.get(keypath); 14011 }); 14012 }); 14013 } 14014 } 14015 14016 this._dirty = false; 14017 } 14018 14019 this.getting = this._firstRun = false; 14020 return this.value; 14021 }, 14022 14023 set: function (value) { 14024 if (this.setting) { 14025 this.value = value; 14026 return; 14027 } 14028 14029 if (!this.setter) { 14030 throw new Error("Computed properties without setters are read-only. (This may change in a future version of Ractive!)"); 14031 } 14032 14033 this.setter(value); 14034 }, 14035 14036 updateDependencies: function (newDeps) { 14037 var i, oldDeps, keypath, dependenciesChanged, unresolved; 14038 14039 oldDeps = this.softDeps; 14040 14041 // remove dependencies that are no longer used 14042 i = oldDeps.length; 14043 while (i--) { 14044 keypath = oldDeps[i]; 14045 14046 if (newDeps.indexOf(keypath) === -1) { 14047 dependenciesChanged = true; 14048 this.viewmodel.unregister(keypath, this, "computed"); 14049 } 14050 } 14051 14052 // create references for any new dependencies 14053 i = newDeps.length; 14054 while (i--) { 14055 keypath = newDeps[i]; 14056 14057 if (oldDeps.indexOf(keypath) === -1 && (!this.hardDeps || this.hardDeps.indexOf(keypath) === -1)) { 14058 dependenciesChanged = true; 14059 14060 // if this keypath is currently unresolved, we need to mark 14061 // it as such. TODO this is a bit muddy... 14062 if (isUnresolved(this.viewmodel, keypath) && !this.unresolvedDeps[keypath.str]) { 14063 unresolved = new Computation_UnresolvedDependency(this, keypath.str); 14064 newDeps.splice(i, 1); 14065 14066 this.unresolvedDeps[keypath.str] = unresolved; 14067 global_runloop.addUnresolved(unresolved); 14068 } else { 14069 this.viewmodel.register(keypath, this, "computed"); 14070 } 14071 } 14072 } 14073 14074 if (dependenciesChanged) { 14075 this.softDeps = newDeps.slice(); 14076 } 14077 14078 return dependenciesChanged; 14079 } 14080 }; 14081 14082 function isUnresolved(viewmodel, keypath) { 14083 var key = keypath.firstKey; 14084 14085 return !(key in viewmodel.data) && !(key in viewmodel.computations) && !(key in viewmodel.mappings); 14086 } 14087 14088 var Computation_Computation = Computation; 14089 14090 var compute = Viewmodel$compute; 14091 function Viewmodel$compute(key, signature) { 14092 var computation = new Computation_Computation(key, signature); 14093 14094 if (this.ready) { 14095 computation.init(this); 14096 } 14097 14098 return this.computations[key.str] = computation; 14099 } 14100 14101 var FAILED_LOOKUP = { FAILED_LOOKUP: true }; 14102 14103 var viewmodel_prototype_get = Viewmodel$get; 14104 14105 var viewmodel_prototype_get__empty = {}; 14106 function Viewmodel$get(keypath, options) { 14107 var cache = this.cache, 14108 value, 14109 computation, 14110 wrapped, 14111 captureGroup, 14112 keypathStr = keypath.str, 14113 key; 14114 14115 options = options || viewmodel_prototype_get__empty; 14116 14117 // capture the keypath, if we're inside a computation 14118 if (options.capture && (captureGroup = lastItem(this.captureGroups))) { 14119 if (! ~captureGroup.indexOf(keypath)) { 14120 captureGroup.push(keypath); 14121 } 14122 } 14123 14124 if (hasOwn.call(this.mappings, keypath.firstKey)) { 14125 return this.mappings[keypath.firstKey].get(keypath, options); 14126 } 14127 14128 if (keypath.isSpecial) { 14129 return keypath.value; 14130 } 14131 14132 if (cache[keypathStr] === undefined) { 14133 14134 // Is this a computed property? 14135 if ((computation = this.computations[keypathStr]) && !computation.bypass) { 14136 value = computation.get(); 14137 this.adapt(keypathStr, value); 14138 } 14139 14140 // Is this a wrapped property? 14141 else if (wrapped = this.wrapped[keypathStr]) { 14142 value = wrapped.value; 14143 } 14144 14145 // Is it the root? 14146 else if (keypath.isRoot) { 14147 this.adapt("", this.data); 14148 value = this.data; 14149 } 14150 14151 // No? Then we need to retrieve the value one key at a time 14152 else { 14153 value = retrieve(this, keypath); 14154 } 14155 14156 cache[keypathStr] = value; 14157 } else { 14158 value = cache[keypathStr]; 14159 } 14160 14161 if (!options.noUnwrap && (wrapped = this.wrapped[keypathStr])) { 14162 value = wrapped.get(); 14163 } 14164 14165 if (keypath.isRoot && options.fullRootGet) { 14166 for (key in this.mappings) { 14167 value[key] = this.mappings[key].getValue(); 14168 } 14169 } 14170 14171 return value === FAILED_LOOKUP ? void 0 : value; 14172 } 14173 14174 function retrieve(viewmodel, keypath) { 14175 14176 var parentValue, cacheMap, value, wrapped; 14177 14178 parentValue = viewmodel.get(keypath.parent); 14179 14180 if (wrapped = viewmodel.wrapped[keypath.parent.str]) { 14181 parentValue = wrapped.get(); 14182 } 14183 14184 if (parentValue === null || parentValue === undefined) { 14185 return; 14186 } 14187 14188 // update cache map 14189 if (!(cacheMap = viewmodel.cacheMap[keypath.parent.str])) { 14190 viewmodel.cacheMap[keypath.parent.str] = [keypath.str]; 14191 } else { 14192 if (cacheMap.indexOf(keypath.str) === -1) { 14193 cacheMap.push(keypath.str); 14194 } 14195 } 14196 14197 // If this property doesn't exist, we return a sentinel value 14198 // so that we know to query parent scope (if such there be) 14199 if (typeof parentValue === "object" && !(keypath.lastKey in parentValue)) { 14200 return viewmodel.cache[keypath.str] = FAILED_LOOKUP; 14201 } 14202 14203 value = parentValue[keypath.lastKey]; 14204 14205 // Do we have an adaptor for this value? 14206 viewmodel.adapt(keypath.str, value, false); 14207 14208 // Update cache 14209 viewmodel.cache[keypath.str] = value; 14210 return value; 14211 } 14212 14213 var viewmodel_prototype_init = Viewmodel$init; 14214 14215 function Viewmodel$init() { 14216 var key; 14217 14218 for (key in this.computations) { 14219 this.computations[key].init(this); 14220 } 14221 } 14222 14223 var prototype_map = Viewmodel$map; 14224 14225 function Viewmodel$map(key, options) { 14226 var mapping = this.mappings[key.str] = new Mapping(key, options); 14227 mapping.initViewmodel(this); 14228 return mapping; 14229 } 14230 14231 var Mapping = function (localKey, options) { 14232 this.localKey = localKey; 14233 this.keypath = options.keypath; 14234 this.origin = options.origin; 14235 14236 this.deps = []; 14237 this.unresolved = []; 14238 14239 this.resolved = false;
14240 }; 14241 14242 Mapping.prototype = { 14243 forceResolution: function () { 14244 // TODO warn, as per #1692? 14245 this.keypath = this.localKey; 14246 this.setup(); 14247 }, 14248 14249 get: function (keypath, options) { 14250 if (!this.resolved) { 14251 return undefined; 14252 } 14253 return this.origin.get(this.map(keypath), options); 14254 }, 14255 14256 getValue: function () { 14257 if (!this.keypath) { 14258 return undefined; 14259 } 14260 return this.origin.get(this.keypath); 14261 }, 14262 14263 initViewmodel: function (viewmodel) { 14264 this.local = viewmodel; 14265 this.setup(); 14266 }, 14267 14268 map: function (keypath) { 14269 if (typeof this.keypath === undefined) { 14270 return this.localKey; 14271 } 14272 return keypath.replace(this.localKey, this.keypath); 14273 }, 14274 14275 register: function (keypath, dependant, group) { 14276 this.deps.push({ keypath: keypath, dep: dependant, group: group }); 14277 14278 if (this.resolved) { 14279 this.origin.register(this.map(keypath), dependant, group); 14280 } 14281 }, 14282 14283 resolve: function (keypath) { 14284 if (this.keypath !== undefined) { 14285 this.unbind(true); 14286 } 14287 14288 this.keypath = keypath; 14289 this.setup(); 14290 }, 14291 14292 set: function (keypath, value) { 14293 if (!this.resolved) { 14294 this.forceResolution(); 14295 } 14296 14297 this.origin.set(this.map(keypath), value); 14298 }, 14299 14300 setup: function () { 14301 var _this = this; 14302 14303 if (this.keypath === undefined) { 14304 return; 14305 } 14306 14307 this.resolved = true; 14308 14309 // accumulated dependants can now be registered 14310 if (this.deps.length) { 14311 this.deps.forEach(function (d) { 14312 var keypath = _this.map(d.keypath); 14313 _this.origin.register(keypath, d.dep, d.group); 14314 14315 // TODO this is a bit of a red flag... all deps should be the same? 14316 if (d.dep.setValue) { 14317 d.dep.setValue(_this.origin.get(keypath)); 14318 } else if (d.dep.invalidate) { 14319 d.dep.invalidate(); 14320 } else { 14321 throw new Error("An unexpected error occurred. Please raise an issue at https://github.com/ractivejs/ractive/issues - thanks!"); 14322 } 14323 }); 14324 14325 this.origin.mark(this.keypath); 14326 } 14327 }, 14328 14329 setValue: function (value) { 14330 if (!this.keypath) { 14331 throw new Error("Mapping does not have keypath, cannot set value. Please raise an issue at https://github.com/ractivejs/ractive/issues - thanks!"); 14332 } 14333 14334 this.origin.set(this.keypath, value); 14335 }, 14336 14337 unbind: function (keepLocal) { 14338 var _this = this; 14339 14340 if (!keepLocal) { 14341 delete this.local.mappings[this.localKey]; 14342 } 14343 14344 if (!this.resolved) { 14345 return; 14346 } 14347 14348 this.deps.forEach(function (d) { 14349 _this.origin.unregister(_this.map(d.keypath), d.dep, d.group); 14350 }); 14351 14352 if (this.tracker) { 14353 this.origin.unregister(this.keypath, this.tracker); 14354 } 14355 }, 14356 14357 unregister: function (keypath, dependant, group) { 14358 var deps, i; 14359 14360 if (!this.resolved) { 14361 return; 14362 } 14363 14364 deps = this.deps; 14365 i = deps.length; 14366 14367 while (i--) { 14368 if (deps[i].dep === dependant) { 14369 deps.splice(i, 1); 14370 break; 14371 } 14372 } 14373 this.origin.unregister(this.map(keypath), dependant, group); 14374 } 14375 }; 14376 14377 var mark = Viewmodel$mark; 14378 14379 function Viewmodel$mark(keypath, options) { 14380 var computation, 14381 keypathStr = keypath.str; 14382 14383 // implicit changes (i.e. `foo.length` on `ractive.push('foo',42)`) 14384 // should not be picked up by pattern observers 14385 if (options) { 14386 if (options.implicit) { 14387 this.implicitChanges[keypathStr] = true; 14388 } 14389 if (options.noCascade) { 14390 this.noCascade[keypathStr] = true; 14391 } 14392 } 14393 14394 if (computation = this.computations[keypathStr]) { 14395 computation.invalidate(); 14396 } 14397 14398 if (this.changes.indexOf(keypath) === -1) { 14399 this.changes.push(keypath); 14400 } 14401 14402 // pass on keepExistingWrapper, if we can 14403 var keepExistingWrapper = options ? options.keepExistingWrapper : false;
14404 14405 this.clearCache(keypathStr, keepExistingWrapper); 14406 14407 if (this.ready) { 14408 this.onchange(); 14409 } 14410 } 14411 14412 var mapOldToNewIndex = function (oldArray, newArray) { 14413 var usedIndices, firstUnusedIndex, newIndices, changed; 14414 14415 usedIndices = {}; 14416 firstUnusedIndex = 0; 14417 14418 newIndices = oldArray.map(function (item, i) { 14419 var index, start, len; 14420 14421 start = firstUnusedIndex; 14422 len = newArray.length; 14423 14424 do { 14425 index = newArray.indexOf(item, start); 14426 14427 if (index === -1) { 14428 changed = true; 14429 return -1; 14430 } 14431 14432 start = index + 1; 14433 } while (usedIndices[index] && start < len); 14434 14435 // keep track of the first unused index, so we don't search 14436 // the whole of newArray for each item in oldArray unnecessarily 14437 if (index === firstUnusedIndex) { 14438 firstUnusedIndex += 1; 14439 } 14440 14441 if (index !== i) { 14442 changed = true; 14443 } 14444 14445 usedIndices[index] = true; 14446 return index; 14447 }); 14448 14449 return newIndices; 14450 }; 14451 14452 var merge = Viewmodel$merge; 14453 14454 var comparators = {}; 14455 function Viewmodel$merge(keypath, currentArray, array, options) { 14456 var oldArray, newArray, comparator, newIndices; 14457 14458 this.mark(keypath); 14459 14460 if (options && options.compare) { 14461 14462 comparator = getComparatorFunction(options.compare); 14463 14464 try { 14465 oldArray = currentArray.map(comparator); 14466 newArray = array.map(comparator); 14467 } catch (err) { 14468 // fallback to an identity check - worst case scenario we have 14469 // to do more DOM manipulation than we thought... 14470 warnIfDebug("merge(): \"%s\" comparison failed. Falling back to identity checking", keypath); 14471 14472 oldArray = currentArray; 14473 newArray = array; 14474 } 14475 } else { 14476 oldArray = currentArray; 14477 newArray = array; 14478 } 14479 14480 // find new indices for members of oldArray 14481 newIndices = mapOldToNewIndex(oldArray, newArray); 14482 14483 this.smartUpdate(keypath, array, newIndices, currentArray.length !== array.length); 14484 } 14485 14486 function stringify(item) { 14487 return JSON.stringify(item); 14488 } 14489 14490 function getComparatorFunction(comparator) { 14491 // If `compare` is `true`, we use JSON.stringify to compare 14492 // objects that are the same shape, but non-identical - i.e. 14493 // { foo: 'bar' } !== { foo: 'bar' } 14494 if (comparator === true) { 14495 return stringify; 14496 } 14497 14498 if (typeof comparator === "string") { 14499 if (!comparators[comparator]) { 14500 comparators[comparator] = function (item) { 14501 return item[comparator]; 14502 }; 14503 } 14504 14505 return comparators[comparator]; 14506 } 14507 14508 if (typeof comparator === "function") { 14509 return comparator; 14510 } 14511 14512 throw new Error("The `compare` option must be a function, or a string representing an identifying field (or `true` to use JSON.stringify)"); 14513 } 14514 14515 var register = Viewmodel$register; 14516 14517 function Viewmodel$register(keypath, dependant) { 14518 var group = arguments[2] === undefined ? "default" : arguments[2]; 14519 14520 var mapping, depsByKeypath, deps; 14521 14522 if (dependant.isStatic) { 14523 return; // TODO we should never get here if a dependant is static... 14524 } 14525 14526 if (mapping = this.mappings[keypath.firstKey]) { 14527 mapping.register(keypath, dependant, group); 14528 } else { 14529 depsByKeypath = this.deps[group] || (this.deps[group] = {}); 14530 deps = depsByKeypath[keypath.str] || (depsByKeypath[keypath.str] = []); 14531 14532 deps.push(dependant); 14533 14534 if (!this.depsMap[group]) { 14535 this.depsMap[group] = {}; 14536 } 14537 14538 if (!keypath.isRoot) { 14539 register__updateDependantsMap(this, keypath, group); 14540 } 14541 } 14542 } 14543 14544 function register__updateDependantsMap(viewmodel, keypath, group) { 14545 var map, parent, keypathStr; 14546 14547 // update dependants map 14548 while (!keypath.isRoot) { 14549 map = viewmodel.depsMap[group]; 14550 parent = map[keypath.parent.str] || (map[keypath.parent.str] = []); 14551 14552 keypathStr = keypath.str; 14553 14554 // TODO find an alternative to this nasty approach 14555 if (parent["_" + keypathStr] === undefined) { 14556 parent["_" + keypathStr] = 0; 14557 parent.push(keypath); 14558 } 14559 14560 parent["_" + keypathStr] += 1; 14561 keypath = keypath.parent; 14562 } 14563 } 14564 14565 var release = Viewmodel$release; 14566 14567 function Viewmodel$release() { 14568 return this.captureGroups.pop(); 14569 } 14570 14571 var reset = Viewmodel$reset; 14572 14573 function Viewmodel$reset(data) { 14574 this.data = data; 14575 this.clearCache(""); 14576 } 14577 14578 var prototype_set = Viewmodel$set; 14579 14580 function Viewmodel$set(keypath, value) { 14581 var options = arguments[2] === undefined ? {} : arguments[2]; 14582 14583 var mapping, computation, wrapper, keepExistingWrapper; 14584 14585 // unless data is being set for data tracking purposes 14586 if (!options.noMapping) { 14587 // If this data belongs to a different viewmodel, 14588 // pass the change along 14589 if (mapping = this.mappings[keypath.firstKey]) { 14590 return mapping.set(keypath, value); 14591 } 14592 } 14593 14594 computation = this.computations[keypath.str]; 14595 if (computation) { 14596 if (computation.setting) { 14597 // let the other computation set() handle things... 14598 return; 14599 } 14600 computation.set(value); 14601 value = computation.get(); 14602 } 14603 14604 if (isEqual(this.cache[keypath.str], value)) { 14605 return; 14606 } 14607 14608 wrapper = this.wrapped[keypath.str]; 14609 14610 // If we have a wrapper with a `reset()` method, we try and use it. If the 14611 // `reset()` method returns false, the wrapper should be torn down, and 14612 // (most likely) a new one should be created later 14613 if (wrapper && wrapper.reset) { 14614 keepExistingWrapper = wrapper.reset(value) !== false; 14615 14616 if (keepExistingWrapper) { 14617 value = wrapper.get(); 14618 } 14619 } 14620 14621 if (!computation && !keepExistingWrapper) { 14622 resolveSet(this, keypath, value); 14623 } 14624 14625 if (!options.silent) { 14626 this.mark(keypath); 14627 } else { 14628 // We're setting a parent of the original target keypath (i.e. 14629 // creating a fresh branch) - we need to clear the cache, but 14630 // not mark it as a change 14631 this.clearCache(keypath.str); 14632 } 14633 } 14634 14635 function resolveSet(viewmodel, keypath, value) { 14636 var wrapper, parentValue, wrapperSet, valueSet; 14637 14638 wrapperSet = function () { 14639 if (wrapper.set) { 14640 wrapper.set(keypath.lastKey, value); 14641 } else { 14642 parentValue = wrapper.get(); 14643 valueSet(); 14644 } 14645 }; 14646 14647 valueSet = function () { 14648 if (!parentValue) { 14649 parentValue = createBranch(keypath.lastKey); 14650 viewmodel.set(keypath.parent, parentValue, { silent: true }); 14651 } 14652 parentValue[keypath.lastKey] = value; 14653 }; 14654 14655 wrapper = viewmodel.wrapped[keypath.parent.str]; 14656 14657 if (wrapper) { 14658 wrapperSet(); 14659 } else { 14660 parentValue = viewmodel.get(keypath.parent); 14661 14662 // may have been wrapped via the above .get() 14663 // call on viewmodel if this is first access via .set()! 14664 if (wrapper = viewmodel.wrapped[keypath.parent.str]) { 14665 wrapperSet(); 14666 } else { 14667 valueSet(); 14668 } 14669 } 14670 } 14671 14672 var smartUpdate = Viewmodel$smartUpdate; 14673 14674 var implicitOption = { implicit: true }, 14675 noCascadeOption = { noCascade: true }; 14676 function Viewmodel$smartUpdate(keypath, array, newIndices) { 14677 var _this = this; 14678 14679 var dependants, oldLength, i; 14680 14681 oldLength = newIndices.length; 14682 14683 // Indices that are being removed should be marked as dirty
14684 newIndices.forEach(function (newIndex, oldIndex) { 14685 if (newIndex === -1) { 14686 _this.mark(keypath.join(oldIndex), noCascadeOption); 14687 } 14688 }); 14689 14690 // Update the model 14691 // TODO allow existing array to be updated in place, rather than replaced? 14692 this.set(keypath, array, { silent: true }); 14693 14694 if (dependants = this.deps["default"][keypath.str]) { 14695 dependants.filter(canShuffle).forEach(function (d) { 14696 return d.shuffle(newIndices, array); 14697 }); 14698 } 14699 14700 if (oldLength !== array.length) { 14701 this.mark(keypath.join("length"), implicitOption); 14702 14703 for (i = newIndices.touchedFrom; i < array.length; i += 1) { 14704 this.mark(keypath.join(i)); 14705 } 14706 14707 // don't allow removed indexes beyond end of new array to trigger recomputations 14708 // TODO is this still necessary, now that computations are lazy? 14709 for (i = array.length; i < oldLength; i += 1) { 14710 this.mark(keypath.join(i), noCascadeOption); 14711 } 14712 } 14713 } 14714 14715 function canShuffle(dependant) { 14716 return typeof dependant.shuffle === "function"; 14717 } 14718 14719 var prototype_teardown = Viewmodel$teardown; 14720 14721 function Viewmodel$teardown() { 14722 var _this = this; 14723 14724 var unresolvedImplicitDependency; 14725 14726 // Clear entire cache - this has the desired side-effect 14727 // of unwrapping adapted values (e.g. arrays) 14728 Object.keys(this.cache).forEach(function (keypath) { 14729 return _this.clearCache(keypath); 14730 }); 14731 14732 // Teardown any failed lookups - we don't need them to resolve any more 14733 while (unresolvedImplicitDependency = this.unresolvedImplicitDependencies.pop()) { 14734 unresolvedImplicitDependency.teardown(); 14735 } 14736 } 14737 14738 var unregister = Viewmodel$unregister; 14739 14740 function Viewmodel$unregister(keypath, dependant) { 14741 var group = arguments[2] === undefined ? "default" : arguments[2]; 14742 14743 var mapping, deps, index; 14744 14745 if (dependant.isStatic) { 14746 return; 14747 } 14748 14749 if (mapping = this.mappings[keypath.firstKey]) { 14750 return mapping.unregister(keypath, dependant, group); 14751 } 14752 14753 deps = this.deps[group][keypath.str]; 14754 index = deps.indexOf(dependant); 14755 14756 if (index === -1) { 14757 throw new Error("Attempted to remove a dependant that was no longer registered! This should not happen. If you are seeing this bug in development please raise an issue at https://github.com/RactiveJS/Ractive/issues - thanks"); 14758 } 14759 14760 deps.splice(index, 1); 14761 14762 if (keypath.isRoot) { 14763 return; 14764 } 14765 14766 unregister__updateDependantsMap(this, keypath, group); 14767 } 14768 14769 function unregister__updateDependantsMap(viewmodel, keypath, group) { 14770 var map, parent; 14771 14772 // update dependants map 14773 while (!keypath.isRoot) { 14774 map = viewmodel.depsMap[group]; 14775 parent = map[keypath.parent.str]; 14776 14777 parent["_" + keypath.str] -= 1; 14778 14779 if (!parent["_" + keypath.str]) { 14780 // remove from parent deps map 14781 removeFromArray(parent, keypath); 14782 parent["_" + keypath.str] = undefined; 14783 } 14784 14785 keypath = keypath.parent; 14786 } 14787 } 14788 14789 var Viewmodel = function (options) { 14790 var adapt = options.adapt; 14791 var data = options.data; 14792 var ractive = options.ractive; 14793 var computed = options.computed; 14794 var mappings = options.mappings; 14795 var key; 14796 var mapping; 14797 14798 // TODO is it possible to remove this reference? 14799 this.ractive = ractive; 14800 14801 this.adaptors = adapt; 14802 this.onchange = options.onchange; 14803 14804 this.cache = {}; // we need to be able to use hasOwnProperty, so can't inherit from null 14805 this.cacheMap = create(null); 14806 14807 this.deps = { 14808 computed: create(null), 14809 "default": create(null) 14810 }; 14811 this.depsMap = { 14812 computed: create(null), 14813 "default": create(null) 14814 }; 14815 14816 this.patternObservers = []; 14817 14818 this.specials = create(null); 14819 14820 this.wrapped = create(null); 14821 this.computations = create(null); 14822 14823 this.captureGroups = []; 14824 this.unresolvedImplicitDependencies = []; 14825 14826 this.changes = []; 14827 this.implicitChanges = {}; 14828 this.noCascade = {}; 14829 14830 this.data = data; 14831 14832 // set up explicit mappings 14833 this.mappings = create(null); 14834 for (key in mappings) { 14835 this.map(getKeypath(key), mappings[key]); 14836 } 14837 14838 if (data) { 14839 // if data exists locally, but is missing on the parent, 14840 // we transfer ownership to the parent 14841 for (key in data) { 14842 if ((mapping = this.mappings[key]) && mapping.getValue() === undefined) { 14843 mapping.setValue(data[key]); 14844 } 14845 } 14846 } 14847 14848 for (key in computed) { 14849 if (mappings && key in mappings) { 14850 fatal("Cannot map to a computed property ('%s')", key); 14851 } 14852 14853 this.compute(getKeypath(key), computed[key]); 14854 } 14855 14856 this.ready = true; 14857 }; 14858 14859 Viewmodel.prototype = { 14860 adapt: prototype_adapt, 14861 applyChanges: applyChanges, 14862 capture: capture, 14863 clearCache: clearCache, 14864 compute: compute, 14865 get: viewmodel_prototype_get, 14866 init: viewmodel_prototype_init, 14867 map: prototype_map, 14868 mark: mark, 14869 merge: merge, 14870 register: register, 14871 release: release, 14872 reset: reset,
14873 set: prototype_set, 14874 smartUpdate: smartUpdate, 14875 teardown: prototype_teardown, 14876 unregister: unregister 14877 }; 14878 14879 var viewmodel_Viewmodel = Viewmodel; 14880 14881 function HookQueue(event) { 14882 this.hook = new hooks_Hook(event); 14883 this.inProcess = {}; 14884 this.queue = {}; 14885 } 14886 14887 HookQueue.prototype = { 14888 14889 constructor: HookQueue, 14890 14891 begin: function (ractive) { 14892 this.inProcess[ractive._guid] = true; 14893 }, 14894 14895 end: function (ractive) { 14896 14897 var parent = ractive.parent; 14898 14899 // If this is *isn't* a child of a component that's in process, 14900 // it should call methods or fire at this point 14901 if (!parent || !this.inProcess[parent._guid]) { 14902 fire(this, ractive); 14903 } 14904 // elsewise, handoff to parent to fire when ready 14905 else { 14906 getChildQueue(this.queue, parent).push(ractive); 14907 } 14908 14909 delete this.inProcess[ractive._guid]; 14910 } 14911 }; 14912 14913 function getChildQueue(queue, ractive) { 14914 return queue[ractive._guid] || (queue[ractive._guid] = []); 14915 } 14916 14917 function fire(hookQueue, ractive) { 14918 14919 var childQueue = getChildQueue(hookQueue.queue, ractive); 14920 14921 hookQueue.hook.fire(ractive); 14922 14923 // queue is "live" because components can end up being 14924 // added while hooks fire on parents that modify data values. 14925 while (childQueue.length) { 14926 fire(hookQueue, childQueue.shift()); 14927 } 14928 14929 delete hookQueue.queue[ractive._guid]; 14930 } 14931 14932 var hooks_HookQueue = HookQueue; 14933 14934 var helpers_getComputationSignatures = getComputationSignatures; 14935 14936 var helpers_getComputationSignatures__pattern = /\$\{([^\}]+)\}/g; 14937 function getComputationSignatures(ractive, computed) { 14938 var signatures = {}, 14939 key; 14940 14941 for (key in computed) { 14942 signatures[key] = getComputationSignature(ractive, key, computed[key]); 14943 } 14944 14945 return signatures; 14946 } 14947 14948 function getComputationSignature(ractive, key, signature) { 14949 var getter, setter; 14950 14951 if (typeof signature === "function") { 14952 getter = helpers_getComputationSignatures__bind(signature, ractive); 14953 } 14954 14955 if (typeof signature === "string") { 14956 getter = createFunctionFromString(ractive, signature); 14957 } 14958 14959 if (typeof signature === "object") { 14960 if (typeof signature.get === "string") { 14961 getter = createFunctionFromString(ractive, signature.get); 14962 } else if (typeof signature.get === "function") { 14963 getter = helpers_getComputationSignatures__bind(signature.get, ractive); 14964 } else { 14965 fatal("`%s` computation must have a `get()` method", key); 14966 } 14967 14968 if (typeof signature.set === "function") { 14969 setter = helpers_getComputationSignatures__bind(signature.set, ractive); 14970 } 14971 } 14972 14973 return { getter: getter, setter: setter }; 14974 } 14975 14976 function createFunctionFromString(ractive, str) { 14977 var functionBody, hasThis, fn; 14978 14979 functionBody = "return (" + str.replace(helpers_getComputationSignatures__pattern, function (match, keypath) {
14980 hasThis = true; 14981 return "__ractive.get(\"" + keypath + "\")"; 14982 }) + ");"; 14983 14984 if (hasThis) { 14985 functionBody = "var __ractive = this; " + functionBody; 14986 } 14987 14988 fn = new Function(functionBody); 14989 return hasThis ? fn.bind(ractive) : fn; 14990 } 14991 14992 function helpers_getComputationSignatures__bind(fn, context) { 14993 return /this/.test(fn.toString()) ? fn.bind(context) : fn; 14994 } 14995 14996 var constructHook = new hooks_Hook("construct"); 14997 var configHook = new hooks_Hook("config"); 14998 var initHook = new hooks_HookQueue("init"); 14999 var initialise__uid = 0; 15000 15001 var initialise__registryNames = ["adaptors", "components", "decorators", "easing", "events", "interpolators", "partials", "transitions"]; 15002 15003 var initialise = initialiseRactiveInstance; 15004 15005 function initialiseRactiveInstance(ractive) { 15006 var userOptions = arguments[1] === undefined ? {} : arguments[1]; 15007 var options = arguments[2] === undefined ? {} : arguments[2]; 15008 15009 var el, viewmodel; 15010 15011 if (_Ractive.DEBUG) { 15012 welcome(); 15013 } 15014 15015 initialiseProperties(ractive, options); 15016 15017 // TODO remove this, eventually 15018 defineProperty(ractive, "data", { get: deprecateRactiveData }); 15019 15020 // TODO don't allow `onconstruct` with `new Ractive()`, there's no need for it 15021 constructHook.fire(ractive, userOptions); 15022 15023 // Add registries 15024 initialise__registryNames.forEach(function (name) { 15025 ractive[name] = utils_object__extend(create(ractive.constructor[name] || null), userOptions[name]); 15026 }); 15027 15028 // Create a viewmodel 15029 viewmodel = new viewmodel_Viewmodel({ 15030 adapt: getAdaptors(ractive, ractive.adapt, userOptions), 15031 data: custom_data.init(ractive.constructor, ractive, userOptions), 15032 computed: helpers_getComputationSignatures(ractive, utils_object__extend(create(ractive.constructor.prototype.computed), userOptions.computed)), 15033 mappings: options.mappings, 15034 ractive: ractive, 15035 onchange: function () { 15036 return global_runloop.addRactive(ractive); 15037 } 15038 }); 15039 15040 ractive.viewmodel = viewmodel; 15041 15042 // This can't happen earlier, because computed properties may call `ractive.get()`, etc 15043 viewmodel.init(); 15044 15045 // init config from Parent and options 15046 config_config.init(ractive.constructor, ractive, userOptions); 15047 15048 configHook.fire(ractive); 15049 initHook.begin(ractive); 15050 15051 // // If this is a component with a function `data` property, call the function 15052 // // with `ractive` as context (unless the child was also a function) 15053 // if ( typeof ractive.constructor.prototype.data === 'function' && typeof userOptions.data !== 'function' ) { 15054 // viewmodel.reset( ractive.constructor.prototype.data.call( ractive ) || fatal( '`data` functions must return a data object' ) ); 15055 // } 15056 15057 // Render virtual DOM 15058 if (ractive.template) { 15059 var cssIds = undefined; 15060 15061 if (options.cssIds || ractive.cssId) { 15062 cssIds = options.cssIds ? options.cssIds.slice() : []; 15063 15064 if (ractive.cssId) { 15065 cssIds.push(ractive.cssId); 15066 } 15067 } 15068 15069 ractive.fragment = new virtualdom_Fragment({ 15070 template: ractive.template, 15071 root: ractive, 15072 owner: ractive, // saves doing `if ( this.parent ) { /*...*/ }` later on 15073 cssIds: cssIds 15074 }); 15075 } 15076 15077 initHook.end(ractive); 15078 15079 // render automatically ( if `el` is specified ) 15080 if (el = getElement(ractive.el)) { 15081 var promise = ractive.render(el, ractive.append); 15082 15083 if (_Ractive.DEBUG_PROMISES) { 15084 promise["catch"](function (err) { 15085 warnOnceIfDebug("Promise debugging is enabled, to help solve errors that happen asynchronously. Some browsers will log unhandled promise rejections, in which case you can safely disable promise debugging:\n Ractive.DEBUG_PROMISES = false;"); 15086 warnIfDebug("An error happened during rendering", { ractive: ractive }); 15087 err.stack && logIfDebug(err.stack); 15088 15089 throw err; 15090 }); 15091 } 15092 } 15093 } 15094 15095 function getAdaptors(ractive, protoAdapt, userOptions) { 15096 var adapt, magic, modifyArrays; 15097 15098 protoAdapt = protoAdapt.map(lookup); 15099 adapt = ensureArray(userOptions.adapt).map(lookup); 15100 15101 adapt = initialise__combine(protoAdapt, adapt); 15102 15103 magic = "magic" in userOptions ? userOptions.magic : ractive.magic;
15104 modifyArrays = "modifyArrays" in userOptions ? userOptions.modifyArrays : ractive.modifyArrays; 15105 15106 if (magic) { 15107 if (!environment__magic) { 15108 throw new Error("Getters and setters (magic mode) are not supported in this browser"); 15109 } 15110 15111 if (modifyArrays) { 15112 adapt.push(magicArray); 15113 } 15114 15115 adapt.push(adaptors_magic); 15116 } 15117 15118 if (modifyArrays) { 15119 adapt.push(array_index); 15120 } 15121 15122 return adapt; 15123 15124 function lookup(adaptor) { 15125 if (typeof adaptor === "string") { 15126 adaptor = findInViewHierarchy("adaptors", ractive, adaptor); 15127 15128 if (!adaptor) { 15129 fatal(missingPlugin(adaptor, "adaptor")); 15130 } 15131 } 15132 15133 return adaptor; 15134 } 15135 } 15136 15137 function initialise__combine(a, b) { 15138 var c = a.slice(), 15139 i = b.length; 15140 15141 while (i--) { 15142 if (! ~c.indexOf(b[i])) { 15143 c.push(b[i]); 15144 } 15145 } 15146 15147 return c; 15148 } 15149 15150 function initialiseProperties(ractive, options) { 15151 // Generate a unique identifier, for places where you'd use a weak map if it 15152 // existed 15153 ractive._guid = "r-" + initialise__uid++; 15154 15155 // events 15156 ractive._subs = create(null); 15157 15158 // storage for item configuration from instantiation to reset, 15159 // like dynamic functions or original values 15160 ractive._config = {}; 15161 15162 // two-way bindings 15163 ractive._twowayBindings = create(null); 15164 15165 // animations (so we can stop any in progress at teardown) 15166 ractive._animations = []; 15167 15168 // nodes registry 15169 ractive.nodes = {}; 15170 15171 // live queries 15172 ractive._liveQueries = []; 15173 ractive._liveComponentQueries = []; 15174 15175 // bound data functions 15176 ractive._boundFunctions = []; 15177 15178 // observers 15179 ractive._observers = []; 15180 15181 // properties specific to inline components 15182 if (options.component) { 15183 ractive.parent = options.parent; 15184 ractive.container = options.container || null; 15185 ractive.root = ractive.parent.root; 15186 15187 ractive.component = options.component; 15188 options.component.instance = ractive; 15189 15190 // for hackability, this could be an open option 15191 // for any ractive instance, but for now, just 15192 // for components and just for ractive... 15193 ractive._inlinePartials = options.inlinePartials; 15194 } else { 15195 ractive.root = ractive; 15196 ractive.parent = ractive.container = null; 15197 } 15198 } 15199 15200 function deprecateRactiveData() { 15201 throw new Error("Using `ractive.data` is no longer supported - you must use the `ractive.get()` API instead"); 15202 } 15203 15204 function ComplexParameter(component, template, callback) { 15205 this.parentFragment = component.parentFragment; 15206 this.callback = callback; 15207 15208 this.fragment = new virtualdom_Fragment({ 15209 template: template, 15210 root: component.root, 15211 owner: this 15212 }); 15213 15214 this.update(); 15215 } 15216 15217 var initialise_ComplexParameter = ComplexParameter; 15218 15219 ComplexParameter.prototype = { 15220 bubble: function () { 15221 if (!this.dirty) { 15222 this.dirty = true; 15223 global_runloop.addView(this); 15224 } 15225 }, 15226 15227 update: function () { 15228 this.callback(this.fragment.getValue()); 15229 this.dirty = false; 15230 }, 15231 15232 rebind: function (oldKeypath, newKeypath) { 15233 this.fragment.rebind(oldKeypath, newKeypath); 15234 }, 15235 15236 unbind: function () { 15237 this.fragment.unbind(); 15238 } 15239 }; 15240 15241 var createInstance = function (component, Component, attributes, yieldTemplate, partials) { 15242 var instance, 15243 parentFragment, 15244 ractive, 15245 fragment, 15246 container, 15247 inlinePartials = {}, 15248 data = {}, 15249 mappings = {}, 15250 ready, 15251 resolvers = []; 15252 15253 parentFragment = component.parentFragment; 15254 ractive = component.root; 15255 15256 partials = partials || {}; 15257 utils_object__extend(inlinePartials, partials); 15258 15259 // Make contents available as a {{>content}} partial 15260 partials.content = yieldTemplate || []; 15261 15262 // set a default partial for yields with no name 15263 inlinePartials[""] = partials.content; 15264 15265 if (Component.defaults.el) { 15266 warnIfDebug("The <%s/> component has a default `el` property; it has been disregarded", component.name); 15267 } 15268 15269 // find container 15270 fragment = parentFragment; 15271 while (fragment) { 15272 if (fragment.owner.type === YIELDER) { 15273 container = fragment.owner.container; 15274 break; 15275 } 15276 15277 fragment = fragment.parent; 15278 } 15279 15280 // each attribute represents either a) data or b) a mapping 15281 if (attributes) {
15282 Object.keys(attributes).forEach(function (key) { 15283 var attribute = attributes[key], 15284 parsed, 15285 resolver; 15286 15287 if (typeof attribute === "string") { 15288 // it's static data 15289 parsed = parseJSON(attribute); 15290 data[key] = parsed ? parsed.value : attribute; 15291 } else if (attribute === 0) { 15292 // it had no '=', so we'll call it true 15293 data[key] = true; 15294 } else if (isArray(attribute)) { 15295 // this represents dynamic data 15296 if (isSingleInterpolator(attribute)) { 15297 mappings[key] = { 15298 origin: component.root.viewmodel, 15299 keypath: undefined 15300 }; 15301 15302 resolver = createResolver(component, attribute[0], function (keypath) { 15303 if (keypath.isSpecial) { 15304 if (ready) { 15305 instance.set(key, keypath.value); // TODO use viewmodel? 15306 } else { 15307 data[key] = keypath.value; 15308 15309 // TODO errr.... would be better if we didn't have to do this 15310 delete mappings[key]; 15311 } 15312 } else { 15313 if (ready) { 15314 instance.viewmodel.mappings[key].resolve(keypath); 15315 } else { 15316 // resolved immediately 15317 mappings[key].keypath = keypath; 15318 } 15319 } 15320 }); 15321 } else { 15322 resolver = new initialise_ComplexParameter(component, attribute, function (value) { 15323 if (ready) { 15324 instance.set(key, value); // TODO use viewmodel? 15325 } else { 15326 data[key] = value; 15327 } 15328 }); 15329 } 15330 15331 resolvers.push(resolver); 15332 } else { 15333 throw new Error("erm wut"); 15334 } 15335 }); 15336 } 15337 15338 instance = create(Component.prototype); 15339 15340 initialise(instance, { 15341 el: null, 15342 append: true, 15343 data: data, 15344 partials: partials, 15345 magic: ractive.magic || Component.defaults.magic, 15346 modifyArrays: ractive.modifyArrays, 15347 // need to inherit runtime parent adaptors 15348 adapt: ractive.adapt 15349 }, { 15350 parent: ractive, 15351 component: component, 15352 container: container, 15353 mappings: mappings, 15354 inlinePartials: inlinePartials, 15355 cssIds: parentFragment.cssIds 15356 }); 15357 15358 ready = true; 15359 component.resolvers = resolvers; 15360 15361 return instance; 15362 }; 15363 15364 function createResolver(component, template, callback) { 15365 var resolver; 15366 15367 if (template.r) { 15368 resolver = Resolvers_createReferenceResolver(component, template.r, callback); 15369 } else if (template.x) { 15370 resolver = new Resolvers_ExpressionResolver(component, component.parentFragment, template.x, callback); 15371 } else if (template.rx) { 15372 resolver = new ReferenceExpressionResolver_ReferenceExpressionResolver(component, template.rx, callback); 15373 } 15374 15375 return resolver; 15376 } 15377 15378 function isSingleInterpolator(template) { 15379 return template.length === 1 && template[0].t === INTERPOLATOR; 15380 } 15381 15382 // TODO how should event arguments be handled? e.g. 15383 // <widget on-foo='bar:1,2,3'/> 15384 // The event 'bar' will be fired on the parent instance 15385 // when 'foo' fires on the child, but the 1,2,3 arguments 15386 // will be lost 15387 15388 var initialise_propagateEvents = propagateEvents; 15389 15390 function propagateEvents(component, eventsDescriptor) { 15391 var eventName; 15392 15393 for (eventName in eventsDescriptor) { 15394 if (eventsDescriptor.hasOwnProperty(eventName)) { 15395 propagateEvent(component.instance, component.root, eventName, eventsDescriptor[eventName]); 15396 } 15397 } 15398 } 15399 15400 function propagateEvent(childInstance, parentInstance, eventName, proxyEventName) { 15401 if (typeof proxyEventName !== "string") { 15402 fatal("Components currently only support simple events - you cannot include arguments. Sorry!"); 15403 } 15404 15405 childInstance.on(eventName, function () { 15406 var event, args; 15407 15408 // semi-weak test, but what else? tag the event obj ._isEvent ? 15409 if (arguments.length && arguments[0] && arguments[0].node) { 15410 event = Array.prototype.shift.call(arguments); 15411 } 15412 15413 args = Array.prototype.slice.call(arguments); 15414 15415 shared_fireEvent(parentInstance, proxyEventName, { event: event, args: args }); 15416 15417 // cancel bubbling 15418 return false; 15419 }); 15420 } 15421 15422 var initialise_updateLiveQueries = function (component) { 15423 var ancestor, query; 15424 15425 // If there's a live query for this component type, add it 15426 ancestor = component.root; 15427 while (ancestor) { 15428 if (query = ancestor._liveComponentQueries["_" + component.name]) { 15429 query.push(component.instance); 15430 } 15431 15432 ancestor = ancestor.parent; 15433 } 15434 }; 15435 15436 var Component_prototype_init = Component$init; 15437 function Component$init(options, Component) { 15438 var parentFragment, root; 15439 15440 if (!Component) { 15441 throw new Error("Component \"" + this.name + "\" not found"); 15442 } 15443 15444 parentFragment = this.parentFragment = options.parentFragment; 15445 root = parentFragment.root; 15446 15447 this.root = root; 15448 this.type = COMPONENT; 15449 this.name = options.template.e; 15450 this.index = options.index; 15451 this.indexRefBindings = {}; 15452 this.yielders = {}; 15453 this.resolvers = []; 15454 15455 createInstance(this, Component, options.template.a, options.template.f, options.template.p); 15456 initialise_propagateEvents(this, options.template.v); 15457 15458 // intro, outro and decorator directives have no effect 15459 if (options.template.t0 || options.template.t1 || options.template.t2 || options.template.o) { 15460 warnIfDebug("The \"intro\", \"outro\" and \"decorator\" directives have no effect on components", { ractive: this.instance }); 15461 } 15462 15463 initialise_updateLiveQueries(this); 15464 } 15465 15466 var Component_prototype_rebind = Component$rebind; 15467 15468 function Component$rebind(oldKeypath, newKeypath) { 15469 var query; 15470
15471 this.resolvers.forEach(rebind); 15472 15473 for (var k in this.yielders) { 15474 if (this.yielders[k][0]) { 15475 rebind(this.yielders[k][0]); 15476 } 15477 } 15478 15479 if (query = this.root._liveComponentQueries["_" + this.name]) { 15480 query._makeDirty(); 15481 } 15482 15483 function rebind(x) { 15484 x.rebind(oldKeypath, newKeypath); 15485 } 15486 } 15487 15488 var Component_prototype_render = Component$render; 15489 15490 function Component$render() { 15491 var instance = this.instance; 15492 15493 instance.render(this.parentFragment.getNode()); 15494 15495 this.rendered = true; 15496 return instance.fragment.detach(); 15497 } 15498 15499 var Component_prototype_toString = Component$toString; 15500 15501 function Component$toString() { 15502 return this.instance.fragment.toString(); 15503 } 15504 15505 var Component_prototype_unbind = Component$unbind; 15506 15507 var Component_prototype_unbind__teardownHook = new hooks_Hook("teardown"); 15508 function Component$unbind() { 15509 var instance = this.instance; 15510 15511 this.resolvers.forEach(methodCallers__unbind); 15512 15513 removeFromLiveComponentQueries(this); 15514 15515 instance._observers.forEach(cancel); 15516 15517 // teardown the instance 15518 instance.fragment.unbind(); 15519 instance.viewmodel.teardown(); 15520 15521 if (instance.fragment.rendered && instance.el.__ractive_instances__) { 15522 removeFromArray(instance.el.__ractive_instances__, instance); 15523 } 15524 15525 Component_prototype_unbind__teardownHook.fire(instance); 15526 } 15527 15528 function removeFromLiveComponentQueries(component) { 15529 var instance, query; 15530 15531 instance = component.root; 15532 15533 do { 15534 if (query = instance._liveComponentQueries["_" + component.name]) { 15535 query._remove(component); 15536 } 15537 } while (instance = instance.parent); 15538 } 15539 15540 var Component_prototype_unrender = Component$unrender; 15541 15542 function Component$unrender(shouldDestroy) { 15543 this.shouldDestroy = shouldDestroy; 15544 this.instance.unrender(); 15545 } 15546 15547 var Component = function (options, Constructor) { 15548 this.init(options, Constructor); 15549 }; 15550 15551 Component.prototype = { 15552 detach: Component_prototype_detach, 15553 find: Component_prototype_find, 15554 findAll: Component_prototype_findAll, 15555 findAllComponents: Component_prototype_findAllComponents, 15556 findComponent: Component_prototype_findComponent, 15557 findNextNode: Component_prototype_findNextNode, 15558 firstNode: Component_prototype_firstNode, 15559 init: Component_prototype_init, 15560 rebind: Component_prototype_rebind, 15561 render: Component_prototype_render, 15562 toString: Component_prototype_toString, 15563 unbind: Component_prototype_unbind, 15564 unrender: Component_prototype_unrender 15565 }; 15566 15567 var _Component = Component; 15568 15569 var Comment = function (options) { 15570 this.type = COMMENT; 15571 this.value = options.template.c; 15572 }; 15573 15574 Comment.prototype = { 15575 detach: shared_detach, 15576 15577 firstNode: function () { 15578 return this.node; 15579 }, 15580 15581 render: function () { 15582 if (!this.node) { 15583 this.node = document.createComment(this.value); 15584 } 15585 15586 return this.node; 15587 }, 15588 15589 toString: function () { 15590 return "<!--" + this.value + "-->"; 15591 }, 15592 15593 unrender: function (shouldDestroy) { 15594 if (shouldDestroy) { 15595 this.node.parentNode.removeChild(this.node); 15596 } 15597 } 15598 }; 15599 15600 var items_Comment = Comment; 15601 15602 var Yielder = function (options) { 15603 var container, component; 15604 15605 this.type = YIELDER; 15606 15607 this.container = container = options.parentFragment.root; 15608 this.component = component = container.component; 15609 15610 this.container = container; 15611 this.containerFragment = options.parentFragment; 15612 this.parentFragment = component.parentFragment; 15613 15614 var name = this.name = options.template.n || ""; 15615 15616 var template = container._inlinePartials[name]; 15617 15618 if (!template) { 15619 warnIfDebug("Could not find template for partial \"" + name + "\"", { ractive: options.root }); 15620 template = []; 15621 } 15622 15623 this.fragment = new virtualdom_Fragment({ 15624 owner: this, 15625 root: container.parent, 15626 template: template, 15627 pElement: this.containerFragment.pElement 15628 }); 15629 15630 // even though only one yielder is allowed, we need to have an array of them 15631 // as it's possible to cause a yielder to be created before the last one 15632 // was destroyed in the same turn of the runloop 15633 if (!isArray(component.yielders[name])) { 15634 component.yielders[name] = [this]; 15635 } else { 15636 component.yielders[name].push(this); 15637 } 15638 15639 global_runloop.scheduleTask(function () { 15640 if (component.yielders[name].length > 1) { 15641 throw new Error("A component template can only have one {{yield" + (name ? " " + name : "") + "}} declaration at a time"); 15642 } 15643 }); 15644 }; 15645 15646 Yielder.prototype = { 15647 detach: function () { 15648 return this.fragment.detach(); 15649 }, 15650 15651 find: function (selector) { 15652 return this.fragment.find(selector); 15653 }, 15654 15655 findAll: function (selector, query) { 15656 return this.fragment.findAll(selector, query); 15657 }, 15658 15659 findComponent: function (selector) { 15660 return this.fragment.findComponent(selector); 15661 }, 15662 15663 findAllComponents: function (selector, query) { 15664 return this.fragment.findAllComponents(selector, query); 15665 }, 15666 15667 findNextNode: function () { 15668 return this.containerFragment.findNextNode(this); 15669 }, 15670 15671 firstNode: function () { 15672 return this.fragment.firstNode(); 15673 }, 15674 15675 getValue: function (options) { 15676 return this.fragment.getValue(options); 15677 }, 15678 15679 render: function () { 15680 return this.fragment.render(); 15681 }, 15682 15683 unbind: function () { 15684 this.fragment.unbind(); 15685 }, 15686 15687 unrender: function (shouldDestroy) { 15688 this.fragment.unrender(shouldDestroy); 15689 removeFromArray(this.component.yielders[this.name], this); 15690 }, 15691 15692 rebind: function (oldKeypath, newKeypath) { 15693 this.fragment.rebind(oldKeypath, newKeypath); 15694 }, 15695 15696 toString: function () { 15697 return this.fragment.toString(); 15698 } 15699 }; 15700 15701 var items_Yielder = Yielder; 15702 15703 var Doctype = function (options) { 15704 this.declaration = options.template.a; 15705 }; 15706 15707 Doctype.prototype = { 15708 init: noop, 15709 render: noop, 15710 unrender: noop, 15711 teardown: noop, 15712 toString: function () { 15713 return "<!DOCTYPE" + this.declaration + ">"; 15714 } 15715 }; 15716 15717 var items_Doctype = Doctype; 15718 15719 var Fragment_prototype_init = Fragment$init; 15720 15721 function Fragment$init(options) { 15722 var _this = this; 15723 15724 this.owner = options.owner; // The item that owns this fragment - an element, section, partial, or attribute 15725 this.parent = this.owner.parentFragment; 15726 15727 // inherited properties 15728 this.root = options.root; 15729 this.pElement = options.pElement; 15730 this.context = options.context; 15731 this.index = options.index; 15732 this.key = options.key; 15733 this.registeredIndexRefs = []; 15734 15735 // encapsulated styles should be inherited until they get applied by an element 15736 this.cssIds = "cssIds" in options ? options.cssIds : this.parent ? this.parent.cssIds : null; 15737 15738 this.items = options.template.map(function (template, i) { 15739 return createItem({ 15740 parentFragment: _this, 15741 pElement: options.pElement, 15742 template: template, 15743 index: i 15744 }); 15745 }); 15746 15747 this.value = this.argsList = null; 15748 this.dirtyArgs = this.dirtyValue = true; 15749 15750 this.bound = true; 15751 } 15752 15753 function createItem(options) { 15754 if (typeof options.template === "string") { 15755 return new items_Text(options); 15756 } 15757 15758 switch (options.template.t) { 15759 case YIELDER: 15760 return new items_Yielder(options); 15761 case INTERPOLATOR: 15762 return new items_Interpolator(options); 15763 case SECTION: 15764 return new _Section(options); 15765 case TRIPLE: 15766 return new _Triple(options); 15767 case ELEMENT: 15768 var constructor = undefined; 15769 if (constructor = Component_getComponent(options.parentFragment.root, options.template.e)) { 15770 return new _Component(options, constructor); 15771 } 15772 return new _Element(options); 15773 case PARTIAL: 15774 return new _Partial(options); 15775 case COMMENT: 15776 return new items_Comment(options); 15777 case DOCTYPE: 15778 return new items_Doctype(options); 15779 15780 default: 15781 throw new Error("Something very strange happened. Please file an issue at https://github.com/ractivejs/ractive/issues. Thanks!"); 15782 } 15783 } 15784 15785 var Fragment_prototype_rebind = Fragment$rebind; 15786 function Fragment$rebind(oldKeypath, newKeypath) { 15787 15788 // assign new context keypath if needed 15789 if (!this.owner || this.owner.hasContext) { 15790 assignNewKeypath(this, "context", oldKeypath, newKeypath); 15791 } 15792
15793 this.items.forEach(function (item) { 15794 if (item.rebind) { 15795 item.rebind(oldKeypath, newKeypath); 15796 } 15797 }); 15798 } 15799 15800 var Fragment_prototype_render = Fragment$render; 15801 15802 function Fragment$render() { 15803 var result; 15804 15805 if (this.items.length === 1) { 15806 result = this.items[0].render(); 15807 } else { 15808 result = document.createDocumentFragment(); 15809 15810 this.items.forEach(function (item) { 15811 result.appendChild(item.render()); 15812 }); 15813 } 15814 15815 this.rendered = true; 15816 return result; 15817 } 15818 15819 var Fragment_prototype_toString = Fragment$toString; 15820 15821 function Fragment$toString(escape) { 15822 if (!this.items) { 15823 return ""; 15824 } 15825 15826 return this.items.map(escape ? toEscapedString : Fragment_prototype_toString__toString).join(""); 15827 } 15828 15829 function Fragment_prototype_toString__toString(item) { 15830 return item.toString(); 15831 } 15832 15833 function toEscapedString(item) { 15834 return item.toString(true); 15835 } 15836 15837 var Fragment_prototype_unbind = Fragment$unbind; 15838 15839 function Fragment$unbind() { 15840 if (!this.bound) { 15841 return; 15842 } 15843 15844 this.items.forEach(unbindItem); 15845 this.bound = false; 15846 } 15847 15848 function unbindItem(item) { 15849 if (item.unbind) { 15850 item.unbind(); 15851 } 15852 } 15853 15854 var Fragment_prototype_unrender = Fragment$unrender; 15855 15856 function Fragment$unrender(shouldDestroy) { 15857 if (!this.rendered) { 15858 throw new Error("Attempted to unrender a fragment that was not rendered"); 15859 } 15860 15861 this.items.forEach(function (i) { 15862 return i.unrender(shouldDestroy); 15863 }); 15864 this.rendered = false; 15865 } 15866 15867 var Fragment = function (options) { 15868 this.init(options); 15869 }; 15870 15871 Fragment.prototype = { 15872 bubble: prototype_bubble, 15873 detach: Fragment_prototype_detach, 15874 find: Fragment_prototype_find, 15875 findAll: Fragment_prototype_findAll, 15876 findAllComponents: Fragment_prototype_findAllComponents, 15877 findComponent: Fragment_prototype_findComponent, 15878 findNextNode: prototype_findNextNode, 15879 firstNode: prototype_firstNode, 15880 getArgsList: getArgsList, 15881 getNode: getNode, 15882 getValue: prototype_getValue, 15883 init: Fragment_prototype_init, 15884 rebind: Fragment_prototype_rebind, 15885 registerIndexRef: function (idx) { 15886 var idxs = this.registeredIndexRefs; 15887 if (idxs.indexOf(idx) === -1) { 15888 idxs.push(idx); 15889 } 15890 }, 15891 render: Fragment_prototype_render, 15892 toString: Fragment_prototype_toString, 15893 unbind: Fragment_prototype_unbind, 15894 unregisterIndexRef: function (idx) { 15895 var idxs = this.registeredIndexRefs; 15896 idxs.splice(idxs.indexOf(idx), 1); 15897 }, 15898 unrender: Fragment_prototype_unrender 15899 }; 15900 15901 var virtualdom_Fragment = Fragment; 15902 15903 var prototype_reset = Ractive$reset; 15904 var shouldRerender = ["template", "partials", "components", "decorators", "events"], 15905 resetHook = new hooks_Hook("reset"); 15906 function Ractive$reset(data) { 15907 var promise, wrapper, changes, i, rerender; 15908 15909 data = data || {}; 15910 15911 if (typeof data !== "object") { 15912 throw new Error("The reset method takes either no arguments, or an object containing new data"); 15913 } 15914 15915 // If the root object is wrapped, try and use the wrapper's reset value 15916 if ((wrapper = this.viewmodel.wrapped[""]) && wrapper.reset) { 15917 if (wrapper.reset(data) === false) { 15918 // reset was rejected, we need to replace the object 15919 this.viewmodel.reset(data); 15920 } 15921 } else { 15922 this.viewmodel.reset(data); 15923 } 15924 15925 // reset config items and track if need to rerender 15926 changes = config_config.reset(this); 15927 15928 i = changes.length; 15929 while (i--) { 15930 if (shouldRerender.indexOf(changes[i]) > -1) { 15931 rerender = true; 15932 break; 15933 } 15934 } 15935 15936 if (rerender) { 15937 var component = undefined; 15938 15939 this.viewmodel.mark(rootKeypath); 15940 15941 // Is this is a component, we need to set the `shouldDestroy` 15942 // flag, otherwise it will assume by default that a parent node 15943 // will be detached, and therefore it doesn't need to bother 15944 // detaching its own nodes 15945 if (component = this.component) { 15946 component.shouldDestroy = true; 15947 } 15948 15949 this.unrender(); 15950 15951 if (component) { 15952 component.shouldDestroy = false; 15953 } 15954 15955 // If the template changed, we need to destroy the parallel DOM 15956 // TODO if we're here, presumably it did? 15957 if (this.fragment.template !== this.template) { 15958 this.fragment.unbind(); 15959 15960 this.fragment = new virtualdom_Fragment({ 15961 template: this.template, 15962 root: this, 15963 owner: this 15964 }); 15965 } 15966 15967 promise = this.render(this.el, this.anchor); 15968 } else { 15969 promise = global_runloop.start(this, true); 15970 this.viewmodel.mark(rootKeypath); 15971 global_runloop.end(); 15972 } 15973 15974 resetHook.fire(this, data); 15975 15976 return promise; 15977 } 15978 15979 var resetPartial = function (name, partial) { 15980 var promise, 15981 collection = []; 15982 15983 function collect(source, dest, ractive) { 15984 // if this is a component and it has its own partial, bail 15985 if (ractive && ractive.partials[name]) return; 15986 15987 source.forEach(function (item) { 15988 // queue to rerender if the item is a partial and the current name matches 15989 if (item.type === PARTIAL && item.getPartialName() === name) { 15990 dest.push(item); 15991 } 15992 15993 // if it has a fragment, process its items 15994 if (item.fragment) { 15995 collect(item.fragment.items, dest, ractive); 15996 } 15997 15998 // or if it has fragments 15999 if (isArray(item.fragments)) { 16000 collect(item.fragments, dest, ractive); 16001 } 16002 16003 // or if it is itself a fragment, process its items 16004 else if (isArray(item.items)) { 16005 collect(item.items, dest, ractive); 16006 } 16007 16008 // or if it is a component, step in and process its items 16009 else if (item.type === COMPONENT && item.instance) { 16010 collect(item.instance.fragment.items, dest, item.instance); 16011 } 16012 16013 // if the item is an element, process its attributes too 16014 if (item.type === ELEMENT) { 16015 if (isArray(item.attributes)) { 16016 collect(item.attributes, dest, ractive); 16017 } 16018 16019 if (isArray(item.conditionalAttributes)) { 16020 collect(item.conditionalAttributes, dest, ractive); 16021 } 16022 } 16023 }); 16024 } 16025 16026 collect(this.fragment.items, collection); 16027 this.partials[name] = partial; 16028 16029 promise = global_runloop.start(this, true); 16030 16031 collection.forEach(function (item) { 16032 item.value = undefined; 16033 item.setValue(name); 16034 }); 16035 16036 global_runloop.end(); 16037 16038 return promise; 16039 }; 16040 16041 // TODO should resetTemplate be asynchronous? i.e. should it be a case 16042 // of outro, update template, intro? I reckon probably not, since that 16043 // could be achieved with unrender-resetTemplate-render. Also, it should 16044 // conceptually be similar to resetPartial, which couldn't be async 16045 16046 var resetTemplate = Ractive$resetTemplate; 16047 function Ractive$resetTemplate(template) { 16048 var transitionsEnabled, component; 16049 16050 template_template.init(null, this, { template: template }); 16051 16052 transitionsEnabled = this.transitionsEnabled; 16053 this.transitionsEnabled = false;
16054 16055 // Is this is a component, we need to set the `shouldDestroy` 16056 // flag, otherwise it will assume by default that a parent node 16057 // will be detached, and therefore it doesn't need to bother 16058 // detaching its own nodes 16059 if (component = this.component) { 16060 component.shouldDestroy = true; 16061 } 16062 16063 this.unrender(); 16064 16065 if (component) { 16066 component.shouldDestroy = false; 16067 } 16068 16069 // remove existing fragment and create new one 16070 this.fragment.unbind(); 16071 this.fragment = new virtualdom_Fragment({ 16072 template: this.template, 16073 root: this, 16074 owner: this 16075 }); 16076 16077 this.render(this.el, this.anchor); 16078 16079 this.transitionsEnabled = transitionsEnabled; 16080 } 16081 16082 var reverse = makeArrayMethod("reverse"); 16083 16084 var Ractive_prototype_set = Ractive$set; 16085 16086 function Ractive$set(keypath, value) { 16087 var map, promise; 16088 16089 promise = global_runloop.start(this, true); 16090 16091 // Set multiple keypaths in one go 16092 if (isObject(keypath)) { 16093 map = keypath; 16094 16095 for (keypath in map) { 16096 if (map.hasOwnProperty(keypath)) { 16097 value = map[keypath]; 16098 set(this, keypath, value); 16099 } 16100 } 16101 } 16102 16103 // Set a single keypath 16104 else { 16105 set(this, keypath, value); 16106 } 16107 16108 global_runloop.end(); 16109 16110 return promise; 16111 } 16112 16113 function set(ractive, keypath, value) { 16114 keypath = getKeypath(normalise(keypath)); 16115 16116 if (keypath.isPattern) {
16117 getMatchingKeypaths(ractive, keypath).forEach(function (keypath) { 16118 ractive.viewmodel.set(keypath, value); 16119 }); 16120 } else { 16121 ractive.viewmodel.set(keypath, value); 16122 } 16123 } 16124 16125 var shift = makeArrayMethod("shift"); 16126 16127 var prototype_sort = makeArrayMethod("sort"); 16128 16129 var splice = makeArrayMethod("splice"); 16130 16131 var subtract = Ractive$subtract; 16132 function Ractive$subtract(keypath, d) { 16133 return shared_add(this, keypath, d === undefined ? -1 : -d); 16134 } 16135 16136 // Teardown. This goes through the root fragment and all its children, removing observers 16137 // and generally cleaning up after itself 16138 16139 var Ractive_prototype_teardown = Ractive$teardown; 16140 16141 var Ractive_prototype_teardown__teardownHook = new hooks_Hook("teardown"); 16142 function Ractive$teardown() { 16143 var promise; 16144 16145 this.fragment.unbind(); 16146 this.viewmodel.teardown(); 16147 16148 this._observers.forEach(cancel); 16149 16150 if (this.fragment.rendered && this.el.__ractive_instances__) { 16151 removeFromArray(this.el.__ractive_instances__, this); 16152 } 16153 16154 this.shouldDestroy = true; 16155 promise = this.fragment.rendered ? this.unrender() : utils_Promise.resolve(); 16156 16157 Ractive_prototype_teardown__teardownHook.fire(this); 16158 16159 this._boundFunctions.forEach(deleteFunctionCopy); 16160 16161 return promise; 16162 } 16163 16164 function deleteFunctionCopy(bound) { 16165 delete bound.fn[bound.prop]; 16166 } 16167 16168 var toggle = Ractive$toggle; 16169 function Ractive$toggle(keypath) { 16170 var _this = this; 16171 16172 if (typeof keypath !== "string") { 16173 throw new TypeError(badArguments); 16174 } 16175 16176 var changes = undefined; 16177 16178 if (/\*/.test(keypath)) { 16179 changes = {}; 16180 16181 getMatchingKeypaths(this, getKeypath(normalise(keypath))).forEach(function (keypath) { 16182 changes[keypath.str] = !_this.viewmodel.get(keypath); 16183 }); 16184 16185 return this.set(changes); 16186 } 16187 16188 return this.set(keypath, !this.get(keypath)); 16189 } 16190 16191 var toHTML = Ractive$toHTML; 16192 16193 function Ractive$toHTML() { 16194 return this.fragment.toString(true); 16195 } 16196 16197 var Ractive_prototype_unrender = Ractive$unrender; 16198 var unrenderHook = new hooks_Hook("unrender"); 16199 function Ractive$unrender() { 16200 var promise, shouldDestroy; 16201 16202 if (!this.fragment.rendered) { 16203 warnIfDebug("ractive.unrender() was called on a Ractive instance that was not rendered"); 16204 return utils_Promise.resolve(); 16205 } 16206 16207 promise = global_runloop.start(this, true); 16208 16209 // If this is a component, and the component isn't marked for destruction, 16210 // don't detach nodes from the DOM unnecessarily 16211 shouldDestroy = !this.component || this.component.shouldDestroy || this.shouldDestroy; 16212 16213 // Cancel any animations in progress 16214 while (this._animations[0]) { 16215 this._animations[0].stop(); // it will remove itself from the index 16216 } 16217 16218 this.fragment.unrender(shouldDestroy); 16219 16220 removeFromArray(this.el.__ractive_instances__, this); 16221 16222 unrenderHook.fire(this); 16223 16224 global_runloop.end(); 16225 return promise; 16226 } 16227 16228 var unshift = makeArrayMethod("unshift"); 16229 16230 var Ractive_prototype_update = Ractive$update; 16231 var updateHook = new hooks_Hook("update"); 16232 function Ractive$update(keypath) { 16233 var promise; 16234 16235 keypath = getKeypath(keypath) || rootKeypath; 16236 16237 promise = global_runloop.start(this, true); 16238 this.viewmodel.mark(keypath); 16239 global_runloop.end(); 16240 16241 updateHook.fire(this, keypath); 16242 16243 return promise; 16244 } 16245 16246 var prototype_updateModel = Ractive$updateModel; 16247 16248 function Ractive$updateModel(keypath, cascade) { 16249 var values, key, bindings; 16250 16251 if (typeof keypath === "string" && !cascade) { 16252 bindings = this._twowayBindings[keypath]; 16253 } else { 16254 bindings = []; 16255 16256 for (key in this._twowayBindings) { 16257 if (!keypath || getKeypath(key).equalsOrStartsWith(keypath)) { 16258 // TODO is this right? 16259 bindings.push.apply(bindings, this._twowayBindings[key]); 16260 } 16261 } 16262 } 16263 16264 values = consolidate(this, bindings); 16265 return this.set(values); 16266 } 16267 16268 function consolidate(ractive, bindings) { 16269 var values = {}, 16270 checkboxGroups = []; 16271
16272 bindings.forEach(function (b) { 16273 var oldValue, newValue; 16274 16275 // special case - radio name bindings 16276 if (b.radioName && !b.element.node.checked) { 16277 return; 16278 } 16279 16280 // special case - checkbox name bindings come in groups, so 16281 // we want to get the value once at most 16282 if (b.checkboxName) { 16283 if (!checkboxGroups[b.keypath.str] && !b.changed()) { 16284 checkboxGroups.push(b.keypath); 16285 checkboxGroups[b.keypath.str] = b; 16286 } 16287 16288 return; 16289 } 16290 16291 oldValue = b.attribute.value; 16292 newValue = b.getValue(); 16293 16294 if (arrayContentsMatch(oldValue, newValue)) { 16295 return; 16296 } 16297 16298 if (!isEqual(oldValue, newValue)) { 16299 values[b.keypath.str] = newValue; 16300 } 16301 }); 16302 16303 // Handle groups of `<input type='checkbox' name='{{foo}}' ...>` 16304 if (checkboxGroups.length) { 16305 checkboxGroups.forEach(function (keypath) { 16306 var binding, oldValue, newValue; 16307 16308 binding = checkboxGroups[keypath.str]; // one to represent the entire group 16309 oldValue = binding.attribute.value; 16310 newValue = binding.getValue(); 16311 16312 if (!arrayContentsMatch(oldValue, newValue)) { 16313 values[keypath.str] = newValue; 16314 } 16315 }); 16316 } 16317 16318 return values; 16319 } 16320 16321 var prototype = { 16322 add: prototype_add, 16323 animate: prototype_animate, 16324 detach: prototype_detach, 16325 find: prototype_find, 16326 findAll: prototype_findAll, 16327 findAllComponents: prototype_findAllComponents, 16328 findComponent: prototype_findComponent, 16329 findContainer: findContainer, 16330 findParent: findParent, 16331 fire: prototype_fire, 16332 get: prototype_get, 16333 insert: insert, 16334 merge: prototype_merge, 16335 observe: observe, 16336 observeOnce: observeOnce, 16337 off: off, 16338 on: on, 16339 once: once, 16340 pop: pop, 16341 push: push, 16342 render: prototype_render, 16343 reset: prototype_reset, 16344 resetPartial: resetPartial, 16345 resetTemplate: resetTemplate, 16346 reverse: reverse, 16347 set: Ractive_prototype_set, 16348 shift: shift, 16349 sort: prototype_sort, 16350 splice: splice, 16351 subtract: subtract, 16352 teardown: Ractive_prototype_teardown, 16353 toggle: toggle, 16354 toHTML: toHTML, 16355 toHtml: toHTML, 16356 unrender: Ractive_prototype_unrender, 16357 unshift: unshift, 16358 update: Ractive_prototype_update, 16359 updateModel: prototype_updateModel 16360 }; 16361 16362 var wrapMethod = function (method, superMethod, force) { 16363 16364 if (force || needsSuper(method, superMethod)) { 16365 16366 return function () { 16367 16368 var hasSuper = ("_super" in this), 16369 _super = this._super, 16370 result; 16371 16372 this._super = superMethod; 16373 16374 result = method.apply(this, arguments); 16375 16376 if (hasSuper) { 16377 this._super = _super; 16378 } 16379 16380 return result; 16381 }; 16382 } else { 16383 return method; 16384 } 16385 }; 16386 16387 function needsSuper(method, superMethod) { 16388 return typeof superMethod === "function" && /_super/.test(method); 16389 } 16390 16391 var unwrapExtended = unwrap; 16392 16393 function unwrap(Child) { 16394 var options = {}; 16395 16396 while (Child) { 16397 addRegistries(Child, options); 16398 addOtherOptions(Child, options); 16399 16400 if (Child._Parent !== _Ractive) { 16401 Child = Child._Parent; 16402 } else { 16403 Child = false; 16404 } 16405 } 16406 16407 return options; 16408 } 16409 16410 function addRegistries(Child, options) { 16411 config_registries.forEach(function (r) { 16412 addRegistry(r.useDefaults ? Child.prototype : Child, options, r.name); 16413 }); 16414 } 16415 16416 function addRegistry(target, options, name) { 16417 var registry, 16418 keys = Object.keys(target[name]); 16419 16420 if (!keys.length) { 16421 return; 16422 } 16423 16424 if (!(registry = options[name])) { 16425 registry = options[name] = {}; 16426 } 16427 16428 keys.filter(function (key) { 16429 return !(key in registry); 16430 }).forEach(function (key) { 16431 return registry[key] = target[name][key]; 16432 }); 16433 } 16434 16435 function addOtherOptions(Child, options) { 16436 Object.keys(Child.prototype).forEach(function (key) { 16437 if (key === "computed") { 16438 return; 16439 } 16440 16441 var value = Child.prototype[key]; 16442 16443 if (!(key in options)) { 16444 options[key] = value._method ? value._method : value; 16445 } 16446 16447 // is it a wrapped function? 16448 else if (typeof options[key] === "function" && typeof value === "function" && options[key]._method) { 16449 16450 var result = undefined, 16451 needsSuper = value._method; 16452 16453 if (needsSuper) { 16454 value = value._method; 16455 } 16456 16457 // rewrap bound directly to parent fn 16458 result = wrapMethod(options[key]._method, value); 16459 16460 if (needsSuper) { 16461 result._method = result; 16462 } 16463 16464 options[key] = result; 16465 } 16466 }); 16467 } 16468 16469 var _extend = _extend__extend; 16470 16471 function _extend__extend() { 16472 for (var _len = arguments.length, options = Array(_len), _key = 0; _key < _len; _key++) { 16473 options[_key] = arguments[_key]; 16474 } 16475 16476 if (!options.length) { 16477 return extendOne(this); 16478 } else { 16479 return options.reduce(extendOne, this); 16480 } 16481 } 16482 16483 function extendOne(Parent) { 16484 var options = arguments[1] === undefined ? {} : arguments[1]; 16485 16486 var Child, proto; 16487 16488 // if we're extending with another Ractive instance... 16489 // 16490 // var Human = Ractive.extend(...), Spider = Ractive.extend(...); 16491 // var Spiderman = Human.extend( Spider ); 16492 // 16493 // ...inherit prototype methods and default options as well 16494 if (options.prototype instanceof _Ractive) { 16495 options = unwrapExtended(options); 16496 } 16497 16498 Child = function (options) { 16499 if (!(this instanceof Child)) return new Child(options); 16500 initialise(this, options); 16501 }; 16502 16503 proto = create(Parent.prototype); 16504 proto.constructor = Child; 16505 16506 // Static properties 16507 defineProperties(Child, { 16508 // alias prototype as defaults 16509 defaults: { value: proto }, 16510 16511 // extendable 16512 extend: { value: _extend__extend, writable: true, configurable: true }, 16513 16514 // Parent - for IE8, can't use Object.getPrototypeOf 16515 _Parent: { value: Parent } 16516 }); 16517 16518 // extend configuration 16519 config_config.extend(Parent, proto, options); 16520 16521 custom_data.extend(Parent, proto, options); 16522 16523 if (options.computed) { 16524 proto.computed = utils_object__extend(create(Parent.prototype.computed), options.computed); 16525 } 16526 16527 Child.prototype = proto; 16528 16529 return Child; 16530 } 16531 16532 var getNodeInfo = function (node) { 16533 var info = {}, 16534 priv, 16535 indices; 16536 16537 if (!node || !(priv = node._ractive)) { 16538 return info; 16539 } 16540 16541 info.ractive = priv.root; 16542 info.keypath = priv.keypath.str; 16543 info.index = {}; 16544 16545 // find all index references and resolve them 16546 if (indices = Resolvers_findIndexRefs(priv.proxy.parentFragment)) { 16547 info.index = Resolvers_findIndexRefs.resolve(indices); 16548 } 16549 16550 return info; 16551 }; 16552 16553 var Ractive, properties; 16554 16555 // Main Ractive required object 16556 Ractive = function (options) { 16557 if (!(this instanceof Ractive)) return new Ractive(options); 16558 initialise(this, options); 16559 }; 16560 16561 // Ractive properties 16562 properties = { 16563 16564 // debug flag 16565 DEBUG: { writable: true, value: true }, 16566 DEBUG_PROMISES: { writable: true, value: true }, 16567 16568 // static methods: 16569 extend: { value: _extend }, 16570 getNodeInfo: { value: getNodeInfo }, 16571 parse: { value: _parse }, 16572 16573 // Namespaced constructors 16574 Promise: { value: utils_Promise }, 16575 16576 // support 16577 svg: { value: svg }, 16578 magic: { value: environment__magic }, 16579 16580 // version 16581 VERSION: { value: "0.7.3" }, 16582 16583 // Plugins 16584 adaptors: { writable: true, value: {} }, 16585 components: { writable: true, value: {} }, 16586 decorators: { writable: true, value: {} }, 16587 easing: { writable: true, value: static_easing }, 16588 events: { writable: true, value: {} }, 16589 interpolators: { writable: true, value: static_interpolators }, 16590 partials: { writable: true, value: {} }, 16591 transitions: { writable: true, value: {} } 16592 }; 16593 16594 // Ractive properties 16595 defineProperties(Ractive, properties); 16596 16597 Ractive.prototype = utils_object__extend(prototype, config_defaults); 16598 16599 Ractive.prototype.constructor = Ractive; 16600 16601 // alias prototype as defaults 16602 Ractive.defaults = Ractive.prototype; 16603 16604 // Ractive.js makes liberal use of things like Array.prototype.indexOf. In 16605 // older browsers, these are made available via a shim - here, we do a quick 16606 // pre-flight check to make sure that either a) we're not in a shit browser, 16607 // or b) we're using a Ractive-legacy.js build 16608 var FUNCTION = "function"; 16609 16610 if (typeof Date.now !== FUNCTION || typeof String.prototype.trim !== FUNCTION || typeof Object.keys !== FUNCTION || typeof Array.prototype.indexOf !== FUNCTION || typeof Array.prototype.forEach !== FUNCTION || typeof Array.prototype.map !== FUNCTION || typeof Array.prototype.filter !== FUNCTION || typeof window !== "undefined" && typeof window.addEventListener !== FUNCTION) { 16611 throw new Error("It looks like you're attempting to use Ractive.js in an older browser. You'll need to use one of the 'legacy builds' in order to continue - see http://docs.ractivejs.org/latest/legacy-builds for more information."); 16612 } 16613 16614 var _Ractive = Ractive; 16615 16616 return _Ractive; 16617 16618})); 16619//# sourceMappingURL=ractive.js.map
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.