;(function() { /*! * @overview Ember - JavaScript Application Framework * @copyright Copyright 2011-2016 Tilde Inc. and contributors * Portions Copyright 2006-2011 Strobe Inc. * Portions Copyright 2008-2011 Apple Inc. All rights reserved. * @license Licensed under MIT license * See https://raw.github.com/emberjs/ember.js/master/LICENSE * @version 2.5.0 */ var enifed, requireModule, require, requirejs, Ember; var mainContext = this; (function() { var isNode = typeof window === 'undefined' && typeof process !== 'undefined' && {}.toString.call(process) === '[object process]'; if (!isNode) { Ember = this.Ember = this.Ember || {}; } if (typeof Ember === 'undefined') { Ember = {}; }; if (typeof Ember.__loader === 'undefined') { var registry = {}; var seen = {}; enifed = function(name, deps, callback) { var value = { }; if (!callback) { value.deps = []; value.callback = deps; } else { value.deps = deps; value.callback = callback; } registry[name] = value; }; requirejs = require = requireModule = function(name) { return internalRequire(name, null); } // setup `require` module require['default'] = require; require.has = function registryHas(moduleName) { return !!registry[moduleName] || !!registry[moduleName + '/index']; }; function missingModule(name, referrerName) { if (referrerName) { throw new Error('Could not find module ' + name + ' required by: ' + referrerName); } else { throw new Error('Could not find module ' + name); } } function internalRequire(_name, referrerName) { var name = _name; var mod = registry[name]; if (!mod) { name = name + '/index'; mod = registry[name]; } var exports = seen[name]; if (exports !== undefined) { return exports; } exports = seen[name] = {}; if (!mod) { missingModule(_name, referrerName); } var deps = mod.deps; var callback = mod.callback; var length = deps.length; var reified = new Array(length);; for (var i = 0; i < length; i++) { if (deps[i] === 'exports') { reified[i] = exports; } else if (deps[i] === 'require') { reified[i] = require; } else { reified[i] = internalRequire(deps[i], name); } } callback.apply(this, reified); return exports; }; requirejs._eak_seen = registry; Ember.__loader = { define: enifed, require: require, registry: registry }; } else { enifed = Ember.__loader.define; requirejs = require = requireModule = Ember.__loader.require; } })(); enifed("backburner/binary-search", ["exports"], function (exports) { "use strict"; exports.default = binarySearch; function binarySearch(time, timers) { var start = 0; var end = timers.length - 2; var middle, l; while (start < end) { // since timers is an array of pairs 'l' will always // be an integer l = (end - start) / 2; // compensate for the index in case even number // of pairs inside timers middle = start + l - l % 2; if (time >= timers[middle]) { start = middle + 2; } else { end = middle; } } return time >= timers[start] ? start + 2 : start; } }); enifed('backburner/deferred-action-queues', ['exports', 'backburner/utils', 'backburner/queue'], function (exports, _backburnerUtils, _backburnerQueue) { 'use strict'; exports.default = DeferredActionQueues; function DeferredActionQueues(queueNames, options) { var queues = this.queues = {}; this.queueNames = queueNames = queueNames || []; this.options = options; _backburnerUtils.each(queueNames, function (queueName) { queues[queueName] = new _backburnerQueue.default(queueName, options[queueName], options); }); } function noSuchQueue(name) { throw new Error('You attempted to schedule an action in a queue (' + name + ') that doesn\'t exist'); } function noSuchMethod(name) { throw new Error('You attempted to schedule an action in a queue (' + name + ') for a method that doesn\'t exist'); } DeferredActionQueues.prototype = { schedule: function (name, target, method, args, onceFlag, stack) { var queues = this.queues; var queue = queues[name]; if (!queue) { noSuchQueue(name); } if (!method) { noSuchMethod(name); } if (onceFlag) { return queue.pushUnique(target, method, args, stack); } else { return queue.push(target, method, args, stack); } }, flush: function () { var queues = this.queues; var queueNames = this.queueNames; var queueName, queue; var queueNameIndex = 0; var numberOfQueues = queueNames.length; while (queueNameIndex < numberOfQueues) { queueName = queueNames[queueNameIndex]; queue = queues[queueName]; var numberOfQueueItems = queue._queue.length; if (numberOfQueueItems === 0) { queueNameIndex++; } else { queue.flush(false /* async */); queueNameIndex = 0; } } } }; }); enifed('backburner/platform', ['exports'], function (exports) { 'use strict'; var GlobalContext; /* global self */ if (typeof self === 'object') { GlobalContext = self; /* global global */ } else if (typeof global === 'object') { GlobalContext = global; /* global window */ } else if (typeof window === 'object') { GlobalContext = window; } else { throw new Error('no global: `self`, `global` nor `window` was found'); } exports.default = GlobalContext; }); enifed('backburner/queue', ['exports', 'backburner/utils'], function (exports, _backburnerUtils) { 'use strict'; exports.default = Queue; function Queue(name, options, globalOptions) { this.name = name; this.globalOptions = globalOptions || {}; this.options = options; this._queue = []; this.targetQueues = {}; this._queueBeingFlushed = undefined; } Queue.prototype = { push: function (target, method, args, stack) { var queue = this._queue; queue.push(target, method, args, stack); return { queue: this, target: target, method: method }; }, pushUniqueWithoutGuid: function (target, method, args, stack) { var queue = this._queue; for (var i = 0, l = queue.length; i < l; i += 4) { var currentTarget = queue[i]; var currentMethod = queue[i + 1]; if (currentTarget === target && currentMethod === method) { queue[i + 2] = args; // replace args queue[i + 3] = stack; // replace stack return; } } queue.push(target, method, args, stack); }, targetQueue: function (targetQueue, target, method, args, stack) { var queue = this._queue; for (var i = 0, l = targetQueue.length; i < l; i += 2) { var currentMethod = targetQueue[i]; var currentIndex = targetQueue[i + 1]; if (currentMethod === method) { queue[currentIndex + 2] = args; // replace args queue[currentIndex + 3] = stack; // replace stack return; } } targetQueue.push(method, queue.push(target, method, args, stack) - 4); }, pushUniqueWithGuid: function (guid, target, method, args, stack) { var hasLocalQueue = this.targetQueues[guid]; if (hasLocalQueue) { this.targetQueue(hasLocalQueue, target, method, args, stack); } else { this.targetQueues[guid] = [method, this._queue.push(target, method, args, stack) - 4]; } return { queue: this, target: target, method: method }; }, pushUnique: function (target, method, args, stack) { var KEY = this.globalOptions.GUID_KEY; if (target && KEY) { var guid = target[KEY]; if (guid) { return this.pushUniqueWithGuid(guid, target, method, args, stack); } } this.pushUniqueWithoutGuid(target, method, args, stack); return { queue: this, target: target, method: method }; }, invoke: function (target, method, args, _, _errorRecordedForStack) { if (args && args.length > 0) { method.apply(target, args); } else { method.call(target); } }, invokeWithOnError: function (target, method, args, onError, errorRecordedForStack) { try { if (args && args.length > 0) { method.apply(target, args); } else { method.call(target); } } catch (error) { onError(error, errorRecordedForStack); } }, flush: function (sync) { var queue = this._queue; var length = queue.length; if (length === 0) { return; } var globalOptions = this.globalOptions; var options = this.options; var before = options && options.before; var after = options && options.after; var onError = globalOptions.onError || globalOptions.onErrorTarget && globalOptions.onErrorTarget[globalOptions.onErrorMethod]; var target, method, args, errorRecordedForStack; var invoke = onError ? this.invokeWithOnError : this.invoke; this.targetQueues = Object.create(null); var queueItems = this._queueBeingFlushed = this._queue.slice(); this._queue = []; if (before) { before(); } for (var i = 0; i < length; i += 4) { target = queueItems[i]; method = queueItems[i + 1]; args = queueItems[i + 2]; errorRecordedForStack = queueItems[i + 3]; // Debugging assistance if (_backburnerUtils.isString(method)) { method = target[method]; } // method could have been nullified / canceled during flush if (method) { // // ** Attention intrepid developer ** // // To find out the stack of this task when it was scheduled onto // the run loop, add the following to your app.js: // // Ember.run.backburner.DEBUG = true; // NOTE: This slows your app, don't leave it on in production. // // Once that is in place, when you are at a breakpoint and navigate // here in the stack explorer, you can look at `errorRecordedForStack.stack`, // which will be the captured stack when this job was scheduled. // invoke(target, method, args, onError, errorRecordedForStack); } } if (after) { after(); } this._queueBeingFlushed = undefined; if (sync !== false && this._queue.length > 0) { // check if new items have been added this.flush(true); } }, cancel: function (actionToCancel) { var queue = this._queue, currentTarget, currentMethod, i, l; var target = actionToCancel.target; var method = actionToCancel.method; var GUID_KEY = this.globalOptions.GUID_KEY; if (GUID_KEY && this.targetQueues && target) { var targetQueue = this.targetQueues[target[GUID_KEY]]; if (targetQueue) { for (i = 0, l = targetQueue.length; i < l; i++) { if (targetQueue[i] === method) { targetQueue.splice(i, 1); } } } } for (i = 0, l = queue.length; i < l; i += 4) { currentTarget = queue[i]; currentMethod = queue[i + 1]; if (currentTarget === target && currentMethod === method) { queue.splice(i, 4); return true; } } // if not found in current queue // could be in the queue that is being flushed queue = this._queueBeingFlushed; if (!queue) { return; } for (i = 0, l = queue.length; i < l; i += 4) { currentTarget = queue[i]; currentMethod = queue[i + 1]; if (currentTarget === target && currentMethod === method) { // don't mess with array during flush // just nullify the method queue[i + 1] = null; return true; } } } }; }); enifed('backburner/utils', ['exports'], function (exports) { 'use strict'; exports.each = each; exports.isString = isString; exports.isFunction = isFunction; exports.isNumber = isNumber; exports.isCoercableNumber = isCoercableNumber; var NUMBER = /\d+/; function each(collection, callback) { for (var i = 0; i < collection.length; i++) { callback(collection[i]); } } function isString(suspect) { return typeof suspect === 'string'; } function isFunction(suspect) { return typeof suspect === 'function'; } function isNumber(suspect) { return typeof suspect === 'number'; } function isCoercableNumber(number) { return isNumber(number) || NUMBER.test(number); } }); enifed('backburner', ['exports', 'backburner/utils', 'backburner/platform', 'backburner/binary-search', 'backburner/deferred-action-queues'], function (exports, _backburnerUtils, _backburnerPlatform, _backburnerBinarySearch, _backburnerDeferredActionQueues) { 'use strict'; exports.default = Backburner; function Backburner(queueNames, options) { this.queueNames = queueNames; this.options = options || {}; if (!this.options.defaultQueue) { this.options.defaultQueue = queueNames[0]; } this.instanceStack = []; this._debouncees = []; this._throttlers = []; this._eventCallbacks = { end: [], begin: [] }; var _this = this; this._boundClearItems = function () { clearItems(); }; this._timerTimeoutId = undefined; this._timers = []; this._platform = this.options._platform || _backburnerPlatform.default; this._boundRunExpiredTimers = function () { _this._runExpiredTimers(); }; } Backburner.prototype = { begin: function () { var options = this.options; var onBegin = options && options.onBegin; var previousInstance = this.currentInstance; if (previousInstance) { this.instanceStack.push(previousInstance); } this.currentInstance = new _backburnerDeferredActionQueues.default(this.queueNames, options); this._trigger('begin', this.currentInstance, previousInstance); if (onBegin) { onBegin(this.currentInstance, previousInstance); } }, end: function () { var options = this.options; var onEnd = options && options.onEnd; var currentInstance = this.currentInstance; var nextInstance = null; // Prevent double-finally bug in Safari 6.0.2 and iOS 6 // This bug appears to be resolved in Safari 6.0.5 and iOS 7 var finallyAlreadyCalled = false; try { currentInstance.flush(); } finally { if (!finallyAlreadyCalled) { finallyAlreadyCalled = true; this.currentInstance = null; if (this.instanceStack.length) { nextInstance = this.instanceStack.pop(); this.currentInstance = nextInstance; } this._trigger('end', currentInstance, nextInstance); if (onEnd) { onEnd(currentInstance, nextInstance); } } } }, /** Trigger an event. Supports up to two arguments. Designed around triggering transition events from one run loop instance to the next, which requires an argument for the first instance and then an argument for the next instance. @private @method _trigger @param {String} eventName @param {any} arg1 @param {any} arg2 */ _trigger: function (eventName, arg1, arg2) { var callbacks = this._eventCallbacks[eventName]; if (callbacks) { for (var i = 0; i < callbacks.length; i++) { callbacks[i](arg1, arg2); } } }, on: function (eventName, callback) { if (typeof callback !== 'function') { throw new TypeError('Callback must be a function'); } var callbacks = this._eventCallbacks[eventName]; if (callbacks) { callbacks.push(callback); } else { throw new TypeError('Cannot on() event "' + eventName + '" because it does not exist'); } }, off: function (eventName, callback) { if (eventName) { var callbacks = this._eventCallbacks[eventName]; var callbackFound = false; if (!callbacks) return; if (callback) { for (var i = 0; i < callbacks.length; i++) { if (callbacks[i] === callback) { callbackFound = true; callbacks.splice(i, 1); i--; } } } if (!callbackFound) { throw new TypeError('Cannot off() callback that does not exist'); } } else { throw new TypeError('Cannot off() event "' + eventName + '" because it does not exist'); } }, run: function () /* target, method, args */{ var length = arguments.length; var method, target, args; if (length === 1) { method = arguments[0]; target = null; } else { target = arguments[0]; method = arguments[1]; } if (_backburnerUtils.isString(method)) { method = target[method]; } if (length > 2) { args = new Array(length - 2); for (var i = 0, l = length - 2; i < l; i++) { args[i] = arguments[i + 2]; } } else { args = []; } var onError = getOnError(this.options); this.begin(); // guard against Safari 6's double-finally bug var didFinally = false; if (onError) { try { return method.apply(target, args); } catch (error) { onError(error); } finally { if (!didFinally) { didFinally = true; this.end(); } } } else { try { return method.apply(target, args); } finally { if (!didFinally) { didFinally = true; this.end(); } } } }, /* Join the passed method with an existing queue and execute immediately, if there isn't one use `Backburner#run`. The join method is like the run method except that it will schedule into an existing queue if one already exists. In either case, the join method will immediately execute the passed in function and return its result. @method join @param {Object} target @param {Function} method The method to be executed @param {any} args The method arguments @return method result */ join: function () /* target, method, args */{ if (!this.currentInstance) { return this.run.apply(this, arguments); } var length = arguments.length; var method, target; if (length === 1) { method = arguments[0]; target = null; } else { target = arguments[0]; method = arguments[1]; } if (_backburnerUtils.isString(method)) { method = target[method]; } if (length === 1) { return method(); } else if (length === 2) { return method.call(target); } else { var args = new Array(length - 2); for (var i = 0, l = length - 2; i < l; i++) { args[i] = arguments[i + 2]; } return method.apply(target, args); } }, /* Defer the passed function to run inside the specified queue. @method defer @param {String} queueName @param {Object} target @param {Function|String} method The method or method name to be executed @param {any} args The method arguments @return method result */ defer: function (queueName /* , target, method, args */) { var length = arguments.length; var method, target, args; if (length === 2) { method = arguments[1]; target = null; } else { target = arguments[1]; method = arguments[2]; } if (_backburnerUtils.isString(method)) { method = target[method]; } var stack = this.DEBUG ? new Error() : undefined; if (length > 3) { args = new Array(length - 3); for (var i = 3; i < length; i++) { args[i - 3] = arguments[i]; } } else { args = undefined; } if (!this.currentInstance) { createAutorun(this); } return this.currentInstance.schedule(queueName, target, method, args, false, stack); }, deferOnce: function (queueName /* , target, method, args */) { var length = arguments.length; var method, target, args; if (length === 2) { method = arguments[1]; target = null; } else { target = arguments[1]; method = arguments[2]; } if (_backburnerUtils.isString(method)) { method = target[method]; } var stack = this.DEBUG ? new Error() : undefined; if (length > 3) { args = new Array(length - 3); for (var i = 3; i < length; i++) { args[i - 3] = arguments[i]; } } else { args = undefined; } if (!this.currentInstance) { createAutorun(this); } return this.currentInstance.schedule(queueName, target, method, args, true, stack); }, setTimeout: function () { var l = arguments.length; var args = new Array(l); for (var x = 0; x < l; x++) { args[x] = arguments[x]; } var length = args.length, method, wait, target, methodOrTarget, methodOrWait, methodOrArgs; if (length === 0) { return; } else if (length === 1) { method = args.shift(); wait = 0; } else if (length === 2) { methodOrTarget = args[0]; methodOrWait = args[1]; if (_backburnerUtils.isFunction(methodOrWait) || _backburnerUtils.isFunction(methodOrTarget[methodOrWait])) { target = args.shift(); method = args.shift(); wait = 0; } else if (_backburnerUtils.isCoercableNumber(methodOrWait)) { method = args.shift(); wait = args.shift(); } else { method = args.shift(); wait = 0; } } else { var last = args[args.length - 1]; if (_backburnerUtils.isCoercableNumber(last)) { wait = args.pop(); } else { wait = 0; } methodOrTarget = args[0]; methodOrArgs = args[1]; if (_backburnerUtils.isFunction(methodOrArgs) || _backburnerUtils.isString(methodOrArgs) && methodOrTarget !== null && methodOrArgs in methodOrTarget) { target = args.shift(); method = args.shift(); } else { method = args.shift(); } } var executeAt = Date.now() + parseInt(wait, 10); if (_backburnerUtils.isString(method)) { method = target[method]; } var onError = getOnError(this.options); function fn() { if (onError) { try { method.apply(target, args); } catch (e) { onError(e); } } else { method.apply(target, args); } } return this._setTimeout(fn, executeAt); }, _setTimeout: function (fn, executeAt) { if (this._timers.length === 0) { this._timers.push(executeAt, fn); this._installTimerTimeout(); return fn; } // find position to insert var i = _backburnerBinarySearch.default(executeAt, this._timers); this._timers.splice(i, 0, executeAt, fn); // we should be the new earliest timer if i == 0 if (i === 0) { this._reinstallTimerTimeout(); } return fn; }, throttle: function (target, method /* , args, wait, [immediate] */) { var backburner = this; var args = new Array(arguments.length); for (var i = 0; i < arguments.length; i++) { args[i] = arguments[i]; } var immediate = args.pop(); var wait, throttler, index, timer; if (_backburnerUtils.isNumber(immediate) || _backburnerUtils.isString(immediate)) { wait = immediate; immediate = true; } else { wait = args.pop(); } wait = parseInt(wait, 10); index = findThrottler(target, method, this._throttlers); if (index > -1) { return this._throttlers[index]; } // throttled timer = this._platform.setTimeout(function () { if (!immediate) { backburner.run.apply(backburner, args); } var index = findThrottler(target, method, backburner._throttlers); if (index > -1) { backburner._throttlers.splice(index, 1); } }, wait); if (immediate) { this.run.apply(this, args); } throttler = [target, method, timer]; this._throttlers.push(throttler); return throttler; }, debounce: function (target, method /* , args, wait, [immediate] */) { var backburner = this; var args = new Array(arguments.length); for (var i = 0; i < arguments.length; i++) { args[i] = arguments[i]; } var immediate = args.pop(); var wait, index, debouncee, timer; if (_backburnerUtils.isNumber(immediate) || _backburnerUtils.isString(immediate)) { wait = immediate; immediate = false; } else { wait = args.pop(); } wait = parseInt(wait, 10); // Remove debouncee index = findDebouncee(target, method, this._debouncees); if (index > -1) { debouncee = this._debouncees[index]; this._debouncees.splice(index, 1); this._platform.clearTimeout(debouncee[2]); } timer = this._platform.setTimeout(function () { if (!immediate) { backburner.run.apply(backburner, args); } var index = findDebouncee(target, method, backburner._debouncees); if (index > -1) { backburner._debouncees.splice(index, 1); } }, wait); if (immediate && index === -1) { backburner.run.apply(backburner, args); } debouncee = [target, method, timer]; backburner._debouncees.push(debouncee); return debouncee; }, cancelTimers: function () { _backburnerUtils.each(this._throttlers, this._boundClearItems); this._throttlers = []; _backburnerUtils.each(this._debouncees, this._boundClearItems); this._debouncees = []; this._clearTimerTimeout(); this._timers = []; if (this._autorun) { this._platform.clearTimeout(this._autorun); this._autorun = null; } }, hasTimers: function () { return !!this._timers.length || !!this._debouncees.length || !!this._throttlers.length || this._autorun; }, cancel: function (timer) { var timerType = typeof timer; if (timer && timerType === 'object' && timer.queue && timer.method) { // we're cancelling a deferOnce return timer.queue.cancel(timer); } else if (timerType === 'function') { // we're cancelling a setTimeout for (var i = 0, l = this._timers.length; i < l; i += 2) { if (this._timers[i + 1] === timer) { this._timers.splice(i, 2); // remove the two elements if (i === 0) { this._reinstallTimerTimeout(); } return true; } } } else if (Object.prototype.toString.call(timer) === '[object Array]') { // we're cancelling a throttle or debounce return this._cancelItem(findThrottler, this._throttlers, timer) || this._cancelItem(findDebouncee, this._debouncees, timer); } else { return; // timer was null or not a timer } }, _cancelItem: function (findMethod, array, timer) { var item, index; if (timer.length < 3) { return false; } index = findMethod(timer[0], timer[1], array); if (index > -1) { item = array[index]; if (item[2] === timer[2]) { array.splice(index, 1); this._platform.clearTimeout(timer[2]); return true; } } return false; }, _runExpiredTimers: function () { this._timerTimeoutId = undefined; this.run(this, this._scheduleExpiredTimers); }, _scheduleExpiredTimers: function () { var n = Date.now(); var timers = this._timers; var i = 0; var l = timers.length; for (; i < l; i += 2) { var executeAt = timers[i]; var fn = timers[i + 1]; if (executeAt <= n) { this.schedule(this.options.defaultQueue, null, fn); } else { break; } } timers.splice(0, i); this._installTimerTimeout(); }, _reinstallTimerTimeout: function () { this._clearTimerTimeout(); this._installTimerTimeout(); }, _clearTimerTimeout: function () { if (!this._timerTimeoutId) { return; } this._platform.clearTimeout(this._timerTimeoutId); this._timerTimeoutId = undefined; }, _installTimerTimeout: function () { if (!this._timers.length) { return; } var minExpiresAt = this._timers[0]; var n = Date.now(); var wait = Math.max(0, minExpiresAt - n); this._timerTimeoutId = this._platform.setTimeout(this._boundRunExpiredTimers, wait); } }; Backburner.prototype.schedule = Backburner.prototype.defer; Backburner.prototype.scheduleOnce = Backburner.prototype.deferOnce; Backburner.prototype.later = Backburner.prototype.setTimeout; function getOnError(options) { return options.onError || options.onErrorTarget && options.onErrorTarget[options.onErrorMethod]; } function createAutorun(backburner) { backburner.begin(); backburner._autorun = backburner._platform.setTimeout(function () { backburner._autorun = null; backburner.end(); }); } function findDebouncee(target, method, debouncees) { return findItem(target, method, debouncees); } function findThrottler(target, method, throttlers) { return findItem(target, method, throttlers); } function findItem(target, method, collection) { var item; var index = -1; for (var i = 0, l = collection.length; i < l; i++) { item = collection[i]; if (item[0] === target && item[1] === method) { index = i; break; } } return index; } function clearItems(item) { this._platform.clearTimeout(item[2]); } }); enifed('container/container', ['exports', 'ember-metal/core', 'ember-metal/debug', 'ember-metal/dictionary', 'ember-metal/features', 'container/owner', 'ember-runtime/mixins/container_proxy', 'ember-metal/symbol'], function (exports, _emberMetalCore, _emberMetalDebug, _emberMetalDictionary, _emberMetalFeatures, _containerOwner, _emberRuntimeMixinsContainer_proxy, _emberMetalSymbol) { 'use strict'; var CONTAINER_OVERRIDE = _emberMetalSymbol.default('CONTAINER_OVERRIDE'); /** A container used to instantiate and cache objects. Every `Container` must be associated with a `Registry`, which is referenced to determine the factory and options that should be used to instantiate objects. The public API for `Container` is still in flux and should not be considered stable. @private @class Container */ function Container(registry, options) { this.registry = registry; this.owner = options && options.owner ? options.owner : null; this.cache = _emberMetalDictionary.default(options && options.cache ? options.cache : null); this.factoryCache = _emberMetalDictionary.default(options && options.factoryCache ? options.factoryCache : null); this.validationCache = _emberMetalDictionary.default(options && options.validationCache ? options.validationCache : null); this._fakeContainerToInject = _emberRuntimeMixinsContainer_proxy.buildFakeContainerWithDeprecations(this); this[CONTAINER_OVERRIDE] = undefined; } Container.prototype = { /** @private @property owner @type Object */ owner: null, /** @private @property registry @type Registry @since 1.11.0 */ registry: null, /** @private @property cache @type InheritingDict */ cache: null, /** @private @property factoryCache @type InheritingDict */ factoryCache: null, /** @private @property validationCache @type InheritingDict */ validationCache: null, /** Given a fullName return a corresponding instance. The default behaviour is for lookup to return a singleton instance. The singleton is scoped to the container, allowing multiple containers to all have their own locally scoped singletons. ```javascript var registry = new Registry(); var container = registry.container(); registry.register('api:twitter', Twitter); var twitter = container.lookup('api:twitter'); twitter instanceof Twitter; // => true // by default the container will return singletons var twitter2 = container.lookup('api:twitter'); twitter2 instanceof Twitter; // => true twitter === twitter2; //=> true ``` If singletons are not wanted, an optional flag can be provided at lookup. ```javascript var registry = new Registry(); var container = registry.container(); registry.register('api:twitter', Twitter); var twitter = container.lookup('api:twitter', { singleton: false }); var twitter2 = container.lookup('api:twitter', { singleton: false }); twitter === twitter2; //=> false ``` @private @method lookup @param {String} fullName @param {Object} [options] @param {String} [options.source] The fullname of the request source (used for local lookup) @return {any} */ lookup: function (fullName, options) { return lookup(this, this.registry.normalize(fullName), options); }, /** Given a fullName, return the corresponding factory. @private @method lookupFactory @param {String} fullName @param {Object} [options] @param {String} [options.source] The fullname of the request source (used for local lookup) @return {any} */ lookupFactory: function (fullName, options) { return factoryFor(this, this.registry.normalize(fullName), options); }, /** A depth first traversal, destroying the container, its descendant containers and all their managed objects. @private @method destroy */ destroy: function () { eachDestroyable(this, function (item) { if (item.destroy) { item.destroy(); } }); this.isDestroyed = true; }, /** Clear either the entire cache or just the cache for a particular key. @private @method reset @param {String} fullName optional key to reset; if missing, resets everything */ reset: function (fullName) { if (arguments.length > 0) { resetMember(this, this.registry.normalize(fullName)); } else { resetCache(this); } }, /** Returns an object that can be used to provide an owner to a manually created instance. @private @method ownerInjection @returns { Object } */ ownerInjection: function () { var _ref; return _ref = {}, _ref[_containerOwner.OWNER] = this.owner, _ref; } }; function isSingleton(container, fullName) { return container.registry.getOption(fullName, 'singleton') !== false; } function lookup(container, fullName) { var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; if (options.source) { fullName = container.registry.expandLocalLookup(fullName, options); // if expandLocalLookup returns falsey, we do not support local lookup if (!fullName) { return; } } if (container.cache[fullName] !== undefined && options.singleton !== false) { return container.cache[fullName]; } var value = instantiate(container, fullName); if (value === undefined) { return; } if (isSingleton(container, fullName) && options.singleton !== false) { container.cache[fullName] = value; } return value; } function markInjectionsAsDynamic(injections) { injections._dynamic = true; } function areInjectionsDynamic(injections) { return !!injections._dynamic; } function buildInjections() /* container, ...injections */{ var hash = {}; if (arguments.length > 1) { var container = arguments[0]; var injections = []; var injection; for (var i = 1, l = arguments.length; i < l; i++) { if (arguments[i]) { injections = injections.concat(arguments[i]); } } container.registry.validateInjections(injections); for (i = 0, l = injections.length; i < l; i++) { injection = injections[i]; hash[injection.property] = lookup(container, injection.fullName); if (!isSingleton(container, injection.fullName)) { markInjectionsAsDynamic(hash); } } } return hash; } function factoryFor(container, fullName) { var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; var registry = container.registry; if (options.source) { fullName = registry.expandLocalLookup(fullName, options); // if expandLocalLookup returns falsey, we do not support local lookup if (!fullName) { return; } } var cache = container.factoryCache; if (cache[fullName]) { return cache[fullName]; } var factory = registry.resolve(fullName); if (factory === undefined) { return; } var type = fullName.split(':')[0]; if (!factory || typeof factory.extend !== 'function' || !_emberMetalCore.default.MODEL_FACTORY_INJECTIONS && type === 'model') { if (factory && typeof factory._onLookup === 'function') { factory._onLookup(fullName); } // TODO: think about a 'safe' merge style extension // for now just fallback to create time injection cache[fullName] = factory; return factory; } else { var injections = injectionsFor(container, fullName); var factoryInjections = factoryInjectionsFor(container, fullName); var cacheable = !areInjectionsDynamic(injections) && !areInjectionsDynamic(factoryInjections); factoryInjections._toString = registry.makeToString(factory, fullName); var injectedFactory = factory.extend(injections); // TODO - remove all `container` injections when Ember reaches v3.0.0 injectDeprecatedContainer(injectedFactory.prototype, container); injectedFactory.reopenClass(factoryInjections); if (factory && typeof factory._onLookup === 'function') { factory._onLookup(fullName); } if (cacheable) { cache[fullName] = injectedFactory; } return injectedFactory; } } function injectionsFor(container, fullName) { var registry = container.registry; var splitName = fullName.split(':'); var type = splitName[0]; var injections = buildInjections(container, registry.getTypeInjections(type), registry.getInjections(fullName)); injections._debugContainerKey = fullName; _containerOwner.setOwner(injections, container.owner); return injections; } function factoryInjectionsFor(container, fullName) { var registry = container.registry; var splitName = fullName.split(':'); var type = splitName[0]; var factoryInjections = buildInjections(container, registry.getFactoryTypeInjections(type), registry.getFactoryInjections(fullName)); factoryInjections._debugContainerKey = fullName; return factoryInjections; } function instantiate(container, fullName) { var factory = factoryFor(container, fullName); var lazyInjections, validationCache; if (container.registry.getOption(fullName, 'instantiate') === false) { return factory; } if (factory) { if (typeof factory.create !== 'function') { throw new Error('Failed to create an instance of \'' + fullName + '\'. ' + 'Most likely an improperly defined class or an invalid module export.'); } validationCache = container.validationCache; // Ensure that all lazy injections are valid at instantiation time if (!validationCache[fullName] && typeof factory._lazyInjections === 'function') { lazyInjections = factory._lazyInjections(); lazyInjections = container.registry.normalizeInjectionsHash(lazyInjections); container.registry.validateInjections(lazyInjections); } validationCache[fullName] = true; var obj = undefined; if (typeof factory.extend === 'function') { // assume the factory was extendable and is already injected obj = factory.create(); } else { // assume the factory was extendable // to create time injections // TODO: support new'ing for instantiation and merge injections for pure JS Functions var injections = injectionsFor(container, fullName); // Ensure that a container is available to an object during instantiation. // TODO - remove when Ember reaches v3.0.0 // This "fake" container will be replaced after instantiation with a // property that raises deprecations every time it is accessed. injections.container = container._fakeContainerToInject; obj = factory.create(injections); // TODO - remove when Ember reaches v3.0.0 if (!Object.isFrozen(obj) && 'container' in obj) { injectDeprecatedContainer(obj, container); } } return obj; } } // TODO - remove when Ember reaches v3.0.0 function injectDeprecatedContainer(object, container) { Object.defineProperty(object, 'container', { configurable: true, enumerable: false, get: function () { return this[CONTAINER_OVERRIDE] || container; }, set: function (value) { this[CONTAINER_OVERRIDE] = value; return value; } }); } function eachDestroyable(container, callback) { var cache = container.cache; var keys = Object.keys(cache); var key, value; for (var i = 0, l = keys.length; i < l; i++) { key = keys[i]; value = cache[key]; if (container.registry.getOption(key, 'instantiate') !== false) { callback(value); } } } function resetCache(container) { eachDestroyable(container, function (value) { if (value.destroy) { value.destroy(); } }); container.cache.dict = _emberMetalDictionary.default(null); } function resetMember(container, fullName) { var member = container.cache[fullName]; delete container.factoryCache[fullName]; if (member) { delete container.cache[fullName]; if (member.destroy) { member.destroy(); } } } exports.default = Container; }); enifed('container/index', ['exports', 'ember-metal/core', 'container/registry', 'container/container', 'container/owner'], function (exports, _emberMetalCore, _containerRegistry, _containerContainer, _containerOwner) { 'use strict'; /* Public API for the container is still in flux. The public API, specified on the application namespace should be considered the stable API. // @module container @private */ /* Flag to enable/disable model factory injections (disabled by default). If model factory injections are enabled, models should not be accessed globally (only through `container.lookupFactory('model:modelName'))`); */ _emberMetalCore.default.MODEL_FACTORY_INJECTIONS = false; if (_emberMetalCore.default.ENV && typeof _emberMetalCore.default.ENV.MODEL_FACTORY_INJECTIONS !== 'undefined') { _emberMetalCore.default.MODEL_FACTORY_INJECTIONS = !!_emberMetalCore.default.ENV.MODEL_FACTORY_INJECTIONS; } exports.Registry = _containerRegistry.default; exports.Container = _containerContainer.default; exports.getOwner = _containerOwner.getOwner; exports.setOwner = _containerOwner.setOwner; }); enifed('container/owner', ['exports', 'ember-metal/symbol'], function (exports, _emberMetalSymbol) { /** @module ember @submodule ember-runtime */ 'use strict'; exports.getOwner = getOwner; exports.setOwner = setOwner; var OWNER = _emberMetalSymbol.default('OWNER'); exports.OWNER = OWNER; /** Framework objects in an Ember application (components, services, routes, etc.) are created via a factory and dependency injection system. Each of these objects is the responsibility of an "owner", which handled its instantiation and manages its lifetime. `getOwner` fetches the owner object responsible for an instance. This can be used to lookup or resolve other class instances, or register new factories into the owner. For example, this component dynamically looks up a service based on the `audioType` passed as an attribute: ``` // app/components/play-audio.js import Ember from 'ember'; // Usage: // // {{play-audio audioType=model.audioType audioFile=model.file}} // export default Ember.Component.extend({ audioService: Ember.computed('audioType', function() { let owner = Ember.getOwner(this); return owner.lookup(`service:${this.get('audioType')}`); }), click() { let player = this.get('audioService'); player.play(this.get('audioFile')); } }); ``` @method getOwner @param {Object} object An object with an owner. @return {Object} An owner object. @for Ember @public */ function getOwner(object) { return object[OWNER]; } /** `setOwner` forces a new owner on a given object instance. This is primarily useful in some testing cases. @method setOwner @param {Object} object An object with an owner. @return {Object} An owner object. @for Ember @public */ function setOwner(object, owner) { object[OWNER] = owner; } }); enifed('container/registry', ['exports', 'ember-metal/features', 'ember-metal/debug', 'ember-metal/dictionary', 'ember-metal/empty_object', 'ember-metal/assign', 'container/container'], function (exports, _emberMetalFeatures, _emberMetalDebug, _emberMetalDictionary, _emberMetalEmpty_object, _emberMetalAssign, _containerContainer) { 'use strict'; var VALID_FULL_NAME_REGEXP = /^[^:]+.+:[^:]+$/; /** A registry used to store factory and option information keyed by type. A `Registry` stores the factory and option information needed by a `Container` to instantiate and cache objects. The API for `Registry` is still in flux and should not be considered stable. @private @class Registry @since 1.11.0 */ function Registry(options) { this.fallback = options && options.fallback ? options.fallback : null; if (options && options.resolver) { this.resolver = options.resolver; if (typeof this.resolver === 'function') { deprecateResolverFunction(this); } } this.registrations = _emberMetalDictionary.default(options && options.registrations ? options.registrations : null); this._typeInjections = _emberMetalDictionary.default(null); this._injections = _emberMetalDictionary.default(null); this._factoryTypeInjections = _emberMetalDictionary.default(null); this._factoryInjections = _emberMetalDictionary.default(null); this._localLookupCache = new _emberMetalEmpty_object.default(); this._normalizeCache = _emberMetalDictionary.default(null); this._resolveCache = _emberMetalDictionary.default(null); this._failCache = _emberMetalDictionary.default(null); this._options = _emberMetalDictionary.default(null); this._typeOptions = _emberMetalDictionary.default(null); } Registry.prototype = { /** A backup registry for resolving registrations when no matches can be found. @private @property fallback @type Registry */ fallback: null, /** An object that has a `resolve` method that resolves a name. @private @property resolver @type Resolver */ resolver: null, /** @private @property registrations @type InheritingDict */ registrations: null, /** @private @property _typeInjections @type InheritingDict */ _typeInjections: null, /** @private @property _injections @type InheritingDict */ _injections: null, /** @private @property _factoryTypeInjections @type InheritingDict */ _factoryTypeInjections: null, /** @private @property _factoryInjections @type InheritingDict */ _factoryInjections: null, /** @private @property _normalizeCache @type InheritingDict */ _normalizeCache: null, /** @private @property _resolveCache @type InheritingDict */ _resolveCache: null, /** @private @property _options @type InheritingDict */ _options: null, /** @private @property _typeOptions @type InheritingDict */ _typeOptions: null, /** Creates a container based on this registry. @private @method container @param {Object} options @return {Container} created container */ container: function (options) { return new _containerContainer.default(this, options); }, /** Registers a factory for later injection. Example: ```javascript var registry = new Registry(); registry.register('model:user', Person, {singleton: false }); registry.register('fruit:favorite', Orange); registry.register('communication:main', Email, {singleton: false}); ``` @private @method register @param {String} fullName @param {Function} factory @param {Object} options */ register: function (fullName, factory) { var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; if (factory === undefined) { throw new TypeError('Attempting to register an unknown factory: `' + fullName + '`'); } var normalizedName = this.normalize(fullName); if (this._resolveCache[normalizedName]) { throw new Error('Cannot re-register: `' + fullName + '`, as it has already been resolved.'); } delete this._failCache[normalizedName]; this.registrations[normalizedName] = factory; this._options[normalizedName] = options; }, /** Unregister a fullName ```javascript var registry = new Registry(); registry.register('model:user', User); registry.resolve('model:user').create() instanceof User //=> true registry.unregister('model:user') registry.resolve('model:user') === undefined //=> true ``` @private @method unregister @param {String} fullName */ unregister: function (fullName) { var normalizedName = this.normalize(fullName); this._localLookupCache = new _emberMetalEmpty_object.default(); delete this.registrations[normalizedName]; delete this._resolveCache[normalizedName]; delete this._failCache[normalizedName]; delete this._options[normalizedName]; }, /** Given a fullName return the corresponding factory. By default `resolve` will retrieve the factory from the registry. ```javascript var registry = new Registry(); registry.register('api:twitter', Twitter); registry.resolve('api:twitter') // => Twitter ``` Optionally the registry can be provided with a custom resolver. If provided, `resolve` will first provide the custom resolver the opportunity to resolve the fullName, otherwise it will fallback to the registry. ```javascript var registry = new Registry(); registry.resolver = function(fullName) { // lookup via the module system of choice }; // the twitter factory is added to the module system registry.resolve('api:twitter') // => Twitter ``` @private @method resolve @param {String} fullName @param {Object} [options] @param {String} [options.source] the fullname of the request source (used for local lookups) @return {Function} fullName's factory */ resolve: function (fullName, options) { var factory = resolve(this, this.normalize(fullName), options); if (factory === undefined && this.fallback) { var _fallback; factory = (_fallback = this.fallback).resolve.apply(_fallback, arguments); } return factory; }, /** A hook that can be used to describe how the resolver will attempt to find the factory. For example, the default Ember `.describe` returns the full class name (including namespace) where Ember's resolver expects to find the `fullName`. @private @method describe @param {String} fullName @return {string} described fullName */ describe: function (fullName) { if (this.resolver && this.resolver.lookupDescription) { return this.resolver.lookupDescription(fullName); } else if (this.fallback) { return this.fallback.describe(fullName); } else { return fullName; } }, /** A hook to enable custom fullName normalization behaviour @private @method normalizeFullName @param {String} fullName @return {string} normalized fullName */ normalizeFullName: function (fullName) { if (this.resolver && this.resolver.normalize) { return this.resolver.normalize(fullName); } else if (this.fallback) { return this.fallback.normalizeFullName(fullName); } else { return fullName; } }, /** Normalize a fullName based on the application's conventions @private @method normalize @param {String} fullName @return {string} normalized fullName */ normalize: function (fullName) { return this._normalizeCache[fullName] || (this._normalizeCache[fullName] = this.normalizeFullName(fullName)); }, /** @method makeToString @private @param {any} factory @param {string} fullName @return {function} toString function */ makeToString: function (factory, fullName) { if (this.resolver && this.resolver.makeToString) { return this.resolver.makeToString(factory, fullName); } else if (this.fallback) { return this.fallback.makeToString(factory, fullName); } else { return factory.toString(); } }, /** Given a fullName check if the container is aware of its factory or singleton instance. @private @method has @param {String} fullName @param {Object} [options] @param {String} [options.source] the fullname of the request source (used for local lookups) @return {Boolean} */ has: function (fullName, options) { var source = undefined; source = options && options.source && this.normalize(options.source); return has(this, this.normalize(fullName), source); }, /** Allow registering options for all factories of a type. ```javascript var registry = new Registry(); var container = registry.container(); // if all of type `connection` must not be singletons registry.optionsForType('connection', { singleton: false }); registry.register('connection:twitter', TwitterConnection); registry.register('connection:facebook', FacebookConnection); var twitter = container.lookup('connection:twitter'); var twitter2 = container.lookup('connection:twitter'); twitter === twitter2; // => false var facebook = container.lookup('connection:facebook'); var facebook2 = container.lookup('connection:facebook'); facebook === facebook2; // => false ``` @private @method optionsForType @param {String} type @param {Object} options */ optionsForType: function (type, options) { this._typeOptions[type] = options; }, getOptionsForType: function (type) { var optionsForType = this._typeOptions[type]; if (optionsForType === undefined && this.fallback) { optionsForType = this.fallback.getOptionsForType(type); } return optionsForType; }, /** @private @method options @param {String} fullName @param {Object} options */ options: function (fullName) { var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; var normalizedName = this.normalize(fullName); this._options[normalizedName] = options; }, getOptions: function (fullName) { var normalizedName = this.normalize(fullName); var options = this._options[normalizedName]; if (options === undefined && this.fallback) { options = this.fallback.getOptions(fullName); } return options; }, getOption: function (fullName, optionName) { var options = this._options[fullName]; if (options && options[optionName] !== undefined) { return options[optionName]; } var type = fullName.split(':')[0]; options = this._typeOptions[type]; if (options && options[optionName] !== undefined) { return options[optionName]; } else if (this.fallback) { return this.fallback.getOption(fullName, optionName); } }, /** Used only via `injection`. Provides a specialized form of injection, specifically enabling all objects of one type to be injected with a reference to another object. For example, provided each object of type `controller` needed a `router`. one would do the following: ```javascript var registry = new Registry(); var container = registry.container(); registry.register('router:main', Router); registry.register('controller:user', UserController); registry.register('controller:post', PostController); registry.typeInjection('controller', 'router', 'router:main'); var user = container.lookup('controller:user'); var post = container.lookup('controller:post'); user.router instanceof Router; //=> true post.router instanceof Router; //=> true // both controllers share the same router user.router === post.router; //=> true ``` @private @method typeInjection @param {String} type @param {String} property @param {String} fullName */ typeInjection: function (type, property, fullName) { var fullNameType = fullName.split(':')[0]; if (fullNameType === type) { throw new Error('Cannot inject a `' + fullName + '` on other ' + type + '(s).'); } var injections = this._typeInjections[type] || (this._typeInjections[type] = []); injections.push({ property: property, fullName: fullName }); }, /** Defines injection rules. These rules are used to inject dependencies onto objects when they are instantiated. Two forms of injections are possible: * Injecting one fullName on another fullName * Injecting one fullName on a type Example: ```javascript var registry = new Registry(); var container = registry.container(); registry.register('source:main', Source); registry.register('model:user', User); registry.register('model:post', Post); // injecting one fullName on another fullName // eg. each user model gets a post model registry.injection('model:user', 'post', 'model:post'); // injecting one fullName on another type registry.injection('model', 'source', 'source:main'); var user = container.lookup('model:user'); var post = container.lookup('model:post'); user.source instanceof Source; //=> true post.source instanceof Source; //=> true user.post instanceof Post; //=> true // and both models share the same source user.source === post.source; //=> true ``` @private @method injection @param {String} factoryName @param {String} property @param {String} injectionName */ injection: function (fullName, property, injectionName) { this.validateFullName(injectionName); var normalizedInjectionName = this.normalize(injectionName); if (fullName.indexOf(':') === -1) { return this.typeInjection(fullName, property, normalizedInjectionName); } var normalizedName = this.normalize(fullName); var injections = this._injections[normalizedName] || (this._injections[normalizedName] = []); injections.push({ property: property, fullName: normalizedInjectionName }); }, /** Used only via `factoryInjection`. Provides a specialized form of injection, specifically enabling all factory of one type to be injected with a reference to another object. For example, provided each factory of type `model` needed a `store`. one would do the following: ```javascript var registry = new Registry(); registry.register('store:main', SomeStore); registry.factoryTypeInjection('model', 'store', 'store:main'); var store = registry.lookup('store:main'); var UserFactory = registry.lookupFactory('model:user'); UserFactory.store instanceof SomeStore; //=> true ``` @private @method factoryTypeInjection @param {String} type @param {String} property @param {String} fullName */ factoryTypeInjection: function (type, property, fullName) { var injections = this._factoryTypeInjections[type] || (this._factoryTypeInjections[type] = []); injections.push({ property: property, fullName: this.normalize(fullName) }); }, /** Defines factory injection rules. Similar to regular injection rules, but are run against factories, via `Registry#lookupFactory`. These rules are used to inject objects onto factories when they are looked up. Two forms of injections are possible: * Injecting one fullName on another fullName * Injecting one fullName on a type Example: ```javascript var registry = new Registry(); var container = registry.container(); registry.register('store:main', Store); registry.register('store:secondary', OtherStore); registry.register('model:user', User); registry.register('model:post', Post); // injecting one fullName on another type registry.factoryInjection('model', 'store', 'store:main'); // injecting one fullName on another fullName registry.factoryInjection('model:post', 'secondaryStore', 'store:secondary'); var UserFactory = container.lookupFactory('model:user'); var PostFactory = container.lookupFactory('model:post'); var store = container.lookup('store:main'); UserFactory.store instanceof Store; //=> true UserFactory.secondaryStore instanceof OtherStore; //=> false PostFactory.store instanceof Store; //=> true PostFactory.secondaryStore instanceof OtherStore; //=> true // and both models share the same source instance UserFactory.store === PostFactory.store; //=> true ``` @private @method factoryInjection @param {String} factoryName @param {String} property @param {String} injectionName */ factoryInjection: function (fullName, property, injectionName) { var normalizedName = this.normalize(fullName); var normalizedInjectionName = this.normalize(injectionName); this.validateFullName(injectionName); if (fullName.indexOf(':') === -1) { return this.factoryTypeInjection(normalizedName, property, normalizedInjectionName); } var injections = this._factoryInjections[normalizedName] || (this._factoryInjections[normalizedName] = []); injections.push({ property: property, fullName: normalizedInjectionName }); }, /** @private @method knownForType @param {String} type the type to iterate over */ knownForType: function (type) { var fallbackKnown = undefined, resolverKnown = undefined; var localKnown = _emberMetalDictionary.default(null); var registeredNames = Object.keys(this.registrations); for (var index = 0, _length = registeredNames.length; index < _length; index++) { var fullName = registeredNames[index]; var itemType = fullName.split(':')[0]; if (itemType === type) { localKnown[fullName] = true; } } if (this.fallback) { fallbackKnown = this.fallback.knownForType(type); } if (this.resolver && this.resolver.knownForType) { resolverKnown = this.resolver.knownForType(type); } return _emberMetalAssign.default({}, fallbackKnown, localKnown, resolverKnown); }, validateFullName: function (fullName) { if (!VALID_FULL_NAME_REGEXP.test(fullName)) { throw new TypeError('Invalid Fullname, expected: `type:name` got: ' + fullName); } return true; }, validateInjections: function (injections) { if (!injections) { return; } var fullName; for (var i = 0, length = injections.length; i < length; i++) { fullName = injections[i].fullName; if (!this.has(fullName)) { throw new Error('Attempting to inject an unknown injection: `' + fullName + '`'); } } }, normalizeInjectionsHash: function (hash) { var injections = []; for (var key in hash) { if (hash.hasOwnProperty(key)) { injections.push({ property: key, fullName: hash[key] }); } } return injections; }, getInjections: function (fullName) { var injections = this._injections[fullName] || []; if (this.fallback) { injections = injections.concat(this.fallback.getInjections(fullName)); } return injections; }, getTypeInjections: function (type) { var injections = this._typeInjections[type] || []; if (this.fallback) { injections = injections.concat(this.fallback.getTypeInjections(type)); } return injections; }, getFactoryInjections: function (fullName) { var injections = this._factoryInjections[fullName] || []; if (this.fallback) { injections = injections.concat(this.fallback.getFactoryInjections(fullName)); } return injections; }, getFactoryTypeInjections: function (type) { var injections = this._factoryTypeInjections[type] || []; if (this.fallback) { injections = injections.concat(this.fallback.getFactoryTypeInjections(type)); } return injections; } }; function deprecateResolverFunction(registry) { registry.resolver = { resolve: registry.resolver }; } /** Given a fullName and a source fullName returns the fully resolved fullName. Used to allow for local lookup. ```javascript var registry = new Registry(); // the twitter factory is added to the module system registry.expandLocalLookup('component:post-title', { source: 'template:post' }) // => component:post/post-title ``` @private @method expandLocalLookup @param {String} fullName @param {Object} [options] @param {String} [options.source] the fullname of the request source (used for local lookups) @return {String} fullName */ Registry.prototype.expandLocalLookup = function Registry_expandLocalLookup(fullName, options) { if (this.resolver && this.resolver.expandLocalLookup) { var normalizedFullName = this.normalize(fullName); var normalizedSource = this.normalize(options.source); return expandLocalLookup(this, normalizedFullName, normalizedSource); } else if (this.fallback) { return this.fallback.expandLocalLookup(fullName, options); } else { return null; } }; function expandLocalLookup(registry, normalizedName, normalizedSource) { var cache = registry._localLookupCache; var normalizedNameCache = cache[normalizedName]; if (!normalizedNameCache) { normalizedNameCache = cache[normalizedName] = new _emberMetalEmpty_object.default(); } var cached = normalizedNameCache[normalizedSource]; if (cached !== undefined) { return cached; } var expanded = registry.resolver.expandLocalLookup(normalizedName, normalizedSource); return normalizedNameCache[normalizedSource] = expanded; } function resolve(registry, normalizedName, options) { if (options && options.source) { // when `source` is provided expand normalizedName // and source into the full normalizedName normalizedName = registry.expandLocalLookup(normalizedName, options); // if expandLocalLookup returns falsey, we do not support local lookup if (!normalizedName) { return; } } var cached = registry._resolveCache[normalizedName]; if (cached !== undefined) { return cached; } if (registry._failCache[normalizedName]) { return; } var resolved = undefined; if (registry.resolver) { resolved = registry.resolver.resolve(normalizedName); } if (resolved === undefined) { resolved = registry.registrations[normalizedName]; } if (resolved === undefined) { registry._failCache[normalizedName] = true; } else { registry._resolveCache[normalizedName] = resolved; } return resolved; } function has(registry, fullName, source) { return registry.resolve(fullName, { source: source }) !== undefined; } exports.default = Registry; }); enifed('dag-map/platform', ['exports'], function (exports) { 'use strict'; var platform; /* global self */ if (typeof self === 'object') { platform = self; /* global global */ } else if (typeof global === 'object') { platform = global; } else { throw new Error('no global: `self` or `global` found'); } exports.default = platform; }); enifed('dag-map', ['exports', 'vertex', 'visit'], function (exports, _vertex, _visit) { 'use strict'; exports.default = DAG; /** * DAG stands for Directed acyclic graph. * * It is used to build a graph of dependencies checking that there isn't circular * dependencies. p.e Registering initializers with a certain precedence order. * * @class DAG * @constructor */ function DAG() { this.names = []; this.vertices = Object.create(null); } /** * Adds a vertex entry to the graph unless it is already added. * * @private * @method add * @param {String} name The name of the vertex to add */ DAG.prototype.add = function (name) { if (!name) { throw new Error("Can't add Vertex without name"); } if (this.vertices[name] !== undefined) { return this.vertices[name]; } var vertex = new _vertex.default(name); this.vertices[name] = vertex; this.names.push(name); return vertex; }; /** * Adds a vertex to the graph and sets its value. * * @private * @method map * @param {String} name The name of the vertex. * @param value The value to put in the vertex. */ DAG.prototype.map = function (name, value) { this.add(name).value = value; }; /** * Connects the vertices with the given names, adding them to the graph if * necessary, only if this does not produce is any circular dependency. * * @private * @method addEdge * @param {String} fromName The name the vertex where the edge starts. * @param {String} toName The name the vertex where the edge ends. */ DAG.prototype.addEdge = function (fromName, toName) { if (!fromName || !toName || fromName === toName) { return; } var from = this.add(fromName); var to = this.add(toName); if (to.incoming.hasOwnProperty(fromName)) { return; } function checkCycle(vertex, path) { if (vertex.name === toName) { throw new Error("cycle detected: " + toName + " <- " + path.join(" <- ")); } } _visit.default(from, checkCycle); from.hasOutgoing = true; to.incoming[fromName] = from; to.incomingNames.push(fromName); }; /** * Visits all the vertex of the graph calling the given function with each one, * ensuring that the vertices are visited respecting their precedence. * * @method topsort * @param {Function} fn The function to be invoked on each vertex. */ DAG.prototype.topsort = function (fn) { var visited = {}; var vertices = this.vertices; var names = this.names; var len = names.length; var i, vertex; for (i = 0; i < len; i++) { vertex = vertices[names[i]]; if (!vertex.hasOutgoing) { _visit.default(vertex, fn, visited); } } }; /** * Adds a vertex with the given name and value to the graph and joins it with the * vertices referenced in _before_ and _after_. If there isn't vertices with those * names, they are added too. * * If either _before_ or _after_ are falsy/empty, the added vertex will not have * an incoming/outgoing edge. * * @method addEdges * @param {String} name The name of the vertex to be added. * @param value The value of that vertex. * @param before An string or array of strings with the names of the vertices before * which this vertex must be visited. * @param after An string or array of strings with the names of the vertex after * which this vertex must be visited. * */ DAG.prototype.addEdges = function (name, value, before, after) { var i; this.map(name, value); if (before) { if (typeof before === 'string') { this.addEdge(name, before); } else { for (i = 0; i < before.length; i++) { this.addEdge(name, before[i]); } } } if (after) { if (typeof after === 'string') { this.addEdge(after, name); } else { for (i = 0; i < after.length; i++) { this.addEdge(after[i], name); } } } }; }); enifed('dag-map.umd', ['exports', 'dag-map/platform', 'dag-map'], function (exports, _dagMapPlatform, _dagMap) { 'use strict'; /* global define:true module:true window: true */ if (typeof define === 'function' && define.amd) { define(function () { return _dagMap.default; }); } else if (typeof module !== 'undefined' && module.exports) { module.exports = _dagMap.default; } else if (typeof _dagMapPlatform.default !== 'undefined') { _dagMapPlatform.default['DAG'] = _dagMap.default; } }); enifed('dom-helper/build-html-dom', ['exports'], function (exports) { /* global XMLSerializer:false */ 'use strict'; var svgHTMLIntegrationPoints = { foreignObject: 1, desc: 1, title: 1 }; exports.svgHTMLIntegrationPoints = svgHTMLIntegrationPoints; var svgNamespace = 'http://www.w3.org/2000/svg'; exports.svgNamespace = svgNamespace; var doc = typeof document === 'undefined' ? false : document; // Safari does not like using innerHTML on SVG HTML integration // points (desc/title/foreignObject). var needsIntegrationPointFix = doc && (function (document) { if (document.createElementNS === undefined) { return; } // In FF title will not accept innerHTML. var testEl = document.createElementNS(svgNamespace, 'title'); testEl.innerHTML = "
"; return testEl.childNodes.length === 0 || testEl.childNodes[0].nodeType !== 1; })(doc); // Internet Explorer prior to 9 does not allow setting innerHTML if the first element // is a "zero-scope" element. This problem can be worked around by making // the first node an invisible text node. We, like Modernizr, use ­ var needsShy = doc && (function (document) { var testEl = document.createElement('div'); testEl.innerHTML = "
"; testEl.firstChild.innerHTML = " ``` And associate it by name using a view's `templateName` property: ```javascript AView = Ember.View.extend({ templateName: 'some-template' }); ``` If you have nested routes, your Handlebars template will look like this: ```html ``` And `templateName` property: ```javascript AView = Ember.View.extend({ templateName: 'posts/new' }); ``` Using a value for `templateName` that does not have a template with a matching `data-template-name` attribute will throw an error. For views classes that may have a template later defined (e.g. as the block portion of a `{{view}}` helper call in another template or in a subclass), you can provide a `defaultTemplate` property set to compiled template function. If a template is not later provided for the view instance the `defaultTemplate` value will be used: ```javascript AView = Ember.View.extend({ defaultTemplate: Ember.HTMLBars.compile('I was the default'), template: null, templateName: null }); ``` Will result in instances with an HTML representation of: ```html
I was the default
``` If a `template` or `templateName` is provided it will take precedence over `defaultTemplate`: ```javascript AView = Ember.View.extend({ defaultTemplate: Ember.HTMLBars.compile('I was the default') }); aView = AView.create({ template: Ember.HTMLBars.compile('I was the template, not default') }); ``` Will result in the following HTML representation when rendered: ```html
I was the template, not default
``` ## View Context The default context of the compiled template is the view's controller: ```javascript AView = Ember.View.extend({ template: Ember.HTMLBars.compile('Hello {{excitedGreeting}}') }); aController = Ember.Object.create({ firstName: 'Barry', excitedGreeting: Ember.computed('content.firstName', function() { return this.get('content.firstName') + '!!!'; }) }); aView = AView.create({ controller: aController }); ``` Will result in an HTML representation of: ```html
Hello Barry!!!
``` A context can also be explicitly supplied through the view's `context` property. If the view has neither `context` nor `controller` properties, the `parentView`'s context will be used. ## Layouts Views can have a secondary template that wraps their main template. Like primary templates, layouts can be any function that accepts an optional context parameter and returns a string of HTML that will be inserted inside view's tag. Views whose HTML element is self closing (e.g. ``) cannot have a layout and this property will be ignored. Most typically in Ember a layout will be a compiled template. A view's layout can be set directly with the `layout` property or reference an existing template by name with the `layoutName` property. A template used as a layout must contain a single use of the `{{yield}}` helper. The HTML contents of a view's rendered `template` will be inserted at this location: ```javascript AViewWithLayout = Ember.View.extend({ layout: Ember.HTMLBars.compile("
{{yield}}
"), template: Ember.HTMLBars.compile("I got wrapped") }); ``` Will result in view instances with an HTML representation of: ```html
I got wrapped
``` See [Ember.Templates.helpers.yield](/api/classes/Ember.Templates.helpers.html#method_yield) for more information. ## Responding to Browser Events Views can respond to user-initiated events in one of three ways: method implementation, through an event manager, and through `{{action}}` helper use in their template or layout. ### Method Implementation Views can respond to user-initiated events by implementing a method that matches the event name. A `jQuery.Event` object will be passed as the argument to this method. ```javascript AView = Ember.View.extend({ click: function(event) { // will be called when an instance's // rendered element is clicked } }); ``` ### Event Managers Views can define an object as their `eventManager` property. This object can then implement methods that match the desired event names. Matching events that occur on the view's rendered HTML or the rendered HTML of any of its DOM descendants will trigger this method. A `jQuery.Event` object will be passed as the first argument to the method and an `Ember.View` object as the second. The `Ember.View` will be the view whose rendered HTML was interacted with. This may be the view with the `eventManager` property or one of its descendant views. ```javascript AView = Ember.View.extend({ eventManager: Ember.Object.create({ doubleClick: function(event, view) { // will be called when an instance's // rendered element or any rendering // of this view's descendant // elements is clicked } }) }); ``` An event defined for an event manager takes precedence over events of the same name handled through methods on the view. ```javascript AView = Ember.View.extend({ mouseEnter: function(event) { // will never trigger. }, eventManager: Ember.Object.create({ mouseEnter: function(event, view) { // takes precedence over AView#mouseEnter } }) }); ``` Similarly a view's event manager will take precedence for events of any views rendered as a descendant. A method name that matches an event name will not be called if the view instance was rendered inside the HTML representation of a view that has an `eventManager` property defined that handles events of the name. Events not handled by the event manager will still trigger method calls on the descendant. ```javascript var App = Ember.Application.create(); App.OuterView = Ember.View.extend({ template: Ember.HTMLBars.compile("outer {{#view 'inner'}}inner{{/view}} outer"), eventManager: Ember.Object.create({ mouseEnter: function(event, view) { // view might be instance of either // OuterView or InnerView depending on // where on the page the user interaction occurred } }) }); App.InnerView = Ember.View.extend({ click: function(event) { // will be called if rendered inside // an OuterView because OuterView's // eventManager doesn't handle click events }, mouseEnter: function(event) { // will never be called if rendered inside // an OuterView. } }); ``` ### `{{action}}` Helper See [Ember.Templates.helpers.action](/api/classes/Ember.Templates.helpers.html#method_action). ### Event Names All of the event handling approaches described above respond to the same set of events. The names of the built-in events are listed below. (The hash of built-in events exists in `Ember.EventDispatcher`.) Additional, custom events can be registered by using `Ember.Application.customEvents`. Touch events: * `touchStart` * `touchMove` * `touchEnd` * `touchCancel` Keyboard events * `keyDown` * `keyUp` * `keyPress` Mouse events * `mouseDown` * `mouseUp` * `contextMenu` * `click` * `doubleClick` * `mouseMove` * `focusIn` * `focusOut` * `mouseEnter` * `mouseLeave` Form events: * `submit` * `change` * `focusIn` * `focusOut` * `input` HTML5 drag and drop events: * `dragStart` * `drag` * `dragEnter` * `dragLeave` * `dragOver` * `dragEnd` * `drop` ## `{{view}}` Helper Other `Ember.View` instances can be included as part of a view's template by using the `{{view}}` helper. See [Ember.Templates.helpers.view](/api/classes/Ember.Templates.helpers.html#method_view) for additional information. @class View @namespace Ember @extends Ember.CoreView @deprecated See http://emberjs.com/deprecations/v1.x/#toc_ember-view @uses Ember.ViewSupport @uses Ember.ViewContextSupport @uses Ember.ViewChildViewsSupport @uses Ember.TemplateRenderingSupport @uses Ember.ClassNamesSupport @uses Ember.AttributeBindingsSupport @uses Ember.LegacyViewSupport @uses Ember.InstrumentationSupport @uses Ember.VisibilitySupport @uses Ember.AriaRoleSupport @public */ // jscs:disable validateIndentation var View = _emberViewsViewsCore_view.default.extend(_emberViewsMixinsView_context_support.default, _emberViewsMixinsView_child_views_support.default, _emberViewsMixinsLegacy_child_views_support.default, _emberViewsMixinsView_state_support.default, _emberViewsMixinsTemplate_rendering_support.default, _emberViewsMixinsClass_names_support.default, _emberViewsMixinsLegacy_view_support.default, _emberViewsMixinsInstrumentation_support.default, _emberViewsMixinsVisibility_support.default, _emberViewsCompatAttrsProxy.default, _emberViewsMixinsAria_role_support.default, _emberViewsMixinsView_support.default, { init: function () { this._super.apply(this, arguments); if (!this._viewRegistry) { this._viewRegistry = View.views; } }, /** Given a property name, returns a dasherized version of that property name if the property evaluates to a non-falsy value. For example, if the view has property `isUrgent` that evaluates to true, passing `isUrgent` to this method will return `"is-urgent"`. @method _classStringForProperty @param property @private */ _classStringForProperty: function (parsedPath) { return View._classStringForValue(parsedPath.path, parsedPath.stream.value(), parsedPath.className, parsedPath.falsyClassName); } }); _emberMetalDeprecate_property.deprecateProperty(View.prototype, 'currentState', '_currentState', { id: 'ember-view.current-state', until: '2.3.0', url: 'http://emberjs.com/deprecations/v2.x/#toc_ember-component-currentstate' }); // jscs:enable validateIndentation /* Describe how the specified actions should behave in the various states that a view can exist in. Possible states: * preRender: when a view is first instantiated, and after its element was destroyed, it is in the preRender state * inBuffer: once a view has been rendered, but before it has been inserted into the DOM, it is in the inBuffer state * hasElement: the DOM representation of the view is created, and is ready to be inserted * inDOM: once a view has been inserted into the DOM it is in the inDOM state. A view spends the vast majority of its existence in this state. * destroyed: once a view has been destroyed (using the destroy method), it is in this state. No further actions can be invoked on a destroyed view. */ // in the destroyed state, everything is illegal // before rendering has begun, all legal manipulations are noops. // inside the buffer, legal manipulations are done on the buffer // once the view has been inserted into the DOM, legal manipulations // are done on the DOM element. View.reopenClass({ /** Global views hash @property views @static @type Object @private */ views: {}, // If someone overrides the child views computed property when // defining their class, we want to be able to process the user's // supplied childViews and then restore the original computed property // at view initialization time. This happens in Ember.ContainerView's init // method. childViewsProperty: _emberViewsMixinsView_child_views_support.childViewsProperty }); function viewDeprecationMessage() {} var DeprecatedView = View.extend({ init: function () { viewDeprecationMessage(); this._super.apply(this, arguments); } }); DeprecatedView.reopen = function () { viewDeprecationMessage(); View.reopen.apply(View, arguments); return this; }; exports.default = View; exports.ViewContextSupport = _emberViewsMixinsView_context_support.default; exports.ViewChildViewsSupport = _emberViewsMixinsView_child_views_support.default; exports.ViewStateSupport = _emberViewsMixinsView_state_support.default; exports.TemplateRenderingSupport = _emberViewsMixinsTemplate_rendering_support.default; exports.ClassNamesSupport = _emberViewsMixinsClass_names_support.default; exports.DeprecatedView = DeprecatedView; }); // for the side effect of extending Ember.run.queues enifed('htmlbars-runtime/expression-visitor', ['exports'], function (exports) { /** # Expression Nodes: These nodes are not directly responsible for any part of the DOM, but are eventually passed to a Statement Node. * get * subexpr * concat */ 'use strict'; exports.acceptParams = acceptParams; exports.acceptHash = acceptHash; function acceptParams(nodes, env, scope) { var array = []; for (var i = 0, l = nodes.length; i < l; i++) { array.push(acceptExpression(nodes[i], env, scope).value); } return array; } function acceptHash(pairs, env, scope) { var object = {}; for (var i = 0, l = pairs.length; i < l; i += 2) { var key = pairs[i]; var value = pairs[i + 1]; object[key] = acceptExpression(value, env, scope).value; } return object; } function acceptExpression(node, env, scope) { var ret = { value: null }; // Primitive literals are unambiguously non-array representations of // themselves. if (typeof node !== 'object' || node === null) { ret.value = node; } else { ret.value = evaluateNode(node, env, scope); } return ret; } function evaluateNode(node, env, scope) { switch (node[0]) { // can be used by manualElement case 'value': return node[1]; case 'get': return evaluateGet(node, env, scope); case 'subexpr': return evaluateSubexpr(node, env, scope); case 'concat': return evaluateConcat(node, env, scope); } } function evaluateGet(node, env, scope) { var path = node[1]; return env.hooks.get(env, scope, path); } function evaluateSubexpr(node, env, scope) { var path = node[1]; var rawParams = node[2]; var rawHash = node[3]; var params = acceptParams(rawParams, env, scope); var hash = acceptHash(rawHash, env, scope); return env.hooks.subexpr(env, scope, path, params, hash); } function evaluateConcat(node, env, scope) { var rawParts = node[1]; var parts = acceptParams(rawParts, env, scope); return env.hooks.concat(env, parts); } }); enifed("htmlbars-runtime/hooks", ["exports", "htmlbars-runtime/render", "morph-range/morph-list", "htmlbars-util/object-utils", "htmlbars-util/morph-utils", "htmlbars-util/template-utils"], function (exports, _htmlbarsRuntimeRender, _morphRangeMorphList, _htmlbarsUtilObjectUtils, _htmlbarsUtilMorphUtils, _htmlbarsUtilTemplateUtils) { "use strict"; exports.wrap = wrap; exports.wrapForHelper = wrapForHelper; exports.createScope = createScope; exports.createFreshScope = createFreshScope; exports.bindShadowScope = bindShadowScope; exports.createChildScope = createChildScope; exports.bindSelf = bindSelf; exports.updateSelf = updateSelf; exports.bindLocal = bindLocal; exports.updateLocal = updateLocal; exports.bindBlock = bindBlock; exports.block = block; exports.continueBlock = continueBlock; exports.hostBlock = hostBlock; exports.handleRedirect = handleRedirect; exports.handleKeyword = handleKeyword; exports.linkRenderNode = linkRenderNode; exports.inline = inline; exports.keyword = keyword; exports.invokeHelper = invokeHelper; exports.classify = classify; exports.partial = partial; exports.range = range; exports.element = element; exports.attribute = attribute; exports.subexpr = subexpr; exports.get = get; exports.getRoot = getRoot; exports.getBlock = getBlock; exports.getChild = getChild; exports.getValue = getValue; exports.getCellOrValue = getCellOrValue; exports.component = component; exports.concat = concat; exports.hasHelper = hasHelper; exports.lookupHelper = lookupHelper; exports.bindScope = bindScope; exports.updateScope = updateScope; /** HTMLBars delegates the runtime behavior of a template to hooks provided by the host environment. These hooks explain the lexical environment of a Handlebars template, the internal representation of references, and the interaction between an HTMLBars template and the DOM it is managing. While HTMLBars host hooks have access to all of this internal machinery, templates and helpers have access to the abstraction provided by the host hooks. ## The Lexical Environment The default lexical environment of an HTMLBars template includes: * Any local variables, provided by *block arguments* * The current value of `self` ## Simple Nesting Let's look at a simple template with a nested block: ```hbs

{{title}}

{{#if author}}

{{author}}

{{/if}} ``` In this case, the lexical environment at the top-level of the template does not change inside of the `if` block. This is achieved via an implementation of `if` that looks like this: ```js registerHelper('if', function(params) { if (!!params[0]) { return this.yield(); } }); ``` A call to `this.yield` invokes the child template using the current lexical environment. ## Block Arguments It is possible for nested blocks to introduce new local variables: ```hbs {{#count-calls as |i|}}

{{title}}

Called {{i}} times

{{/count}} ``` In this example, the child block inherits its surrounding lexical environment, but augments it with a single new variable binding. The implementation of `count-calls` supplies the value of `i`, but does not otherwise alter the environment: ```js var count = 0; registerHelper('count-calls', function() { return this.yield([ ++count ]); }); ``` */ function wrap(template) { if (template === null) { return null; } return { meta: template.meta, arity: template.arity, raw: template, render: function (self, env, options, blockArguments) { var scope = env.hooks.createFreshScope(); var contextualElement = options && options.contextualElement; var renderOptions = new _htmlbarsRuntimeRender.RenderOptions(null, self, blockArguments, contextualElement); return _htmlbarsRuntimeRender.default(template, env, scope, renderOptions); } }; } function wrapForHelper(template, env, scope, morph, renderState, visitor) { if (!template) { return {}; } var yieldArgs = yieldTemplate(template, env, scope, morph, renderState, visitor); return { meta: template.meta, arity: template.arity, 'yield': yieldArgs, // quoted since it's a reserved word, see issue #420 yieldItem: yieldItem(template, env, scope, morph, renderState, visitor), raw: template, render: function (self, blockArguments) { yieldArgs(blockArguments, self); } }; } // Called by a user-land helper to render a template. function yieldTemplate(template, env, parentScope, morph, renderState, visitor) { return function (blockArguments, self) { // Render state is used to track the progress of the helper (since it // may call into us multiple times). As the user-land helper calls // into library code, we track what needs to be cleaned up after the // helper has returned. // // Here, we remember that a template has been yielded and so we do not // need to remove the previous template. (If no template is yielded // this render by the helper, we assume nothing should be shown and // remove any previous rendered templates.) renderState.morphToClear = null; // In this conditional is true, it means that on the previous rendering pass // the helper yielded multiple items via `yieldItem()`, but this time they // are yielding a single template. In that case, we mark the morph list for // cleanup so it is removed from the DOM. if (morph.morphList) { _htmlbarsUtilTemplateUtils.clearMorphList(morph.morphList, morph, env); renderState.morphListToClear = null; } var scope = parentScope; if (morph.lastYielded && isStableTemplate(template, morph.lastYielded)) { return morph.lastResult.revalidateWith(env, undefined, self, blockArguments, visitor); } // Check to make sure that we actually **need** a new scope, and can't // share the parent scope. Note that we need to move this check into // a host hook, because the host's notion of scope may require a new // scope in more cases than the ones we can determine statically. if (self !== undefined || parentScope === null || template.arity) { scope = env.hooks.createChildScope(parentScope); } morph.lastYielded = { self: self, template: template, shadowTemplate: null }; // Render the template that was selected by the helper var renderOptions = new _htmlbarsRuntimeRender.RenderOptions(morph, self, blockArguments); _htmlbarsRuntimeRender.default(template, env, scope, renderOptions); }; } function yieldItem(template, env, parentScope, morph, renderState, visitor) { // Initialize state that tracks multiple items being // yielded in. var currentMorph = null; // Candidate morphs for deletion. var candidates = {}; // Reuse existing MorphList if this is not a first-time // render. var morphList = morph.morphList; if (morphList) { currentMorph = morphList.firstChildMorph; } // Advances the currentMorph pointer to the morph in the previously-rendered // list that matches the yielded key. While doing so, it marks any morphs // that it advances past as candidates for deletion. Assuming those morphs // are not yielded in later, they will be removed in the prune step during // cleanup. // Note that this helper function assumes that the morph being seeked to is // guaranteed to exist in the previous MorphList; if this is called and the // morph does not exist, it will result in an infinite loop function advanceToKey(key) { var seek = currentMorph; while (seek.key !== key) { candidates[seek.key] = seek; seek = seek.nextMorph; } currentMorph = seek.nextMorph; return seek; } return function (_key, blockArguments, self) { if (typeof _key !== 'string') { throw new Error("You must provide a string key when calling `yieldItem`; you provided " + _key); } // At least one item has been yielded, so we do not wholesale // clear the last MorphList but instead apply a prune operation. renderState.morphListToClear = null; morph.lastYielded = null; var morphList, morphMap; if (!morph.morphList) { morph.morphList = new _morphRangeMorphList.default(); morph.morphMap = {}; morph.setMorphList(morph.morphList); } morphList = morph.morphList; morphMap = morph.morphMap; // A map of morphs that have been yielded in on this // rendering pass. Any morphs that do not make it into // this list will be pruned from the MorphList during the cleanup // process. var handledMorphs = renderState.handledMorphs; var key = undefined; if (_key in handledMorphs) { // In this branch we are dealing with a duplicate key. The strategy // is to take the original key and append a counter to it that is // incremented every time the key is reused. In order to greatly // reduce the chance of colliding with another valid key we also add // an extra string "--z8mS2hvDW0A--" to the new key. var collisions = renderState.collisions; if (collisions === undefined) { collisions = renderState.collisions = {}; } var count = collisions[_key] | 0; collisions[_key] = ++count; key = _key + '--z8mS2hvDW0A--' + count; } else { key = _key; } if (currentMorph && currentMorph.key === key) { yieldTemplate(template, env, parentScope, currentMorph, renderState, visitor)(blockArguments, self); currentMorph = currentMorph.nextMorph; handledMorphs[key] = currentMorph; } else if (morphMap[key] !== undefined) { var foundMorph = morphMap[key]; if (key in candidates) { // If we already saw this morph, move it forward to this position morphList.insertBeforeMorph(foundMorph, currentMorph); } else { // Otherwise, move the pointer forward to the existing morph for this key advanceToKey(key); } handledMorphs[foundMorph.key] = foundMorph; yieldTemplate(template, env, parentScope, foundMorph, renderState, visitor)(blockArguments, self); } else { var childMorph = _htmlbarsRuntimeRender.createChildMorph(env.dom, morph); childMorph.key = key; morphMap[key] = handledMorphs[key] = childMorph; morphList.insertBeforeMorph(childMorph, currentMorph); yieldTemplate(template, env, parentScope, childMorph, renderState, visitor)(blockArguments, self); } renderState.morphListToPrune = morphList; morph.childNodes = null; }; } function isStableTemplate(template, lastYielded) { return !lastYielded.shadowTemplate && template === lastYielded.template; } function optionsFor(template, inverse, env, scope, morph, visitor) { // If there was a template yielded last time, set morphToClear so it will be cleared // if no template is yielded on this render. var morphToClear = morph.lastResult ? morph : null; var renderState = new _htmlbarsUtilTemplateUtils.RenderState(morphToClear, morph.morphList || null); return { templates: { template: wrapForHelper(template, env, scope, morph, renderState, visitor), inverse: wrapForHelper(inverse, env, scope, morph, renderState, visitor) }, renderState: renderState }; } function thisFor(options) { return { arity: options.template.arity, 'yield': options.template.yield, // quoted since it's a reserved word, see issue #420 yieldItem: options.template.yieldItem, yieldIn: options.template.yieldIn }; } /** Host Hook: createScope @param {Scope?} parentScope @return Scope Corresponds to entering a new HTMLBars block. This hook is invoked when a block is entered with a new `self` or additional local variables. When invoked for a top-level template, the `parentScope` is `null`, and this hook should return a fresh Scope. When invoked for a child template, the `parentScope` is the scope for the parent environment. Note that the `Scope` is an opaque value that is passed to other host hooks. For example, the `get` hook uses the scope to retrieve a value for a given scope and variable name. */ function createScope(env, parentScope) { if (parentScope) { return env.hooks.createChildScope(parentScope); } else { return env.hooks.createFreshScope(); } } function createFreshScope() { // because `in` checks have unpredictable performance, keep a // separate dictionary to track whether a local was bound. // See `bindLocal` for more information. return { self: null, blocks: {}, locals: {}, localPresent: {} }; } /** Host Hook: bindShadowScope @param {Scope?} parentScope @return Scope Corresponds to rendering a new template into an existing render tree, but with a new top-level lexical scope. This template is called the "shadow root". If a shadow template invokes `{{yield}}`, it will render the block provided to the shadow root in the original lexical scope. ```hbs {{!-- post template --}}

{{props.title}}

{{yield}} {{!-- blog template --}} {{#post title="Hello world"}}

by {{byline}}

This is my first post
{{/post}} {{#post title="Goodbye world"}}

by {{byline}}

This is my last post
{{/post}} ``` ```js helpers.post = function(params, hash, options) { options.template.yieldIn(postTemplate, { props: hash }); }; blog.render({ byline: "Yehuda Katz" }); ``` Produces: ```html

Hello world

by Yehuda Katz

This is my first post

Goodbye world

by Yehuda Katz

This is my last post
``` In short, `yieldIn` creates a new top-level scope for the provided template and renders it, making the original block available to `{{yield}}` in that template. */ function bindShadowScope(env /*, parentScope, shadowScope */) { return env.hooks.createFreshScope(); } function createChildScope(parent) { var scope = Object.create(parent); scope.locals = Object.create(parent.locals); scope.localPresent = Object.create(parent.localPresent); scope.blocks = Object.create(parent.blocks); return scope; } /** Host Hook: bindSelf @param {Scope} scope @param {any} self Corresponds to entering a template. This hook is invoked when the `self` value for a scope is ready to be bound. The host must ensure that child scopes reflect the change to the `self` in future calls to the `get` hook. */ function bindSelf(env, scope, self) { scope.self = self; } function updateSelf(env, scope, self) { env.hooks.bindSelf(env, scope, self); } /** Host Hook: bindLocal @param {Environment} env @param {Scope} scope @param {String} name @param {any} value Corresponds to entering a template with block arguments. This hook is invoked when a local variable for a scope has been provided. The host must ensure that child scopes reflect the change in future calls to the `get` hook. */ function bindLocal(env, scope, name, value) { scope.localPresent[name] = true; scope.locals[name] = value; } function updateLocal(env, scope, name, value) { env.hooks.bindLocal(env, scope, name, value); } /** Host Hook: bindBlock @param {Environment} env @param {Scope} scope @param {Function} block Corresponds to entering a shadow template that was invoked by a block helper with `yieldIn`. This hook is invoked with an opaque block that will be passed along to the shadow template, and inserted into the shadow template when `{{yield}}` is used. Optionally provide a non-default block name that can be targeted by `{{yield to=blockName}}`. */ function bindBlock(env, scope, block) { var name = arguments.length <= 3 || arguments[3] === undefined ? 'default' : arguments[3]; scope.blocks[name] = block; } /** Host Hook: block @param {RenderNode} renderNode @param {Environment} env @param {Scope} scope @param {String} path @param {Array} params @param {Object} hash @param {Block} block @param {Block} elseBlock Corresponds to: ```hbs {{#helper param1 param2 key1=val1 key2=val2}} {{!-- child template --}} {{/helper}} ``` This host hook is a workhorse of the system. It is invoked whenever a block is encountered, and is responsible for resolving the helper to call, and then invoke it. The helper should be invoked with: - `{Array} params`: the parameters passed to the helper in the template. - `{Object} hash`: an object containing the keys and values passed in the hash position in the template. The values in `params` and `hash` will already be resolved through a previous call to the `get` host hook. The helper should be invoked with a `this` value that is an object with one field: `{Function} yield`: when invoked, this function executes the block with the current scope. It takes an optional array of block parameters. If block parameters are supplied, HTMLBars will invoke the `bindLocal` host hook to bind the supplied values to the block arguments provided by the template. In general, the default implementation of `block` should work for most host environments. It delegates to other host hooks where appropriate, and properly invokes the helper with the appropriate arguments. */ function block(morph, env, scope, path, params, hash, template, inverse, visitor) { if (handleRedirect(morph, env, scope, path, params, hash, template, inverse, visitor)) { return; } continueBlock(morph, env, scope, path, params, hash, template, inverse, visitor); } function continueBlock(morph, env, scope, path, params, hash, template, inverse, visitor) { hostBlock(morph, env, scope, template, inverse, null, visitor, function (options) { var helper = env.hooks.lookupHelper(env, scope, path); return env.hooks.invokeHelper(morph, env, scope, visitor, params, hash, helper, options.templates, thisFor(options.templates)); }); } function hostBlock(morph, env, scope, template, inverse, shadowOptions, visitor, callback) { var options = optionsFor(template, inverse, env, scope, morph, visitor); _htmlbarsUtilTemplateUtils.renderAndCleanup(morph, env, options, shadowOptions, callback); } function handleRedirect(morph, env, scope, path, params, hash, template, inverse, visitor) { if (!path) { return false; } var redirect = env.hooks.classify(env, scope, path); if (redirect) { switch (redirect) { case 'component': env.hooks.component(morph, env, scope, path, params, hash, { default: template, inverse: inverse }, visitor);break; case 'inline': env.hooks.inline(morph, env, scope, path, params, hash, visitor);break; case 'block': env.hooks.block(morph, env, scope, path, params, hash, template, inverse, visitor);break; default: throw new Error("Internal HTMLBars redirection to " + redirect + " not supported"); } return true; } if (handleKeyword(path, morph, env, scope, params, hash, template, inverse, visitor)) { return true; } return false; } function handleKeyword(path, morph, env, scope, params, hash, template, inverse, visitor) { var keyword = env.hooks.keywords[path]; if (!keyword) { return false; } if (typeof keyword === 'function') { return keyword(morph, env, scope, params, hash, template, inverse, visitor); } if (keyword.willRender) { keyword.willRender(morph, env); } var lastState, newState; if (keyword.setupState) { lastState = _htmlbarsUtilObjectUtils.shallowCopy(morph.getState()); newState = morph.setState(keyword.setupState(lastState, env, scope, params, hash)); } if (keyword.childEnv) { // Build the child environment... env = keyword.childEnv(morph.getState(), env); // ..then save off the child env builder on the render node. If the render // node tree is re-rendered and this node is not dirty, the child env // builder will still be invoked so that child dirty render nodes still get // the correct child env. morph.buildChildEnv = keyword.childEnv; } var firstTime = !morph.rendered; if (keyword.isEmpty) { var isEmpty = keyword.isEmpty(morph.getState(), env, scope, params, hash); if (isEmpty) { if (!firstTime) { _htmlbarsUtilTemplateUtils.clearMorph(morph, env, false); } return true; } } if (firstTime) { if (keyword.render) { keyword.render(morph, env, scope, params, hash, template, inverse, visitor); } morph.rendered = true; return true; } var isStable; if (keyword.isStable) { isStable = keyword.isStable(lastState, newState); } else { isStable = stableState(lastState, newState); } if (isStable) { if (keyword.rerender) { var newEnv = keyword.rerender(morph, env, scope, params, hash, template, inverse, visitor); env = newEnv || env; } _htmlbarsUtilMorphUtils.validateChildMorphs(env, morph, visitor); return true; } else { _htmlbarsUtilTemplateUtils.clearMorph(morph, env, false); } // If the node is unstable, re-render from scratch if (keyword.render) { keyword.render(morph, env, scope, params, hash, template, inverse, visitor); morph.rendered = true; return true; } } function stableState(oldState, newState) { if (_htmlbarsUtilObjectUtils.keyLength(oldState) !== _htmlbarsUtilObjectUtils.keyLength(newState)) { return false; } for (var prop in oldState) { if (oldState[prop] !== newState[prop]) { return false; } } return true; } function linkRenderNode() /* morph, env, scope, params, hash */{ return; } /** Host Hook: inline @param {RenderNode} renderNode @param {Environment} env @param {Scope} scope @param {String} path @param {Array} params @param {Hash} hash Corresponds to: ```hbs {{helper param1 param2 key1=val1 key2=val2}} ``` This host hook is similar to the `block` host hook, but it invokes helpers that do not supply an attached block. Like the `block` hook, the helper should be invoked with: - `{Array} params`: the parameters passed to the helper in the template. - `{Object} hash`: an object containing the keys and values passed in the hash position in the template. The values in `params` and `hash` will already be resolved through a previous call to the `get` host hook. In general, the default implementation of `inline` should work for most host environments. It delegates to other host hooks where appropriate, and properly invokes the helper with the appropriate arguments. The default implementation of `inline` also makes `partial` a keyword. Instead of invoking a helper named `partial`, it invokes the `partial` host hook. */ function inline(morph, env, scope, path, params, hash, visitor) { if (handleRedirect(morph, env, scope, path, params, hash, null, null, visitor)) { return; } var value = undefined, hasValue = undefined; if (morph.linkedResult) { value = env.hooks.getValue(morph.linkedResult); hasValue = true; } else { var options = optionsFor(null, null, env, scope, morph); var helper = env.hooks.lookupHelper(env, scope, path); var result = env.hooks.invokeHelper(morph, env, scope, visitor, params, hash, helper, options.templates, thisFor(options.templates)); if (result && result.link) { morph.linkedResult = result.value; _htmlbarsUtilMorphUtils.linkParams(env, scope, morph, '@content-helper', [morph.linkedResult], null); } if (result && 'value' in result) { value = env.hooks.getValue(result.value); hasValue = true; } } if (hasValue) { if (morph.lastValue !== value) { morph.setContent(value); } morph.lastValue = value; } } function keyword(path, morph, env, scope, params, hash, template, inverse, visitor) { handleKeyword(path, morph, env, scope, params, hash, template, inverse, visitor); } function invokeHelper(morph, env, scope, visitor, _params, _hash, helper, templates, context) { var params = normalizeArray(env, _params); var hash = normalizeObject(env, _hash); return { value: helper.call(context, params, hash, templates) }; } function normalizeArray(env, array) { var out = new Array(array.length); for (var i = 0, l = array.length; i < l; i++) { out[i] = env.hooks.getCellOrValue(array[i]); } return out; } function normalizeObject(env, object) { var out = {}; for (var prop in object) { out[prop] = env.hooks.getCellOrValue(object[prop]); } return out; } function classify() /* env, scope, path */{ return null; } var keywords = { partial: function (morph, env, scope, params) { var value = env.hooks.partial(morph, env, scope, params[0]); morph.setContent(value); return true; }, // quoted since it's a reserved word, see issue #420 'yield': function (morph, env, scope, params, hash, template, inverse, visitor) { // the current scope is provided purely for the creation of shadow // scopes; it should not be provided to user code. var to = env.hooks.getValue(hash.to) || 'default'; var block = env.hooks.getBlock(scope, to); if (block) { block.invoke(env, params, hash.self, morph, scope, visitor); } return true; }, hasBlock: function (morph, env, scope, params) { var name = env.hooks.getValue(params[0]) || 'default'; return !!env.hooks.getBlock(scope, name); }, hasBlockParams: function (morph, env, scope, params) { var name = env.hooks.getValue(params[0]) || 'default'; var block = env.hooks.getBlock(scope, name); return !!(block && block.arity); } }; exports.keywords = keywords; /** Host Hook: partial @param {RenderNode} renderNode @param {Environment} env @param {Scope} scope @param {String} path Corresponds to: ```hbs {{partial "location"}} ``` This host hook is invoked by the default implementation of the `inline` hook. This makes `partial` a keyword in an HTMLBars environment using the default `inline` host hook. It is implemented as a host hook so that it can retrieve the named partial out of the `Environment`. Helpers, in contrast, only have access to the values passed in to them, and not to the ambient lexical environment. The host hook should invoke the referenced partial with the ambient `self`. */ function partial(renderNode, env, scope, path) { var template = env.partials[path]; return template.render(scope.self, env, {}).fragment; } /** Host hook: range @param {RenderNode} renderNode @param {Environment} env @param {Scope} scope @param {any} value Corresponds to: ```hbs {{content}} {{{unescaped}}} ``` This hook is responsible for updating a render node that represents a range of content with a value. */ function range(morph, env, scope, path, value, visitor) { if (handleRedirect(morph, env, scope, path, [], {}, null, null, visitor)) { return; } value = env.hooks.getValue(value); if (morph.lastValue !== value) { morph.setContent(value); } morph.lastValue = value; } /** Host hook: element @param {RenderNode} renderNode @param {Environment} env @param {Scope} scope @param {String} path @param {Array} params @param {Hash} hash Corresponds to: ```hbs
``` This hook is responsible for invoking a helper that modifies an element. Its purpose is largely legacy support for awkward idioms that became common when using the string-based Handlebars engine. Most of the uses of the `element` hook are expected to be superseded by component syntax and the `attribute` hook. */ function element(morph, env, scope, path, params, hash, visitor) { if (handleRedirect(morph, env, scope, path, params, hash, null, null, visitor)) { return; } var helper = env.hooks.lookupHelper(env, scope, path); if (helper) { env.hooks.invokeHelper(null, env, scope, null, params, hash, helper, { element: morph.element }); } } /** Host hook: attribute @param {RenderNode} renderNode @param {Environment} env @param {String} name @param {any} value Corresponds to: ```hbs
``` This hook is responsible for updating a render node that represents an element's attribute with a value. It receives the name of the attribute as well as an already-resolved value, and should update the render node with the value if appropriate. */ function attribute(morph, env, scope, name, value) { value = env.hooks.getValue(value); if (morph.lastValue !== value) { morph.setContent(value); } morph.lastValue = value; } function subexpr(env, scope, helperName, params, hash) { var helper = env.hooks.lookupHelper(env, scope, helperName); var result = env.hooks.invokeHelper(null, env, scope, null, params, hash, helper, {}); if (result && 'value' in result) { return env.hooks.getValue(result.value); } } /** Host Hook: get @param {Environment} env @param {Scope} scope @param {String} path Corresponds to: ```hbs {{foo.bar}} ^ {{helper foo.bar key=value}} ^ ^ ``` This hook is the "leaf" hook of the system. It is used to resolve a path relative to the current scope. */ function get(env, scope, path) { if (path === '') { return scope.self; } var keys = path.split('.'); var value = env.hooks.getRoot(scope, keys[0])[0]; for (var i = 1; i < keys.length; i++) { if (value) { value = env.hooks.getChild(value, keys[i]); } else { break; } } return value; } function getRoot(scope, key) { if (scope.localPresent[key]) { return [scope.locals[key]]; } else if (scope.self) { return [scope.self[key]]; } else { return [undefined]; } } function getBlock(scope, key) { return scope.blocks[key]; } function getChild(value, key) { return value[key]; } function getValue(reference) { return reference; } function getCellOrValue(reference) { return reference; } function component(morph, env, scope, tagName, params, attrs, templates, visitor) { if (env.hooks.hasHelper(env, scope, tagName)) { return env.hooks.block(morph, env, scope, tagName, params, attrs, templates.default, templates.inverse, visitor); } componentFallback(morph, env, scope, tagName, attrs, templates.default); } function concat(env, params) { var value = ""; for (var i = 0, l = params.length; i < l; i++) { value += env.hooks.getValue(params[i]); } return value; } function componentFallback(morph, env, scope, tagName, attrs, template) { var element = env.dom.createElement(tagName); for (var name in attrs) { element.setAttribute(name, env.hooks.getValue(attrs[name])); } var fragment = _htmlbarsRuntimeRender.default(template, env, scope, {}).fragment; element.appendChild(fragment); morph.setNode(element); } function hasHelper(env, scope, helperName) { return env.helpers[helperName] !== undefined; } function lookupHelper(env, scope, helperName) { return env.helpers[helperName]; } function bindScope() /* env, scope */{ // this function is used to handle host-specified extensions to scope // other than `self`, `locals` and `block`. } function updateScope(env, scope) { env.hooks.bindScope(env, scope); } exports.default = { // fundamental hooks that you will likely want to override bindLocal: bindLocal, bindSelf: bindSelf, bindScope: bindScope, classify: classify, component: component, concat: concat, createFreshScope: createFreshScope, getChild: getChild, getRoot: getRoot, getBlock: getBlock, getValue: getValue, getCellOrValue: getCellOrValue, keywords: keywords, linkRenderNode: linkRenderNode, partial: partial, subexpr: subexpr, // fundamental hooks with good default behavior bindBlock: bindBlock, bindShadowScope: bindShadowScope, updateLocal: updateLocal, updateSelf: updateSelf, updateScope: updateScope, createChildScope: createChildScope, hasHelper: hasHelper, lookupHelper: lookupHelper, invokeHelper: invokeHelper, cleanupRenderNode: null, destroyRenderNode: null, willCleanupTree: null, didCleanupTree: null, willRenderNode: null, didRenderNode: null, // derived hooks attribute: attribute, block: block, createScope: createScope, element: element, get: get, inline: inline, range: range, keyword: keyword }; }); enifed("htmlbars-runtime/morph", ["exports", "morph-range"], function (exports, _morphRange) { "use strict"; var guid = 1; function HTMLBarsMorph(domHelper, contextualElement) { this.super$constructor(domHelper, contextualElement); this._state = undefined; this.ownerNode = null; this.isDirty = false; this.isSubtreeDirty = false; this.lastYielded = null; this.lastResult = null; this.lastValue = null; this.buildChildEnv = null; this.morphList = null; this.morphMap = null; this.key = null; this.linkedParams = null; this.linkedResult = null; this.childNodes = null; this.rendered = false; this.guid = "range" + guid++; this.seen = false; } HTMLBarsMorph.empty = function (domHelper, contextualElement) { var morph = new HTMLBarsMorph(domHelper, contextualElement); morph.clear(); return morph; }; HTMLBarsMorph.create = function (domHelper, contextualElement, node) { var morph = new HTMLBarsMorph(domHelper, contextualElement); morph.setNode(node); return morph; }; HTMLBarsMorph.attach = function (domHelper, contextualElement, firstNode, lastNode) { var morph = new HTMLBarsMorph(domHelper, contextualElement); morph.setRange(firstNode, lastNode); return morph; }; var prototype = HTMLBarsMorph.prototype = Object.create(_morphRange.default.prototype); prototype.constructor = HTMLBarsMorph; prototype.super$constructor = _morphRange.default; prototype.getState = function () { if (!this._state) { this._state = {}; } return this._state; }; prototype.setState = function (newState) { /*jshint -W093 */ return this._state = newState; }; exports.default = HTMLBarsMorph; }); enifed("htmlbars-runtime/node-visitor", ["exports", "htmlbars-util/morph-utils", "htmlbars-runtime/expression-visitor"], function (exports, _htmlbarsUtilMorphUtils, _htmlbarsRuntimeExpressionVisitor) { "use strict"; /** Node classification: # Primary Statement Nodes: These nodes are responsible for a render node that represents a morph-range. * block * inline * content * element * component # Leaf Statement Nodes: This node is responsible for a render node that represents a morph-attr. * attribute */ function linkParamsAndHash(env, scope, morph, path, params, hash) { if (morph.linkedParams) { params = morph.linkedParams.params; hash = morph.linkedParams.hash; } else { params = params && _htmlbarsRuntimeExpressionVisitor.acceptParams(params, env, scope); hash = hash && _htmlbarsRuntimeExpressionVisitor.acceptHash(hash, env, scope); } _htmlbarsUtilMorphUtils.linkParams(env, scope, morph, path, params, hash); return [params, hash]; } var AlwaysDirtyVisitor = { block: function (node, morph, env, scope, template, visitor) { var path = node[1]; var params = node[2]; var hash = node[3]; var templateId = node[4]; var inverseId = node[5]; var paramsAndHash = linkParamsAndHash(env, scope, morph, path, params, hash); morph.isDirty = morph.isSubtreeDirty = false; env.hooks.block(morph, env, scope, path, paramsAndHash[0], paramsAndHash[1], templateId === null ? null : template.templates[templateId], inverseId === null ? null : template.templates[inverseId], visitor); }, inline: function (node, morph, env, scope, visitor) { var path = node[1]; var params = node[2]; var hash = node[3]; var paramsAndHash = linkParamsAndHash(env, scope, morph, path, params, hash); morph.isDirty = morph.isSubtreeDirty = false; env.hooks.inline(morph, env, scope, path, paramsAndHash[0], paramsAndHash[1], visitor); }, content: function (node, morph, env, scope, visitor) { var path = node[1]; morph.isDirty = morph.isSubtreeDirty = false; if (isHelper(env, scope, path)) { env.hooks.inline(morph, env, scope, path, [], {}, visitor); if (morph.linkedResult) { _htmlbarsUtilMorphUtils.linkParams(env, scope, morph, '@content-helper', [morph.linkedResult], null); } return; } var params = undefined; if (morph.linkedParams) { params = morph.linkedParams.params; } else { params = [env.hooks.get(env, scope, path)]; } _htmlbarsUtilMorphUtils.linkParams(env, scope, morph, '@range', params, null); env.hooks.range(morph, env, scope, path, params[0], visitor); }, element: function (node, morph, env, scope, visitor) { var path = node[1]; var params = node[2]; var hash = node[3]; var paramsAndHash = linkParamsAndHash(env, scope, morph, path, params, hash); morph.isDirty = morph.isSubtreeDirty = false; env.hooks.element(morph, env, scope, path, paramsAndHash[0], paramsAndHash[1], visitor); }, attribute: function (node, morph, env, scope) { var name = node[1]; var value = node[2]; var paramsAndHash = linkParamsAndHash(env, scope, morph, '@attribute', [value], null); morph.isDirty = morph.isSubtreeDirty = false; env.hooks.attribute(morph, env, scope, name, paramsAndHash[0][0]); }, component: function (node, morph, env, scope, template, visitor) { var path = node[1]; var attrs = node[2]; var templateId = node[3]; var inverseId = node[4]; var paramsAndHash = linkParamsAndHash(env, scope, morph, path, [], attrs); var templates = { default: template.templates[templateId], inverse: template.templates[inverseId] }; morph.isDirty = morph.isSubtreeDirty = false; env.hooks.component(morph, env, scope, path, paramsAndHash[0], paramsAndHash[1], templates, visitor); }, attributes: function (node, morph, env, scope, parentMorph, visitor) { var template = node[1]; env.hooks.attributes(morph, env, scope, template, parentMorph, visitor); } }; exports.AlwaysDirtyVisitor = AlwaysDirtyVisitor; exports.default = { block: function (node, morph, env, scope, template, visitor) { dirtyCheck(env, morph, visitor, function (visitor) { AlwaysDirtyVisitor.block(node, morph, env, scope, template, visitor); }); }, inline: function (node, morph, env, scope, visitor) { dirtyCheck(env, morph, visitor, function (visitor) { AlwaysDirtyVisitor.inline(node, morph, env, scope, visitor); }); }, content: function (node, morph, env, scope, visitor) { dirtyCheck(env, morph, visitor, function (visitor) { AlwaysDirtyVisitor.content(node, morph, env, scope, visitor); }); }, element: function (node, morph, env, scope, template, visitor) { dirtyCheck(env, morph, visitor, function (visitor) { AlwaysDirtyVisitor.element(node, morph, env, scope, template, visitor); }); }, attribute: function (node, morph, env, scope, template) { dirtyCheck(env, morph, null, function () { AlwaysDirtyVisitor.attribute(node, morph, env, scope, template); }); }, component: function (node, morph, env, scope, template, visitor) { dirtyCheck(env, morph, visitor, function (visitor) { AlwaysDirtyVisitor.component(node, morph, env, scope, template, visitor); }); }, attributes: function (node, morph, env, scope, parentMorph, visitor) { AlwaysDirtyVisitor.attributes(node, morph, env, scope, parentMorph, visitor); } }; function dirtyCheck(_env, morph, visitor, callback) { var isDirty = morph.isDirty; var isSubtreeDirty = morph.isSubtreeDirty; var env = _env; if (isSubtreeDirty) { visitor = AlwaysDirtyVisitor; } if (isDirty || isSubtreeDirty) { callback(visitor); } else { if (morph.buildChildEnv) { env = morph.buildChildEnv(morph.getState(), env); } _htmlbarsUtilMorphUtils.validateChildMorphs(env, morph, visitor); } } function isHelper(env, scope, path) { return env.hooks.keywords[path] !== undefined || env.hooks.hasHelper(env, scope, path); } }); enifed("htmlbars-runtime/render", ["exports", "htmlbars-util/morph-utils", "htmlbars-runtime/node-visitor", "htmlbars-runtime/morph", "htmlbars-util/template-utils", "htmlbars-util/void-tag-names"], function (exports, _htmlbarsUtilMorphUtils, _htmlbarsRuntimeNodeVisitor, _htmlbarsRuntimeMorph, _htmlbarsUtilTemplateUtils, _htmlbarsUtilVoidTagNames) { "use strict"; exports.default = render; exports.RenderOptions = RenderOptions; exports.manualElement = manualElement; exports.attachAttributes = attachAttributes; exports.createChildMorph = createChildMorph; exports.getCachedFragment = getCachedFragment; var svgNamespace = "http://www.w3.org/2000/svg"; function render(template, env, scope, options) { var dom = env.dom; var contextualElement; if (options) { if (options.renderNode) { contextualElement = options.renderNode.contextualElement; } else if (options.contextualElement) { contextualElement = options.contextualElement; } } dom.detectNamespace(contextualElement); var renderResult = RenderResult.build(env, scope, template, options, contextualElement); renderResult.render(); return renderResult; } function RenderOptions(renderNode, self, blockArguments, contextualElement) { this.renderNode = renderNode || null; this.self = self; this.blockArguments = blockArguments || null; this.contextualElement = contextualElement || null; } function RenderResult(env, scope, options, rootNode, ownerNode, nodes, fragment, template, shouldSetContent) { this.root = rootNode; this.fragment = fragment; this.nodes = nodes; this.template = template; this.statements = template.statements.slice(); this.env = env; this.scope = scope; this.shouldSetContent = shouldSetContent; if (options.self !== undefined) { this.bindSelf(options.self); } if (options.blockArguments !== undefined) { this.bindLocals(options.blockArguments); } this.initializeNodes(ownerNode); } RenderResult.build = function (env, scope, template, options, contextualElement) { var dom = env.dom; var fragment = getCachedFragment(template, env); var nodes = template.buildRenderNodes(dom, fragment, contextualElement); var rootNode, ownerNode, shouldSetContent; if (options && options.renderNode) { rootNode = options.renderNode; ownerNode = rootNode.ownerNode; shouldSetContent = true; } else { rootNode = dom.createMorph(null, fragment.firstChild, fragment.lastChild, contextualElement); ownerNode = rootNode; rootNode.ownerNode = ownerNode; shouldSetContent = false; } if (rootNode.childNodes) { _htmlbarsUtilMorphUtils.visitChildren(rootNode.childNodes, function (node) { _htmlbarsUtilTemplateUtils.clearMorph(node, env, true); }); } rootNode.childNodes = nodes; return new RenderResult(env, scope, options, rootNode, ownerNode, nodes, fragment, template, shouldSetContent); }; function manualElement(tagName, attributes, _isEmpty) { var statements = []; for (var key in attributes) { if (typeof attributes[key] === 'string') { continue; } statements.push(["attribute", key, attributes[key]]); } var isEmpty = _isEmpty || _htmlbarsUtilVoidTagNames.default[tagName]; if (!isEmpty) { statements.push(['content', 'yield']); } var template = { arity: 0, cachedFragment: null, hasRendered: false, buildFragment: function buildFragment(dom) { var el0 = dom.createDocumentFragment(); if (tagName === 'svg') { dom.setNamespace(svgNamespace); } var el1 = dom.createElement(tagName); for (var key in attributes) { if (typeof attributes[key] !== 'string') { continue; } dom.setAttribute(el1, key, attributes[key]); } if (!isEmpty) { var el2 = dom.createComment(""); dom.appendChild(el1, el2); } dom.appendChild(el0, el1); return el0; }, buildRenderNodes: function buildRenderNodes(dom, fragment) { var element = dom.childAt(fragment, [0]); var morphs = []; for (var key in attributes) { if (typeof attributes[key] === 'string') { continue; } morphs.push(dom.createAttrMorph(element, key)); } if (!isEmpty) { morphs.push(dom.createMorphAt(element, 0, 0)); } return morphs; }, statements: statements, locals: [], templates: [] }; return template; } function attachAttributes(attributes) { var statements = []; for (var key in attributes) { if (typeof attributes[key] === 'string') { continue; } statements.push(["attribute", key, attributes[key]]); } var template = { arity: 0, cachedFragment: null, hasRendered: false, buildFragment: function buildFragment(dom) { var el0 = this.element; if (el0.namespaceURI === "http://www.w3.org/2000/svg") { dom.setNamespace(svgNamespace); } for (var key in attributes) { if (typeof attributes[key] !== 'string') { continue; } dom.setAttribute(el0, key, attributes[key]); } return el0; }, buildRenderNodes: function buildRenderNodes(dom) { var element = this.element; var morphs = []; for (var key in attributes) { if (typeof attributes[key] === 'string') { continue; } morphs.push(dom.createAttrMorph(element, key)); } return morphs; }, statements: statements, locals: [], templates: [], element: null }; return template; } RenderResult.prototype.initializeNodes = function (ownerNode) { var childNodes = this.root.childNodes; for (var i = 0, l = childNodes.length; i < l; i++) { childNodes[i].ownerNode = ownerNode; } }; RenderResult.prototype.render = function () { this.root.lastResult = this; this.root.rendered = true; this.populateNodes(_htmlbarsRuntimeNodeVisitor.AlwaysDirtyVisitor); if (this.shouldSetContent && this.root.setContent) { this.root.setContent(this.fragment); } }; RenderResult.prototype.dirty = function () { _htmlbarsUtilMorphUtils.visitChildren([this.root], function (node) { node.isDirty = true; }); }; RenderResult.prototype.revalidate = function (env, self, blockArguments, scope) { this.revalidateWith(env, scope, self, blockArguments, _htmlbarsRuntimeNodeVisitor.default); }; RenderResult.prototype.rerender = function (env, self, blockArguments, scope) { this.revalidateWith(env, scope, self, blockArguments, _htmlbarsRuntimeNodeVisitor.AlwaysDirtyVisitor); }; RenderResult.prototype.revalidateWith = function (env, scope, self, blockArguments, visitor) { if (env !== undefined) { this.env = env; } if (scope !== undefined) { this.scope = scope; } this.updateScope(); if (self !== undefined) { this.updateSelf(self); } if (blockArguments !== undefined) { this.updateLocals(blockArguments); } this.populateNodes(visitor); }; RenderResult.prototype.destroy = function () { var rootNode = this.root; _htmlbarsUtilTemplateUtils.clearMorph(rootNode, this.env, true); }; RenderResult.prototype.populateNodes = function (visitor) { var env = this.env; var scope = this.scope; var template = this.template; var nodes = this.nodes; var statements = this.statements; var i, l; for (i = 0, l = statements.length; i < l; i++) { var statement = statements[i]; var morph = nodes[i]; if (env.hooks.willRenderNode) { env.hooks.willRenderNode(morph, env, scope); } switch (statement[0]) { case 'block': visitor.block(statement, morph, env, scope, template, visitor);break; case 'inline': visitor.inline(statement, morph, env, scope, visitor);break; case 'content': visitor.content(statement, morph, env, scope, visitor);break; case 'element': visitor.element(statement, morph, env, scope, template, visitor);break; case 'attribute': visitor.attribute(statement, morph, env, scope);break; case 'component': visitor.component(statement, morph, env, scope, template, visitor);break; } if (env.hooks.didRenderNode) { env.hooks.didRenderNode(morph, env, scope); } } }; RenderResult.prototype.bindScope = function () { this.env.hooks.bindScope(this.env, this.scope); }; RenderResult.prototype.updateScope = function () { this.env.hooks.updateScope(this.env, this.scope); }; RenderResult.prototype.bindSelf = function (self) { this.env.hooks.bindSelf(this.env, this.scope, self); }; RenderResult.prototype.updateSelf = function (self) { this.env.hooks.updateSelf(this.env, this.scope, self); }; RenderResult.prototype.bindLocals = function (blockArguments) { var localNames = this.template.locals; for (var i = 0, l = localNames.length; i < l; i++) { this.env.hooks.bindLocal(this.env, this.scope, localNames[i], blockArguments[i]); } }; RenderResult.prototype.updateLocals = function (blockArguments) { var localNames = this.template.locals; for (var i = 0, l = localNames.length; i < l; i++) { this.env.hooks.updateLocal(this.env, this.scope, localNames[i], blockArguments[i]); } }; function initializeNode(node, owner) { node.ownerNode = owner; } function createChildMorph(dom, parentMorph, contextualElement) { var morph = _htmlbarsRuntimeMorph.default.empty(dom, contextualElement || parentMorph.contextualElement); initializeNode(morph, parentMorph.ownerNode); return morph; } function getCachedFragment(template, env) { var dom = env.dom, fragment; if (env.useFragmentCache && dom.canClone) { if (template.cachedFragment === null) { fragment = template.buildFragment(dom); if (template.hasRendered) { template.cachedFragment = fragment; } else { template.hasRendered = true; } } if (template.cachedFragment) { fragment = dom.cloneNode(template.cachedFragment, true); } } else if (!fragment) { fragment = template.buildFragment(dom); } return fragment; } }); enifed('htmlbars-runtime', ['exports', 'htmlbars-runtime/hooks', 'htmlbars-runtime/render', 'htmlbars-util/morph-utils', 'htmlbars-util/template-utils'], function (exports, _htmlbarsRuntimeHooks, _htmlbarsRuntimeRender, _htmlbarsUtilMorphUtils, _htmlbarsUtilTemplateUtils) { 'use strict'; var internal = { blockFor: _htmlbarsUtilTemplateUtils.blockFor, manualElement: _htmlbarsRuntimeRender.manualElement, hostBlock: _htmlbarsRuntimeHooks.hostBlock, continueBlock: _htmlbarsRuntimeHooks.continueBlock, hostYieldWithShadowTemplate: _htmlbarsRuntimeHooks.hostYieldWithShadowTemplate, visitChildren: _htmlbarsUtilMorphUtils.visitChildren, validateChildMorphs: _htmlbarsUtilMorphUtils.validateChildMorphs, clearMorph: _htmlbarsUtilTemplateUtils.clearMorph }; exports.hooks = _htmlbarsRuntimeHooks.default; exports.render = _htmlbarsRuntimeRender.default; exports.internal = internal; }); enifed('htmlbars-util/array-utils', ['exports'], function (exports) { 'use strict'; exports.forEach = forEach; exports.map = map; function forEach(array, callback, binding) { var i, l; if (binding === undefined) { for (i = 0, l = array.length; i < l; i++) { callback(array[i], i, array); } } else { for (i = 0, l = array.length; i < l; i++) { callback.call(binding, array[i], i, array); } } } function map(array, callback) { var output = []; var i, l; for (i = 0, l = array.length; i < l; i++) { output.push(callback(array[i], i, array)); } return output; } var getIdx; if (Array.prototype.indexOf) { getIdx = function (array, obj, from) { return array.indexOf(obj, from); }; } else { getIdx = function (array, obj, from) { if (from === undefined || from === null) { from = 0; } else if (from < 0) { from = Math.max(0, array.length + from); } for (var i = from, l = array.length; i < l; i++) { if (array[i] === obj) { return i; } } return -1; }; } var isArray = Array.isArray || function (array) { return Object.prototype.toString.call(array) === '[object Array]'; }; exports.isArray = isArray; var indexOfArray = getIdx; exports.indexOfArray = indexOfArray; }); enifed('htmlbars-util/handlebars/safe-string', ['exports'], function (exports) { // Build out our basic SafeString type 'use strict'; function SafeString(string) { this.string = string; } SafeString.prototype.toString = SafeString.prototype.toHTML = function () { return '' + this.string; }; exports.default = SafeString; }); enifed('htmlbars-util/handlebars/utils', ['exports'], function (exports) { 'use strict'; exports.extend = extend; exports.indexOf = indexOf; exports.escapeExpression = escapeExpression; exports.isEmpty = isEmpty; exports.blockParams = blockParams; exports.appendContextPath = appendContextPath; var escape = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '`': '`' }; var badChars = /[&<>"'`]/g, possible = /[&<>"'`]/; function escapeChar(chr) { return escape[chr]; } function extend(obj /* , ...source */) { for (var i = 1; i < arguments.length; i++) { for (var key in arguments[i]) { if (Object.prototype.hasOwnProperty.call(arguments[i], key)) { obj[key] = arguments[i][key]; } } } return obj; } var toString = Object.prototype.toString; exports.toString = toString; // Sourced from lodash // https://github.com/bestiejs/lodash/blob/master/LICENSE.txt /*eslint-disable func-style, no-var */ var isFunction = function (value) { return typeof value === 'function'; }; // fallback for older versions of Chrome and Safari /* istanbul ignore next */ if (isFunction(/x/)) { exports.isFunction = isFunction = function (value) { return typeof value === 'function' && toString.call(value) === '[object Function]'; }; } var isFunction; exports.isFunction = isFunction; /*eslint-enable func-style, no-var */ /* istanbul ignore next */ var isArray = Array.isArray || function (value) { return value && typeof value === 'object' ? toString.call(value) === '[object Array]' : false; }; exports.isArray = isArray; // Older IE versions do not directly support indexOf so we must implement our own, sadly. function indexOf(array, value) { for (var i = 0, len = array.length; i < len; i++) { if (array[i] === value) { return i; } } return -1; } function escapeExpression(string) { if (typeof string !== 'string') { // don't escape SafeStrings, since they're already safe if (string && string.toHTML) { return string.toHTML(); } else if (string == null) { return ''; } else if (!string) { return string + ''; } // Force a string conversion as this will be done by the append regardless and // the regex test will do this transparently behind the scenes, causing issues if // an object's to string has escaped characters in it. string = '' + string; } if (!possible.test(string)) { return string; } return string.replace(badChars, escapeChar); } function isEmpty(value) { if (!value && value !== 0) { return true; } else if (isArray(value) && value.length === 0) { return true; } else { return false; } } function blockParams(params, ids) { params.path = ids; return params; } function appendContextPath(contextPath, id) { return (contextPath ? contextPath + '.' : '') + id; } }); enifed("htmlbars-util/morph-utils", ["exports"], function (exports) { /*globals console*/ "use strict"; exports.visitChildren = visitChildren; exports.validateChildMorphs = validateChildMorphs; exports.linkParams = linkParams; exports.dump = dump; function visitChildren(nodes, callback) { if (!nodes || nodes.length === 0) { return; } nodes = nodes.slice(); while (nodes.length) { var node = nodes.pop(); callback(node); if (node.childNodes) { nodes.push.apply(nodes, node.childNodes); } else if (node.firstChildMorph) { var current = node.firstChildMorph; while (current) { nodes.push(current); current = current.nextMorph; } } else if (node.morphList) { var current = node.morphList.firstChildMorph; while (current) { nodes.push(current); current = current.nextMorph; } } } } function validateChildMorphs(env, morph, visitor) { var morphList = morph.morphList; if (morph.morphList) { var current = morphList.firstChildMorph; while (current) { var next = current.nextMorph; validateChildMorphs(env, current, visitor); current = next; } } else if (morph.lastResult) { morph.lastResult.revalidateWith(env, undefined, undefined, undefined, visitor); } else if (morph.childNodes) { // This means that the childNodes were wired up manually for (var i = 0, l = morph.childNodes.length; i < l; i++) { validateChildMorphs(env, morph.childNodes[i], visitor); } } } function linkParams(env, scope, morph, path, params, hash) { if (morph.linkedParams) { return; } if (env.hooks.linkRenderNode(morph, env, scope, path, params, hash)) { morph.linkedParams = { params: params, hash: hash }; } } function dump(node) { console.group(node, node.isDirty); if (node.childNodes) { map(node.childNodes, dump); } else if (node.firstChildMorph) { var current = node.firstChildMorph; while (current) { dump(current); current = current.nextMorph; } } else if (node.morphList) { dump(node.morphList); } console.groupEnd(); } function map(nodes, cb) { for (var i = 0, l = nodes.length; i < l; i++) { cb(nodes[i]); } } }); enifed('htmlbars-util/namespaces', ['exports'], function (exports) { // ref http://dev.w3.org/html5/spec-LC/namespaces.html 'use strict'; exports.getAttrNamespace = getAttrNamespace; var defaultNamespaces = { html: 'http://www.w3.org/1999/xhtml', mathml: 'http://www.w3.org/1998/Math/MathML', svg: 'http://www.w3.org/2000/svg', xlink: 'http://www.w3.org/1999/xlink', xml: 'http://www.w3.org/XML/1998/namespace' }; function getAttrNamespace(attrName, detectedNamespace) { if (detectedNamespace) { return detectedNamespace; } var namespace; var colonIndex = attrName.indexOf(':'); if (colonIndex !== -1) { var prefix = attrName.slice(0, colonIndex); namespace = defaultNamespaces[prefix]; } return namespace || null; } }); enifed("htmlbars-util/object-utils", ["exports"], function (exports) { "use strict"; exports.merge = merge; exports.shallowCopy = shallowCopy; exports.keySet = keySet; exports.keyLength = keyLength; function merge(options, defaults) { for (var prop in defaults) { if (options.hasOwnProperty(prop)) { continue; } options[prop] = defaults[prop]; } return options; } function shallowCopy(obj) { return merge({}, obj); } function keySet(obj) { var set = {}; for (var prop in obj) { if (obj.hasOwnProperty(prop)) { set[prop] = true; } } return set; } function keyLength(obj) { var count = 0; for (var prop in obj) { if (obj.hasOwnProperty(prop)) { count++; } } return count; } }); enifed("htmlbars-util/quoting", ["exports"], function (exports) { "use strict"; exports.hash = hash; exports.repeat = repeat; function escapeString(str) { str = str.replace(/\\/g, "\\\\"); str = str.replace(/"/g, '\\"'); str = str.replace(/\n/g, "\\n"); return str; } exports.escapeString = escapeString; function string(str) { return '"' + escapeString(str) + '"'; } exports.string = string; function array(a) { return "[" + a + "]"; } exports.array = array; function hash(pairs) { return "{" + pairs.join(", ") + "}"; } function repeat(chars, times) { var str = ""; while (times--) { str += chars; } return str; } }); enifed('htmlbars-util/safe-string', ['exports', 'htmlbars-util/handlebars/safe-string'], function (exports, _htmlbarsUtilHandlebarsSafeString) { 'use strict'; exports.default = _htmlbarsUtilHandlebarsSafeString.default; }); enifed("htmlbars-util/template-utils", ["exports", "htmlbars-util/morph-utils", "htmlbars-runtime/render"], function (exports, _htmlbarsUtilMorphUtils, _htmlbarsRuntimeRender) { "use strict"; exports.RenderState = RenderState; exports.blockFor = blockFor; exports.renderAndCleanup = renderAndCleanup; exports.clearMorph = clearMorph; exports.clearMorphList = clearMorphList; function RenderState(renderNode, morphList) { // The morph list that is no longer needed and can be // destroyed. this.morphListToClear = morphList; // The morph list that needs to be pruned of any items // that were not yielded on a subsequent render. this.morphListToPrune = null; // A map of morphs for each item yielded in during this // rendering pass. Any morphs in the DOM but not in this map // will be pruned during cleanup. this.handledMorphs = {}; this.collisions = undefined; // The morph to clear once rendering is complete. By // default, we set this to the previous morph (to catch // the case where nothing is yielded; in that case, we // should just clear the morph). Otherwise this gets set // to null if anything is rendered. this.morphToClear = renderNode; this.shadowOptions = null; } function Block(render, template, blockOptions) { this.render = render; this.template = template; this.blockOptions = blockOptions; this.arity = template.arity; } Block.prototype.invoke = function (env, blockArguments, _self, renderNode, parentScope, visitor) { if (renderNode.lastResult) { renderNode.lastResult.revalidateWith(env, undefined, _self, blockArguments, visitor); } else { this._firstRender(env, blockArguments, _self, renderNode, parentScope); } }; Block.prototype._firstRender = function (env, blockArguments, _self, renderNode, parentScope) { var options = { renderState: new RenderState(renderNode) }; var render = this.render; var template = this.template; var scope = this.blockOptions.scope; var shadowScope = scope ? env.hooks.createChildScope(scope) : env.hooks.createFreshScope(); env.hooks.bindShadowScope(env, parentScope, shadowScope, this.blockOptions.options); if (_self !== undefined) { env.hooks.bindSelf(env, shadowScope, _self); } else if (this.blockOptions.self !== undefined) { env.hooks.bindSelf(env, shadowScope, this.blockOptions.self); } bindBlocks(env, shadowScope, this.blockOptions.yieldTo); renderAndCleanup(renderNode, env, options, null, function () { options.renderState.morphToClear = null; var renderOptions = new _htmlbarsRuntimeRender.RenderOptions(renderNode, undefined, blockArguments); render(template, env, shadowScope, renderOptions); }); }; function blockFor(render, template, blockOptions) { return new Block(render, template, blockOptions); } function bindBlocks(env, shadowScope, blocks) { if (!blocks) { return; } if (blocks instanceof Block) { env.hooks.bindBlock(env, shadowScope, blocks); } else { for (var name in blocks) { if (blocks.hasOwnProperty(name)) { env.hooks.bindBlock(env, shadowScope, blocks[name], name); } } } } function renderAndCleanup(morph, env, options, shadowOptions, callback) { // The RenderState object is used to collect information about what the // helper or hook being invoked has yielded. Once it has finished either // yielding multiple items (via yieldItem) or a single template (via // yieldTemplate), we detect what was rendered and how it differs from // the previous render, cleaning up old state in DOM as appropriate. var renderState = options.renderState; renderState.collisions = undefined; renderState.shadowOptions = shadowOptions; // Invoke the callback, instructing it to save information about what it // renders into RenderState. var result = callback(options); // The hook can opt-out of cleanup if it handled cleanup itself. if (result && result.handled) { return; } var morphMap = morph.morphMap; // Walk the morph list, clearing any items that were yielded in a previous // render but were not yielded during this render. var morphList = renderState.morphListToPrune; if (morphList) { var handledMorphs = renderState.handledMorphs; var item = morphList.firstChildMorph; while (item) { var next = item.nextMorph; // If we don't see the key in handledMorphs, it wasn't // yielded in and we can safely remove it from DOM. if (!(item.key in handledMorphs)) { morphMap[item.key] = undefined; clearMorph(item, env, true); item.destroy(); } item = next; } } morphList = renderState.morphListToClear; if (morphList) { clearMorphList(morphList, morph, env); } var toClear = renderState.morphToClear; if (toClear) { clearMorph(toClear, env); } } function clearMorph(morph, env, destroySelf) { var cleanup = env.hooks.cleanupRenderNode; var destroy = env.hooks.destroyRenderNode; var willCleanup = env.hooks.willCleanupTree; var didCleanup = env.hooks.didCleanupTree; function destroyNode(node) { if (cleanup) { cleanup(node); } if (destroy) { destroy(node); } } if (willCleanup) { willCleanup(env, morph, destroySelf); } if (cleanup) { cleanup(morph); } if (destroySelf && destroy) { destroy(morph); } _htmlbarsUtilMorphUtils.visitChildren(morph.childNodes, destroyNode); // TODO: Deal with logical children that are not in the DOM tree morph.clear(); if (didCleanup) { didCleanup(env, morph, destroySelf); } morph.lastResult = null; morph.lastYielded = null; morph.childNodes = null; } function clearMorphList(morphList, morph, env) { var item = morphList.firstChildMorph; while (item) { var next = item.nextMorph; morph.morphMap[item.key] = undefined; clearMorph(item, env, true); item.destroy(); item = next; } // Remove the MorphList from the morph. morphList.clear(); morph.morphList = null; } }); enifed("htmlbars-util/void-tag-names", ["exports", "htmlbars-util/array-utils"], function (exports, _htmlbarsUtilArrayUtils) { "use strict"; // The HTML elements in this list are speced by // http://www.w3.org/TR/html-markup/syntax.html#syntax-elements, // and will be forced to close regardless of if they have a // self-closing /> at the end. var voidTagNames = "area base br col command embed hr img input keygen link meta param source track wbr"; var voidMap = {}; _htmlbarsUtilArrayUtils.forEach(voidTagNames.split(" "), function (tagName) { voidMap[tagName] = true; }); exports.default = voidMap; }); enifed('htmlbars-util', ['exports', 'htmlbars-util/safe-string', 'htmlbars-util/handlebars/utils', 'htmlbars-util/namespaces', 'htmlbars-util/morph-utils'], function (exports, _htmlbarsUtilSafeString, _htmlbarsUtilHandlebarsUtils, _htmlbarsUtilNamespaces, _htmlbarsUtilMorphUtils) { 'use strict'; exports.SafeString = _htmlbarsUtilSafeString.default; exports.escapeExpression = _htmlbarsUtilHandlebarsUtils.escapeExpression; exports.getAttrNamespace = _htmlbarsUtilNamespaces.getAttrNamespace; exports.validateChildMorphs = _htmlbarsUtilMorphUtils.validateChildMorphs; exports.linkParams = _htmlbarsUtilMorphUtils.linkParams; exports.dump = _htmlbarsUtilMorphUtils.dump; }); enifed('morph-attr/sanitize-attribute-value', ['exports'], function (exports) { /* jshint scripturl:true */ 'use strict'; exports.sanitizeAttributeValue = sanitizeAttributeValue; var badProtocols = { 'javascript:': true, 'vbscript:': true }; var badTags = { 'A': true, 'BODY': true, 'LINK': true, 'IMG': true, 'IFRAME': true, 'BASE': true, 'FORM': true }; var badTagsForDataURI = { 'EMBED': true }; var badAttributes = { 'href': true, 'src': true, 'background': true, 'action': true }; exports.badAttributes = badAttributes; var badAttributesForDataURI = { 'src': true }; function sanitizeAttributeValue(dom, element, attribute, value) { var tagName; if (!element) { tagName = null; } else { tagName = element.tagName.toUpperCase(); } if (value && value.toHTML) { return value.toHTML(); } if ((tagName === null || badTags[tagName]) && badAttributes[attribute]) { var protocol = dom.protocolForURL(value); if (badProtocols[protocol] === true) { return 'unsafe:' + value; } } if (badTagsForDataURI[tagName] && badAttributesForDataURI[attribute]) { return 'unsafe:' + value; } return value; } }); enifed("morph-attr", ["exports", "morph-attr/sanitize-attribute-value", "dom-helper/prop", "dom-helper/build-html-dom", "htmlbars-util"], function (exports, _morphAttrSanitizeAttributeValue, _domHelperProp, _domHelperBuildHtmlDom, _htmlbarsUtil) { "use strict"; function getProperty() { return this.domHelper.getPropertyStrict(this.element, this.attrName); } function updateProperty(value) { if (this._renderedInitially === true || !_domHelperProp.isAttrRemovalValue(value)) { var element = this.element; var attrName = this.attrName; if (attrName === 'value' && element.tagName === 'INPUT' && element.value === value) { // Do nothing. Attempts to avoid accidently changing the input cursor location. // See https://github.com/tildeio/htmlbars/pull/447 for more details. } else { // do not render if initial value is undefined or null this.domHelper.setPropertyStrict(element, attrName, value); } } this._renderedInitially = true; } function getAttribute() { return this.domHelper.getAttribute(this.element, this.attrName); } function updateAttribute(value) { if (_domHelperProp.isAttrRemovalValue(value)) { this.domHelper.removeAttribute(this.element, this.attrName); } else { this.domHelper.setAttribute(this.element, this.attrName, value); } } function getAttributeNS() { return this.domHelper.getAttributeNS(this.element, this.namespace, this.attrName); } function updateAttributeNS(value) { if (_domHelperProp.isAttrRemovalValue(value)) { this.domHelper.removeAttribute(this.element, this.attrName); } else { this.domHelper.setAttributeNS(this.element, this.namespace, this.attrName, value); } } var UNSET = { unset: true }; var guid = 1; AttrMorph.create = function (element, attrName, domHelper, namespace) { var ns = _htmlbarsUtil.getAttrNamespace(attrName, namespace); if (ns) { return new AttributeNSAttrMorph(element, attrName, domHelper, ns); } else { return createNonNamespacedAttrMorph(element, attrName, domHelper); } }; function createNonNamespacedAttrMorph(element, attrName, domHelper) { var _normalizeProperty = _domHelperProp.normalizeProperty(element, attrName); var normalized = _normalizeProperty.normalized; var type = _normalizeProperty.type; if (element.namespaceURI === _domHelperBuildHtmlDom.svgNamespace || attrName === 'style' || type === 'attr') { return new AttributeAttrMorph(element, normalized, domHelper); } else { return new PropertyAttrMorph(element, normalized, domHelper); } } function AttrMorph(element, attrName, domHelper) { this.element = element; this.domHelper = domHelper; this.attrName = attrName; this._state = undefined; this.isDirty = false; this.isSubtreeDirty = false; this.escaped = true; this.lastValue = UNSET; this.lastResult = null; this.lastYielded = null; this.childNodes = null; this.linkedParams = null; this.linkedResult = null; this.guid = "attr" + guid++; this.seen = false; this.ownerNode = null; this.rendered = false; this._renderedInitially = false; this.namespace = undefined; this.didInit(); } AttrMorph.prototype.getState = function () { if (!this._state) { this._state = {}; } return this._state; }; AttrMorph.prototype.setState = function (newState) { /*jshint -W093 */ return this._state = newState; }; AttrMorph.prototype.didInit = function () {}; AttrMorph.prototype.willSetContent = function () {}; AttrMorph.prototype.setContent = function (value) { this.willSetContent(value); if (this.lastValue === value) { return; } this.lastValue = value; if (this.escaped) { var sanitized = _morphAttrSanitizeAttributeValue.sanitizeAttributeValue(this.domHelper, this.element, this.attrName, value); this._update(sanitized, this.namespace); } else { this._update(value, this.namespace); } }; AttrMorph.prototype.getContent = function () { var value = this.lastValue = this._get(); return value; }; // renderAndCleanup calls `clear` on all items in the morph map // just before calling `destroy` on the morph. // // As a future refactor this could be changed to set the property // back to its original/default value. AttrMorph.prototype.clear = function () {}; AttrMorph.prototype.destroy = function () { this.element = null; this.domHelper = null; }; AttrMorph.prototype._$superAttrMorph = AttrMorph; function PropertyAttrMorph(element, attrName, domHelper) { this._$superAttrMorph(element, attrName, domHelper); } PropertyAttrMorph.prototype = Object.create(AttrMorph.prototype); PropertyAttrMorph.prototype._update = updateProperty; PropertyAttrMorph.prototype._get = getProperty; function AttributeNSAttrMorph(element, attrName, domHelper, namespace) { this._$superAttrMorph(element, attrName, domHelper); this.namespace = namespace; } AttributeNSAttrMorph.prototype = Object.create(AttrMorph.prototype); AttributeNSAttrMorph.prototype._update = updateAttributeNS; AttributeNSAttrMorph.prototype._get = getAttributeNS; function AttributeAttrMorph(element, attrName, domHelper) { this._$superAttrMorph(element, attrName, domHelper); } AttributeAttrMorph.prototype = Object.create(AttrMorph.prototype); AttributeAttrMorph.prototype._update = updateAttribute; AttributeAttrMorph.prototype._get = getAttribute; exports.default = AttrMorph; exports.sanitizeAttributeValue = _morphAttrSanitizeAttributeValue.sanitizeAttributeValue; }); enifed('morph-range/morph-list', ['exports', 'morph-range/utils'], function (exports, _morphRangeUtils) { 'use strict'; function MorphList() { // morph graph this.firstChildMorph = null; this.lastChildMorph = null; this.mountedMorph = null; } var prototype = MorphList.prototype; prototype.clear = function MorphList$clear() { var current = this.firstChildMorph; while (current) { var next = current.nextMorph; current.previousMorph = null; current.nextMorph = null; current.parentMorphList = null; current = next; } this.firstChildMorph = this.lastChildMorph = null; }; prototype.destroy = function MorphList$destroy() {}; prototype.appendMorph = function MorphList$appendMorph(morph) { this.insertBeforeMorph(morph, null); }; prototype.insertBeforeMorph = function MorphList$insertBeforeMorph(morph, referenceMorph) { if (morph.parentMorphList !== null) { morph.unlink(); } if (referenceMorph && referenceMorph.parentMorphList !== this) { throw new Error('The morph before which the new morph is to be inserted is not a child of this morph.'); } var mountedMorph = this.mountedMorph; if (mountedMorph) { var parentNode = mountedMorph.firstNode.parentNode; var referenceNode = referenceMorph ? referenceMorph.firstNode : mountedMorph.lastNode.nextSibling; _morphRangeUtils.insertBefore(parentNode, morph.firstNode, morph.lastNode, referenceNode); // was not in list mode replace current content if (!this.firstChildMorph) { _morphRangeUtils.clear(this.mountedMorph.firstNode.parentNode, this.mountedMorph.firstNode, this.mountedMorph.lastNode); } } morph.parentMorphList = this; var previousMorph = referenceMorph ? referenceMorph.previousMorph : this.lastChildMorph; if (previousMorph) { previousMorph.nextMorph = morph; morph.previousMorph = previousMorph; } else { this.firstChildMorph = morph; } if (referenceMorph) { referenceMorph.previousMorph = morph; morph.nextMorph = referenceMorph; } else { this.lastChildMorph = morph; } this.firstChildMorph._syncFirstNode(); this.lastChildMorph._syncLastNode(); }; prototype.removeChildMorph = function MorphList$removeChildMorph(morph) { if (morph.parentMorphList !== this) { throw new Error("Cannot remove a morph from a parent it is not inside of"); } morph.destroy(); }; exports.default = MorphList; }); enifed('morph-range/morph-list.umd', ['exports', 'morph-range/morph-list'], function (exports, _morphRangeMorphList) { 'use strict'; (function (root, factory) { if (typeof define === 'function' && define.amd) { define([], factory); } else if (typeof exports === 'object') { module.exports = factory(); } else { root.MorphList = factory(); } })(undefined, function () { return _morphRangeMorphList.default; }); }); enifed("morph-range/utils", ["exports"], function (exports) { // inclusive of both nodes "use strict"; exports.clear = clear; exports.insertBefore = insertBefore; function clear(parentNode, firstNode, lastNode) { if (!parentNode) { return; } var node = firstNode; var nextNode; do { nextNode = node.nextSibling; parentNode.removeChild(node); if (node === lastNode) { break; } node = nextNode; } while (node); } function insertBefore(parentNode, firstNode, lastNode, refNode) { var node = firstNode; var nextNode; do { nextNode = node.nextSibling; parentNode.insertBefore(node, refNode); if (node === lastNode) { break; } node = nextNode; } while (node); } }); enifed('morph-range', ['exports', 'morph-range/utils'], function (exports, _morphRangeUtils) { 'use strict'; // constructor just initializes the fields // use one of the static initializers to create a valid morph. function Morph(domHelper, contextualElement) { this.domHelper = domHelper; // context if content if current content is detached this.contextualElement = contextualElement; // inclusive range of morph // these should be nodeType 1, 3, or 8 this.firstNode = null; this.lastNode = null; // flag to force text to setContent to be treated as html this.parseTextAsHTML = false; // morph list graph this.parentMorphList = null; this.previousMorph = null; this.nextMorph = null; } Morph.empty = function (domHelper, contextualElement) { var morph = new Morph(domHelper, contextualElement); morph.clear(); return morph; }; Morph.create = function (domHelper, contextualElement, node) { var morph = new Morph(domHelper, contextualElement); morph.setNode(node); return morph; }; Morph.attach = function (domHelper, contextualElement, firstNode, lastNode) { var morph = new Morph(domHelper, contextualElement); morph.setRange(firstNode, lastNode); return morph; }; Morph.prototype.setContent = function Morph$setContent(content) { if (content === null || content === undefined) { return this.clear(); } var type = typeof content; switch (type) { case 'string': if (this.parseTextAsHTML) { return this.domHelper.setMorphHTML(this, content); } return this.setText(content); case 'object': if (typeof content.nodeType === 'number') { return this.setNode(content); } /* Handlebars.SafeString */ if (typeof content.toHTML === 'function') { return this.setHTML(content.toHTML()); } if (this.parseTextAsHTML) { return this.setHTML(content.toString()); } /* falls through */ case 'boolean': case 'number': return this.setText(content.toString()); case 'function': raiseCannotBindToFunction(content); default: throw new TypeError('unsupported content'); } }; function raiseCannotBindToFunction(content) { var functionName = content.name; var message; if (functionName) { message = 'Unsupported Content: Cannot bind to function `' + functionName + '`'; } else { message = 'Unsupported Content: Cannot bind to function'; } throw new TypeError(message); } Morph.prototype.clear = function Morph$clear() { var node = this.setNode(this.domHelper.createComment('')); return node; }; Morph.prototype.setText = function Morph$setText(text) { var firstNode = this.firstNode; var lastNode = this.lastNode; if (firstNode && lastNode === firstNode && firstNode.nodeType === 3) { firstNode.nodeValue = text; return firstNode; } return this.setNode(text ? this.domHelper.createTextNode(text) : this.domHelper.createComment('')); }; Morph.prototype.setNode = function Morph$setNode(newNode) { var firstNode, lastNode; switch (newNode.nodeType) { case 3: firstNode = newNode; lastNode = newNode; break; case 11: firstNode = newNode.firstChild; lastNode = newNode.lastChild; if (firstNode === null) { firstNode = this.domHelper.createComment(''); newNode.appendChild(firstNode); lastNode = firstNode; } break; default: firstNode = newNode; lastNode = newNode; break; } this.setRange(firstNode, lastNode); return newNode; }; Morph.prototype.setRange = function (firstNode, lastNode) { var previousFirstNode = this.firstNode; if (previousFirstNode !== null) { var parentNode = previousFirstNode.parentNode; if (parentNode !== null) { _morphRangeUtils.insertBefore(parentNode, firstNode, lastNode, previousFirstNode); _morphRangeUtils.clear(parentNode, previousFirstNode, this.lastNode); } } this.firstNode = firstNode; this.lastNode = lastNode; if (this.parentMorphList) { this._syncFirstNode(); this._syncLastNode(); } }; Morph.prototype.destroy = function Morph$destroy() { this.unlink(); var firstNode = this.firstNode; var lastNode = this.lastNode; var parentNode = firstNode && firstNode.parentNode; this.firstNode = null; this.lastNode = null; _morphRangeUtils.clear(parentNode, firstNode, lastNode); }; Morph.prototype.unlink = function Morph$unlink() { var parentMorphList = this.parentMorphList; var previousMorph = this.previousMorph; var nextMorph = this.nextMorph; if (previousMorph) { if (nextMorph) { previousMorph.nextMorph = nextMorph; nextMorph.previousMorph = previousMorph; } else { previousMorph.nextMorph = null; parentMorphList.lastChildMorph = previousMorph; } } else { if (nextMorph) { nextMorph.previousMorph = null; parentMorphList.firstChildMorph = nextMorph; } else if (parentMorphList) { parentMorphList.lastChildMorph = parentMorphList.firstChildMorph = null; } } this.parentMorphList = null; this.nextMorph = null; this.previousMorph = null; if (parentMorphList && parentMorphList.mountedMorph) { if (!parentMorphList.firstChildMorph) { // list is empty parentMorphList.mountedMorph.clear(); return; } else { parentMorphList.firstChildMorph._syncFirstNode(); parentMorphList.lastChildMorph._syncLastNode(); } } }; Morph.prototype.setHTML = function (text) { var fragment = this.domHelper.parseHTML(text, this.contextualElement); return this.setNode(fragment); }; Morph.prototype.setMorphList = function Morph$appendMorphList(morphList) { morphList.mountedMorph = this; this.clear(); var originalFirstNode = this.firstNode; if (morphList.firstChildMorph) { this.firstNode = morphList.firstChildMorph.firstNode; this.lastNode = morphList.lastChildMorph.lastNode; var current = morphList.firstChildMorph; while (current) { var next = current.nextMorph; current.insertBeforeNode(originalFirstNode, null); current = next; } originalFirstNode.parentNode.removeChild(originalFirstNode); } }; Morph.prototype._syncFirstNode = function Morph$syncFirstNode() { var morph = this; var parentMorphList; while (parentMorphList = morph.parentMorphList) { if (parentMorphList.mountedMorph === null) { break; } if (morph !== parentMorphList.firstChildMorph) { break; } if (morph.firstNode === parentMorphList.mountedMorph.firstNode) { break; } parentMorphList.mountedMorph.firstNode = morph.firstNode; morph = parentMorphList.mountedMorph; } }; Morph.prototype._syncLastNode = function Morph$syncLastNode() { var morph = this; var parentMorphList; while (parentMorphList = morph.parentMorphList) { if (parentMorphList.mountedMorph === null) { break; } if (morph !== parentMorphList.lastChildMorph) { break; } if (morph.lastNode === parentMorphList.mountedMorph.lastNode) { break; } parentMorphList.mountedMorph.lastNode = morph.lastNode; morph = parentMorphList.mountedMorph; } }; Morph.prototype.insertBeforeNode = function Morph$insertBeforeNode(parentNode, refNode) { _morphRangeUtils.insertBefore(parentNode, this.firstNode, this.lastNode, refNode); }; Morph.prototype.appendToNode = function Morph$appendToNode(parentNode) { _morphRangeUtils.insertBefore(parentNode, this.firstNode, this.lastNode, null); }; exports.default = Morph; }); enifed("route-recognizer/dsl", ["exports"], function (exports) { "use strict"; function Target(path, matcher, delegate) { this.path = path; this.matcher = matcher; this.delegate = delegate; } Target.prototype = { to: function (target, callback) { var delegate = this.delegate; if (delegate && delegate.willAddRoute) { target = delegate.willAddRoute(this.matcher.target, target); } this.matcher.add(this.path, target); if (callback) { if (callback.length === 0) { throw new Error("You must have an argument in the function passed to `to`"); } this.matcher.addChild(this.path, target, callback, this.delegate); } return this; } }; function Matcher(target) { this.routes = {}; this.children = {}; this.target = target; } Matcher.prototype = { add: function (path, handler) { this.routes[path] = handler; }, addChild: function (path, target, callback, delegate) { var matcher = new Matcher(target); this.children[path] = matcher; var match = generateMatch(path, matcher, delegate); if (delegate && delegate.contextEntered) { delegate.contextEntered(target, match); } callback(match); } }; function generateMatch(startingPath, matcher, delegate) { return function (path, nestedCallback) { var fullPath = startingPath + path; if (nestedCallback) { nestedCallback(generateMatch(fullPath, matcher, delegate)); } else { return new Target(startingPath + path, matcher, delegate); } }; } function addRoute(routeArray, path, handler) { var len = 0; for (var i = 0, l = routeArray.length; i < l; i++) { len += routeArray[i].path.length; } path = path.substr(len); var route = { path: path, handler: handler }; routeArray.push(route); } function eachRoute(baseRoute, matcher, callback, binding) { var routes = matcher.routes; for (var path in routes) { if (routes.hasOwnProperty(path)) { var routeArray = baseRoute.slice(); addRoute(routeArray, path, routes[path]); if (matcher.children[path]) { eachRoute(routeArray, matcher.children[path], callback, binding); } else { callback.call(binding, routeArray); } } } } exports.default = function (callback, addRouteCallback) { var matcher = new Matcher(); callback(generateMatch("", matcher, this.delegate)); eachRoute([], matcher, function (route) { if (addRouteCallback) { addRouteCallback(this, route); } else { this.add(route); } }, this); }; }); enifed('route-recognizer', ['exports', 'route-recognizer/dsl'], function (exports, _routeRecognizerDsl) { 'use strict'; var specials = ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\']; var escapeRegex = new RegExp('(\\' + specials.join('|\\') + ')', 'g'); function isArray(test) { return Object.prototype.toString.call(test) === "[object Array]"; } // A Segment represents a segment in the original route description. // Each Segment type provides an `eachChar` and `regex` method. // // The `eachChar` method invokes the callback with one or more character // specifications. A character specification consumes one or more input // characters. // // The `regex` method returns a regex fragment for the segment. If the // segment is a dynamic of star segment, the regex fragment also includes // a capture. // // A character specification contains: // // * `validChars`: a String with a list of all valid characters, or // * `invalidChars`: a String with a list of all invalid characters // * `repeat`: true if the character specification can repeat function StaticSegment(string) { this.string = string; } StaticSegment.prototype = { eachChar: function (callback) { var string = this.string, ch; for (var i = 0, l = string.length; i < l; i++) { ch = string.charAt(i); callback({ validChars: ch }); } }, regex: function () { return this.string.replace(escapeRegex, '\\$1'); }, generate: function () { return this.string; } }; function DynamicSegment(name) { this.name = name; } DynamicSegment.prototype = { eachChar: function (callback) { callback({ invalidChars: "/", repeat: true }); }, regex: function () { return "([^/]+)"; }, generate: function (params) { return params[this.name]; } }; function StarSegment(name) { this.name = name; } StarSegment.prototype = { eachChar: function (callback) { callback({ invalidChars: "", repeat: true }); }, regex: function () { return "(.+)"; }, generate: function (params) { return params[this.name]; } }; function EpsilonSegment() {} EpsilonSegment.prototype = { eachChar: function () {}, regex: function () { return ""; }, generate: function () { return ""; } }; function parse(route, names, types) { // normalize route as not starting with a "/". Recognition will // also normalize. if (route.charAt(0) === "/") { route = route.substr(1); } var segments = route.split("/"), results = []; for (var i = 0, l = segments.length; i < l; i++) { var segment = segments[i], match; if (match = segment.match(/^:([^\/]+)$/)) { results.push(new DynamicSegment(match[1])); names.push(match[1]); types.dynamics++; } else if (match = segment.match(/^\*([^\/]+)$/)) { results.push(new StarSegment(match[1])); names.push(match[1]); types.stars++; } else if (segment === "") { results.push(new EpsilonSegment()); } else { results.push(new StaticSegment(segment)); types.statics++; } } return results; } // A State has a character specification and (`charSpec`) and a list of possible // subsequent states (`nextStates`). // // If a State is an accepting state, it will also have several additional // properties: // // * `regex`: A regular expression that is used to extract parameters from paths // that reached this accepting state. // * `handlers`: Information on how to convert the list of captures into calls // to registered handlers with the specified parameters // * `types`: How many static, dynamic or star segments in this route. Used to // decide which route to use if multiple registered routes match a path. // // Currently, State is implemented naively by looping over `nextStates` and // comparing a character specification against a character. A more efficient // implementation would use a hash of keys pointing at one or more next states. function State(charSpec) { this.charSpec = charSpec; this.nextStates = []; } State.prototype = { get: function (charSpec) { var nextStates = this.nextStates; for (var i = 0, l = nextStates.length; i < l; i++) { var child = nextStates[i]; var isEqual = child.charSpec.validChars === charSpec.validChars; isEqual = isEqual && child.charSpec.invalidChars === charSpec.invalidChars; if (isEqual) { return child; } } }, put: function (charSpec) { var state; // If the character specification already exists in a child of the current // state, just return that state. if (state = this.get(charSpec)) { return state; } // Make a new state for the character spec state = new State(charSpec); // Insert the new state as a child of the current state this.nextStates.push(state); // If this character specification repeats, insert the new state as a child // of itself. Note that this will not trigger an infinite loop because each // transition during recognition consumes a character. if (charSpec.repeat) { state.nextStates.push(state); } // Return the new state return state; }, // Find a list of child states matching the next character match: function (ch) { // DEBUG "Processing `" + ch + "`:" var nextStates = this.nextStates, child, charSpec, chars; // DEBUG " " + debugState(this) var returned = []; for (var i = 0, l = nextStates.length; i < l; i++) { child = nextStates[i]; charSpec = child.charSpec; if (typeof (chars = charSpec.validChars) !== 'undefined') { if (chars.indexOf(ch) !== -1) { returned.push(child); } } else if (typeof (chars = charSpec.invalidChars) !== 'undefined') { if (chars.indexOf(ch) === -1) { returned.push(child); } } } return returned; } /** IF DEBUG , debug: function() { var charSpec = this.charSpec, debug = "[", chars = charSpec.validChars || charSpec.invalidChars; if (charSpec.invalidChars) { debug += "^"; } debug += chars; debug += "]"; if (charSpec.repeat) { debug += "+"; } return debug; } END IF **/ }; /** IF DEBUG function debug(log) { console.log(log); } function debugState(state) { return state.nextStates.map(function(n) { if (n.nextStates.length === 0) { return "( " + n.debug() + " [accepting] )"; } return "( " + n.debug() + " " + n.nextStates.map(function(s) { return s.debug() }).join(" or ") + " )"; }).join(", ") } END IF **/ // This is a somewhat naive strategy, but should work in a lot of cases // A better strategy would properly resolve /posts/:id/new and /posts/edit/:id. // // This strategy generally prefers more static and less dynamic matching. // Specifically, it // // * prefers fewer stars to more, then // * prefers using stars for less of the match to more, then // * prefers fewer dynamic segments to more, then // * prefers more static segments to more function sortSolutions(states) { return states.sort(function (a, b) { if (a.types.stars !== b.types.stars) { return a.types.stars - b.types.stars; } if (a.types.stars) { if (a.types.statics !== b.types.statics) { return b.types.statics - a.types.statics; } if (a.types.dynamics !== b.types.dynamics) { return b.types.dynamics - a.types.dynamics; } } if (a.types.dynamics !== b.types.dynamics) { return a.types.dynamics - b.types.dynamics; } if (a.types.statics !== b.types.statics) { return b.types.statics - a.types.statics; } return 0; }); } function recognizeChar(states, ch) { var nextStates = []; for (var i = 0, l = states.length; i < l; i++) { var state = states[i]; nextStates = nextStates.concat(state.match(ch)); } return nextStates; } var oCreate = Object.create || function (proto) { function F() {} F.prototype = proto; return new F(); }; function RecognizeResults(queryParams) { this.queryParams = queryParams || {}; } RecognizeResults.prototype = oCreate({ splice: Array.prototype.splice, slice: Array.prototype.slice, push: Array.prototype.push, length: 0, queryParams: null }); function findHandler(state, path, queryParams) { var handlers = state.handlers, regex = state.regex; var captures = path.match(regex), currentCapture = 1; var result = new RecognizeResults(queryParams); for (var i = 0, l = handlers.length; i < l; i++) { var handler = handlers[i], names = handler.names, params = {}; for (var j = 0, m = names.length; j < m; j++) { params[names[j]] = captures[currentCapture++]; } result.push({ handler: handler.handler, params: params, isDynamic: !!names.length }); } return result; } function addSegment(currentState, segment) { segment.eachChar(function (ch) { var state; currentState = currentState.put(ch); }); return currentState; } function decodeQueryParamPart(part) { // http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1 part = part.replace(/\+/gm, '%20'); return decodeURIComponent(part); } // The main interface var RouteRecognizer = function () { this.rootState = new State(); this.names = {}; }; RouteRecognizer.prototype = { add: function (routes, options) { var currentState = this.rootState, regex = "^", types = { statics: 0, dynamics: 0, stars: 0 }, handlers = [], allSegments = [], name; var isEmpty = true; for (var i = 0, l = routes.length; i < l; i++) { var route = routes[i], names = []; var segments = parse(route.path, names, types); allSegments = allSegments.concat(segments); for (var j = 0, m = segments.length; j < m; j++) { var segment = segments[j]; if (segment instanceof EpsilonSegment) { continue; } isEmpty = false; // Add a "/" for the new segment currentState = currentState.put({ validChars: "/" }); regex += "/"; // Add a representation of the segment to the NFA and regex currentState = addSegment(currentState, segment); regex += segment.regex(); } var handler = { handler: route.handler, names: names }; handlers.push(handler); } if (isEmpty) { currentState = currentState.put({ validChars: "/" }); regex += "/"; } currentState.handlers = handlers; currentState.regex = new RegExp(regex + "$"); currentState.types = types; if (name = options && options.as) { this.names[name] = { segments: allSegments, handlers: handlers }; } }, handlersFor: function (name) { var route = this.names[name], result = []; if (!route) { throw new Error("There is no route named " + name); } for (var i = 0, l = route.handlers.length; i < l; i++) { result.push(route.handlers[i]); } return result; }, hasRoute: function (name) { return !!this.names[name]; }, generate: function (name, params) { var route = this.names[name], output = ""; if (!route) { throw new Error("There is no route named " + name); } var segments = route.segments; for (var i = 0, l = segments.length; i < l; i++) { var segment = segments[i]; if (segment instanceof EpsilonSegment) { continue; } output += "/"; output += segment.generate(params); } if (output.charAt(0) !== '/') { output = '/' + output; } if (params && params.queryParams) { output += this.generateQueryString(params.queryParams, route.handlers); } return output; }, generateQueryString: function (params, handlers) { var pairs = []; var keys = []; for (var key in params) { if (params.hasOwnProperty(key)) { keys.push(key); } } keys.sort(); for (var i = 0, len = keys.length; i < len; i++) { key = keys[i]; var value = params[key]; if (value == null) { continue; } var pair = encodeURIComponent(key); if (isArray(value)) { for (var j = 0, l = value.length; j < l; j++) { var arrayPair = key + '[]' + '=' + encodeURIComponent(value[j]); pairs.push(arrayPair); } } else { pair += "=" + encodeURIComponent(value); pairs.push(pair); } } if (pairs.length === 0) { return ''; } return "?" + pairs.join("&"); }, parseQueryString: function (queryString) { var pairs = queryString.split("&"), queryParams = {}; for (var i = 0; i < pairs.length; i++) { var pair = pairs[i].split('='), key = decodeQueryParamPart(pair[0]), keyLength = key.length, isArray = false, value; if (pair.length === 1) { value = 'true'; } else { //Handle arrays if (keyLength > 2 && key.slice(keyLength - 2) === '[]') { isArray = true; key = key.slice(0, keyLength - 2); if (!queryParams[key]) { queryParams[key] = []; } } value = pair[1] ? decodeQueryParamPart(pair[1]) : ''; } if (isArray) { queryParams[key].push(value); } else { queryParams[key] = value; } } return queryParams; }, recognize: function (path) { var states = [this.rootState], pathLen, i, l, queryStart, queryParams = {}, isSlashDropped = false; queryStart = path.indexOf('?'); if (queryStart !== -1) { var queryString = path.substr(queryStart + 1, path.length); path = path.substr(0, queryStart); queryParams = this.parseQueryString(queryString); } path = decodeURI(path); // DEBUG GROUP path if (path.charAt(0) !== "/") { path = "/" + path; } pathLen = path.length; if (pathLen > 1 && path.charAt(pathLen - 1) === "/") { path = path.substr(0, pathLen - 1); isSlashDropped = true; } for (i = 0, l = path.length; i < l; i++) { states = recognizeChar(states, path.charAt(i)); if (!states.length) { break; } } // END DEBUG GROUP var solutions = []; for (i = 0, l = states.length; i < l; i++) { if (states[i].handlers) { solutions.push(states[i]); } } states = sortSolutions(solutions); var state = solutions[0]; if (state && state.handlers) { // if a trailing slash was dropped and a star segment is the last segment // specified, put the trailing slash back if (isSlashDropped && state.regex.source.slice(-5) === "(.+)$") { path = path + "/"; } return findHandler(state, path, queryParams); } } }; RouteRecognizer.prototype.map = _routeRecognizerDsl.default; RouteRecognizer.VERSION = '0.1.5'; exports.default = RouteRecognizer; }); enifed('router/handler-info/factory', ['exports', 'router/handler-info/resolved-handler-info', 'router/handler-info/unresolved-handler-info-by-object', 'router/handler-info/unresolved-handler-info-by-param'], function (exports, _routerHandlerInfoResolvedHandlerInfo, _routerHandlerInfoUnresolvedHandlerInfoByObject, _routerHandlerInfoUnresolvedHandlerInfoByParam) { 'use strict'; handlerInfoFactory.klasses = { resolved: _routerHandlerInfoResolvedHandlerInfo.default, param: _routerHandlerInfoUnresolvedHandlerInfoByParam.default, object: _routerHandlerInfoUnresolvedHandlerInfoByObject.default }; function handlerInfoFactory(name, props) { var Ctor = handlerInfoFactory.klasses[name], handlerInfo = new Ctor(props || {}); handlerInfo.factory = handlerInfoFactory; return handlerInfo; } exports.default = handlerInfoFactory; }); enifed('router/handler-info/resolved-handler-info', ['exports', 'router/handler-info', 'router/utils', 'rsvp/promise'], function (exports, _routerHandlerInfo, _routerUtils, _rsvpPromise) { 'use strict'; var ResolvedHandlerInfo = _routerUtils.subclass(_routerHandlerInfo.default, { resolve: function (shouldContinue, payload) { // A ResolvedHandlerInfo just resolved with itself. if (payload && payload.resolvedModels) { payload.resolvedModels[this.name] = this.context; } return _rsvpPromise.default.resolve(this, this.promiseLabel("Resolve")); }, getUnresolved: function () { return this.factory('param', { name: this.name, handler: this.handler, params: this.params }); }, isResolved: true }); exports.default = ResolvedHandlerInfo; }); enifed('router/handler-info/unresolved-handler-info-by-object', ['exports', 'router/handler-info', 'router/utils', 'rsvp/promise'], function (exports, _routerHandlerInfo, _routerUtils, _rsvpPromise) { 'use strict'; var UnresolvedHandlerInfoByObject = _routerUtils.subclass(_routerHandlerInfo.default, { getModel: function (payload) { this.log(payload, this.name + ": resolving provided model"); return _rsvpPromise.default.resolve(this.context); }, initialize: function (props) { this.names = props.names || []; this.context = props.context; }, /** @private Serializes a handler using its custom `serialize` method or by a default that looks up the expected property name from the dynamic segment. @param {Object} model the model to be serialized for this handler */ serialize: function (_model) { var model = _model || this.context, names = this.names, handler = this.handler; var object = {}; if (_routerUtils.isParam(model)) { object[names[0]] = model; return object; } // Use custom serialize if it exists. if (handler.serialize) { return handler.serialize(model, names); } if (names.length !== 1) { return; } var name = names[0]; if (/_id$/.test(name)) { object[name] = model.id; } else { object[name] = model; } return object; } }); exports.default = UnresolvedHandlerInfoByObject; }); enifed('router/handler-info/unresolved-handler-info-by-param', ['exports', 'router/handler-info', 'router/utils'], function (exports, _routerHandlerInfo, _routerUtils) { 'use strict'; // Generated by URL transitions and non-dynamic route segments in named Transitions. var UnresolvedHandlerInfoByParam = _routerUtils.subclass(_routerHandlerInfo.default, { initialize: function (props) { this.params = props.params || {}; }, getModel: function (payload) { var fullParams = this.params; if (payload && payload.queryParams) { fullParams = {}; _routerUtils.merge(fullParams, this.params); fullParams.queryParams = payload.queryParams; } var handler = this.handler; var hookName = _routerUtils.resolveHook(handler, 'deserialize') || _routerUtils.resolveHook(handler, 'model'); return this.runSharedModelHook(payload, hookName, [fullParams]); } }); exports.default = UnresolvedHandlerInfoByParam; }); enifed('router/handler-info', ['exports', 'router/utils', 'rsvp/promise'], function (exports, _routerUtils, _rsvpPromise) { 'use strict'; function HandlerInfo(_props) { var props = _props || {}; _routerUtils.merge(this, props); this.initialize(props); } HandlerInfo.prototype = { name: null, handler: null, params: null, context: null, // Injected by the handler info factory. factory: null, initialize: function () {}, log: function (payload, message) { if (payload.log) { payload.log(this.name + ': ' + message); } }, promiseLabel: function (label) { return _routerUtils.promiseLabel("'" + this.name + "' " + label); }, getUnresolved: function () { return this; }, serialize: function () { return this.params || {}; }, resolve: function (shouldContinue, payload) { var checkForAbort = _routerUtils.bind(this, this.checkForAbort, shouldContinue), beforeModel = _routerUtils.bind(this, this.runBeforeModelHook, payload), model = _routerUtils.bind(this, this.getModel, payload), afterModel = _routerUtils.bind(this, this.runAfterModelHook, payload), becomeResolved = _routerUtils.bind(this, this.becomeResolved, payload); return _rsvpPromise.default.resolve(undefined, this.promiseLabel("Start handler")).then(checkForAbort, null, this.promiseLabel("Check for abort")).then(beforeModel, null, this.promiseLabel("Before model")).then(checkForAbort, null, this.promiseLabel("Check if aborted during 'beforeModel' hook")).then(model, null, this.promiseLabel("Model")).then(checkForAbort, null, this.promiseLabel("Check if aborted in 'model' hook")).then(afterModel, null, this.promiseLabel("After model")).then(checkForAbort, null, this.promiseLabel("Check if aborted in 'afterModel' hook")).then(becomeResolved, null, this.promiseLabel("Become resolved")); }, runBeforeModelHook: function (payload) { if (payload.trigger) { payload.trigger(true, 'willResolveModel', payload, this.handler); } return this.runSharedModelHook(payload, 'beforeModel', []); }, runAfterModelHook: function (payload, resolvedModel) { // Stash the resolved model on the payload. // This makes it possible for users to swap out // the resolved model in afterModel. var name = this.name; this.stashResolvedModel(payload, resolvedModel); return this.runSharedModelHook(payload, 'afterModel', [resolvedModel]).then(function () { // Ignore the fulfilled value returned from afterModel. // Return the value stashed in resolvedModels, which // might have been swapped out in afterModel. return payload.resolvedModels[name]; }, null, this.promiseLabel("Ignore fulfillment value and return model value")); }, runSharedModelHook: function (payload, hookName, args) { this.log(payload, "calling " + hookName + " hook"); if (this.queryParams) { args.push(this.queryParams); } args.push(payload); var result = _routerUtils.applyHook(this.handler, hookName, args); if (result && result.isTransition) { result = null; } return _rsvpPromise.default.resolve(result, this.promiseLabel("Resolve value returned from one of the model hooks")); }, // overridden by subclasses getModel: null, checkForAbort: function (shouldContinue, promiseValue) { return _rsvpPromise.default.resolve(shouldContinue(), this.promiseLabel("Check for abort")).then(function () { // We don't care about shouldContinue's resolve value; // pass along the original value passed to this fn. return promiseValue; }, null, this.promiseLabel("Ignore fulfillment value and continue")); }, stashResolvedModel: function (payload, resolvedModel) { payload.resolvedModels = payload.resolvedModels || {}; payload.resolvedModels[this.name] = resolvedModel; }, becomeResolved: function (payload, resolvedContext) { var params = this.serialize(resolvedContext); if (payload) { this.stashResolvedModel(payload, resolvedContext); payload.params = payload.params || {}; payload.params[this.name] = params; } return this.factory('resolved', { context: resolvedContext, name: this.name, handler: this.handler, params: params }); }, shouldSupercede: function (other) { // Prefer this newer handlerInfo over `other` if: // 1) The other one doesn't exist // 2) The names don't match // 3) This handler has a context that doesn't match // the other one (or the other one doesn't have one). // 4) This handler has parameters that don't match the other. if (!other) { return true; } var contextsMatch = other.context === this.context; return other.name !== this.name || this.hasOwnProperty('context') && !contextsMatch || this.hasOwnProperty('params') && !paramsMatch(this.params, other.params); } }; function paramsMatch(a, b) { if (!a ^ !b) { // Only one is null. return false; } if (!a) { // Both must be null. return true; } // Note: this assumes that both params have the same // number of keys, but since we're comparing the // same handlers, they should. for (var k in a) { if (a.hasOwnProperty(k) && a[k] !== b[k]) { return false; } } return true; } exports.default = HandlerInfo; }); enifed('router/router', ['exports', 'route-recognizer', 'rsvp/promise', 'router/utils', 'router/transition-state', 'router/transition', 'router/transition-intent/named-transition-intent', 'router/transition-intent/url-transition-intent', 'router/handler-info'], function (exports, _routeRecognizer, _rsvpPromise, _routerUtils, _routerTransitionState, _routerTransition, _routerTransitionIntentNamedTransitionIntent, _routerTransitionIntentUrlTransitionIntent, _routerHandlerInfo) { 'use strict'; var pop = Array.prototype.pop; function Router(_options) { var options = _options || {}; this.getHandler = options.getHandler || this.getHandler; this.updateURL = options.updateURL || this.updateURL; this.replaceURL = options.replaceURL || this.replaceURL; this.didTransition = options.didTransition || this.didTransition; this.willTransition = options.willTransition || this.willTransition; this.delegate = options.delegate || this.delegate; this.triggerEvent = options.triggerEvent || this.triggerEvent; this.log = options.log || this.log; this.recognizer = new _routeRecognizer.default(); this.reset(); } function getTransitionByIntent(intent, isIntermediate) { var wasTransitioning = !!this.activeTransition; var oldState = wasTransitioning ? this.activeTransition.state : this.state; var newTransition; var newState = intent.applyToState(oldState, this.recognizer, this.getHandler, isIntermediate); var queryParamChangelist = _routerUtils.getChangelist(oldState.queryParams, newState.queryParams); if (handlerInfosEqual(newState.handlerInfos, oldState.handlerInfos)) { // This is a no-op transition. See if query params changed. if (queryParamChangelist) { newTransition = this.queryParamsTransition(queryParamChangelist, wasTransitioning, oldState, newState); if (newTransition) { return newTransition; } } // No-op. No need to create a new transition. return this.activeTransition || new _routerTransition.Transition(this); } if (isIntermediate) { setupContexts(this, newState); return; } // Create a new transition to the destination route. newTransition = new _routerTransition.Transition(this, intent, newState); // Abort and usurp any previously active transition. if (this.activeTransition) { this.activeTransition.abort(); } this.activeTransition = newTransition; // Transition promises by default resolve with resolved state. // For our purposes, swap out the promise to resolve // after the transition has been finalized. newTransition.promise = newTransition.promise.then(function (result) { return finalizeTransition(newTransition, result.state); }, null, _routerUtils.promiseLabel("Settle transition promise when transition is finalized")); if (!wasTransitioning) { notifyExistingHandlers(this, newState, newTransition); } fireQueryParamDidChange(this, newState, queryParamChangelist); return newTransition; } Router.prototype = { /** The main entry point into the router. The API is essentially the same as the `map` method in `route-recognizer`. This method extracts the String handler at the last `.to()` call and uses it as the name of the whole route. @param {Function} callback */ map: function (callback) { this.recognizer.delegate = this.delegate; this.recognizer.map(callback, function (recognizer, routes) { for (var i = routes.length - 1, proceed = true; i >= 0 && proceed; --i) { var route = routes[i]; recognizer.add(routes, { as: route.handler }); proceed = route.path === '/' || route.path === '' || route.handler.slice(-6) === '.index'; } }); }, hasRoute: function (route) { return this.recognizer.hasRoute(route); }, getHandler: function () {}, queryParamsTransition: function (changelist, wasTransitioning, oldState, newState) { var router = this; fireQueryParamDidChange(this, newState, changelist); if (!wasTransitioning && this.activeTransition) { // One of the handlers in queryParamsDidChange // caused a transition. Just return that transition. return this.activeTransition; } else { // Running queryParamsDidChange didn't change anything. // Just update query params and be on our way. // We have to return a noop transition that will // perform a URL update at the end. This gives // the user the ability to set the url update // method (default is replaceState). var newTransition = new _routerTransition.Transition(this); newTransition.queryParamsOnly = true; oldState.queryParams = finalizeQueryParamChange(this, newState.handlerInfos, newState.queryParams, newTransition); newTransition.promise = newTransition.promise.then(function (result) { updateURL(newTransition, oldState, true); if (router.didTransition) { router.didTransition(router.currentHandlerInfos); } return result; }, null, _routerUtils.promiseLabel("Transition complete")); return newTransition; } }, // NOTE: this doesn't really belong here, but here // it shall remain until our ES6 transpiler can // handle cyclical deps. transitionByIntent: function (intent, isIntermediate) { try { return getTransitionByIntent.apply(this, arguments); } catch (e) { return new _routerTransition.Transition(this, intent, null, e); } }, /** Clears the current and target route handlers and triggers exit on each of them starting at the leaf and traversing up through its ancestors. */ reset: function () { if (this.state) { _routerUtils.forEach(this.state.handlerInfos.slice().reverse(), function (handlerInfo) { var handler = handlerInfo.handler; _routerUtils.callHook(handler, 'exit'); }); } this.state = new _routerTransitionState.default(); this.currentHandlerInfos = null; }, activeTransition: null, /** var handler = handlerInfo.handler; The entry point for handling a change to the URL (usually via the back and forward button). Returns an Array of handlers and the parameters associated with those parameters. @param {String} url a URL to process @return {Array} an Array of `[handler, parameter]` tuples */ handleURL: function (url) { // Perform a URL-based transition, but don't change // the URL afterward, since it already happened. var args = _routerUtils.slice.call(arguments); if (url.charAt(0) !== '/') { args[0] = '/' + url; } return doTransition(this, args).method(null); }, /** Hook point for updating the URL. @param {String} url a URL to update to */ updateURL: function () { throw new Error("updateURL is not implemented"); }, /** Hook point for replacing the current URL, i.e. with replaceState By default this behaves the same as `updateURL` @param {String} url a URL to update to */ replaceURL: function (url) { this.updateURL(url); }, /** Transition into the specified named route. If necessary, trigger the exit callback on any handlers that are no longer represented by the target route. @param {String} name the name of the route */ transitionTo: function (name) { return doTransition(this, arguments); }, intermediateTransitionTo: function (name) { return doTransition(this, arguments, true); }, refresh: function (pivotHandler) { var state = this.activeTransition ? this.activeTransition.state : this.state; var handlerInfos = state.handlerInfos; var params = {}; for (var i = 0, len = handlerInfos.length; i < len; ++i) { var handlerInfo = handlerInfos[i]; params[handlerInfo.name] = handlerInfo.params || {}; } _routerUtils.log(this, "Starting a refresh transition"); var intent = new _routerTransitionIntentNamedTransitionIntent.default({ name: handlerInfos[handlerInfos.length - 1].name, pivotHandler: pivotHandler || handlerInfos[0].handler, contexts: [], // TODO collect contexts...? queryParams: this._changedQueryParams || state.queryParams || {} }); return this.transitionByIntent(intent, false); }, /** Identical to `transitionTo` except that the current URL will be replaced if possible. This method is intended primarily for use with `replaceState`. @param {String} name the name of the route */ replaceWith: function (name) { return doTransition(this, arguments).method('replace'); }, /** Take a named route and context objects and generate a URL. @param {String} name the name of the route to generate a URL for @param {...Object} objects a list of objects to serialize @return {String} a URL */ generate: function (handlerName) { var partitionedArgs = _routerUtils.extractQueryParams(_routerUtils.slice.call(arguments, 1)), suppliedParams = partitionedArgs[0], queryParams = partitionedArgs[1]; // Construct a TransitionIntent with the provided params // and apply it to the present state of the router. var intent = new _routerTransitionIntentNamedTransitionIntent.default({ name: handlerName, contexts: suppliedParams }); var state = intent.applyToState(this.state, this.recognizer, this.getHandler); var params = {}; for (var i = 0, len = state.handlerInfos.length; i < len; ++i) { var handlerInfo = state.handlerInfos[i]; var handlerParams = handlerInfo.serialize(); _routerUtils.merge(params, handlerParams); } params.queryParams = queryParams; return this.recognizer.generate(handlerName, params); }, applyIntent: function (handlerName, contexts) { var intent = new _routerTransitionIntentNamedTransitionIntent.default({ name: handlerName, contexts: contexts }); var state = this.activeTransition && this.activeTransition.state || this.state; return intent.applyToState(state, this.recognizer, this.getHandler); }, isActiveIntent: function (handlerName, contexts, queryParams, _state) { var state = _state || this.state, targetHandlerInfos = state.handlerInfos, found = false, names, object, handlerInfo, handlerObj, i, len; if (!targetHandlerInfos.length) { return false; } var targetHandler = targetHandlerInfos[targetHandlerInfos.length - 1].name; var recogHandlers = this.recognizer.handlersFor(targetHandler); var index = 0; for (len = recogHandlers.length; index < len; ++index) { handlerInfo = targetHandlerInfos[index]; if (handlerInfo.name === handlerName) { break; } } if (index === recogHandlers.length) { // The provided route name isn't even in the route hierarchy. return false; } var testState = new _routerTransitionState.default(); testState.handlerInfos = targetHandlerInfos.slice(0, index + 1); recogHandlers = recogHandlers.slice(0, index + 1); var intent = new _routerTransitionIntentNamedTransitionIntent.default({ name: targetHandler, contexts: contexts }); var newState = intent.applyToHandlers(testState, recogHandlers, this.getHandler, targetHandler, true, true); var handlersEqual = handlerInfosEqual(newState.handlerInfos, testState.handlerInfos); if (!queryParams || !handlersEqual) { return handlersEqual; } // Get a hash of QPs that will still be active on new route var activeQPsOnNewHandler = {}; _routerUtils.merge(activeQPsOnNewHandler, queryParams); var activeQueryParams = state.queryParams; for (var key in activeQueryParams) { if (activeQueryParams.hasOwnProperty(key) && activeQPsOnNewHandler.hasOwnProperty(key)) { activeQPsOnNewHandler[key] = activeQueryParams[key]; } } return handlersEqual && !_routerUtils.getChangelist(activeQPsOnNewHandler, queryParams); }, isActive: function (handlerName) { var partitionedArgs = _routerUtils.extractQueryParams(_routerUtils.slice.call(arguments, 1)); return this.isActiveIntent(handlerName, partitionedArgs[0], partitionedArgs[1]); }, trigger: function (name) { var args = _routerUtils.slice.call(arguments); _routerUtils.trigger(this, this.currentHandlerInfos, false, args); }, /** Hook point for logging transition status updates. @param {String} message The message to log. */ log: null }; /** @private Fires queryParamsDidChange event */ function fireQueryParamDidChange(router, newState, queryParamChangelist) { // If queryParams changed trigger event if (queryParamChangelist) { // This is a little hacky but we need some way of storing // changed query params given that no activeTransition // is guaranteed to have occurred. router._changedQueryParams = queryParamChangelist.all; _routerUtils.trigger(router, newState.handlerInfos, true, ['queryParamsDidChange', queryParamChangelist.changed, queryParamChangelist.all, queryParamChangelist.removed]); router._changedQueryParams = null; } } /** @private Takes an Array of `HandlerInfo`s, figures out which ones are exiting, entering, or changing contexts, and calls the proper handler hooks. For example, consider the following tree of handlers. Each handler is followed by the URL segment it handles. ``` |~index ("/") | |~posts ("/posts") | | |-showPost ("/:id") | | |-newPost ("/new") | | |-editPost ("/edit") | |~about ("/about/:id") ``` Consider the following transitions: 1. A URL transition to `/posts/1`. 1. Triggers the `*model` callbacks on the `index`, `posts`, and `showPost` handlers 2. Triggers the `enter` callback on the same 3. Triggers the `setup` callback on the same 2. A direct transition to `newPost` 1. Triggers the `exit` callback on `showPost` 2. Triggers the `enter` callback on `newPost` 3. Triggers the `setup` callback on `newPost` 3. A direct transition to `about` with a specified context object 1. Triggers the `exit` callback on `newPost` and `posts` 2. Triggers the `serialize` callback on `about` 3. Triggers the `enter` callback on `about` 4. Triggers the `setup` callback on `about` @param {Router} transition @param {TransitionState} newState */ function setupContexts(router, newState, transition) { var partition = partitionHandlers(router.state, newState); var i, l, handler; for (i = 0, l = partition.exited.length; i < l; i++) { handler = partition.exited[i].handler; delete handler.context; _routerUtils.callHook(handler, 'reset', true, transition); _routerUtils.callHook(handler, 'exit', transition); } var oldState = router.oldState = router.state; router.state = newState; var currentHandlerInfos = router.currentHandlerInfos = partition.unchanged.slice(); try { for (i = 0, l = partition.reset.length; i < l; i++) { handler = partition.reset[i].handler; _routerUtils.callHook(handler, 'reset', false, transition); } for (i = 0, l = partition.updatedContext.length; i < l; i++) { handlerEnteredOrUpdated(currentHandlerInfos, partition.updatedContext[i], false, transition); } for (i = 0, l = partition.entered.length; i < l; i++) { handlerEnteredOrUpdated(currentHandlerInfos, partition.entered[i], true, transition); } } catch (e) { router.state = oldState; router.currentHandlerInfos = oldState.handlerInfos; throw e; } router.state.queryParams = finalizeQueryParamChange(router, currentHandlerInfos, newState.queryParams, transition); } /** @private Helper method used by setupContexts. Handles errors or redirects that may happen in enter/setup. */ function handlerEnteredOrUpdated(currentHandlerInfos, handlerInfo, enter, transition) { var handler = handlerInfo.handler, context = handlerInfo.context; if (enter) { _routerUtils.callHook(handler, 'enter', transition); } if (transition && transition.isAborted) { throw new _routerTransition.TransitionAborted(); } handler.context = context; _routerUtils.callHook(handler, 'contextDidChange'); _routerUtils.callHook(handler, 'setup', context, transition); if (transition && transition.isAborted) { throw new _routerTransition.TransitionAborted(); } currentHandlerInfos.push(handlerInfo); return true; } /** @private This function is called when transitioning from one URL to another to determine which handlers are no longer active, which handlers are newly active, and which handlers remain active but have their context changed. Take a list of old handlers and new handlers and partition them into four buckets: * unchanged: the handler was active in both the old and new URL, and its context remains the same * updated context: the handler was active in both the old and new URL, but its context changed. The handler's `setup` method, if any, will be called with the new context. * exited: the handler was active in the old URL, but is no longer active. * entered: the handler was not active in the old URL, but is now active. The PartitionedHandlers structure has four fields: * `updatedContext`: a list of `HandlerInfo` objects that represent handlers that remain active but have a changed context * `entered`: a list of `HandlerInfo` objects that represent handlers that are newly active * `exited`: a list of `HandlerInfo` objects that are no longer active. * `unchanged`: a list of `HanderInfo` objects that remain active. @param {Array[HandlerInfo]} oldHandlers a list of the handler information for the previous URL (or `[]` if this is the first handled transition) @param {Array[HandlerInfo]} newHandlers a list of the handler information for the new URL @return {Partition} */ function partitionHandlers(oldState, newState) { var oldHandlers = oldState.handlerInfos; var newHandlers = newState.handlerInfos; var handlers = { updatedContext: [], exited: [], entered: [], unchanged: [] }; var handlerChanged, contextChanged = false, i, l; for (i = 0, l = newHandlers.length; i < l; i++) { var oldHandler = oldHandlers[i], newHandler = newHandlers[i]; if (!oldHandler || oldHandler.handler !== newHandler.handler) { handlerChanged = true; } if (handlerChanged) { handlers.entered.push(newHandler); if (oldHandler) { handlers.exited.unshift(oldHandler); } } else if (contextChanged || oldHandler.context !== newHandler.context) { contextChanged = true; handlers.updatedContext.push(newHandler); } else { handlers.unchanged.push(oldHandler); } } for (i = newHandlers.length, l = oldHandlers.length; i < l; i++) { handlers.exited.unshift(oldHandlers[i]); } handlers.reset = handlers.updatedContext.slice(); handlers.reset.reverse(); return handlers; } function updateURL(transition, state, inputUrl) { var urlMethod = transition.urlMethod; if (!urlMethod) { return; } var router = transition.router, handlerInfos = state.handlerInfos, handlerName = handlerInfos[handlerInfos.length - 1].name, params = {}; for (var i = handlerInfos.length - 1; i >= 0; --i) { var handlerInfo = handlerInfos[i]; _routerUtils.merge(params, handlerInfo.params); if (handlerInfo.handler.inaccessibleByURL) { urlMethod = null; } } if (urlMethod) { params.queryParams = transition._visibleQueryParams || state.queryParams; var url = router.recognizer.generate(handlerName, params); if (urlMethod === 'replace') { router.replaceURL(url); } else { router.updateURL(url); } } } /** @private Updates the URL (if necessary) and calls `setupContexts` to update the router's array of `currentHandlerInfos`. */ function finalizeTransition(transition, newState) { try { _routerUtils.log(transition.router, transition.sequence, "Resolved all models on destination route; finalizing transition."); var router = transition.router, handlerInfos = newState.handlerInfos, seq = transition.sequence; // Run all the necessary enter/setup/exit hooks setupContexts(router, newState, transition); // Check if a redirect occurred in enter/setup if (transition.isAborted) { // TODO: cleaner way? distinguish b/w targetHandlerInfos? router.state.handlerInfos = router.currentHandlerInfos; return _rsvpPromise.default.reject(_routerTransition.logAbort(transition)); } updateURL(transition, newState, transition.intent.url); transition.isActive = false; router.activeTransition = null; _routerUtils.trigger(router, router.currentHandlerInfos, true, ['didTransition']); if (router.didTransition) { router.didTransition(router.currentHandlerInfos); } _routerUtils.log(router, transition.sequence, "TRANSITION COMPLETE."); // Resolve with the final handler. return handlerInfos[handlerInfos.length - 1].handler; } catch (e) { if (!(e instanceof _routerTransition.TransitionAborted)) { //var erroneousHandler = handlerInfos.pop(); var infos = transition.state.handlerInfos; transition.trigger(true, 'error', e, transition, infos[infos.length - 1].handler); transition.abort(); } throw e; } } /** @private Begins and returns a Transition based on the provided arguments. Accepts arguments in the form of both URL transitions and named transitions. @param {Router} router @param {Array[Object]} args arguments passed to transitionTo, replaceWith, or handleURL */ function doTransition(router, args, isIntermediate) { // Normalize blank transitions to root URL transitions. var name = args[0] || '/'; var lastArg = args[args.length - 1]; var queryParams = {}; if (lastArg && lastArg.hasOwnProperty('queryParams')) { queryParams = pop.call(args).queryParams; } var intent; if (args.length === 0) { _routerUtils.log(router, "Updating query params"); // A query param update is really just a transition // into the route you're already on. var handlerInfos = router.state.handlerInfos; intent = new _routerTransitionIntentNamedTransitionIntent.default({ name: handlerInfos[handlerInfos.length - 1].name, contexts: [], queryParams: queryParams }); } else if (name.charAt(0) === '/') { _routerUtils.log(router, "Attempting URL transition to " + name); intent = new _routerTransitionIntentUrlTransitionIntent.default({ url: name }); } else { _routerUtils.log(router, "Attempting transition to " + name); intent = new _routerTransitionIntentNamedTransitionIntent.default({ name: args[0], contexts: _routerUtils.slice.call(args, 1), queryParams: queryParams }); } return router.transitionByIntent(intent, isIntermediate); } function handlerInfosEqual(handlerInfos, otherHandlerInfos) { if (handlerInfos.length !== otherHandlerInfos.length) { return false; } for (var i = 0, len = handlerInfos.length; i < len; ++i) { if (handlerInfos[i] !== otherHandlerInfos[i]) { return false; } } return true; } function finalizeQueryParamChange(router, resolvedHandlers, newQueryParams, transition) { // We fire a finalizeQueryParamChange event which // gives the new route hierarchy a chance to tell // us which query params it's consuming and what // their final values are. If a query param is // no longer consumed in the final route hierarchy, // its serialized segment will be removed // from the URL. for (var k in newQueryParams) { if (newQueryParams.hasOwnProperty(k) && newQueryParams[k] === null) { delete newQueryParams[k]; } } var finalQueryParamsArray = []; _routerUtils.trigger(router, resolvedHandlers, true, ['finalizeQueryParamChange', newQueryParams, finalQueryParamsArray, transition]); if (transition) { transition._visibleQueryParams = {}; } var finalQueryParams = {}; for (var i = 0, len = finalQueryParamsArray.length; i < len; ++i) { var qp = finalQueryParamsArray[i]; finalQueryParams[qp.key] = qp.value; if (transition && qp.visible !== false) { transition._visibleQueryParams[qp.key] = qp.value; } } return finalQueryParams; } function notifyExistingHandlers(router, newState, newTransition) { var oldHandlers = router.state.handlerInfos, changing = [], leavingIndex = null, leaving, leavingChecker, i, oldHandlerLen, oldHandler, newHandler; oldHandlerLen = oldHandlers.length; for (i = 0; i < oldHandlerLen; i++) { oldHandler = oldHandlers[i]; newHandler = newState.handlerInfos[i]; if (!newHandler || oldHandler.name !== newHandler.name) { leavingIndex = i; break; } if (!newHandler.isResolved) { changing.push(oldHandler); } } if (leavingIndex !== null) { leaving = oldHandlers.slice(leavingIndex, oldHandlerLen); leavingChecker = function (name) { for (var h = 0, len = leaving.length; h < len; h++) { if (leaving[h].name === name) { return true; } } return false; }; } _routerUtils.trigger(router, oldHandlers, true, ['willTransition', newTransition]); if (router.willTransition) { router.willTransition(oldHandlers, newState.handlerInfos, newTransition); } } exports.default = Router; }); enifed('router/transition-intent/named-transition-intent', ['exports', 'router/transition-intent', 'router/transition-state', 'router/handler-info/factory', 'router/utils'], function (exports, _routerTransitionIntent, _routerTransitionState, _routerHandlerInfoFactory, _routerUtils) { 'use strict'; exports.default = _routerUtils.subclass(_routerTransitionIntent.default, { name: null, pivotHandler: null, contexts: null, queryParams: null, initialize: function (props) { this.name = props.name; this.pivotHandler = props.pivotHandler; this.contexts = props.contexts || []; this.queryParams = props.queryParams; }, applyToState: function (oldState, recognizer, getHandler, isIntermediate) { var partitionedArgs = _routerUtils.extractQueryParams([this.name].concat(this.contexts)), pureArgs = partitionedArgs[0], queryParams = partitionedArgs[1], handlers = recognizer.handlersFor(pureArgs[0]); var targetRouteName = handlers[handlers.length - 1].handler; return this.applyToHandlers(oldState, handlers, getHandler, targetRouteName, isIntermediate); }, applyToHandlers: function (oldState, handlers, getHandler, targetRouteName, isIntermediate, checkingIfActive) { var i, len; var newState = new _routerTransitionState.default(); var objects = this.contexts.slice(0); var invalidateIndex = handlers.length; // Pivot handlers are provided for refresh transitions if (this.pivotHandler) { for (i = 0, len = handlers.length; i < len; ++i) { if (getHandler(handlers[i].handler) === this.pivotHandler) { invalidateIndex = i; break; } } } var pivotHandlerFound = !this.pivotHandler; for (i = handlers.length - 1; i >= 0; --i) { var result = handlers[i]; var name = result.handler; var handler = getHandler(name); var oldHandlerInfo = oldState.handlerInfos[i]; var newHandlerInfo = null; if (result.names.length > 0) { if (i >= invalidateIndex) { newHandlerInfo = this.createParamHandlerInfo(name, handler, result.names, objects, oldHandlerInfo); } else { newHandlerInfo = this.getHandlerInfoForDynamicSegment(name, handler, result.names, objects, oldHandlerInfo, targetRouteName, i); } } else { // This route has no dynamic segment. // Therefore treat as a param-based handlerInfo // with empty params. This will cause the `model` // hook to be called with empty params, which is desirable. newHandlerInfo = this.createParamHandlerInfo(name, handler, result.names, objects, oldHandlerInfo); } if (checkingIfActive) { // If we're performing an isActive check, we want to // serialize URL params with the provided context, but // ignore mismatches between old and new context. newHandlerInfo = newHandlerInfo.becomeResolved(null, newHandlerInfo.context); var oldContext = oldHandlerInfo && oldHandlerInfo.context; if (result.names.length > 0 && newHandlerInfo.context === oldContext) { // If contexts match in isActive test, assume params also match. // This allows for flexibility in not requiring that every last // handler provide a `serialize` method newHandlerInfo.params = oldHandlerInfo && oldHandlerInfo.params; } newHandlerInfo.context = oldContext; } var handlerToUse = oldHandlerInfo; if (i >= invalidateIndex || newHandlerInfo.shouldSupercede(oldHandlerInfo)) { invalidateIndex = Math.min(i, invalidateIndex); handlerToUse = newHandlerInfo; } if (isIntermediate && !checkingIfActive) { handlerToUse = handlerToUse.becomeResolved(null, handlerToUse.context); } newState.handlerInfos.unshift(handlerToUse); } if (objects.length > 0) { throw new Error("More context objects were passed than there are dynamic segments for the route: " + targetRouteName); } if (!isIntermediate) { this.invalidateChildren(newState.handlerInfos, invalidateIndex); } _routerUtils.merge(newState.queryParams, this.queryParams || {}); return newState; }, invalidateChildren: function (handlerInfos, invalidateIndex) { for (var i = invalidateIndex, l = handlerInfos.length; i < l; ++i) { var handlerInfo = handlerInfos[i]; handlerInfos[i] = handlerInfos[i].getUnresolved(); } }, getHandlerInfoForDynamicSegment: function (name, handler, names, objects, oldHandlerInfo, targetRouteName, i) { var numNames = names.length; var objectToUse; if (objects.length > 0) { // Use the objects provided for this transition. objectToUse = objects[objects.length - 1]; if (_routerUtils.isParam(objectToUse)) { return this.createParamHandlerInfo(name, handler, names, objects, oldHandlerInfo); } else { objects.pop(); } } else if (oldHandlerInfo && oldHandlerInfo.name === name) { // Reuse the matching oldHandlerInfo return oldHandlerInfo; } else { if (this.preTransitionState) { var preTransitionHandlerInfo = this.preTransitionState.handlerInfos[i]; objectToUse = preTransitionHandlerInfo && preTransitionHandlerInfo.context; } else { // Ideally we should throw this error to provide maximal // information to the user that not enough context objects // were provided, but this proves too cumbersome in Ember // in cases where inner template helpers are evaluated // before parent helpers un-render, in which cases this // error somewhat prematurely fires. //throw new Error("Not enough context objects were provided to complete a transition to " + targetRouteName + ". Specifically, the " + name + " route needs an object that can be serialized into its dynamic URL segments [" + names.join(', ') + "]"); return oldHandlerInfo; } } return _routerHandlerInfoFactory.default('object', { name: name, handler: handler, context: objectToUse, names: names }); }, createParamHandlerInfo: function (name, handler, names, objects, oldHandlerInfo) { var params = {}; // Soak up all the provided string/numbers var numNames = names.length; while (numNames--) { // Only use old params if the names match with the new handler var oldParams = oldHandlerInfo && name === oldHandlerInfo.name && oldHandlerInfo.params || {}; var peek = objects[objects.length - 1]; var paramName = names[numNames]; if (_routerUtils.isParam(peek)) { params[paramName] = "" + objects.pop(); } else { // If we're here, this means only some of the params // were string/number params, so try and use a param // value from a previous handler. if (oldParams.hasOwnProperty(paramName)) { params[paramName] = oldParams[paramName]; } else { throw new Error("You didn't provide enough string/numeric parameters to satisfy all of the dynamic segments for route " + name); } } } return _routerHandlerInfoFactory.default('param', { name: name, handler: handler, params: params }); } }); }); enifed('router/transition-intent/url-transition-intent', ['exports', 'router/transition-intent', 'router/transition-state', 'router/handler-info/factory', 'router/utils', 'router/unrecognized-url-error'], function (exports, _routerTransitionIntent, _routerTransitionState, _routerHandlerInfoFactory, _routerUtils, _routerUnrecognizedUrlError) { 'use strict'; exports.default = _routerUtils.subclass(_routerTransitionIntent.default, { url: null, initialize: function (props) { this.url = props.url; }, applyToState: function (oldState, recognizer, getHandler) { var newState = new _routerTransitionState.default(); var results = recognizer.recognize(this.url), queryParams = {}, i, len; if (!results) { throw new _routerUnrecognizedUrlError.default(this.url); } var statesDiffer = false; for (i = 0, len = results.length; i < len; ++i) { var result = results[i]; var name = result.handler; var handler = getHandler(name); if (handler.inaccessibleByURL) { throw new _routerUnrecognizedUrlError.default(this.url); } var newHandlerInfo = _routerHandlerInfoFactory.default('param', { name: name, handler: handler, params: result.params }); var oldHandlerInfo = oldState.handlerInfos[i]; if (statesDiffer || newHandlerInfo.shouldSupercede(oldHandlerInfo)) { statesDiffer = true; newState.handlerInfos[i] = newHandlerInfo; } else { newState.handlerInfos[i] = oldHandlerInfo; } } _routerUtils.merge(newState.queryParams, results.queryParams); return newState; } }); }); enifed('router/transition-intent', ['exports', 'router/utils'], function (exports, _routerUtils) { 'use strict'; function TransitionIntent(props) { this.initialize(props); // TODO: wat this.data = this.data || {}; } TransitionIntent.prototype = { initialize: null, applyToState: null }; exports.default = TransitionIntent; }); enifed('router/transition-state', ['exports', 'router/handler-info', 'router/utils', 'rsvp/promise'], function (exports, _routerHandlerInfo, _routerUtils, _rsvpPromise) { 'use strict'; function TransitionState(other) { this.handlerInfos = []; this.queryParams = {}; this.params = {}; } TransitionState.prototype = { handlerInfos: null, queryParams: null, params: null, promiseLabel: function (label) { var targetName = ''; _routerUtils.forEach(this.handlerInfos, function (handlerInfo) { if (targetName !== '') { targetName += '.'; } targetName += handlerInfo.name; }); return _routerUtils.promiseLabel("'" + targetName + "': " + label); }, resolve: function (shouldContinue, payload) { var self = this; // First, calculate params for this state. This is useful // information to provide to the various route hooks. var params = this.params; _routerUtils.forEach(this.handlerInfos, function (handlerInfo) { params[handlerInfo.name] = handlerInfo.params || {}; }); payload = payload || {}; payload.resolveIndex = 0; var currentState = this; var wasAborted = false; // The prelude RSVP.resolve() asyncs us into the promise land. return _rsvpPromise.default.resolve(null, this.promiseLabel("Start transition")).then(resolveOneHandlerInfo, null, this.promiseLabel('Resolve handler'))['catch'](handleError, this.promiseLabel('Handle error')); function innerShouldContinue() { return _rsvpPromise.default.resolve(shouldContinue(), currentState.promiseLabel("Check if should continue"))['catch'](function (reason) { // We distinguish between errors that occurred // during resolution (e.g. beforeModel/model/afterModel), // and aborts due to a rejecting promise from shouldContinue(). wasAborted = true; return _rsvpPromise.default.reject(reason); }, currentState.promiseLabel("Handle abort")); } function handleError(error) { // This is the only possible // reject value of TransitionState#resolve var handlerInfos = currentState.handlerInfos; var errorHandlerIndex = payload.resolveIndex >= handlerInfos.length ? handlerInfos.length - 1 : payload.resolveIndex; return _rsvpPromise.default.reject({ error: error, handlerWithError: currentState.handlerInfos[errorHandlerIndex].handler, wasAborted: wasAborted, state: currentState }); } function proceed(resolvedHandlerInfo) { var wasAlreadyResolved = currentState.handlerInfos[payload.resolveIndex].isResolved; // Swap the previously unresolved handlerInfo with // the resolved handlerInfo currentState.handlerInfos[payload.resolveIndex++] = resolvedHandlerInfo; if (!wasAlreadyResolved) { // Call the redirect hook. The reason we call it here // vs. afterModel is so that redirects into child // routes don't re-run the model hooks for this // already-resolved route. var handler = resolvedHandlerInfo.handler; _routerUtils.callHook(handler, 'redirect', resolvedHandlerInfo.context, payload); } // Proceed after ensuring that the redirect hook // didn't abort this transition by transitioning elsewhere. return innerShouldContinue().then(resolveOneHandlerInfo, null, currentState.promiseLabel('Resolve handler')); } function resolveOneHandlerInfo() { if (payload.resolveIndex === currentState.handlerInfos.length) { // This is is the only possible // fulfill value of TransitionState#resolve return { error: null, state: currentState }; } var handlerInfo = currentState.handlerInfos[payload.resolveIndex]; return handlerInfo.resolve(innerShouldContinue, payload).then(proceed, null, currentState.promiseLabel('Proceed')); } } }; exports.default = TransitionState; }); enifed('router/transition', ['exports', 'rsvp/promise', 'router/handler-info', 'router/utils'], function (exports, _rsvpPromise, _routerHandlerInfo, _routerUtils) { 'use strict'; /** @private A Transition is a thennable (a promise-like object) that represents an attempt to transition to another route. It can be aborted, either explicitly via `abort` or by attempting another transition while a previous one is still underway. An aborted transition can also be `retry()`d later. */ function Transition(router, intent, state, error) { var transition = this; this.state = state || router.state; this.intent = intent; this.router = router; this.data = this.intent && this.intent.data || {}; this.resolvedModels = {}; this.queryParams = {}; if (error) { this.promise = _rsvpPromise.default.reject(error); this.error = error; return; } if (state) { this.params = state.params; this.queryParams = state.queryParams; this.handlerInfos = state.handlerInfos; var len = state.handlerInfos.length; if (len) { this.targetName = state.handlerInfos[len - 1].name; } for (var i = 0; i < len; ++i) { var handlerInfo = state.handlerInfos[i]; // TODO: this all seems hacky if (!handlerInfo.isResolved) { break; } this.pivotHandler = handlerInfo.handler; } this.sequence = Transition.currentSequence++; this.promise = state.resolve(checkForAbort, this)['catch'](function (result) { if (result.wasAborted || transition.isAborted) { return _rsvpPromise.default.reject(logAbort(transition)); } else { transition.trigger('error', result.error, transition, result.handlerWithError); transition.abort(); return _rsvpPromise.default.reject(result.error); } }, _routerUtils.promiseLabel('Handle Abort')); } else { this.promise = _rsvpPromise.default.resolve(this.state); this.params = {}; } function checkForAbort() { if (transition.isAborted) { return _rsvpPromise.default.reject(undefined, _routerUtils.promiseLabel("Transition aborted - reject")); } } } Transition.currentSequence = 0; Transition.prototype = { targetName: null, urlMethod: 'update', intent: null, params: null, pivotHandler: null, resolveIndex: 0, handlerInfos: null, resolvedModels: null, isActive: true, state: null, queryParamsOnly: false, isTransition: true, isExiting: function (handler) { var handlerInfos = this.handlerInfos; for (var i = 0, len = handlerInfos.length; i < len; ++i) { var handlerInfo = handlerInfos[i]; if (handlerInfo.name === handler || handlerInfo.handler === handler) { return false; } } return true; }, /** @public The Transition's internal promise. Calling `.then` on this property is that same as calling `.then` on the Transition object itself, but this property is exposed for when you want to pass around a Transition's promise, but not the Transition object itself, since Transition object can be externally `abort`ed, while the promise cannot. */ promise: null, /** @public Custom state can be stored on a Transition's `data` object. This can be useful for decorating a Transition within an earlier hook and shared with a later hook. Properties set on `data` will be copied to new transitions generated by calling `retry` on this transition. */ data: null, /** @public A standard promise hook that resolves if the transition succeeds and rejects if it fails/redirects/aborts. Forwards to the internal `promise` property which you can use in situations where you want to pass around a thennable, but not the Transition itself. @param {Function} onFulfilled @param {Function} onRejected @param {String} label optional string for labeling the promise. Useful for tooling. @return {Promise} */ then: function (onFulfilled, onRejected, label) { return this.promise.then(onFulfilled, onRejected, label); }, /** @public Forwards to the internal `promise` property which you can use in situations where you want to pass around a thennable, but not the Transition itself. @method catch @param {Function} onRejection @param {String} label optional string for labeling the promise. Useful for tooling. @return {Promise} */ catch: function (onRejection, label) { return this.promise.catch(onRejection, label); }, /** @public Forwards to the internal `promise` property which you can use in situations where you want to pass around a thennable, but not the Transition itself. @method finally @param {Function} callback @param {String} label optional string for labeling the promise. Useful for tooling. @return {Promise} */ finally: function (callback, label) { return this.promise.finally(callback, label); }, /** @public Aborts the Transition. Note you can also implicitly abort a transition by initiating another transition while a previous one is underway. */ abort: function () { if (this.isAborted) { return this; } _routerUtils.log(this.router, this.sequence, this.targetName + ": transition was aborted"); this.intent.preTransitionState = this.router.state; this.isAborted = true; this.isActive = false; this.router.activeTransition = null; return this; }, /** @public Retries a previously-aborted transition (making sure to abort the transition if it's still active). Returns a new transition that represents the new attempt to transition. */ retry: function () { // TODO: add tests for merged state retry()s this.abort(); return this.router.transitionByIntent(this.intent, false); }, /** @public Sets the URL-changing method to be employed at the end of a successful transition. By default, a new Transition will just use `updateURL`, but passing 'replace' to this method will cause the URL to update using 'replaceWith' instead. Omitting a parameter will disable the URL change, allowing for transitions that don't update the URL at completion (this is also used for handleURL, since the URL has already changed before the transition took place). @param {String} method the type of URL-changing method to use at the end of a transition. Accepted values are 'replace', falsy values, or any other non-falsy value (which is interpreted as an updateURL transition). @return {Transition} this transition */ method: function (method) { this.urlMethod = method; return this; }, /** @public Fires an event on the current list of resolved/resolving handlers within this transition. Useful for firing events on route hierarchies that haven't fully been entered yet. Note: This method is also aliased as `send` @param {Boolean} [ignoreFailure=false] a boolean specifying whether unhandled events throw an error @param {String} name the name of the event to fire */ trigger: function (ignoreFailure) { var args = _routerUtils.slice.call(arguments); if (typeof ignoreFailure === 'boolean') { args.shift(); } else { // Throw errors on unhandled trigger events by default ignoreFailure = false; } _routerUtils.trigger(this.router, this.state.handlerInfos.slice(0, this.resolveIndex + 1), ignoreFailure, args); }, /** @public Transitions are aborted and their promises rejected when redirects occur; this method returns a promise that will follow any redirects that occur and fulfill with the value fulfilled by any redirecting transitions that occur. @return {Promise} a promise that fulfills with the same value that the final redirecting transition fulfills with */ followRedirects: function () { var router = this.router; return this.promise['catch'](function (reason) { if (router.activeTransition) { return router.activeTransition.followRedirects(); } return _rsvpPromise.default.reject(reason); }); }, toString: function () { return "Transition (sequence " + this.sequence + ")"; }, /** @private */ log: function (message) { _routerUtils.log(this.router, this.sequence, message); } }; // Alias 'trigger' as 'send' Transition.prototype.send = Transition.prototype.trigger; /** @private Logs and returns a TransitionAborted error. */ function logAbort(transition) { _routerUtils.log(transition.router, transition.sequence, "detected abort."); return new TransitionAborted(); } function TransitionAborted(message) { this.message = message || "TransitionAborted"; this.name = "TransitionAborted"; } exports.Transition = Transition; exports.logAbort = logAbort; exports.TransitionAborted = TransitionAborted; }); enifed("router/unrecognized-url-error", ["exports", "router/utils"], function (exports, _routerUtils) { "use strict"; /** Promise reject reasons passed to promise rejection handlers for failed transitions. */ function UnrecognizedURLError(message) { this.message = message || "UnrecognizedURLError"; this.name = "UnrecognizedURLError"; Error.call(this); } UnrecognizedURLError.prototype = _routerUtils.oCreate(Error.prototype); exports.default = UnrecognizedURLError; }); enifed('router/utils', ['exports'], function (exports) { 'use strict'; exports.extractQueryParams = extractQueryParams; exports.log = log; exports.bind = bind; exports.forEach = forEach; exports.trigger = trigger; exports.getChangelist = getChangelist; exports.promiseLabel = promiseLabel; exports.subclass = subclass; var slice = Array.prototype.slice; var _isArray; if (!Array.isArray) { _isArray = function (x) { return Object.prototype.toString.call(x) === "[object Array]"; }; } else { _isArray = Array.isArray; } var isArray = _isArray; exports.isArray = isArray; function merge(hash, other) { for (var prop in other) { if (other.hasOwnProperty(prop)) { hash[prop] = other[prop]; } } } var oCreate = Object.create || function (proto) { function F() {} F.prototype = proto; return new F(); }; exports.oCreate = oCreate; /** @private Extracts query params from the end of an array **/ function extractQueryParams(array) { var len = array && array.length, head, queryParams; if (len && len > 0 && array[len - 1] && array[len - 1].hasOwnProperty('queryParams')) { queryParams = array[len - 1].queryParams; head = slice.call(array, 0, len - 1); return [head, queryParams]; } else { return [array, null]; } } /** @private Coerces query param properties and array elements into strings. **/ function coerceQueryParamsToString(queryParams) { for (var key in queryParams) { if (typeof queryParams[key] === 'number') { queryParams[key] = '' + queryParams[key]; } else if (isArray(queryParams[key])) { for (var i = 0, l = queryParams[key].length; i < l; i++) { queryParams[key][i] = '' + queryParams[key][i]; } } } } /** @private */ function log(router, sequence, msg) { if (!router.log) { return; } if (arguments.length === 3) { router.log("Transition #" + sequence + ": " + msg); } else { msg = sequence; router.log(msg); } } function bind(context, fn) { var boundArgs = arguments; return function (value) { var args = slice.call(boundArgs, 2); args.push(value); return fn.apply(context, args); }; } function isParam(object) { return typeof object === "string" || object instanceof String || typeof object === "number" || object instanceof Number; } function forEach(array, callback) { for (var i = 0, l = array.length; i < l && false !== callback(array[i]); i++) {} } function trigger(router, handlerInfos, ignoreFailure, args) { if (router.triggerEvent) { router.triggerEvent(handlerInfos, ignoreFailure, args); return; } var name = args.shift(); if (!handlerInfos) { if (ignoreFailure) { return; } throw new Error("Could not trigger event '" + name + "'. There are no active handlers"); } var eventWasHandled = false; for (var i = handlerInfos.length - 1; i >= 0; i--) { var handlerInfo = handlerInfos[i], handler = handlerInfo.handler; if (handler.events && handler.events[name]) { if (handler.events[name].apply(handler, args) === true) { eventWasHandled = true; } else { return; } } } if (!eventWasHandled && !ignoreFailure) { throw new Error("Nothing handled the event '" + name + "'."); } } function getChangelist(oldObject, newObject) { var key; var results = { all: {}, changed: {}, removed: {} }; merge(results.all, newObject); var didChange = false; coerceQueryParamsToString(oldObject); coerceQueryParamsToString(newObject); // Calculate removals for (key in oldObject) { if (oldObject.hasOwnProperty(key)) { if (!newObject.hasOwnProperty(key)) { didChange = true; results.removed[key] = oldObject[key]; } } } // Calculate changes for (key in newObject) { if (newObject.hasOwnProperty(key)) { if (isArray(oldObject[key]) && isArray(newObject[key])) { if (oldObject[key].length !== newObject[key].length) { results.changed[key] = newObject[key]; didChange = true; } else { for (var i = 0, l = oldObject[key].length; i < l; i++) { if (oldObject[key][i] !== newObject[key][i]) { results.changed[key] = newObject[key]; didChange = true; } } } } else { if (oldObject[key] !== newObject[key]) { results.changed[key] = newObject[key]; didChange = true; } } } } return didChange && results; } function promiseLabel(label) { return 'Router: ' + label; } function subclass(parentConstructor, proto) { function C(props) { parentConstructor.call(this, props || {}); } C.prototype = oCreate(parentConstructor.prototype); merge(C.prototype, proto); return C; } function resolveHook(obj, hookName) { if (!obj) { return; } var underscored = "_" + hookName; return obj[underscored] && underscored || obj[hookName] && hookName; } function callHook(obj, _hookName, arg1, arg2) { var hookName = resolveHook(obj, _hookName); return hookName && obj[hookName].call(obj, arg1, arg2); } function applyHook(obj, _hookName, args) { var hookName = resolveHook(obj, _hookName); if (hookName) { if (args.length === 0) { return obj[hookName].call(obj); } else if (args.length === 1) { return obj[hookName].call(obj, args[0]); } else if (args.length === 2) { return obj[hookName].call(obj, args[0], args[1]); } else { return obj[hookName].apply(obj, args); } } } exports.merge = merge; exports.slice = slice; exports.isParam = isParam; exports.coerceQueryParamsToString = coerceQueryParamsToString; exports.callHook = callHook; exports.resolveHook = resolveHook; exports.applyHook = applyHook; }); enifed('router', ['exports', 'router/router'], function (exports, _routerRouter) { 'use strict'; exports.default = _routerRouter.default; }); enifed('rsvp/-internal', ['exports', 'rsvp/utils', 'rsvp/instrument', 'rsvp/config'], function (exports, _rsvpUtils, _rsvpInstrument, _rsvpConfig) { 'use strict'; function withOwnPromise() { return new TypeError('A promises callback cannot return that same promise.'); } function noop() {} var PENDING = void 0; var FULFILLED = 1; var REJECTED = 2; var GET_THEN_ERROR = new ErrorObject(); function getThen(promise) { try { return promise.then; } catch (error) { GET_THEN_ERROR.error = error; return GET_THEN_ERROR; } } function tryThen(then, value, fulfillmentHandler, rejectionHandler) { try { then.call(value, fulfillmentHandler, rejectionHandler); } catch (e) { return e; } } function handleForeignThenable(promise, thenable, then) { _rsvpConfig.config.async(function (promise) { var sealed = false; var error = tryThen(then, thenable, function (value) { if (sealed) { return; } sealed = true; if (thenable !== value) { resolve(promise, value); } else { fulfill(promise, value); } }, function (reason) { if (sealed) { return; } sealed = true; reject(promise, reason); }, 'Settle: ' + (promise._label || ' unknown promise')); if (!sealed && error) { sealed = true; reject(promise, error); } }, promise); } function handleOwnThenable(promise, thenable) { if (thenable._state === FULFILLED) { fulfill(promise, thenable._result); } else if (thenable._state === REJECTED) { thenable._onError = null; reject(promise, thenable._result); } else { subscribe(thenable, undefined, function (value) { if (thenable !== value) { resolve(promise, value); } else { fulfill(promise, value); } }, function (reason) { reject(promise, reason); }); } } function handleMaybeThenable(promise, maybeThenable) { if (maybeThenable.constructor === promise.constructor) { handleOwnThenable(promise, maybeThenable); } else { var then = getThen(maybeThenable); if (then === GET_THEN_ERROR) { reject(promise, GET_THEN_ERROR.error); } else if (then === undefined) { fulfill(promise, maybeThenable); } else if (_rsvpUtils.isFunction(then)) { handleForeignThenable(promise, maybeThenable, then); } else { fulfill(promise, maybeThenable); } } } function resolve(promise, value) { if (promise === value) { fulfill(promise, value); } else if (_rsvpUtils.objectOrFunction(value)) { handleMaybeThenable(promise, value); } else { fulfill(promise, value); } } function publishRejection(promise) { if (promise._onError) { promise._onError(promise._result); } publish(promise); } function fulfill(promise, value) { if (promise._state !== PENDING) { return; } promise._result = value; promise._state = FULFILLED; if (promise._subscribers.length === 0) { if (_rsvpConfig.config.instrument) { _rsvpInstrument.default('fulfilled', promise); } } else { _rsvpConfig.config.async(publish, promise); } } function reject(promise, reason) { if (promise._state !== PENDING) { return; } promise._state = REJECTED; promise._result = reason; _rsvpConfig.config.async(publishRejection, promise); } function subscribe(parent, child, onFulfillment, onRejection) { var subscribers = parent._subscribers; var length = subscribers.length; parent._onError = null; subscribers[length] = child; subscribers[length + FULFILLED] = onFulfillment; subscribers[length + REJECTED] = onRejection; if (length === 0 && parent._state) { _rsvpConfig.config.async(publish, parent); } } function publish(promise) { var subscribers = promise._subscribers; var settled = promise._state; if (_rsvpConfig.config.instrument) { _rsvpInstrument.default(settled === FULFILLED ? 'fulfilled' : 'rejected', promise); } if (subscribers.length === 0) { return; } var child, callback, detail = promise._result; for (var i = 0; i < subscribers.length; i += 3) { child = subscribers[i]; callback = subscribers[i + settled]; if (child) { invokeCallback(settled, child, callback, detail); } else { callback(detail); } } promise._subscribers.length = 0; } function ErrorObject() { this.error = null; } var TRY_CATCH_ERROR = new ErrorObject(); function tryCatch(callback, detail) { try { return callback(detail); } catch (e) { TRY_CATCH_ERROR.error = e; return TRY_CATCH_ERROR; } } function invokeCallback(settled, promise, callback, detail) { var hasCallback = _rsvpUtils.isFunction(callback), value, error, succeeded, failed; if (hasCallback) { value = tryCatch(callback, detail); if (value === TRY_CATCH_ERROR) { failed = true; error = value.error; value = null; } else { succeeded = true; } if (promise === value) { reject(promise, withOwnPromise()); return; } } else { value = detail; succeeded = true; } if (promise._state !== PENDING) { // noop } else if (hasCallback && succeeded) { resolve(promise, value); } else if (failed) { reject(promise, error); } else if (settled === FULFILLED) { fulfill(promise, value); } else if (settled === REJECTED) { reject(promise, value); } } function initializePromise(promise, resolver) { var resolved = false; try { resolver(function resolvePromise(value) { if (resolved) { return; } resolved = true; resolve(promise, value); }, function rejectPromise(reason) { if (resolved) { return; } resolved = true; reject(promise, reason); }); } catch (e) { reject(promise, e); } } exports.noop = noop; exports.resolve = resolve; exports.reject = reject; exports.fulfill = fulfill; exports.subscribe = subscribe; exports.publish = publish; exports.publishRejection = publishRejection; exports.initializePromise = initializePromise; exports.invokeCallback = invokeCallback; exports.FULFILLED = FULFILLED; exports.REJECTED = REJECTED; exports.PENDING = PENDING; }); enifed('rsvp/all-settled', ['exports', 'rsvp/enumerator', 'rsvp/promise', 'rsvp/utils'], function (exports, _rsvpEnumerator, _rsvpPromise, _rsvpUtils) { 'use strict'; exports.default = allSettled; function AllSettled(Constructor, entries, label) { this._superConstructor(Constructor, entries, false, /* don't abort on reject */label); } AllSettled.prototype = _rsvpUtils.o_create(_rsvpEnumerator.default.prototype); AllSettled.prototype._superConstructor = _rsvpEnumerator.default; AllSettled.prototype._makeResult = _rsvpEnumerator.makeSettledResult; AllSettled.prototype._validationError = function () { return new Error('allSettled must be called with an array'); }; /** `RSVP.allSettled` is similar to `RSVP.all`, but instead of implementing a fail-fast method, it waits until all the promises have returned and shows you all the results. This is useful if you want to handle multiple promises' failure states together as a set. Returns a promise that is fulfilled when all the given promises have been settled. The return promise is fulfilled with an array of the states of the promises passed into the `promises` array argument. Each state object will either indicate fulfillment or rejection, and provide the corresponding value or reason. The states will take one of the following formats: ```javascript { state: 'fulfilled', value: value } or { state: 'rejected', reason: reason } ``` Example: ```javascript var promise1 = RSVP.Promise.resolve(1); var promise2 = RSVP.Promise.reject(new Error('2')); var promise3 = RSVP.Promise.reject(new Error('3')); var promises = [ promise1, promise2, promise3 ]; RSVP.allSettled(promises).then(function(array){ // array == [ // { state: 'fulfilled', value: 1 }, // { state: 'rejected', reason: Error }, // { state: 'rejected', reason: Error } // ] // Note that for the second item, reason.message will be '2', and for the // third item, reason.message will be '3'. }, function(error) { // Not run. (This block would only be called if allSettled had failed, // for instance if passed an incorrect argument type.) }); ``` @method allSettled @static @for RSVP @param {Array} entries @param {String} label - optional string that describes the promise. Useful for tooling. @return {Promise} promise that is fulfilled with an array of the settled states of the constituent promises. */ function allSettled(entries, label) { return new AllSettled(_rsvpPromise.default, entries, label).promise; } }); enifed("rsvp/all", ["exports", "rsvp/promise"], function (exports, _rsvpPromise) { "use strict"; exports.default = all; /** This is a convenient alias for `RSVP.Promise.all`. @method all @static @for RSVP @param {Array} array Array of promises. @param {String} label An optional label. This is useful for tooling. */ function all(array, label) { return _rsvpPromise.default.all(array, label); } }); enifed('rsvp/asap', ['exports'], function (exports) { 'use strict'; exports.default = asap; var len = 0; var toString = ({}).toString; var vertxNext; function asap(callback, arg) { queue[len] = callback; queue[len + 1] = arg; len += 2; if (len === 2) { // If len is 1, that means that we need to schedule an async flush. // If additional callbacks are queued before the queue is flushed, they // will be processed by this flush that we are scheduling. scheduleFlush(); } } var browserWindow = typeof window !== 'undefined' ? window : undefined; var browserGlobal = browserWindow || {}; var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver; var isNode = typeof window === 'undefined' && typeof process !== 'undefined' && ({}).toString.call(process) === '[object process]'; // test for web worker but not in IE10 var isWorker = typeof Uint8ClampedArray !== 'undefined' && typeof importScripts !== 'undefined' && typeof MessageChannel !== 'undefined'; // node function useNextTick() { var nextTick = process.nextTick; // node version 0.10.x displays a deprecation warning when nextTick is used recursively // setImmediate should be used instead instead var version = process.versions.node.match(/^(?:(\d+)\.)?(?:(\d+)\.)?(\*|\d+)$/); if (Array.isArray(version) && version[1] === '0' && version[2] === '10') { nextTick = setImmediate; } return function () { nextTick(flush); }; } // vertx function useVertxTimer() { return function () { vertxNext(flush); }; } function useMutationObserver() { var iterations = 0; var observer = new BrowserMutationObserver(flush); var node = document.createTextNode(''); observer.observe(node, { characterData: true }); return function () { node.data = iterations = ++iterations % 2; }; } // web worker function useMessageChannel() { var channel = new MessageChannel(); channel.port1.onmessage = flush; return function () { channel.port2.postMessage(0); }; } function useSetTimeout() { return function () { setTimeout(flush, 1); }; } var queue = new Array(1000); function flush() { for (var i = 0; i < len; i += 2) { var callback = queue[i]; var arg = queue[i + 1]; callback(arg); queue[i] = undefined; queue[i + 1] = undefined; } len = 0; } function attemptVertex() { try { var r = require; var vertx = r('vertx'); vertxNext = vertx.runOnLoop || vertx.runOnContext; return useVertxTimer(); } catch (e) { return useSetTimeout(); } } var scheduleFlush; // Decide what async method to use to triggering processing of queued callbacks: if (isNode) { scheduleFlush = useNextTick(); } else if (BrowserMutationObserver) { scheduleFlush = useMutationObserver(); } else if (isWorker) { scheduleFlush = useMessageChannel(); } else if (browserWindow === undefined && typeof require === 'function') { scheduleFlush = attemptVertex(); } else { scheduleFlush = useSetTimeout(); } }); enifed('rsvp/config', ['exports', 'rsvp/events'], function (exports, _rsvpEvents) { 'use strict'; var config = { instrument: false }; _rsvpEvents.default['mixin'](config); function configure(name, value) { if (name === 'onerror') { // handle for legacy users that expect the actual // error to be passed to their function added via // `RSVP.configure('onerror', someFunctionHere);` config['on']('error', value); return; } if (arguments.length === 2) { config[name] = value; } else { return config[name]; } } exports.config = config; exports.configure = configure; }); enifed('rsvp/defer', ['exports', 'rsvp/promise'], function (exports, _rsvpPromise) { 'use strict'; exports.default = defer; /** `RSVP.defer` returns an object similar to jQuery's `$.Deferred`. `RSVP.defer` should be used when porting over code reliant on `$.Deferred`'s interface. New code should use the `RSVP.Promise` constructor instead. The object returned from `RSVP.defer` is a plain object with three properties: * promise - an `RSVP.Promise`. * reject - a function that causes the `promise` property on this object to become rejected * resolve - a function that causes the `promise` property on this object to become fulfilled. Example: ```javascript var deferred = RSVP.defer(); deferred.resolve("Success!"); deferred.promise.then(function(value){ // value here is "Success!" }); ``` @method defer @static @for RSVP @param {String} label optional string for labeling the promise. Useful for tooling. @return {Object} */ function defer(label) { var deferred = {}; deferred['promise'] = new _rsvpPromise.default(function (resolve, reject) { deferred['resolve'] = resolve; deferred['reject'] = reject; }, label); return deferred; } }); enifed('rsvp/enumerator', ['exports', 'rsvp/utils', 'rsvp/-internal'], function (exports, _rsvpUtils, _rsvpInternal) { 'use strict'; exports.makeSettledResult = makeSettledResult; function makeSettledResult(state, position, value) { if (state === _rsvpInternal.FULFILLED) { return { state: 'fulfilled', value: value }; } else { return { state: 'rejected', reason: value }; } } function Enumerator(Constructor, input, abortOnReject, label) { var enumerator = this; enumerator._instanceConstructor = Constructor; enumerator.promise = new Constructor(_rsvpInternal.noop, label); enumerator._abortOnReject = abortOnReject; if (enumerator._validateInput(input)) { enumerator._input = input; enumerator.length = input.length; enumerator._remaining = input.length; enumerator._init(); if (enumerator.length === 0) { _rsvpInternal.fulfill(enumerator.promise, enumerator._result); } else { enumerator.length = enumerator.length || 0; enumerator._enumerate(); if (enumerator._remaining === 0) { _rsvpInternal.fulfill(enumerator.promise, enumerator._result); } } } else { _rsvpInternal.reject(enumerator.promise, enumerator._validationError()); } } exports.default = Enumerator; Enumerator.prototype._validateInput = function (input) { return _rsvpUtils.isArray(input); }; Enumerator.prototype._validationError = function () { return new Error('Array Methods must be provided an Array'); }; Enumerator.prototype._init = function () { this._result = new Array(this.length); }; Enumerator.prototype._enumerate = function () { var enumerator = this; var length = enumerator.length; var promise = enumerator.promise; var input = enumerator._input; for (var i = 0; promise._state === _rsvpInternal.PENDING && i < length; i++) { enumerator._eachEntry(input[i], i); } }; Enumerator.prototype._eachEntry = function (entry, i) { var enumerator = this; var c = enumerator._instanceConstructor; if (_rsvpUtils.isMaybeThenable(entry)) { if (entry.constructor === c && entry._state !== _rsvpInternal.PENDING) { entry._onError = null; enumerator._settledAt(entry._state, i, entry._result); } else { enumerator._willSettleAt(c.resolve(entry), i); } } else { enumerator._remaining--; enumerator._result[i] = enumerator._makeResult(_rsvpInternal.FULFILLED, i, entry); } }; Enumerator.prototype._settledAt = function (state, i, value) { var enumerator = this; var promise = enumerator.promise; if (promise._state === _rsvpInternal.PENDING) { enumerator._remaining--; if (enumerator._abortOnReject && state === _rsvpInternal.REJECTED) { _rsvpInternal.reject(promise, value); } else { enumerator._result[i] = enumerator._makeResult(state, i, value); } } if (enumerator._remaining === 0) { _rsvpInternal.fulfill(promise, enumerator._result); } }; Enumerator.prototype._makeResult = function (state, i, value) { return value; }; Enumerator.prototype._willSettleAt = function (promise, i) { var enumerator = this; _rsvpInternal.subscribe(promise, undefined, function (value) { enumerator._settledAt(_rsvpInternal.FULFILLED, i, value); }, function (reason) { enumerator._settledAt(_rsvpInternal.REJECTED, i, reason); }); }; }); enifed('rsvp/events', ['exports'], function (exports) { 'use strict'; function indexOf(callbacks, callback) { for (var i = 0, l = callbacks.length; i < l; i++) { if (callbacks[i] === callback) { return i; } } return -1; } function callbacksFor(object) { var callbacks = object._promiseCallbacks; if (!callbacks) { callbacks = object._promiseCallbacks = {}; } return callbacks; } /** @class RSVP.EventTarget */ exports.default = { /** `RSVP.EventTarget.mixin` extends an object with EventTarget methods. For Example: ```javascript var object = {}; RSVP.EventTarget.mixin(object); object.on('finished', function(event) { // handle event }); object.trigger('finished', { detail: value }); ``` `EventTarget.mixin` also works with prototypes: ```javascript var Person = function() {}; RSVP.EventTarget.mixin(Person.prototype); var yehuda = new Person(); var tom = new Person(); yehuda.on('poke', function(event) { console.log('Yehuda says OW'); }); tom.on('poke', function(event) { console.log('Tom says OW'); }); yehuda.trigger('poke'); tom.trigger('poke'); ``` @method mixin @for RSVP.EventTarget @private @param {Object} object object to extend with EventTarget methods */ 'mixin': function (object) { object['on'] = this['on']; object['off'] = this['off']; object['trigger'] = this['trigger']; object._promiseCallbacks = undefined; return object; }, /** Registers a callback to be executed when `eventName` is triggered ```javascript object.on('event', function(eventInfo){ // handle the event }); object.trigger('event'); ``` @method on @for RSVP.EventTarget @private @param {String} eventName name of the event to listen for @param {Function} callback function to be called when the event is triggered. */ 'on': function (eventName, callback) { if (typeof callback !== 'function') { throw new TypeError('Callback must be a function'); } var allCallbacks = callbacksFor(this), callbacks; callbacks = allCallbacks[eventName]; if (!callbacks) { callbacks = allCallbacks[eventName] = []; } if (indexOf(callbacks, callback) === -1) { callbacks.push(callback); } }, /** You can use `off` to stop firing a particular callback for an event: ```javascript function doStuff() { // do stuff! } object.on('stuff', doStuff); object.trigger('stuff'); // doStuff will be called // Unregister ONLY the doStuff callback object.off('stuff', doStuff); object.trigger('stuff'); // doStuff will NOT be called ``` If you don't pass a `callback` argument to `off`, ALL callbacks for the event will not be executed when the event fires. For example: ```javascript var callback1 = function(){}; var callback2 = function(){}; object.on('stuff', callback1); object.on('stuff', callback2); object.trigger('stuff'); // callback1 and callback2 will be executed. object.off('stuff'); object.trigger('stuff'); // callback1 and callback2 will not be executed! ``` @method off @for RSVP.EventTarget @private @param {String} eventName event to stop listening to @param {Function} callback optional argument. If given, only the function given will be removed from the event's callback queue. If no `callback` argument is given, all callbacks will be removed from the event's callback queue. */ 'off': function (eventName, callback) { var allCallbacks = callbacksFor(this), callbacks, index; if (!callback) { allCallbacks[eventName] = []; return; } callbacks = allCallbacks[eventName]; index = indexOf(callbacks, callback); if (index !== -1) { callbacks.splice(index, 1); } }, /** Use `trigger` to fire custom events. For example: ```javascript object.on('foo', function(){ console.log('foo event happened!'); }); object.trigger('foo'); // 'foo event happened!' logged to the console ``` You can also pass a value as a second argument to `trigger` that will be passed as an argument to all event listeners for the event: ```javascript object.on('foo', function(value){ console.log(value.name); }); object.trigger('foo', { name: 'bar' }); // 'bar' logged to the console ``` @method trigger @for RSVP.EventTarget @private @param {String} eventName name of the event to be triggered @param {*} options optional value to be passed to any event handlers for the given `eventName` */ 'trigger': function (eventName, options) { var allCallbacks = callbacksFor(this), callbacks, callback; if (callbacks = allCallbacks[eventName]) { // Don't cache the callbacks.length since it may grow for (var i = 0; i < callbacks.length; i++) { callback = callbacks[i]; callback(options); } } } }; }); enifed('rsvp/filter', ['exports', 'rsvp/promise', 'rsvp/utils'], function (exports, _rsvpPromise, _rsvpUtils) { 'use strict'; exports.default = filter; /** `RSVP.filter` is similar to JavaScript's native `filter` method, except that it waits for all promises to become fulfilled before running the `filterFn` on each item in given to `promises`. `RSVP.filter` returns a promise that will become fulfilled with the result of running `filterFn` on the values the promises become fulfilled with. For example: ```javascript var promise1 = RSVP.resolve(1); var promise2 = RSVP.resolve(2); var promise3 = RSVP.resolve(3); var promises = [promise1, promise2, promise3]; var filterFn = function(item){ return item > 1; }; RSVP.filter(promises, filterFn).then(function(result){ // result is [ 2, 3 ] }); ``` If any of the `promises` given to `RSVP.filter` are rejected, the first promise that is rejected will be given as an argument to the returned promise's rejection handler. For example: ```javascript var promise1 = RSVP.resolve(1); var promise2 = RSVP.reject(new Error('2')); var promise3 = RSVP.reject(new Error('3')); var promises = [ promise1, promise2, promise3 ]; var filterFn = function(item){ return item > 1; }; RSVP.filter(promises, filterFn).then(function(array){ // Code here never runs because there are rejected promises! }, function(reason) { // reason.message === '2' }); ``` `RSVP.filter` will also wait for any promises returned from `filterFn`. For instance, you may want to fetch a list of users then return a subset of those users based on some asynchronous operation: ```javascript var alice = { name: 'alice' }; var bob = { name: 'bob' }; var users = [ alice, bob ]; var promises = users.map(function(user){ return RSVP.resolve(user); }); var filterFn = function(user){ // Here, Alice has permissions to create a blog post, but Bob does not. return getPrivilegesForUser(user).then(function(privs){ return privs.can_create_blog_post === true; }); }; RSVP.filter(promises, filterFn).then(function(users){ // true, because the server told us only Alice can create a blog post. users.length === 1; // false, because Alice is the only user present in `users` users[0] === bob; }); ``` @method filter @static @for RSVP @param {Array} promises @param {Function} filterFn - function to be called on each resolved value to filter the final results. @param {String} label optional string describing the promise. Useful for tooling. @return {Promise} */ function filter(promises, filterFn, label) { return _rsvpPromise.default.all(promises, label).then(function (values) { if (!_rsvpUtils.isFunction(filterFn)) { throw new TypeError("You must pass a function as filter's second argument."); } var length = values.length; var filtered = new Array(length); for (var i = 0; i < length; i++) { filtered[i] = filterFn(values[i]); } return _rsvpPromise.default.all(filtered, label).then(function (filtered) { var results = new Array(length); var newLength = 0; for (var i = 0; i < length; i++) { if (filtered[i]) { results[newLength] = values[i]; newLength++; } } results.length = newLength; return results; }); }); } }); enifed('rsvp/hash-settled', ['exports', 'rsvp/promise', 'rsvp/enumerator', 'rsvp/promise-hash', 'rsvp/utils'], function (exports, _rsvpPromise, _rsvpEnumerator, _rsvpPromiseHash, _rsvpUtils) { 'use strict'; exports.default = hashSettled; function HashSettled(Constructor, object, label) { this._superConstructor(Constructor, object, false, label); } HashSettled.prototype = _rsvpUtils.o_create(_rsvpPromiseHash.default.prototype); HashSettled.prototype._superConstructor = _rsvpEnumerator.default; HashSettled.prototype._makeResult = _rsvpEnumerator.makeSettledResult; HashSettled.prototype._validationError = function () { return new Error('hashSettled must be called with an object'); }; /** `RSVP.hashSettled` is similar to `RSVP.allSettled`, but takes an object instead of an array for its `promises` argument. Unlike `RSVP.all` or `RSVP.hash`, which implement a fail-fast method, but like `RSVP.allSettled`, `hashSettled` waits until all the constituent promises have returned and then shows you all the results with their states and values/reasons. This is useful if you want to handle multiple promises' failure states together as a set. Returns a promise that is fulfilled when all the given promises have been settled, or rejected if the passed parameters are invalid. The returned promise is fulfilled with a hash that has the same key names as the `promises` object argument. If any of the values in the object are not promises, they will be copied over to the fulfilled object and marked with state 'fulfilled'. Example: ```javascript var promises = { myPromise: RSVP.Promise.resolve(1), yourPromise: RSVP.Promise.resolve(2), theirPromise: RSVP.Promise.resolve(3), notAPromise: 4 }; RSVP.hashSettled(promises).then(function(hash){ // hash here is an object that looks like: // { // myPromise: { state: 'fulfilled', value: 1 }, // yourPromise: { state: 'fulfilled', value: 2 }, // theirPromise: { state: 'fulfilled', value: 3 }, // notAPromise: { state: 'fulfilled', value: 4 } // } }); ``` If any of the `promises` given to `RSVP.hash` are rejected, the state will be set to 'rejected' and the reason for rejection provided. Example: ```javascript var promises = { myPromise: RSVP.Promise.resolve(1), rejectedPromise: RSVP.Promise.reject(new Error('rejection')), anotherRejectedPromise: RSVP.Promise.reject(new Error('more rejection')), }; RSVP.hashSettled(promises).then(function(hash){ // hash here is an object that looks like: // { // myPromise: { state: 'fulfilled', value: 1 }, // rejectedPromise: { state: 'rejected', reason: Error }, // anotherRejectedPromise: { state: 'rejected', reason: Error }, // } // Note that for rejectedPromise, reason.message == 'rejection', // and for anotherRejectedPromise, reason.message == 'more rejection'. }); ``` An important note: `RSVP.hashSettled` is intended for plain JavaScript objects that are just a set of keys and values. `RSVP.hashSettled` will NOT preserve prototype chains. Example: ```javascript function MyConstructor(){ this.example = RSVP.Promise.resolve('Example'); } MyConstructor.prototype = { protoProperty: RSVP.Promise.resolve('Proto Property') }; var myObject = new MyConstructor(); RSVP.hashSettled(myObject).then(function(hash){ // protoProperty will not be present, instead you will just have an // object that looks like: // { // example: { state: 'fulfilled', value: 'Example' } // } // // hash.hasOwnProperty('protoProperty'); // false // 'undefined' === typeof hash.protoProperty }); ``` @method hashSettled @for RSVP @param {Object} object @param {String} label optional string that describes the promise. Useful for tooling. @return {Promise} promise that is fulfilled when when all properties of `promises` have been settled. @static */ function hashSettled(object, label) { return new HashSettled(_rsvpPromise.default, object, label).promise; } }); enifed('rsvp/hash', ['exports', 'rsvp/promise', 'rsvp/promise-hash'], function (exports, _rsvpPromise, _rsvpPromiseHash) { 'use strict'; exports.default = hash; /** `RSVP.hash` is similar to `RSVP.all`, but takes an object instead of an array for its `promises` argument. Returns a promise that is fulfilled when all the given promises have been fulfilled, or rejected if any of them become rejected. The returned promise is fulfilled with a hash that has the same key names as the `promises` object argument. If any of the values in the object are not promises, they will simply be copied over to the fulfilled object. Example: ```javascript var promises = { myPromise: RSVP.resolve(1), yourPromise: RSVP.resolve(2), theirPromise: RSVP.resolve(3), notAPromise: 4 }; RSVP.hash(promises).then(function(hash){ // hash here is an object that looks like: // { // myPromise: 1, // yourPromise: 2, // theirPromise: 3, // notAPromise: 4 // } }); ```` If any of the `promises` given to `RSVP.hash` are rejected, the first promise that is rejected will be given as the reason to the rejection handler. Example: ```javascript var promises = { myPromise: RSVP.resolve(1), rejectedPromise: RSVP.reject(new Error('rejectedPromise')), anotherRejectedPromise: RSVP.reject(new Error('anotherRejectedPromise')), }; RSVP.hash(promises).then(function(hash){ // Code here never runs because there are rejected promises! }, function(reason) { // reason.message === 'rejectedPromise' }); ``` An important note: `RSVP.hash` is intended for plain JavaScript objects that are just a set of keys and values. `RSVP.hash` will NOT preserve prototype chains. Example: ```javascript function MyConstructor(){ this.example = RSVP.resolve('Example'); } MyConstructor.prototype = { protoProperty: RSVP.resolve('Proto Property') }; var myObject = new MyConstructor(); RSVP.hash(myObject).then(function(hash){ // protoProperty will not be present, instead you will just have an // object that looks like: // { // example: 'Example' // } // // hash.hasOwnProperty('protoProperty'); // false // 'undefined' === typeof hash.protoProperty }); ``` @method hash @static @for RSVP @param {Object} object @param {String} label optional string that describes the promise. Useful for tooling. @return {Promise} promise that is fulfilled when all properties of `promises` have been fulfilled, or rejected if any of them become rejected. */ function hash(object, label) { return new _rsvpPromiseHash.default(_rsvpPromise.default, object, label).promise; } }); enifed('rsvp/instrument', ['exports', 'rsvp/config', 'rsvp/utils'], function (exports, _rsvpConfig, _rsvpUtils) { 'use strict'; exports.default = instrument; var queue = []; function scheduleFlush() { setTimeout(function () { var entry; for (var i = 0; i < queue.length; i++) { entry = queue[i]; var payload = entry.payload; payload.guid = payload.key + payload.id; payload.childGuid = payload.key + payload.childId; if (payload.error) { payload.stack = payload.error.stack; } _rsvpConfig.config['trigger'](entry.name, entry.payload); } queue.length = 0; }, 50); } function instrument(eventName, promise, child) { if (1 === queue.push({ name: eventName, payload: { key: promise._guidKey, id: promise._id, eventName: eventName, detail: promise._result, childId: child && child._id, label: promise._label, timeStamp: _rsvpUtils.now(), error: _rsvpConfig.config["instrument-with-stack"] ? new Error(promise._label) : null } })) { scheduleFlush(); } } }); enifed('rsvp/map', ['exports', 'rsvp/promise', 'rsvp/utils'], function (exports, _rsvpPromise, _rsvpUtils) { 'use strict'; exports.default = map; /** `RSVP.map` is similar to JavaScript's native `map` method, except that it waits for all promises to become fulfilled before running the `mapFn` on each item in given to `promises`. `RSVP.map` returns a promise that will become fulfilled with the result of running `mapFn` on the values the promises become fulfilled with. For example: ```javascript var promise1 = RSVP.resolve(1); var promise2 = RSVP.resolve(2); var promise3 = RSVP.resolve(3); var promises = [ promise1, promise2, promise3 ]; var mapFn = function(item){ return item + 1; }; RSVP.map(promises, mapFn).then(function(result){ // result is [ 2, 3, 4 ] }); ``` If any of the `promises` given to `RSVP.map` are rejected, the first promise that is rejected will be given as an argument to the returned promise's rejection handler. For example: ```javascript var promise1 = RSVP.resolve(1); var promise2 = RSVP.reject(new Error('2')); var promise3 = RSVP.reject(new Error('3')); var promises = [ promise1, promise2, promise3 ]; var mapFn = function(item){ return item + 1; }; RSVP.map(promises, mapFn).then(function(array){ // Code here never runs because there are rejected promises! }, function(reason) { // reason.message === '2' }); ``` `RSVP.map` will also wait if a promise is returned from `mapFn`. For example, say you want to get all comments from a set of blog posts, but you need the blog posts first because they contain a url to those comments. ```javscript var mapFn = function(blogPost){ // getComments does some ajax and returns an RSVP.Promise that is fulfilled // with some comments data return getComments(blogPost.comments_url); }; // getBlogPosts does some ajax and returns an RSVP.Promise that is fulfilled // with some blog post data RSVP.map(getBlogPosts(), mapFn).then(function(comments){ // comments is the result of asking the server for the comments // of all blog posts returned from getBlogPosts() }); ``` @method map @static @for RSVP @param {Array} promises @param {Function} mapFn function to be called on each fulfilled promise. @param {String} label optional string for labeling the promise. Useful for tooling. @return {Promise} promise that is fulfilled with the result of calling `mapFn` on each fulfilled promise or value when they become fulfilled. The promise will be rejected if any of the given `promises` become rejected. @static */ function map(promises, mapFn, label) { return _rsvpPromise.default.all(promises, label).then(function (values) { if (!_rsvpUtils.isFunction(mapFn)) { throw new TypeError("You must pass a function as map's second argument."); } var length = values.length; var results = new Array(length); for (var i = 0; i < length; i++) { results[i] = mapFn(values[i]); } return _rsvpPromise.default.all(results, label); }); } }); enifed('rsvp/node', ['exports', 'rsvp/promise', 'rsvp/-internal', 'rsvp/utils'], function (exports, _rsvpPromise, _rsvpInternal, _rsvpUtils) { 'use strict'; exports.default = denodeify; function _defaults(obj, defaults) { var keys = Object.getOwnPropertyNames(defaults); for (var i = 0; i < keys.length; i++) { var key = keys[i]; var value = Object.getOwnPropertyDescriptor(defaults, key); if (value && value.configurable && obj[key] === undefined) { Object.defineProperty(obj, key, value); } } return obj; } function Result() { this.value = undefined; } var ERROR = new Result(); var GET_THEN_ERROR = new Result(); function getThen(obj) { try { return obj.then; } catch (error) { ERROR.value = error; return ERROR; } } function tryApply(f, s, a) { try { f.apply(s, a); } catch (error) { ERROR.value = error; return ERROR; } } function makeObject(_, argumentNames) { var obj = {}; var name; var i; var length = _.length; var args = new Array(length); for (var x = 0; x < length; x++) { args[x] = _[x]; } for (i = 0; i < argumentNames.length; i++) { name = argumentNames[i]; obj[name] = args[i + 1]; } return obj; } function arrayResult(_) { var length = _.length; var args = new Array(length - 1); for (var i = 1; i < length; i++) { args[i - 1] = _[i]; } return args; } function wrapThenable(then, promise) { return { then: function (onFulFillment, onRejection) { return then.call(promise, onFulFillment, onRejection); } }; } /** `RSVP.denodeify` takes a 'node-style' function and returns a function that will return an `RSVP.Promise`. You can use `denodeify` in Node.js or the browser when you'd prefer to use promises over using callbacks. For example, `denodeify` transforms the following: ```javascript var fs = require('fs'); fs.readFile('myfile.txt', function(err, data){ if (err) return handleError(err); handleData(data); }); ``` into: ```javascript var fs = require('fs'); var readFile = RSVP.denodeify(fs.readFile); readFile('myfile.txt').then(handleData, handleError); ``` If the node function has multiple success parameters, then `denodeify` just returns the first one: ```javascript var request = RSVP.denodeify(require('request')); request('http://example.com').then(function(res) { // ... }); ``` However, if you need all success parameters, setting `denodeify`'s second parameter to `true` causes it to return all success parameters as an array: ```javascript var request = RSVP.denodeify(require('request'), true); request('http://example.com').then(function(result) { // result[0] -> res // result[1] -> body }); ``` Or if you pass it an array with names it returns the parameters as a hash: ```javascript var request = RSVP.denodeify(require('request'), ['res', 'body']); request('http://example.com').then(function(result) { // result.res // result.body }); ``` Sometimes you need to retain the `this`: ```javascript var app = require('express')(); var render = RSVP.denodeify(app.render.bind(app)); ``` The denodified function inherits from the original function. It works in all environments, except IE 10 and below. Consequently all properties of the original function are available to you. However, any properties you change on the denodeified function won't be changed on the original function. Example: ```javascript var request = RSVP.denodeify(require('request')), cookieJar = request.jar(); // <- Inheritance is used here request('http://example.com', {jar: cookieJar}).then(function(res) { // cookieJar.cookies holds now the cookies returned by example.com }); ``` Using `denodeify` makes it easier to compose asynchronous operations instead of using callbacks. For example, instead of: ```javascript var fs = require('fs'); fs.readFile('myfile.txt', function(err, data){ if (err) { ... } // Handle error fs.writeFile('myfile2.txt', data, function(err){ if (err) { ... } // Handle error console.log('done') }); }); ``` you can chain the operations together using `then` from the returned promise: ```javascript var fs = require('fs'); var readFile = RSVP.denodeify(fs.readFile); var writeFile = RSVP.denodeify(fs.writeFile); readFile('myfile.txt').then(function(data){ return writeFile('myfile2.txt', data); }).then(function(){ console.log('done') }).catch(function(error){ // Handle error }); ``` @method denodeify @static @for RSVP @param {Function} nodeFunc a 'node-style' function that takes a callback as its last argument. The callback expects an error to be passed as its first argument (if an error occurred, otherwise null), and the value from the operation as its second argument ('function(err, value){ }'). @param {Boolean|Array} [options] An optional paramter that if set to `true` causes the promise to fulfill with the callback's success arguments as an array. This is useful if the node function has multiple success paramters. If you set this paramter to an array with names, the promise will fulfill with a hash with these names as keys and the success parameters as values. @return {Function} a function that wraps `nodeFunc` to return an `RSVP.Promise` @static */ function denodeify(nodeFunc, options) { var fn = function () { var self = this; var l = arguments.length; var args = new Array(l + 1); var arg; var promiseInput = false; for (var i = 0; i < l; ++i) { arg = arguments[i]; if (!promiseInput) { // TODO: clean this up promiseInput = needsPromiseInput(arg); if (promiseInput === GET_THEN_ERROR) { var p = new _rsvpPromise.default(_rsvpInternal.noop); _rsvpInternal.reject(p, GET_THEN_ERROR.value); return p; } else if (promiseInput && promiseInput !== true) { arg = wrapThenable(promiseInput, arg); } } args[i] = arg; } var promise = new _rsvpPromise.default(_rsvpInternal.noop); args[l] = function (err, val) { if (err) _rsvpInternal.reject(promise, err);else if (options === undefined) _rsvpInternal.resolve(promise, val);else if (options === true) _rsvpInternal.resolve(promise, arrayResult(arguments));else if (_rsvpUtils.isArray(options)) _rsvpInternal.resolve(promise, makeObject(arguments, options));else _rsvpInternal.resolve(promise, val); }; if (promiseInput) { return handlePromiseInput(promise, args, nodeFunc, self); } else { return handleValueInput(promise, args, nodeFunc, self); } }; _defaults(fn, nodeFunc); return fn; } function handleValueInput(promise, args, nodeFunc, self) { var result = tryApply(nodeFunc, self, args); if (result === ERROR) { _rsvpInternal.reject(promise, result.value); } return promise; } function handlePromiseInput(promise, args, nodeFunc, self) { return _rsvpPromise.default.all(args).then(function (args) { var result = tryApply(nodeFunc, self, args); if (result === ERROR) { _rsvpInternal.reject(promise, result.value); } return promise; }); } function needsPromiseInput(arg) { if (arg && typeof arg === 'object') { if (arg.constructor === _rsvpPromise.default) { return true; } else { return getThen(arg); } } else { return false; } } }); enifed('rsvp/platform', ['exports'], function (exports) { 'use strict'; var platform; /* global self */ if (typeof self === 'object') { platform = self; /* global global */ } else if (typeof global === 'object') { platform = global; } else { throw new Error('no global: `self` or `global` found'); } exports.default = platform; }); enifed('rsvp/promise/all', ['exports', 'rsvp/enumerator'], function (exports, _rsvpEnumerator) { 'use strict'; exports.default = all; /** `RSVP.Promise.all` accepts an array of promises, and returns a new promise which is fulfilled with an array of fulfillment values for the passed promises, or rejected with the reason of the first passed promise to be rejected. It casts all elements of the passed iterable to promises as it runs this algorithm. Example: ```javascript var promise1 = RSVP.resolve(1); var promise2 = RSVP.resolve(2); var promise3 = RSVP.resolve(3); var promises = [ promise1, promise2, promise3 ]; RSVP.Promise.all(promises).then(function(array){ // The array here would be [ 1, 2, 3 ]; }); ``` If any of the `promises` given to `RSVP.all` are rejected, the first promise that is rejected will be given as an argument to the returned promises's rejection handler. For example: Example: ```javascript var promise1 = RSVP.resolve(1); var promise2 = RSVP.reject(new Error("2")); var promise3 = RSVP.reject(new Error("3")); var promises = [ promise1, promise2, promise3 ]; RSVP.Promise.all(promises).then(function(array){ // Code here never runs because there are rejected promises! }, function(error) { // error.message === "2" }); ``` @method all @static @param {Array} entries array of promises @param {String} label optional string for labeling the promise. Useful for tooling. @return {Promise} promise that is fulfilled when all `promises` have been fulfilled, or rejected if any of them become rejected. @static */ function all(entries, label) { return new _rsvpEnumerator.default(this, entries, true, /* abort on reject */label).promise; } }); enifed('rsvp/promise/race', ['exports', 'rsvp/utils', 'rsvp/-internal'], function (exports, _rsvpUtils, _rsvpInternal) { 'use strict'; exports.default = race; /** `RSVP.Promise.race` returns a new promise which is settled in the same way as the first passed promise to settle. Example: ```javascript var promise1 = new RSVP.Promise(function(resolve, reject){ setTimeout(function(){ resolve('promise 1'); }, 200); }); var promise2 = new RSVP.Promise(function(resolve, reject){ setTimeout(function(){ resolve('promise 2'); }, 100); }); RSVP.Promise.race([promise1, promise2]).then(function(result){ // result === 'promise 2' because it was resolved before promise1 // was resolved. }); ``` `RSVP.Promise.race` is deterministic in that only the state of the first settled promise matters. For example, even if other promises given to the `promises` array argument are resolved, but the first settled promise has become rejected before the other promises became fulfilled, the returned promise will become rejected: ```javascript var promise1 = new RSVP.Promise(function(resolve, reject){ setTimeout(function(){ resolve('promise 1'); }, 200); }); var promise2 = new RSVP.Promise(function(resolve, reject){ setTimeout(function(){ reject(new Error('promise 2')); }, 100); }); RSVP.Promise.race([promise1, promise2]).then(function(result){ // Code here never runs }, function(reason){ // reason.message === 'promise 2' because promise 2 became rejected before // promise 1 became fulfilled }); ``` An example real-world use case is implementing timeouts: ```javascript RSVP.Promise.race([ajax('foo.json'), timeout(5000)]) ``` @method race @static @param {Array} entries array of promises to observe @param {String} label optional string for describing the promise returned. Useful for tooling. @return {Promise} a promise which settles in the same way as the first passed promise to settle. */ function race(entries, label) { /*jshint validthis:true */ var Constructor = this; var promise = new Constructor(_rsvpInternal.noop, label); if (!_rsvpUtils.isArray(entries)) { _rsvpInternal.reject(promise, new TypeError('You must pass an array to race.')); return promise; } var length = entries.length; function onFulfillment(value) { _rsvpInternal.resolve(promise, value); } function onRejection(reason) { _rsvpInternal.reject(promise, reason); } for (var i = 0; promise._state === _rsvpInternal.PENDING && i < length; i++) { _rsvpInternal.subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection); } return promise; } }); enifed('rsvp/promise/reject', ['exports', 'rsvp/-internal'], function (exports, _rsvpInternal) { 'use strict'; exports.default = reject; /** `RSVP.Promise.reject` returns a promise rejected with the passed `reason`. It is shorthand for the following: ```javascript var promise = new RSVP.Promise(function(resolve, reject){ reject(new Error('WHOOPS')); }); promise.then(function(value){ // Code here doesn't run because the promise is rejected! }, function(reason){ // reason.message === 'WHOOPS' }); ``` Instead of writing the above, your code now simply becomes the following: ```javascript var promise = RSVP.Promise.reject(new Error('WHOOPS')); promise.then(function(value){ // Code here doesn't run because the promise is rejected! }, function(reason){ // reason.message === 'WHOOPS' }); ``` @method reject @static @param {*} reason value that the returned promise will be rejected with. @param {String} label optional string for identifying the returned promise. Useful for tooling. @return {Promise} a promise rejected with the given `reason`. */ function reject(reason, label) { /*jshint validthis:true */ var Constructor = this; var promise = new Constructor(_rsvpInternal.noop, label); _rsvpInternal.reject(promise, reason); return promise; } }); enifed('rsvp/promise/resolve', ['exports', 'rsvp/-internal'], function (exports, _rsvpInternal) { 'use strict'; exports.default = resolve; /** `RSVP.Promise.resolve` returns a promise that will become resolved with the passed `value`. It is shorthand for the following: ```javascript var promise = new RSVP.Promise(function(resolve, reject){ resolve(1); }); promise.then(function(value){ // value === 1 }); ``` Instead of writing the above, your code now simply becomes the following: ```javascript var promise = RSVP.Promise.resolve(1); promise.then(function(value){ // value === 1 }); ``` @method resolve @static @param {*} object value that the returned promise will be resolved with @param {String} label optional string for identifying the returned promise. Useful for tooling. @return {Promise} a promise that will become fulfilled with the given `value` */ function resolve(object, label) { /*jshint validthis:true */ var Constructor = this; if (object && typeof object === 'object' && object.constructor === Constructor) { return object; } var promise = new Constructor(_rsvpInternal.noop, label); _rsvpInternal.resolve(promise, object); return promise; } }); enifed('rsvp/promise-hash', ['exports', 'rsvp/enumerator', 'rsvp/-internal', 'rsvp/utils'], function (exports, _rsvpEnumerator, _rsvpInternal, _rsvpUtils) { 'use strict'; function PromiseHash(Constructor, object, label) { this._superConstructor(Constructor, object, true, label); } exports.default = PromiseHash; PromiseHash.prototype = _rsvpUtils.o_create(_rsvpEnumerator.default.prototype); PromiseHash.prototype._superConstructor = _rsvpEnumerator.default; PromiseHash.prototype._init = function () { this._result = {}; }; PromiseHash.prototype._validateInput = function (input) { return input && typeof input === 'object'; }; PromiseHash.prototype._validationError = function () { return new Error('Promise.hash must be called with an object'); }; PromiseHash.prototype._enumerate = function () { var enumerator = this; var promise = enumerator.promise; var input = enumerator._input; var results = []; for (var key in input) { if (promise._state === _rsvpInternal.PENDING && Object.prototype.hasOwnProperty.call(input, key)) { results.push({ position: key, entry: input[key] }); } } var length = results.length; enumerator._remaining = length; var result; for (var i = 0; promise._state === _rsvpInternal.PENDING && i < length; i++) { result = results[i]; enumerator._eachEntry(result.entry, result.position); } }; }); enifed('rsvp/promise', ['exports', 'rsvp/config', 'rsvp/instrument', 'rsvp/utils', 'rsvp/-internal', 'rsvp/promise/all', 'rsvp/promise/race', 'rsvp/promise/resolve', 'rsvp/promise/reject'], function (exports, _rsvpConfig, _rsvpInstrument, _rsvpUtils, _rsvpInternal, _rsvpPromiseAll, _rsvpPromiseRace, _rsvpPromiseResolve, _rsvpPromiseReject) { 'use strict'; exports.default = Promise; var guidKey = 'rsvp_' + _rsvpUtils.now() + '-'; var counter = 0; function needsResolver() { throw new TypeError('You must pass a resolver function as the first argument to the promise constructor'); } function needsNew() { throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function."); } /** Promise objects represent the eventual result of an asynchronous operation. The primary way of interacting with a promise is through its `then` method, which registers callbacks to receive either a promise’s eventual value or the reason why the promise cannot be fulfilled. Terminology ----------- - `promise` is an object or function with a `then` method whose behavior conforms to this specification. - `thenable` is an object or function that defines a `then` method. - `value` is any legal JavaScript value (including undefined, a thenable, or a promise). - `exception` is a value that is thrown using the throw statement. - `reason` is a value that indicates why a promise was rejected. - `settled` the final resting state of a promise, fulfilled or rejected. A promise can be in one of three states: pending, fulfilled, or rejected. Promises that are fulfilled have a fulfillment value and are in the fulfilled state. Promises that are rejected have a rejection reason and are in the rejected state. A fulfillment value is never a thenable. Promises can also be said to *resolve* a value. If this value is also a promise, then the original promise's settled state will match the value's settled state. So a promise that *resolves* a promise that rejects will itself reject, and a promise that *resolves* a promise that fulfills will itself fulfill. Basic Usage: ------------ ```js var promise = new Promise(function(resolve, reject) { // on success resolve(value); // on failure reject(reason); }); promise.then(function(value) { // on fulfillment }, function(reason) { // on rejection }); ``` Advanced Usage: --------------- Promises shine when abstracting away asynchronous interactions such as `XMLHttpRequest`s. ```js function getJSON(url) { return new Promise(function(resolve, reject){ var xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.onreadystatechange = handler; xhr.responseType = 'json'; xhr.setRequestHeader('Accept', 'application/json'); xhr.send(); function handler() { if (this.readyState === this.DONE) { if (this.status === 200) { resolve(this.response); } else { reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']')); } } }; }); } getJSON('/posts.json').then(function(json) { // on fulfillment }, function(reason) { // on rejection }); ``` Unlike callbacks, promises are great composable primitives. ```js Promise.all([ getJSON('/posts'), getJSON('/comments') ]).then(function(values){ values[0] // => postsJSON values[1] // => commentsJSON return values; }); ``` @class RSVP.Promise @param {function} resolver @param {String} label optional string for labeling the promise. Useful for tooling. @constructor */ function Promise(resolver, label) { var promise = this; promise._id = counter++; promise._label = label; promise._state = undefined; promise._result = undefined; promise._subscribers = []; if (_rsvpConfig.config.instrument) { _rsvpInstrument.default('created', promise); } if (_rsvpInternal.noop !== resolver) { if (!_rsvpUtils.isFunction(resolver)) { needsResolver(); } if (!(promise instanceof Promise)) { needsNew(); } _rsvpInternal.initializePromise(promise, resolver); } } Promise.cast = _rsvpPromiseResolve.default; // deprecated Promise.all = _rsvpPromiseAll.default; Promise.race = _rsvpPromiseRace.default; Promise.resolve = _rsvpPromiseResolve.default; Promise.reject = _rsvpPromiseReject.default; Promise.prototype = { constructor: Promise, _guidKey: guidKey, _onError: function (reason) { var promise = this; _rsvpConfig.config.after(function () { if (promise._onError) { _rsvpConfig.config['trigger']('error', reason); } }); }, /** The primary way of interacting with a promise is through its `then` method, which registers callbacks to receive either a promise's eventual value or the reason why the promise cannot be fulfilled. ```js findUser().then(function(user){ // user is available }, function(reason){ // user is unavailable, and you are given the reason why }); ``` Chaining -------- The return value of `then` is itself a promise. This second, 'downstream' promise is resolved with the return value of the first promise's fulfillment or rejection handler, or rejected if the handler throws an exception. ```js findUser().then(function (user) { return user.name; }, function (reason) { return 'default name'; }).then(function (userName) { // If `findUser` fulfilled, `userName` will be the user's name, otherwise it // will be `'default name'` }); findUser().then(function (user) { throw new Error('Found user, but still unhappy'); }, function (reason) { throw new Error('`findUser` rejected and we're unhappy'); }).then(function (value) { // never reached }, function (reason) { // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'. // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'. }); ``` If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream. ```js findUser().then(function (user) { throw new PedagogicalException('Upstream error'); }).then(function (value) { // never reached }).then(function (value) { // never reached }, function (reason) { // The `PedgagocialException` is propagated all the way down to here }); ``` Assimilation ------------ Sometimes the value you want to propagate to a downstream promise can only be retrieved asynchronously. This can be achieved by returning a promise in the fulfillment or rejection handler. The downstream promise will then be pending until the returned promise is settled. This is called *assimilation*. ```js findUser().then(function (user) { return findCommentsByAuthor(user); }).then(function (comments) { // The user's comments are now available }); ``` If the assimliated promise rejects, then the downstream promise will also reject. ```js findUser().then(function (user) { return findCommentsByAuthor(user); }).then(function (comments) { // If `findCommentsByAuthor` fulfills, we'll have the value here }, function (reason) { // If `findCommentsByAuthor` rejects, we'll have the reason here }); ``` Simple Example -------------- Synchronous Example ```javascript var result; try { result = findResult(); // success } catch(reason) { // failure } ``` Errback Example ```js findResult(function(result, err){ if (err) { // failure } else { // success } }); ``` Promise Example; ```javascript findResult().then(function(result){ // success }, function(reason){ // failure }); ``` Advanced Example -------------- Synchronous Example ```javascript var author, books; try { author = findAuthor(); books = findBooksByAuthor(author); // success } catch(reason) { // failure } ``` Errback Example ```js function foundBooks(books) { } function failure(reason) { } findAuthor(function(author, err){ if (err) { failure(err); // failure } else { try { findBoooksByAuthor(author, function(books, err) { if (err) { failure(err); } else { try { foundBooks(books); } catch(reason) { failure(reason); } } }); } catch(error) { failure(err); } // success } }); ``` Promise Example; ```javascript findAuthor(). then(findBooksByAuthor). then(function(books){ // found books }).catch(function(reason){ // something went wrong }); ``` @method then @param {Function} onFulfillment @param {Function} onRejection @param {String} label optional string for labeling the promise. Useful for tooling. @return {Promise} */ then: function (onFulfillment, onRejection, label) { var parent = this; var state = parent._state; if (state === _rsvpInternal.FULFILLED && !onFulfillment || state === _rsvpInternal.REJECTED && !onRejection) { if (_rsvpConfig.config.instrument) { _rsvpInstrument.default('chained', parent, parent); } return parent; } parent._onError = null; var child = new parent.constructor(_rsvpInternal.noop, label); var result = parent._result; if (_rsvpConfig.config.instrument) { _rsvpInstrument.default('chained', parent, child); } if (state) { var callback = arguments[state - 1]; _rsvpConfig.config.async(function () { _rsvpInternal.invokeCallback(state, child, callback, result); }); } else { _rsvpInternal.subscribe(parent, child, onFulfillment, onRejection); } return child; }, /** `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same as the catch block of a try/catch statement. ```js function findAuthor(){ throw new Error('couldn't find that author'); } // synchronous try { findAuthor(); } catch(reason) { // something went wrong } // async with promises findAuthor().catch(function(reason){ // something went wrong }); ``` @method catch @param {Function} onRejection @param {String} label optional string for labeling the promise. Useful for tooling. @return {Promise} */ 'catch': function (onRejection, label) { return this.then(undefined, onRejection, label); }, /** `finally` will be invoked regardless of the promise's fate just as native try/catch/finally behaves Synchronous example: ```js findAuthor() { if (Math.random() > 0.5) { throw new Error(); } return new Author(); } try { return findAuthor(); // succeed or fail } catch(error) { return findOtherAuther(); } finally { // always runs // doesn't affect the return value } ``` Asynchronous example: ```js findAuthor().catch(function(reason){ return findOtherAuther(); }).finally(function(){ // author was either found, or not }); ``` @method finally @param {Function} callback @param {String} label optional string for labeling the promise. Useful for tooling. @return {Promise} */ 'finally': function (callback, label) { var promise = this; var constructor = promise.constructor; return promise.then(function (value) { return constructor.resolve(callback()).then(function () { return value; }); }, function (reason) { return constructor.resolve(callback()).then(function () { throw reason; }); }, label); } }; }); enifed('rsvp/race', ['exports', 'rsvp/promise'], function (exports, _rsvpPromise) { 'use strict'; exports.default = race; /** This is a convenient alias for `RSVP.Promise.race`. @method race @static @for RSVP @param {Array} array Array of promises. @param {String} label An optional label. This is useful for tooling. */ function race(array, label) { return _rsvpPromise.default.race(array, label); } }); enifed('rsvp/reject', ['exports', 'rsvp/promise'], function (exports, _rsvpPromise) { 'use strict'; exports.default = reject; /** This is a convenient alias for `RSVP.Promise.reject`. @method reject @static @for RSVP @param {*} reason value that the returned promise will be rejected with. @param {String} label optional string for identifying the returned promise. Useful for tooling. @return {Promise} a promise rejected with the given `reason`. */ function reject(reason, label) { return _rsvpPromise.default.reject(reason, label); } }); enifed('rsvp/resolve', ['exports', 'rsvp/promise'], function (exports, _rsvpPromise) { 'use strict'; exports.default = resolve; /** This is a convenient alias for `RSVP.Promise.resolve`. @method resolve @static @for RSVP @param {*} value value that the returned promise will be resolved with @param {String} label optional string for identifying the returned promise. Useful for tooling. @return {Promise} a promise that will become fulfilled with the given `value` */ function resolve(value, label) { return _rsvpPromise.default.resolve(value, label); } }); enifed("rsvp/rethrow", ["exports"], function (exports) { /** `RSVP.rethrow` will rethrow an error on the next turn of the JavaScript event loop in order to aid debugging. Promises A+ specifies that any exceptions that occur with a promise must be caught by the promises implementation and bubbled to the last handler. For this reason, it is recommended that you always specify a second rejection handler function to `then`. However, `RSVP.rethrow` will throw the exception outside of the promise, so it bubbles up to your console if in the browser, or domain/cause uncaught exception in Node. `rethrow` will also throw the error again so the error can be handled by the promise per the spec. ```javascript function throws(){ throw new Error('Whoops!'); } var promise = new RSVP.Promise(function(resolve, reject){ throws(); }); promise.catch(RSVP.rethrow).then(function(){ // Code here doesn't run because the promise became rejected due to an // error! }, function (err){ // handle the error here }); ``` The 'Whoops' error will be thrown on the next turn of the event loop and you can watch for it in your console. You can also handle it using a rejection handler given to `.then` or `.catch` on the returned promise. @method rethrow @static @for RSVP @param {Error} reason reason the promise became rejected. @throws Error @static */ "use strict"; exports.default = rethrow; function rethrow(reason) { setTimeout(function () { throw reason; }); throw reason; } }); enifed('rsvp/utils', ['exports'], function (exports) { 'use strict'; exports.objectOrFunction = objectOrFunction; exports.isFunction = isFunction; exports.isMaybeThenable = isMaybeThenable; function objectOrFunction(x) { return typeof x === 'function' || typeof x === 'object' && x !== null; } function isFunction(x) { return typeof x === 'function'; } function isMaybeThenable(x) { return typeof x === 'object' && x !== null; } var _isArray; if (!Array.isArray) { _isArray = function (x) { return Object.prototype.toString.call(x) === '[object Array]'; }; } else { _isArray = Array.isArray; } var isArray = _isArray; exports.isArray = isArray; // Date.now is not available in browsers < IE9 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now#Compatibility var now = Date.now || function () { return new Date().getTime(); }; exports.now = now; function F() {} var o_create = Object.create || function (o) { if (arguments.length > 1) { throw new Error('Second argument not supported'); } if (typeof o !== 'object') { throw new TypeError('Argument must be an object'); } F.prototype = o; return new F(); }; exports.o_create = o_create; }); enifed('rsvp', ['exports', 'rsvp/promise', 'rsvp/events', 'rsvp/node', 'rsvp/all', 'rsvp/all-settled', 'rsvp/race', 'rsvp/hash', 'rsvp/hash-settled', 'rsvp/rethrow', 'rsvp/defer', 'rsvp/config', 'rsvp/map', 'rsvp/resolve', 'rsvp/reject', 'rsvp/filter', 'rsvp/asap'], function (exports, _rsvpPromise, _rsvpEvents, _rsvpNode, _rsvpAll, _rsvpAllSettled, _rsvpRace, _rsvpHash, _rsvpHashSettled, _rsvpRethrow, _rsvpDefer, _rsvpConfig, _rsvpMap, _rsvpResolve, _rsvpReject, _rsvpFilter, _rsvpAsap) { 'use strict'; // defaults _rsvpConfig.config.async = _rsvpAsap.default; _rsvpConfig.config.after = function (cb) { setTimeout(cb, 0); }; var cast = _rsvpResolve.default; function async(callback, arg) { _rsvpConfig.config.async(callback, arg); } function on() { _rsvpConfig.config['on'].apply(_rsvpConfig.config, arguments); } function off() { _rsvpConfig.config['off'].apply(_rsvpConfig.config, arguments); } // Set up instrumentation through `window.__PROMISE_INTRUMENTATION__` if (typeof window !== 'undefined' && typeof window['__PROMISE_INSTRUMENTATION__'] === 'object') { var callbacks = window['__PROMISE_INSTRUMENTATION__']; _rsvpConfig.configure('instrument', true); for (var eventName in callbacks) { if (callbacks.hasOwnProperty(eventName)) { on(eventName, callbacks[eventName]); } } } exports.cast = cast; exports.Promise = _rsvpPromise.default; exports.EventTarget = _rsvpEvents.default; exports.all = _rsvpAll.default; exports.allSettled = _rsvpAllSettled.default; exports.race = _rsvpRace.default; exports.hash = _rsvpHash.default; exports.hashSettled = _rsvpHashSettled.default; exports.rethrow = _rsvpRethrow.default; exports.defer = _rsvpDefer.default; exports.denodeify = _rsvpNode.default; exports.configure = _rsvpConfig.configure; exports.on = on; exports.off = off; exports.resolve = _rsvpResolve.default; exports.reject = _rsvpReject.default; exports.async = async; exports.map = _rsvpMap.default; exports.filter = _rsvpFilter.default; }); enifed('rsvp.umd', ['exports', 'rsvp/platform', 'rsvp'], function (exports, _rsvpPlatform, _rsvp) { 'use strict'; var RSVP = { 'race': _rsvp.race, 'Promise': _rsvp.Promise, 'allSettled': _rsvp.allSettled, 'hash': _rsvp.hash, 'hashSettled': _rsvp.hashSettled, 'denodeify': _rsvp.denodeify, 'on': _rsvp.on, 'off': _rsvp.off, 'map': _rsvp.map, 'filter': _rsvp.filter, 'resolve': _rsvp.resolve, 'reject': _rsvp.reject, 'all': _rsvp.all, 'rethrow': _rsvp.rethrow, 'defer': _rsvp.defer, 'EventTarget': _rsvp.EventTarget, 'configure': _rsvp.configure, 'async': _rsvp.async }; /* global define:true module:true window: true */ if (typeof define === 'function' && define['amd']) { define(function () { return RSVP; }); } else if (typeof module !== 'undefined' && module['exports']) { module['exports'] = RSVP; } else if (typeof _rsvpPlatform.default !== 'undefined') { _rsvpPlatform.default['RSVP'] = RSVP; } }); enifed("vertex", ["exports"], function (exports) { /** * DAG Vertex * * @class Vertex * @constructor */ "use strict"; exports.default = Vertex; function Vertex(name) { this.name = name; this.incoming = {}; this.incomingNames = []; this.hasOutgoing = false; this.value = null; } }); enifed("visit", ["exports"], function (exports) { "use strict"; exports.default = visit; function visit(vertex, fn, visited, path) { var name = vertex.name; var vertices = vertex.incoming; var names = vertex.incomingNames; var len = names.length; var i; if (!visited) { visited = {}; } if (!path) { path = []; } if (visited.hasOwnProperty(name)) { return; } path.push(name); visited[name] = true; for (i = 0; i < len; i++) { visit(vertices[names[i]], fn, visited, path); } fn(vertex, path); path.pop(); } }); requireModule("ember"); }());