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', './utils', './queue'], function (exports, _utils, _queue) { 'use strict'; exports.default = DeferredActionQueues; function DeferredActionQueues(queueNames, options) { var queues = this.queues = {}; this.queueNames = queueNames = queueNames || []; this.options = options; _utils.each(queueNames, function (queueName) { queues[queueName] = new _queue.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, queueItems, priorQueueNameIndex; var queueNameIndex = 0; var numberOfQueues = queueNames.length; var options = this.options; 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) { // In IE 6-8, try/finally doesn't work without a catch. // Unfortunately, this is impossible to test for since wrapping it in a parent try/catch doesn't trigger the bug. // This tests for another broken try/catch behavior that only exhibits in the same versions of IE. 'use strict'; var needsIETryCatchFix = (function (e, x) { try { x(); } catch (e) {} // jshint ignore:line return !!e; })(); exports.needsIETryCatchFix = needsIETryCatchFix; 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('backburner/queue', ['exports', './utils'], function (exports, _utils) { '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 queue = this._queue, currentTarget, currentMethod, i, l; 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 (_utils.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; exports.wrapInTryCatch = wrapInTryCatch; var NUMBER = /\d+/; function each(collection, callback) { for (var i = 0; i < collection.length; i++) { callback(collection[i]); } } // 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 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); } function wrapInTryCatch(func) { return function () { try { return func.apply(this, arguments); } catch (e) { throw e; } }; } }); 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._timers = []; this._eventCallbacks = { end: [], begin: [] }; } 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: function () /* target, method, args */{ if (this.currentInstance) { 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); } } else { return this.run.apply(this, arguments); } }, 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 = _backburnerUtils.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); } } // find position to insert var i = _backburnerBinarySearch.default(executeAt, this._timers); this._timers.splice(i, 0, executeAt, fn); updateLaterTimer(this, executeAt, wait); 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 = _backburnerPlatform.default.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); clearTimeout(debouncee[2]); } timer = _backburnerPlatform.default.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 () { var clearItems = function (item) { clearTimeout(item[2]); }; _backburnerUtils.each(this._throttlers, clearItems); this._throttlers = []; _backburnerUtils.each(this._debouncees, clearItems); this._debouncees = []; if (this._laterTimer) { clearTimeout(this._laterTimer); this._laterTimer = null; } this._timers = []; if (this._autorun) { 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) { if (this._laterTimer) { // Active timer? Then clear timer and reset for future timer clearTimeout(this._laterTimer); this._laterTimer = null; } if (this._timers.length > 0) { // Update to next available timer when available updateLaterTimer(this, this._timers[0], this._timers[0] - _backburnerUtils.now()); } } 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); clearTimeout(timer[2]); return true; } } return false; } }; Backburner.prototype.schedule = Backburner.prototype.defer; Backburner.prototype.scheduleOnce = Backburner.prototype.deferOnce; Backburner.prototype.later = Backburner.prototype.setTimeout; if (_backburnerPlatform.needsIETryCatchFix) { var originalRun = Backburner.prototype.run; Backburner.prototype.run = _backburnerUtils.wrapInTryCatch(originalRun); var originalEnd = Backburner.prototype.end; Backburner.prototype.end = _backburnerUtils.wrapInTryCatch(originalEnd); } function getOnError(options) { return options.onError || options.onErrorTarget && options.onErrorTarget[options.onErrorMethod]; } function createAutorun(backburner) { backburner.begin(); backburner._autorun = _backburnerPlatform.default.setTimeout(function () { backburner._autorun = null; backburner.end(); }); } function updateLaterTimer(backburner, executeAt, wait) { var n = _backburnerUtils.now(); if (!backburner._laterTimer || executeAt < backburner._laterTimerExpiresAt || backburner._laterTimerExpiresAt < n) { if (backburner._laterTimer) { // Clear when: // - Already expired // - New timer is earlier clearTimeout(backburner._laterTimer); if (backburner._laterTimerExpiresAt < n) { // If timer was never triggered // Calculate the left-over wait-time wait = Math.max(0, executeAt - n); } } backburner._laterTimer = _backburnerPlatform.default.setTimeout(function () { backburner._laterTimer = null; backburner._laterTimerExpiresAt = null; executeTimers(backburner); }, wait); backburner._laterTimerExpiresAt = n + wait; } } function executeTimers(backburner) { var n = _backburnerUtils.now(); var fns, i, l; backburner.run(function () { i = _backburnerBinarySearch.default(n, backburner._timers); fns = backburner._timers.splice(0, i); for (i = 1, l = fns.length; i < l; i += 2) { backburner.schedule(backburner.options.defaultQueue, null, fns[i]); } }); if (backburner._timers.length) { updateLaterTimer(backburner, backburner._timers[0], backburner._timers[0] - n); } } 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; } }); requireModule("ember"); enifed('container/container', ['exports', 'ember-metal/core', 'ember-metal/dictionary'], function (exports, _emberMetalCore, _emberMetalDictionary) { 'use strict'; /** 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.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); } Container.prototype = { /** @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 @return {any} */ lookup: function (fullName, options) { _emberMetalCore.default.assert('fullName must be a proper full name', this._registry.validateFullName(fullName)); return lookup(this, this._registry.normalize(fullName), options); }, /** Given a fullName return the corresponding factory. @private @method lookupFactory @param {String} fullName @return {any} */ lookupFactory: function (fullName) { _emberMetalCore.default.assert('fullName must be a proper full name', this._registry.validateFullName(fullName)); return factoryFor(this, this._registry.normalize(fullName)); }, /** 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); } } }; function lookup(container, fullName, options) { options = options || {}; if (container.cache[fullName] && options.singleton !== false) { return container.cache[fullName]; } var value = instantiate(container, fullName); if (value === undefined) { return; } if (container._registry.getOption(fullName, 'singleton') !== false && options.singleton !== false) { container.cache[fullName] = value; } return value; } function buildInjections(container) { var hash = {}; if (arguments.length > 1) { var injectionArgs = Array.prototype.slice.call(arguments, 1); var injections = []; var injection; for (var i = 0, l = injectionArgs.length; i < l; i++) { if (injectionArgs[i]) { injections = injections.concat(injectionArgs[i]); } } container._registry.validateInjections(injections); for (i = 0, l = injections.length; i < l; i++) { injection = injections[i]; hash[injection.property] = lookup(container, injection.fullName); } } return hash; } function factoryFor(container, fullName) { var cache = container.factoryCache; if (cache[fullName]) { return cache[fullName]; } var registry = container._registry; 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); factoryInjections._toString = registry.makeToString(factory, fullName); var injectedFactory = factory.extend(injections); injectedFactory.reopenClass(factoryInjections); if (factory && typeof factory._onLookup === 'function') { factory._onLookup(fullName); } 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; injections.container = container; 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; if (typeof factory.extend === 'function') { // assume the factory was extendable and is already injected return 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 return factory.create(injectionsFor(container, fullName)); } } } 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; }); // Ember.assert enifed('container/registry', ['exports', 'ember-metal/core', 'ember-metal/dictionary', 'ember-metal/merge', './container'], function (exports, _emberMetalCore, _emberMetalDictionary, _emberMetalMerge, _container) { '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 public 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; this.resolver = options && options.resolver ? options.resolver : function () {}; 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._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, /** @private @property resolver @type function */ 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 _container.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, options) { _emberMetalCore.default.assert('fullName must be a proper full name', this.validateFullName(fullName)); 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) { _emberMetalCore.default.assert('fullName must be a proper full name', this.validateFullName(fullName)); var normalizedName = this.normalize(fullName); 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 @return {Function} fullName's factory */ resolve: function (fullName) { _emberMetalCore.default.assert('fullName must be a proper full name', this.validateFullName(fullName)); var factory = resolve(this, this.normalize(fullName)); if (factory === undefined && this.fallback) { factory = this.fallback.resolve(fullName); } 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) { return fullName; }, /** A hook to enable custom fullName normalization behaviour @private @method normalizeFullName @param {String} fullName @return {string} normalized fullName */ normalizeFullName: function (fullName) { return fullName; }, /** normalize a fullName based on the applications 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) { return factory.toString(); }, /** Given a fullName check if the container is aware of its factory or singleton instance. @private @method has @param {String} fullName @return {Boolean} */ has: function (fullName) { _emberMetalCore.default.assert('fullName must be a proper full name', this.validateFullName(fullName)); return has(this, this.normalize(fullName)); }, /** 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, options) { options = options || {}; 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) { _emberMetalCore.default.assert('fullName must be a proper full name', this.validateFullName(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); } _emberMetalCore.default.assert('fullName must be a proper full name', this.validateFullName(fullName)); 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 }); }, /** @method knownForType @param {String} type the type to iterate over @private */ 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.knownForType) { resolverKnown = this.resolver.knownForType(type); } return _emberMetalMerge.assign({}, 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)) { _emberMetalCore.default.assert('Expected a proper full name, given \'' + hash[key] + '\'', this.validateFullName(hash[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 resolve(registry, normalizedName) { var cached = registry._resolveCache[normalizedName]; if (cached) { return cached; } if (registry._failCache[normalizedName]) { return; } var resolved = registry.resolver(normalizedName) || registry.registrations[normalizedName]; if (resolved) { registry._resolveCache[normalizedName] = resolved; } else { registry._failCache[normalizedName] = true; } return resolved; } function has(registry, fullName) { return registry.resolve(fullName) !== undefined; } exports.default = Registry; }); // Ember.assert enifed('container', ['exports', 'ember-metal/core', 'container/registry', 'container/container'], function (exports, _emberMetalCore, _containerRegistry, _containerContainer) { '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; }); enifed("dag-map", ["exports"], function (exports) { "use strict"; 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(); } /** * 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); } /** * DAG Vertex * * @class Vertex * @constructor */ function Vertex(name) { this.name = name; this.incoming = {}; this.incomingNames = []; this.hasOutgoing = false; this.value = 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(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(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(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); } } } }; exports.default = DAG; }); enifed('dag-map.umd', ['exports', './dag-map'], function (exports, _dagMap) { 'use strict'; /* global define:true module:true window: true */ if (typeof enifed === 'function' && enifed.amd) { enifed(function () { return _dagMap.default; }); } else if (typeof module !== 'undefined' && module.exports) { module.exports = _dagMap.default; } else if (typeof undefined !== 'undefined') { undefined['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 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 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.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, _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, { concatenatedProperties: ['attributeBindings'], /** @property isView @type Boolean @default true @static @private */ isView: true, // .......................................................... // TEMPLATE SUPPORT // /** The name of the template to lookup if no template is provided. By default `Ember.View` will lookup a template with this name in `Ember.TEMPLATES` (a shared global object). @property templateName @type String @default null @private */ templateName: null, /** The name of the layout to lookup if no layout is provided. By default `Ember.View` will lookup a template with this name in `Ember.TEMPLATES` (a shared global object). @property layoutName @type String @default null @public */ layoutName: null, /** The template used to render the view. This should be a function that accepts an optional context parameter and returns a string of HTML that will be inserted into the DOM relative to its parent view. In general, you should set the `templateName` property instead of setting the template yourself. @property template @type Function @private */ template: _emberMetalComputed.computed({ get: function () { var templateName = _emberMetalProperty_get.get(this, 'templateName'); var template = this.templateForName(templateName, 'template'); _emberMetalCore.default.assert('You specified the templateName ' + templateName + ' for ' + this + ', but it did not exist.', !templateName || !!template); return template || _emberMetalProperty_get.get(this, 'defaultTemplate'); }, set: function (key, value) { if (value !== undefined) { return value; } return _emberMetalProperty_get.get(this, key); } }), /** A view may contain a layout. A layout is a regular template but supersedes the `template` property during rendering. It is the responsibility of the layout template to retrieve the `template` property from the view (or alternatively, call `Handlebars.helpers.yield`, `{{yield}}`) to render it in the correct location. This is useful for a view that has a shared wrapper, but which delegates the rendering of the contents of the wrapper to the `template` property on a subclass. @property layout @type Function @public */ layout: _emberMetalComputed.computed({ get: function (key) { var layoutName = _emberMetalProperty_get.get(this, 'layoutName'); var layout = this.templateForName(layoutName, 'layout'); _emberMetalCore.default.assert('You specified the layoutName ' + layoutName + ' for ' + this + ', but it did not exist.', !layoutName || !!layout); return layout || _emberMetalProperty_get.get(this, 'defaultLayout'); }, set: function (key, value) { return value; } }), templateForName: function (name, type) { if (!name) { return; } _emberMetalCore.default.assert('templateNames are not allowed to contain periods: ' + name, name.indexOf('.') === -1); if (!this.container) { throw new _emberMetalError.default('Container was not found when looking up a views template. ' + 'This is most likely due to manually instantiating an Ember.View. ' + 'See: http://git.io/EKPpnA'); } return this.container.lookup('template:' + name); }, /** If a value that affects template rendering changes, the view should be re-rendered to reflect the new value. @method _contextDidChange @private @private */ _contextDidChange: _emberMetalMixin.observer('context', function () { this.rerender(); }), /** Return the nearest ancestor that is an instance of the provided class or mixin. @method nearestOfType @param {Class,Mixin} klass Subclass of Ember.View (or Ember.View itself), or an instance of Ember.Mixin. @return Ember.View @private */ nearestOfType: function (klass) { var view = _emberMetalProperty_get.get(this, 'parentView'); var isOfType = klass instanceof _emberMetalMixin.Mixin ? function (view) { return klass.detect(view); } : function (view) { return klass.detect(view.constructor); }; while (view) { if (isOfType(view)) { return view; } view = _emberMetalProperty_get.get(view, 'parentView'); } }, /** Return the nearest ancestor that has a given property. @method nearestWithProperty @param {String} property A property name @return Ember.View @private */ nearestWithProperty: function (property) { var view = _emberMetalProperty_get.get(this, 'parentView'); while (view) { if (property in view) { return view; } view = _emberMetalProperty_get.get(view, 'parentView'); } }, /** Renders the view again. This will work regardless of whether the view is already in the DOM or not. If the view is in the DOM, the rendering process will be deferred to give bindings a chance to synchronize. If children were added during the rendering process using `appendChild`, `rerender` will remove them, because they will be added again if needed by the next `render`. In general, if the display of your view changes, you should modify the DOM element directly instead of manually calling `rerender`, which can be slow. @method rerender @public */ rerender: function () { return this.currentState.rerender(this); }, /** 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); }, // .......................................................... // ELEMENT SUPPORT // /** Returns the current DOM element for the view. @property element @type DOMElement @public */ element: null, /** Returns a jQuery object for this view's element. If you pass in a selector string, this method will return a jQuery object, using the current element as its buffer. For example, calling `view.$('li')` will return a jQuery object containing all of the `li` elements inside the DOM element of this view. @method $ @param {String} [selector] a jQuery-compatible selector string @return {jQuery} the jQuery object for the DOM node @public */ $: function (sel) { _emberMetalCore.default.assert('You cannot access this.$() on a component with `tagName: \'\'` specified.', this.tagName !== ''); return this.currentState.$(this, sel); }, forEachChildView: function (callback) { var childViews = this.childViews; if (!childViews) { return this; } var len = childViews.length; var view, idx; for (idx = 0; idx < len; idx++) { view = childViews[idx]; callback(view); } return this; }, /** Appends the view's element to the specified parent element. If the view does not have an HTML representation yet, `createElement()` will be called automatically. Note that this method just schedules the view to be appended; the DOM element will not be appended to the given element until all bindings have finished synchronizing. This is not typically a function that you will need to call directly when building your application. You might consider using `Ember.ContainerView` instead. If you do need to use `appendTo`, be sure that the target element you are providing is associated with an `Ember.Application` and does not have an ancestor element that is associated with an Ember view. @method appendTo @param {String|DOMElement|jQuery} A selector, element, HTML string, or jQuery object @return {Ember.View} receiver @private */ appendTo: function (selector) { var target = _emberViewsSystemJquery.default(selector); _emberMetalCore.default.assert('You tried to append to (' + selector + ') but that isn\'t in the DOM', target.length > 0); _emberMetalCore.default.assert('You cannot append to an existing Ember.View. Consider using Ember.ContainerView instead.', !target.is('.ember-view') && !target.parents().is('.ember-view')); this.renderer.appendTo(this, target[0]); return this; }, /** @private Creates a new DOM element, renders the view into it, then returns the element. By default, the element created and rendered into will be a `BODY` element, since this is the default context that views are rendered into when being inserted directly into the DOM. ```js var element = view.renderToElement(); element.tagName; // => "BODY" ``` You can override the kind of element rendered into and returned by specifying an optional tag name as the first argument. ```js var element = view.renderToElement('table'); element.tagName; // => "TABLE" ``` This method is useful if you want to render the view into an element that is not in the document's body. Instead, a new `body` element, detached from the DOM is returned. FastBoot uses this to serialize the rendered view into a string for transmission over the network. ```js app.visit('/').then(function(instance) { var element; Ember.run(function() { element = renderToElement(instance); }); res.send(serialize(element)); }); ``` @method renderToElement @param {String} tagName The tag of the element to create and render into. Defaults to "body". @return {HTMLBodyElement} element @private */ renderToElement: function (tagName) { tagName = tagName || 'body'; var element = this.renderer._dom.createElement(tagName); this.renderer.appendTo(this, element); return element; }, /** Replaces the content of the specified parent element with this view's element. If the view does not have an HTML representation yet, the element will be generated automatically. Note that this method just schedules the view to be appended; the DOM element will not be appended to the given element until all bindings have finished synchronizing @method replaceIn @param {String|DOMElement|jQuery} target A selector, element, HTML string, or jQuery object @return {Ember.View} received @private */ replaceIn: function (selector) { var target = _emberViewsSystemJquery.default(selector); _emberMetalCore.default.assert('You tried to replace in (' + selector + ') but that isn\'t in the DOM', target.length > 0); _emberMetalCore.default.assert('You cannot replace an existing Ember.View. Consider using Ember.ContainerView instead.', !target.is('.ember-view') && !target.parents().is('.ember-view')); this.renderer.replaceIn(this, target[0]); return this; }, /** Appends the view's element to the document body. If the view does not have an HTML representation yet the element will be generated automatically. If your application uses the `rootElement` property, you must append the view within that element. Rendering views outside of the `rootElement` is not supported. Note that this method just schedules the view to be appended; the DOM element will not be appended to the document body until all bindings have finished synchronizing. @method append @return {Ember.View} receiver @private */ append: function () { return this.appendTo(document.body); }, /** Removes the view's element from the element to which it is attached. @method remove @return {Ember.View} receiver @private */ remove: function () { // What we should really do here is wait until the end of the run loop // to determine if the element has been re-appended to a different // element. // In the interim, we will just re-render if that happens. It is more // important than elements get garbage collected. if (!this.removedFromDOM) { this.destroyElement(); } // Set flag to avoid future renders this._willInsert = false; }, /** The HTML `id` of the view's element in the DOM. You can provide this value yourself but it must be unique (just as in HTML): ```handlebars {{my-component elementId="a-really-cool-id"}} ``` If not manually set a default value will be provided by the framework. Once rendered an element's `elementId` is considered immutable and you should never change it. If you need to compute a dynamic value for the `elementId`, you should do this when the component or element is being instantiated: ```javascript export default Ember.Component.extend({ setElementId: Ember.on('init', function() { var index = this.get('index'); this.set('elementId', 'component-id' + index); }) }); ``` @property elementId @type String @public */ elementId: null, /** Attempts to discover the element in the parent element. The default implementation looks for an element with an ID of `elementId` (or the view's guid if `elementId` is null). You can override this method to provide your own form of lookup. For example, if you want to discover your element using a CSS class name instead of an ID. @method findElementInParentElement @param {DOMElement} parentElement The parent's DOM element @return {DOMElement} The discovered element @private */ findElementInParentElement: function (parentElem) { var id = '#' + this.elementId; return _emberViewsSystemJquery.default(id)[0] || _emberViewsSystemJquery.default(id, parentElem)[0]; }, /** Creates a DOM representation of the view and all of its child views by recursively calling the `render()` method. Once the element is created, it sets the `element` property of the view to the rendered element. After the element has been inserted into the DOM, `didInsertElement` will be called on this view and all of its child views. @method createElement @return {Ember.View} receiver @private */ createElement: function () { if (this.element) { return this; } this.renderer.createElement(this); return this; }, /** Called when a view is going to insert an element into the DOM. @event willInsertElement @public */ willInsertElement: K, /** Called when the element of the view has been inserted into the DOM or after the view was re-rendered. Override this function to do any set up that requires an element in the document body. When a view has children, didInsertElement will be called on the child view(s) first, bubbling upwards through the hierarchy. @event didInsertElement @public */ didInsertElement: K, /** Called when the view is about to rerender, but before anything has been torn down. This is a good opportunity to tear down any manual observers you have installed based on the DOM state @event willClearRender @public */ willClearRender: K, /** Destroys any existing element along with the element for any child views as well. If the view does not currently have a element, then this method will do nothing. If you implement `willDestroyElement()` on your view, then this method will be invoked on your view before your element is destroyed to give you a chance to clean up any event handlers, etc. If you write a `willDestroyElement()` handler, you can assume that your `didInsertElement()` handler was called earlier for the same element. You should not call or override this method yourself, but you may want to implement the above callbacks. @method destroyElement @return {Ember.View} receiver @private */ destroyElement: function () { return this.currentState.destroyElement(this); }, /** Called when the element of the view is going to be destroyed. Override this function to do any teardown that requires an element, like removing event listeners. Please note: any property changes made during this event will have no effect on object observers. @event willDestroyElement @public */ willDestroyElement: K, /** Called when the parentView property has changed. @event parentViewDidChange @private */ parentViewDidChange: K, // .......................................................... // STANDARD RENDER PROPERTIES // /** Tag name for the view's outer element. The tag name is only used when an element is first created. If you change the `tagName` for an element, you must destroy and recreate the view element. By default, the render buffer will use a `
` tag for views. @property tagName @type String @default null @public */ // We leave this null by default so we can tell the difference between // the default case and a user-specified tag. tagName: null, /* Used to specify a default tagName that can be overridden when extending or invoking from a template. @property _defaultTagName @private */ /** Normally, Ember's component model is "write-only". The component takes a bunch of attributes that it got passed in, and uses them to render its template. One nice thing about this model is that if you try to set a value to the same thing as last time, Ember (through HTMLBars) will avoid doing any work on the DOM. This is not just a performance optimization. If an attribute has not changed, it is important not to clobber the element's "hidden state". For example, if you set an input's `value` to the same value as before, it will clobber selection state and cursor position. In other words, setting an attribute is not **always** idempotent. This method provides a way to read an element's attribute and also update the last value Ember knows about at the same time. This makes setting an attribute idempotent. In particular, what this means is that if you get an `` element's `value` attribute and then re-render the template with the same value, it will avoid clobbering the cursor and selection position. Since most attribute sets are idempotent in the browser, you typically can get away with reading attributes using jQuery, but the most reliable way to do so is through this method. @method readDOMAttr @param {String} name the name of the attribute @return String @public */ readDOMAttr: function (name) { var attr = this._renderNode.childNodes.filter(function (node) { return node.attrName === name; })[0]; if (!attr) { return null; } return attr.getContent(); }, // ....................................................... // CORE DISPLAY METHODS // /** Setup a view, but do not finish waking it up. * configure `childViews` * register the view with the global views hash, which is used for event dispatch @method init @private */ init: function () { if (!this.elementId) { this.elementId = _emberMetalUtils.guidFor(this); } this.scheduledRevalidation = false; this._super.apply(this, arguments); if (!this._viewRegistry) { this._viewRegistry = View.views; } this.renderer.componentInitAttrs(this, this.attrs || {}); _emberMetalCore.default.assert('Using a custom `.render` function is no longer supported.', !this.render); }, __defineNonEnumerable: function (property) { this[property.name] = property.descriptor.value; }, revalidate: function () { this.renderer.revalidateTopLevelView(this); this.scheduledRevalidation = false; }, scheduleRevalidate: function (node, label, manualRerender) { if (node && !this._dispatching && node.guid in this.env.renderedNodes) { if (manualRerender) { _emberMetalCore.default.deprecate('You manually rerendered ' + label + ' (a parent component) from a child component during the rendering process. This rarely worked in Ember 1.x and will be removed in Ember 2.0', false, { id: 'ember-views.manual-parent-rerender', until: '3.0.0' }); } else { _emberMetalCore.default.deprecate('You modified ' + label + ' twice in a single render. This was unreliable in Ember 1.x and will be removed in Ember 2.0', false, { id: 'ember-views.render-double-modify', until: '3.0.0' }); } _emberMetalRun_loop.default.scheduleOnce('render', this, this.revalidate); return; } _emberMetalCore.default.deprecate('A property of ' + this + ' was modified inside the ' + this._dispatching + ' hook. You should never change properties on components, services or models during ' + this._dispatching + ' because it causes significant performance degradation.', !this._dispatching, { id: 'ember-views.dispatching-modify-property', until: '3.0.0' }); if (!this.scheduledRevalidation || this._dispatching) { this.scheduledRevalidation = true; _emberMetalRun_loop.default.scheduleOnce('render', this, this.revalidate); } }, appendAttr: function (node, buffer) { return this.currentState.appendAttr(this, node, buffer); }, templateRenderer: null, /** Removes the view from its `parentView`, if one is found. Otherwise does nothing. @method removeFromParent @return {Ember.View} receiver @private */ removeFromParent: function () { var parent = this.parentView; // Remove DOM element from parent this.remove(); if (parent) { parent.removeChild(this); } return this; }, /** You must call `destroy` on a view to destroy the view (and all of its child views). This will remove the view from any parent node, then make sure that the DOM element managed by the view can be released by the memory manager. @method destroy @private */ destroy: function () { // get parentView before calling super because it'll be destroyed var parentView = this.parentView; var viewName = this.viewName; if (!this._super.apply(this, arguments)) { return; } // remove from non-virtual parent view if viewName was specified if (viewName && parentView) { parentView.set(viewName, null); } // Destroy HTMLbars template if (this.lastResult) { this.lastResult.destroy(); } return this; }, // ....................................................... // EVENT HANDLING // /** Handle events from `Ember.EventDispatcher` @method handleEvent @param eventName {String} @param evt {Event} @private */ handleEvent: function (eventName, evt) { return this.currentState.handleEvent(this, eventName, evt); }, /** Registers the view in the view registry, keyed on the view's `elementId`. This is used by the EventDispatcher to locate the view in response to events. This method should only be called once the view has been inserted into the DOM. @method _register @private */ _register: function () { _emberMetalCore.default.assert('Attempted to register a view with an id already in use: ' + this.elementId, !this._viewRegistry[this.elementId]); this._viewRegistry[this.elementId] = this; }, /** Removes the view from the view registry. This should be called when the view is removed from DOM. @method _unregister @private */ _unregister: function () { delete this._viewRegistry[this.elementId]; }, registerObserver: function (root, path, target, observer) { if (!observer && 'function' === typeof target) { observer = target; target = null; } if (!root || typeof root !== 'object') { return; } var scheduledObserver = this._wrapAsScheduled(observer); _emberMetalObserver.addObserver(root, path, target, scheduledObserver); this.one('willClearRender', function () { _emberMetalObserver.removeObserver(root, path, target, scheduledObserver); }); }, _wrapAsScheduled: function (fn) { var view = this; var stateCheckedFn = function () { view.currentState.invokeObserver(this, fn); }; var scheduledFn = function () { _emberMetalRun_loop.default.scheduleOnce('render', this, stateCheckedFn); }; return scheduledFn; } }); // 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() { _emberMetalCore.default.deprecate('Ember.View is deprecated. Consult the Deprecations Guide for a migration strategy.', !!_emberMetalCore.default.ENV._ENABLE_LEGACY_VIEW_SUPPORT, { url: 'http://emberjs.com/deprecations/v1.x/#toc_ember-view', id: 'ember-views.view-deprecated', until: '2.4.0' }); } 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('ember-views', ['exports', 'ember-runtime', 'ember-views/system/jquery', 'ember-views/system/utils', 'ember-views/system/ext', 'ember-views/views/states', 'ember-metal-views/renderer', 'ember-views/views/core_view', 'ember-views/views/view', 'ember-views/views/container_view', 'ember-views/views/collection_view', 'ember-views/views/component', 'ember-views/system/event_dispatcher', 'ember-views/mixins/view_target_action_support', 'ember-views/component_lookup', 'ember-views/views/checkbox', 'ember-views/mixins/text_support', 'ember-views/views/text_field', 'ember-views/views/text_area', 'ember-views/views/select', 'ember-views/compat/metamorph_view', 'ember-views/views/legacy_each_view'], function (exports, _emberRuntime, _emberViewsSystemJquery, _emberViewsSystemUtils, _emberViewsSystemExt, _emberViewsViewsStates, _emberMetalViewsRenderer, _emberViewsViewsCore_view, _emberViewsViewsView, _emberViewsViewsContainer_view, _emberViewsViewsCollection_view, _emberViewsViewsComponent, _emberViewsSystemEvent_dispatcher, _emberViewsMixinsView_target_action_support, _emberViewsComponent_lookup, _emberViewsViewsCheckbox, _emberViewsMixinsText_support, _emberViewsViewsText_field, _emberViewsViewsText_area, _emberViewsViewsSelect, _emberViewsCompatMetamorph_view, _emberViewsViewsLegacy_each_view) { /** @module ember @submodule ember-views */ // BEGIN IMPORTS 'use strict'; // END IMPORTS /** Alias for jQuery @method $ @for Ember @public */ // BEGIN EXPORTS _emberRuntime.default.$ = _emberViewsSystemJquery.default; _emberRuntime.default.ViewTargetActionSupport = _emberViewsMixinsView_target_action_support.default; var ViewUtils = _emberRuntime.default.ViewUtils = {}; ViewUtils.isSimpleClick = _emberViewsSystemUtils.isSimpleClick; ViewUtils.getViewClientRects = _emberViewsSystemUtils.getViewClientRects; ViewUtils.getViewBoundingClientRect = _emberViewsSystemUtils.getViewBoundingClientRect; if (_emberRuntime.default.ENV._ENABLE_LEGACY_VIEW_SUPPORT) { _emberRuntime.default.CoreView = _emberViewsViewsCore_view.DeprecatedCoreView; _emberRuntime.default.View = _emberViewsViewsView.DeprecatedView; _emberRuntime.default.View.states = _emberViewsViewsStates.states; _emberRuntime.default.View.cloneStates = _emberViewsViewsStates.cloneStates; _emberRuntime.default.View._Renderer = _emberMetalViewsRenderer.default; _emberRuntime.default.ContainerView = _emberViewsViewsContainer_view.DeprecatedContainerView; _emberRuntime.default.CollectionView = _emberViewsViewsCollection_view.DeprecatedCollectionView; } _emberRuntime.default._Renderer = _emberMetalViewsRenderer.default; _emberRuntime.default.Checkbox = _emberViewsViewsCheckbox.default; _emberRuntime.default.TextField = _emberViewsViewsText_field.default; _emberRuntime.default.TextArea = _emberViewsViewsText_area.default; if (_emberRuntime.default.ENV._ENABLE_LEGACY_VIEW_SUPPORT) { _emberRuntime.default.Select = _emberViewsViewsSelect.Select; } _emberRuntime.default.SelectOption = _emberViewsViewsSelect.SelectOption; _emberRuntime.default.SelectOptgroup = _emberViewsViewsSelect.SelectOptgroup; _emberRuntime.default.TextSupport = _emberViewsMixinsText_support.default; _emberRuntime.default.ComponentLookup = _emberViewsComponent_lookup.default; _emberRuntime.default.Component = _emberViewsViewsComponent.default; _emberRuntime.default.EventDispatcher = _emberViewsSystemEvent_dispatcher.default; // Deprecated: if (_emberRuntime.default.ENV._ENABLE_LEGACY_VIEW_SUPPORT) { _emberRuntime.default._Metamorph = _emberViewsCompatMetamorph_view._Metamorph; _emberRuntime.default._MetamorphView = _emberViewsCompatMetamorph_view.default; _emberRuntime.default._LegacyEachView = _emberViewsViewsLegacy_each_view.default; } // END EXPORTS exports.default = _emberRuntime.default; }); // for the side effect of extending Ember.run.queues enifed('ember', ['exports', 'ember-metal', 'ember-runtime', 'ember-views', 'ember-routing', 'ember-application', 'ember-extension-support', 'ember-htmlbars', 'ember-routing-htmlbars', 'ember-routing-views', 'ember-metal/core', 'ember-runtime/system/lazy_load'], function (exports, _emberMetal, _emberRuntime, _emberViews, _emberRouting, _emberApplication, _emberExtensionSupport, _emberHtmlbars, _emberRoutingHtmlbars, _emberRoutingViews, _emberMetalCore, _emberRuntimeSystemLazy_load) { // require the main entry points for each of these packages // this is so that the global exports occur properly 'use strict'; if (_emberMetalCore.default.__loader.registry['ember-template-compiler']) { requireModule('ember-template-compiler'); } // do this to ensure that Ember.Test is defined properly on the global // if it is present. if (_emberMetalCore.default.__loader.registry['ember-testing']) { requireModule('ember-testing'); } _emberRuntimeSystemLazy_load.runLoadHooks('Ember'); /** @module ember */ }); enifed("htmlbars-runtime/expression-visitor", ["exports", "../htmlbars-util/object-utils", "../htmlbars-util/morph-utils"], function (exports, _htmlbarsUtilObjectUtils, _htmlbarsUtilMorphUtils) { "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 # 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 */ var base = { acceptExpression: function (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; return ret; } switch (node[0]) { // can be used by manualElement case 'value': ret.value = node[1];break; case 'get': ret.value = this.get(node, env, scope);break; case 'subexpr': ret.value = this.subexpr(node, env, scope);break; case 'concat': ret.value = this.concat(node, env, scope);break; } return ret; }, acceptParams: function (nodes, env, scope) { var arr = new Array(nodes.length); for (var i = 0, l = nodes.length; i < l; i++) { arr[i] = this.acceptExpression(nodes[i], env, scope).value; } return arr; }, acceptHash: function (pairs, env, scope) { var object = {}; for (var i = 0, l = pairs.length; i < l; i += 2) { object[pairs[i]] = this.acceptExpression(pairs[i + 1], env, scope).value; } return object; }, // [ 'get', path ] get: function (node, env, scope) { return env.hooks.get(env, scope, node[1]); }, // [ 'subexpr', path, params, hash ] subexpr: function (node, env, scope) { var path = node[1], params = node[2], hash = node[3]; return env.hooks.subexpr(env, scope, path, this.acceptParams(params, env, scope), this.acceptHash(hash, env, scope)); }, // [ 'concat', parts ] concat: function (node, env, scope) { return env.hooks.concat(env, this.acceptParams(node[1], env, scope)); }, linkParamsAndHash: function (env, scope, morph, path, params, hash) { if (morph.linkedParams) { params = morph.linkedParams.params; hash = morph.linkedParams.hash; } else { params = params && this.acceptParams(params, env, scope); hash = hash && this.acceptHash(hash, env, scope); } _htmlbarsUtilMorphUtils.linkParams(env, scope, morph, path, params, hash); return [params, hash]; } }; var AlwaysDirtyVisitor = _htmlbarsUtilObjectUtils.merge(Object.create(base), { // [ 'block', path, params, hash, templateId, inverseId ] block: function (node, morph, env, scope, template, visitor) { var path = node[1], params = node[2], hash = node[3], templateId = node[4], inverseId = node[5]; var paramsAndHash = this.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', path, params, hash ] inline: function (node, morph, env, scope, visitor) { var path = node[1], params = node[2], hash = node[3]; var paramsAndHash = this.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', path ] 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; 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', path, params, hash ] element: function (node, morph, env, scope, visitor) { var path = node[1], params = node[2], hash = node[3]; var paramsAndHash = this.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', name, value ] attribute: function (node, morph, env, scope) { var name = node[1], value = node[2]; var paramsAndHash = this.linkParamsAndHash(env, scope, morph, '@attribute', [value], null); morph.isDirty = morph.isSubtreeDirty = false; env.hooks.attribute(morph, env, scope, name, paramsAndHash[0][0]); }, // [ 'component', path, attrs, templateId, inverseId ] component: function (node, morph, env, scope, template, visitor) { var path = node[1], attrs = node[2], templateId = node[3], inverseId = node[4]; var paramsAndHash = this.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', template ] 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 = _htmlbarsUtilObjectUtils.merge(Object.create(base), { // [ 'block', path, params, hash, templateId, inverseId ] block: function (node, morph, env, scope, template, visitor) { dirtyCheck(env, morph, visitor, function (visitor) { AlwaysDirtyVisitor.block(node, morph, env, scope, template, visitor); }); }, // [ 'inline', path, params, hash ] inline: function (node, morph, env, scope, visitor) { dirtyCheck(env, morph, visitor, function (visitor) { AlwaysDirtyVisitor.inline(node, morph, env, scope, visitor); }); }, // [ 'content', path ] content: function (node, morph, env, scope, visitor) { dirtyCheck(env, morph, visitor, function (visitor) { AlwaysDirtyVisitor.content(node, morph, env, scope, visitor); }); }, // [ 'element', path, params, hash ] element: function (node, morph, env, scope, template, visitor) { dirtyCheck(env, morph, visitor, function (visitor) { AlwaysDirtyVisitor.element(node, morph, env, scope, template, visitor); }); }, // [ 'attribute', name, value ] attribute: function (node, morph, env, scope, template) { dirtyCheck(env, morph, null, function () { AlwaysDirtyVisitor.attribute(node, morph, env, scope, template); }); }, // [ 'component', path, attrs, templateId ] component: function (node, morph, env, scope, template, visitor) { dirtyCheck(env, morph, visitor, function (visitor) { AlwaysDirtyVisitor.component(node, morph, env, scope, template, visitor); }); }, // [ 'attributes', template ] 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.state, 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/hooks", ["exports", "./render", "../morph-range/morph-list", "../htmlbars-util/object-utils", "../htmlbars-util/morph-utils", "../htmlbars-util/template-utils"], function (exports, _render, _morphRangeMorphList, _htmlbarsUtilObjectUtils, _htmlbarsUtilMorphUtils, _htmlbarsUtilTemplateUtils) { "use strict"; exports.wrap = wrap; exports.wrapForHelper = wrapForHelper; exports.hostYieldWithShadowTemplate = hostYieldWithShadowTemplate; 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.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(); options = options || {}; options.self = self; options.blockArguments = blockArguments; return _render.default(template, env, scope, options); } }; } function wrapForHelper(template, env, scope, morph, renderState, visitor) { if (!template) { return { yieldIn: yieldInShadowTemplate(null, env, scope, morph, renderState, visitor) }; } var yieldArgs = yieldTemplate(template, env, scope, morph, renderState, visitor); return { meta: template.meta, arity: template.arity, yield: yieldArgs, yieldItem: yieldItem(template, env, scope, morph, renderState, visitor), yieldIn: yieldInShadowTemplate(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 _render.default(template, env, scope, { renderNode: morph, self: self, blockArguments: blockArguments }); }; } 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 = _render.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 yieldInShadowTemplate(template, env, parentScope, morph, renderState, visitor) { var hostYield = hostYieldWithShadowTemplate(template, env, parentScope, morph, renderState, visitor); return function (shadowTemplate, self) { hostYield(shadowTemplate, env, self, []); }; } function hostYieldWithShadowTemplate(template, env, parentScope, morph, renderState, visitor) { return function (shadowTemplate, env, self, blockArguments) { renderState.morphToClear = null; if (morph.lastYielded && isStableShadowRoot(template, shadowTemplate, morph.lastYielded)) { return morph.lastResult.revalidateWith(env, undefined, self, blockArguments, visitor); } var shadowScope = env.hooks.createFreshScope(); env.hooks.bindShadowScope(env, parentScope, shadowScope, renderState.shadowOptions); blockToYield.arity = template.arity; env.hooks.bindBlock(env, shadowScope, blockToYield); morph.lastYielded = { self: self, template: template, shadowTemplate: shadowTemplate }; // Render the shadow template with the block available _render.default(shadowTemplate.raw, env, shadowScope, { renderNode: morph, self: self, blockArguments: blockArguments }); }; function blockToYield(env, blockArguments, self, renderNode, shadowParent, visitor) { if (renderNode.lastResult) { renderNode.lastResult.revalidateWith(env, undefined, undefined, blockArguments, visitor); } else { var scope = parentScope; // Since a yielded template shares a `self` with its original context, // we only need to create a new scope if the template has block parameters if (template.arity) { scope = env.hooks.createChildScope(parentScope); } _render.default(template, env, scope, { renderNode: renderNode, self: self, blockArguments: blockArguments }); } } } function isStableShadowRoot(template, shadowTemplate, lastYielded) { return template === lastYielded.template && shadowTemplate === lastYielded.shadowTemplate; } 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, 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); 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.state); newState = morph.state = keyword.setupState(lastState, env, scope, params, hash); } if (keyword.childEnv) { // Build the child environment... env = keyword.childEnv(morph.state, 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.state, 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; }, 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'; if (scope.blocks[to]) { scope.blocks[to](env, params, hash.self, morph, scope, visitor); } return true; }, hasBlock: function (morph, env, scope, params) { var name = env.hooks.getValue(params[0]) || 'default'; return !!scope.blocks[name]; }, hasBlockParams: function (morph, env, scope, params) { var name = env.hooks.getValue(params[0]) || 'default'; return !!(scope.blocks[name] && scope.blocks[name].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, [value], {}, 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 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 = _render.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, 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 = {}; 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++; } 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; exports.default = HTMLBarsMorph; }); enifed("htmlbars-runtime/render", ["exports", "../htmlbars-util/array-utils", "../htmlbars-util/morph-utils", "./expression-visitor", "./morph", "../htmlbars-util/template-utils", "../htmlbars-util/void-tag-names"], function (exports, _htmlbarsUtilArrayUtils, _htmlbarsUtilMorphUtils, _expressionVisitor, _morph, _htmlbarsUtilTemplateUtils, _htmlbarsUtilVoidTagNames) { "use strict"; exports.default = render; 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 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; this.bindScope(); if (options.attributes !== undefined) { nodes.push({ state: {} }); this.statements.push(['attributes', attachAttributes(options.attributes)]); } 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; initializeNode(rootNode, 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) { var statements = []; for (var key in attributes) { if (typeof attributes[key] === 'string') { continue; } statements.push(["attribute", key, attributes[key]]); } 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 (!_htmlbarsUtilVoidTagNames.default[tagName]) { 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)); } 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) { _htmlbarsUtilArrayUtils.forEach(this.root.childNodes, function (node) { initializeNode(node, ownerNode); }); }; RenderResult.prototype.render = function () { this.root.lastResult = this; this.root.rendered = true; this.populateNodes(_expressionVisitor.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, _expressionVisitor.default); }; RenderResult.prototype.rerender = function (env, self, blockArguments, scope) { this.revalidateWith(env, scope, self, blockArguments, _expressionVisitor.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; case 'attributes': visitor.attributes(statement, morph, env, scope, this.fragment, 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 = _morph.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', './htmlbars-runtime/expression-visitor', 'htmlbars-runtime/hooks'], function (exports, _htmlbarsRuntimeHooks, _htmlbarsRuntimeRender, _htmlbarsUtilMorphUtils, _htmlbarsUtilTemplateUtils, _htmlbarsRuntimeExpressionVisitor, _htmlbarsRuntimeHooks2) { 'use strict'; var internal = { blockFor: _htmlbarsUtilTemplateUtils.blockFor, manualElement: _htmlbarsRuntimeRender.manualElement, hostBlock: _htmlbarsRuntimeHooks2.hostBlock, continueBlock: _htmlbarsRuntimeHooks2.continueBlock, hostYieldWithShadowTemplate: _htmlbarsRuntimeHooks2.hostYieldWithShadowTemplate, visitChildren: _htmlbarsUtilMorphUtils.visitChildren, validateChildMorphs: _htmlbarsRuntimeExpressionVisitor.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) { 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', './handlebars/safe-string'], function (exports, _handlebarsSafeString) { 'use strict'; exports.default = _handlebarsSafeString.default; }); enifed("htmlbars-util/template-utils", ["exports", "../htmlbars-util/morph-utils"], function (exports, _htmlbarsUtilMorphUtils) { "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 blockFor(render, template, blockOptions) { var block = function (env, blockArguments, self, renderNode, parentScope, visitor) { if (renderNode.lastResult) { renderNode.lastResult.revalidateWith(env, undefined, self, blockArguments, visitor); } else { var options = { renderState: new RenderState(renderNode) }; var scope = blockOptions.scope; var shadowScope = scope ? env.hooks.createChildScope(scope) : env.hooks.createFreshScope(); var attributes = blockOptions.attributes; env.hooks.bindShadowScope(env, parentScope, shadowScope, blockOptions.options); if (self !== undefined) { env.hooks.bindSelf(env, shadowScope, self); } else if (blockOptions.self !== undefined) { env.hooks.bindSelf(env, shadowScope, blockOptions.self); } bindBlocks(env, shadowScope, blockOptions.yieldTo); renderAndCleanup(renderNode, env, options, null, function () { options.renderState.morphToClear = null; render(template, env, shadowScope, { renderNode: renderNode, blockArguments: blockArguments, attributes: attributes }); }); } }; block.arity = template.arity; return block; } function bindBlocks(env, shadowScope, blocks) { if (!blocks) { return; } if (typeof blocks === 'function') { 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)) { delete morphMap[item.key]; 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; delete morph.morphMap[item.key]; 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", "./array-utils"], function (exports, _arrayUtils) { "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 = {}; _arrayUtils.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; }); (function() { })(); /*! * @overview Ember - JavaScript Application Framework * @copyright Copyright 2011-2015 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.0.3 */ var enifed, requireModule, eriuqer, 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 = eriuqer = requireModule = function(name) { return internalRequire(name, null); } function internalRequire(name, referrerName) { var exports = seen[name]; if (exports !== undefined) { return exports; } exports = seen[name] = {}; if (!registry[name]) { if (referrerName) { throw new Error('Could not find module ' + name + ' required by: ' + referrerName); } else { throw new Error('Could not find module ' + name); } } var mod = registry[name]; var deps = mod.deps; var callback = mod.callback; var reified = []; var length = deps.length; for (var i=0; i " + 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', '../handler-info', 'router/utils', 'rsvp/promise'], function (exports, _handlerInfo, _routerUtils, _rsvpPromise) { 'use strict'; var ResolvedHandlerInfo = _routerUtils.subclass(_handlerInfo.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', '../handler-info', 'router/utils', 'rsvp/promise'], function (exports, _handlerInfo, _routerUtils, _rsvpPromise) { 'use strict'; var UnresolvedHandlerInfoByObject = _routerUtils.subclass(_handlerInfo.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', '../handler-info', 'router/utils'], function (exports, _handlerInfo, _routerUtils) { 'use strict'; // Generated by URL transitions and non-dynamic route segments in named Transitions. var UnresolvedHandlerInfoByParam = _routerUtils.subclass(_handlerInfo.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', './utils', 'rsvp/promise'], function (exports, _utils, _rsvpPromise) { 'use strict'; function HandlerInfo(_props) { var props = _props || {}; _utils.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 _utils.promiseLabel("'" + this.name + "' " + label); }, getUnresolved: function () { return this; }, serialize: function () { return this.params || {}; }, resolve: function (shouldContinue, payload) { var checkForAbort = _utils.bind(this, this.checkForAbort, shouldContinue), beforeModel = _utils.bind(this, this.runBeforeModelHook, payload), model = _utils.bind(this, this.getModel, payload), afterModel = _utils.bind(this, this.runAfterModelHook, payload), becomeResolved = _utils.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 = _utils.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', './utils', './transition-state', './transition', './transition-intent/named-transition-intent', './transition-intent/url-transition-intent', './handler-info'], function (exports, _routeRecognizer, _rsvpPromise, _utils, _transitionState, _transition, _transitionIntentNamedTransitionIntent, _transitionIntentUrlTransitionIntent, _handlerInfo) { '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 = _utils.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 _transition.Transition(this); } if (isIntermediate) { setupContexts(this, newState); return; } // Create a new transition to the destination route. newTransition = new _transition.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, _utils.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 _transition.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, _utils.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 _transition.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) { _utils.forEach(this.state.handlerInfos.slice().reverse(), function (handlerInfo) { var handler = handlerInfo.handler; _utils.callHook(handler, 'exit'); }); } this.state = new _transitionState.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 = _utils.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 || {}; } _utils.log(this, "Starting a refresh transition"); var intent = new _transitionIntentNamedTransitionIntent.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 = _utils.extractQueryParams(_utils.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 _transitionIntentNamedTransitionIntent.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(); _utils.merge(params, handlerParams); } params.queryParams = queryParams; return this.recognizer.generate(handlerName, params); }, applyIntent: function (handlerName, contexts) { var intent = new _transitionIntentNamedTransitionIntent.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 _transitionState.default(); testState.handlerInfos = targetHandlerInfos.slice(0, index + 1); recogHandlers = recogHandlers.slice(0, index + 1); var intent = new _transitionIntentNamedTransitionIntent.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 = {}; _utils.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 && !_utils.getChangelist(activeQPsOnNewHandler, queryParams); }, isActive: function (handlerName) { var partitionedArgs = _utils.extractQueryParams(_utils.slice.call(arguments, 1)); return this.isActiveIntent(handlerName, partitionedArgs[0], partitionedArgs[1]); }, trigger: function (name) { var args = _utils.slice.call(arguments); _utils.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; _utils.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; _utils.callHook(handler, 'reset', true, transition); _utils.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; _utils.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) { _utils.callHook(handler, 'enter', transition); } if (transition && transition.isAborted) { throw new _transition.TransitionAborted(); } handler.context = context; _utils.callHook(handler, 'contextDidChange'); _utils.callHook(handler, 'setup', context, transition); if (transition && transition.isAborted) { throw new _transition.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]; _utils.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 { _utils.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(_transition.logAbort(transition)); } updateURL(transition, newState, transition.intent.url); transition.isActive = false; router.activeTransition = null; _utils.trigger(router, router.currentHandlerInfos, true, ['didTransition']); if (router.didTransition) { router.didTransition(router.currentHandlerInfos); } _utils.log(router, transition.sequence, "TRANSITION COMPLETE."); // Resolve with the final handler. return handlerInfos[handlerInfos.length - 1].handler; } catch (e) { if (!(e instanceof _transition.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) { _utils.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 _transitionIntentNamedTransitionIntent.default({ name: handlerInfos[handlerInfos.length - 1].name, contexts: [], queryParams: queryParams }); } else if (name.charAt(0) === '/') { _utils.log(router, "Attempting URL transition to " + name); intent = new _transitionIntentUrlTransitionIntent.default({ url: name }); } else { _utils.log(router, "Attempting transition to " + name); intent = new _transitionIntentNamedTransitionIntent.default({ name: args[0], contexts: _utils.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 = []; _utils.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; }; } _utils.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', '../transition-intent', '../transition-state', '../handler-info/factory', '../utils'], function (exports, _transitionIntent, _transitionState, _handlerInfoFactory, _utils) { 'use strict'; exports.default = _utils.subclass(_transitionIntent.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 = _utils.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 _transitionState.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); } _utils.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 (_utils.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 _handlerInfoFactory.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 (_utils.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 _handlerInfoFactory.default('param', { name: name, handler: handler, params: params }); } }); }); enifed('router/transition-intent/url-transition-intent', ['exports', '../transition-intent', '../transition-state', '../handler-info/factory', '../utils', './../unrecognized-url-error'], function (exports, _transitionIntent, _transitionState, _handlerInfoFactory, _utils, _unrecognizedUrlError) { 'use strict'; exports.default = _utils.subclass(_transitionIntent.default, { url: null, initialize: function (props) { this.url = props.url; }, applyToState: function (oldState, recognizer, getHandler) { var newState = new _transitionState.default(); var results = recognizer.recognize(this.url), queryParams = {}, i, len; if (!results) { throw new _unrecognizedUrlError.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 _unrecognizedUrlError.default(this.url); } var newHandlerInfo = _handlerInfoFactory.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; } } _utils.merge(newState.queryParams, results.queryParams); return newState; } }); }); enifed('router/transition-intent', ['exports', './utils'], function (exports, _utils) { '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', './handler-info', './utils', 'rsvp/promise'], function (exports, _handlerInfo, _utils, _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 = ''; _utils.forEach(this.handlerInfos, function (handlerInfo) { if (targetName !== '') { targetName += '.'; } targetName += handlerInfo.name; }); return _utils.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; _utils.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; _utils.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', './handler-info', './utils'], function (exports, _rsvpPromise, _handlerInfo, _utils) { '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); } }, _utils.promiseLabel('Handle Abort')); } else { this.promise = _rsvpPromise.default.resolve(this.state); this.params = {}; } function checkForAbort() { if (transition.isAborted) { return _rsvpPromise.default.reject(undefined, _utils.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; } _utils.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 = _utils.slice.call(arguments); if (typeof ignoreFailure === 'boolean') { args.shift(); } else { // Throw errors on unhandled trigger events by default ignoreFailure = false; } _utils.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) { _utils.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) { _utils.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", "./utils"], function (exports, _utils) { "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 = _utils.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', './utils', './instrument', './config'], function (exports, _utils, _instrument, _config) { '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) { _config.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 (_utils.isFunction(then)) { handleForeignThenable(promise, maybeThenable, then); } else { fulfill(promise, maybeThenable); } } } function resolve(promise, value) { if (promise === value) { fulfill(promise, value); } else if (_utils.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 (_config.config.instrument) { _instrument.default('fulfilled', promise); } } else { _config.config.async(publish, promise); } } function reject(promise, reason) { if (promise._state !== PENDING) { return; } promise._state = REJECTED; promise._result = reason; _config.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) { _config.config.async(publish, parent); } } function publish(promise) { var subscribers = promise._subscribers; var settled = promise._state; if (_config.config.instrument) { _instrument.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 = _utils.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', './enumerator', './promise', './utils'], function (exports, _enumerator, _promise, _utils) { 'use strict'; exports.default = allSettled; function AllSettled(Constructor, entries, label) { this._superConstructor(Constructor, entries, false, /* don't abort on reject */label); } AllSettled.prototype = _utils.o_create(_enumerator.default.prototype); AllSettled.prototype._superConstructor = _enumerator.default; AllSettled.prototype._makeResult = _enumerator.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(_promise.default, entries, label).promise; } }); enifed("rsvp/all", ["exports", "./promise"], function (exports, _promise) { "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 _promise.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 = eriuqer; 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 eriuqer === 'function') { scheduleFlush = attemptVertex(); } else { scheduleFlush = useSetTimeout(); } }); enifed('rsvp/config', ['exports', './events'], function (exports, _events) { 'use strict'; var config = { instrument: false }; _events.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', './promise'], function (exports, _promise) { '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 _promise.default(function (resolve, reject) { deferred['resolve'] = resolve; deferred['reject'] = reject; }, label); return deferred; } }); enifed('rsvp/enumerator', ['exports', './utils', './-internal'], function (exports, _utils, _internal) { 'use strict'; exports.makeSettledResult = makeSettledResult; function makeSettledResult(state, position, value) { if (state === _internal.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(_internal.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) { _internal.fulfill(enumerator.promise, enumerator._result); } else { enumerator.length = enumerator.length || 0; enumerator._enumerate(); if (enumerator._remaining === 0) { _internal.fulfill(enumerator.promise, enumerator._result); } } } else { _internal.reject(enumerator.promise, enumerator._validationError()); } } exports.default = Enumerator; Enumerator.prototype._validateInput = function (input) { return _utils.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 === _internal.PENDING && i < length; i++) { enumerator._eachEntry(input[i], i); } }; Enumerator.prototype._eachEntry = function (entry, i) { var enumerator = this; var c = enumerator._instanceConstructor; if (_utils.isMaybeThenable(entry)) { if (entry.constructor === c && entry._state !== _internal.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(_internal.FULFILLED, i, entry); } }; Enumerator.prototype._settledAt = function (state, i, value) { var enumerator = this; var promise = enumerator.promise; if (promise._state === _internal.PENDING) { enumerator._remaining--; if (enumerator._abortOnReject && state === _internal.REJECTED) { _internal.reject(promise, value); } else { enumerator._result[i] = enumerator._makeResult(state, i, value); } } if (enumerator._remaining === 0) { _internal.fulfill(promise, enumerator._result); } }; Enumerator.prototype._makeResult = function (state, i, value) { return value; }; Enumerator.prototype._willSettleAt = function (promise, i) { var enumerator = this; _internal.subscribe(promise, undefined, function (value) { enumerator._settledAt(_internal.FULFILLED, i, value); }, function (reason) { enumerator._settledAt(_internal.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', './promise', './utils'], function (exports, _promise, _utils) { '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 _promise.default.all(promises, label).then(function (values) { if (!_utils.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 _promise.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', './promise', './enumerator', './promise-hash', './utils'], function (exports, _promise, _enumerator, _promiseHash, _utils) { 'use strict'; exports.default = hashSettled; function HashSettled(Constructor, object, label) { this._superConstructor(Constructor, object, false, label); } HashSettled.prototype = _utils.o_create(_promiseHash.default.prototype); HashSettled.prototype._superConstructor = _enumerator.default; HashSettled.prototype._makeResult = _enumerator.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(_promise.default, object, label).promise; } }); enifed('rsvp/hash', ['exports', './promise', './promise-hash'], function (exports, _promise, _promiseHash) { '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 _promiseHash.default(_promise.default, object, label).promise; } }); enifed('rsvp/instrument', ['exports', './config', './utils'], function (exports, _config, _utils) { '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; } _config.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: _utils.now(), error: _config.config["instrument-with-stack"] ? new Error(promise._label) : null } })) { scheduleFlush(); } } }); enifed('rsvp/map', ['exports', './promise', './utils'], function (exports, _promise, _utils) { '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 _promise.default.all(promises, label).then(function (values) { if (!_utils.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 _promise.default.all(results, label); }); } }); enifed('rsvp/node', ['exports', './promise', './-internal', './utils'], function (exports, _promise, _internal, _utils) { 'use strict'; exports.default = denodeify; 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 _promise.default(_internal.noop); _internal.reject(p, GET_THEN_ERROR.value); return p; } else if (promiseInput && promiseInput !== true) { arg = wrapThenable(promiseInput, arg); } } args[i] = arg; } var promise = new _promise.default(_internal.noop); args[l] = function (err, val) { if (err) _internal.reject(promise, err);else if (options === undefined) _internal.resolve(promise, val);else if (options === true) _internal.resolve(promise, arrayResult(arguments));else if (_utils.isArray(options)) _internal.resolve(promise, makeObject(arguments, options));else _internal.resolve(promise, val); }; if (promiseInput) { return handlePromiseInput(promise, args, nodeFunc, self); } else { return handleValueInput(promise, args, nodeFunc, self); } }; fn.__proto__ = nodeFunc; return fn; } function handleValueInput(promise, args, nodeFunc, self) { var result = tryApply(nodeFunc, self, args); if (result === ERROR) { _internal.reject(promise, result.value); } return promise; } function handlePromiseInput(promise, args, nodeFunc, self) { return _promise.default.all(args).then(function (args) { var result = tryApply(nodeFunc, self, args); if (result === ERROR) { _internal.reject(promise, result.value); } return promise; }); } function needsPromiseInput(arg) { if (arg && typeof arg === 'object') { if (arg.constructor === _promise.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', '../enumerator'], function (exports, _enumerator) { '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 _enumerator.default(this, entries, true, /* abort on reject */label).promise; } }); enifed('rsvp/promise/race', ['exports', '../utils', '../-internal'], function (exports, _utils, _internal) { '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(_internal.noop, label); if (!_utils.isArray(entries)) { _internal.reject(promise, new TypeError('You must pass an array to race.')); return promise; } var length = entries.length; function onFulfillment(value) { _internal.resolve(promise, value); } function onRejection(reason) { _internal.reject(promise, reason); } for (var i = 0; promise._state === _internal.PENDING && i < length; i++) { _internal.subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection); } return promise; } }); enifed('rsvp/promise/reject', ['exports', '../-internal'], function (exports, _internal) { '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(_internal.noop, label); _internal.reject(promise, reason); return promise; } }); enifed('rsvp/promise/resolve', ['exports', '../-internal'], function (exports, _internal) { '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(_internal.noop, label); _internal.resolve(promise, object); return promise; } }); enifed('rsvp/promise-hash', ['exports', './enumerator', './-internal', './utils'], function (exports, _enumerator, _internal, _utils) { 'use strict'; function PromiseHash(Constructor, object, label) { this._superConstructor(Constructor, object, true, label); } exports.default = PromiseHash; PromiseHash.prototype = _utils.o_create(_enumerator.default.prototype); PromiseHash.prototype._superConstructor = _enumerator.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 === _internal.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 === _internal.PENDING && i < length; i++) { result = results[i]; enumerator._eachEntry(result.entry, result.position); } }; }); enifed('rsvp/promise', ['exports', './config', './instrument', './utils', './-internal', './promise/all', './promise/race', './promise/resolve', './promise/reject'], function (exports, _config, _instrument, _utils, _internal, _promiseAll, _promiseRace, _promiseResolve, _promiseReject) { 'use strict'; exports.default = Promise; var guidKey = 'rsvp_' + _utils.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 (_config.config.instrument) { _instrument.default('created', promise); } if (_internal.noop !== resolver) { if (!_utils.isFunction(resolver)) { needsResolver(); } if (!(promise instanceof Promise)) { needsNew(); } _internal.initializePromise(promise, resolver); } } Promise.cast = _promiseResolve.default; // deprecated Promise.all = _promiseAll.default; Promise.race = _promiseRace.default; Promise.resolve = _promiseResolve.default; Promise.reject = _promiseReject.default; Promise.prototype = { constructor: Promise, _guidKey: guidKey, _onError: function (reason) { var promise = this; _config.config.after(function () { if (promise._onError) { _config.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 === _internal.FULFILLED && !onFulfillment || state === _internal.REJECTED && !onRejection) { if (_config.config.instrument) { _instrument.default('chained', parent, parent); } return parent; } parent._onError = null; var child = new parent.constructor(_internal.noop, label); var result = parent._result; if (_config.config.instrument) { _instrument.default('chained', parent, child); } if (state) { var callback = arguments[state - 1]; _config.config.async(function () { _internal.invokeCallback(state, child, callback, result); }); } else { _internal.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', './promise'], function (exports, _promise) { '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 _promise.default.race(array, label); } }); enifed('rsvp/reject', ['exports', './promise'], function (exports, _promise) { '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 _promise.default.reject(reason, label); } }); enifed('rsvp/resolve', ['exports', './promise'], function (exports, _promise) { '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 _promise.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 enifed === 'function' && enifed['amd']) { enifed(function () { return RSVP; }); } else if (typeof module !== 'undefined' && module['exports']) { module['exports'] = RSVP; } else if (typeof _rsvpPlatform.default !== 'undefined') { _rsvpPlatform.default['RSVP'] = RSVP; } });