;(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.4.0-beta.2
*/
var enifed, requireModule, require, requirejs, Ember;
var mainContext = this;
(function() {
var isNode = typeof window === 'undefined' &&
typeof process !== 'undefined' && {}.toString.call(process) === '[object process]';
if (!isNode) {
Ember = this.Ember = this.Ember || {};
}
if (typeof Ember === 'undefined') { Ember = {}; };
if (typeof Ember.__loader === 'undefined') {
var registry = {};
var seen = {};
enifed = function(name, deps, callback) {
var value = { };
if (!callback) {
value.deps = [];
value.callback = deps;
} else {
value.deps = deps;
value.callback = callback;
}
registry[name] = value;
};
requirejs = require = requireModule = function(name) {
return internalRequire(name, null);
}
// setup `require` module
require['default'] = require;
require.has = function registryHas(moduleName) {
return !!registry[moduleName] || !!registry[moduleName + '/index'];
};
function missingModule(name, referrerName) {
if (referrerName) {
throw new Error('Could not find module ' + name + ' required by: ' + referrerName);
} else {
throw new Error('Could not find module ' + name);
}
}
function internalRequire(_name, referrerName) {
var name = _name;
var mod = registry[name];
if (!mod) {
name = name + '/index';
mod = registry[name];
}
var exports = seen[name];
if (exports !== undefined) {
return exports;
}
exports = seen[name] = {};
if (!mod) {
missingModule(_name, referrerName);
}
var deps = mod.deps;
var callback = mod.callback;
var length = deps.length;
var reified = new Array(length);;
for (var i = 0; i < length; i++) {
if (deps[i] === 'exports') {
reified[i] = exports;
} else if (deps[i] === 'require') {
reified[i] = require;
} else {
reified[i] = internalRequire(deps[i], name);
}
}
callback.apply(this, reified);
return exports;
};
requirejs._eak_seen = registry;
Ember.__loader = {
define: enifed,
require: require,
registry: registry
};
} else {
enifed = Ember.__loader.define;
requirejs = require = requireModule = Ember.__loader.require;
}
})();
enifed("backburner/binary-search", ["exports"], function (exports) {
"use strict";
exports.default = binarySearch;
function binarySearch(time, timers) {
var start = 0;
var end = timers.length - 2;
var middle, l;
while (start < end) {
// since timers is an array of pairs 'l' will always
// be an integer
l = (end - start) / 2;
// compensate for the index in case even number
// of pairs inside timers
middle = start + l - l % 2;
if (time >= timers[middle]) {
start = middle + 2;
} else {
end = middle;
}
}
return time >= timers[start] ? start + 2 : start;
}
});
enifed('backburner/deferred-action-queues', ['exports', 'backburner/utils', 'backburner/queue'], function (exports, _backburnerUtils, _backburnerQueue) {
'use strict';
exports.default = DeferredActionQueues;
function DeferredActionQueues(queueNames, options) {
var queues = this.queues = {};
this.queueNames = queueNames = queueNames || [];
this.options = options;
_backburnerUtils.each(queueNames, function (queueName) {
queues[queueName] = new _backburnerQueue.default(queueName, options[queueName], options);
});
}
function noSuchQueue(name) {
throw new Error('You attempted to schedule an action in a queue (' + name + ') that doesn\'t exist');
}
function noSuchMethod(name) {
throw new Error('You attempted to schedule an action in a queue (' + name + ') for a method that doesn\'t exist');
}
DeferredActionQueues.prototype = {
schedule: function (name, target, method, args, onceFlag, stack) {
var queues = this.queues;
var queue = queues[name];
if (!queue) {
noSuchQueue(name);
}
if (!method) {
noSuchMethod(name);
}
if (onceFlag) {
return queue.pushUnique(target, method, args, stack);
} else {
return queue.push(target, method, args, stack);
}
},
flush: function () {
var queues = this.queues;
var queueNames = this.queueNames;
var queueName, queue;
var queueNameIndex = 0;
var numberOfQueues = queueNames.length;
while (queueNameIndex < numberOfQueues) {
queueName = queueNames[queueNameIndex];
queue = queues[queueName];
var numberOfQueueItems = queue._queue.length;
if (numberOfQueueItems === 0) {
queueNameIndex++;
} else {
queue.flush(false /* async */);
queueNameIndex = 0;
}
}
}
};
});
enifed('backburner/platform', ['exports'], function (exports) {
'use strict';
var 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', 'backburner/utils'], function (exports, _backburnerUtils) {
'use strict';
exports.default = Queue;
function Queue(name, options, globalOptions) {
this.name = name;
this.globalOptions = globalOptions || {};
this.options = options;
this._queue = [];
this.targetQueues = {};
this._queueBeingFlushed = undefined;
}
Queue.prototype = {
push: function (target, method, args, stack) {
var queue = this._queue;
queue.push(target, method, args, stack);
return {
queue: this,
target: target,
method: method
};
},
pushUniqueWithoutGuid: function (target, method, args, stack) {
var queue = this._queue;
for (var i = 0, l = queue.length; i < l; i += 4) {
var currentTarget = queue[i];
var currentMethod = queue[i + 1];
if (currentTarget === target && currentMethod === method) {
queue[i + 2] = args; // replace args
queue[i + 3] = stack; // replace stack
return;
}
}
queue.push(target, method, args, stack);
},
targetQueue: function (targetQueue, target, method, args, stack) {
var queue = this._queue;
for (var i = 0, l = targetQueue.length; i < l; i += 2) {
var currentMethod = targetQueue[i];
var currentIndex = targetQueue[i + 1];
if (currentMethod === method) {
queue[currentIndex + 2] = args; // replace args
queue[currentIndex + 3] = stack; // replace stack
return;
}
}
targetQueue.push(method, queue.push(target, method, args, stack) - 4);
},
pushUniqueWithGuid: function (guid, target, method, args, stack) {
var hasLocalQueue = this.targetQueues[guid];
if (hasLocalQueue) {
this.targetQueue(hasLocalQueue, target, method, args, stack);
} else {
this.targetQueues[guid] = [method, this._queue.push(target, method, args, stack) - 4];
}
return {
queue: this,
target: target,
method: method
};
},
pushUnique: function (target, method, args, stack) {
var KEY = this.globalOptions.GUID_KEY;
if (target && KEY) {
var guid = target[KEY];
if (guid) {
return this.pushUniqueWithGuid(guid, target, method, args, stack);
}
}
this.pushUniqueWithoutGuid(target, method, args, stack);
return {
queue: this,
target: target,
method: method
};
},
invoke: function (target, method, args, _, _errorRecordedForStack) {
if (args && args.length > 0) {
method.apply(target, args);
} else {
method.call(target);
}
},
invokeWithOnError: function (target, method, args, onError, errorRecordedForStack) {
try {
if (args && args.length > 0) {
method.apply(target, args);
} else {
method.call(target);
}
} catch (error) {
onError(error, errorRecordedForStack);
}
},
flush: function (sync) {
var queue = this._queue;
var length = queue.length;
if (length === 0) {
return;
}
var globalOptions = this.globalOptions;
var options = this.options;
var before = options && options.before;
var after = options && options.after;
var onError = globalOptions.onError || globalOptions.onErrorTarget && globalOptions.onErrorTarget[globalOptions.onErrorMethod];
var target, method, args, errorRecordedForStack;
var invoke = onError ? this.invokeWithOnError : this.invoke;
this.targetQueues = Object.create(null);
var queueItems = this._queueBeingFlushed = this._queue.slice();
this._queue = [];
if (before) {
before();
}
for (var i = 0; i < length; i += 4) {
target = queueItems[i];
method = queueItems[i + 1];
args = queueItems[i + 2];
errorRecordedForStack = queueItems[i + 3]; // Debugging assistance
if (_backburnerUtils.isString(method)) {
method = target[method];
}
// method could have been nullified / canceled during flush
if (method) {
//
// ** Attention intrepid developer **
//
// To find out the stack of this task when it was scheduled onto
// the run loop, add the following to your app.js:
//
// Ember.run.backburner.DEBUG = true; // NOTE: This slows your app, don't leave it on in production.
//
// Once that is in place, when you are at a breakpoint and navigate
// here in the stack explorer, you can look at `errorRecordedForStack.stack`,
// which will be the captured stack when this job was scheduled.
//
invoke(target, method, args, onError, errorRecordedForStack);
}
}
if (after) {
after();
}
this._queueBeingFlushed = undefined;
if (sync !== false && this._queue.length > 0) {
// check if new items have been added
this.flush(true);
}
},
cancel: function (actionToCancel) {
var queue = this._queue,
currentTarget,
currentMethod,
i,
l;
var target = actionToCancel.target;
var method = actionToCancel.method;
var GUID_KEY = this.globalOptions.GUID_KEY;
if (GUID_KEY && this.targetQueues && target) {
var targetQueue = this.targetQueues[target[GUID_KEY]];
if (targetQueue) {
for (i = 0, l = targetQueue.length; i < l; i++) {
if (targetQueue[i] === method) {
targetQueue.splice(i, 1);
}
}
}
}
for (i = 0, l = queue.length; i < l; i += 4) {
currentTarget = queue[i];
currentMethod = queue[i + 1];
if (currentTarget === target && currentMethod === method) {
queue.splice(i, 4);
return true;
}
}
// if not found in current queue
// could be in the queue that is being flushed
queue = this._queueBeingFlushed;
if (!queue) {
return;
}
for (i = 0, l = queue.length; i < l; i += 4) {
currentTarget = queue[i];
currentMethod = queue[i + 1];
if (currentTarget === target && currentMethod === method) {
// don't mess with array during flush
// just nullify the method
queue[i + 1] = null;
return true;
}
}
}
};
});
enifed('backburner/utils', ['exports'], function (exports) {
'use strict';
exports.each = each;
exports.isString = isString;
exports.isFunction = isFunction;
exports.isNumber = isNumber;
exports.isCoercableNumber = isCoercableNumber;
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._eventCallbacks = {
end: [],
begin: []
};
this._timerTimeoutId = undefined;
this._timers = [];
var _this = this;
this._boundRunExpiredTimers = function () {
_this._runExpiredTimers();
};
}
Backburner.prototype = {
begin: function () {
var options = this.options;
var onBegin = options && options.onBegin;
var previousInstance = this.currentInstance;
if (previousInstance) {
this.instanceStack.push(previousInstance);
}
this.currentInstance = new _backburnerDeferredActionQueues.default(this.queueNames, options);
this._trigger('begin', this.currentInstance, previousInstance);
if (onBegin) {
onBegin(this.currentInstance, previousInstance);
}
},
end: function () {
var options = this.options;
var onEnd = options && options.onEnd;
var currentInstance = this.currentInstance;
var nextInstance = null;
// Prevent double-finally bug in Safari 6.0.2 and iOS 6
// This bug appears to be resolved in Safari 6.0.5 and iOS 7
var finallyAlreadyCalled = false;
try {
currentInstance.flush();
} finally {
if (!finallyAlreadyCalled) {
finallyAlreadyCalled = true;
this.currentInstance = null;
if (this.instanceStack.length) {
nextInstance = this.instanceStack.pop();
this.currentInstance = nextInstance;
}
this._trigger('end', currentInstance, nextInstance);
if (onEnd) {
onEnd(currentInstance, nextInstance);
}
}
}
},
/**
Trigger an event. Supports up to two arguments. Designed around
triggering transition events from one run loop instance to the
next, which requires an argument for the first instance and then
an argument for the next instance.
@private
@method _trigger
@param {String} eventName
@param {any} arg1
@param {any} arg2
*/
_trigger: function (eventName, arg1, arg2) {
var callbacks = this._eventCallbacks[eventName];
if (callbacks) {
for (var i = 0; i < callbacks.length; i++) {
callbacks[i](arg1, arg2);
}
}
},
on: function (eventName, callback) {
if (typeof callback !== 'function') {
throw new TypeError('Callback must be a function');
}
var callbacks = this._eventCallbacks[eventName];
if (callbacks) {
callbacks.push(callback);
} else {
throw new TypeError('Cannot on() event "' + eventName + '" because it does not exist');
}
},
off: function (eventName, callback) {
if (eventName) {
var callbacks = this._eventCallbacks[eventName];
var callbackFound = false;
if (!callbacks) return;
if (callback) {
for (var i = 0; i < callbacks.length; i++) {
if (callbacks[i] === callback) {
callbackFound = true;
callbacks.splice(i, 1);
i--;
}
}
}
if (!callbackFound) {
throw new TypeError('Cannot off() callback that does not exist');
}
} else {
throw new TypeError('Cannot off() event "' + eventName + '" because it does not exist');
}
},
run: function () /* target, method, args */{
var length = arguments.length;
var method, target, args;
if (length === 1) {
method = arguments[0];
target = null;
} else {
target = arguments[0];
method = arguments[1];
}
if (_backburnerUtils.isString(method)) {
method = target[method];
}
if (length > 2) {
args = new Array(length - 2);
for (var i = 0, l = length - 2; i < l; i++) {
args[i] = arguments[i + 2];
}
} else {
args = [];
}
var onError = getOnError(this.options);
this.begin();
// guard against Safari 6's double-finally bug
var didFinally = false;
if (onError) {
try {
return method.apply(target, args);
} catch (error) {
onError(error);
} finally {
if (!didFinally) {
didFinally = true;
this.end();
}
}
} else {
try {
return method.apply(target, args);
} finally {
if (!didFinally) {
didFinally = true;
this.end();
}
}
}
},
/*
Join the passed method with an existing queue and execute immediately,
if there isn't one use `Backburner#run`.
The join method is like the run method except that it will schedule into
an existing queue if one already exists. In either case, the join method will
immediately execute the passed in function and return its result.
@method join
@param {Object} target
@param {Function} method The method to be executed
@param {any} args The method arguments
@return method result
*/
join: function () /* target, method, args */{
if (!this.currentInstance) {
return this.run.apply(this, arguments);
}
var length = arguments.length;
var method, target;
if (length === 1) {
method = arguments[0];
target = null;
} else {
target = arguments[0];
method = arguments[1];
}
if (_backburnerUtils.isString(method)) {
method = target[method];
}
if (length === 1) {
return method();
} else if (length === 2) {
return method.call(target);
} else {
var args = new Array(length - 2);
for (var i = 0, l = length - 2; i < l; i++) {
args[i] = arguments[i + 2];
}
return method.apply(target, args);
}
},
/*
Defer the passed function to run inside the specified queue.
@method defer
@param {String} queueName
@param {Object} target
@param {Function|String} method The method or method name to be executed
@param {any} args The method arguments
@return method result
*/
defer: function (queueName /* , target, method, args */) {
var length = arguments.length;
var method, target, args;
if (length === 2) {
method = arguments[1];
target = null;
} else {
target = arguments[1];
method = arguments[2];
}
if (_backburnerUtils.isString(method)) {
method = target[method];
}
var stack = this.DEBUG ? new Error() : undefined;
if (length > 3) {
args = new Array(length - 3);
for (var i = 3; i < length; i++) {
args[i - 3] = arguments[i];
}
} else {
args = undefined;
}
if (!this.currentInstance) {
createAutorun(this);
}
return this.currentInstance.schedule(queueName, target, method, args, false, stack);
},
deferOnce: function (queueName /* , target, method, args */) {
var length = arguments.length;
var method, target, args;
if (length === 2) {
method = arguments[1];
target = null;
} else {
target = arguments[1];
method = arguments[2];
}
if (_backburnerUtils.isString(method)) {
method = target[method];
}
var stack = this.DEBUG ? new Error() : undefined;
if (length > 3) {
args = new Array(length - 3);
for (var i = 3; i < length; i++) {
args[i - 3] = arguments[i];
}
} else {
args = undefined;
}
if (!this.currentInstance) {
createAutorun(this);
}
return this.currentInstance.schedule(queueName, target, method, args, true, stack);
},
setTimeout: function () {
var l = arguments.length;
var args = new Array(l);
for (var x = 0; x < l; x++) {
args[x] = arguments[x];
}
var length = args.length,
method,
wait,
target,
methodOrTarget,
methodOrWait,
methodOrArgs;
if (length === 0) {
return;
} else if (length === 1) {
method = args.shift();
wait = 0;
} else if (length === 2) {
methodOrTarget = args[0];
methodOrWait = args[1];
if (_backburnerUtils.isFunction(methodOrWait) || _backburnerUtils.isFunction(methodOrTarget[methodOrWait])) {
target = args.shift();
method = args.shift();
wait = 0;
} else if (_backburnerUtils.isCoercableNumber(methodOrWait)) {
method = args.shift();
wait = args.shift();
} else {
method = args.shift();
wait = 0;
}
} else {
var last = args[args.length - 1];
if (_backburnerUtils.isCoercableNumber(last)) {
wait = args.pop();
} else {
wait = 0;
}
methodOrTarget = args[0];
methodOrArgs = args[1];
if (_backburnerUtils.isFunction(methodOrArgs) || _backburnerUtils.isString(methodOrArgs) && methodOrTarget !== null && methodOrArgs in methodOrTarget) {
target = args.shift();
method = args.shift();
} else {
method = args.shift();
}
}
var executeAt = _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);
}
}
return this._setTimeout(fn, executeAt);
},
_setTimeout: function (fn, executeAt) {
if (this._timers.length === 0) {
this._timers.push(executeAt, fn);
this._installTimerTimeout();
return fn;
}
// find position to insert
var i = _backburnerBinarySearch.default(executeAt, this._timers);
this._timers.splice(i, 0, executeAt, fn);
// we should be the new earliest timer if i == 0
if (i === 0) {
this._reinstallTimerTimeout();
}
return fn;
},
throttle: function (target, method /* , args, wait, [immediate] */) {
var backburner = this;
var args = new Array(arguments.length);
for (var i = 0; i < arguments.length; i++) {
args[i] = arguments[i];
}
var immediate = args.pop();
var wait, throttler, index, timer;
if (_backburnerUtils.isNumber(immediate) || _backburnerUtils.isString(immediate)) {
wait = immediate;
immediate = true;
} else {
wait = args.pop();
}
wait = parseInt(wait, 10);
index = findThrottler(target, method, this._throttlers);
if (index > -1) {
return this._throttlers[index];
} // throttled
timer = _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 () {
_backburnerUtils.each(this._throttlers, clearItems);
this._throttlers = [];
_backburnerUtils.each(this._debouncees, clearItems);
this._debouncees = [];
this._clearTimerTimeout();
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) {
this._reinstallTimerTimeout();
}
return true;
}
}
} else if (Object.prototype.toString.call(timer) === '[object Array]') {
// we're cancelling a throttle or debounce
return this._cancelItem(findThrottler, this._throttlers, timer) || this._cancelItem(findDebouncee, this._debouncees, timer);
} else {
return; // timer was null or not a timer
}
},
_cancelItem: function (findMethod, array, timer) {
var item, index;
if (timer.length < 3) {
return false;
}
index = findMethod(timer[0], timer[1], array);
if (index > -1) {
item = array[index];
if (item[2] === timer[2]) {
array.splice(index, 1);
clearTimeout(timer[2]);
return true;
}
}
return false;
},
_runExpiredTimers: function () {
this._timerTimeoutId = undefined;
this.run(this, this._scheduleExpiredTimers);
},
_scheduleExpiredTimers: function () {
var n = _backburnerUtils.now();
var timers = this._timers;
var i = 0;
var l = timers.length;
for (; i < l; i += 2) {
var executeAt = timers[i];
var fn = timers[i + 1];
if (executeAt <= n) {
this.schedule(this.options.defaultQueue, null, fn);
} else {
break;
}
}
timers.splice(0, i);
this._installTimerTimeout();
},
_reinstallTimerTimeout: function () {
this._clearTimerTimeout();
this._installTimerTimeout();
},
_clearTimerTimeout: function () {
if (!this._timerTimeoutId) {
return;
}
clearTimeout(this._timerTimeoutId);
this._timerTimeoutId = undefined;
},
_installTimerTimeout: function () {
if (!this._timers.length) {
return;
}
var minExpiresAt = this._timers[0];
var n = _backburnerUtils.now();
var wait = Math.max(0, minExpiresAt - n);
this._timerTimeoutId = setTimeout(this._boundRunExpiredTimers, wait);
}
};
Backburner.prototype.schedule = Backburner.prototype.defer;
Backburner.prototype.scheduleOnce = Backburner.prototype.deferOnce;
Backburner.prototype.later = Backburner.prototype.setTimeout;
function getOnError(options) {
return options.onError || options.onErrorTarget && options.onErrorTarget[options.onErrorMethod];
}
function createAutorun(backburner) {
backburner.begin();
backburner._autorun = _backburnerPlatform.default.setTimeout(function () {
backburner._autorun = null;
backburner.end();
});
}
function findDebouncee(target, method, debouncees) {
return findItem(target, method, debouncees);
}
function findThrottler(target, method, throttlers) {
return findItem(target, method, throttlers);
}
function findItem(target, method, collection) {
var item;
var index = -1;
for (var i = 0, l = collection.length; i < l; i++) {
item = collection[i];
if (item[0] === target && item[1] === method) {
index = i;
break;
}
}
return index;
}
function clearItems(item) {
clearTimeout(item[2]);
}
});
enifed('container/container', ['exports', 'ember-metal/core', 'ember-metal/debug', 'ember-metal/dictionary', 'ember-metal/features', 'container/owner', 'ember-runtime/mixins/container_proxy', 'ember-metal/symbol'], function (exports, _emberMetalCore, _emberMetalDebug, _emberMetalDictionary, _emberMetalFeatures, _containerOwner, _emberRuntimeMixinsContainer_proxy, _emberMetalSymbol) {
'use strict';
var CONTAINER_OVERRIDE = _emberMetalSymbol.default('CONTAINER_OVERRIDE');
/**
A container used to instantiate and cache objects.
Every `Container` must be associated with a `Registry`, which is referenced
to determine the factory and options that should be used to instantiate
objects.
The public API for `Container` is still in flux and should not be considered
stable.
@private
@class Container
*/
function Container(registry, options) {
this.registry = registry;
this.owner = options && options.owner ? options.owner : null;
this.cache = _emberMetalDictionary.default(options && options.cache ? options.cache : null);
this.factoryCache = _emberMetalDictionary.default(options && options.factoryCache ? options.factoryCache : null);
this.validationCache = _emberMetalDictionary.default(options && options.validationCache ? options.validationCache : null);
this._fakeContainerToInject = _emberRuntimeMixinsContainer_proxy.buildFakeContainerWithDeprecations(this);
this[CONTAINER_OVERRIDE] = undefined;
}
Container.prototype = {
/**
@private
@property owner
@type Object
*/
owner: null,
/**
@private
@property registry
@type Registry
@since 1.11.0
*/
registry: null,
/**
@private
@property cache
@type InheritingDict
*/
cache: null,
/**
@private
@property factoryCache
@type InheritingDict
*/
factoryCache: null,
/**
@private
@property validationCache
@type InheritingDict
*/
validationCache: null,
/**
Given a fullName return a corresponding instance.
The default behaviour is for lookup to return a singleton instance.
The singleton is scoped to the container, allowing multiple containers
to all have their own locally scoped singletons.
```javascript
var registry = new Registry();
var container = registry.container();
registry.register('api:twitter', Twitter);
var twitter = container.lookup('api:twitter');
twitter instanceof Twitter; // => true
// by default the container will return singletons
var twitter2 = container.lookup('api:twitter');
twitter2 instanceof Twitter; // => true
twitter === twitter2; //=> true
```
If singletons are not wanted an optional flag can be provided at lookup.
```javascript
var registry = new Registry();
var container = registry.container();
registry.register('api:twitter', Twitter);
var twitter = container.lookup('api:twitter', { singleton: false });
var twitter2 = container.lookup('api:twitter', { singleton: false });
twitter === twitter2; //=> false
```
@private
@method lookup
@param {String} fullName
@param {Object} [options]
@param {String} [options.source] The fullname of the request source (used for local lookup)
@return {any}
*/
lookup: function (fullName, options) {
return lookup(this, this.registry.normalize(fullName), options);
},
/**
Given a fullName return the corresponding factory.
@private
@method lookupFactory
@param {String} fullName
@param {Object} [options]
@param {String} [options.source] The fullname of the request source (used for local lookup)
@return {any}
*/
lookupFactory: function (fullName, options) {
return factoryFor(this, this.registry.normalize(fullName), options);
},
/**
A depth first traversal, destroying the container, its descendant containers and all
their managed objects.
@private
@method destroy
*/
destroy: function () {
eachDestroyable(this, function (item) {
if (item.destroy) {
item.destroy();
}
});
this.isDestroyed = true;
},
/**
Clear either the entire cache or just the cache for a particular key.
@private
@method reset
@param {String} fullName optional key to reset; if missing, resets everything
*/
reset: function (fullName) {
if (arguments.length > 0) {
resetMember(this, this.registry.normalize(fullName));
} else {
resetCache(this);
}
},
/**
Returns an object that can be used to provide an owner to a
manually created instance.
@private
@method ownerInjection
@returns { Object }
*/
ownerInjection: function () {
var _ref;
return _ref = {}, _ref[_containerOwner.OWNER] = this.owner, _ref;
}
};
function isSingleton(container, fullName) {
return container.registry.getOption(fullName, 'singleton') !== false;
}
function lookup(container, _fullName, _options) {
var options = _options || {};
var fullName = _fullName;
if (container.cache[fullName] !== undefined && options.singleton !== false) {
return container.cache[fullName];
}
var value = instantiate(container, fullName);
if (value === undefined) {
return;
}
if (isSingleton(container, fullName) && options.singleton !== false) {
container.cache[fullName] = value;
}
return value;
}
function markInjectionsAsDynamic(injections) {
injections._dynamic = true;
}
function areInjectionsDynamic(injections) {
return !!injections._dynamic;
}
function buildInjections() /* container, ...injections */{
var hash = {};
if (arguments.length > 1) {
var container = arguments[0];
var injections = [];
var injection;
for (var i = 1, l = arguments.length; i < l; i++) {
if (arguments[i]) {
injections = injections.concat(arguments[i]);
}
}
container.registry.validateInjections(injections);
for (i = 0, l = injections.length; i < l; i++) {
injection = injections[i];
hash[injection.property] = lookup(container, injection.fullName);
if (!isSingleton(container, injection.fullName)) {
markInjectionsAsDynamic(hash);
}
}
}
return hash;
}
function factoryFor(container, _fullName, _options) {
var options = _options || {};
var registry = container.registry;
var fullName = _fullName;
var cache = container.factoryCache;
if (cache[fullName]) {
return cache[fullName];
}
var factory = registry.resolve(fullName);
if (factory === undefined) {
return;
}
var type = fullName.split(':')[0];
if (!factory || typeof factory.extend !== 'function' || !_emberMetalCore.default.MODEL_FACTORY_INJECTIONS && type === 'model') {
if (factory && typeof factory._onLookup === 'function') {
factory._onLookup(fullName);
}
// TODO: think about a 'safe' merge style extension
// for now just fallback to create time injection
cache[fullName] = factory;
return factory;
} else {
var injections = injectionsFor(container, fullName);
var factoryInjections = factoryInjectionsFor(container, fullName);
var cacheable = !areInjectionsDynamic(injections) && !areInjectionsDynamic(factoryInjections);
factoryInjections._toString = registry.makeToString(factory, fullName);
var injectedFactory = factory.extend(injections);
// TODO - remove all `container` injections when Ember reaches v3.0.0
injectDeprecatedContainer(injectedFactory.prototype, container);
injectedFactory.reopenClass(factoryInjections);
if (factory && typeof factory._onLookup === 'function') {
factory._onLookup(fullName);
}
if (cacheable) {
cache[fullName] = injectedFactory;
}
return injectedFactory;
}
}
function injectionsFor(container, fullName) {
var registry = container.registry;
var splitName = fullName.split(':');
var type = splitName[0];
var injections = buildInjections(container, registry.getTypeInjections(type), registry.getInjections(fullName));
injections._debugContainerKey = fullName;
_containerOwner.setOwner(injections, container.owner);
return injections;
}
function factoryInjectionsFor(container, fullName) {
var registry = container.registry;
var splitName = fullName.split(':');
var type = splitName[0];
var factoryInjections = buildInjections(container, registry.getFactoryTypeInjections(type), registry.getFactoryInjections(fullName));
factoryInjections._debugContainerKey = fullName;
return factoryInjections;
}
function instantiate(container, fullName) {
var factory = factoryFor(container, fullName);
var lazyInjections, validationCache;
if (container.registry.getOption(fullName, 'instantiate') === false) {
return factory;
}
if (factory) {
if (typeof factory.create !== 'function') {
throw new Error('Failed to create an instance of \'' + fullName + '\'. ' + 'Most likely an improperly defined class or an invalid module export.');
}
validationCache = container.validationCache;
// Ensure that all lazy injections are valid at instantiation time
if (!validationCache[fullName] && typeof factory._lazyInjections === 'function') {
lazyInjections = factory._lazyInjections();
lazyInjections = container.registry.normalizeInjectionsHash(lazyInjections);
container.registry.validateInjections(lazyInjections);
}
validationCache[fullName] = true;
var obj = undefined;
if (typeof factory.extend === 'function') {
// assume the factory was extendable and is already injected
obj = factory.create();
} else {
// assume the factory was extendable
// to create time injections
// TODO: support new'ing for instantiation and merge injections for pure JS Functions
var injections = injectionsFor(container, fullName);
// Ensure that a container is available to an object during instantiation.
// TODO - remove when Ember reaches v3.0.0
// This "fake" container will be replaced after instantiation with a
// property that raises deprecations every time it is accessed.
injections.container = container._fakeContainerToInject;
obj = factory.create(injections);
// TODO - remove when Ember reaches v3.0.0
if (!Object.isFrozen(obj) && 'container' in obj) {
injectDeprecatedContainer(obj, container);
}
}
return obj;
}
}
// TODO - remove when Ember reaches v3.0.0
function injectDeprecatedContainer(object, container) {
Object.defineProperty(object, 'container', {
configurable: true,
enumerable: false,
get: function () {
return this[CONTAINER_OVERRIDE] || container;
},
set: function (value) {
this[CONTAINER_OVERRIDE] = value;
return value;
}
});
}
function eachDestroyable(container, callback) {
var cache = container.cache;
var keys = Object.keys(cache);
var key, value;
for (var i = 0, l = keys.length; i < l; i++) {
key = keys[i];
value = cache[key];
if (container.registry.getOption(key, 'instantiate') !== false) {
callback(value);
}
}
}
function resetCache(container) {
eachDestroyable(container, function (value) {
if (value.destroy) {
value.destroy();
}
});
container.cache.dict = _emberMetalDictionary.default(null);
}
function resetMember(container, fullName) {
var member = container.cache[fullName];
delete container.factoryCache[fullName];
if (member) {
delete container.cache[fullName];
if (member.destroy) {
member.destroy();
}
}
}
exports.default = Container;
});
// if expandLocalLookup returns falsey, we do not support local lookup
// if expandLocalLookup returns falsey, we do not support local lookup
enifed('container/index', ['exports', 'ember-metal/core', 'container/registry', 'container/container', 'container/owner'], function (exports, _emberMetalCore, _containerRegistry, _containerContainer, _containerOwner) {
'use strict';
/*
Public api for the container is still in flux.
The public api, specified on the application namespace should be considered the stable api.
// @module container
@private
*/
/*
Flag to enable/disable model factory injections (disabled by default)
If model factory injections are enabled, models should not be
accessed globally (only through `container.lookupFactory('model:modelName'))`);
*/
_emberMetalCore.default.MODEL_FACTORY_INJECTIONS = false;
if (_emberMetalCore.default.ENV && typeof _emberMetalCore.default.ENV.MODEL_FACTORY_INJECTIONS !== 'undefined') {
_emberMetalCore.default.MODEL_FACTORY_INJECTIONS = !!_emberMetalCore.default.ENV.MODEL_FACTORY_INJECTIONS;
}
exports.Registry = _containerRegistry.default;
exports.Container = _containerContainer.default;
exports.getOwner = _containerOwner.getOwner;
exports.setOwner = _containerOwner.setOwner;
});
enifed('container/owner', ['exports', 'ember-metal/symbol'], function (exports, _emberMetalSymbol) {
/**
@module ember
@submodule ember-runtime
*/
'use strict';
exports.getOwner = getOwner;
exports.setOwner = setOwner;
var OWNER = _emberMetalSymbol.default('OWNER');
exports.OWNER = OWNER;
/**
Framework objects in an Ember application (components, services, routes, etc.)
are created via a factory and dependency injection system. Each of these
objects is the responsibility of an "owner", which handled its
instantiation and manages its lifetime.
`getOwner` fetches the owner object responsible for an instance. This can
be used to lookup or resolve other class instances, or register new factories
into the owner.
For example, this component dynamically looks up a service based on the
`audioType` passed as an attribute:
```
// app/components/play-audio.js
import Ember from 'ember';
// Usage:
//
// {{play-audio audioType=model.audioType audioFile=model.file}}
//
export default Ember.Component.extend({
audioService: Ember.computed('audioType', function() {
let owner = Ember.getOwner(this);
return owner.lookup(`service:${this.get('audioType')}`);
}),
click() {
let player = this.get('audioService');
player.play(this.get('audioFile'));
}
});
```
@method getOwner
@param {Object} object A object with an owner.
@return {Object} an owner object.
@for Ember
@public
*/
function getOwner(object) {
return object[OWNER];
}
/**
`setOwner` forces a new owner on a given object instance. This is primarily
useful in some testing cases.
@method setOwner
@param {Object} object A object with an owner.
@return {Object} an owner object.
@for Ember
@public
*/
function setOwner(object, owner) {
object[OWNER] = owner;
}
});
enifed('container/registry', ['exports', 'ember-metal/features', 'ember-metal/debug', 'ember-metal/dictionary', 'ember-metal/empty_object', 'ember-metal/assign', 'container/container'], function (exports, _emberMetalFeatures, _emberMetalDebug, _emberMetalDictionary, _emberMetalEmpty_object, _emberMetalAssign, _containerContainer) {
'use strict';
var VALID_FULL_NAME_REGEXP = /^[^:]+.+:[^:]+$/;
/**
A registry used to store factory and option information keyed
by type.
A `Registry` stores the factory and option information needed by a
`Container` to instantiate and cache objects.
The API for `Registry` is still in flux and should not be considered stable.
@private
@class Registry
@since 1.11.0
*/
function Registry(options) {
this.fallback = options && options.fallback ? options.fallback : null;
if (options && options.resolver) {
this.resolver = options.resolver;
if (typeof this.resolver === 'function') {
deprecateResolverFunction(this);
}
}
this.registrations = _emberMetalDictionary.default(options && options.registrations ? options.registrations : null);
this._typeInjections = _emberMetalDictionary.default(null);
this._injections = _emberMetalDictionary.default(null);
this._factoryTypeInjections = _emberMetalDictionary.default(null);
this._factoryInjections = _emberMetalDictionary.default(null);
this._localLookupCache = new _emberMetalEmpty_object.default();
this._normalizeCache = _emberMetalDictionary.default(null);
this._resolveCache = _emberMetalDictionary.default(null);
this._failCache = _emberMetalDictionary.default(null);
this._options = _emberMetalDictionary.default(null);
this._typeOptions = _emberMetalDictionary.default(null);
}
Registry.prototype = {
/**
A backup registry for resolving registrations when no matches can be found.
@private
@property fallback
@type Registry
*/
fallback: null,
/**
An object that has a `resolve` method that resolves a name.
@private
@property resolver
@type Resolver
*/
resolver: null,
/**
@private
@property registrations
@type InheritingDict
*/
registrations: null,
/**
@private
@property _typeInjections
@type InheritingDict
*/
_typeInjections: null,
/**
@private
@property _injections
@type InheritingDict
*/
_injections: null,
/**
@private
@property _factoryTypeInjections
@type InheritingDict
*/
_factoryTypeInjections: null,
/**
@private
@property _factoryInjections
@type InheritingDict
*/
_factoryInjections: null,
/**
@private
@property _normalizeCache
@type InheritingDict
*/
_normalizeCache: null,
/**
@private
@property _resolveCache
@type InheritingDict
*/
_resolveCache: null,
/**
@private
@property _options
@type InheritingDict
*/
_options: null,
/**
@private
@property _typeOptions
@type InheritingDict
*/
_typeOptions: null,
/**
Creates a container based on this registry.
@private
@method container
@param {Object} options
@return {Container} created container
*/
container: function (options) {
return new _containerContainer.default(this, options);
},
/**
Registers a factory for later injection.
Example:
```javascript
var registry = new Registry();
registry.register('model:user', Person, {singleton: false });
registry.register('fruit:favorite', Orange);
registry.register('communication:main', Email, {singleton: false});
```
@private
@method register
@param {String} fullName
@param {Function} factory
@param {Object} options
*/
register: function (fullName, factory, options) {
if (factory === undefined) {
throw new TypeError('Attempting to register an unknown factory: `' + fullName + '`');
}
var normalizedName = this.normalize(fullName);
if (this._resolveCache[normalizedName]) {
throw new Error('Cannot re-register: `' + fullName + '`, as it has already been resolved.');
}
delete this._failCache[normalizedName];
this.registrations[normalizedName] = factory;
this._options[normalizedName] = options || {};
},
/**
Unregister a fullName
```javascript
var registry = new Registry();
registry.register('model:user', User);
registry.resolve('model:user').create() instanceof User //=> true
registry.unregister('model:user')
registry.resolve('model:user') === undefined //=> true
```
@private
@method unregister
@param {String} fullName
*/
unregister: function (fullName) {
var normalizedName = this.normalize(fullName);
this._localLookupCache = new _emberMetalEmpty_object.default();
delete this.registrations[normalizedName];
delete this._resolveCache[normalizedName];
delete this._failCache[normalizedName];
delete this._options[normalizedName];
},
/**
Given a fullName return the corresponding factory.
By default `resolve` will retrieve the factory from
the registry.
```javascript
var registry = new Registry();
registry.register('api:twitter', Twitter);
registry.resolve('api:twitter') // => Twitter
```
Optionally the registry can be provided with a custom resolver.
If provided, `resolve` will first provide the custom resolver
the opportunity to resolve the fullName, otherwise it will fallback
to the registry.
```javascript
var registry = new Registry();
registry.resolver = function(fullName) {
// lookup via the module system of choice
};
// the twitter factory is added to the module system
registry.resolve('api:twitter') // => Twitter
```
@private
@method resolve
@param {String} fullName
@param {Object} [options]
@param {String} [options.source] the fullname of the request source (used for local lookups)
@return {Function} fullName's factory
*/
resolve: function (fullName, options) {
var factory = resolve(this, this.normalize(fullName), options);
if (factory === undefined && this.fallback) {
var _fallback;
factory = (_fallback = this.fallback).resolve.apply(_fallback, arguments);
}
return factory;
},
/**
A hook that can be used to describe how the resolver will
attempt to find the factory.
For example, the default Ember `.describe` returns the full
class name (including namespace) where Ember's resolver expects
to find the `fullName`.
@private
@method describe
@param {String} fullName
@return {string} described fullName
*/
describe: function (fullName) {
if (this.resolver && this.resolver.lookupDescription) {
return this.resolver.lookupDescription(fullName);
} else if (this.fallback) {
return this.fallback.describe(fullName);
} else {
return fullName;
}
},
/**
A hook to enable custom fullName normalization behaviour
@private
@method normalizeFullName
@param {String} fullName
@return {string} normalized fullName
*/
normalizeFullName: function (fullName) {
if (this.resolver && this.resolver.normalize) {
return this.resolver.normalize(fullName);
} else if (this.fallback) {
return this.fallback.normalizeFullName(fullName);
} else {
return fullName;
}
},
/**
Normalize a fullName based on the application's conventions
@private
@method normalize
@param {String} fullName
@return {string} normalized fullName
*/
normalize: function (fullName) {
return this._normalizeCache[fullName] || (this._normalizeCache[fullName] = this.normalizeFullName(fullName));
},
/**
@method makeToString
@private
@param {any} factory
@param {string} fullName
@return {function} toString function
*/
makeToString: function (factory, fullName) {
if (this.resolver && this.resolver.makeToString) {
return this.resolver.makeToString(factory, fullName);
} else if (this.fallback) {
return this.fallback.makeToString(factory, fullName);
} else {
return factory.toString();
}
},
/**
Given a fullName check if the container is aware of its factory
or singleton instance.
@private
@method has
@param {String} fullName
@param {Object} [options]
@param {String} [options.source] the fullname of the request source (used for local lookups)
@return {Boolean}
*/
has: function (fullName, options) {
var source = undefined;
return has(this, this.normalize(fullName), source);
},
/**
Allow registering options for all factories of a type.
```javascript
var registry = new Registry();
var container = registry.container();
// if all of type `connection` must not be singletons
registry.optionsForType('connection', { singleton: false });
registry.register('connection:twitter', TwitterConnection);
registry.register('connection:facebook', FacebookConnection);
var twitter = container.lookup('connection:twitter');
var twitter2 = container.lookup('connection:twitter');
twitter === twitter2; // => false
var facebook = container.lookup('connection:facebook');
var facebook2 = container.lookup('connection:facebook');
facebook === facebook2; // => false
```
@private
@method optionsForType
@param {String} type
@param {Object} options
*/
optionsForType: function (type, options) {
this._typeOptions[type] = options;
},
getOptionsForType: function (type) {
var optionsForType = this._typeOptions[type];
if (optionsForType === undefined && this.fallback) {
optionsForType = this.fallback.getOptionsForType(type);
}
return optionsForType;
},
/**
@private
@method options
@param {String} fullName
@param {Object} options
*/
options: function (fullName, _options) {
var 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) {
var fullNameType = fullName.split(':')[0];
if (fullNameType === type) {
throw new Error('Cannot inject a `' + fullName + '` on other ' + type + '(s).');
}
var injections = this._typeInjections[type] || (this._typeInjections[type] = []);
injections.push({
property: property,
fullName: fullName
});
},
/**
Defines injection rules.
These rules are used to inject dependencies onto objects when they
are instantiated.
Two forms of injections are possible:
* Injecting one fullName on another fullName
* Injecting one fullName on a type
Example:
```javascript
var registry = new Registry();
var container = registry.container();
registry.register('source:main', Source);
registry.register('model:user', User);
registry.register('model:post', Post);
// injecting one fullName on another fullName
// eg. each user model gets a post model
registry.injection('model:user', 'post', 'model:post');
// injecting one fullName on another type
registry.injection('model', 'source', 'source:main');
var user = container.lookup('model:user');
var post = container.lookup('model:post');
user.source instanceof Source; //=> true
post.source instanceof Source; //=> true
user.post instanceof Post; //=> true
// and both models share the same source
user.source === post.source; //=> true
```
@private
@method injection
@param {String} factoryName
@param {String} property
@param {String} injectionName
*/
injection: function (fullName, property, injectionName) {
this.validateFullName(injectionName);
var normalizedInjectionName = this.normalize(injectionName);
if (fullName.indexOf(':') === -1) {
return this.typeInjection(fullName, property, normalizedInjectionName);
}
var normalizedName = this.normalize(fullName);
var injections = this._injections[normalizedName] || (this._injections[normalizedName] = []);
injections.push({
property: property,
fullName: normalizedInjectionName
});
},
/**
Used only via `factoryInjection`.
Provides a specialized form of injection, specifically enabling
all factory of one type to be injected with a reference to another
object.
For example, provided each factory of type `model` needed a `store`.
one would do the following:
```javascript
var registry = new Registry();
registry.register('store:main', SomeStore);
registry.factoryTypeInjection('model', 'store', 'store:main');
var store = registry.lookup('store:main');
var UserFactory = registry.lookupFactory('model:user');
UserFactory.store instanceof SomeStore; //=> true
```
@private
@method factoryTypeInjection
@param {String} type
@param {String} property
@param {String} fullName
*/
factoryTypeInjection: function (type, property, fullName) {
var injections = this._factoryTypeInjections[type] || (this._factoryTypeInjections[type] = []);
injections.push({
property: property,
fullName: this.normalize(fullName)
});
},
/**
Defines factory injection rules.
Similar to regular injection rules, but are run against factories, via
`Registry#lookupFactory`.
These rules are used to inject objects onto factories when they
are looked up.
Two forms of injections are possible:
* Injecting one fullName on another fullName
* Injecting one fullName on a type
Example:
```javascript
var registry = new Registry();
var container = registry.container();
registry.register('store:main', Store);
registry.register('store:secondary', OtherStore);
registry.register('model:user', User);
registry.register('model:post', Post);
// injecting one fullName on another type
registry.factoryInjection('model', 'store', 'store:main');
// injecting one fullName on another fullName
registry.factoryInjection('model:post', 'secondaryStore', 'store:secondary');
var UserFactory = container.lookupFactory('model:user');
var PostFactory = container.lookupFactory('model:post');
var store = container.lookup('store:main');
UserFactory.store instanceof Store; //=> true
UserFactory.secondaryStore instanceof OtherStore; //=> false
PostFactory.store instanceof Store; //=> true
PostFactory.secondaryStore instanceof OtherStore; //=> true
// and both models share the same source instance
UserFactory.store === PostFactory.store; //=> true
```
@private
@method factoryInjection
@param {String} factoryName
@param {String} property
@param {String} injectionName
*/
factoryInjection: function (fullName, property, injectionName) {
var normalizedName = this.normalize(fullName);
var normalizedInjectionName = this.normalize(injectionName);
this.validateFullName(injectionName);
if (fullName.indexOf(':') === -1) {
return this.factoryTypeInjection(normalizedName, property, normalizedInjectionName);
}
var injections = this._factoryInjections[normalizedName] || (this._factoryInjections[normalizedName] = []);
injections.push({
property: property,
fullName: normalizedInjectionName
});
},
/**
@private
@method knownForType
@param {String} type the type to iterate over
*/
knownForType: function (type) {
var fallbackKnown = undefined,
resolverKnown = undefined;
var localKnown = _emberMetalDictionary.default(null);
var registeredNames = Object.keys(this.registrations);
for (var index = 0, _length = registeredNames.length; index < _length; index++) {
var fullName = registeredNames[index];
var itemType = fullName.split(':')[0];
if (itemType === type) {
localKnown[fullName] = true;
}
}
if (this.fallback) {
fallbackKnown = this.fallback.knownForType(type);
}
if (this.resolver && this.resolver.knownForType) {
resolverKnown = this.resolver.knownForType(type);
}
return _emberMetalAssign.default({}, fallbackKnown, localKnown, resolverKnown);
},
validateFullName: function (fullName) {
if (!VALID_FULL_NAME_REGEXP.test(fullName)) {
throw new TypeError('Invalid Fullname, expected: `type:name` got: ' + fullName);
}
return true;
},
validateInjections: function (injections) {
if (!injections) {
return;
}
var fullName;
for (var i = 0, length = injections.length; i < length; i++) {
fullName = injections[i].fullName;
if (!this.has(fullName)) {
throw new Error('Attempting to inject an unknown injection: `' + fullName + '`');
}
}
},
normalizeInjectionsHash: function (hash) {
var injections = [];
for (var key in hash) {
if (hash.hasOwnProperty(key)) {
injections.push({
property: key,
fullName: hash[key]
});
}
}
return injections;
},
getInjections: function (fullName) {
var injections = this._injections[fullName] || [];
if (this.fallback) {
injections = injections.concat(this.fallback.getInjections(fullName));
}
return injections;
},
getTypeInjections: function (type) {
var injections = this._typeInjections[type] || [];
if (this.fallback) {
injections = injections.concat(this.fallback.getTypeInjections(type));
}
return injections;
},
getFactoryInjections: function (fullName) {
var injections = this._factoryInjections[fullName] || [];
if (this.fallback) {
injections = injections.concat(this.fallback.getFactoryInjections(fullName));
}
return injections;
},
getFactoryTypeInjections: function (type) {
var injections = this._factoryTypeInjections[type] || [];
if (this.fallback) {
injections = injections.concat(this.fallback.getFactoryTypeInjections(type));
}
return injections;
}
};
function deprecateResolverFunction(registry) {
registry.resolver = {
resolve: registry.resolver
};
}
function expandLocalLookup(registry, normalizedName, normalizedSource) {
var cache = registry._localLookupCache;
var normalizedNameCache = cache[normalizedName];
if (!normalizedNameCache) {
normalizedNameCache = cache[normalizedName] = new _emberMetalEmpty_object.default();
}
var cached = normalizedNameCache[normalizedSource];
if (cached !== undefined) {
return cached;
}
var expanded = registry.resolver.expandLocalLookup(normalizedName, normalizedSource);
return normalizedNameCache[normalizedSource] = expanded;
}
function resolve(registry, normalizedName, options) {
var cached = registry._resolveCache[normalizedName];
if (cached !== undefined) {
return cached;
}
if (registry._failCache[normalizedName]) {
return;
}
var resolved = undefined;
if (registry.resolver) {
resolved = registry.resolver.resolve(normalizedName);
}
if (resolved === undefined) {
resolved = registry.registrations[normalizedName];
}
if (resolved === undefined) {
registry._failCache[normalizedName] = true;
} else {
registry._resolveCache[normalizedName] = resolved;
}
return resolved;
}
function has(registry, fullName, source) {
return registry.resolve(fullName, { source: source }) !== undefined;
}
exports.default = Registry;
});
/**
Given a fullName and a source fullName returns the fully resolved
fullName. Used to allow for local lookup.
```javascript
var registry = new Registry();
// the twitter factory is added to the module system
registry.expandLocalLookup('component:post-title', { source: 'template:post' }) // => component:post/post-title
```
@private
@method expandLocalLookup
@param {String} fullName
@param {Object} [options]
@param {String} [options.source] the fullname of the request source (used for local lookups)
@return {String} fullName
*/
// when `source` is provided expand normalizedName
// and source into the full normalizedName
// if expandLocalLookup returns falsey, we do not support local lookup
enifed('dag-map/platform', ['exports'], function (exports) {
'use strict';
var platform;
/* global self */
if (typeof self === 'object') {
platform = self;
/* global global */
} else if (typeof global === 'object') {
platform = global;
} else {
throw new Error('no global: `self` or `global` found');
}
exports.default = platform;
});
enifed('dag-map', ['exports', 'vertex', 'visit'], function (exports, _vertex, _visit) {
'use strict';
exports.default = DAG;
/**
* DAG stands for Directed acyclic graph.
*
* It is used to build a graph of dependencies checking that there isn't circular
* dependencies. p.e Registering initializers with a certain precedence order.
*
* @class DAG
* @constructor
*/
function DAG() {
this.names = [];
this.vertices = Object.create(null);
}
/**
* Adds a vertex entry to the graph unless it is already added.
*
* @private
* @method add
* @param {String} name The name of the vertex to add
*/
DAG.prototype.add = function (name) {
if (!name) {
throw new Error("Can't add Vertex without name");
}
if (this.vertices[name] !== undefined) {
return this.vertices[name];
}
var vertex = new _vertex.default(name);
this.vertices[name] = vertex;
this.names.push(name);
return vertex;
};
/**
* Adds a vertex to the graph and sets its value.
*
* @private
* @method map
* @param {String} name The name of the vertex.
* @param value The value to put in the vertex.
*/
DAG.prototype.map = function (name, value) {
this.add(name).value = value;
};
/**
* Connects the vertices with the given names, adding them to the graph if
* necessary, only if this does not produce is any circular dependency.
*
* @private
* @method addEdge
* @param {String} fromName The name the vertex where the edge starts.
* @param {String} toName The name the vertex where the edge ends.
*/
DAG.prototype.addEdge = function (fromName, toName) {
if (!fromName || !toName || fromName === toName) {
return;
}
var from = this.add(fromName);
var to = this.add(toName);
if (to.incoming.hasOwnProperty(fromName)) {
return;
}
function checkCycle(vertex, path) {
if (vertex.name === toName) {
throw new Error("cycle detected: " + toName + " <- " + path.join(" <- "));
}
}
_visit.default(from, checkCycle);
from.hasOutgoing = true;
to.incoming[fromName] = from;
to.incomingNames.push(fromName);
};
/**
* Visits all the vertex of the graph calling the given function with each one,
* ensuring that the vertices are visited respecting their precedence.
*
* @method topsort
* @param {Function} fn The function to be invoked on each vertex.
*/
DAG.prototype.topsort = function (fn) {
var visited = {};
var vertices = this.vertices;
var names = this.names;
var len = names.length;
var i, vertex;
for (i = 0; i < len; i++) {
vertex = vertices[names[i]];
if (!vertex.hasOutgoing) {
_visit.default(vertex, fn, visited);
}
}
};
/**
* Adds a vertex with the given name and value to the graph and joins it with the
* vertices referenced in _before_ and _after_. If there isn't vertices with those
* names, they are added too.
*
* If either _before_ or _after_ are falsy/empty, the added vertex will not have
* an incoming/outgoing edge.
*
* @method addEdges
* @param {String} name The name of the vertex to be added.
* @param value The value of that vertex.
* @param before An string or array of strings with the names of the vertices before
* which this vertex must be visited.
* @param after An string or array of strings with the names of the vertex after
* which this vertex must be visited.
*
*/
DAG.prototype.addEdges = function (name, value, before, after) {
var i;
this.map(name, value);
if (before) {
if (typeof before === 'string') {
this.addEdge(name, before);
} else {
for (i = 0; i < before.length; i++) {
this.addEdge(name, before[i]);
}
}
}
if (after) {
if (typeof after === 'string') {
this.addEdge(after, name);
} else {
for (i = 0; i < after.length; i++) {
this.addEdge(after[i], name);
}
}
}
};
});
enifed('dag-map.umd', ['exports', 'dag-map/platform', 'dag-map'], function (exports, _dagMapPlatform, _dagMap) {
'use strict';
/* global define:true module:true window: true */
if (typeof define === 'function' && define.amd) {
define(function () {
return _dagMap.default;
});
} else if (typeof module !== 'undefined' && module.exports) {
module.exports = _dagMap.default;
} else if (typeof _dagMapPlatform.default !== 'undefined') {
_dagMapPlatform.default['DAG'] = _dagMap.default;
}
});
enifed('dom-helper/build-html-dom', ['exports'], function (exports) {
/* global XMLSerializer:false */
'use strict';
var svgHTMLIntegrationPoints = { foreignObject: 1, desc: 1, title: 1 };
exports.svgHTMLIntegrationPoints = svgHTMLIntegrationPoints;
var svgNamespace = 'http://www.w3.org/2000/svg';
exports.svgNamespace = svgNamespace;
var doc = typeof document === 'undefined' ? false : document;
// Safari does not like using innerHTML on SVG HTML integration
// points (desc/title/foreignObject).
var needsIntegrationPointFix = doc && (function (document) {
if (document.createElementNS === undefined) {
return;
}
// In FF title will not accept innerHTML.
var testEl = document.createElementNS(svgNamespace, 'title');
testEl.innerHTML = "
";
return testEl.childNodes.length === 0 || testEl.childNodes[0].nodeType !== 1;
})(doc);
// Internet Explorer prior to 9 does not allow setting innerHTML if the first element
// is a "zero-scope" element. This problem can be worked around by making
// the first node an invisible text node. We, like Modernizr, use
var needsShy = doc && (function (document) {
var testEl = document.createElement('div');
testEl.innerHTML = "";
testEl.firstChild.innerHTML = "
```
And associate it by name using a view's `templateName` property:
```javascript
AView = Ember.View.extend({
templateName: 'some-template'
});
```
If you have nested routes, your Handlebars template will look like this:
```html
```
And `templateName` property:
```javascript
AView = Ember.View.extend({
templateName: 'posts/new'
});
```
Using a value for `templateName` that does not have a template
with a matching `data-template-name` attribute will throw an error.
For views classes that may have a template later defined (e.g. as the block
portion of a `{{view}}` helper call in another template or in
a subclass), you can provide a `defaultTemplate` property set to compiled
template function. If a template is not later provided for the view instance
the `defaultTemplate` value will be used:
```javascript
AView = Ember.View.extend({
defaultTemplate: Ember.HTMLBars.compile('I was the default'),
template: null,
templateName: null
});
```
Will result in instances with an HTML representation of:
```html
I was the default
```
If a `template` or `templateName` is provided it will take precedence over
`defaultTemplate`:
```javascript
AView = Ember.View.extend({
defaultTemplate: Ember.HTMLBars.compile('I was the default')
});
aView = AView.create({
template: Ember.HTMLBars.compile('I was the template, not default')
});
```
Will result in the following HTML representation when rendered:
```html
I was the template, not default
```
## View Context
The default context of the compiled template is the view's controller:
```javascript
AView = Ember.View.extend({
template: Ember.HTMLBars.compile('Hello {{excitedGreeting}}')
});
aController = Ember.Object.create({
firstName: 'Barry',
excitedGreeting: Ember.computed('content.firstName', function() {
return this.get('content.firstName') + '!!!';
})
});
aView = AView.create({
controller: aController
});
```
Will result in an HTML representation of:
```html
Hello Barry!!!
```
A context can also be explicitly supplied through the view's `context`
property. If the view has neither `context` nor `controller` properties, the
`parentView`'s context will be used.
## Layouts
Views can have a secondary template that wraps their main template. Like
primary templates, layouts can be any function that accepts an optional
context parameter and returns a string of HTML that will be inserted inside
view's tag. Views whose HTML element is self closing (e.g. ``)
cannot have a layout and this property will be ignored.
Most typically in Ember a layout will be a compiled template.
A view's layout can be set directly with the `layout` property or reference
an existing template by name with the `layoutName` property.
A template used as a layout must contain a single use of the
`{{yield}}` helper. The HTML contents of a view's rendered `template` will be
inserted at this location:
```javascript
AViewWithLayout = Ember.View.extend({
layout: Ember.HTMLBars.compile("
{{yield}}
"),
template: Ember.HTMLBars.compile("I got wrapped")
});
```
Will result in view instances with an HTML representation of:
```html
I got wrapped
```
See [Ember.Templates.helpers.yield](/api/classes/Ember.Templates.helpers.html#method_yield)
for more information.
## Responding to Browser Events
Views can respond to user-initiated events in one of three ways: method
implementation, through an event manager, and through `{{action}}` helper use
in their template or layout.
### Method Implementation
Views can respond to user-initiated events by implementing a method that
matches the event name. A `jQuery.Event` object will be passed as the
argument to this method.
```javascript
AView = Ember.View.extend({
click: function(event) {
// will be called when an instance's
// rendered element is clicked
}
});
```
### Event Managers
Views can define an object as their `eventManager` property. This object can
then implement methods that match the desired event names. Matching events
that occur on the view's rendered HTML or the rendered HTML of any of its DOM
descendants will trigger this method. A `jQuery.Event` object will be passed
as the first argument to the method and an `Ember.View` object as the
second. The `Ember.View` will be the view whose rendered HTML was interacted
with. This may be the view with the `eventManager` property or one of its
descendant views.
```javascript
AView = Ember.View.extend({
eventManager: Ember.Object.create({
doubleClick: function(event, view) {
// will be called when an instance's
// rendered element or any rendering
// of this view's descendant
// elements is clicked
}
})
});
```
An event defined for an event manager takes precedence over events of the
same name handled through methods on the view.
```javascript
AView = Ember.View.extend({
mouseEnter: function(event) {
// will never trigger.
},
eventManager: Ember.Object.create({
mouseEnter: function(event, view) {
// takes precedence over AView#mouseEnter
}
})
});
```
Similarly a view's event manager will take precedence for events of any views
rendered as a descendant. A method name that matches an event name will not
be called if the view instance was rendered inside the HTML representation of
a view that has an `eventManager` property defined that handles events of the
name. Events not handled by the event manager will still trigger method calls
on the descendant.
```javascript
var App = Ember.Application.create();
App.OuterView = Ember.View.extend({
template: Ember.HTMLBars.compile("outer {{#view 'inner'}}inner{{/view}} outer"),
eventManager: Ember.Object.create({
mouseEnter: function(event, view) {
// view might be instance of either
// OuterView or InnerView depending on
// where on the page the user interaction occurred
}
})
});
App.InnerView = Ember.View.extend({
click: function(event) {
// will be called if rendered inside
// an OuterView because OuterView's
// eventManager doesn't handle click events
},
mouseEnter: function(event) {
// will never be called if rendered inside
// an OuterView.
}
});
```
### `{{action}}` Helper
See [Ember.Templates.helpers.action](/api/classes/Ember.Templates.helpers.html#method_action).
### Event Names
All of the event handling approaches described above respond to the same set
of events. The names of the built-in events are listed below. (The hash of
built-in events exists in `Ember.EventDispatcher`.) Additional, custom events
can be registered by using `Ember.Application.customEvents`.
Touch events:
* `touchStart`
* `touchMove`
* `touchEnd`
* `touchCancel`
Keyboard events
* `keyDown`
* `keyUp`
* `keyPress`
Mouse events
* `mouseDown`
* `mouseUp`
* `contextMenu`
* `click`
* `doubleClick`
* `mouseMove`
* `focusIn`
* `focusOut`
* `mouseEnter`
* `mouseLeave`
Form events:
* `submit`
* `change`
* `focusIn`
* `focusOut`
* `input`
HTML5 drag and drop events:
* `dragStart`
* `drag`
* `dragEnter`
* `dragLeave`
* `dragOver`
* `dragEnd`
* `drop`
## `{{view}}` Helper
Other `Ember.View` instances can be included as part of a view's template by
using the `{{view}}` helper. See [Ember.Templates.helpers.view](/api/classes/Ember.Templates.helpers.html#method_view)
for additional information.
@class View
@namespace Ember
@extends Ember.CoreView
@deprecated See http://emberjs.com/deprecations/v1.x/#toc_ember-view
@uses Ember.ViewContextSupport
@uses Ember.ViewChildViewsSupport
@uses Ember.TemplateRenderingSupport
@uses Ember.ClassNamesSupport
@uses Ember.AttributeBindingsSupport
@uses Ember.LegacyViewSupport
@uses Ember.InstrumentationSupport
@uses Ember.VisibilitySupport
@uses Ember.AriaRoleSupport
@public
*/
// jscs:disable validateIndentation
var View = _emberViewsViewsCore_view.default.extend(_emberViewsMixinsView_context_support.default, _emberViewsMixinsView_child_views_support.default, _emberViewsMixinsLegacy_child_views_support.default, _emberViewsMixinsView_state_support.default, _emberViewsMixinsTemplate_rendering_support.default, _emberViewsMixinsClass_names_support.default, _emberViewsMixinsLegacy_view_support.default, _emberViewsMixinsInstrumentation_support.default, _emberViewsMixinsVisibility_support.default, _emberViewsCompatAttrsProxy.default, _emberViewsMixinsAria_role_support.default, _emberViewsMixinsView_support.default, {
init: function () {
this._super.apply(this, arguments);
if (!this._viewRegistry) {
this._viewRegistry = View.views;
}
},
/**
Given a property name, returns a dasherized version of that
property name if the property evaluates to a non-falsy value.
For example, if the view has property `isUrgent` that evaluates to true,
passing `isUrgent` to this method will return `"is-urgent"`.
@method _classStringForProperty
@param property
@private
*/
_classStringForProperty: function (parsedPath) {
return View._classStringForValue(parsedPath.path, parsedPath.stream.value(), parsedPath.className, parsedPath.falsyClassName);
}
});
_emberMetalDeprecate_property.deprecateProperty(View.prototype, 'currentState', '_currentState', {
id: 'ember-view.current-state',
until: '2.3.0',
url: 'http://emberjs.com/deprecations/v2.x/#toc_ember-component-currentstate'
});
// jscs:enable validateIndentation
/*
Describe how the specified actions should behave in the various
states that a view can exist in. Possible states:
* preRender: when a view is first instantiated, and after its
element was destroyed, it is in the preRender state
* inBuffer: once a view has been rendered, but before it has
been inserted into the DOM, it is in the inBuffer state
* hasElement: the DOM representation of the view is created,
and is ready to be inserted
* inDOM: once a view has been inserted into the DOM it is in
the inDOM state. A view spends the vast majority of its
existence in this state.
* destroyed: once a view has been destroyed (using the destroy
method), it is in this state. No further actions can be invoked
on a destroyed view.
*/
// in the destroyed state, everything is illegal
// before rendering has begun, all legal manipulations are noops.
// inside the buffer, legal manipulations are done on the buffer
// once the view has been inserted into the DOM, legal manipulations
// are done on the DOM element.
View.reopenClass({
/**
Global views hash
@property views
@static
@type Object
@private
*/
views: {},
// If someone overrides the child views computed property when
// defining their class, we want to be able to process the user's
// supplied childViews and then restore the original computed property
// at view initialization time. This happens in Ember.ContainerView's init
// method.
childViewsProperty: _emberViewsMixinsView_child_views_support.childViewsProperty
});
function viewDeprecationMessage() {}
var DeprecatedView = View.extend({
init: function () {
viewDeprecationMessage();
this._super.apply(this, arguments);
}
});
DeprecatedView.reopen = function () {
viewDeprecationMessage();
View.reopen.apply(View, arguments);
return this;
};
exports.default = View;
exports.ViewContextSupport = _emberViewsMixinsView_context_support.default;
exports.ViewChildViewsSupport = _emberViewsMixinsView_child_views_support.default;
exports.ViewStateSupport = _emberViewsMixinsView_state_support.default;
exports.TemplateRenderingSupport = _emberViewsMixinsTemplate_rendering_support.default;
exports.ClassNamesSupport = _emberViewsMixinsClass_names_support.default;
exports.DeprecatedView = DeprecatedView;
});
// for the side effect of extending Ember.run.queues
enifed('htmlbars-runtime/expression-visitor', ['exports'], function (exports) {
/**
# Expression Nodes:
These nodes are not directly responsible for any part of the DOM, but are
eventually passed to a Statement Node.
* get
* subexpr
* concat
*/
'use strict';
exports.acceptParams = acceptParams;
exports.acceptHash = acceptHash;
function acceptParams(nodes, env, scope) {
var array = [];
for (var i = 0, l = nodes.length; i < l; i++) {
array.push(acceptExpression(nodes[i], env, scope).value);
}
return array;
}
function acceptHash(pairs, env, scope) {
var object = {};
for (var i = 0, l = pairs.length; i < l; i += 2) {
var key = pairs[i];
var value = pairs[i + 1];
object[key] = acceptExpression(value, env, scope).value;
}
return object;
}
function acceptExpression(node, env, scope) {
var ret = { value: null };
// Primitive literals are unambiguously non-array representations of
// themselves.
if (typeof node !== 'object' || node === null) {
ret.value = node;
} else {
ret.value = evaluateNode(node, env, scope);
}
return ret;
}
function evaluateNode(node, env, scope) {
switch (node[0]) {
// can be used by manualElement
case 'value':
return node[1];
case 'get':
return evaluateGet(node, env, scope);
case 'subexpr':
return evaluateSubexpr(node, env, scope);
case 'concat':
return evaluateConcat(node, env, scope);
}
}
function evaluateGet(node, env, scope) {
var path = node[1];
return env.hooks.get(env, scope, path);
}
function evaluateSubexpr(node, env, scope) {
var path = node[1];
var rawParams = node[2];
var rawHash = node[3];
var params = acceptParams(rawParams, env, scope);
var hash = acceptHash(rawHash, env, scope);
return env.hooks.subexpr(env, scope, path, params, hash);
}
function evaluateConcat(node, env, scope) {
var rawParts = node[1];
var parts = acceptParams(rawParts, env, scope);
return env.hooks.concat(env, parts);
}
});
enifed("htmlbars-runtime/hooks", ["exports", "htmlbars-runtime/render", "morph-range/morph-list", "htmlbars-util/object-utils", "htmlbars-util/morph-utils", "htmlbars-util/template-utils"], function (exports, _htmlbarsRuntimeRender, _morphRangeMorphList, _htmlbarsUtilObjectUtils, _htmlbarsUtilMorphUtils, _htmlbarsUtilTemplateUtils) {
"use strict";
exports.wrap = wrap;
exports.wrapForHelper = wrapForHelper;
exports.createScope = createScope;
exports.createFreshScope = createFreshScope;
exports.bindShadowScope = bindShadowScope;
exports.createChildScope = createChildScope;
exports.bindSelf = bindSelf;
exports.updateSelf = updateSelf;
exports.bindLocal = bindLocal;
exports.updateLocal = updateLocal;
exports.bindBlock = bindBlock;
exports.block = block;
exports.continueBlock = continueBlock;
exports.hostBlock = hostBlock;
exports.handleRedirect = handleRedirect;
exports.handleKeyword = handleKeyword;
exports.linkRenderNode = linkRenderNode;
exports.inline = inline;
exports.keyword = keyword;
exports.invokeHelper = invokeHelper;
exports.classify = classify;
exports.partial = partial;
exports.range = range;
exports.element = element;
exports.attribute = attribute;
exports.subexpr = subexpr;
exports.get = get;
exports.getRoot = getRoot;
exports.getBlock = getBlock;
exports.getChild = getChild;
exports.getValue = getValue;
exports.getCellOrValue = getCellOrValue;
exports.component = component;
exports.concat = concat;
exports.hasHelper = hasHelper;
exports.lookupHelper = lookupHelper;
exports.bindScope = bindScope;
exports.updateScope = updateScope;
/**
HTMLBars delegates the runtime behavior of a template to
hooks provided by the host environment. These hooks explain
the lexical environment of a Handlebars template, the internal
representation of references, and the interaction between an
HTMLBars template and the DOM it is managing.
While HTMLBars host hooks have access to all of this internal
machinery, templates and helpers have access to the abstraction
provided by the host hooks.
## The Lexical Environment
The default lexical environment of an HTMLBars template includes:
* Any local variables, provided by *block arguments*
* The current value of `self`
## Simple Nesting
Let's look at a simple template with a nested block:
```hbs
{{title}}
{{#if author}}
{{author}}
{{/if}}
```
In this case, the lexical environment at the top-level of the
template does not change inside of the `if` block. This is
achieved via an implementation of `if` that looks like this:
```js
registerHelper('if', function(params) {
if (!!params[0]) {
return this.yield();
}
});
```
A call to `this.yield` invokes the child template using the
current lexical environment.
## Block Arguments
It is possible for nested blocks to introduce new local
variables:
```hbs
{{#count-calls as |i|}}
{{title}}
Called {{i}} times
{{/count}}
```
In this example, the child block inherits its surrounding
lexical environment, but augments it with a single new
variable binding.
The implementation of `count-calls` supplies the value of
`i`, but does not otherwise alter the environment:
```js
var count = 0;
registerHelper('count-calls', function() {
return this.yield([ ++count ]);
});
```
*/
function wrap(template) {
if (template === null) {
return null;
}
return {
meta: template.meta,
arity: template.arity,
raw: template,
render: function (self, env, options, blockArguments) {
var scope = env.hooks.createFreshScope();
var contextualElement = options && options.contextualElement;
var renderOptions = new _htmlbarsRuntimeRender.RenderOptions(null, self, blockArguments, contextualElement);
return _htmlbarsRuntimeRender.default(template, env, scope, renderOptions);
}
};
}
function wrapForHelper(template, env, scope, morph, renderState, visitor) {
if (!template) {
return {};
}
var yieldArgs = yieldTemplate(template, env, scope, morph, renderState, visitor);
return {
meta: template.meta,
arity: template.arity,
'yield': yieldArgs, // quoted since it's a reserved word, see issue #420
yieldItem: yieldItem(template, env, scope, morph, renderState, visitor),
raw: template,
render: function (self, blockArguments) {
yieldArgs(blockArguments, self);
}
};
}
// Called by a user-land helper to render a template.
function yieldTemplate(template, env, parentScope, morph, renderState, visitor) {
return function (blockArguments, self) {
// Render state is used to track the progress of the helper (since it
// may call into us multiple times). As the user-land helper calls
// into library code, we track what needs to be cleaned up after the
// helper has returned.
//
// Here, we remember that a template has been yielded and so we do not
// need to remove the previous template. (If no template is yielded
// this render by the helper, we assume nothing should be shown and
// remove any previous rendered templates.)
renderState.morphToClear = null;
// In this conditional is true, it means that on the previous rendering pass
// the helper yielded multiple items via `yieldItem()`, but this time they
// are yielding a single template. In that case, we mark the morph list for
// cleanup so it is removed from the DOM.
if (morph.morphList) {
_htmlbarsUtilTemplateUtils.clearMorphList(morph.morphList, morph, env);
renderState.morphListToClear = null;
}
var scope = parentScope;
if (morph.lastYielded && isStableTemplate(template, morph.lastYielded)) {
return morph.lastResult.revalidateWith(env, undefined, self, blockArguments, visitor);
}
// Check to make sure that we actually **need** a new scope, and can't
// share the parent scope. Note that we need to move this check into
// a host hook, because the host's notion of scope may require a new
// scope in more cases than the ones we can determine statically.
if (self !== undefined || parentScope === null || template.arity) {
scope = env.hooks.createChildScope(parentScope);
}
morph.lastYielded = { self: self, template: template, shadowTemplate: null };
// Render the template that was selected by the helper
var renderOptions = new _htmlbarsRuntimeRender.RenderOptions(morph, self, blockArguments);
_htmlbarsRuntimeRender.default(template, env, scope, renderOptions);
};
}
function yieldItem(template, env, parentScope, morph, renderState, visitor) {
// Initialize state that tracks multiple items being
// yielded in.
var currentMorph = null;
// Candidate morphs for deletion.
var candidates = {};
// Reuse existing MorphList if this is not a first-time
// render.
var morphList = morph.morphList;
if (morphList) {
currentMorph = morphList.firstChildMorph;
}
// Advances the currentMorph pointer to the morph in the previously-rendered
// list that matches the yielded key. While doing so, it marks any morphs
// that it advances past as candidates for deletion. Assuming those morphs
// are not yielded in later, they will be removed in the prune step during
// cleanup.
// Note that this helper function assumes that the morph being seeked to is
// guaranteed to exist in the previous MorphList; if this is called and the
// morph does not exist, it will result in an infinite loop
function advanceToKey(key) {
var seek = currentMorph;
while (seek.key !== key) {
candidates[seek.key] = seek;
seek = seek.nextMorph;
}
currentMorph = seek.nextMorph;
return seek;
}
return function (_key, blockArguments, self) {
if (typeof _key !== 'string') {
throw new Error("You must provide a string key when calling `yieldItem`; you provided " + _key);
}
// At least one item has been yielded, so we do not wholesale
// clear the last MorphList but instead apply a prune operation.
renderState.morphListToClear = null;
morph.lastYielded = null;
var morphList, morphMap;
if (!morph.morphList) {
morph.morphList = new _morphRangeMorphList.default();
morph.morphMap = {};
morph.setMorphList(morph.morphList);
}
morphList = morph.morphList;
morphMap = morph.morphMap;
// A map of morphs that have been yielded in on this
// rendering pass. Any morphs that do not make it into
// this list will be pruned from the MorphList during the cleanup
// process.
var handledMorphs = renderState.handledMorphs;
var key = undefined;
if (_key in handledMorphs) {
// In this branch we are dealing with a duplicate key. The strategy
// is to take the original key and append a counter to it that is
// incremented every time the key is reused. In order to greatly
// reduce the chance of colliding with another valid key we also add
// an extra string "--z8mS2hvDW0A--" to the new key.
var collisions = renderState.collisions;
if (collisions === undefined) {
collisions = renderState.collisions = {};
}
var count = collisions[_key] | 0;
collisions[_key] = ++count;
key = _key + '--z8mS2hvDW0A--' + count;
} else {
key = _key;
}
if (currentMorph && currentMorph.key === key) {
yieldTemplate(template, env, parentScope, currentMorph, renderState, visitor)(blockArguments, self);
currentMorph = currentMorph.nextMorph;
handledMorphs[key] = currentMorph;
} else if (morphMap[key] !== undefined) {
var foundMorph = morphMap[key];
if (key in candidates) {
// If we already saw this morph, move it forward to this position
morphList.insertBeforeMorph(foundMorph, currentMorph);
} else {
// Otherwise, move the pointer forward to the existing morph for this key
advanceToKey(key);
}
handledMorphs[foundMorph.key] = foundMorph;
yieldTemplate(template, env, parentScope, foundMorph, renderState, visitor)(blockArguments, self);
} else {
var childMorph = _htmlbarsRuntimeRender.createChildMorph(env.dom, morph);
childMorph.key = key;
morphMap[key] = handledMorphs[key] = childMorph;
morphList.insertBeforeMorph(childMorph, currentMorph);
yieldTemplate(template, env, parentScope, childMorph, renderState, visitor)(blockArguments, self);
}
renderState.morphListToPrune = morphList;
morph.childNodes = null;
};
}
function isStableTemplate(template, lastYielded) {
return !lastYielded.shadowTemplate && template === lastYielded.template;
}
function optionsFor(template, inverse, env, scope, morph, visitor) {
// If there was a template yielded last time, set morphToClear so it will be cleared
// if no template is yielded on this render.
var morphToClear = morph.lastResult ? morph : null;
var renderState = new _htmlbarsUtilTemplateUtils.RenderState(morphToClear, morph.morphList || null);
return {
templates: {
template: wrapForHelper(template, env, scope, morph, renderState, visitor),
inverse: wrapForHelper(inverse, env, scope, morph, renderState, visitor)
},
renderState: renderState
};
}
function thisFor(options) {
return {
arity: options.template.arity,
'yield': options.template.yield, // quoted since it's a reserved word, see issue #420
yieldItem: options.template.yieldItem,
yieldIn: options.template.yieldIn
};
}
/**
Host Hook: createScope
@param {Scope?} parentScope
@return Scope
Corresponds to entering a new HTMLBars block.
This hook is invoked when a block is entered with
a new `self` or additional local variables.
When invoked for a top-level template, the
`parentScope` is `null`, and this hook should return
a fresh Scope.
When invoked for a child template, the `parentScope`
is the scope for the parent environment.
Note that the `Scope` is an opaque value that is
passed to other host hooks. For example, the `get`
hook uses the scope to retrieve a value for a given
scope and variable name.
*/
function createScope(env, parentScope) {
if (parentScope) {
return env.hooks.createChildScope(parentScope);
} else {
return env.hooks.createFreshScope();
}
}
function createFreshScope() {
// because `in` checks have unpredictable performance, keep a
// separate dictionary to track whether a local was bound.
// See `bindLocal` for more information.
return { self: null, blocks: {}, locals: {}, localPresent: {} };
}
/**
Host Hook: bindShadowScope
@param {Scope?} parentScope
@return Scope
Corresponds to rendering a new template into an existing
render tree, but with a new top-level lexical scope. This
template is called the "shadow root".
If a shadow template invokes `{{yield}}`, it will render
the block provided to the shadow root in the original
lexical scope.
```hbs
{{!-- post template --}}
{{props.title}}
{{yield}}
{{!-- blog template --}}
{{#post title="Hello world"}}
by {{byline}}
This is my first post
{{/post}}
{{#post title="Goodbye world"}}
by {{byline}}
This is my last post
{{/post}}
```
```js
helpers.post = function(params, hash, options) {
options.template.yieldIn(postTemplate, { props: hash });
};
blog.render({ byline: "Yehuda Katz" });
```
Produces:
```html
Hello world
by Yehuda Katz
This is my first post
Goodbye world
by Yehuda Katz
This is my last post
```
In short, `yieldIn` creates a new top-level scope for the
provided template and renders it, making the original block
available to `{{yield}}` in that template.
*/
function bindShadowScope(env /*, parentScope, shadowScope */) {
return env.hooks.createFreshScope();
}
function createChildScope(parent) {
var scope = Object.create(parent);
scope.locals = Object.create(parent.locals);
scope.localPresent = Object.create(parent.localPresent);
scope.blocks = Object.create(parent.blocks);
return scope;
}
/**
Host Hook: bindSelf
@param {Scope} scope
@param {any} self
Corresponds to entering a template.
This hook is invoked when the `self` value for a scope is ready to be bound.
The host must ensure that child scopes reflect the change to the `self` in
future calls to the `get` hook.
*/
function bindSelf(env, scope, self) {
scope.self = self;
}
function updateSelf(env, scope, self) {
env.hooks.bindSelf(env, scope, self);
}
/**
Host Hook: bindLocal
@param {Environment} env
@param {Scope} scope
@param {String} name
@param {any} value
Corresponds to entering a template with block arguments.
This hook is invoked when a local variable for a scope has been provided.
The host must ensure that child scopes reflect the change in future calls
to the `get` hook.
*/
function bindLocal(env, scope, name, value) {
scope.localPresent[name] = true;
scope.locals[name] = value;
}
function updateLocal(env, scope, name, value) {
env.hooks.bindLocal(env, scope, name, value);
}
/**
Host Hook: bindBlock
@param {Environment} env
@param {Scope} scope
@param {Function} block
Corresponds to entering a shadow template that was invoked by a block helper with
`yieldIn`.
This hook is invoked with an opaque block that will be passed along
to the shadow template, and inserted into the shadow template when
`{{yield}}` is used. Optionally provide a non-default block name
that can be targeted by `{{yield to=blockName}}`.
*/
function bindBlock(env, scope, block) {
var name = arguments.length <= 3 || arguments[3] === undefined ? 'default' : arguments[3];
scope.blocks[name] = block;
}
/**
Host Hook: block
@param {RenderNode} renderNode
@param {Environment} env
@param {Scope} scope
@param {String} path
@param {Array} params
@param {Object} hash
@param {Block} block
@param {Block} elseBlock
Corresponds to:
```hbs
{{#helper param1 param2 key1=val1 key2=val2}}
{{!-- child template --}}
{{/helper}}
```
This host hook is a workhorse of the system. It is invoked
whenever a block is encountered, and is responsible for
resolving the helper to call, and then invoke it.
The helper should be invoked with:
- `{Array} params`: the parameters passed to the helper
in the template.
- `{Object} hash`: an object containing the keys and values passed
in the hash position in the template.
The values in `params` and `hash` will already be resolved
through a previous call to the `get` host hook.
The helper should be invoked with a `this` value that is
an object with one field:
`{Function} yield`: when invoked, this function executes the
block with the current scope. It takes an optional array of
block parameters. If block parameters are supplied, HTMLBars
will invoke the `bindLocal` host hook to bind the supplied
values to the block arguments provided by the template.
In general, the default implementation of `block` should work
for most host environments. It delegates to other host hooks
where appropriate, and properly invokes the helper with the
appropriate arguments.
*/
function block(morph, env, scope, path, params, hash, template, inverse, visitor) {
if (handleRedirect(morph, env, scope, path, params, hash, template, inverse, visitor)) {
return;
}
continueBlock(morph, env, scope, path, params, hash, template, inverse, visitor);
}
function continueBlock(morph, env, scope, path, params, hash, template, inverse, visitor) {
hostBlock(morph, env, scope, template, inverse, null, visitor, function (options) {
var helper = env.hooks.lookupHelper(env, scope, path);
return env.hooks.invokeHelper(morph, env, scope, visitor, params, hash, helper, options.templates, thisFor(options.templates));
});
}
function hostBlock(morph, env, scope, template, inverse, shadowOptions, visitor, callback) {
var options = optionsFor(template, inverse, env, scope, morph, visitor);
_htmlbarsUtilTemplateUtils.renderAndCleanup(morph, env, options, shadowOptions, callback);
}
function handleRedirect(morph, env, scope, path, params, hash, template, inverse, visitor) {
if (!path) {
return false;
}
var redirect = env.hooks.classify(env, scope, path);
if (redirect) {
switch (redirect) {
case 'component':
env.hooks.component(morph, env, scope, path, params, hash, { default: template, inverse: inverse }, visitor);break;
case 'inline':
env.hooks.inline(morph, env, scope, path, params, hash, visitor);break;
case 'block':
env.hooks.block(morph, env, scope, path, params, hash, template, inverse, visitor);break;
default:
throw new Error("Internal HTMLBars redirection to " + redirect + " not supported");
}
return true;
}
if (handleKeyword(path, morph, env, scope, params, hash, template, inverse, visitor)) {
return true;
}
return false;
}
function handleKeyword(path, morph, env, scope, params, hash, template, inverse, visitor) {
var keyword = env.hooks.keywords[path];
if (!keyword) {
return false;
}
if (typeof keyword === 'function') {
return keyword(morph, env, scope, params, hash, template, inverse, visitor);
}
if (keyword.willRender) {
keyword.willRender(morph, env);
}
var lastState, newState;
if (keyword.setupState) {
lastState = _htmlbarsUtilObjectUtils.shallowCopy(morph.getState());
newState = morph.setState(keyword.setupState(lastState, env, scope, params, hash));
}
if (keyword.childEnv) {
// Build the child environment...
env = keyword.childEnv(morph.getState(), env);
// ..then save off the child env builder on the render node. If the render
// node tree is re-rendered and this node is not dirty, the child env
// builder will still be invoked so that child dirty render nodes still get
// the correct child env.
morph.buildChildEnv = keyword.childEnv;
}
var firstTime = !morph.rendered;
if (keyword.isEmpty) {
var isEmpty = keyword.isEmpty(morph.getState(), env, scope, params, hash);
if (isEmpty) {
if (!firstTime) {
_htmlbarsUtilTemplateUtils.clearMorph(morph, env, false);
}
return true;
}
}
if (firstTime) {
if (keyword.render) {
keyword.render(morph, env, scope, params, hash, template, inverse, visitor);
}
morph.rendered = true;
return true;
}
var isStable;
if (keyword.isStable) {
isStable = keyword.isStable(lastState, newState);
} else {
isStable = stableState(lastState, newState);
}
if (isStable) {
if (keyword.rerender) {
var newEnv = keyword.rerender(morph, env, scope, params, hash, template, inverse, visitor);
env = newEnv || env;
}
_htmlbarsUtilMorphUtils.validateChildMorphs(env, morph, visitor);
return true;
} else {
_htmlbarsUtilTemplateUtils.clearMorph(morph, env, false);
}
// If the node is unstable, re-render from scratch
if (keyword.render) {
keyword.render(morph, env, scope, params, hash, template, inverse, visitor);
morph.rendered = true;
return true;
}
}
function stableState(oldState, newState) {
if (_htmlbarsUtilObjectUtils.keyLength(oldState) !== _htmlbarsUtilObjectUtils.keyLength(newState)) {
return false;
}
for (var prop in oldState) {
if (oldState[prop] !== newState[prop]) {
return false;
}
}
return true;
}
function linkRenderNode() /* morph, env, scope, params, hash */{
return;
}
/**
Host Hook: inline
@param {RenderNode} renderNode
@param {Environment} env
@param {Scope} scope
@param {String} path
@param {Array} params
@param {Hash} hash
Corresponds to:
```hbs
{{helper param1 param2 key1=val1 key2=val2}}
```
This host hook is similar to the `block` host hook, but it
invokes helpers that do not supply an attached block.
Like the `block` hook, the helper should be invoked with:
- `{Array} params`: the parameters passed to the helper
in the template.
- `{Object} hash`: an object containing the keys and values passed
in the hash position in the template.
The values in `params` and `hash` will already be resolved
through a previous call to the `get` host hook.
In general, the default implementation of `inline` should work
for most host environments. It delegates to other host hooks
where appropriate, and properly invokes the helper with the
appropriate arguments.
The default implementation of `inline` also makes `partial`
a keyword. Instead of invoking a helper named `partial`,
it invokes the `partial` host hook.
*/
function inline(morph, env, scope, path, params, hash, visitor) {
if (handleRedirect(morph, env, scope, path, params, hash, null, null, visitor)) {
return;
}
var value = undefined,
hasValue = undefined;
if (morph.linkedResult) {
value = env.hooks.getValue(morph.linkedResult);
hasValue = true;
} else {
var options = optionsFor(null, null, env, scope, morph);
var helper = env.hooks.lookupHelper(env, scope, path);
var result = env.hooks.invokeHelper(morph, env, scope, visitor, params, hash, helper, options.templates, thisFor(options.templates));
if (result && result.link) {
morph.linkedResult = result.value;
_htmlbarsUtilMorphUtils.linkParams(env, scope, morph, '@content-helper', [morph.linkedResult], null);
}
if (result && 'value' in result) {
value = env.hooks.getValue(result.value);
hasValue = true;
}
}
if (hasValue) {
if (morph.lastValue !== value) {
morph.setContent(value);
}
morph.lastValue = value;
}
}
function keyword(path, morph, env, scope, params, hash, template, inverse, visitor) {
handleKeyword(path, morph, env, scope, params, hash, template, inverse, visitor);
}
function invokeHelper(morph, env, scope, visitor, _params, _hash, helper, templates, context) {
var params = normalizeArray(env, _params);
var hash = normalizeObject(env, _hash);
return { value: helper.call(context, params, hash, templates) };
}
function normalizeArray(env, array) {
var out = new Array(array.length);
for (var i = 0, l = array.length; i < l; i++) {
out[i] = env.hooks.getCellOrValue(array[i]);
}
return out;
}
function normalizeObject(env, object) {
var out = {};
for (var prop in object) {
out[prop] = env.hooks.getCellOrValue(object[prop]);
}
return out;
}
function classify() /* env, scope, path */{
return null;
}
var keywords = {
partial: function (morph, env, scope, params) {
var value = env.hooks.partial(morph, env, scope, params[0]);
morph.setContent(value);
return true;
},
// quoted since it's a reserved word, see issue #420
'yield': function (morph, env, scope, params, hash, template, inverse, visitor) {
// the current scope is provided purely for the creation of shadow
// scopes; it should not be provided to user code.
var to = env.hooks.getValue(hash.to) || 'default';
var block = env.hooks.getBlock(scope, to);
if (block) {
block.invoke(env, params, hash.self, morph, scope, visitor);
}
return true;
},
hasBlock: function (morph, env, scope, params) {
var name = env.hooks.getValue(params[0]) || 'default';
return !!env.hooks.getBlock(scope, name);
},
hasBlockParams: function (morph, env, scope, params) {
var name = env.hooks.getValue(params[0]) || 'default';
var block = env.hooks.getBlock(scope, name);
return !!(block && block.arity);
}
};
exports.keywords = keywords;
/**
Host Hook: partial
@param {RenderNode} renderNode
@param {Environment} env
@param {Scope} scope
@param {String} path
Corresponds to:
```hbs
{{partial "location"}}
```
This host hook is invoked by the default implementation of
the `inline` hook. This makes `partial` a keyword in an
HTMLBars environment using the default `inline` host hook.
It is implemented as a host hook so that it can retrieve
the named partial out of the `Environment`. Helpers, in
contrast, only have access to the values passed in to them,
and not to the ambient lexical environment.
The host hook should invoke the referenced partial with
the ambient `self`.
*/
function partial(renderNode, env, scope, path) {
var template = env.partials[path];
return template.render(scope.self, env, {}).fragment;
}
/**
Host hook: range
@param {RenderNode} renderNode
@param {Environment} env
@param {Scope} scope
@param {any} value
Corresponds to:
```hbs
{{content}}
{{{unescaped}}}
```
This hook is responsible for updating a render node
that represents a range of content with a value.
*/
function range(morph, env, scope, path, value, visitor) {
if (handleRedirect(morph, env, scope, path, [], {}, null, null, visitor)) {
return;
}
value = env.hooks.getValue(value);
if (morph.lastValue !== value) {
morph.setContent(value);
}
morph.lastValue = value;
}
/**
Host hook: element
@param {RenderNode} renderNode
@param {Environment} env
@param {Scope} scope
@param {String} path
@param {Array} params
@param {Hash} hash
Corresponds to:
```hbs
```
This hook is responsible for invoking a helper that
modifies an element.
Its purpose is largely legacy support for awkward
idioms that became common when using the string-based
Handlebars engine.
Most of the uses of the `element` hook are expected
to be superseded by component syntax and the
`attribute` hook.
*/
function element(morph, env, scope, path, params, hash, visitor) {
if (handleRedirect(morph, env, scope, path, params, hash, null, null, visitor)) {
return;
}
var helper = env.hooks.lookupHelper(env, scope, path);
if (helper) {
env.hooks.invokeHelper(null, env, scope, null, params, hash, helper, { element: morph.element });
}
}
/**
Host hook: attribute
@param {RenderNode} renderNode
@param {Environment} env
@param {String} name
@param {any} value
Corresponds to:
```hbs
```
This hook is responsible for updating a render node
that represents an element's attribute with a value.
It receives the name of the attribute as well as an
already-resolved value, and should update the render
node with the value if appropriate.
*/
function attribute(morph, env, scope, name, value) {
value = env.hooks.getValue(value);
if (morph.lastValue !== value) {
morph.setContent(value);
}
morph.lastValue = value;
}
function subexpr(env, scope, helperName, params, hash) {
var helper = env.hooks.lookupHelper(env, scope, helperName);
var result = env.hooks.invokeHelper(null, env, scope, null, params, hash, helper, {});
if (result && 'value' in result) {
return env.hooks.getValue(result.value);
}
}
/**
Host Hook: get
@param {Environment} env
@param {Scope} scope
@param {String} path
Corresponds to:
```hbs
{{foo.bar}}
^
{{helper foo.bar key=value}}
^ ^
```
This hook is the "leaf" hook of the system. It is used to
resolve a path relative to the current scope.
*/
function get(env, scope, path) {
if (path === '') {
return scope.self;
}
var keys = path.split('.');
var value = env.hooks.getRoot(scope, keys[0])[0];
for (var i = 1; i < keys.length; i++) {
if (value) {
value = env.hooks.getChild(value, keys[i]);
} else {
break;
}
}
return value;
}
function getRoot(scope, key) {
if (scope.localPresent[key]) {
return [scope.locals[key]];
} else if (scope.self) {
return [scope.self[key]];
} else {
return [undefined];
}
}
function getBlock(scope, key) {
return scope.blocks[key];
}
function getChild(value, key) {
return value[key];
}
function getValue(reference) {
return reference;
}
function getCellOrValue(reference) {
return reference;
}
function component(morph, env, scope, tagName, params, attrs, templates, visitor) {
if (env.hooks.hasHelper(env, scope, tagName)) {
return env.hooks.block(morph, env, scope, tagName, params, attrs, templates.default, templates.inverse, visitor);
}
componentFallback(morph, env, scope, tagName, attrs, templates.default);
}
function concat(env, params) {
var value = "";
for (var i = 0, l = params.length; i < l; i++) {
value += env.hooks.getValue(params[i]);
}
return value;
}
function componentFallback(morph, env, scope, tagName, attrs, template) {
var element = env.dom.createElement(tagName);
for (var name in attrs) {
element.setAttribute(name, env.hooks.getValue(attrs[name]));
}
var fragment = _htmlbarsRuntimeRender.default(template, env, scope, {}).fragment;
element.appendChild(fragment);
morph.setNode(element);
}
function hasHelper(env, scope, helperName) {
return env.helpers[helperName] !== undefined;
}
function lookupHelper(env, scope, helperName) {
return env.helpers[helperName];
}
function bindScope() /* env, scope */{
// this function is used to handle host-specified extensions to scope
// other than `self`, `locals` and `block`.
}
function updateScope(env, scope) {
env.hooks.bindScope(env, scope);
}
exports.default = {
// fundamental hooks that you will likely want to override
bindLocal: bindLocal,
bindSelf: bindSelf,
bindScope: bindScope,
classify: classify,
component: component,
concat: concat,
createFreshScope: createFreshScope,
getChild: getChild,
getRoot: getRoot,
getBlock: getBlock,
getValue: getValue,
getCellOrValue: getCellOrValue,
keywords: keywords,
linkRenderNode: linkRenderNode,
partial: partial,
subexpr: subexpr,
// fundamental hooks with good default behavior
bindBlock: bindBlock,
bindShadowScope: bindShadowScope,
updateLocal: updateLocal,
updateSelf: updateSelf,
updateScope: updateScope,
createChildScope: createChildScope,
hasHelper: hasHelper,
lookupHelper: lookupHelper,
invokeHelper: invokeHelper,
cleanupRenderNode: null,
destroyRenderNode: null,
willCleanupTree: null,
didCleanupTree: null,
willRenderNode: null,
didRenderNode: null,
// derived hooks
attribute: attribute,
block: block,
createScope: createScope,
element: element,
get: get,
inline: inline,
range: range,
keyword: keyword
};
});
enifed("htmlbars-runtime/morph", ["exports", "morph-range"], function (exports, _morphRange) {
"use strict";
var guid = 1;
function HTMLBarsMorph(domHelper, contextualElement) {
this.super$constructor(domHelper, contextualElement);
this._state = undefined;
this.ownerNode = null;
this.isDirty = false;
this.isSubtreeDirty = false;
this.lastYielded = null;
this.lastResult = null;
this.lastValue = null;
this.buildChildEnv = null;
this.morphList = null;
this.morphMap = null;
this.key = null;
this.linkedParams = null;
this.linkedResult = null;
this.childNodes = null;
this.rendered = false;
this.guid = "range" + guid++;
this.seen = false;
}
HTMLBarsMorph.empty = function (domHelper, contextualElement) {
var morph = new HTMLBarsMorph(domHelper, contextualElement);
morph.clear();
return morph;
};
HTMLBarsMorph.create = function (domHelper, contextualElement, node) {
var morph = new HTMLBarsMorph(domHelper, contextualElement);
morph.setNode(node);
return morph;
};
HTMLBarsMorph.attach = function (domHelper, contextualElement, firstNode, lastNode) {
var morph = new HTMLBarsMorph(domHelper, contextualElement);
morph.setRange(firstNode, lastNode);
return morph;
};
var prototype = HTMLBarsMorph.prototype = Object.create(_morphRange.default.prototype);
prototype.constructor = HTMLBarsMorph;
prototype.super$constructor = _morphRange.default;
prototype.getState = function () {
if (!this._state) {
this._state = {};
}
return this._state;
};
prototype.setState = function (newState) {
/*jshint -W093 */
return this._state = newState;
};
exports.default = HTMLBarsMorph;
});
enifed("htmlbars-runtime/node-visitor", ["exports", "htmlbars-util/morph-utils", "htmlbars-runtime/expression-visitor"], function (exports, _htmlbarsUtilMorphUtils, _htmlbarsRuntimeExpressionVisitor) {
"use strict";
/**
Node classification:
# Primary Statement Nodes:
These nodes are responsible for a render node that represents a morph-range.
* block
* inline
* content
* element
* component
# Leaf Statement Nodes:
This node is responsible for a render node that represents a morph-attr.
* attribute
*/
function linkParamsAndHash(env, scope, morph, path, params, hash) {
if (morph.linkedParams) {
params = morph.linkedParams.params;
hash = morph.linkedParams.hash;
} else {
params = params && _htmlbarsRuntimeExpressionVisitor.acceptParams(params, env, scope);
hash = hash && _htmlbarsRuntimeExpressionVisitor.acceptHash(hash, env, scope);
}
_htmlbarsUtilMorphUtils.linkParams(env, scope, morph, path, params, hash);
return [params, hash];
}
var AlwaysDirtyVisitor = {
block: function (node, morph, env, scope, template, visitor) {
var path = node[1];
var params = node[2];
var hash = node[3];
var templateId = node[4];
var inverseId = node[5];
var paramsAndHash = linkParamsAndHash(env, scope, morph, path, params, hash);
morph.isDirty = morph.isSubtreeDirty = false;
env.hooks.block(morph, env, scope, path, paramsAndHash[0], paramsAndHash[1], templateId === null ? null : template.templates[templateId], inverseId === null ? null : template.templates[inverseId], visitor);
},
inline: function (node, morph, env, scope, visitor) {
var path = node[1];
var params = node[2];
var hash = node[3];
var paramsAndHash = linkParamsAndHash(env, scope, morph, path, params, hash);
morph.isDirty = morph.isSubtreeDirty = false;
env.hooks.inline(morph, env, scope, path, paramsAndHash[0], paramsAndHash[1], visitor);
},
content: function (node, morph, env, scope, visitor) {
var path = node[1];
morph.isDirty = morph.isSubtreeDirty = false;
if (isHelper(env, scope, path)) {
env.hooks.inline(morph, env, scope, path, [], {}, visitor);
if (morph.linkedResult) {
_htmlbarsUtilMorphUtils.linkParams(env, scope, morph, '@content-helper', [morph.linkedResult], null);
}
return;
}
var params = undefined;
if (morph.linkedParams) {
params = morph.linkedParams.params;
} else {
params = [env.hooks.get(env, scope, path)];
}
_htmlbarsUtilMorphUtils.linkParams(env, scope, morph, '@range', params, null);
env.hooks.range(morph, env, scope, path, params[0], visitor);
},
element: function (node, morph, env, scope, visitor) {
var path = node[1];
var params = node[2];
var hash = node[3];
var paramsAndHash = linkParamsAndHash(env, scope, morph, path, params, hash);
morph.isDirty = morph.isSubtreeDirty = false;
env.hooks.element(morph, env, scope, path, paramsAndHash[0], paramsAndHash[1], visitor);
},
attribute: function (node, morph, env, scope) {
var name = node[1];
var value = node[2];
var paramsAndHash = linkParamsAndHash(env, scope, morph, '@attribute', [value], null);
morph.isDirty = morph.isSubtreeDirty = false;
env.hooks.attribute(morph, env, scope, name, paramsAndHash[0][0]);
},
component: function (node, morph, env, scope, template, visitor) {
var path = node[1];
var attrs = node[2];
var templateId = node[3];
var inverseId = node[4];
var paramsAndHash = linkParamsAndHash(env, scope, morph, path, [], attrs);
var templates = {
default: template.templates[templateId],
inverse: template.templates[inverseId]
};
morph.isDirty = morph.isSubtreeDirty = false;
env.hooks.component(morph, env, scope, path, paramsAndHash[0], paramsAndHash[1], templates, visitor);
},
attributes: function (node, morph, env, scope, parentMorph, visitor) {
var template = node[1];
env.hooks.attributes(morph, env, scope, template, parentMorph, visitor);
}
};
exports.AlwaysDirtyVisitor = AlwaysDirtyVisitor;
exports.default = {
block: function (node, morph, env, scope, template, visitor) {
dirtyCheck(env, morph, visitor, function (visitor) {
AlwaysDirtyVisitor.block(node, morph, env, scope, template, visitor);
});
},
inline: function (node, morph, env, scope, visitor) {
dirtyCheck(env, morph, visitor, function (visitor) {
AlwaysDirtyVisitor.inline(node, morph, env, scope, visitor);
});
},
content: function (node, morph, env, scope, visitor) {
dirtyCheck(env, morph, visitor, function (visitor) {
AlwaysDirtyVisitor.content(node, morph, env, scope, visitor);
});
},
element: function (node, morph, env, scope, template, visitor) {
dirtyCheck(env, morph, visitor, function (visitor) {
AlwaysDirtyVisitor.element(node, morph, env, scope, template, visitor);
});
},
attribute: function (node, morph, env, scope, template) {
dirtyCheck(env, morph, null, function () {
AlwaysDirtyVisitor.attribute(node, morph, env, scope, template);
});
},
component: function (node, morph, env, scope, template, visitor) {
dirtyCheck(env, morph, visitor, function (visitor) {
AlwaysDirtyVisitor.component(node, morph, env, scope, template, visitor);
});
},
attributes: function (node, morph, env, scope, parentMorph, visitor) {
AlwaysDirtyVisitor.attributes(node, morph, env, scope, parentMorph, visitor);
}
};
function dirtyCheck(_env, morph, visitor, callback) {
var isDirty = morph.isDirty;
var isSubtreeDirty = morph.isSubtreeDirty;
var env = _env;
if (isSubtreeDirty) {
visitor = AlwaysDirtyVisitor;
}
if (isDirty || isSubtreeDirty) {
callback(visitor);
} else {
if (morph.buildChildEnv) {
env = morph.buildChildEnv(morph.getState(), env);
}
_htmlbarsUtilMorphUtils.validateChildMorphs(env, morph, visitor);
}
}
function isHelper(env, scope, path) {
return env.hooks.keywords[path] !== undefined || env.hooks.hasHelper(env, scope, path);
}
});
enifed("htmlbars-runtime/render", ["exports", "htmlbars-util/morph-utils", "htmlbars-runtime/node-visitor", "htmlbars-runtime/morph", "htmlbars-util/template-utils", "htmlbars-util/void-tag-names"], function (exports, _htmlbarsUtilMorphUtils, _htmlbarsRuntimeNodeVisitor, _htmlbarsRuntimeMorph, _htmlbarsUtilTemplateUtils, _htmlbarsUtilVoidTagNames) {
"use strict";
exports.default = render;
exports.RenderOptions = RenderOptions;
exports.manualElement = manualElement;
exports.attachAttributes = attachAttributes;
exports.createChildMorph = createChildMorph;
exports.getCachedFragment = getCachedFragment;
var svgNamespace = "http://www.w3.org/2000/svg";
function render(template, env, scope, options) {
var dom = env.dom;
var contextualElement;
if (options) {
if (options.renderNode) {
contextualElement = options.renderNode.contextualElement;
} else if (options.contextualElement) {
contextualElement = options.contextualElement;
}
}
dom.detectNamespace(contextualElement);
var renderResult = RenderResult.build(env, scope, template, options, contextualElement);
renderResult.render();
return renderResult;
}
function RenderOptions(renderNode, self, blockArguments, contextualElement) {
this.renderNode = renderNode || null;
this.self = self;
this.blockArguments = blockArguments || null;
this.contextualElement = contextualElement || null;
}
function RenderResult(env, scope, options, rootNode, ownerNode, nodes, fragment, template, shouldSetContent) {
this.root = rootNode;
this.fragment = fragment;
this.nodes = nodes;
this.template = template;
this.statements = template.statements.slice();
this.env = env;
this.scope = scope;
this.shouldSetContent = shouldSetContent;
if (options.self !== undefined) {
this.bindSelf(options.self);
}
if (options.blockArguments !== undefined) {
this.bindLocals(options.blockArguments);
}
this.initializeNodes(ownerNode);
}
RenderResult.build = function (env, scope, template, options, contextualElement) {
var dom = env.dom;
var fragment = getCachedFragment(template, env);
var nodes = template.buildRenderNodes(dom, fragment, contextualElement);
var rootNode, ownerNode, shouldSetContent;
if (options && options.renderNode) {
rootNode = options.renderNode;
ownerNode = rootNode.ownerNode;
shouldSetContent = true;
} else {
rootNode = dom.createMorph(null, fragment.firstChild, fragment.lastChild, contextualElement);
ownerNode = rootNode;
rootNode.ownerNode = ownerNode;
shouldSetContent = false;
}
if (rootNode.childNodes) {
_htmlbarsUtilMorphUtils.visitChildren(rootNode.childNodes, function (node) {
_htmlbarsUtilTemplateUtils.clearMorph(node, env, true);
});
}
rootNode.childNodes = nodes;
return new RenderResult(env, scope, options, rootNode, ownerNode, nodes, fragment, template, shouldSetContent);
};
function manualElement(tagName, attributes, _isEmpty) {
var statements = [];
for (var key in attributes) {
if (typeof attributes[key] === 'string') {
continue;
}
statements.push(["attribute", key, attributes[key]]);
}
var isEmpty = _isEmpty || _htmlbarsUtilVoidTagNames.default[tagName];
if (!isEmpty) {
statements.push(['content', 'yield']);
}
var template = {
arity: 0,
cachedFragment: null,
hasRendered: false,
buildFragment: function buildFragment(dom) {
var el0 = dom.createDocumentFragment();
if (tagName === 'svg') {
dom.setNamespace(svgNamespace);
}
var el1 = dom.createElement(tagName);
for (var key in attributes) {
if (typeof attributes[key] !== 'string') {
continue;
}
dom.setAttribute(el1, key, attributes[key]);
}
if (!isEmpty) {
var el2 = dom.createComment("");
dom.appendChild(el1, el2);
}
dom.appendChild(el0, el1);
return el0;
},
buildRenderNodes: function buildRenderNodes(dom, fragment) {
var element = dom.childAt(fragment, [0]);
var morphs = [];
for (var key in attributes) {
if (typeof attributes[key] === 'string') {
continue;
}
morphs.push(dom.createAttrMorph(element, key));
}
if (!isEmpty) {
morphs.push(dom.createMorphAt(element, 0, 0));
}
return morphs;
},
statements: statements,
locals: [],
templates: []
};
return template;
}
function attachAttributes(attributes) {
var statements = [];
for (var key in attributes) {
if (typeof attributes[key] === 'string') {
continue;
}
statements.push(["attribute", key, attributes[key]]);
}
var template = {
arity: 0,
cachedFragment: null,
hasRendered: false,
buildFragment: function buildFragment(dom) {
var el0 = this.element;
if (el0.namespaceURI === "http://www.w3.org/2000/svg") {
dom.setNamespace(svgNamespace);
}
for (var key in attributes) {
if (typeof attributes[key] !== 'string') {
continue;
}
dom.setAttribute(el0, key, attributes[key]);
}
return el0;
},
buildRenderNodes: function buildRenderNodes(dom) {
var element = this.element;
var morphs = [];
for (var key in attributes) {
if (typeof attributes[key] === 'string') {
continue;
}
morphs.push(dom.createAttrMorph(element, key));
}
return morphs;
},
statements: statements,
locals: [],
templates: [],
element: null
};
return template;
}
RenderResult.prototype.initializeNodes = function (ownerNode) {
var childNodes = this.root.childNodes;
for (var i = 0, l = childNodes.length; i < l; i++) {
childNodes[i].ownerNode = ownerNode;
}
};
RenderResult.prototype.render = function () {
this.root.lastResult = this;
this.root.rendered = true;
this.populateNodes(_htmlbarsRuntimeNodeVisitor.AlwaysDirtyVisitor);
if (this.shouldSetContent && this.root.setContent) {
this.root.setContent(this.fragment);
}
};
RenderResult.prototype.dirty = function () {
_htmlbarsUtilMorphUtils.visitChildren([this.root], function (node) {
node.isDirty = true;
});
};
RenderResult.prototype.revalidate = function (env, self, blockArguments, scope) {
this.revalidateWith(env, scope, self, blockArguments, _htmlbarsRuntimeNodeVisitor.default);
};
RenderResult.prototype.rerender = function (env, self, blockArguments, scope) {
this.revalidateWith(env, scope, self, blockArguments, _htmlbarsRuntimeNodeVisitor.AlwaysDirtyVisitor);
};
RenderResult.prototype.revalidateWith = function (env, scope, self, blockArguments, visitor) {
if (env !== undefined) {
this.env = env;
}
if (scope !== undefined) {
this.scope = scope;
}
this.updateScope();
if (self !== undefined) {
this.updateSelf(self);
}
if (blockArguments !== undefined) {
this.updateLocals(blockArguments);
}
this.populateNodes(visitor);
};
RenderResult.prototype.destroy = function () {
var rootNode = this.root;
_htmlbarsUtilTemplateUtils.clearMorph(rootNode, this.env, true);
};
RenderResult.prototype.populateNodes = function (visitor) {
var env = this.env;
var scope = this.scope;
var template = this.template;
var nodes = this.nodes;
var statements = this.statements;
var i, l;
for (i = 0, l = statements.length; i < l; i++) {
var statement = statements[i];
var morph = nodes[i];
if (env.hooks.willRenderNode) {
env.hooks.willRenderNode(morph, env, scope);
}
switch (statement[0]) {
case 'block':
visitor.block(statement, morph, env, scope, template, visitor);break;
case 'inline':
visitor.inline(statement, morph, env, scope, visitor);break;
case 'content':
visitor.content(statement, morph, env, scope, visitor);break;
case 'element':
visitor.element(statement, morph, env, scope, template, visitor);break;
case 'attribute':
visitor.attribute(statement, morph, env, scope);break;
case 'component':
visitor.component(statement, morph, env, scope, template, visitor);break;
}
if (env.hooks.didRenderNode) {
env.hooks.didRenderNode(morph, env, scope);
}
}
};
RenderResult.prototype.bindScope = function () {
this.env.hooks.bindScope(this.env, this.scope);
};
RenderResult.prototype.updateScope = function () {
this.env.hooks.updateScope(this.env, this.scope);
};
RenderResult.prototype.bindSelf = function (self) {
this.env.hooks.bindSelf(this.env, this.scope, self);
};
RenderResult.prototype.updateSelf = function (self) {
this.env.hooks.updateSelf(this.env, this.scope, self);
};
RenderResult.prototype.bindLocals = function (blockArguments) {
var localNames = this.template.locals;
for (var i = 0, l = localNames.length; i < l; i++) {
this.env.hooks.bindLocal(this.env, this.scope, localNames[i], blockArguments[i]);
}
};
RenderResult.prototype.updateLocals = function (blockArguments) {
var localNames = this.template.locals;
for (var i = 0, l = localNames.length; i < l; i++) {
this.env.hooks.updateLocal(this.env, this.scope, localNames[i], blockArguments[i]);
}
};
function initializeNode(node, owner) {
node.ownerNode = owner;
}
function createChildMorph(dom, parentMorph, contextualElement) {
var morph = _htmlbarsRuntimeMorph.default.empty(dom, contextualElement || parentMorph.contextualElement);
initializeNode(morph, parentMorph.ownerNode);
return morph;
}
function getCachedFragment(template, env) {
var dom = env.dom,
fragment;
if (env.useFragmentCache && dom.canClone) {
if (template.cachedFragment === null) {
fragment = template.buildFragment(dom);
if (template.hasRendered) {
template.cachedFragment = fragment;
} else {
template.hasRendered = true;
}
}
if (template.cachedFragment) {
fragment = dom.cloneNode(template.cachedFragment, true);
}
} else if (!fragment) {
fragment = template.buildFragment(dom);
}
return fragment;
}
});
enifed('htmlbars-runtime', ['exports', 'htmlbars-runtime/hooks', 'htmlbars-runtime/render', 'htmlbars-util/morph-utils', 'htmlbars-util/template-utils'], function (exports, _htmlbarsRuntimeHooks, _htmlbarsRuntimeRender, _htmlbarsUtilMorphUtils, _htmlbarsUtilTemplateUtils) {
'use strict';
var internal = {
blockFor: _htmlbarsUtilTemplateUtils.blockFor,
manualElement: _htmlbarsRuntimeRender.manualElement,
hostBlock: _htmlbarsRuntimeHooks.hostBlock,
continueBlock: _htmlbarsRuntimeHooks.continueBlock,
hostYieldWithShadowTemplate: _htmlbarsRuntimeHooks.hostYieldWithShadowTemplate,
visitChildren: _htmlbarsUtilMorphUtils.visitChildren,
validateChildMorphs: _htmlbarsUtilMorphUtils.validateChildMorphs,
clearMorph: _htmlbarsUtilTemplateUtils.clearMorph
};
exports.hooks = _htmlbarsRuntimeHooks.default;
exports.render = _htmlbarsRuntimeRender.default;
exports.internal = internal;
});
enifed('htmlbars-util/array-utils', ['exports'], function (exports) {
'use strict';
exports.forEach = forEach;
exports.map = map;
function forEach(array, callback, binding) {
var i, l;
if (binding === undefined) {
for (i = 0, l = array.length; i < l; i++) {
callback(array[i], i, array);
}
} else {
for (i = 0, l = array.length; i < l; i++) {
callback.call(binding, array[i], i, array);
}
}
}
function map(array, callback) {
var output = [];
var i, l;
for (i = 0, l = array.length; i < l; i++) {
output.push(callback(array[i], i, array));
}
return output;
}
var getIdx;
if (Array.prototype.indexOf) {
getIdx = function (array, obj, from) {
return array.indexOf(obj, from);
};
} else {
getIdx = function (array, obj, from) {
if (from === undefined || from === null) {
from = 0;
} else if (from < 0) {
from = Math.max(0, array.length + from);
}
for (var i = from, l = array.length; i < l; i++) {
if (array[i] === obj) {
return i;
}
}
return -1;
};
}
var isArray = Array.isArray || function (array) {
return Object.prototype.toString.call(array) === '[object Array]';
};
exports.isArray = isArray;
var indexOfArray = getIdx;
exports.indexOfArray = indexOfArray;
});
enifed('htmlbars-util/handlebars/safe-string', ['exports'], function (exports) {
// Build out our basic SafeString type
'use strict';
function SafeString(string) {
this.string = string;
}
SafeString.prototype.toString = SafeString.prototype.toHTML = function () {
return '' + this.string;
};
exports.default = SafeString;
});
enifed('htmlbars-util/handlebars/utils', ['exports'], function (exports) {
'use strict';
exports.extend = extend;
exports.indexOf = indexOf;
exports.escapeExpression = escapeExpression;
exports.isEmpty = isEmpty;
exports.blockParams = blockParams;
exports.appendContextPath = appendContextPath;
var escape = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'`': '`'
};
var badChars = /[&<>"'`]/g,
possible = /[&<>"'`]/;
function escapeChar(chr) {
return escape[chr];
}
function extend(obj /* , ...source */) {
for (var i = 1; i < arguments.length; i++) {
for (var key in arguments[i]) {
if (Object.prototype.hasOwnProperty.call(arguments[i], key)) {
obj[key] = arguments[i][key];
}
}
}
return obj;
}
var toString = Object.prototype.toString;
exports.toString = toString;
// Sourced from lodash
// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
/*eslint-disable func-style, no-var */
var isFunction = function (value) {
return typeof value === 'function';
};
// fallback for older versions of Chrome and Safari
/* istanbul ignore next */
if (isFunction(/x/)) {
exports.isFunction = isFunction = function (value) {
return typeof value === 'function' && toString.call(value) === '[object Function]';
};
}
var isFunction;
exports.isFunction = isFunction;
/*eslint-enable func-style, no-var */
/* istanbul ignore next */
var isArray = Array.isArray || function (value) {
return value && typeof value === 'object' ? toString.call(value) === '[object Array]' : false;
};
exports.isArray = isArray;
// Older IE versions do not directly support indexOf so we must implement our own, sadly.
function indexOf(array, value) {
for (var i = 0, len = array.length; i < len; i++) {
if (array[i] === value) {
return i;
}
}
return -1;
}
function escapeExpression(string) {
if (typeof string !== 'string') {
// don't escape SafeStrings, since they're already safe
if (string && string.toHTML) {
return string.toHTML();
} else if (string == null) {
return '';
} else if (!string) {
return string + '';
}
// Force a string conversion as this will be done by the append regardless and
// the regex test will do this transparently behind the scenes, causing issues if
// an object's to string has escaped characters in it.
string = '' + string;
}
if (!possible.test(string)) {
return string;
}
return string.replace(badChars, escapeChar);
}
function isEmpty(value) {
if (!value && value !== 0) {
return true;
} else if (isArray(value) && value.length === 0) {
return true;
} else {
return false;
}
}
function blockParams(params, ids) {
params.path = ids;
return params;
}
function appendContextPath(contextPath, id) {
return (contextPath ? contextPath + '.' : '') + id;
}
});
enifed("htmlbars-util/morph-utils", ["exports"], function (exports) {
/*globals console*/
"use strict";
exports.visitChildren = visitChildren;
exports.validateChildMorphs = validateChildMorphs;
exports.linkParams = linkParams;
exports.dump = dump;
function visitChildren(nodes, callback) {
if (!nodes || nodes.length === 0) {
return;
}
nodes = nodes.slice();
while (nodes.length) {
var node = nodes.pop();
callback(node);
if (node.childNodes) {
nodes.push.apply(nodes, node.childNodes);
} else if (node.firstChildMorph) {
var current = node.firstChildMorph;
while (current) {
nodes.push(current);
current = current.nextMorph;
}
} else if (node.morphList) {
var current = node.morphList.firstChildMorph;
while (current) {
nodes.push(current);
current = current.nextMorph;
}
}
}
}
function validateChildMorphs(env, morph, visitor) {
var morphList = morph.morphList;
if (morph.morphList) {
var current = morphList.firstChildMorph;
while (current) {
var next = current.nextMorph;
validateChildMorphs(env, current, visitor);
current = next;
}
} else if (morph.lastResult) {
morph.lastResult.revalidateWith(env, undefined, undefined, undefined, visitor);
} else if (morph.childNodes) {
// This means that the childNodes were wired up manually
for (var i = 0, l = morph.childNodes.length; i < l; i++) {
validateChildMorphs(env, morph.childNodes[i], visitor);
}
}
}
function linkParams(env, scope, morph, path, params, hash) {
if (morph.linkedParams) {
return;
}
if (env.hooks.linkRenderNode(morph, env, scope, path, params, hash)) {
morph.linkedParams = { params: params, hash: hash };
}
}
function dump(node) {
console.group(node, node.isDirty);
if (node.childNodes) {
map(node.childNodes, dump);
} else if (node.firstChildMorph) {
var current = node.firstChildMorph;
while (current) {
dump(current);
current = current.nextMorph;
}
} else if (node.morphList) {
dump(node.morphList);
}
console.groupEnd();
}
function map(nodes, cb) {
for (var i = 0, l = nodes.length; i < l; i++) {
cb(nodes[i]);
}
}
});
enifed('htmlbars-util/namespaces', ['exports'], function (exports) {
// ref http://dev.w3.org/html5/spec-LC/namespaces.html
'use strict';
exports.getAttrNamespace = getAttrNamespace;
var defaultNamespaces = {
html: 'http://www.w3.org/1999/xhtml',
mathml: 'http://www.w3.org/1998/Math/MathML',
svg: 'http://www.w3.org/2000/svg',
xlink: 'http://www.w3.org/1999/xlink',
xml: 'http://www.w3.org/XML/1998/namespace'
};
function getAttrNamespace(attrName, detectedNamespace) {
if (detectedNamespace) {
return detectedNamespace;
}
var namespace;
var colonIndex = attrName.indexOf(':');
if (colonIndex !== -1) {
var prefix = attrName.slice(0, colonIndex);
namespace = defaultNamespaces[prefix];
}
return namespace || null;
}
});
enifed("htmlbars-util/object-utils", ["exports"], function (exports) {
"use strict";
exports.merge = merge;
exports.shallowCopy = shallowCopy;
exports.keySet = keySet;
exports.keyLength = keyLength;
function merge(options, defaults) {
for (var prop in defaults) {
if (options.hasOwnProperty(prop)) {
continue;
}
options[prop] = defaults[prop];
}
return options;
}
function shallowCopy(obj) {
return merge({}, obj);
}
function keySet(obj) {
var set = {};
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
set[prop] = true;
}
}
return set;
}
function keyLength(obj) {
var count = 0;
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
count++;
}
}
return count;
}
});
enifed("htmlbars-util/quoting", ["exports"], function (exports) {
"use strict";
exports.hash = hash;
exports.repeat = repeat;
function escapeString(str) {
str = str.replace(/\\/g, "\\\\");
str = str.replace(/"/g, '\\"');
str = str.replace(/\n/g, "\\n");
return str;
}
exports.escapeString = escapeString;
function string(str) {
return '"' + escapeString(str) + '"';
}
exports.string = string;
function array(a) {
return "[" + a + "]";
}
exports.array = array;
function hash(pairs) {
return "{" + pairs.join(", ") + "}";
}
function repeat(chars, times) {
var str = "";
while (times--) {
str += chars;
}
return str;
}
});
enifed('htmlbars-util/safe-string', ['exports', 'htmlbars-util/handlebars/safe-string'], function (exports, _htmlbarsUtilHandlebarsSafeString) {
'use strict';
exports.default = _htmlbarsUtilHandlebarsSafeString.default;
});
enifed("htmlbars-util/template-utils", ["exports", "htmlbars-util/morph-utils", "htmlbars-runtime/render"], function (exports, _htmlbarsUtilMorphUtils, _htmlbarsRuntimeRender) {
"use strict";
exports.RenderState = RenderState;
exports.blockFor = blockFor;
exports.renderAndCleanup = renderAndCleanup;
exports.clearMorph = clearMorph;
exports.clearMorphList = clearMorphList;
function RenderState(renderNode, morphList) {
// The morph list that is no longer needed and can be
// destroyed.
this.morphListToClear = morphList;
// The morph list that needs to be pruned of any items
// that were not yielded on a subsequent render.
this.morphListToPrune = null;
// A map of morphs for each item yielded in during this
// rendering pass. Any morphs in the DOM but not in this map
// will be pruned during cleanup.
this.handledMorphs = {};
this.collisions = undefined;
// The morph to clear once rendering is complete. By
// default, we set this to the previous morph (to catch
// the case where nothing is yielded; in that case, we
// should just clear the morph). Otherwise this gets set
// to null if anything is rendered.
this.morphToClear = renderNode;
this.shadowOptions = null;
}
function Block(render, template, blockOptions) {
this.render = render;
this.template = template;
this.blockOptions = blockOptions;
this.arity = template.arity;
}
Block.prototype.invoke = function (env, blockArguments, _self, renderNode, parentScope, visitor) {
if (renderNode.lastResult) {
renderNode.lastResult.revalidateWith(env, undefined, _self, blockArguments, visitor);
} else {
this._firstRender(env, blockArguments, _self, renderNode, parentScope);
}
};
Block.prototype._firstRender = function (env, blockArguments, _self, renderNode, parentScope) {
var options = { renderState: new RenderState(renderNode) };
var render = this.render;
var template = this.template;
var scope = this.blockOptions.scope;
var shadowScope = scope ? env.hooks.createChildScope(scope) : env.hooks.createFreshScope();
env.hooks.bindShadowScope(env, parentScope, shadowScope, this.blockOptions.options);
if (_self !== undefined) {
env.hooks.bindSelf(env, shadowScope, _self);
} else if (this.blockOptions.self !== undefined) {
env.hooks.bindSelf(env, shadowScope, this.blockOptions.self);
}
bindBlocks(env, shadowScope, this.blockOptions.yieldTo);
renderAndCleanup(renderNode, env, options, null, function () {
options.renderState.morphToClear = null;
var renderOptions = new _htmlbarsRuntimeRender.RenderOptions(renderNode, undefined, blockArguments);
render(template, env, shadowScope, renderOptions);
});
};
function blockFor(render, template, blockOptions) {
return new Block(render, template, blockOptions);
}
function bindBlocks(env, shadowScope, blocks) {
if (!blocks) {
return;
}
if (blocks instanceof Block) {
env.hooks.bindBlock(env, shadowScope, blocks);
} else {
for (var name in blocks) {
if (blocks.hasOwnProperty(name)) {
env.hooks.bindBlock(env, shadowScope, blocks[name], name);
}
}
}
}
function renderAndCleanup(morph, env, options, shadowOptions, callback) {
// The RenderState object is used to collect information about what the
// helper or hook being invoked has yielded. Once it has finished either
// yielding multiple items (via yieldItem) or a single template (via
// yieldTemplate), we detect what was rendered and how it differs from
// the previous render, cleaning up old state in DOM as appropriate.
var renderState = options.renderState;
renderState.collisions = undefined;
renderState.shadowOptions = shadowOptions;
// Invoke the callback, instructing it to save information about what it
// renders into RenderState.
var result = callback(options);
// The hook can opt-out of cleanup if it handled cleanup itself.
if (result && result.handled) {
return;
}
var morphMap = morph.morphMap;
// Walk the morph list, clearing any items that were yielded in a previous
// render but were not yielded during this render.
var morphList = renderState.morphListToPrune;
if (morphList) {
var handledMorphs = renderState.handledMorphs;
var item = morphList.firstChildMorph;
while (item) {
var next = item.nextMorph;
// If we don't see the key in handledMorphs, it wasn't
// yielded in and we can safely remove it from DOM.
if (!(item.key in handledMorphs)) {
morphMap[item.key] = undefined;
clearMorph(item, env, true);
item.destroy();
}
item = next;
}
}
morphList = renderState.morphListToClear;
if (morphList) {
clearMorphList(morphList, morph, env);
}
var toClear = renderState.morphToClear;
if (toClear) {
clearMorph(toClear, env);
}
}
function clearMorph(morph, env, destroySelf) {
var cleanup = env.hooks.cleanupRenderNode;
var destroy = env.hooks.destroyRenderNode;
var willCleanup = env.hooks.willCleanupTree;
var didCleanup = env.hooks.didCleanupTree;
function destroyNode(node) {
if (cleanup) {
cleanup(node);
}
if (destroy) {
destroy(node);
}
}
if (willCleanup) {
willCleanup(env, morph, destroySelf);
}
if (cleanup) {
cleanup(morph);
}
if (destroySelf && destroy) {
destroy(morph);
}
_htmlbarsUtilMorphUtils.visitChildren(morph.childNodes, destroyNode);
// TODO: Deal with logical children that are not in the DOM tree
morph.clear();
if (didCleanup) {
didCleanup(env, morph, destroySelf);
}
morph.lastResult = null;
morph.lastYielded = null;
morph.childNodes = null;
}
function clearMorphList(morphList, morph, env) {
var item = morphList.firstChildMorph;
while (item) {
var next = item.nextMorph;
morph.morphMap[item.key] = undefined;
clearMorph(item, env, true);
item.destroy();
item = next;
}
// Remove the MorphList from the morph.
morphList.clear();
morph.morphList = null;
}
});
enifed("htmlbars-util/void-tag-names", ["exports", "htmlbars-util/array-utils"], function (exports, _htmlbarsUtilArrayUtils) {
"use strict";
// The HTML elements in this list are speced by
// http://www.w3.org/TR/html-markup/syntax.html#syntax-elements,
// and will be forced to close regardless of if they have a
// self-closing /> at the end.
var voidTagNames = "area base br col command embed hr img input keygen link meta param source track wbr";
var voidMap = {};
_htmlbarsUtilArrayUtils.forEach(voidTagNames.split(" "), function (tagName) {
voidMap[tagName] = true;
});
exports.default = voidMap;
});
enifed('htmlbars-util', ['exports', 'htmlbars-util/safe-string', 'htmlbars-util/handlebars/utils', 'htmlbars-util/namespaces', 'htmlbars-util/morph-utils'], function (exports, _htmlbarsUtilSafeString, _htmlbarsUtilHandlebarsUtils, _htmlbarsUtilNamespaces, _htmlbarsUtilMorphUtils) {
'use strict';
exports.SafeString = _htmlbarsUtilSafeString.default;
exports.escapeExpression = _htmlbarsUtilHandlebarsUtils.escapeExpression;
exports.getAttrNamespace = _htmlbarsUtilNamespaces.getAttrNamespace;
exports.validateChildMorphs = _htmlbarsUtilMorphUtils.validateChildMorphs;
exports.linkParams = _htmlbarsUtilMorphUtils.linkParams;
exports.dump = _htmlbarsUtilMorphUtils.dump;
});
enifed('morph-attr/sanitize-attribute-value', ['exports'], function (exports) {
/* jshint scripturl:true */
'use strict';
exports.sanitizeAttributeValue = sanitizeAttributeValue;
var badProtocols = {
'javascript:': true,
'vbscript:': true
};
var badTags = {
'A': true,
'BODY': true,
'LINK': true,
'IMG': true,
'IFRAME': true,
'BASE': true,
'FORM': true
};
var badTagsForDataURI = {
'EMBED': true
};
var badAttributes = {
'href': true,
'src': true,
'background': true,
'action': true
};
exports.badAttributes = badAttributes;
var badAttributesForDataURI = {
'src': true
};
function sanitizeAttributeValue(dom, element, attribute, value) {
var tagName;
if (!element) {
tagName = null;
} else {
tagName = element.tagName.toUpperCase();
}
if (value && value.toHTML) {
return value.toHTML();
}
if ((tagName === null || badTags[tagName]) && badAttributes[attribute]) {
var protocol = dom.protocolForURL(value);
if (badProtocols[protocol] === true) {
return 'unsafe:' + value;
}
}
if (badTagsForDataURI[tagName] && badAttributesForDataURI[attribute]) {
return 'unsafe:' + value;
}
return value;
}
});
enifed("morph-attr", ["exports", "morph-attr/sanitize-attribute-value", "dom-helper/prop", "dom-helper/build-html-dom", "htmlbars-util"], function (exports, _morphAttrSanitizeAttributeValue, _domHelperProp, _domHelperBuildHtmlDom, _htmlbarsUtil) {
"use strict";
function getProperty() {
return this.domHelper.getPropertyStrict(this.element, this.attrName);
}
function updateProperty(value) {
if (this._renderedInitially === true || !_domHelperProp.isAttrRemovalValue(value)) {
var element = this.element;
var attrName = this.attrName;
if (attrName === 'value' && element.tagName === 'INPUT' && element.value === value) {
// Do nothing. Attempts to avoid accidently changing the input cursor location.
// See https://github.com/tildeio/htmlbars/pull/447 for more details.
} else {
// do not render if initial value is undefined or null
this.domHelper.setPropertyStrict(element, attrName, value);
}
}
this._renderedInitially = true;
}
function getAttribute() {
return this.domHelper.getAttribute(this.element, this.attrName);
}
function updateAttribute(value) {
if (_domHelperProp.isAttrRemovalValue(value)) {
this.domHelper.removeAttribute(this.element, this.attrName);
} else {
this.domHelper.setAttribute(this.element, this.attrName, value);
}
}
function getAttributeNS() {
return this.domHelper.getAttributeNS(this.element, this.namespace, this.attrName);
}
function updateAttributeNS(value) {
if (_domHelperProp.isAttrRemovalValue(value)) {
this.domHelper.removeAttribute(this.element, this.attrName);
} else {
this.domHelper.setAttributeNS(this.element, this.namespace, this.attrName, value);
}
}
var UNSET = { unset: true };
var guid = 1;
AttrMorph.create = function (element, attrName, domHelper, namespace) {
var ns = _htmlbarsUtil.getAttrNamespace(attrName, namespace);
if (ns) {
return new AttributeNSAttrMorph(element, attrName, domHelper, ns);
} else {
return createNonNamespacedAttrMorph(element, attrName, domHelper);
}
};
function createNonNamespacedAttrMorph(element, attrName, domHelper) {
var _normalizeProperty = _domHelperProp.normalizeProperty(element, attrName);
var normalized = _normalizeProperty.normalized;
var type = _normalizeProperty.type;
if (element.namespaceURI === _domHelperBuildHtmlDom.svgNamespace || attrName === 'style' || type === 'attr') {
return new AttributeAttrMorph(element, normalized, domHelper);
} else {
return new PropertyAttrMorph(element, normalized, domHelper);
}
}
function AttrMorph(element, attrName, domHelper) {
this.element = element;
this.domHelper = domHelper;
this.attrName = attrName;
this._state = undefined;
this.isDirty = false;
this.isSubtreeDirty = false;
this.escaped = true;
this.lastValue = UNSET;
this.lastResult = null;
this.lastYielded = null;
this.childNodes = null;
this.linkedParams = null;
this.linkedResult = null;
this.guid = "attr" + guid++;
this.seen = false;
this.ownerNode = null;
this.rendered = false;
this._renderedInitially = false;
this.namespace = undefined;
this.didInit();
}
AttrMorph.prototype.getState = function () {
if (!this._state) {
this._state = {};
}
return this._state;
};
AttrMorph.prototype.setState = function (newState) {
/*jshint -W093 */
return this._state = newState;
};
AttrMorph.prototype.didInit = function () {};
AttrMorph.prototype.willSetContent = function () {};
AttrMorph.prototype.setContent = function (value) {
this.willSetContent(value);
if (this.lastValue === value) {
return;
}
this.lastValue = value;
if (this.escaped) {
var sanitized = _morphAttrSanitizeAttributeValue.sanitizeAttributeValue(this.domHelper, this.element, this.attrName, value);
this._update(sanitized, this.namespace);
} else {
this._update(value, this.namespace);
}
};
AttrMorph.prototype.getContent = function () {
var value = this.lastValue = this._get();
return value;
};
// renderAndCleanup calls `clear` on all items in the morph map
// just before calling `destroy` on the morph.
//
// As a future refactor this could be changed to set the property
// back to its original/default value.
AttrMorph.prototype.clear = function () {};
AttrMorph.prototype.destroy = function () {
this.element = null;
this.domHelper = null;
};
AttrMorph.prototype._$superAttrMorph = AttrMorph;
function PropertyAttrMorph(element, attrName, domHelper) {
this._$superAttrMorph(element, attrName, domHelper);
}
PropertyAttrMorph.prototype = Object.create(AttrMorph.prototype);
PropertyAttrMorph.prototype._update = updateProperty;
PropertyAttrMorph.prototype._get = getProperty;
function AttributeNSAttrMorph(element, attrName, domHelper, namespace) {
this._$superAttrMorph(element, attrName, domHelper);
this.namespace = namespace;
}
AttributeNSAttrMorph.prototype = Object.create(AttrMorph.prototype);
AttributeNSAttrMorph.prototype._update = updateAttributeNS;
AttributeNSAttrMorph.prototype._get = getAttributeNS;
function AttributeAttrMorph(element, attrName, domHelper) {
this._$superAttrMorph(element, attrName, domHelper);
}
AttributeAttrMorph.prototype = Object.create(AttrMorph.prototype);
AttributeAttrMorph.prototype._update = updateAttribute;
AttributeAttrMorph.prototype._get = getAttribute;
exports.default = AttrMorph;
exports.sanitizeAttributeValue = _morphAttrSanitizeAttributeValue.sanitizeAttributeValue;
});
enifed('morph-range/morph-list', ['exports', 'morph-range/utils'], function (exports, _morphRangeUtils) {
'use strict';
function MorphList() {
// morph graph
this.firstChildMorph = null;
this.lastChildMorph = null;
this.mountedMorph = null;
}
var prototype = MorphList.prototype;
prototype.clear = function MorphList$clear() {
var current = this.firstChildMorph;
while (current) {
var next = current.nextMorph;
current.previousMorph = null;
current.nextMorph = null;
current.parentMorphList = null;
current = next;
}
this.firstChildMorph = this.lastChildMorph = null;
};
prototype.destroy = function MorphList$destroy() {};
prototype.appendMorph = function MorphList$appendMorph(morph) {
this.insertBeforeMorph(morph, null);
};
prototype.insertBeforeMorph = function MorphList$insertBeforeMorph(morph, referenceMorph) {
if (morph.parentMorphList !== null) {
morph.unlink();
}
if (referenceMorph && referenceMorph.parentMorphList !== this) {
throw new Error('The morph before which the new morph is to be inserted is not a child of this morph.');
}
var mountedMorph = this.mountedMorph;
if (mountedMorph) {
var parentNode = mountedMorph.firstNode.parentNode;
var referenceNode = referenceMorph ? referenceMorph.firstNode : mountedMorph.lastNode.nextSibling;
_morphRangeUtils.insertBefore(parentNode, morph.firstNode, morph.lastNode, referenceNode);
// was not in list mode replace current content
if (!this.firstChildMorph) {
_morphRangeUtils.clear(this.mountedMorph.firstNode.parentNode, this.mountedMorph.firstNode, this.mountedMorph.lastNode);
}
}
morph.parentMorphList = this;
var previousMorph = referenceMorph ? referenceMorph.previousMorph : this.lastChildMorph;
if (previousMorph) {
previousMorph.nextMorph = morph;
morph.previousMorph = previousMorph;
} else {
this.firstChildMorph = morph;
}
if (referenceMorph) {
referenceMorph.previousMorph = morph;
morph.nextMorph = referenceMorph;
} else {
this.lastChildMorph = morph;
}
this.firstChildMorph._syncFirstNode();
this.lastChildMorph._syncLastNode();
};
prototype.removeChildMorph = function MorphList$removeChildMorph(morph) {
if (morph.parentMorphList !== this) {
throw new Error("Cannot remove a morph from a parent it is not inside of");
}
morph.destroy();
};
exports.default = MorphList;
});
enifed('morph-range/morph-list.umd', ['exports', 'morph-range/morph-list'], function (exports, _morphRangeMorphList) {
'use strict';
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define([], factory);
} else if (typeof exports === 'object') {
module.exports = factory();
} else {
root.MorphList = factory();
}
})(undefined, function () {
return _morphRangeMorphList.default;
});
});
enifed("morph-range/utils", ["exports"], function (exports) {
// inclusive of both nodes
"use strict";
exports.clear = clear;
exports.insertBefore = insertBefore;
function clear(parentNode, firstNode, lastNode) {
if (!parentNode) {
return;
}
var node = firstNode;
var nextNode;
do {
nextNode = node.nextSibling;
parentNode.removeChild(node);
if (node === lastNode) {
break;
}
node = nextNode;
} while (node);
}
function insertBefore(parentNode, firstNode, lastNode, refNode) {
var node = firstNode;
var nextNode;
do {
nextNode = node.nextSibling;
parentNode.insertBefore(node, refNode);
if (node === lastNode) {
break;
}
node = nextNode;
} while (node);
}
});
enifed('morph-range', ['exports', 'morph-range/utils'], function (exports, _morphRangeUtils) {
'use strict';
// constructor just initializes the fields
// use one of the static initializers to create a valid morph.
function Morph(domHelper, contextualElement) {
this.domHelper = domHelper;
// context if content if current content is detached
this.contextualElement = contextualElement;
// inclusive range of morph
// these should be nodeType 1, 3, or 8
this.firstNode = null;
this.lastNode = null;
// flag to force text to setContent to be treated as html
this.parseTextAsHTML = false;
// morph list graph
this.parentMorphList = null;
this.previousMorph = null;
this.nextMorph = null;
}
Morph.empty = function (domHelper, contextualElement) {
var morph = new Morph(domHelper, contextualElement);
morph.clear();
return morph;
};
Morph.create = function (domHelper, contextualElement, node) {
var morph = new Morph(domHelper, contextualElement);
morph.setNode(node);
return morph;
};
Morph.attach = function (domHelper, contextualElement, firstNode, lastNode) {
var morph = new Morph(domHelper, contextualElement);
morph.setRange(firstNode, lastNode);
return morph;
};
Morph.prototype.setContent = function Morph$setContent(content) {
if (content === null || content === undefined) {
return this.clear();
}
var type = typeof content;
switch (type) {
case 'string':
if (this.parseTextAsHTML) {
return this.domHelper.setMorphHTML(this, content);
}
return this.setText(content);
case 'object':
if (typeof content.nodeType === 'number') {
return this.setNode(content);
}
/* Handlebars.SafeString */
if (typeof content.toHTML === 'function') {
return this.setHTML(content.toHTML());
}
if (this.parseTextAsHTML) {
return this.setHTML(content.toString());
}
/* falls through */
case 'boolean':
case 'number':
return this.setText(content.toString());
case 'function':
raiseCannotBindToFunction(content);
default:
throw new TypeError('unsupported content');
}
};
function raiseCannotBindToFunction(content) {
var functionName = content.name;
var message;
if (functionName) {
message = 'Unsupported Content: Cannot bind to function `' + functionName + '`';
} else {
message = 'Unsupported Content: Cannot bind to function';
}
throw new TypeError(message);
}
Morph.prototype.clear = function Morph$clear() {
var node = this.setNode(this.domHelper.createComment(''));
return node;
};
Morph.prototype.setText = function Morph$setText(text) {
var firstNode = this.firstNode;
var lastNode = this.lastNode;
if (firstNode && lastNode === firstNode && firstNode.nodeType === 3) {
firstNode.nodeValue = text;
return firstNode;
}
return this.setNode(text ? this.domHelper.createTextNode(text) : this.domHelper.createComment(''));
};
Morph.prototype.setNode = function Morph$setNode(newNode) {
var firstNode, lastNode;
switch (newNode.nodeType) {
case 3:
firstNode = newNode;
lastNode = newNode;
break;
case 11:
firstNode = newNode.firstChild;
lastNode = newNode.lastChild;
if (firstNode === null) {
firstNode = this.domHelper.createComment('');
newNode.appendChild(firstNode);
lastNode = firstNode;
}
break;
default:
firstNode = newNode;
lastNode = newNode;
break;
}
this.setRange(firstNode, lastNode);
return newNode;
};
Morph.prototype.setRange = function (firstNode, lastNode) {
var previousFirstNode = this.firstNode;
if (previousFirstNode !== null) {
var parentNode = previousFirstNode.parentNode;
if (parentNode !== null) {
_morphRangeUtils.insertBefore(parentNode, firstNode, lastNode, previousFirstNode);
_morphRangeUtils.clear(parentNode, previousFirstNode, this.lastNode);
}
}
this.firstNode = firstNode;
this.lastNode = lastNode;
if (this.parentMorphList) {
this._syncFirstNode();
this._syncLastNode();
}
};
Morph.prototype.destroy = function Morph$destroy() {
this.unlink();
var firstNode = this.firstNode;
var lastNode = this.lastNode;
var parentNode = firstNode && firstNode.parentNode;
this.firstNode = null;
this.lastNode = null;
_morphRangeUtils.clear(parentNode, firstNode, lastNode);
};
Morph.prototype.unlink = function Morph$unlink() {
var parentMorphList = this.parentMorphList;
var previousMorph = this.previousMorph;
var nextMorph = this.nextMorph;
if (previousMorph) {
if (nextMorph) {
previousMorph.nextMorph = nextMorph;
nextMorph.previousMorph = previousMorph;
} else {
previousMorph.nextMorph = null;
parentMorphList.lastChildMorph = previousMorph;
}
} else {
if (nextMorph) {
nextMorph.previousMorph = null;
parentMorphList.firstChildMorph = nextMorph;
} else if (parentMorphList) {
parentMorphList.lastChildMorph = parentMorphList.firstChildMorph = null;
}
}
this.parentMorphList = null;
this.nextMorph = null;
this.previousMorph = null;
if (parentMorphList && parentMorphList.mountedMorph) {
if (!parentMorphList.firstChildMorph) {
// list is empty
parentMorphList.mountedMorph.clear();
return;
} else {
parentMorphList.firstChildMorph._syncFirstNode();
parentMorphList.lastChildMorph._syncLastNode();
}
}
};
Morph.prototype.setHTML = function (text) {
var fragment = this.domHelper.parseHTML(text, this.contextualElement);
return this.setNode(fragment);
};
Morph.prototype.setMorphList = function Morph$appendMorphList(morphList) {
morphList.mountedMorph = this;
this.clear();
var originalFirstNode = this.firstNode;
if (morphList.firstChildMorph) {
this.firstNode = morphList.firstChildMorph.firstNode;
this.lastNode = morphList.lastChildMorph.lastNode;
var current = morphList.firstChildMorph;
while (current) {
var next = current.nextMorph;
current.insertBeforeNode(originalFirstNode, null);
current = next;
}
originalFirstNode.parentNode.removeChild(originalFirstNode);
}
};
Morph.prototype._syncFirstNode = function Morph$syncFirstNode() {
var morph = this;
var parentMorphList;
while (parentMorphList = morph.parentMorphList) {
if (parentMorphList.mountedMorph === null) {
break;
}
if (morph !== parentMorphList.firstChildMorph) {
break;
}
if (morph.firstNode === parentMorphList.mountedMorph.firstNode) {
break;
}
parentMorphList.mountedMorph.firstNode = morph.firstNode;
morph = parentMorphList.mountedMorph;
}
};
Morph.prototype._syncLastNode = function Morph$syncLastNode() {
var morph = this;
var parentMorphList;
while (parentMorphList = morph.parentMorphList) {
if (parentMorphList.mountedMorph === null) {
break;
}
if (morph !== parentMorphList.lastChildMorph) {
break;
}
if (morph.lastNode === parentMorphList.mountedMorph.lastNode) {
break;
}
parentMorphList.mountedMorph.lastNode = morph.lastNode;
morph = parentMorphList.mountedMorph;
}
};
Morph.prototype.insertBeforeNode = function Morph$insertBeforeNode(parentNode, refNode) {
_morphRangeUtils.insertBefore(parentNode, this.firstNode, this.lastNode, refNode);
};
Morph.prototype.appendToNode = function Morph$appendToNode(parentNode) {
_morphRangeUtils.insertBefore(parentNode, this.firstNode, this.lastNode, null);
};
exports.default = Morph;
});
enifed("route-recognizer/dsl", ["exports"], function (exports) {
"use strict";
function Target(path, matcher, delegate) {
this.path = path;
this.matcher = matcher;
this.delegate = delegate;
}
Target.prototype = {
to: function (target, callback) {
var delegate = this.delegate;
if (delegate && delegate.willAddRoute) {
target = delegate.willAddRoute(this.matcher.target, target);
}
this.matcher.add(this.path, target);
if (callback) {
if (callback.length === 0) {
throw new Error("You must have an argument in the function passed to `to`");
}
this.matcher.addChild(this.path, target, callback, this.delegate);
}
return this;
}
};
function Matcher(target) {
this.routes = {};
this.children = {};
this.target = target;
}
Matcher.prototype = {
add: function (path, handler) {
this.routes[path] = handler;
},
addChild: function (path, target, callback, delegate) {
var matcher = new Matcher(target);
this.children[path] = matcher;
var match = generateMatch(path, matcher, delegate);
if (delegate && delegate.contextEntered) {
delegate.contextEntered(target, match);
}
callback(match);
}
};
function generateMatch(startingPath, matcher, delegate) {
return function (path, nestedCallback) {
var fullPath = startingPath + path;
if (nestedCallback) {
nestedCallback(generateMatch(fullPath, matcher, delegate));
} else {
return new Target(startingPath + path, matcher, delegate);
}
};
}
function addRoute(routeArray, path, handler) {
var len = 0;
for (var i = 0, l = routeArray.length; i < l; i++) {
len += routeArray[i].path.length;
}
path = path.substr(len);
var route = { path: path, handler: handler };
routeArray.push(route);
}
function eachRoute(baseRoute, matcher, callback, binding) {
var routes = matcher.routes;
for (var path in routes) {
if (routes.hasOwnProperty(path)) {
var routeArray = baseRoute.slice();
addRoute(routeArray, path, routes[path]);
if (matcher.children[path]) {
eachRoute(routeArray, matcher.children[path], callback, binding);
} else {
callback.call(binding, routeArray);
}
}
}
}
exports.default = function (callback, addRouteCallback) {
var matcher = new Matcher();
callback(generateMatch("", matcher, this.delegate));
eachRoute([], matcher, function (route) {
if (addRouteCallback) {
addRouteCallback(this, route);
} else {
this.add(route);
}
}, this);
};
});
enifed('route-recognizer', ['exports', 'route-recognizer/dsl'], function (exports, _routeRecognizerDsl) {
'use strict';
var specials = ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\'];
var escapeRegex = new RegExp('(\\' + specials.join('|\\') + ')', 'g');
function isArray(test) {
return Object.prototype.toString.call(test) === "[object Array]";
}
// A Segment represents a segment in the original route description.
// Each Segment type provides an `eachChar` and `regex` method.
//
// The `eachChar` method invokes the callback with one or more character
// specifications. A character specification consumes one or more input
// characters.
//
// The `regex` method returns a regex fragment for the segment. If the
// segment is a dynamic of star segment, the regex fragment also includes
// a capture.
//
// A character specification contains:
//
// * `validChars`: a String with a list of all valid characters, or
// * `invalidChars`: a String with a list of all invalid characters
// * `repeat`: true if the character specification can repeat
function StaticSegment(string) {
this.string = string;
}
StaticSegment.prototype = {
eachChar: function (callback) {
var string = this.string,
ch;
for (var i = 0, l = string.length; i < l; i++) {
ch = string.charAt(i);
callback({ validChars: ch });
}
},
regex: function () {
return this.string.replace(escapeRegex, '\\$1');
},
generate: function () {
return this.string;
}
};
function DynamicSegment(name) {
this.name = name;
}
DynamicSegment.prototype = {
eachChar: function (callback) {
callback({ invalidChars: "/", repeat: true });
},
regex: function () {
return "([^/]+)";
},
generate: function (params) {
return params[this.name];
}
};
function StarSegment(name) {
this.name = name;
}
StarSegment.prototype = {
eachChar: function (callback) {
callback({ invalidChars: "", repeat: true });
},
regex: function () {
return "(.+)";
},
generate: function (params) {
return params[this.name];
}
};
function EpsilonSegment() {}
EpsilonSegment.prototype = {
eachChar: function () {},
regex: function () {
return "";
},
generate: function () {
return "";
}
};
function parse(route, names, types) {
// normalize route as not starting with a "/". Recognition will
// also normalize.
if (route.charAt(0) === "/") {
route = route.substr(1);
}
var segments = route.split("/"),
results = [];
for (var i = 0, l = segments.length; i < l; i++) {
var segment = segments[i],
match;
if (match = segment.match(/^:([^\/]+)$/)) {
results.push(new DynamicSegment(match[1]));
names.push(match[1]);
types.dynamics++;
} else if (match = segment.match(/^\*([^\/]+)$/)) {
results.push(new StarSegment(match[1]));
names.push(match[1]);
types.stars++;
} else if (segment === "") {
results.push(new EpsilonSegment());
} else {
results.push(new StaticSegment(segment));
types.statics++;
}
}
return results;
}
// A State has a character specification and (`charSpec`) and a list of possible
// subsequent states (`nextStates`).
//
// If a State is an accepting state, it will also have several additional
// properties:
//
// * `regex`: A regular expression that is used to extract parameters from paths
// that reached this accepting state.
// * `handlers`: Information on how to convert the list of captures into calls
// to registered handlers with the specified parameters
// * `types`: How many static, dynamic or star segments in this route. Used to
// decide which route to use if multiple registered routes match a path.
//
// Currently, State is implemented naively by looping over `nextStates` and
// comparing a character specification against a character. A more efficient
// implementation would use a hash of keys pointing at one or more next states.
function State(charSpec) {
this.charSpec = charSpec;
this.nextStates = [];
}
State.prototype = {
get: function (charSpec) {
var nextStates = this.nextStates;
for (var i = 0, l = nextStates.length; i < l; i++) {
var child = nextStates[i];
var isEqual = child.charSpec.validChars === charSpec.validChars;
isEqual = isEqual && child.charSpec.invalidChars === charSpec.invalidChars;
if (isEqual) {
return child;
}
}
},
put: function (charSpec) {
var state;
// If the character specification already exists in a child of the current
// state, just return that state.
if (state = this.get(charSpec)) {
return state;
}
// Make a new state for the character spec
state = new State(charSpec);
// Insert the new state as a child of the current state
this.nextStates.push(state);
// If this character specification repeats, insert the new state as a child
// of itself. Note that this will not trigger an infinite loop because each
// transition during recognition consumes a character.
if (charSpec.repeat) {
state.nextStates.push(state);
}
// Return the new state
return state;
},
// Find a list of child states matching the next character
match: function (ch) {
// DEBUG "Processing `" + ch + "`:"
var nextStates = this.nextStates,
child,
charSpec,
chars;
// DEBUG " " + debugState(this)
var returned = [];
for (var i = 0, l = nextStates.length; i < l; i++) {
child = nextStates[i];
charSpec = child.charSpec;
if (typeof (chars = charSpec.validChars) !== 'undefined') {
if (chars.indexOf(ch) !== -1) {
returned.push(child);
}
} else if (typeof (chars = charSpec.invalidChars) !== 'undefined') {
if (chars.indexOf(ch) === -1) {
returned.push(child);
}
}
}
return returned;
}
/** IF DEBUG
, debug: function() {
var charSpec = this.charSpec,
debug = "[",
chars = charSpec.validChars || charSpec.invalidChars;
if (charSpec.invalidChars) { debug += "^"; }
debug += chars;
debug += "]";
if (charSpec.repeat) { debug += "+"; }
return debug;
}
END IF **/
};
/** IF DEBUG
function debug(log) {
console.log(log);
}
function debugState(state) {
return state.nextStates.map(function(n) {
if (n.nextStates.length === 0) { return "( " + n.debug() + " [accepting] )"; }
return "( " + n.debug() + " " + n.nextStates.map(function(s) { return s.debug() }).join(" or ") + " )";
}).join(", ")
}
END IF **/
// This is a somewhat naive strategy, but should work in a lot of cases
// A better strategy would properly resolve /posts/:id/new and /posts/edit/:id.
//
// This strategy generally prefers more static and less dynamic matching.
// Specifically, it
//
// * prefers fewer stars to more, then
// * prefers using stars for less of the match to more, then
// * prefers fewer dynamic segments to more, then
// * prefers more static segments to more
function sortSolutions(states) {
return states.sort(function (a, b) {
if (a.types.stars !== b.types.stars) {
return a.types.stars - b.types.stars;
}
if (a.types.stars) {
if (a.types.statics !== b.types.statics) {
return b.types.statics - a.types.statics;
}
if (a.types.dynamics !== b.types.dynamics) {
return b.types.dynamics - a.types.dynamics;
}
}
if (a.types.dynamics !== b.types.dynamics) {
return a.types.dynamics - b.types.dynamics;
}
if (a.types.statics !== b.types.statics) {
return b.types.statics - a.types.statics;
}
return 0;
});
}
function recognizeChar(states, ch) {
var nextStates = [];
for (var i = 0, l = states.length; i < l; i++) {
var state = states[i];
nextStates = nextStates.concat(state.match(ch));
}
return nextStates;
}
var oCreate = Object.create || function (proto) {
function F() {}
F.prototype = proto;
return new F();
};
function RecognizeResults(queryParams) {
this.queryParams = queryParams || {};
}
RecognizeResults.prototype = oCreate({
splice: Array.prototype.splice,
slice: Array.prototype.slice,
push: Array.prototype.push,
length: 0,
queryParams: null
});
function findHandler(state, path, queryParams) {
var handlers = state.handlers,
regex = state.regex;
var captures = path.match(regex),
currentCapture = 1;
var result = new RecognizeResults(queryParams);
for (var i = 0, l = handlers.length; i < l; i++) {
var handler = handlers[i],
names = handler.names,
params = {};
for (var j = 0, m = names.length; j < m; j++) {
params[names[j]] = captures[currentCapture++];
}
result.push({ handler: handler.handler, params: params, isDynamic: !!names.length });
}
return result;
}
function addSegment(currentState, segment) {
segment.eachChar(function (ch) {
var state;
currentState = currentState.put(ch);
});
return currentState;
}
function decodeQueryParamPart(part) {
// http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1
part = part.replace(/\+/gm, '%20');
return decodeURIComponent(part);
}
// The main interface
var RouteRecognizer = function () {
this.rootState = new State();
this.names = {};
};
RouteRecognizer.prototype = {
add: function (routes, options) {
var currentState = this.rootState,
regex = "^",
types = { statics: 0, dynamics: 0, stars: 0 },
handlers = [],
allSegments = [],
name;
var isEmpty = true;
for (var i = 0, l = routes.length; i < l; i++) {
var route = routes[i],
names = [];
var segments = parse(route.path, names, types);
allSegments = allSegments.concat(segments);
for (var j = 0, m = segments.length; j < m; j++) {
var segment = segments[j];
if (segment instanceof EpsilonSegment) {
continue;
}
isEmpty = false;
// Add a "/" for the new segment
currentState = currentState.put({ validChars: "/" });
regex += "/";
// Add a representation of the segment to the NFA and regex
currentState = addSegment(currentState, segment);
regex += segment.regex();
}
var handler = { handler: route.handler, names: names };
handlers.push(handler);
}
if (isEmpty) {
currentState = currentState.put({ validChars: "/" });
regex += "/";
}
currentState.handlers = handlers;
currentState.regex = new RegExp(regex + "$");
currentState.types = types;
if (name = options && options.as) {
this.names[name] = {
segments: allSegments,
handlers: handlers
};
}
},
handlersFor: function (name) {
var route = this.names[name],
result = [];
if (!route) {
throw new Error("There is no route named " + name);
}
for (var i = 0, l = route.handlers.length; i < l; i++) {
result.push(route.handlers[i]);
}
return result;
},
hasRoute: function (name) {
return !!this.names[name];
},
generate: function (name, params) {
var route = this.names[name],
output = "";
if (!route) {
throw new Error("There is no route named " + name);
}
var segments = route.segments;
for (var i = 0, l = segments.length; i < l; i++) {
var segment = segments[i];
if (segment instanceof EpsilonSegment) {
continue;
}
output += "/";
output += segment.generate(params);
}
if (output.charAt(0) !== '/') {
output = '/' + output;
}
if (params && params.queryParams) {
output += this.generateQueryString(params.queryParams, route.handlers);
}
return output;
},
generateQueryString: function (params, handlers) {
var pairs = [];
var keys = [];
for (var key in params) {
if (params.hasOwnProperty(key)) {
keys.push(key);
}
}
keys.sort();
for (var i = 0, len = keys.length; i < len; i++) {
key = keys[i];
var value = params[key];
if (value == null) {
continue;
}
var pair = encodeURIComponent(key);
if (isArray(value)) {
for (var j = 0, l = value.length; j < l; j++) {
var arrayPair = key + '[]' + '=' + encodeURIComponent(value[j]);
pairs.push(arrayPair);
}
} else {
pair += "=" + encodeURIComponent(value);
pairs.push(pair);
}
}
if (pairs.length === 0) {
return '';
}
return "?" + pairs.join("&");
},
parseQueryString: function (queryString) {
var pairs = queryString.split("&"),
queryParams = {};
for (var i = 0; i < pairs.length; i++) {
var pair = pairs[i].split('='),
key = decodeQueryParamPart(pair[0]),
keyLength = key.length,
isArray = false,
value;
if (pair.length === 1) {
value = 'true';
} else {
//Handle arrays
if (keyLength > 2 && key.slice(keyLength - 2) === '[]') {
isArray = true;
key = key.slice(0, keyLength - 2);
if (!queryParams[key]) {
queryParams[key] = [];
}
}
value = pair[1] ? decodeQueryParamPart(pair[1]) : '';
}
if (isArray) {
queryParams[key].push(value);
} else {
queryParams[key] = value;
}
}
return queryParams;
},
recognize: function (path) {
var states = [this.rootState],
pathLen,
i,
l,
queryStart,
queryParams = {},
isSlashDropped = false;
queryStart = path.indexOf('?');
if (queryStart !== -1) {
var queryString = path.substr(queryStart + 1, path.length);
path = path.substr(0, queryStart);
queryParams = this.parseQueryString(queryString);
}
path = decodeURI(path);
// DEBUG GROUP path
if (path.charAt(0) !== "/") {
path = "/" + path;
}
pathLen = path.length;
if (pathLen > 1 && path.charAt(pathLen - 1) === "/") {
path = path.substr(0, pathLen - 1);
isSlashDropped = true;
}
for (i = 0, l = path.length; i < l; i++) {
states = recognizeChar(states, path.charAt(i));
if (!states.length) {
break;
}
}
// END DEBUG GROUP
var solutions = [];
for (i = 0, l = states.length; i < l; i++) {
if (states[i].handlers) {
solutions.push(states[i]);
}
}
states = sortSolutions(solutions);
var state = solutions[0];
if (state && state.handlers) {
// if a trailing slash was dropped and a star segment is the last segment
// specified, put the trailing slash back
if (isSlashDropped && state.regex.source.slice(-5) === "(.+)$") {
path = path + "/";
}
return findHandler(state, path, queryParams);
}
}
};
RouteRecognizer.prototype.map = _routeRecognizerDsl.default;
RouteRecognizer.VERSION = '0.1.5';
exports.default = RouteRecognizer;
});
enifed('router/handler-info/factory', ['exports', 'router/handler-info/resolved-handler-info', 'router/handler-info/unresolved-handler-info-by-object', 'router/handler-info/unresolved-handler-info-by-param'], function (exports, _routerHandlerInfoResolvedHandlerInfo, _routerHandlerInfoUnresolvedHandlerInfoByObject, _routerHandlerInfoUnresolvedHandlerInfoByParam) {
'use strict';
handlerInfoFactory.klasses = {
resolved: _routerHandlerInfoResolvedHandlerInfo.default,
param: _routerHandlerInfoUnresolvedHandlerInfoByParam.default,
object: _routerHandlerInfoUnresolvedHandlerInfoByObject.default
};
function handlerInfoFactory(name, props) {
var Ctor = handlerInfoFactory.klasses[name],
handlerInfo = new Ctor(props || {});
handlerInfo.factory = handlerInfoFactory;
return handlerInfo;
}
exports.default = handlerInfoFactory;
});
enifed('router/handler-info/resolved-handler-info', ['exports', 'router/handler-info', 'router/utils', 'rsvp/promise'], function (exports, _routerHandlerInfo, _routerUtils, _rsvpPromise) {
'use strict';
var ResolvedHandlerInfo = _routerUtils.subclass(_routerHandlerInfo.default, {
resolve: function (shouldContinue, payload) {
// A ResolvedHandlerInfo just resolved with itself.
if (payload && payload.resolvedModels) {
payload.resolvedModels[this.name] = this.context;
}
return _rsvpPromise.default.resolve(this, this.promiseLabel("Resolve"));
},
getUnresolved: function () {
return this.factory('param', {
name: this.name,
handler: this.handler,
params: this.params
});
},
isResolved: true
});
exports.default = ResolvedHandlerInfo;
});
enifed('router/handler-info/unresolved-handler-info-by-object', ['exports', 'router/handler-info', 'router/utils', 'rsvp/promise'], function (exports, _routerHandlerInfo, _routerUtils, _rsvpPromise) {
'use strict';
var UnresolvedHandlerInfoByObject = _routerUtils.subclass(_routerHandlerInfo.default, {
getModel: function (payload) {
this.log(payload, this.name + ": resolving provided model");
return _rsvpPromise.default.resolve(this.context);
},
initialize: function (props) {
this.names = props.names || [];
this.context = props.context;
},
/**
@private
Serializes a handler using its custom `serialize` method or
by a default that looks up the expected property name from
the dynamic segment.
@param {Object} model the model to be serialized for this handler
*/
serialize: function (_model) {
var model = _model || this.context,
names = this.names,
handler = this.handler;
var object = {};
if (_routerUtils.isParam(model)) {
object[names[0]] = model;
return object;
}
// Use custom serialize if it exists.
if (handler.serialize) {
return handler.serialize(model, names);
}
if (names.length !== 1) {
return;
}
var name = names[0];
if (/_id$/.test(name)) {
object[name] = model.id;
} else {
object[name] = model;
}
return object;
}
});
exports.default = UnresolvedHandlerInfoByObject;
});
enifed('router/handler-info/unresolved-handler-info-by-param', ['exports', 'router/handler-info', 'router/utils'], function (exports, _routerHandlerInfo, _routerUtils) {
'use strict';
// Generated by URL transitions and non-dynamic route segments in named Transitions.
var UnresolvedHandlerInfoByParam = _routerUtils.subclass(_routerHandlerInfo.default, {
initialize: function (props) {
this.params = props.params || {};
},
getModel: function (payload) {
var fullParams = this.params;
if (payload && payload.queryParams) {
fullParams = {};
_routerUtils.merge(fullParams, this.params);
fullParams.queryParams = payload.queryParams;
}
var handler = this.handler;
var hookName = _routerUtils.resolveHook(handler, 'deserialize') || _routerUtils.resolveHook(handler, 'model');
return this.runSharedModelHook(payload, hookName, [fullParams]);
}
});
exports.default = UnresolvedHandlerInfoByParam;
});
enifed('router/handler-info', ['exports', 'router/utils', 'rsvp/promise'], function (exports, _routerUtils, _rsvpPromise) {
'use strict';
function HandlerInfo(_props) {
var props = _props || {};
_routerUtils.merge(this, props);
this.initialize(props);
}
HandlerInfo.prototype = {
name: null,
handler: null,
params: null,
context: null,
// Injected by the handler info factory.
factory: null,
initialize: function () {},
log: function (payload, message) {
if (payload.log) {
payload.log(this.name + ': ' + message);
}
},
promiseLabel: function (label) {
return _routerUtils.promiseLabel("'" + this.name + "' " + label);
},
getUnresolved: function () {
return this;
},
serialize: function () {
return this.params || {};
},
resolve: function (shouldContinue, payload) {
var checkForAbort = _routerUtils.bind(this, this.checkForAbort, shouldContinue),
beforeModel = _routerUtils.bind(this, this.runBeforeModelHook, payload),
model = _routerUtils.bind(this, this.getModel, payload),
afterModel = _routerUtils.bind(this, this.runAfterModelHook, payload),
becomeResolved = _routerUtils.bind(this, this.becomeResolved, payload);
return _rsvpPromise.default.resolve(undefined, this.promiseLabel("Start handler")).then(checkForAbort, null, this.promiseLabel("Check for abort")).then(beforeModel, null, this.promiseLabel("Before model")).then(checkForAbort, null, this.promiseLabel("Check if aborted during 'beforeModel' hook")).then(model, null, this.promiseLabel("Model")).then(checkForAbort, null, this.promiseLabel("Check if aborted in 'model' hook")).then(afterModel, null, this.promiseLabel("After model")).then(checkForAbort, null, this.promiseLabel("Check if aborted in 'afterModel' hook")).then(becomeResolved, null, this.promiseLabel("Become resolved"));
},
runBeforeModelHook: function (payload) {
if (payload.trigger) {
payload.trigger(true, 'willResolveModel', payload, this.handler);
}
return this.runSharedModelHook(payload, 'beforeModel', []);
},
runAfterModelHook: function (payload, resolvedModel) {
// Stash the resolved model on the payload.
// This makes it possible for users to swap out
// the resolved model in afterModel.
var name = this.name;
this.stashResolvedModel(payload, resolvedModel);
return this.runSharedModelHook(payload, 'afterModel', [resolvedModel]).then(function () {
// Ignore the fulfilled value returned from afterModel.
// Return the value stashed in resolvedModels, which
// might have been swapped out in afterModel.
return payload.resolvedModels[name];
}, null, this.promiseLabel("Ignore fulfillment value and return model value"));
},
runSharedModelHook: function (payload, hookName, args) {
this.log(payload, "calling " + hookName + " hook");
if (this.queryParams) {
args.push(this.queryParams);
}
args.push(payload);
var result = _routerUtils.applyHook(this.handler, hookName, args);
if (result && result.isTransition) {
result = null;
}
return _rsvpPromise.default.resolve(result, this.promiseLabel("Resolve value returned from one of the model hooks"));
},
// overridden by subclasses
getModel: null,
checkForAbort: function (shouldContinue, promiseValue) {
return _rsvpPromise.default.resolve(shouldContinue(), this.promiseLabel("Check for abort")).then(function () {
// We don't care about shouldContinue's resolve value;
// pass along the original value passed to this fn.
return promiseValue;
}, null, this.promiseLabel("Ignore fulfillment value and continue"));
},
stashResolvedModel: function (payload, resolvedModel) {
payload.resolvedModels = payload.resolvedModels || {};
payload.resolvedModels[this.name] = resolvedModel;
},
becomeResolved: function (payload, resolvedContext) {
var params = this.serialize(resolvedContext);
if (payload) {
this.stashResolvedModel(payload, resolvedContext);
payload.params = payload.params || {};
payload.params[this.name] = params;
}
return this.factory('resolved', {
context: resolvedContext,
name: this.name,
handler: this.handler,
params: params
});
},
shouldSupercede: function (other) {
// Prefer this newer handlerInfo over `other` if:
// 1) The other one doesn't exist
// 2) The names don't match
// 3) This handler has a context that doesn't match
// the other one (or the other one doesn't have one).
// 4) This handler has parameters that don't match the other.
if (!other) {
return true;
}
var contextsMatch = other.context === this.context;
return other.name !== this.name || this.hasOwnProperty('context') && !contextsMatch || this.hasOwnProperty('params') && !paramsMatch(this.params, other.params);
}
};
function paramsMatch(a, b) {
if (!a ^ !b) {
// Only one is null.
return false;
}
if (!a) {
// Both must be null.
return true;
}
// Note: this assumes that both params have the same
// number of keys, but since we're comparing the
// same handlers, they should.
for (var k in a) {
if (a.hasOwnProperty(k) && a[k] !== b[k]) {
return false;
}
}
return true;
}
exports.default = HandlerInfo;
});
enifed('router/router', ['exports', 'route-recognizer', 'rsvp/promise', 'router/utils', 'router/transition-state', 'router/transition', 'router/transition-intent/named-transition-intent', 'router/transition-intent/url-transition-intent', 'router/handler-info'], function (exports, _routeRecognizer, _rsvpPromise, _routerUtils, _routerTransitionState, _routerTransition, _routerTransitionIntentNamedTransitionIntent, _routerTransitionIntentUrlTransitionIntent, _routerHandlerInfo) {
'use strict';
var pop = Array.prototype.pop;
function Router(_options) {
var options = _options || {};
this.getHandler = options.getHandler || this.getHandler;
this.updateURL = options.updateURL || this.updateURL;
this.replaceURL = options.replaceURL || this.replaceURL;
this.didTransition = options.didTransition || this.didTransition;
this.willTransition = options.willTransition || this.willTransition;
this.delegate = options.delegate || this.delegate;
this.triggerEvent = options.triggerEvent || this.triggerEvent;
this.log = options.log || this.log;
this.recognizer = new _routeRecognizer.default();
this.reset();
}
function getTransitionByIntent(intent, isIntermediate) {
var wasTransitioning = !!this.activeTransition;
var oldState = wasTransitioning ? this.activeTransition.state : this.state;
var newTransition;
var newState = intent.applyToState(oldState, this.recognizer, this.getHandler, isIntermediate);
var queryParamChangelist = _routerUtils.getChangelist(oldState.queryParams, newState.queryParams);
if (handlerInfosEqual(newState.handlerInfos, oldState.handlerInfos)) {
// This is a no-op transition. See if query params changed.
if (queryParamChangelist) {
newTransition = this.queryParamsTransition(queryParamChangelist, wasTransitioning, oldState, newState);
if (newTransition) {
return newTransition;
}
}
// No-op. No need to create a new transition.
return this.activeTransition || new _routerTransition.Transition(this);
}
if (isIntermediate) {
setupContexts(this, newState);
return;
}
// Create a new transition to the destination route.
newTransition = new _routerTransition.Transition(this, intent, newState);
// Abort and usurp any previously active transition.
if (this.activeTransition) {
this.activeTransition.abort();
}
this.activeTransition = newTransition;
// Transition promises by default resolve with resolved state.
// For our purposes, swap out the promise to resolve
// after the transition has been finalized.
newTransition.promise = newTransition.promise.then(function (result) {
return finalizeTransition(newTransition, result.state);
}, null, _routerUtils.promiseLabel("Settle transition promise when transition is finalized"));
if (!wasTransitioning) {
notifyExistingHandlers(this, newState, newTransition);
}
fireQueryParamDidChange(this, newState, queryParamChangelist);
return newTransition;
}
Router.prototype = {
/**
The main entry point into the router. The API is essentially
the same as the `map` method in `route-recognizer`.
This method extracts the String handler at the last `.to()`
call and uses it as the name of the whole route.
@param {Function} callback
*/
map: function (callback) {
this.recognizer.delegate = this.delegate;
this.recognizer.map(callback, function (recognizer, routes) {
for (var i = routes.length - 1, proceed = true; i >= 0 && proceed; --i) {
var route = routes[i];
recognizer.add(routes, { as: route.handler });
proceed = route.path === '/' || route.path === '' || route.handler.slice(-6) === '.index';
}
});
},
hasRoute: function (route) {
return this.recognizer.hasRoute(route);
},
getHandler: function () {},
queryParamsTransition: function (changelist, wasTransitioning, oldState, newState) {
var router = this;
fireQueryParamDidChange(this, newState, changelist);
if (!wasTransitioning && this.activeTransition) {
// One of the handlers in queryParamsDidChange
// caused a transition. Just return that transition.
return this.activeTransition;
} else {
// Running queryParamsDidChange didn't change anything.
// Just update query params and be on our way.
// We have to return a noop transition that will
// perform a URL update at the end. This gives
// the user the ability to set the url update
// method (default is replaceState).
var newTransition = new _routerTransition.Transition(this);
newTransition.queryParamsOnly = true;
oldState.queryParams = finalizeQueryParamChange(this, newState.handlerInfos, newState.queryParams, newTransition);
newTransition.promise = newTransition.promise.then(function (result) {
updateURL(newTransition, oldState, true);
if (router.didTransition) {
router.didTransition(router.currentHandlerInfos);
}
return result;
}, null, _routerUtils.promiseLabel("Transition complete"));
return newTransition;
}
},
// NOTE: this doesn't really belong here, but here
// it shall remain until our ES6 transpiler can
// handle cyclical deps.
transitionByIntent: function (intent, isIntermediate) {
try {
return getTransitionByIntent.apply(this, arguments);
} catch (e) {
return new _routerTransition.Transition(this, intent, null, e);
}
},
/**
Clears the current and target route handlers and triggers exit
on each of them starting at the leaf and traversing up through
its ancestors.
*/
reset: function () {
if (this.state) {
_routerUtils.forEach(this.state.handlerInfos.slice().reverse(), function (handlerInfo) {
var handler = handlerInfo.handler;
_routerUtils.callHook(handler, 'exit');
});
}
this.state = new _routerTransitionState.default();
this.currentHandlerInfos = null;
},
activeTransition: null,
/**
var handler = handlerInfo.handler;
The entry point for handling a change to the URL (usually
via the back and forward button).
Returns an Array of handlers and the parameters associated
with those parameters.
@param {String} url a URL to process
@return {Array} an Array of `[handler, parameter]` tuples
*/
handleURL: function (url) {
// Perform a URL-based transition, but don't change
// the URL afterward, since it already happened.
var args = _routerUtils.slice.call(arguments);
if (url.charAt(0) !== '/') {
args[0] = '/' + url;
}
return doTransition(this, args).method(null);
},
/**
Hook point for updating the URL.
@param {String} url a URL to update to
*/
updateURL: function () {
throw new Error("updateURL is not implemented");
},
/**
Hook point for replacing the current URL, i.e. with replaceState
By default this behaves the same as `updateURL`
@param {String} url a URL to update to
*/
replaceURL: function (url) {
this.updateURL(url);
},
/**
Transition into the specified named route.
If necessary, trigger the exit callback on any handlers
that are no longer represented by the target route.
@param {String} name the name of the route
*/
transitionTo: function (name) {
return doTransition(this, arguments);
},
intermediateTransitionTo: function (name) {
return doTransition(this, arguments, true);
},
refresh: function (pivotHandler) {
var state = this.activeTransition ? this.activeTransition.state : this.state;
var handlerInfos = state.handlerInfos;
var params = {};
for (var i = 0, len = handlerInfos.length; i < len; ++i) {
var handlerInfo = handlerInfos[i];
params[handlerInfo.name] = handlerInfo.params || {};
}
_routerUtils.log(this, "Starting a refresh transition");
var intent = new _routerTransitionIntentNamedTransitionIntent.default({
name: handlerInfos[handlerInfos.length - 1].name,
pivotHandler: pivotHandler || handlerInfos[0].handler,
contexts: [], // TODO collect contexts...?
queryParams: this._changedQueryParams || state.queryParams || {}
});
return this.transitionByIntent(intent, false);
},
/**
Identical to `transitionTo` except that the current URL will be replaced
if possible.
This method is intended primarily for use with `replaceState`.
@param {String} name the name of the route
*/
replaceWith: function (name) {
return doTransition(this, arguments).method('replace');
},
/**
Take a named route and context objects and generate a
URL.
@param {String} name the name of the route to generate
a URL for
@param {...Object} objects a list of objects to serialize
@return {String} a URL
*/
generate: function (handlerName) {
var partitionedArgs = _routerUtils.extractQueryParams(_routerUtils.slice.call(arguments, 1)),
suppliedParams = partitionedArgs[0],
queryParams = partitionedArgs[1];
// Construct a TransitionIntent with the provided params
// and apply it to the present state of the router.
var intent = new _routerTransitionIntentNamedTransitionIntent.default({ name: handlerName, contexts: suppliedParams });
var state = intent.applyToState(this.state, this.recognizer, this.getHandler);
var params = {};
for (var i = 0, len = state.handlerInfos.length; i < len; ++i) {
var handlerInfo = state.handlerInfos[i];
var handlerParams = handlerInfo.serialize();
_routerUtils.merge(params, handlerParams);
}
params.queryParams = queryParams;
return this.recognizer.generate(handlerName, params);
},
applyIntent: function (handlerName, contexts) {
var intent = new _routerTransitionIntentNamedTransitionIntent.default({
name: handlerName,
contexts: contexts
});
var state = this.activeTransition && this.activeTransition.state || this.state;
return intent.applyToState(state, this.recognizer, this.getHandler);
},
isActiveIntent: function (handlerName, contexts, queryParams, _state) {
var state = _state || this.state,
targetHandlerInfos = state.handlerInfos,
found = false,
names,
object,
handlerInfo,
handlerObj,
i,
len;
if (!targetHandlerInfos.length) {
return false;
}
var targetHandler = targetHandlerInfos[targetHandlerInfos.length - 1].name;
var recogHandlers = this.recognizer.handlersFor(targetHandler);
var index = 0;
for (len = recogHandlers.length; index < len; ++index) {
handlerInfo = targetHandlerInfos[index];
if (handlerInfo.name === handlerName) {
break;
}
}
if (index === recogHandlers.length) {
// The provided route name isn't even in the route hierarchy.
return false;
}
var testState = new _routerTransitionState.default();
testState.handlerInfos = targetHandlerInfos.slice(0, index + 1);
recogHandlers = recogHandlers.slice(0, index + 1);
var intent = new _routerTransitionIntentNamedTransitionIntent.default({
name: targetHandler,
contexts: contexts
});
var newState = intent.applyToHandlers(testState, recogHandlers, this.getHandler, targetHandler, true, true);
var handlersEqual = handlerInfosEqual(newState.handlerInfos, testState.handlerInfos);
if (!queryParams || !handlersEqual) {
return handlersEqual;
}
// Get a hash of QPs that will still be active on new route
var activeQPsOnNewHandler = {};
_routerUtils.merge(activeQPsOnNewHandler, queryParams);
var activeQueryParams = state.queryParams;
for (var key in activeQueryParams) {
if (activeQueryParams.hasOwnProperty(key) && activeQPsOnNewHandler.hasOwnProperty(key)) {
activeQPsOnNewHandler[key] = activeQueryParams[key];
}
}
return handlersEqual && !_routerUtils.getChangelist(activeQPsOnNewHandler, queryParams);
},
isActive: function (handlerName) {
var partitionedArgs = _routerUtils.extractQueryParams(_routerUtils.slice.call(arguments, 1));
return this.isActiveIntent(handlerName, partitionedArgs[0], partitionedArgs[1]);
},
trigger: function (name) {
var args = _routerUtils.slice.call(arguments);
_routerUtils.trigger(this, this.currentHandlerInfos, false, args);
},
/**
Hook point for logging transition status updates.
@param {String} message The message to log.
*/
log: null
};
/**
@private
Fires queryParamsDidChange event
*/
function fireQueryParamDidChange(router, newState, queryParamChangelist) {
// If queryParams changed trigger event
if (queryParamChangelist) {
// This is a little hacky but we need some way of storing
// changed query params given that no activeTransition
// is guaranteed to have occurred.
router._changedQueryParams = queryParamChangelist.all;
_routerUtils.trigger(router, newState.handlerInfos, true, ['queryParamsDidChange', queryParamChangelist.changed, queryParamChangelist.all, queryParamChangelist.removed]);
router._changedQueryParams = null;
}
}
/**
@private
Takes an Array of `HandlerInfo`s, figures out which ones are
exiting, entering, or changing contexts, and calls the
proper handler hooks.
For example, consider the following tree of handlers. Each handler is
followed by the URL segment it handles.
```
|~index ("/")
| |~posts ("/posts")
| | |-showPost ("/:id")
| | |-newPost ("/new")
| | |-editPost ("/edit")
| |~about ("/about/:id")
```
Consider the following transitions:
1. A URL transition to `/posts/1`.
1. Triggers the `*model` callbacks on the
`index`, `posts`, and `showPost` handlers
2. Triggers the `enter` callback on the same
3. Triggers the `setup` callback on the same
2. A direct transition to `newPost`
1. Triggers the `exit` callback on `showPost`
2. Triggers the `enter` callback on `newPost`
3. Triggers the `setup` callback on `newPost`
3. A direct transition to `about` with a specified
context object
1. Triggers the `exit` callback on `newPost`
and `posts`
2. Triggers the `serialize` callback on `about`
3. Triggers the `enter` callback on `about`
4. Triggers the `setup` callback on `about`
@param {Router} transition
@param {TransitionState} newState
*/
function setupContexts(router, newState, transition) {
var partition = partitionHandlers(router.state, newState);
var i, l, handler;
for (i = 0, l = partition.exited.length; i < l; i++) {
handler = partition.exited[i].handler;
delete handler.context;
_routerUtils.callHook(handler, 'reset', true, transition);
_routerUtils.callHook(handler, 'exit', transition);
}
var oldState = router.oldState = router.state;
router.state = newState;
var currentHandlerInfos = router.currentHandlerInfos = partition.unchanged.slice();
try {
for (i = 0, l = partition.reset.length; i < l; i++) {
handler = partition.reset[i].handler;
_routerUtils.callHook(handler, 'reset', false, transition);
}
for (i = 0, l = partition.updatedContext.length; i < l; i++) {
handlerEnteredOrUpdated(currentHandlerInfos, partition.updatedContext[i], false, transition);
}
for (i = 0, l = partition.entered.length; i < l; i++) {
handlerEnteredOrUpdated(currentHandlerInfos, partition.entered[i], true, transition);
}
} catch (e) {
router.state = oldState;
router.currentHandlerInfos = oldState.handlerInfos;
throw e;
}
router.state.queryParams = finalizeQueryParamChange(router, currentHandlerInfos, newState.queryParams, transition);
}
/**
@private
Helper method used by setupContexts. Handles errors or redirects
that may happen in enter/setup.
*/
function handlerEnteredOrUpdated(currentHandlerInfos, handlerInfo, enter, transition) {
var handler = handlerInfo.handler,
context = handlerInfo.context;
if (enter) {
_routerUtils.callHook(handler, 'enter', transition);
}
if (transition && transition.isAborted) {
throw new _routerTransition.TransitionAborted();
}
handler.context = context;
_routerUtils.callHook(handler, 'contextDidChange');
_routerUtils.callHook(handler, 'setup', context, transition);
if (transition && transition.isAborted) {
throw new _routerTransition.TransitionAborted();
}
currentHandlerInfos.push(handlerInfo);
return true;
}
/**
@private
This function is called when transitioning from one URL to
another to determine which handlers are no longer active,
which handlers are newly active, and which handlers remain
active but have their context changed.
Take a list of old handlers and new handlers and partition
them into four buckets:
* unchanged: the handler was active in both the old and
new URL, and its context remains the same
* updated context: the handler was active in both the
old and new URL, but its context changed. The handler's
`setup` method, if any, will be called with the new
context.
* exited: the handler was active in the old URL, but is
no longer active.
* entered: the handler was not active in the old URL, but
is now active.
The PartitionedHandlers structure has four fields:
* `updatedContext`: a list of `HandlerInfo` objects that
represent handlers that remain active but have a changed
context
* `entered`: a list of `HandlerInfo` objects that represent
handlers that are newly active
* `exited`: a list of `HandlerInfo` objects that are no
longer active.
* `unchanged`: a list of `HanderInfo` objects that remain active.
@param {Array[HandlerInfo]} oldHandlers a list of the handler
information for the previous URL (or `[]` if this is the
first handled transition)
@param {Array[HandlerInfo]} newHandlers a list of the handler
information for the new URL
@return {Partition}
*/
function partitionHandlers(oldState, newState) {
var oldHandlers = oldState.handlerInfos;
var newHandlers = newState.handlerInfos;
var handlers = {
updatedContext: [],
exited: [],
entered: [],
unchanged: []
};
var handlerChanged,
contextChanged = false,
i,
l;
for (i = 0, l = newHandlers.length; i < l; i++) {
var oldHandler = oldHandlers[i],
newHandler = newHandlers[i];
if (!oldHandler || oldHandler.handler !== newHandler.handler) {
handlerChanged = true;
}
if (handlerChanged) {
handlers.entered.push(newHandler);
if (oldHandler) {
handlers.exited.unshift(oldHandler);
}
} else if (contextChanged || oldHandler.context !== newHandler.context) {
contextChanged = true;
handlers.updatedContext.push(newHandler);
} else {
handlers.unchanged.push(oldHandler);
}
}
for (i = newHandlers.length, l = oldHandlers.length; i < l; i++) {
handlers.exited.unshift(oldHandlers[i]);
}
handlers.reset = handlers.updatedContext.slice();
handlers.reset.reverse();
return handlers;
}
function updateURL(transition, state, inputUrl) {
var urlMethod = transition.urlMethod;
if (!urlMethod) {
return;
}
var router = transition.router,
handlerInfos = state.handlerInfos,
handlerName = handlerInfos[handlerInfos.length - 1].name,
params = {};
for (var i = handlerInfos.length - 1; i >= 0; --i) {
var handlerInfo = handlerInfos[i];
_routerUtils.merge(params, handlerInfo.params);
if (handlerInfo.handler.inaccessibleByURL) {
urlMethod = null;
}
}
if (urlMethod) {
params.queryParams = transition._visibleQueryParams || state.queryParams;
var url = router.recognizer.generate(handlerName, params);
if (urlMethod === 'replace') {
router.replaceURL(url);
} else {
router.updateURL(url);
}
}
}
/**
@private
Updates the URL (if necessary) and calls `setupContexts`
to update the router's array of `currentHandlerInfos`.
*/
function finalizeTransition(transition, newState) {
try {
_routerUtils.log(transition.router, transition.sequence, "Resolved all models on destination route; finalizing transition.");
var router = transition.router,
handlerInfos = newState.handlerInfos,
seq = transition.sequence;
// Run all the necessary enter/setup/exit hooks
setupContexts(router, newState, transition);
// Check if a redirect occurred in enter/setup
if (transition.isAborted) {
// TODO: cleaner way? distinguish b/w targetHandlerInfos?
router.state.handlerInfos = router.currentHandlerInfos;
return _rsvpPromise.default.reject(_routerTransition.logAbort(transition));
}
updateURL(transition, newState, transition.intent.url);
transition.isActive = false;
router.activeTransition = null;
_routerUtils.trigger(router, router.currentHandlerInfos, true, ['didTransition']);
if (router.didTransition) {
router.didTransition(router.currentHandlerInfos);
}
_routerUtils.log(router, transition.sequence, "TRANSITION COMPLETE.");
// Resolve with the final handler.
return handlerInfos[handlerInfos.length - 1].handler;
} catch (e) {
if (!(e instanceof _routerTransition.TransitionAborted)) {
//var erroneousHandler = handlerInfos.pop();
var infos = transition.state.handlerInfos;
transition.trigger(true, 'error', e, transition, infos[infos.length - 1].handler);
transition.abort();
}
throw e;
}
}
/**
@private
Begins and returns a Transition based on the provided
arguments. Accepts arguments in the form of both URL
transitions and named transitions.
@param {Router} router
@param {Array[Object]} args arguments passed to transitionTo,
replaceWith, or handleURL
*/
function doTransition(router, args, isIntermediate) {
// Normalize blank transitions to root URL transitions.
var name = args[0] || '/';
var lastArg = args[args.length - 1];
var queryParams = {};
if (lastArg && lastArg.hasOwnProperty('queryParams')) {
queryParams = pop.call(args).queryParams;
}
var intent;
if (args.length === 0) {
_routerUtils.log(router, "Updating query params");
// A query param update is really just a transition
// into the route you're already on.
var handlerInfos = router.state.handlerInfos;
intent = new _routerTransitionIntentNamedTransitionIntent.default({
name: handlerInfos[handlerInfos.length - 1].name,
contexts: [],
queryParams: queryParams
});
} else if (name.charAt(0) === '/') {
_routerUtils.log(router, "Attempting URL transition to " + name);
intent = new _routerTransitionIntentUrlTransitionIntent.default({ url: name });
} else {
_routerUtils.log(router, "Attempting transition to " + name);
intent = new _routerTransitionIntentNamedTransitionIntent.default({
name: args[0],
contexts: _routerUtils.slice.call(args, 1),
queryParams: queryParams
});
}
return router.transitionByIntent(intent, isIntermediate);
}
function handlerInfosEqual(handlerInfos, otherHandlerInfos) {
if (handlerInfos.length !== otherHandlerInfos.length) {
return false;
}
for (var i = 0, len = handlerInfos.length; i < len; ++i) {
if (handlerInfos[i] !== otherHandlerInfos[i]) {
return false;
}
}
return true;
}
function finalizeQueryParamChange(router, resolvedHandlers, newQueryParams, transition) {
// We fire a finalizeQueryParamChange event which
// gives the new route hierarchy a chance to tell
// us which query params it's consuming and what
// their final values are. If a query param is
// no longer consumed in the final route hierarchy,
// its serialized segment will be removed
// from the URL.
for (var k in newQueryParams) {
if (newQueryParams.hasOwnProperty(k) && newQueryParams[k] === null) {
delete newQueryParams[k];
}
}
var finalQueryParamsArray = [];
_routerUtils.trigger(router, resolvedHandlers, true, ['finalizeQueryParamChange', newQueryParams, finalQueryParamsArray, transition]);
if (transition) {
transition._visibleQueryParams = {};
}
var finalQueryParams = {};
for (var i = 0, len = finalQueryParamsArray.length; i < len; ++i) {
var qp = finalQueryParamsArray[i];
finalQueryParams[qp.key] = qp.value;
if (transition && qp.visible !== false) {
transition._visibleQueryParams[qp.key] = qp.value;
}
}
return finalQueryParams;
}
function notifyExistingHandlers(router, newState, newTransition) {
var oldHandlers = router.state.handlerInfos,
changing = [],
leavingIndex = null,
leaving,
leavingChecker,
i,
oldHandlerLen,
oldHandler,
newHandler;
oldHandlerLen = oldHandlers.length;
for (i = 0; i < oldHandlerLen; i++) {
oldHandler = oldHandlers[i];
newHandler = newState.handlerInfos[i];
if (!newHandler || oldHandler.name !== newHandler.name) {
leavingIndex = i;
break;
}
if (!newHandler.isResolved) {
changing.push(oldHandler);
}
}
if (leavingIndex !== null) {
leaving = oldHandlers.slice(leavingIndex, oldHandlerLen);
leavingChecker = function (name) {
for (var h = 0, len = leaving.length; h < len; h++) {
if (leaving[h].name === name) {
return true;
}
}
return false;
};
}
_routerUtils.trigger(router, oldHandlers, true, ['willTransition', newTransition]);
if (router.willTransition) {
router.willTransition(oldHandlers, newState.handlerInfos, newTransition);
}
}
exports.default = Router;
});
enifed('router/transition-intent/named-transition-intent', ['exports', 'router/transition-intent', 'router/transition-state', 'router/handler-info/factory', 'router/utils'], function (exports, _routerTransitionIntent, _routerTransitionState, _routerHandlerInfoFactory, _routerUtils) {
'use strict';
exports.default = _routerUtils.subclass(_routerTransitionIntent.default, {
name: null,
pivotHandler: null,
contexts: null,
queryParams: null,
initialize: function (props) {
this.name = props.name;
this.pivotHandler = props.pivotHandler;
this.contexts = props.contexts || [];
this.queryParams = props.queryParams;
},
applyToState: function (oldState, recognizer, getHandler, isIntermediate) {
var partitionedArgs = _routerUtils.extractQueryParams([this.name].concat(this.contexts)),
pureArgs = partitionedArgs[0],
queryParams = partitionedArgs[1],
handlers = recognizer.handlersFor(pureArgs[0]);
var targetRouteName = handlers[handlers.length - 1].handler;
return this.applyToHandlers(oldState, handlers, getHandler, targetRouteName, isIntermediate);
},
applyToHandlers: function (oldState, handlers, getHandler, targetRouteName, isIntermediate, checkingIfActive) {
var i, len;
var newState = new _routerTransitionState.default();
var objects = this.contexts.slice(0);
var invalidateIndex = handlers.length;
// Pivot handlers are provided for refresh transitions
if (this.pivotHandler) {
for (i = 0, len = handlers.length; i < len; ++i) {
if (getHandler(handlers[i].handler) === this.pivotHandler) {
invalidateIndex = i;
break;
}
}
}
var pivotHandlerFound = !this.pivotHandler;
for (i = handlers.length - 1; i >= 0; --i) {
var result = handlers[i];
var name = result.handler;
var handler = getHandler(name);
var oldHandlerInfo = oldState.handlerInfos[i];
var newHandlerInfo = null;
if (result.names.length > 0) {
if (i >= invalidateIndex) {
newHandlerInfo = this.createParamHandlerInfo(name, handler, result.names, objects, oldHandlerInfo);
} else {
newHandlerInfo = this.getHandlerInfoForDynamicSegment(name, handler, result.names, objects, oldHandlerInfo, targetRouteName, i);
}
} else {
// This route has no dynamic segment.
// Therefore treat as a param-based handlerInfo
// with empty params. This will cause the `model`
// hook to be called with empty params, which is desirable.
newHandlerInfo = this.createParamHandlerInfo(name, handler, result.names, objects, oldHandlerInfo);
}
if (checkingIfActive) {
// If we're performing an isActive check, we want to
// serialize URL params with the provided context, but
// ignore mismatches between old and new context.
newHandlerInfo = newHandlerInfo.becomeResolved(null, newHandlerInfo.context);
var oldContext = oldHandlerInfo && oldHandlerInfo.context;
if (result.names.length > 0 && newHandlerInfo.context === oldContext) {
// If contexts match in isActive test, assume params also match.
// This allows for flexibility in not requiring that every last
// handler provide a `serialize` method
newHandlerInfo.params = oldHandlerInfo && oldHandlerInfo.params;
}
newHandlerInfo.context = oldContext;
}
var handlerToUse = oldHandlerInfo;
if (i >= invalidateIndex || newHandlerInfo.shouldSupercede(oldHandlerInfo)) {
invalidateIndex = Math.min(i, invalidateIndex);
handlerToUse = newHandlerInfo;
}
if (isIntermediate && !checkingIfActive) {
handlerToUse = handlerToUse.becomeResolved(null, handlerToUse.context);
}
newState.handlerInfos.unshift(handlerToUse);
}
if (objects.length > 0) {
throw new Error("More context objects were passed than there are dynamic segments for the route: " + targetRouteName);
}
if (!isIntermediate) {
this.invalidateChildren(newState.handlerInfos, invalidateIndex);
}
_routerUtils.merge(newState.queryParams, this.queryParams || {});
return newState;
},
invalidateChildren: function (handlerInfos, invalidateIndex) {
for (var i = invalidateIndex, l = handlerInfos.length; i < l; ++i) {
var handlerInfo = handlerInfos[i];
handlerInfos[i] = handlerInfos[i].getUnresolved();
}
},
getHandlerInfoForDynamicSegment: function (name, handler, names, objects, oldHandlerInfo, targetRouteName, i) {
var numNames = names.length;
var objectToUse;
if (objects.length > 0) {
// Use the objects provided for this transition.
objectToUse = objects[objects.length - 1];
if (_routerUtils.isParam(objectToUse)) {
return this.createParamHandlerInfo(name, handler, names, objects, oldHandlerInfo);
} else {
objects.pop();
}
} else if (oldHandlerInfo && oldHandlerInfo.name === name) {
// Reuse the matching oldHandlerInfo
return oldHandlerInfo;
} else {
if (this.preTransitionState) {
var preTransitionHandlerInfo = this.preTransitionState.handlerInfos[i];
objectToUse = preTransitionHandlerInfo && preTransitionHandlerInfo.context;
} else {
// Ideally we should throw this error to provide maximal
// information to the user that not enough context objects
// were provided, but this proves too cumbersome in Ember
// in cases where inner template helpers are evaluated
// before parent helpers un-render, in which cases this
// error somewhat prematurely fires.
//throw new Error("Not enough context objects were provided to complete a transition to " + targetRouteName + ". Specifically, the " + name + " route needs an object that can be serialized into its dynamic URL segments [" + names.join(', ') + "]");
return oldHandlerInfo;
}
}
return _routerHandlerInfoFactory.default('object', {
name: name,
handler: handler,
context: objectToUse,
names: names
});
},
createParamHandlerInfo: function (name, handler, names, objects, oldHandlerInfo) {
var params = {};
// Soak up all the provided string/numbers
var numNames = names.length;
while (numNames--) {
// Only use old params if the names match with the new handler
var oldParams = oldHandlerInfo && name === oldHandlerInfo.name && oldHandlerInfo.params || {};
var peek = objects[objects.length - 1];
var paramName = names[numNames];
if (_routerUtils.isParam(peek)) {
params[paramName] = "" + objects.pop();
} else {
// If we're here, this means only some of the params
// were string/number params, so try and use a param
// value from a previous handler.
if (oldParams.hasOwnProperty(paramName)) {
params[paramName] = oldParams[paramName];
} else {
throw new Error("You didn't provide enough string/numeric parameters to satisfy all of the dynamic segments for route " + name);
}
}
}
return _routerHandlerInfoFactory.default('param', {
name: name,
handler: handler,
params: params
});
}
});
});
enifed('router/transition-intent/url-transition-intent', ['exports', 'router/transition-intent', 'router/transition-state', 'router/handler-info/factory', 'router/utils', 'router/unrecognized-url-error'], function (exports, _routerTransitionIntent, _routerTransitionState, _routerHandlerInfoFactory, _routerUtils, _routerUnrecognizedUrlError) {
'use strict';
exports.default = _routerUtils.subclass(_routerTransitionIntent.default, {
url: null,
initialize: function (props) {
this.url = props.url;
},
applyToState: function (oldState, recognizer, getHandler) {
var newState = new _routerTransitionState.default();
var results = recognizer.recognize(this.url),
queryParams = {},
i,
len;
if (!results) {
throw new _routerUnrecognizedUrlError.default(this.url);
}
var statesDiffer = false;
for (i = 0, len = results.length; i < len; ++i) {
var result = results[i];
var name = result.handler;
var handler = getHandler(name);
if (handler.inaccessibleByURL) {
throw new _routerUnrecognizedUrlError.default(this.url);
}
var newHandlerInfo = _routerHandlerInfoFactory.default('param', {
name: name,
handler: handler,
params: result.params
});
var oldHandlerInfo = oldState.handlerInfos[i];
if (statesDiffer || newHandlerInfo.shouldSupercede(oldHandlerInfo)) {
statesDiffer = true;
newState.handlerInfos[i] = newHandlerInfo;
} else {
newState.handlerInfos[i] = oldHandlerInfo;
}
}
_routerUtils.merge(newState.queryParams, results.queryParams);
return newState;
}
});
});
enifed('router/transition-intent', ['exports', 'router/utils'], function (exports, _routerUtils) {
'use strict';
function TransitionIntent(props) {
this.initialize(props);
// TODO: wat
this.data = this.data || {};
}
TransitionIntent.prototype = {
initialize: null,
applyToState: null
};
exports.default = TransitionIntent;
});
enifed('router/transition-state', ['exports', 'router/handler-info', 'router/utils', 'rsvp/promise'], function (exports, _routerHandlerInfo, _routerUtils, _rsvpPromise) {
'use strict';
function TransitionState(other) {
this.handlerInfos = [];
this.queryParams = {};
this.params = {};
}
TransitionState.prototype = {
handlerInfos: null,
queryParams: null,
params: null,
promiseLabel: function (label) {
var targetName = '';
_routerUtils.forEach(this.handlerInfos, function (handlerInfo) {
if (targetName !== '') {
targetName += '.';
}
targetName += handlerInfo.name;
});
return _routerUtils.promiseLabel("'" + targetName + "': " + label);
},
resolve: function (shouldContinue, payload) {
var self = this;
// First, calculate params for this state. This is useful
// information to provide to the various route hooks.
var params = this.params;
_routerUtils.forEach(this.handlerInfos, function (handlerInfo) {
params[handlerInfo.name] = handlerInfo.params || {};
});
payload = payload || {};
payload.resolveIndex = 0;
var currentState = this;
var wasAborted = false;
// The prelude RSVP.resolve() asyncs us into the promise land.
return _rsvpPromise.default.resolve(null, this.promiseLabel("Start transition")).then(resolveOneHandlerInfo, null, this.promiseLabel('Resolve handler'))['catch'](handleError, this.promiseLabel('Handle error'));
function innerShouldContinue() {
return _rsvpPromise.default.resolve(shouldContinue(), currentState.promiseLabel("Check if should continue"))['catch'](function (reason) {
// We distinguish between errors that occurred
// during resolution (e.g. beforeModel/model/afterModel),
// and aborts due to a rejecting promise from shouldContinue().
wasAborted = true;
return _rsvpPromise.default.reject(reason);
}, currentState.promiseLabel("Handle abort"));
}
function handleError(error) {
// This is the only possible
// reject value of TransitionState#resolve
var handlerInfos = currentState.handlerInfos;
var errorHandlerIndex = payload.resolveIndex >= handlerInfos.length ? handlerInfos.length - 1 : payload.resolveIndex;
return _rsvpPromise.default.reject({
error: error,
handlerWithError: currentState.handlerInfos[errorHandlerIndex].handler,
wasAborted: wasAborted,
state: currentState
});
}
function proceed(resolvedHandlerInfo) {
var wasAlreadyResolved = currentState.handlerInfos[payload.resolveIndex].isResolved;
// Swap the previously unresolved handlerInfo with
// the resolved handlerInfo
currentState.handlerInfos[payload.resolveIndex++] = resolvedHandlerInfo;
if (!wasAlreadyResolved) {
// Call the redirect hook. The reason we call it here
// vs. afterModel is so that redirects into child
// routes don't re-run the model hooks for this
// already-resolved route.
var handler = resolvedHandlerInfo.handler;
_routerUtils.callHook(handler, 'redirect', resolvedHandlerInfo.context, payload);
}
// Proceed after ensuring that the redirect hook
// didn't abort this transition by transitioning elsewhere.
return innerShouldContinue().then(resolveOneHandlerInfo, null, currentState.promiseLabel('Resolve handler'));
}
function resolveOneHandlerInfo() {
if (payload.resolveIndex === currentState.handlerInfos.length) {
// This is is the only possible
// fulfill value of TransitionState#resolve
return {
error: null,
state: currentState
};
}
var handlerInfo = currentState.handlerInfos[payload.resolveIndex];
return handlerInfo.resolve(innerShouldContinue, payload).then(proceed, null, currentState.promiseLabel('Proceed'));
}
}
};
exports.default = TransitionState;
});
enifed('router/transition', ['exports', 'rsvp/promise', 'router/handler-info', 'router/utils'], function (exports, _rsvpPromise, _routerHandlerInfo, _routerUtils) {
'use strict';
/**
@private
A Transition is a thennable (a promise-like object) that represents
an attempt to transition to another route. It can be aborted, either
explicitly via `abort` or by attempting another transition while a
previous one is still underway. An aborted transition can also
be `retry()`d later.
*/
function Transition(router, intent, state, error) {
var transition = this;
this.state = state || router.state;
this.intent = intent;
this.router = router;
this.data = this.intent && this.intent.data || {};
this.resolvedModels = {};
this.queryParams = {};
if (error) {
this.promise = _rsvpPromise.default.reject(error);
this.error = error;
return;
}
if (state) {
this.params = state.params;
this.queryParams = state.queryParams;
this.handlerInfos = state.handlerInfos;
var len = state.handlerInfos.length;
if (len) {
this.targetName = state.handlerInfos[len - 1].name;
}
for (var i = 0; i < len; ++i) {
var handlerInfo = state.handlerInfos[i];
// TODO: this all seems hacky
if (!handlerInfo.isResolved) {
break;
}
this.pivotHandler = handlerInfo.handler;
}
this.sequence = Transition.currentSequence++;
this.promise = state.resolve(checkForAbort, this)['catch'](function (result) {
if (result.wasAborted || transition.isAborted) {
return _rsvpPromise.default.reject(logAbort(transition));
} else {
transition.trigger('error', result.error, transition, result.handlerWithError);
transition.abort();
return _rsvpPromise.default.reject(result.error);
}
}, _routerUtils.promiseLabel('Handle Abort'));
} else {
this.promise = _rsvpPromise.default.resolve(this.state);
this.params = {};
}
function checkForAbort() {
if (transition.isAborted) {
return _rsvpPromise.default.reject(undefined, _routerUtils.promiseLabel("Transition aborted - reject"));
}
}
}
Transition.currentSequence = 0;
Transition.prototype = {
targetName: null,
urlMethod: 'update',
intent: null,
params: null,
pivotHandler: null,
resolveIndex: 0,
handlerInfos: null,
resolvedModels: null,
isActive: true,
state: null,
queryParamsOnly: false,
isTransition: true,
isExiting: function (handler) {
var handlerInfos = this.handlerInfos;
for (var i = 0, len = handlerInfos.length; i < len; ++i) {
var handlerInfo = handlerInfos[i];
if (handlerInfo.name === handler || handlerInfo.handler === handler) {
return false;
}
}
return true;
},
/**
@public
The Transition's internal promise. Calling `.then` on this property
is that same as calling `.then` on the Transition object itself, but
this property is exposed for when you want to pass around a
Transition's promise, but not the Transition object itself, since
Transition object can be externally `abort`ed, while the promise
cannot.
*/
promise: null,
/**
@public
Custom state can be stored on a Transition's `data` object.
This can be useful for decorating a Transition within an earlier
hook and shared with a later hook. Properties set on `data` will
be copied to new transitions generated by calling `retry` on this
transition.
*/
data: null,
/**
@public
A standard promise hook that resolves if the transition
succeeds and rejects if it fails/redirects/aborts.
Forwards to the internal `promise` property which you can
use in situations where you want to pass around a thennable,
but not the Transition itself.
@param {Function} onFulfilled
@param {Function} onRejected
@param {String} label optional string for labeling the promise.
Useful for tooling.
@return {Promise}
*/
then: function (onFulfilled, onRejected, label) {
return this.promise.then(onFulfilled, onRejected, label);
},
/**
@public
Forwards to the internal `promise` property which you can
use in situations where you want to pass around a thennable,
but not the Transition itself.
@method catch
@param {Function} onRejection
@param {String} label optional string for labeling the promise.
Useful for tooling.
@return {Promise}
*/
catch: function (onRejection, label) {
return this.promise.catch(onRejection, label);
},
/**
@public
Forwards to the internal `promise` property which you can
use in situations where you want to pass around a thennable,
but not the Transition itself.
@method finally
@param {Function} callback
@param {String} label optional string for labeling the promise.
Useful for tooling.
@return {Promise}
*/
finally: function (callback, label) {
return this.promise.finally(callback, label);
},
/**
@public
Aborts the Transition. Note you can also implicitly abort a transition
by initiating another transition while a previous one is underway.
*/
abort: function () {
if (this.isAborted) {
return this;
}
_routerUtils.log(this.router, this.sequence, this.targetName + ": transition was aborted");
this.intent.preTransitionState = this.router.state;
this.isAborted = true;
this.isActive = false;
this.router.activeTransition = null;
return this;
},
/**
@public
Retries a previously-aborted transition (making sure to abort the
transition if it's still active). Returns a new transition that
represents the new attempt to transition.
*/
retry: function () {
// TODO: add tests for merged state retry()s
this.abort();
return this.router.transitionByIntent(this.intent, false);
},
/**
@public
Sets the URL-changing method to be employed at the end of a
successful transition. By default, a new Transition will just
use `updateURL`, but passing 'replace' to this method will
cause the URL to update using 'replaceWith' instead. Omitting
a parameter will disable the URL change, allowing for transitions
that don't update the URL at completion (this is also used for
handleURL, since the URL has already changed before the
transition took place).
@param {String} method the type of URL-changing method to use
at the end of a transition. Accepted values are 'replace',
falsy values, or any other non-falsy value (which is
interpreted as an updateURL transition).
@return {Transition} this transition
*/
method: function (method) {
this.urlMethod = method;
return this;
},
/**
@public
Fires an event on the current list of resolved/resolving
handlers within this transition. Useful for firing events
on route hierarchies that haven't fully been entered yet.
Note: This method is also aliased as `send`
@param {Boolean} [ignoreFailure=false] a boolean specifying whether unhandled events throw an error
@param {String} name the name of the event to fire
*/
trigger: function (ignoreFailure) {
var args = _routerUtils.slice.call(arguments);
if (typeof ignoreFailure === 'boolean') {
args.shift();
} else {
// Throw errors on unhandled trigger events by default
ignoreFailure = false;
}
_routerUtils.trigger(this.router, this.state.handlerInfos.slice(0, this.resolveIndex + 1), ignoreFailure, args);
},
/**
@public
Transitions are aborted and their promises rejected
when redirects occur; this method returns a promise
that will follow any redirects that occur and fulfill
with the value fulfilled by any redirecting transitions
that occur.
@return {Promise} a promise that fulfills with the same
value that the final redirecting transition fulfills with
*/
followRedirects: function () {
var router = this.router;
return this.promise['catch'](function (reason) {
if (router.activeTransition) {
return router.activeTransition.followRedirects();
}
return _rsvpPromise.default.reject(reason);
});
},
toString: function () {
return "Transition (sequence " + this.sequence + ")";
},
/**
@private
*/
log: function (message) {
_routerUtils.log(this.router, this.sequence, message);
}
};
// Alias 'trigger' as 'send'
Transition.prototype.send = Transition.prototype.trigger;
/**
@private
Logs and returns a TransitionAborted error.
*/
function logAbort(transition) {
_routerUtils.log(transition.router, transition.sequence, "detected abort.");
return new TransitionAborted();
}
function TransitionAborted(message) {
this.message = message || "TransitionAborted";
this.name = "TransitionAborted";
}
exports.Transition = Transition;
exports.logAbort = logAbort;
exports.TransitionAborted = TransitionAborted;
});
enifed("router/unrecognized-url-error", ["exports", "router/utils"], function (exports, _routerUtils) {
"use strict";
/**
Promise reject reasons passed to promise rejection
handlers for failed transitions.
*/
function UnrecognizedURLError(message) {
this.message = message || "UnrecognizedURLError";
this.name = "UnrecognizedURLError";
Error.call(this);
}
UnrecognizedURLError.prototype = _routerUtils.oCreate(Error.prototype);
exports.default = UnrecognizedURLError;
});
enifed('router/utils', ['exports'], function (exports) {
'use strict';
exports.extractQueryParams = extractQueryParams;
exports.log = log;
exports.bind = bind;
exports.forEach = forEach;
exports.trigger = trigger;
exports.getChangelist = getChangelist;
exports.promiseLabel = promiseLabel;
exports.subclass = subclass;
var slice = Array.prototype.slice;
var _isArray;
if (!Array.isArray) {
_isArray = function (x) {
return Object.prototype.toString.call(x) === "[object Array]";
};
} else {
_isArray = Array.isArray;
}
var isArray = _isArray;
exports.isArray = isArray;
function merge(hash, other) {
for (var prop in other) {
if (other.hasOwnProperty(prop)) {
hash[prop] = other[prop];
}
}
}
var oCreate = Object.create || function (proto) {
function F() {}
F.prototype = proto;
return new F();
};
exports.oCreate = oCreate;
/**
@private
Extracts query params from the end of an array
**/
function extractQueryParams(array) {
var len = array && array.length,
head,
queryParams;
if (len && len > 0 && array[len - 1] && array[len - 1].hasOwnProperty('queryParams')) {
queryParams = array[len - 1].queryParams;
head = slice.call(array, 0, len - 1);
return [head, queryParams];
} else {
return [array, null];
}
}
/**
@private
Coerces query param properties and array elements into strings.
**/
function coerceQueryParamsToString(queryParams) {
for (var key in queryParams) {
if (typeof queryParams[key] === 'number') {
queryParams[key] = '' + queryParams[key];
} else if (isArray(queryParams[key])) {
for (var i = 0, l = queryParams[key].length; i < l; i++) {
queryParams[key][i] = '' + queryParams[key][i];
}
}
}
}
/**
@private
*/
function log(router, sequence, msg) {
if (!router.log) {
return;
}
if (arguments.length === 3) {
router.log("Transition #" + sequence + ": " + msg);
} else {
msg = sequence;
router.log(msg);
}
}
function bind(context, fn) {
var boundArgs = arguments;
return function (value) {
var args = slice.call(boundArgs, 2);
args.push(value);
return fn.apply(context, args);
};
}
function isParam(object) {
return typeof object === "string" || object instanceof String || typeof object === "number" || object instanceof Number;
}
function forEach(array, callback) {
for (var i = 0, l = array.length; i < l && false !== callback(array[i]); i++) {}
}
function trigger(router, handlerInfos, ignoreFailure, args) {
if (router.triggerEvent) {
router.triggerEvent(handlerInfos, ignoreFailure, args);
return;
}
var name = args.shift();
if (!handlerInfos) {
if (ignoreFailure) {
return;
}
throw new Error("Could not trigger event '" + name + "'. There are no active handlers");
}
var eventWasHandled = false;
for (var i = handlerInfos.length - 1; i >= 0; i--) {
var handlerInfo = handlerInfos[i],
handler = handlerInfo.handler;
if (handler.events && handler.events[name]) {
if (handler.events[name].apply(handler, args) === true) {
eventWasHandled = true;
} else {
return;
}
}
}
if (!eventWasHandled && !ignoreFailure) {
throw new Error("Nothing handled the event '" + name + "'.");
}
}
function getChangelist(oldObject, newObject) {
var key;
var results = {
all: {},
changed: {},
removed: {}
};
merge(results.all, newObject);
var didChange = false;
coerceQueryParamsToString(oldObject);
coerceQueryParamsToString(newObject);
// Calculate removals
for (key in oldObject) {
if (oldObject.hasOwnProperty(key)) {
if (!newObject.hasOwnProperty(key)) {
didChange = true;
results.removed[key] = oldObject[key];
}
}
}
// Calculate changes
for (key in newObject) {
if (newObject.hasOwnProperty(key)) {
if (isArray(oldObject[key]) && isArray(newObject[key])) {
if (oldObject[key].length !== newObject[key].length) {
results.changed[key] = newObject[key];
didChange = true;
} else {
for (var i = 0, l = oldObject[key].length; i < l; i++) {
if (oldObject[key][i] !== newObject[key][i]) {
results.changed[key] = newObject[key];
didChange = true;
}
}
}
} else {
if (oldObject[key] !== newObject[key]) {
results.changed[key] = newObject[key];
didChange = true;
}
}
}
}
return didChange && results;
}
function promiseLabel(label) {
return 'Router: ' + label;
}
function subclass(parentConstructor, proto) {
function C(props) {
parentConstructor.call(this, props || {});
}
C.prototype = oCreate(parentConstructor.prototype);
merge(C.prototype, proto);
return C;
}
function resolveHook(obj, hookName) {
if (!obj) {
return;
}
var underscored = "_" + hookName;
return obj[underscored] && underscored || obj[hookName] && hookName;
}
function callHook(obj, _hookName, arg1, arg2) {
var hookName = resolveHook(obj, _hookName);
return hookName && obj[hookName].call(obj, arg1, arg2);
}
function applyHook(obj, _hookName, args) {
var hookName = resolveHook(obj, _hookName);
if (hookName) {
if (args.length === 0) {
return obj[hookName].call(obj);
} else if (args.length === 1) {
return obj[hookName].call(obj, args[0]);
} else if (args.length === 2) {
return obj[hookName].call(obj, args[0], args[1]);
} else {
return obj[hookName].apply(obj, args);
}
}
}
exports.merge = merge;
exports.slice = slice;
exports.isParam = isParam;
exports.coerceQueryParamsToString = coerceQueryParamsToString;
exports.callHook = callHook;
exports.resolveHook = resolveHook;
exports.applyHook = applyHook;
});
enifed('router', ['exports', 'router/router'], function (exports, _routerRouter) {
'use strict';
exports.default = _routerRouter.default;
});
enifed('rsvp/-internal', ['exports', 'rsvp/utils', 'rsvp/instrument', 'rsvp/config'], function (exports, _rsvpUtils, _rsvpInstrument, _rsvpConfig) {
'use strict';
function withOwnPromise() {
return new TypeError('A promises callback cannot return that same promise.');
}
function noop() {}
var PENDING = void 0;
var FULFILLED = 1;
var REJECTED = 2;
var GET_THEN_ERROR = new ErrorObject();
function getThen(promise) {
try {
return promise.then;
} catch (error) {
GET_THEN_ERROR.error = error;
return GET_THEN_ERROR;
}
}
function tryThen(then, value, fulfillmentHandler, rejectionHandler) {
try {
then.call(value, fulfillmentHandler, rejectionHandler);
} catch (e) {
return e;
}
}
function handleForeignThenable(promise, thenable, then) {
_rsvpConfig.config.async(function (promise) {
var sealed = false;
var error = tryThen(then, thenable, function (value) {
if (sealed) {
return;
}
sealed = true;
if (thenable !== value) {
resolve(promise, value);
} else {
fulfill(promise, value);
}
}, function (reason) {
if (sealed) {
return;
}
sealed = true;
reject(promise, reason);
}, 'Settle: ' + (promise._label || ' unknown promise'));
if (!sealed && error) {
sealed = true;
reject(promise, error);
}
}, promise);
}
function handleOwnThenable(promise, thenable) {
if (thenable._state === FULFILLED) {
fulfill(promise, thenable._result);
} else if (thenable._state === REJECTED) {
thenable._onError = null;
reject(promise, thenable._result);
} else {
subscribe(thenable, undefined, function (value) {
if (thenable !== value) {
resolve(promise, value);
} else {
fulfill(promise, value);
}
}, function (reason) {
reject(promise, reason);
});
}
}
function handleMaybeThenable(promise, maybeThenable) {
if (maybeThenable.constructor === promise.constructor) {
handleOwnThenable(promise, maybeThenable);
} else {
var then = getThen(maybeThenable);
if (then === GET_THEN_ERROR) {
reject(promise, GET_THEN_ERROR.error);
} else if (then === undefined) {
fulfill(promise, maybeThenable);
} else if (_rsvpUtils.isFunction(then)) {
handleForeignThenable(promise, maybeThenable, then);
} else {
fulfill(promise, maybeThenable);
}
}
}
function resolve(promise, value) {
if (promise === value) {
fulfill(promise, value);
} else if (_rsvpUtils.objectOrFunction(value)) {
handleMaybeThenable(promise, value);
} else {
fulfill(promise, value);
}
}
function publishRejection(promise) {
if (promise._onError) {
promise._onError(promise._result);
}
publish(promise);
}
function fulfill(promise, value) {
if (promise._state !== PENDING) {
return;
}
promise._result = value;
promise._state = FULFILLED;
if (promise._subscribers.length === 0) {
if (_rsvpConfig.config.instrument) {
_rsvpInstrument.default('fulfilled', promise);
}
} else {
_rsvpConfig.config.async(publish, promise);
}
}
function reject(promise, reason) {
if (promise._state !== PENDING) {
return;
}
promise._state = REJECTED;
promise._result = reason;
_rsvpConfig.config.async(publishRejection, promise);
}
function subscribe(parent, child, onFulfillment, onRejection) {
var subscribers = parent._subscribers;
var length = subscribers.length;
parent._onError = null;
subscribers[length] = child;
subscribers[length + FULFILLED] = onFulfillment;
subscribers[length + REJECTED] = onRejection;
if (length === 0 && parent._state) {
_rsvpConfig.config.async(publish, parent);
}
}
function publish(promise) {
var subscribers = promise._subscribers;
var settled = promise._state;
if (_rsvpConfig.config.instrument) {
_rsvpInstrument.default(settled === FULFILLED ? 'fulfilled' : 'rejected', promise);
}
if (subscribers.length === 0) {
return;
}
var child,
callback,
detail = promise._result;
for (var i = 0; i < subscribers.length; i += 3) {
child = subscribers[i];
callback = subscribers[i + settled];
if (child) {
invokeCallback(settled, child, callback, detail);
} else {
callback(detail);
}
}
promise._subscribers.length = 0;
}
function ErrorObject() {
this.error = null;
}
var TRY_CATCH_ERROR = new ErrorObject();
function tryCatch(callback, detail) {
try {
return callback(detail);
} catch (e) {
TRY_CATCH_ERROR.error = e;
return TRY_CATCH_ERROR;
}
}
function invokeCallback(settled, promise, callback, detail) {
var hasCallback = _rsvpUtils.isFunction(callback),
value,
error,
succeeded,
failed;
if (hasCallback) {
value = tryCatch(callback, detail);
if (value === TRY_CATCH_ERROR) {
failed = true;
error = value.error;
value = null;
} else {
succeeded = true;
}
if (promise === value) {
reject(promise, withOwnPromise());
return;
}
} else {
value = detail;
succeeded = true;
}
if (promise._state !== PENDING) {
// noop
} else if (hasCallback && succeeded) {
resolve(promise, value);
} else if (failed) {
reject(promise, error);
} else if (settled === FULFILLED) {
fulfill(promise, value);
} else if (settled === REJECTED) {
reject(promise, value);
}
}
function initializePromise(promise, resolver) {
var resolved = false;
try {
resolver(function resolvePromise(value) {
if (resolved) {
return;
}
resolved = true;
resolve(promise, value);
}, function rejectPromise(reason) {
if (resolved) {
return;
}
resolved = true;
reject(promise, reason);
});
} catch (e) {
reject(promise, e);
}
}
exports.noop = noop;
exports.resolve = resolve;
exports.reject = reject;
exports.fulfill = fulfill;
exports.subscribe = subscribe;
exports.publish = publish;
exports.publishRejection = publishRejection;
exports.initializePromise = initializePromise;
exports.invokeCallback = invokeCallback;
exports.FULFILLED = FULFILLED;
exports.REJECTED = REJECTED;
exports.PENDING = PENDING;
});
enifed('rsvp/all-settled', ['exports', 'rsvp/enumerator', 'rsvp/promise', 'rsvp/utils'], function (exports, _rsvpEnumerator, _rsvpPromise, _rsvpUtils) {
'use strict';
exports.default = allSettled;
function AllSettled(Constructor, entries, label) {
this._superConstructor(Constructor, entries, false, /* don't abort on reject */label);
}
AllSettled.prototype = _rsvpUtils.o_create(_rsvpEnumerator.default.prototype);
AllSettled.prototype._superConstructor = _rsvpEnumerator.default;
AllSettled.prototype._makeResult = _rsvpEnumerator.makeSettledResult;
AllSettled.prototype._validationError = function () {
return new Error('allSettled must be called with an array');
};
/**
`RSVP.allSettled` is similar to `RSVP.all`, but instead of implementing
a fail-fast method, it waits until all the promises have returned and
shows you all the results. This is useful if you want to handle multiple
promises' failure states together as a set.
Returns a promise that is fulfilled when all the given promises have been
settled. The return promise is fulfilled with an array of the states of
the promises passed into the `promises` array argument.
Each state object will either indicate fulfillment or rejection, and
provide the corresponding value or reason. The states will take one of
the following formats:
```javascript
{ state: 'fulfilled', value: value }
or
{ state: 'rejected', reason: reason }
```
Example:
```javascript
var promise1 = RSVP.Promise.resolve(1);
var promise2 = RSVP.Promise.reject(new Error('2'));
var promise3 = RSVP.Promise.reject(new Error('3'));
var promises = [ promise1, promise2, promise3 ];
RSVP.allSettled(promises).then(function(array){
// array == [
// { state: 'fulfilled', value: 1 },
// { state: 'rejected', reason: Error },
// { state: 'rejected', reason: Error }
// ]
// Note that for the second item, reason.message will be '2', and for the
// third item, reason.message will be '3'.
}, function(error) {
// Not run. (This block would only be called if allSettled had failed,
// for instance if passed an incorrect argument type.)
});
```
@method allSettled
@static
@for RSVP
@param {Array} entries
@param {String} label - optional string that describes the promise.
Useful for tooling.
@return {Promise} promise that is fulfilled with an array of the settled
states of the constituent promises.
*/
function allSettled(entries, label) {
return new AllSettled(_rsvpPromise.default, entries, label).promise;
}
});
enifed("rsvp/all", ["exports", "rsvp/promise"], function (exports, _rsvpPromise) {
"use strict";
exports.default = all;
/**
This is a convenient alias for `RSVP.Promise.all`.
@method all
@static
@for RSVP
@param {Array} array Array of promises.
@param {String} label An optional label. This is useful
for tooling.
*/
function all(array, label) {
return _rsvpPromise.default.all(array, label);
}
});
enifed('rsvp/asap', ['exports'], function (exports) {
'use strict';
exports.default = asap;
var len = 0;
var toString = ({}).toString;
var vertxNext;
function asap(callback, arg) {
queue[len] = callback;
queue[len + 1] = arg;
len += 2;
if (len === 2) {
// If len is 1, that means that we need to schedule an async flush.
// If additional callbacks are queued before the queue is flushed, they
// will be processed by this flush that we are scheduling.
scheduleFlush();
}
}
var browserWindow = typeof window !== 'undefined' ? window : undefined;
var browserGlobal = browserWindow || {};
var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
var isNode = typeof window === 'undefined' && typeof process !== 'undefined' && ({}).toString.call(process) === '[object process]';
// test for web worker but not in IE10
var isWorker = typeof Uint8ClampedArray !== 'undefined' && typeof importScripts !== 'undefined' && typeof MessageChannel !== 'undefined';
// node
function useNextTick() {
var nextTick = process.nextTick;
// node version 0.10.x displays a deprecation warning when nextTick is used recursively
// setImmediate should be used instead instead
var version = process.versions.node.match(/^(?:(\d+)\.)?(?:(\d+)\.)?(\*|\d+)$/);
if (Array.isArray(version) && version[1] === '0' && version[2] === '10') {
nextTick = setImmediate;
}
return function () {
nextTick(flush);
};
}
// vertx
function useVertxTimer() {
return function () {
vertxNext(flush);
};
}
function useMutationObserver() {
var iterations = 0;
var observer = new BrowserMutationObserver(flush);
var node = document.createTextNode('');
observer.observe(node, { characterData: true });
return function () {
node.data = iterations = ++iterations % 2;
};
}
// web worker
function useMessageChannel() {
var channel = new MessageChannel();
channel.port1.onmessage = flush;
return function () {
channel.port2.postMessage(0);
};
}
function useSetTimeout() {
return function () {
setTimeout(flush, 1);
};
}
var queue = new Array(1000);
function flush() {
for (var i = 0; i < len; i += 2) {
var callback = queue[i];
var arg = queue[i + 1];
callback(arg);
queue[i] = undefined;
queue[i + 1] = undefined;
}
len = 0;
}
function attemptVertex() {
try {
var r = require;
var vertx = r('vertx');
vertxNext = vertx.runOnLoop || vertx.runOnContext;
return useVertxTimer();
} catch (e) {
return useSetTimeout();
}
}
var scheduleFlush;
// Decide what async method to use to triggering processing of queued callbacks:
if (isNode) {
scheduleFlush = useNextTick();
} else if (BrowserMutationObserver) {
scheduleFlush = useMutationObserver();
} else if (isWorker) {
scheduleFlush = useMessageChannel();
} else if (browserWindow === undefined && typeof require === 'function') {
scheduleFlush = attemptVertex();
} else {
scheduleFlush = useSetTimeout();
}
});
enifed('rsvp/config', ['exports', 'rsvp/events'], function (exports, _rsvpEvents) {
'use strict';
var config = {
instrument: false
};
_rsvpEvents.default['mixin'](config);
function configure(name, value) {
if (name === 'onerror') {
// handle for legacy users that expect the actual
// error to be passed to their function added via
// `RSVP.configure('onerror', someFunctionHere);`
config['on']('error', value);
return;
}
if (arguments.length === 2) {
config[name] = value;
} else {
return config[name];
}
}
exports.config = config;
exports.configure = configure;
});
enifed('rsvp/defer', ['exports', 'rsvp/promise'], function (exports, _rsvpPromise) {
'use strict';
exports.default = defer;
/**
`RSVP.defer` returns an object similar to jQuery's `$.Deferred`.
`RSVP.defer` should be used when porting over code reliant on `$.Deferred`'s
interface. New code should use the `RSVP.Promise` constructor instead.
The object returned from `RSVP.defer` is a plain object with three properties:
* promise - an `RSVP.Promise`.
* reject - a function that causes the `promise` property on this object to
become rejected
* resolve - a function that causes the `promise` property on this object to
become fulfilled.
Example:
```javascript
var deferred = RSVP.defer();
deferred.resolve("Success!");
deferred.promise.then(function(value){
// value here is "Success!"
});
```
@method defer
@static
@for RSVP
@param {String} label optional string for labeling the promise.
Useful for tooling.
@return {Object}
*/
function defer(label) {
var deferred = {};
deferred['promise'] = new _rsvpPromise.default(function (resolve, reject) {
deferred['resolve'] = resolve;
deferred['reject'] = reject;
}, label);
return deferred;
}
});
enifed('rsvp/enumerator', ['exports', 'rsvp/utils', 'rsvp/-internal'], function (exports, _rsvpUtils, _rsvpInternal) {
'use strict';
exports.makeSettledResult = makeSettledResult;
function makeSettledResult(state, position, value) {
if (state === _rsvpInternal.FULFILLED) {
return {
state: 'fulfilled',
value: value
};
} else {
return {
state: 'rejected',
reason: value
};
}
}
function Enumerator(Constructor, input, abortOnReject, label) {
var enumerator = this;
enumerator._instanceConstructor = Constructor;
enumerator.promise = new Constructor(_rsvpInternal.noop, label);
enumerator._abortOnReject = abortOnReject;
if (enumerator._validateInput(input)) {
enumerator._input = input;
enumerator.length = input.length;
enumerator._remaining = input.length;
enumerator._init();
if (enumerator.length === 0) {
_rsvpInternal.fulfill(enumerator.promise, enumerator._result);
} else {
enumerator.length = enumerator.length || 0;
enumerator._enumerate();
if (enumerator._remaining === 0) {
_rsvpInternal.fulfill(enumerator.promise, enumerator._result);
}
}
} else {
_rsvpInternal.reject(enumerator.promise, enumerator._validationError());
}
}
exports.default = Enumerator;
Enumerator.prototype._validateInput = function (input) {
return _rsvpUtils.isArray(input);
};
Enumerator.prototype._validationError = function () {
return new Error('Array Methods must be provided an Array');
};
Enumerator.prototype._init = function () {
this._result = new Array(this.length);
};
Enumerator.prototype._enumerate = function () {
var enumerator = this;
var length = enumerator.length;
var promise = enumerator.promise;
var input = enumerator._input;
for (var i = 0; promise._state === _rsvpInternal.PENDING && i < length; i++) {
enumerator._eachEntry(input[i], i);
}
};
Enumerator.prototype._eachEntry = function (entry, i) {
var enumerator = this;
var c = enumerator._instanceConstructor;
if (_rsvpUtils.isMaybeThenable(entry)) {
if (entry.constructor === c && entry._state !== _rsvpInternal.PENDING) {
entry._onError = null;
enumerator._settledAt(entry._state, i, entry._result);
} else {
enumerator._willSettleAt(c.resolve(entry), i);
}
} else {
enumerator._remaining--;
enumerator._result[i] = enumerator._makeResult(_rsvpInternal.FULFILLED, i, entry);
}
};
Enumerator.prototype._settledAt = function (state, i, value) {
var enumerator = this;
var promise = enumerator.promise;
if (promise._state === _rsvpInternal.PENDING) {
enumerator._remaining--;
if (enumerator._abortOnReject && state === _rsvpInternal.REJECTED) {
_rsvpInternal.reject(promise, value);
} else {
enumerator._result[i] = enumerator._makeResult(state, i, value);
}
}
if (enumerator._remaining === 0) {
_rsvpInternal.fulfill(promise, enumerator._result);
}
};
Enumerator.prototype._makeResult = function (state, i, value) {
return value;
};
Enumerator.prototype._willSettleAt = function (promise, i) {
var enumerator = this;
_rsvpInternal.subscribe(promise, undefined, function (value) {
enumerator._settledAt(_rsvpInternal.FULFILLED, i, value);
}, function (reason) {
enumerator._settledAt(_rsvpInternal.REJECTED, i, reason);
});
};
});
enifed('rsvp/events', ['exports'], function (exports) {
'use strict';
function indexOf(callbacks, callback) {
for (var i = 0, l = callbacks.length; i < l; i++) {
if (callbacks[i] === callback) {
return i;
}
}
return -1;
}
function callbacksFor(object) {
var callbacks = object._promiseCallbacks;
if (!callbacks) {
callbacks = object._promiseCallbacks = {};
}
return callbacks;
}
/**
@class RSVP.EventTarget
*/
exports.default = {
/**
`RSVP.EventTarget.mixin` extends an object with EventTarget methods. For
Example:
```javascript
var object = {};
RSVP.EventTarget.mixin(object);
object.on('finished', function(event) {
// handle event
});
object.trigger('finished', { detail: value });
```
`EventTarget.mixin` also works with prototypes:
```javascript
var Person = function() {};
RSVP.EventTarget.mixin(Person.prototype);
var yehuda = new Person();
var tom = new Person();
yehuda.on('poke', function(event) {
console.log('Yehuda says OW');
});
tom.on('poke', function(event) {
console.log('Tom says OW');
});
yehuda.trigger('poke');
tom.trigger('poke');
```
@method mixin
@for RSVP.EventTarget
@private
@param {Object} object object to extend with EventTarget methods
*/
'mixin': function (object) {
object['on'] = this['on'];
object['off'] = this['off'];
object['trigger'] = this['trigger'];
object._promiseCallbacks = undefined;
return object;
},
/**
Registers a callback to be executed when `eventName` is triggered
```javascript
object.on('event', function(eventInfo){
// handle the event
});
object.trigger('event');
```
@method on
@for RSVP.EventTarget
@private
@param {String} eventName name of the event to listen for
@param {Function} callback function to be called when the event is triggered.
*/
'on': function (eventName, callback) {
if (typeof callback !== 'function') {
throw new TypeError('Callback must be a function');
}
var allCallbacks = callbacksFor(this),
callbacks;
callbacks = allCallbacks[eventName];
if (!callbacks) {
callbacks = allCallbacks[eventName] = [];
}
if (indexOf(callbacks, callback) === -1) {
callbacks.push(callback);
}
},
/**
You can use `off` to stop firing a particular callback for an event:
```javascript
function doStuff() { // do stuff! }
object.on('stuff', doStuff);
object.trigger('stuff'); // doStuff will be called
// Unregister ONLY the doStuff callback
object.off('stuff', doStuff);
object.trigger('stuff'); // doStuff will NOT be called
```
If you don't pass a `callback` argument to `off`, ALL callbacks for the
event will not be executed when the event fires. For example:
```javascript
var callback1 = function(){};
var callback2 = function(){};
object.on('stuff', callback1);
object.on('stuff', callback2);
object.trigger('stuff'); // callback1 and callback2 will be executed.
object.off('stuff');
object.trigger('stuff'); // callback1 and callback2 will not be executed!
```
@method off
@for RSVP.EventTarget
@private
@param {String} eventName event to stop listening to
@param {Function} callback optional argument. If given, only the function
given will be removed from the event's callback queue. If no `callback`
argument is given, all callbacks will be removed from the event's callback
queue.
*/
'off': function (eventName, callback) {
var allCallbacks = callbacksFor(this),
callbacks,
index;
if (!callback) {
allCallbacks[eventName] = [];
return;
}
callbacks = allCallbacks[eventName];
index = indexOf(callbacks, callback);
if (index !== -1) {
callbacks.splice(index, 1);
}
},
/**
Use `trigger` to fire custom events. For example:
```javascript
object.on('foo', function(){
console.log('foo event happened!');
});
object.trigger('foo');
// 'foo event happened!' logged to the console
```
You can also pass a value as a second argument to `trigger` that will be
passed as an argument to all event listeners for the event:
```javascript
object.on('foo', function(value){
console.log(value.name);
});
object.trigger('foo', { name: 'bar' });
// 'bar' logged to the console
```
@method trigger
@for RSVP.EventTarget
@private
@param {String} eventName name of the event to be triggered
@param {*} options optional value to be passed to any event handlers for
the given `eventName`
*/
'trigger': function (eventName, options) {
var allCallbacks = callbacksFor(this),
callbacks,
callback;
if (callbacks = allCallbacks[eventName]) {
// Don't cache the callbacks.length since it may grow
for (var i = 0; i < callbacks.length; i++) {
callback = callbacks[i];
callback(options);
}
}
}
};
});
enifed('rsvp/filter', ['exports', 'rsvp/promise', 'rsvp/utils'], function (exports, _rsvpPromise, _rsvpUtils) {
'use strict';
exports.default = filter;
/**
`RSVP.filter` is similar to JavaScript's native `filter` method, except that it
waits for all promises to become fulfilled before running the `filterFn` on
each item in given to `promises`. `RSVP.filter` returns a promise that will
become fulfilled with the result of running `filterFn` on the values the
promises become fulfilled with.
For example:
```javascript
var promise1 = RSVP.resolve(1);
var promise2 = RSVP.resolve(2);
var promise3 = RSVP.resolve(3);
var promises = [promise1, promise2, promise3];
var filterFn = function(item){
return item > 1;
};
RSVP.filter(promises, filterFn).then(function(result){
// result is [ 2, 3 ]
});
```
If any of the `promises` given to `RSVP.filter` are rejected, the first promise
that is rejected will be given as an argument to the returned promise's
rejection handler. For example:
```javascript
var promise1 = RSVP.resolve(1);
var promise2 = RSVP.reject(new Error('2'));
var promise3 = RSVP.reject(new Error('3'));
var promises = [ promise1, promise2, promise3 ];
var filterFn = function(item){
return item > 1;
};
RSVP.filter(promises, filterFn).then(function(array){
// Code here never runs because there are rejected promises!
}, function(reason) {
// reason.message === '2'
});
```
`RSVP.filter` will also wait for any promises returned from `filterFn`.
For instance, you may want to fetch a list of users then return a subset
of those users based on some asynchronous operation:
```javascript
var alice = { name: 'alice' };
var bob = { name: 'bob' };
var users = [ alice, bob ];
var promises = users.map(function(user){
return RSVP.resolve(user);
});
var filterFn = function(user){
// Here, Alice has permissions to create a blog post, but Bob does not.
return getPrivilegesForUser(user).then(function(privs){
return privs.can_create_blog_post === true;
});
};
RSVP.filter(promises, filterFn).then(function(users){
// true, because the server told us only Alice can create a blog post.
users.length === 1;
// false, because Alice is the only user present in `users`
users[0] === bob;
});
```
@method filter
@static
@for RSVP
@param {Array} promises
@param {Function} filterFn - function to be called on each resolved value to
filter the final results.
@param {String} label optional string describing the promise. Useful for
tooling.
@return {Promise}
*/
function filter(promises, filterFn, label) {
return _rsvpPromise.default.all(promises, label).then(function (values) {
if (!_rsvpUtils.isFunction(filterFn)) {
throw new TypeError("You must pass a function as filter's second argument.");
}
var length = values.length;
var filtered = new Array(length);
for (var i = 0; i < length; i++) {
filtered[i] = filterFn(values[i]);
}
return _rsvpPromise.default.all(filtered, label).then(function (filtered) {
var results = new Array(length);
var newLength = 0;
for (var i = 0; i < length; i++) {
if (filtered[i]) {
results[newLength] = values[i];
newLength++;
}
}
results.length = newLength;
return results;
});
});
}
});
enifed('rsvp/hash-settled', ['exports', 'rsvp/promise', 'rsvp/enumerator', 'rsvp/promise-hash', 'rsvp/utils'], function (exports, _rsvpPromise, _rsvpEnumerator, _rsvpPromiseHash, _rsvpUtils) {
'use strict';
exports.default = hashSettled;
function HashSettled(Constructor, object, label) {
this._superConstructor(Constructor, object, false, label);
}
HashSettled.prototype = _rsvpUtils.o_create(_rsvpPromiseHash.default.prototype);
HashSettled.prototype._superConstructor = _rsvpEnumerator.default;
HashSettled.prototype._makeResult = _rsvpEnumerator.makeSettledResult;
HashSettled.prototype._validationError = function () {
return new Error('hashSettled must be called with an object');
};
/**
`RSVP.hashSettled` is similar to `RSVP.allSettled`, but takes an object
instead of an array for its `promises` argument.
Unlike `RSVP.all` or `RSVP.hash`, which implement a fail-fast method,
but like `RSVP.allSettled`, `hashSettled` waits until all the
constituent promises have returned and then shows you all the results
with their states and values/reasons. This is useful if you want to
handle multiple promises' failure states together as a set.
Returns a promise that is fulfilled when all the given promises have been
settled, or rejected if the passed parameters are invalid.
The returned promise is fulfilled with a hash that has the same key names as
the `promises` object argument. If any of the values in the object are not
promises, they will be copied over to the fulfilled object and marked with state
'fulfilled'.
Example:
```javascript
var promises = {
myPromise: RSVP.Promise.resolve(1),
yourPromise: RSVP.Promise.resolve(2),
theirPromise: RSVP.Promise.resolve(3),
notAPromise: 4
};
RSVP.hashSettled(promises).then(function(hash){
// hash here is an object that looks like:
// {
// myPromise: { state: 'fulfilled', value: 1 },
// yourPromise: { state: 'fulfilled', value: 2 },
// theirPromise: { state: 'fulfilled', value: 3 },
// notAPromise: { state: 'fulfilled', value: 4 }
// }
});
```
If any of the `promises` given to `RSVP.hash` are rejected, the state will
be set to 'rejected' and the reason for rejection provided.
Example:
```javascript
var promises = {
myPromise: RSVP.Promise.resolve(1),
rejectedPromise: RSVP.Promise.reject(new Error('rejection')),
anotherRejectedPromise: RSVP.Promise.reject(new Error('more rejection')),
};
RSVP.hashSettled(promises).then(function(hash){
// hash here is an object that looks like:
// {
// myPromise: { state: 'fulfilled', value: 1 },
// rejectedPromise: { state: 'rejected', reason: Error },
// anotherRejectedPromise: { state: 'rejected', reason: Error },
// }
// Note that for rejectedPromise, reason.message == 'rejection',
// and for anotherRejectedPromise, reason.message == 'more rejection'.
});
```
An important note: `RSVP.hashSettled` is intended for plain JavaScript objects that
are just a set of keys and values. `RSVP.hashSettled` will NOT preserve prototype
chains.
Example:
```javascript
function MyConstructor(){
this.example = RSVP.Promise.resolve('Example');
}
MyConstructor.prototype = {
protoProperty: RSVP.Promise.resolve('Proto Property')
};
var myObject = new MyConstructor();
RSVP.hashSettled(myObject).then(function(hash){
// protoProperty will not be present, instead you will just have an
// object that looks like:
// {
// example: { state: 'fulfilled', value: 'Example' }
// }
//
// hash.hasOwnProperty('protoProperty'); // false
// 'undefined' === typeof hash.protoProperty
});
```
@method hashSettled
@for RSVP
@param {Object} object
@param {String} label optional string that describes the promise.
Useful for tooling.
@return {Promise} promise that is fulfilled when when all properties of `promises`
have been settled.
@static
*/
function hashSettled(object, label) {
return new HashSettled(_rsvpPromise.default, object, label).promise;
}
});
enifed('rsvp/hash', ['exports', 'rsvp/promise', 'rsvp/promise-hash'], function (exports, _rsvpPromise, _rsvpPromiseHash) {
'use strict';
exports.default = hash;
/**
`RSVP.hash` is similar to `RSVP.all`, but takes an object instead of an array
for its `promises` argument.
Returns a promise that is fulfilled when all the given promises have been
fulfilled, or rejected if any of them become rejected. The returned promise
is fulfilled with a hash that has the same key names as the `promises` object
argument. If any of the values in the object are not promises, they will
simply be copied over to the fulfilled object.
Example:
```javascript
var promises = {
myPromise: RSVP.resolve(1),
yourPromise: RSVP.resolve(2),
theirPromise: RSVP.resolve(3),
notAPromise: 4
};
RSVP.hash(promises).then(function(hash){
// hash here is an object that looks like:
// {
// myPromise: 1,
// yourPromise: 2,
// theirPromise: 3,
// notAPromise: 4
// }
});
````
If any of the `promises` given to `RSVP.hash` are rejected, the first promise
that is rejected will be given as the reason to the rejection handler.
Example:
```javascript
var promises = {
myPromise: RSVP.resolve(1),
rejectedPromise: RSVP.reject(new Error('rejectedPromise')),
anotherRejectedPromise: RSVP.reject(new Error('anotherRejectedPromise')),
};
RSVP.hash(promises).then(function(hash){
// Code here never runs because there are rejected promises!
}, function(reason) {
// reason.message === 'rejectedPromise'
});
```
An important note: `RSVP.hash` is intended for plain JavaScript objects that
are just a set of keys and values. `RSVP.hash` will NOT preserve prototype
chains.
Example:
```javascript
function MyConstructor(){
this.example = RSVP.resolve('Example');
}
MyConstructor.prototype = {
protoProperty: RSVP.resolve('Proto Property')
};
var myObject = new MyConstructor();
RSVP.hash(myObject).then(function(hash){
// protoProperty will not be present, instead you will just have an
// object that looks like:
// {
// example: 'Example'
// }
//
// hash.hasOwnProperty('protoProperty'); // false
// 'undefined' === typeof hash.protoProperty
});
```
@method hash
@static
@for RSVP
@param {Object} object
@param {String} label optional string that describes the promise.
Useful for tooling.
@return {Promise} promise that is fulfilled when all properties of `promises`
have been fulfilled, or rejected if any of them become rejected.
*/
function hash(object, label) {
return new _rsvpPromiseHash.default(_rsvpPromise.default, object, label).promise;
}
});
enifed('rsvp/instrument', ['exports', 'rsvp/config', 'rsvp/utils'], function (exports, _rsvpConfig, _rsvpUtils) {
'use strict';
exports.default = instrument;
var queue = [];
function scheduleFlush() {
setTimeout(function () {
var entry;
for (var i = 0; i < queue.length; i++) {
entry = queue[i];
var payload = entry.payload;
payload.guid = payload.key + payload.id;
payload.childGuid = payload.key + payload.childId;
if (payload.error) {
payload.stack = payload.error.stack;
}
_rsvpConfig.config['trigger'](entry.name, entry.payload);
}
queue.length = 0;
}, 50);
}
function instrument(eventName, promise, child) {
if (1 === queue.push({
name: eventName,
payload: {
key: promise._guidKey,
id: promise._id,
eventName: eventName,
detail: promise._result,
childId: child && child._id,
label: promise._label,
timeStamp: _rsvpUtils.now(),
error: _rsvpConfig.config["instrument-with-stack"] ? new Error(promise._label) : null
} })) {
scheduleFlush();
}
}
});
enifed('rsvp/map', ['exports', 'rsvp/promise', 'rsvp/utils'], function (exports, _rsvpPromise, _rsvpUtils) {
'use strict';
exports.default = map;
/**
`RSVP.map` is similar to JavaScript's native `map` method, except that it
waits for all promises to become fulfilled before running the `mapFn` on
each item in given to `promises`. `RSVP.map` returns a promise that will
become fulfilled with the result of running `mapFn` on the values the promises
become fulfilled with.
For example:
```javascript
var promise1 = RSVP.resolve(1);
var promise2 = RSVP.resolve(2);
var promise3 = RSVP.resolve(3);
var promises = [ promise1, promise2, promise3 ];
var mapFn = function(item){
return item + 1;
};
RSVP.map(promises, mapFn).then(function(result){
// result is [ 2, 3, 4 ]
});
```
If any of the `promises` given to `RSVP.map` are rejected, the first promise
that is rejected will be given as an argument to the returned promise's
rejection handler. For example:
```javascript
var promise1 = RSVP.resolve(1);
var promise2 = RSVP.reject(new Error('2'));
var promise3 = RSVP.reject(new Error('3'));
var promises = [ promise1, promise2, promise3 ];
var mapFn = function(item){
return item + 1;
};
RSVP.map(promises, mapFn).then(function(array){
// Code here never runs because there are rejected promises!
}, function(reason) {
// reason.message === '2'
});
```
`RSVP.map` will also wait if a promise is returned from `mapFn`. For example,
say you want to get all comments from a set of blog posts, but you need
the blog posts first because they contain a url to those comments.
```javscript
var mapFn = function(blogPost){
// getComments does some ajax and returns an RSVP.Promise that is fulfilled
// with some comments data
return getComments(blogPost.comments_url);
};
// getBlogPosts does some ajax and returns an RSVP.Promise that is fulfilled
// with some blog post data
RSVP.map(getBlogPosts(), mapFn).then(function(comments){
// comments is the result of asking the server for the comments
// of all blog posts returned from getBlogPosts()
});
```
@method map
@static
@for RSVP
@param {Array} promises
@param {Function} mapFn function to be called on each fulfilled promise.
@param {String} label optional string for labeling the promise.
Useful for tooling.
@return {Promise} promise that is fulfilled with the result of calling
`mapFn` on each fulfilled promise or value when they become fulfilled.
The promise will be rejected if any of the given `promises` become rejected.
@static
*/
function map(promises, mapFn, label) {
return _rsvpPromise.default.all(promises, label).then(function (values) {
if (!_rsvpUtils.isFunction(mapFn)) {
throw new TypeError("You must pass a function as map's second argument.");
}
var length = values.length;
var results = new Array(length);
for (var i = 0; i < length; i++) {
results[i] = mapFn(values[i]);
}
return _rsvpPromise.default.all(results, label);
});
}
});
enifed('rsvp/node', ['exports', 'rsvp/promise', 'rsvp/-internal', 'rsvp/utils'], function (exports, _rsvpPromise, _rsvpInternal, _rsvpUtils) {
'use strict';
exports.default = denodeify;
function Result() {
this.value = undefined;
}
var ERROR = new Result();
var GET_THEN_ERROR = new Result();
function getThen(obj) {
try {
return obj.then;
} catch (error) {
ERROR.value = error;
return ERROR;
}
}
function tryApply(f, s, a) {
try {
f.apply(s, a);
} catch (error) {
ERROR.value = error;
return ERROR;
}
}
function makeObject(_, argumentNames) {
var obj = {};
var name;
var i;
var length = _.length;
var args = new Array(length);
for (var x = 0; x < length; x++) {
args[x] = _[x];
}
for (i = 0; i < argumentNames.length; i++) {
name = argumentNames[i];
obj[name] = args[i + 1];
}
return obj;
}
function arrayResult(_) {
var length = _.length;
var args = new Array(length - 1);
for (var i = 1; i < length; i++) {
args[i - 1] = _[i];
}
return args;
}
function wrapThenable(then, promise) {
return {
then: function (onFulFillment, onRejection) {
return then.call(promise, onFulFillment, onRejection);
}
};
}
/**
`RSVP.denodeify` takes a 'node-style' function and returns a function that
will return an `RSVP.Promise`. You can use `denodeify` in Node.js or the
browser when you'd prefer to use promises over using callbacks. For example,
`denodeify` transforms the following:
```javascript
var fs = require('fs');
fs.readFile('myfile.txt', function(err, data){
if (err) return handleError(err);
handleData(data);
});
```
into:
```javascript
var fs = require('fs');
var readFile = RSVP.denodeify(fs.readFile);
readFile('myfile.txt').then(handleData, handleError);
```
If the node function has multiple success parameters, then `denodeify`
just returns the first one:
```javascript
var request = RSVP.denodeify(require('request'));
request('http://example.com').then(function(res) {
// ...
});
```
However, if you need all success parameters, setting `denodeify`'s
second parameter to `true` causes it to return all success parameters
as an array:
```javascript
var request = RSVP.denodeify(require('request'), true);
request('http://example.com').then(function(result) {
// result[0] -> res
// result[1] -> body
});
```
Or if you pass it an array with names it returns the parameters as a hash:
```javascript
var request = RSVP.denodeify(require('request'), ['res', 'body']);
request('http://example.com').then(function(result) {
// result.res
// result.body
});
```
Sometimes you need to retain the `this`:
```javascript
var app = require('express')();
var render = RSVP.denodeify(app.render.bind(app));
```
The denodified function inherits from the original function. It works in all
environments, except IE 10 and below. Consequently all properties of the original
function are available to you. However, any properties you change on the
denodeified function won't be changed on the original function. Example:
```javascript
var request = RSVP.denodeify(require('request')),
cookieJar = request.jar(); // <- Inheritance is used here
request('http://example.com', {jar: cookieJar}).then(function(res) {
// cookieJar.cookies holds now the cookies returned by example.com
});
```
Using `denodeify` makes it easier to compose asynchronous operations instead
of using callbacks. For example, instead of:
```javascript
var fs = require('fs');
fs.readFile('myfile.txt', function(err, data){
if (err) { ... } // Handle error
fs.writeFile('myfile2.txt', data, function(err){
if (err) { ... } // Handle error
console.log('done')
});
});
```
you can chain the operations together using `then` from the returned promise:
```javascript
var fs = require('fs');
var readFile = RSVP.denodeify(fs.readFile);
var writeFile = RSVP.denodeify(fs.writeFile);
readFile('myfile.txt').then(function(data){
return writeFile('myfile2.txt', data);
}).then(function(){
console.log('done')
}).catch(function(error){
// Handle error
});
```
@method denodeify
@static
@for RSVP
@param {Function} nodeFunc a 'node-style' function that takes a callback as
its last argument. The callback expects an error to be passed as its first
argument (if an error occurred, otherwise null), and the value from the
operation as its second argument ('function(err, value){ }').
@param {Boolean|Array} [options] An optional paramter that if set
to `true` causes the promise to fulfill with the callback's success arguments
as an array. This is useful if the node function has multiple success
paramters. If you set this paramter to an array with names, the promise will
fulfill with a hash with these names as keys and the success parameters as
values.
@return {Function} a function that wraps `nodeFunc` to return an
`RSVP.Promise`
@static
*/
function denodeify(nodeFunc, options) {
var fn = function () {
var self = this;
var l = arguments.length;
var args = new Array(l + 1);
var arg;
var promiseInput = false;
for (var i = 0; i < l; ++i) {
arg = arguments[i];
if (!promiseInput) {
// TODO: clean this up
promiseInput = needsPromiseInput(arg);
if (promiseInput === GET_THEN_ERROR) {
var p = new _rsvpPromise.default(_rsvpInternal.noop);
_rsvpInternal.reject(p, GET_THEN_ERROR.value);
return p;
} else if (promiseInput && promiseInput !== true) {
arg = wrapThenable(promiseInput, arg);
}
}
args[i] = arg;
}
var promise = new _rsvpPromise.default(_rsvpInternal.noop);
args[l] = function (err, val) {
if (err) _rsvpInternal.reject(promise, err);else if (options === undefined) _rsvpInternal.resolve(promise, val);else if (options === true) _rsvpInternal.resolve(promise, arrayResult(arguments));else if (_rsvpUtils.isArray(options)) _rsvpInternal.resolve(promise, makeObject(arguments, options));else _rsvpInternal.resolve(promise, val);
};
if (promiseInput) {
return handlePromiseInput(promise, args, nodeFunc, self);
} else {
return handleValueInput(promise, args, nodeFunc, self);
}
};
fn.__proto__ = nodeFunc;
return fn;
}
function handleValueInput(promise, args, nodeFunc, self) {
var result = tryApply(nodeFunc, self, args);
if (result === ERROR) {
_rsvpInternal.reject(promise, result.value);
}
return promise;
}
function handlePromiseInput(promise, args, nodeFunc, self) {
return _rsvpPromise.default.all(args).then(function (args) {
var result = tryApply(nodeFunc, self, args);
if (result === ERROR) {
_rsvpInternal.reject(promise, result.value);
}
return promise;
});
}
function needsPromiseInput(arg) {
if (arg && typeof arg === 'object') {
if (arg.constructor === _rsvpPromise.default) {
return true;
} else {
return getThen(arg);
}
} else {
return false;
}
}
});
enifed('rsvp/platform', ['exports'], function (exports) {
'use strict';
var platform;
/* global self */
if (typeof self === 'object') {
platform = self;
/* global global */
} else if (typeof global === 'object') {
platform = global;
} else {
throw new Error('no global: `self` or `global` found');
}
exports.default = platform;
});
enifed('rsvp/promise/all', ['exports', 'rsvp/enumerator'], function (exports, _rsvpEnumerator) {
'use strict';
exports.default = all;
/**
`RSVP.Promise.all` accepts an array of promises, and returns a new promise which
is fulfilled with an array of fulfillment values for the passed promises, or
rejected with the reason of the first passed promise to be rejected. It casts all
elements of the passed iterable to promises as it runs this algorithm.
Example:
```javascript
var promise1 = RSVP.resolve(1);
var promise2 = RSVP.resolve(2);
var promise3 = RSVP.resolve(3);
var promises = [ promise1, promise2, promise3 ];
RSVP.Promise.all(promises).then(function(array){
// The array here would be [ 1, 2, 3 ];
});
```
If any of the `promises` given to `RSVP.all` are rejected, the first promise
that is rejected will be given as an argument to the returned promises's
rejection handler. For example:
Example:
```javascript
var promise1 = RSVP.resolve(1);
var promise2 = RSVP.reject(new Error("2"));
var promise3 = RSVP.reject(new Error("3"));
var promises = [ promise1, promise2, promise3 ];
RSVP.Promise.all(promises).then(function(array){
// Code here never runs because there are rejected promises!
}, function(error) {
// error.message === "2"
});
```
@method all
@static
@param {Array} entries array of promises
@param {String} label optional string for labeling the promise.
Useful for tooling.
@return {Promise} promise that is fulfilled when all `promises` have been
fulfilled, or rejected if any of them become rejected.
@static
*/
function all(entries, label) {
return new _rsvpEnumerator.default(this, entries, true, /* abort on reject */label).promise;
}
});
enifed('rsvp/promise/race', ['exports', 'rsvp/utils', 'rsvp/-internal'], function (exports, _rsvpUtils, _rsvpInternal) {
'use strict';
exports.default = race;
/**
`RSVP.Promise.race` returns a new promise which is settled in the same way as the
first passed promise to settle.
Example:
```javascript
var promise1 = new RSVP.Promise(function(resolve, reject){
setTimeout(function(){
resolve('promise 1');
}, 200);
});
var promise2 = new RSVP.Promise(function(resolve, reject){
setTimeout(function(){
resolve('promise 2');
}, 100);
});
RSVP.Promise.race([promise1, promise2]).then(function(result){
// result === 'promise 2' because it was resolved before promise1
// was resolved.
});
```
`RSVP.Promise.race` is deterministic in that only the state of the first
settled promise matters. For example, even if other promises given to the
`promises` array argument are resolved, but the first settled promise has
become rejected before the other promises became fulfilled, the returned
promise will become rejected:
```javascript
var promise1 = new RSVP.Promise(function(resolve, reject){
setTimeout(function(){
resolve('promise 1');
}, 200);
});
var promise2 = new RSVP.Promise(function(resolve, reject){
setTimeout(function(){
reject(new Error('promise 2'));
}, 100);
});
RSVP.Promise.race([promise1, promise2]).then(function(result){
// Code here never runs
}, function(reason){
// reason.message === 'promise 2' because promise 2 became rejected before
// promise 1 became fulfilled
});
```
An example real-world use case is implementing timeouts:
```javascript
RSVP.Promise.race([ajax('foo.json'), timeout(5000)])
```
@method race
@static
@param {Array} entries array of promises to observe
@param {String} label optional string for describing the promise returned.
Useful for tooling.
@return {Promise} a promise which settles in the same way as the first passed
promise to settle.
*/
function race(entries, label) {
/*jshint validthis:true */
var Constructor = this;
var promise = new Constructor(_rsvpInternal.noop, label);
if (!_rsvpUtils.isArray(entries)) {
_rsvpInternal.reject(promise, new TypeError('You must pass an array to race.'));
return promise;
}
var length = entries.length;
function onFulfillment(value) {
_rsvpInternal.resolve(promise, value);
}
function onRejection(reason) {
_rsvpInternal.reject(promise, reason);
}
for (var i = 0; promise._state === _rsvpInternal.PENDING && i < length; i++) {
_rsvpInternal.subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection);
}
return promise;
}
});
enifed('rsvp/promise/reject', ['exports', 'rsvp/-internal'], function (exports, _rsvpInternal) {
'use strict';
exports.default = reject;
/**
`RSVP.Promise.reject` returns a promise rejected with the passed `reason`.
It is shorthand for the following:
```javascript
var promise = new RSVP.Promise(function(resolve, reject){
reject(new Error('WHOOPS'));
});
promise.then(function(value){
// Code here doesn't run because the promise is rejected!
}, function(reason){
// reason.message === 'WHOOPS'
});
```
Instead of writing the above, your code now simply becomes the following:
```javascript
var promise = RSVP.Promise.reject(new Error('WHOOPS'));
promise.then(function(value){
// Code here doesn't run because the promise is rejected!
}, function(reason){
// reason.message === 'WHOOPS'
});
```
@method reject
@static
@param {*} reason value that the returned promise will be rejected with.
@param {String} label optional string for identifying the returned promise.
Useful for tooling.
@return {Promise} a promise rejected with the given `reason`.
*/
function reject(reason, label) {
/*jshint validthis:true */
var Constructor = this;
var promise = new Constructor(_rsvpInternal.noop, label);
_rsvpInternal.reject(promise, reason);
return promise;
}
});
enifed('rsvp/promise/resolve', ['exports', 'rsvp/-internal'], function (exports, _rsvpInternal) {
'use strict';
exports.default = resolve;
/**
`RSVP.Promise.resolve` returns a promise that will become resolved with the
passed `value`. It is shorthand for the following:
```javascript
var promise = new RSVP.Promise(function(resolve, reject){
resolve(1);
});
promise.then(function(value){
// value === 1
});
```
Instead of writing the above, your code now simply becomes the following:
```javascript
var promise = RSVP.Promise.resolve(1);
promise.then(function(value){
// value === 1
});
```
@method resolve
@static
@param {*} object value that the returned promise will be resolved with
@param {String} label optional string for identifying the returned promise.
Useful for tooling.
@return {Promise} a promise that will become fulfilled with the given
`value`
*/
function resolve(object, label) {
/*jshint validthis:true */
var Constructor = this;
if (object && typeof object === 'object' && object.constructor === Constructor) {
return object;
}
var promise = new Constructor(_rsvpInternal.noop, label);
_rsvpInternal.resolve(promise, object);
return promise;
}
});
enifed('rsvp/promise-hash', ['exports', 'rsvp/enumerator', 'rsvp/-internal', 'rsvp/utils'], function (exports, _rsvpEnumerator, _rsvpInternal, _rsvpUtils) {
'use strict';
function PromiseHash(Constructor, object, label) {
this._superConstructor(Constructor, object, true, label);
}
exports.default = PromiseHash;
PromiseHash.prototype = _rsvpUtils.o_create(_rsvpEnumerator.default.prototype);
PromiseHash.prototype._superConstructor = _rsvpEnumerator.default;
PromiseHash.prototype._init = function () {
this._result = {};
};
PromiseHash.prototype._validateInput = function (input) {
return input && typeof input === 'object';
};
PromiseHash.prototype._validationError = function () {
return new Error('Promise.hash must be called with an object');
};
PromiseHash.prototype._enumerate = function () {
var enumerator = this;
var promise = enumerator.promise;
var input = enumerator._input;
var results = [];
for (var key in input) {
if (promise._state === _rsvpInternal.PENDING && Object.prototype.hasOwnProperty.call(input, key)) {
results.push({
position: key,
entry: input[key]
});
}
}
var length = results.length;
enumerator._remaining = length;
var result;
for (var i = 0; promise._state === _rsvpInternal.PENDING && i < length; i++) {
result = results[i];
enumerator._eachEntry(result.entry, result.position);
}
};
});
enifed('rsvp/promise', ['exports', 'rsvp/config', 'rsvp/instrument', 'rsvp/utils', 'rsvp/-internal', 'rsvp/promise/all', 'rsvp/promise/race', 'rsvp/promise/resolve', 'rsvp/promise/reject'], function (exports, _rsvpConfig, _rsvpInstrument, _rsvpUtils, _rsvpInternal, _rsvpPromiseAll, _rsvpPromiseRace, _rsvpPromiseResolve, _rsvpPromiseReject) {
'use strict';
exports.default = Promise;
var guidKey = 'rsvp_' + _rsvpUtils.now() + '-';
var counter = 0;
function needsResolver() {
throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
}
function needsNew() {
throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
}
/**
Promise objects represent the eventual result of an asynchronous operation. The
primary way of interacting with a promise is through its `then` method, which
registers callbacks to receive either a promise’s eventual value or the reason
why the promise cannot be fulfilled.
Terminology
-----------
- `promise` is an object or function with a `then` method whose behavior conforms to this specification.
- `thenable` is an object or function that defines a `then` method.
- `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
- `exception` is a value that is thrown using the throw statement.
- `reason` is a value that indicates why a promise was rejected.
- `settled` the final resting state of a promise, fulfilled or rejected.
A promise can be in one of three states: pending, fulfilled, or rejected.
Promises that are fulfilled have a fulfillment value and are in the fulfilled
state. Promises that are rejected have a rejection reason and are in the
rejected state. A fulfillment value is never a thenable.
Promises can also be said to *resolve* a value. If this value is also a
promise, then the original promise's settled state will match the value's
settled state. So a promise that *resolves* a promise that rejects will
itself reject, and a promise that *resolves* a promise that fulfills will
itself fulfill.
Basic Usage:
------------
```js
var promise = new Promise(function(resolve, reject) {
// on success
resolve(value);
// on failure
reject(reason);
});
promise.then(function(value) {
// on fulfillment
}, function(reason) {
// on rejection
});
```
Advanced Usage:
---------------
Promises shine when abstracting away asynchronous interactions such as
`XMLHttpRequest`s.
```js
function getJSON(url) {
return new Promise(function(resolve, reject){
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onreadystatechange = handler;
xhr.responseType = 'json';
xhr.setRequestHeader('Accept', 'application/json');
xhr.send();
function handler() {
if (this.readyState === this.DONE) {
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
}
}
};
});
}
getJSON('/posts.json').then(function(json) {
// on fulfillment
}, function(reason) {
// on rejection
});
```
Unlike callbacks, promises are great composable primitives.
```js
Promise.all([
getJSON('/posts'),
getJSON('/comments')
]).then(function(values){
values[0] // => postsJSON
values[1] // => commentsJSON
return values;
});
```
@class RSVP.Promise
@param {function} resolver
@param {String} label optional string for labeling the promise.
Useful for tooling.
@constructor
*/
function Promise(resolver, label) {
var promise = this;
promise._id = counter++;
promise._label = label;
promise._state = undefined;
promise._result = undefined;
promise._subscribers = [];
if (_rsvpConfig.config.instrument) {
_rsvpInstrument.default('created', promise);
}
if (_rsvpInternal.noop !== resolver) {
if (!_rsvpUtils.isFunction(resolver)) {
needsResolver();
}
if (!(promise instanceof Promise)) {
needsNew();
}
_rsvpInternal.initializePromise(promise, resolver);
}
}
Promise.cast = _rsvpPromiseResolve.default; // deprecated
Promise.all = _rsvpPromiseAll.default;
Promise.race = _rsvpPromiseRace.default;
Promise.resolve = _rsvpPromiseResolve.default;
Promise.reject = _rsvpPromiseReject.default;
Promise.prototype = {
constructor: Promise,
_guidKey: guidKey,
_onError: function (reason) {
var promise = this;
_rsvpConfig.config.after(function () {
if (promise._onError) {
_rsvpConfig.config['trigger']('error', reason);
}
});
},
/**
The primary way of interacting with a promise is through its `then` method,
which registers callbacks to receive either a promise's eventual value or the
reason why the promise cannot be fulfilled.
```js
findUser().then(function(user){
// user is available
}, function(reason){
// user is unavailable, and you are given the reason why
});
```
Chaining
--------
The return value of `then` is itself a promise. This second, 'downstream'
promise is resolved with the return value of the first promise's fulfillment
or rejection handler, or rejected if the handler throws an exception.
```js
findUser().then(function (user) {
return user.name;
}, function (reason) {
return 'default name';
}).then(function (userName) {
// If `findUser` fulfilled, `userName` will be the user's name, otherwise it
// will be `'default name'`
});
findUser().then(function (user) {
throw new Error('Found user, but still unhappy');
}, function (reason) {
throw new Error('`findUser` rejected and we're unhappy');
}).then(function (value) {
// never reached
}, function (reason) {
// if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
// If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
});
```
If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
```js
findUser().then(function (user) {
throw new PedagogicalException('Upstream error');
}).then(function (value) {
// never reached
}).then(function (value) {
// never reached
}, function (reason) {
// The `PedgagocialException` is propagated all the way down to here
});
```
Assimilation
------------
Sometimes the value you want to propagate to a downstream promise can only be
retrieved asynchronously. This can be achieved by returning a promise in the
fulfillment or rejection handler. The downstream promise will then be pending
until the returned promise is settled. This is called *assimilation*.
```js
findUser().then(function (user) {
return findCommentsByAuthor(user);
}).then(function (comments) {
// The user's comments are now available
});
```
If the assimliated promise rejects, then the downstream promise will also reject.
```js
findUser().then(function (user) {
return findCommentsByAuthor(user);
}).then(function (comments) {
// If `findCommentsByAuthor` fulfills, we'll have the value here
}, function (reason) {
// If `findCommentsByAuthor` rejects, we'll have the reason here
});
```
Simple Example
--------------
Synchronous Example
```javascript
var result;
try {
result = findResult();
// success
} catch(reason) {
// failure
}
```
Errback Example
```js
findResult(function(result, err){
if (err) {
// failure
} else {
// success
}
});
```
Promise Example;
```javascript
findResult().then(function(result){
// success
}, function(reason){
// failure
});
```
Advanced Example
--------------
Synchronous Example
```javascript
var author, books;
try {
author = findAuthor();
books = findBooksByAuthor(author);
// success
} catch(reason) {
// failure
}
```
Errback Example
```js
function foundBooks(books) {
}
function failure(reason) {
}
findAuthor(function(author, err){
if (err) {
failure(err);
// failure
} else {
try {
findBoooksByAuthor(author, function(books, err) {
if (err) {
failure(err);
} else {
try {
foundBooks(books);
} catch(reason) {
failure(reason);
}
}
});
} catch(error) {
failure(err);
}
// success
}
});
```
Promise Example;
```javascript
findAuthor().
then(findBooksByAuthor).
then(function(books){
// found books
}).catch(function(reason){
// something went wrong
});
```
@method then
@param {Function} onFulfillment
@param {Function} onRejection
@param {String} label optional string for labeling the promise.
Useful for tooling.
@return {Promise}
*/
then: function (onFulfillment, onRejection, label) {
var parent = this;
var state = parent._state;
if (state === _rsvpInternal.FULFILLED && !onFulfillment || state === _rsvpInternal.REJECTED && !onRejection) {
if (_rsvpConfig.config.instrument) {
_rsvpInstrument.default('chained', parent, parent);
}
return parent;
}
parent._onError = null;
var child = new parent.constructor(_rsvpInternal.noop, label);
var result = parent._result;
if (_rsvpConfig.config.instrument) {
_rsvpInstrument.default('chained', parent, child);
}
if (state) {
var callback = arguments[state - 1];
_rsvpConfig.config.async(function () {
_rsvpInternal.invokeCallback(state, child, callback, result);
});
} else {
_rsvpInternal.subscribe(parent, child, onFulfillment, onRejection);
}
return child;
},
/**
`catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
as the catch block of a try/catch statement.
```js
function findAuthor(){
throw new Error('couldn't find that author');
}
// synchronous
try {
findAuthor();
} catch(reason) {
// something went wrong
}
// async with promises
findAuthor().catch(function(reason){
// something went wrong
});
```
@method catch
@param {Function} onRejection
@param {String} label optional string for labeling the promise.
Useful for tooling.
@return {Promise}
*/
'catch': function (onRejection, label) {
return this.then(undefined, onRejection, label);
},
/**
`finally` will be invoked regardless of the promise's fate just as native
try/catch/finally behaves
Synchronous example:
```js
findAuthor() {
if (Math.random() > 0.5) {
throw new Error();
}
return new Author();
}
try {
return findAuthor(); // succeed or fail
} catch(error) {
return findOtherAuther();
} finally {
// always runs
// doesn't affect the return value
}
```
Asynchronous example:
```js
findAuthor().catch(function(reason){
return findOtherAuther();
}).finally(function(){
// author was either found, or not
});
```
@method finally
@param {Function} callback
@param {String} label optional string for labeling the promise.
Useful for tooling.
@return {Promise}
*/
'finally': function (callback, label) {
var promise = this;
var constructor = promise.constructor;
return promise.then(function (value) {
return constructor.resolve(callback()).then(function () {
return value;
});
}, function (reason) {
return constructor.resolve(callback()).then(function () {
throw reason;
});
}, label);
}
};
});
enifed('rsvp/race', ['exports', 'rsvp/promise'], function (exports, _rsvpPromise) {
'use strict';
exports.default = race;
/**
This is a convenient alias for `RSVP.Promise.race`.
@method race
@static
@for RSVP
@param {Array} array Array of promises.
@param {String} label An optional label. This is useful
for tooling.
*/
function race(array, label) {
return _rsvpPromise.default.race(array, label);
}
});
enifed('rsvp/reject', ['exports', 'rsvp/promise'], function (exports, _rsvpPromise) {
'use strict';
exports.default = reject;
/**
This is a convenient alias for `RSVP.Promise.reject`.
@method reject
@static
@for RSVP
@param {*} reason value that the returned promise will be rejected with.
@param {String} label optional string for identifying the returned promise.
Useful for tooling.
@return {Promise} a promise rejected with the given `reason`.
*/
function reject(reason, label) {
return _rsvpPromise.default.reject(reason, label);
}
});
enifed('rsvp/resolve', ['exports', 'rsvp/promise'], function (exports, _rsvpPromise) {
'use strict';
exports.default = resolve;
/**
This is a convenient alias for `RSVP.Promise.resolve`.
@method resolve
@static
@for RSVP
@param {*} value value that the returned promise will be resolved with
@param {String} label optional string for identifying the returned promise.
Useful for tooling.
@return {Promise} a promise that will become fulfilled with the given
`value`
*/
function resolve(value, label) {
return _rsvpPromise.default.resolve(value, label);
}
});
enifed("rsvp/rethrow", ["exports"], function (exports) {
/**
`RSVP.rethrow` will rethrow an error on the next turn of the JavaScript event
loop in order to aid debugging.
Promises A+ specifies that any exceptions that occur with a promise must be
caught by the promises implementation and bubbled to the last handler. For
this reason, it is recommended that you always specify a second rejection
handler function to `then`. However, `RSVP.rethrow` will throw the exception
outside of the promise, so it bubbles up to your console if in the browser,
or domain/cause uncaught exception in Node. `rethrow` will also throw the
error again so the error can be handled by the promise per the spec.
```javascript
function throws(){
throw new Error('Whoops!');
}
var promise = new RSVP.Promise(function(resolve, reject){
throws();
});
promise.catch(RSVP.rethrow).then(function(){
// Code here doesn't run because the promise became rejected due to an
// error!
}, function (err){
// handle the error here
});
```
The 'Whoops' error will be thrown on the next turn of the event loop
and you can watch for it in your console. You can also handle it using a
rejection handler given to `.then` or `.catch` on the returned promise.
@method rethrow
@static
@for RSVP
@param {Error} reason reason the promise became rejected.
@throws Error
@static
*/
"use strict";
exports.default = rethrow;
function rethrow(reason) {
setTimeout(function () {
throw reason;
});
throw reason;
}
});
enifed('rsvp/utils', ['exports'], function (exports) {
'use strict';
exports.objectOrFunction = objectOrFunction;
exports.isFunction = isFunction;
exports.isMaybeThenable = isMaybeThenable;
function objectOrFunction(x) {
return typeof x === 'function' || typeof x === 'object' && x !== null;
}
function isFunction(x) {
return typeof x === 'function';
}
function isMaybeThenable(x) {
return typeof x === 'object' && x !== null;
}
var _isArray;
if (!Array.isArray) {
_isArray = function (x) {
return Object.prototype.toString.call(x) === '[object Array]';
};
} else {
_isArray = Array.isArray;
}
var isArray = _isArray;
exports.isArray = isArray;
// Date.now is not available in browsers < IE9
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now#Compatibility
var now = Date.now || function () {
return new Date().getTime();
};
exports.now = now;
function F() {}
var o_create = Object.create || function (o) {
if (arguments.length > 1) {
throw new Error('Second argument not supported');
}
if (typeof o !== 'object') {
throw new TypeError('Argument must be an object');
}
F.prototype = o;
return new F();
};
exports.o_create = o_create;
});
enifed('rsvp', ['exports', 'rsvp/promise', 'rsvp/events', 'rsvp/node', 'rsvp/all', 'rsvp/all-settled', 'rsvp/race', 'rsvp/hash', 'rsvp/hash-settled', 'rsvp/rethrow', 'rsvp/defer', 'rsvp/config', 'rsvp/map', 'rsvp/resolve', 'rsvp/reject', 'rsvp/filter', 'rsvp/asap'], function (exports, _rsvpPromise, _rsvpEvents, _rsvpNode, _rsvpAll, _rsvpAllSettled, _rsvpRace, _rsvpHash, _rsvpHashSettled, _rsvpRethrow, _rsvpDefer, _rsvpConfig, _rsvpMap, _rsvpResolve, _rsvpReject, _rsvpFilter, _rsvpAsap) {
'use strict';
// defaults
_rsvpConfig.config.async = _rsvpAsap.default;
_rsvpConfig.config.after = function (cb) {
setTimeout(cb, 0);
};
var cast = _rsvpResolve.default;
function async(callback, arg) {
_rsvpConfig.config.async(callback, arg);
}
function on() {
_rsvpConfig.config['on'].apply(_rsvpConfig.config, arguments);
}
function off() {
_rsvpConfig.config['off'].apply(_rsvpConfig.config, arguments);
}
// Set up instrumentation through `window.__PROMISE_INTRUMENTATION__`
if (typeof window !== 'undefined' && typeof window['__PROMISE_INSTRUMENTATION__'] === 'object') {
var callbacks = window['__PROMISE_INSTRUMENTATION__'];
_rsvpConfig.configure('instrument', true);
for (var eventName in callbacks) {
if (callbacks.hasOwnProperty(eventName)) {
on(eventName, callbacks[eventName]);
}
}
}
exports.cast = cast;
exports.Promise = _rsvpPromise.default;
exports.EventTarget = _rsvpEvents.default;
exports.all = _rsvpAll.default;
exports.allSettled = _rsvpAllSettled.default;
exports.race = _rsvpRace.default;
exports.hash = _rsvpHash.default;
exports.hashSettled = _rsvpHashSettled.default;
exports.rethrow = _rsvpRethrow.default;
exports.defer = _rsvpDefer.default;
exports.denodeify = _rsvpNode.default;
exports.configure = _rsvpConfig.configure;
exports.on = on;
exports.off = off;
exports.resolve = _rsvpResolve.default;
exports.reject = _rsvpReject.default;
exports.async = async;
exports.map = _rsvpMap.default;
exports.filter = _rsvpFilter.default;
});
enifed('rsvp.umd', ['exports', 'rsvp/platform', 'rsvp'], function (exports, _rsvpPlatform, _rsvp) {
'use strict';
var RSVP = {
'race': _rsvp.race,
'Promise': _rsvp.Promise,
'allSettled': _rsvp.allSettled,
'hash': _rsvp.hash,
'hashSettled': _rsvp.hashSettled,
'denodeify': _rsvp.denodeify,
'on': _rsvp.on,
'off': _rsvp.off,
'map': _rsvp.map,
'filter': _rsvp.filter,
'resolve': _rsvp.resolve,
'reject': _rsvp.reject,
'all': _rsvp.all,
'rethrow': _rsvp.rethrow,
'defer': _rsvp.defer,
'EventTarget': _rsvp.EventTarget,
'configure': _rsvp.configure,
'async': _rsvp.async
};
/* global define:true module:true window: true */
if (typeof define === 'function' && define['amd']) {
define(function () {
return RSVP;
});
} else if (typeof module !== 'undefined' && module['exports']) {
module['exports'] = RSVP;
} else if (typeof _rsvpPlatform.default !== 'undefined') {
_rsvpPlatform.default['RSVP'] = RSVP;
}
});
enifed("vertex", ["exports"], function (exports) {
/**
* DAG Vertex
*
* @class Vertex
* @constructor
*/
"use strict";
exports.default = Vertex;
function Vertex(name) {
this.name = name;
this.incoming = {};
this.incomingNames = [];
this.hasOutgoing = false;
this.value = null;
}
});
enifed("visit", ["exports"], function (exports) {
"use strict";
exports.default = visit;
function visit(vertex, fn, visited, path) {
var name = vertex.name;
var vertices = vertex.incoming;
var names = vertex.incomingNames;
var len = names.length;
var i;
if (!visited) {
visited = {};
}
if (!path) {
path = [];
}
if (visited.hasOwnProperty(name)) {
return;
}
path.push(name);
visited[name] = true;
for (i = 0; i < len; i++) {
visit(vertices[names[i]], fn, visited, path);
}
fn(vertex, path);
path.pop();
}
});
requireModule("ember");
}());