6d655d75fe
Minifier gets really confused when you give it already-compressed javascript.
52910 lines
1.6 MiB
52910 lines
1.6 MiB
/*!
|
|
* @overview Ember - JavaScript Application Framework
|
|
* @copyright Copyright 2011-2014 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 1.10.1
|
|
*/
|
|
|
|
(function() {
|
|
var enifed, requireModule, eriuqer, requirejs, Ember;
|
|
|
|
(function() {
|
|
Ember = this.Ember = this.Ember || {};
|
|
if (typeof Ember === 'undefined') { Ember = {}; };
|
|
function UNDEFINED() { }
|
|
|
|
if (typeof Ember.__loader === 'undefined') {
|
|
var registry = {}, seen = {};
|
|
|
|
enifed = function(name, deps, callback) {
|
|
registry[name] = { deps: deps, callback: callback };
|
|
};
|
|
|
|
requirejs = eriuqer = requireModule = function(name) {
|
|
var s = seen[name];
|
|
|
|
if (s !== undefined) { return seen[name]; }
|
|
if (s === UNDEFINED) { return undefined; }
|
|
|
|
seen[name] = {};
|
|
|
|
if (!registry[name]) {
|
|
throw new Error('Could not find module ' + name);
|
|
}
|
|
|
|
var mod = registry[name];
|
|
var deps = mod.deps;
|
|
var callback = mod.callback;
|
|
var reified = [];
|
|
var exports;
|
|
var length = deps.length;
|
|
|
|
for (var i=0; i<length; i++) {
|
|
if (deps[i] === 'exports') {
|
|
reified.push(exports = {});
|
|
} else {
|
|
reified.push(requireModule(resolve(deps[i], name)));
|
|
}
|
|
}
|
|
|
|
var value = length === 0 ? callback.call(this) : callback.apply(this, reified);
|
|
|
|
return seen[name] = exports || (value === undefined ? UNDEFINED : value);
|
|
};
|
|
|
|
function resolve(child, name) {
|
|
if (child.charAt(0) !== '.') {
|
|
return child;
|
|
}
|
|
var parts = child.split('/');
|
|
var parentBase = name.split('/').slice(0, -1);
|
|
|
|
for (var i=0, l=parts.length; i<l; i++) {
|
|
var part = parts[i];
|
|
|
|
if (part === '..') { parentBase.pop(); }
|
|
else if (part === '.') { continue; }
|
|
else { parentBase.push(part); }
|
|
}
|
|
|
|
return parentBase.join('/');
|
|
}
|
|
|
|
requirejs._eak_seen = registry;
|
|
|
|
Ember.__loader = {
|
|
define: enifed,
|
|
require: eriuqer,
|
|
registry: registry
|
|
};
|
|
} else {
|
|
enifed = Ember.__loader.define;
|
|
requirejs = eriuqer = requireModule = Ember.__loader.require;
|
|
}
|
|
})();
|
|
|
|
enifed("backburner",
|
|
["backburner/utils","backburner/platform","backburner/binary-search","backburner/deferred-action-queues","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
|
|
"use strict";
|
|
var each = __dependency1__.each;
|
|
var isString = __dependency1__.isString;
|
|
var isFunction = __dependency1__.isFunction;
|
|
var isNumber = __dependency1__.isNumber;
|
|
var isCoercableNumber = __dependency1__.isCoercableNumber;
|
|
var wrapInTryCatch = __dependency1__.wrapInTryCatch;
|
|
var now = __dependency1__.now;
|
|
|
|
var needsIETryCatchFix = __dependency2__.needsIETryCatchFix;
|
|
|
|
var searchTimer = __dependency3__["default"];
|
|
|
|
var DeferredActionQueues = __dependency4__["default"];
|
|
|
|
var slice = [].slice;
|
|
var pop = [].pop;
|
|
var global = this;
|
|
|
|
function Backburner(queueNames, options) {
|
|
this.queueNames = queueNames;
|
|
this.options = options || {};
|
|
if (!this.options.defaultQueue) {
|
|
this.options.defaultQueue = queueNames[0];
|
|
}
|
|
this.instanceStack = [];
|
|
this._debouncees = [];
|
|
this._throttlers = [];
|
|
this._timers = [];
|
|
}
|
|
|
|
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 DeferredActionQueues(this.queueNames, options);
|
|
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;
|
|
}
|
|
|
|
if (onEnd) {
|
|
onEnd(currentInstance, nextInstance);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
run: function(target, method /*, args */) {
|
|
var onError = getOnError(this.options);
|
|
|
|
this.begin();
|
|
|
|
if (!method) {
|
|
method = target;
|
|
target = null;
|
|
}
|
|
|
|
if (isString(method)) {
|
|
method = target[method];
|
|
}
|
|
|
|
var args = slice.call(arguments, 2);
|
|
|
|
// guard against Safari 6's double-finally bug
|
|
var didFinally = false;
|
|
|
|
if (onError) {
|
|
try {
|
|
return method.apply(target, args);
|
|
} catch(error) {
|
|
onError(error);
|
|
} finally {
|
|
if (!didFinally) {
|
|
didFinally = true;
|
|
this.end();
|
|
}
|
|
}
|
|
} else {
|
|
try {
|
|
return method.apply(target, args);
|
|
} finally {
|
|
if (!didFinally) {
|
|
didFinally = true;
|
|
this.end();
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
join: function(target, method /*, args */) {
|
|
if (this.currentInstance) {
|
|
if (!method) {
|
|
method = target;
|
|
target = null;
|
|
}
|
|
|
|
if (isString(method)) {
|
|
method = target[method];
|
|
}
|
|
|
|
return method.apply(target, slice.call(arguments, 2));
|
|
} else {
|
|
return this.run.apply(this, arguments);
|
|
}
|
|
},
|
|
|
|
defer: function(queueName, target, method /* , args */) {
|
|
if (!method) {
|
|
method = target;
|
|
target = null;
|
|
}
|
|
|
|
if (isString(method)) {
|
|
method = target[method];
|
|
}
|
|
|
|
var stack = this.DEBUG ? new Error() : undefined;
|
|
var length = arguments.length;
|
|
var args;
|
|
|
|
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 */) {
|
|
if (!method) {
|
|
method = target;
|
|
target = null;
|
|
}
|
|
|
|
if (isString(method)) {
|
|
method = target[method];
|
|
}
|
|
|
|
var stack = this.DEBUG ? new Error() : undefined;
|
|
var length = arguments.length;
|
|
var args;
|
|
|
|
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 (isFunction(methodOrWait) || isFunction(methodOrTarget[methodOrWait])) {
|
|
target = args.shift();
|
|
method = args.shift();
|
|
wait = 0;
|
|
} else if (isCoercableNumber(methodOrWait)) {
|
|
method = args.shift();
|
|
wait = args.shift();
|
|
} else {
|
|
method = args.shift();
|
|
wait = 0;
|
|
}
|
|
} else {
|
|
var last = args[args.length - 1];
|
|
|
|
if (isCoercableNumber(last)) {
|
|
wait = args.pop();
|
|
} else {
|
|
wait = 0;
|
|
}
|
|
|
|
methodOrTarget = args[0];
|
|
methodOrArgs = args[1];
|
|
|
|
if (isFunction(methodOrArgs) || (isString(methodOrArgs) &&
|
|
methodOrTarget !== null &&
|
|
methodOrArgs in methodOrTarget)) {
|
|
target = args.shift();
|
|
method = args.shift();
|
|
} else {
|
|
method = args.shift();
|
|
}
|
|
}
|
|
|
|
var executeAt = now() + parseInt(wait, 10);
|
|
|
|
if (isString(method)) {
|
|
method = target[method];
|
|
}
|
|
|
|
var onError = getOnError(this.options);
|
|
|
|
function fn() {
|
|
if (onError) {
|
|
try {
|
|
method.apply(target, args);
|
|
} catch (e) {
|
|
onError(e);
|
|
}
|
|
} else {
|
|
method.apply(target, args);
|
|
}
|
|
}
|
|
|
|
// find position to insert
|
|
var i = searchTimer(executeAt, this._timers);
|
|
|
|
this._timers.splice(i, 0, executeAt, fn);
|
|
|
|
updateLaterTimer(this, executeAt, wait);
|
|
|
|
return fn;
|
|
},
|
|
|
|
throttle: function(target, method /* , args, wait, [immediate] */) {
|
|
var backburner = this;
|
|
var args = arguments;
|
|
var immediate = pop.call(args);
|
|
var wait, throttler, index, timer;
|
|
|
|
if (isNumber(immediate) || isString(immediate)) {
|
|
wait = immediate;
|
|
immediate = true;
|
|
} else {
|
|
wait = pop.call(args);
|
|
}
|
|
|
|
wait = parseInt(wait, 10);
|
|
|
|
index = findThrottler(target, method, this._throttlers);
|
|
if (index > -1) { return this._throttlers[index]; } // throttled
|
|
|
|
timer = global.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 = arguments;
|
|
var immediate = pop.call(args);
|
|
var wait, index, debouncee, timer;
|
|
|
|
if (isNumber(immediate) || isString(immediate)) {
|
|
wait = immediate;
|
|
immediate = false;
|
|
} else {
|
|
wait = pop.call(args);
|
|
}
|
|
|
|
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 = global.setTimeout(function() {
|
|
if (!immediate) {
|
|
backburner.run.apply(backburner, args);
|
|
}
|
|
var index = findDebouncee(target, method, backburner._debouncees);
|
|
if (index > -1) {
|
|
backburner._debouncees.splice(index, 1);
|
|
}
|
|
}, wait);
|
|
|
|
if (immediate && index === -1) {
|
|
backburner.run.apply(backburner, args);
|
|
}
|
|
|
|
debouncee = [
|
|
target,
|
|
method,
|
|
timer
|
|
];
|
|
|
|
backburner._debouncees.push(debouncee);
|
|
|
|
return debouncee;
|
|
},
|
|
|
|
cancelTimers: function() {
|
|
var clearItems = function(item) {
|
|
clearTimeout(item[2]);
|
|
};
|
|
|
|
each(this._throttlers, clearItems);
|
|
this._throttlers = [];
|
|
|
|
each(this._debouncees, clearItems);
|
|
this._debouncees = [];
|
|
|
|
if (this._laterTimer) {
|
|
clearTimeout(this._laterTimer);
|
|
this._laterTimer = null;
|
|
}
|
|
this._timers = [];
|
|
|
|
if (this._autorun) {
|
|
clearTimeout(this._autorun);
|
|
this._autorun = null;
|
|
}
|
|
},
|
|
|
|
hasTimers: function() {
|
|
return !!this._timers.length || !!this._debouncees.length || !!this._throttlers.length || this._autorun;
|
|
},
|
|
|
|
cancel: function(timer) {
|
|
var timerType = typeof timer;
|
|
|
|
if (timer && timerType === 'object' && timer.queue && timer.method) { // we're cancelling a deferOnce
|
|
return timer.queue.cancel(timer);
|
|
} else if (timerType === 'function') { // we're cancelling a setTimeout
|
|
for (var i = 0, l = this._timers.length; i < l; i += 2) {
|
|
if (this._timers[i + 1] === timer) {
|
|
this._timers.splice(i, 2); // remove the two elements
|
|
if (i === 0) {
|
|
if (this._laterTimer) { // Active timer? Then clear timer and reset for future timer
|
|
clearTimeout(this._laterTimer);
|
|
this._laterTimer = null;
|
|
}
|
|
if (this._timers.length > 0) { // Update to next available timer when available
|
|
updateLaterTimer(this, this._timers[0], this._timers[0] - now());
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
} else if (Object.prototype.toString.call(timer) === "[object Array]"){ // we're cancelling a throttle or debounce
|
|
return this._cancelItem(findThrottler, this._throttlers, timer) ||
|
|
this._cancelItem(findDebouncee, this._debouncees, timer);
|
|
} else {
|
|
return; // timer was null or not a timer
|
|
}
|
|
},
|
|
|
|
_cancelItem: function(findMethod, array, timer){
|
|
var item, index;
|
|
|
|
if (timer.length < 3) { return false; }
|
|
|
|
index = findMethod(timer[0], timer[1], array);
|
|
|
|
if (index > -1) {
|
|
|
|
item = array[index];
|
|
|
|
if (item[2] === timer[2]) {
|
|
array.splice(index, 1);
|
|
clearTimeout(timer[2]);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
};
|
|
|
|
Backburner.prototype.schedule = Backburner.prototype.defer;
|
|
Backburner.prototype.scheduleOnce = Backburner.prototype.deferOnce;
|
|
Backburner.prototype.later = Backburner.prototype.setTimeout;
|
|
|
|
if (needsIETryCatchFix) {
|
|
var originalRun = Backburner.prototype.run;
|
|
Backburner.prototype.run = wrapInTryCatch(originalRun);
|
|
|
|
var originalEnd = Backburner.prototype.end;
|
|
Backburner.prototype.end = wrapInTryCatch(originalEnd);
|
|
}
|
|
|
|
function getOnError(options) {
|
|
return options.onError || (options.onErrorTarget && options.onErrorTarget[options.onErrorMethod]);
|
|
}
|
|
|
|
function createAutorun(backburner) {
|
|
backburner.begin();
|
|
backburner._autorun = global.setTimeout(function() {
|
|
backburner._autorun = null;
|
|
backburner.end();
|
|
});
|
|
}
|
|
|
|
function updateLaterTimer(backburner, executeAt, wait) {
|
|
var n = now();
|
|
if (!backburner._laterTimer || executeAt < backburner._laterTimerExpiresAt || backburner._laterTimerExpiresAt < n) {
|
|
|
|
if (backburner._laterTimer) {
|
|
// Clear when:
|
|
// - Already expired
|
|
// - New timer is earlier
|
|
clearTimeout(backburner._laterTimer);
|
|
|
|
if (backburner._laterTimerExpiresAt < n) { // If timer was never triggered
|
|
// Calculate the left-over wait-time
|
|
wait = Math.max(0, executeAt - n);
|
|
}
|
|
}
|
|
|
|
backburner._laterTimer = global.setTimeout(function() {
|
|
backburner._laterTimer = null;
|
|
backburner._laterTimerExpiresAt = null;
|
|
executeTimers(backburner);
|
|
}, wait);
|
|
|
|
backburner._laterTimerExpiresAt = n + wait;
|
|
}
|
|
}
|
|
|
|
function executeTimers(backburner) {
|
|
var n = now();
|
|
var fns, i, l;
|
|
|
|
backburner.run(function() {
|
|
i = searchTimer(n, backburner._timers);
|
|
|
|
fns = backburner._timers.splice(0, i);
|
|
|
|
for (i = 1, l = fns.length; i < l; i += 2) {
|
|
backburner.schedule(backburner.options.defaultQueue, null, fns[i]);
|
|
}
|
|
});
|
|
|
|
if (backburner._timers.length) {
|
|
updateLaterTimer(backburner, backburner._timers[0], backburner._timers[0] - n);
|
|
}
|
|
}
|
|
|
|
function findDebouncee(target, method, debouncees) {
|
|
return findItem(target, method, debouncees);
|
|
}
|
|
|
|
function findThrottler(target, method, throttlers) {
|
|
return findItem(target, method, throttlers);
|
|
}
|
|
|
|
function findItem(target, method, collection) {
|
|
var item;
|
|
var index = -1;
|
|
|
|
for (var i = 0, l = collection.length; i < l; i++) {
|
|
item = collection[i];
|
|
if (item[0] === target && item[1] === method) {
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
__exports__["default"] = Backburner;
|
|
});
|
|
enifed("backburner.umd",
|
|
["./backburner"],
|
|
function(__dependency1__) {
|
|
"use strict";
|
|
var Backburner = __dependency1__["default"];
|
|
|
|
/* global define:true module:true window: true */
|
|
if (typeof enifed === 'function' && enifed.amd) {
|
|
enifed(function() { return Backburner; });
|
|
} else if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = Backburner;
|
|
} else if (typeof this !== 'undefined') {
|
|
this['Backburner'] = Backburner;
|
|
}
|
|
});
|
|
enifed("backburner/binary-search",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
__exports__["default"] = 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",
|
|
["./utils","./queue","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var each = __dependency1__.each;
|
|
var Queue = __dependency2__["default"];
|
|
|
|
function DeferredActionQueues(queueNames, options) {
|
|
var queues = this.queues = Object.create(null);
|
|
this.queueNames = queueNames = queueNames || [];
|
|
|
|
this.options = options;
|
|
|
|
each(queueNames, function(queueName) {
|
|
queues[queueName] = new Queue(queueName, options[queueName], options);
|
|
});
|
|
}
|
|
|
|
function noSuchQueue(name) {
|
|
throw new Error("You attempted to schedule an action in a queue (" + name + ") 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 (onceFlag) {
|
|
return queue.pushUnique(target, method, args, stack);
|
|
} else {
|
|
return queue.push(target, method, args, stack);
|
|
}
|
|
},
|
|
|
|
flush: function() {
|
|
var queues = this.queues;
|
|
var queueNames = this.queueNames;
|
|
var queueName, queue, queueItems, priorQueueNameIndex;
|
|
var queueNameIndex = 0;
|
|
var numberOfQueues = queueNames.length;
|
|
var options = this.options;
|
|
|
|
while (queueNameIndex < numberOfQueues) {
|
|
queueName = queueNames[queueNameIndex];
|
|
queue = queues[queueName];
|
|
|
|
var numberOfQueueItems = queue._queue.length;
|
|
|
|
if (numberOfQueueItems === 0) {
|
|
queueNameIndex++;
|
|
} else {
|
|
queue.flush(false /* async */);
|
|
queueNameIndex = 0;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
__exports__["default"] = DeferredActionQueues;
|
|
});
|
|
enifed("backburner/platform",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
// In IE 6-8, try/finally doesn't work without a catch.
|
|
// Unfortunately, this is impossible to test for since wrapping it in a parent try/catch doesn't trigger the bug.
|
|
// This tests for another broken try/catch behavior that only exhibits in the same versions of IE.
|
|
var needsIETryCatchFix = (function(e,x){
|
|
try{ x(); }
|
|
catch(e) { } // jshint ignore:line
|
|
return !!e;
|
|
})();
|
|
__exports__.needsIETryCatchFix = needsIETryCatchFix;
|
|
});
|
|
enifed("backburner/queue",
|
|
["./utils","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var isString = __dependency1__.isString;
|
|
|
|
function Queue(name, options, globalOptions) {
|
|
this.name = name;
|
|
this.globalOptions = globalOptions || {};
|
|
this.options = options;
|
|
this._queue = [];
|
|
this.targetQueues = Object.create(null);
|
|
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 += 4) {
|
|
var currentMethod = targetQueue[i];
|
|
var currentIndex = targetQueue[i + 1];
|
|
|
|
if (currentMethod === method) {
|
|
queue[currentIndex + 2] = args; // replace args
|
|
queue[currentIndex + 3] = stack; // replace stack
|
|
return;
|
|
}
|
|
}
|
|
|
|
targetQueue.push(
|
|
method,
|
|
queue.push(target, method, args, stack) - 4
|
|
);
|
|
},
|
|
|
|
pushUniqueWithGuid: function(guid, target, method, args, stack) {
|
|
var hasLocalQueue = this.targetQueues[guid];
|
|
|
|
if (hasLocalQueue) {
|
|
this.targetQueue(hasLocalQueue, target, method, args, stack);
|
|
} else {
|
|
this.targetQueues[guid] = [
|
|
method,
|
|
this._queue.push(target, method, args, stack) - 4
|
|
];
|
|
}
|
|
|
|
return {
|
|
queue: this,
|
|
target: target,
|
|
method: method
|
|
};
|
|
},
|
|
|
|
pushUnique: function(target, method, args, stack) {
|
|
var queue = this._queue, currentTarget, currentMethod, i, l;
|
|
var KEY = this.globalOptions.GUID_KEY;
|
|
|
|
if (target && KEY) {
|
|
var guid = target[KEY];
|
|
if (guid) {
|
|
return this.pushUniqueWithGuid(guid, target, method, args, stack);
|
|
}
|
|
}
|
|
|
|
this.pushUniqueWithoutGuid(target, method, args, stack);
|
|
|
|
return {
|
|
queue: this,
|
|
target: target,
|
|
method: method
|
|
};
|
|
},
|
|
|
|
invoke: function(target, method, args, _, _errorRecordedForStack) {
|
|
if (args && args.length > 0) {
|
|
method.apply(target, args);
|
|
} else {
|
|
method.call(target);
|
|
}
|
|
},
|
|
|
|
invokeWithOnError: function(target, method, args, onError, errorRecordedForStack) {
|
|
try {
|
|
if (args && args.length > 0) {
|
|
method.apply(target, args);
|
|
} else {
|
|
method.call(target);
|
|
}
|
|
} catch(error) {
|
|
onError(error, errorRecordedForStack);
|
|
}
|
|
},
|
|
|
|
flush: function(sync) {
|
|
var queue = this._queue;
|
|
var length = queue.length;
|
|
|
|
if (length === 0) {
|
|
return;
|
|
}
|
|
|
|
var globalOptions = this.globalOptions;
|
|
var options = this.options;
|
|
var before = options && options.before;
|
|
var after = options && options.after;
|
|
var onError = globalOptions.onError || (globalOptions.onErrorTarget &&
|
|
globalOptions.onErrorTarget[globalOptions.onErrorMethod]);
|
|
var target, method, args, errorRecordedForStack;
|
|
var invoke = onError ? this.invokeWithOnError : this.invoke;
|
|
|
|
this.targetQueues = Object.create(null);
|
|
var queueItems = this._queueBeingFlushed = this._queue.slice();
|
|
this._queue = [];
|
|
|
|
if (before) {
|
|
before();
|
|
}
|
|
|
|
for (var i = 0; i < length; i += 4) {
|
|
target = queueItems[i];
|
|
method = queueItems[i+1];
|
|
args = queueItems[i+2];
|
|
errorRecordedForStack = queueItems[i+3]; // Debugging assistance
|
|
|
|
if (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;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
__exports__["default"] = Queue;
|
|
});
|
|
enifed("backburner/utils",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
var NUMBER = /\d+/;
|
|
|
|
function each(collection, callback) {
|
|
for (var i = 0; i < collection.length; i++) {
|
|
callback(collection[i]);
|
|
}
|
|
}
|
|
|
|
__exports__.each = each;// 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';
|
|
}
|
|
|
|
__exports__.isString = isString;function isFunction(suspect) {
|
|
return typeof suspect === 'function';
|
|
}
|
|
|
|
__exports__.isFunction = isFunction;function isNumber(suspect) {
|
|
return typeof suspect === 'number';
|
|
}
|
|
|
|
__exports__.isNumber = isNumber;function isCoercableNumber(number) {
|
|
return isNumber(number) || NUMBER.test(number);
|
|
}
|
|
|
|
__exports__.isCoercableNumber = isCoercableNumber;function wrapInTryCatch(func) {
|
|
return function () {
|
|
try {
|
|
return func.apply(this, arguments);
|
|
} catch (e) {
|
|
throw e;
|
|
}
|
|
};
|
|
}
|
|
|
|
__exports__.wrapInTryCatch = wrapInTryCatch;
|
|
});
|
|
enifed("calculateVersion",
|
|
[],
|
|
function() {
|
|
"use strict";
|
|
'use strict';
|
|
|
|
var fs = eriuqer('fs');
|
|
var path = eriuqer('path');
|
|
|
|
module.exports = function () {
|
|
var packageVersion = eriuqer('../package.json').version;
|
|
var output = [packageVersion];
|
|
var gitPath = path.join(__dirname,'..','.git');
|
|
var headFilePath = path.join(gitPath, 'HEAD');
|
|
|
|
if (packageVersion.indexOf('+') > -1) {
|
|
try {
|
|
if (fs.existsSync(headFilePath)) {
|
|
var headFile = fs.readFileSync(headFilePath, {encoding: 'utf8'});
|
|
var branchName = headFile.split('/').slice(-1)[0].trim();
|
|
var refPath = headFile.split(' ')[1];
|
|
var branchSHA;
|
|
|
|
if (refPath) {
|
|
var branchPath = path.join(gitPath, refPath.trim());
|
|
branchSHA = fs.readFileSync(branchPath);
|
|
} else {
|
|
branchSHA = branchName;
|
|
}
|
|
|
|
output.push(branchSHA.slice(0,10));
|
|
}
|
|
} catch (err) {
|
|
console.error(err.stack);
|
|
}
|
|
return output.join('.');
|
|
} else {
|
|
return packageVersion;
|
|
}
|
|
};
|
|
});
|
|
enifed("container",
|
|
["container/container","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"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'))`);
|
|
*/
|
|
Ember.MODEL_FACTORY_INJECTIONS = false;
|
|
|
|
if (Ember.ENV && typeof Ember.ENV.MODEL_FACTORY_INJECTIONS !== 'undefined') {
|
|
Ember.MODEL_FACTORY_INJECTIONS = !!Ember.ENV.MODEL_FACTORY_INJECTIONS;
|
|
}
|
|
|
|
|
|
var Container = __dependency1__["default"];
|
|
|
|
__exports__["default"] = Container;
|
|
});
|
|
enifed("container/container",
|
|
["ember-metal/core","ember-metal/keys","ember-metal/dictionary","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.assert
|
|
var emberKeys = __dependency2__["default"];
|
|
var dictionary = __dependency3__["default"];
|
|
|
|
// A lightweight container that helps to assemble and decouple components.
|
|
// Public api for the container is still in flux.
|
|
// The public api, specified on the application namespace should be considered the stable api.
|
|
function Container(parent) {
|
|
this.parent = parent;
|
|
this.children = [];
|
|
|
|
this.resolver = parent && parent.resolver || function() {};
|
|
|
|
this.registry = dictionary(parent ? parent.registry : null);
|
|
this.cache = dictionary(parent ? parent.cache : null);
|
|
this.factoryCache = dictionary(parent ? parent.factoryCache : null);
|
|
this.resolveCache = dictionary(parent ? parent.resolveCache : null);
|
|
this.typeInjections = dictionary(parent ? parent.typeInjections : null);
|
|
this.injections = dictionary(null);
|
|
this.normalizeCache = dictionary(null);
|
|
|
|
this.validationCache = dictionary(parent ? parent.validationCache : null);
|
|
|
|
|
|
this.factoryTypeInjections = dictionary(parent ? parent.factoryTypeInjections : null);
|
|
this.factoryInjections = dictionary(null);
|
|
|
|
this._options = dictionary(parent ? parent._options : null);
|
|
this._typeOptions = dictionary(parent ? parent._typeOptions : null);
|
|
}
|
|
|
|
Container.prototype = {
|
|
|
|
/**
|
|
@property parent
|
|
@type Container
|
|
@default null
|
|
*/
|
|
parent: null,
|
|
|
|
/**
|
|
@property children
|
|
@type Array
|
|
@default []
|
|
*/
|
|
children: null,
|
|
|
|
/**
|
|
@property resolver
|
|
@type function
|
|
*/
|
|
resolver: null,
|
|
|
|
/**
|
|
@property registry
|
|
@type InheritingDict
|
|
*/
|
|
registry: null,
|
|
|
|
/**
|
|
@property cache
|
|
@type InheritingDict
|
|
*/
|
|
cache: null,
|
|
|
|
/**
|
|
@property typeInjections
|
|
@type InheritingDict
|
|
*/
|
|
typeInjections: null,
|
|
|
|
/**
|
|
@property injections
|
|
@type Object
|
|
@default {}
|
|
*/
|
|
injections: null,
|
|
|
|
/**
|
|
@private
|
|
|
|
@property _options
|
|
@type InheritingDict
|
|
@default null
|
|
*/
|
|
_options: null,
|
|
|
|
/**
|
|
@private
|
|
|
|
@property _typeOptions
|
|
@type InheritingDict
|
|
*/
|
|
_typeOptions: null,
|
|
|
|
/**
|
|
Returns a new child of the current container. These children are configured
|
|
to correctly inherit from the current container.
|
|
|
|
@method child
|
|
@return {Container}
|
|
*/
|
|
child: function() {
|
|
var container = new Container(this);
|
|
this.children.push(container);
|
|
return container;
|
|
},
|
|
|
|
/**
|
|
Registers a factory for later injection.
|
|
|
|
Example:
|
|
|
|
```javascript
|
|
var container = new Container();
|
|
|
|
container.register('model:user', Person, {singleton: false });
|
|
container.register('fruit:favorite', Orange);
|
|
container.register('communication:main', Email, {singleton: false});
|
|
```
|
|
|
|
@method register
|
|
@param {String} fullName
|
|
@param {Function} factory
|
|
@param {Object} options
|
|
*/
|
|
register: function(fullName, factory, options) {
|
|
Ember.assert('fullName must be a proper full name', validateFullName(fullName));
|
|
|
|
if (factory === undefined) {
|
|
throw new TypeError('Attempting to register an unknown factory: `' + fullName + '`');
|
|
}
|
|
|
|
var normalizedName = this.normalize(fullName);
|
|
|
|
if (normalizedName in this.cache) {
|
|
throw new Error('Cannot re-register: `' + fullName +'`, as it has already been looked up.');
|
|
}
|
|
|
|
this.registry[normalizedName] = factory;
|
|
this._options[normalizedName] = (options || {});
|
|
},
|
|
|
|
/**
|
|
Unregister a fullName
|
|
|
|
```javascript
|
|
var container = new Container();
|
|
container.register('model:user', User);
|
|
|
|
container.lookup('model:user') instanceof User //=> true
|
|
|
|
container.unregister('model:user')
|
|
container.lookup('model:user') === undefined //=> true
|
|
```
|
|
|
|
@method unregister
|
|
@param {String} fullName
|
|
*/
|
|
unregister: function(fullName) {
|
|
Ember.assert('fullName must be a proper full name', validateFullName(fullName));
|
|
|
|
var normalizedName = this.normalize(fullName);
|
|
|
|
delete this.registry[normalizedName];
|
|
delete this.cache[normalizedName];
|
|
delete this.factoryCache[normalizedName];
|
|
delete this.resolveCache[normalizedName];
|
|
delete this._options[normalizedName];
|
|
|
|
delete this.validationCache[normalizedName];
|
|
|
|
},
|
|
|
|
/**
|
|
Given a fullName return the corresponding factory.
|
|
|
|
By default `resolve` will retrieve the factory from
|
|
its container's registry.
|
|
|
|
```javascript
|
|
var container = new Container();
|
|
container.register('api:twitter', Twitter);
|
|
|
|
container.resolve('api:twitter') // => Twitter
|
|
```
|
|
|
|
Optionally the container 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 container = new Container();
|
|
container.resolver = function(fullName) {
|
|
// lookup via the module system of choice
|
|
};
|
|
|
|
// the twitter factory is added to the module system
|
|
container.resolve('api:twitter') // => Twitter
|
|
```
|
|
|
|
@method resolve
|
|
@param {String} fullName
|
|
@return {Function} fullName's factory
|
|
*/
|
|
resolve: function(fullName) {
|
|
Ember.assert('fullName must be a proper full name', validateFullName(fullName));
|
|
return resolve(this, this.normalize(fullName));
|
|
},
|
|
|
|
/**
|
|
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`.
|
|
|
|
@method describe
|
|
@param {String} fullName
|
|
@return {string} described fullName
|
|
*/
|
|
describe: function(fullName) {
|
|
return fullName;
|
|
},
|
|
|
|
/**
|
|
A hook to enable custom fullName normalization behaviour
|
|
|
|
@method normalizeFullName
|
|
@param {String} fullName
|
|
@return {string} normalized fullName
|
|
*/
|
|
normalizeFullName: function(fullName) {
|
|
return fullName;
|
|
},
|
|
|
|
/**
|
|
normalize a fullName based on the applications conventions
|
|
|
|
@method normalize
|
|
@param {String} fullName
|
|
@return {string} normalized fullName
|
|
*/
|
|
normalize: function(fullName) {
|
|
return this.normalizeCache[fullName] || (
|
|
this.normalizeCache[fullName] = this.normalizeFullName(fullName)
|
|
);
|
|
},
|
|
|
|
/**
|
|
@method makeToString
|
|
|
|
@param {any} factory
|
|
@param {string} fullName
|
|
@return {function} toString function
|
|
*/
|
|
makeToString: function(factory, fullName) {
|
|
return factory.toString();
|
|
},
|
|
|
|
/**
|
|
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 container = new Container();
|
|
container.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 container = new Container();
|
|
container.register('api:twitter', Twitter);
|
|
|
|
var twitter = container.lookup('api:twitter', { singleton: false });
|
|
var twitter2 = container.lookup('api:twitter', { singleton: false });
|
|
|
|
twitter === twitter2; //=> false
|
|
```
|
|
|
|
@method lookup
|
|
@param {String} fullName
|
|
@param {Object} options
|
|
@return {any}
|
|
*/
|
|
lookup: function(fullName, options) {
|
|
Ember.assert('fullName must be a proper full name', validateFullName(fullName));
|
|
return lookup(this, this.normalize(fullName), options);
|
|
},
|
|
|
|
/**
|
|
Given a fullName return the corresponding factory.
|
|
|
|
@method lookupFactory
|
|
@param {String} fullName
|
|
@return {any}
|
|
*/
|
|
lookupFactory: function(fullName) {
|
|
Ember.assert('fullName must be a proper full name', validateFullName(fullName));
|
|
return factoryFor(this, this.normalize(fullName));
|
|
},
|
|
|
|
/**
|
|
Given a fullName check if the container is aware of its factory
|
|
or singleton instance.
|
|
|
|
@method has
|
|
@param {String} fullName
|
|
@return {Boolean}
|
|
*/
|
|
has: function(fullName) {
|
|
Ember.assert('fullName must be a proper full name', validateFullName(fullName));
|
|
return has(this, this.normalize(fullName));
|
|
},
|
|
|
|
/**
|
|
Allow registering options for all factories of a type.
|
|
|
|
```javascript
|
|
var container = new Container();
|
|
|
|
// if all of type `connection` must not be singletons
|
|
container.optionsForType('connection', { singleton: false });
|
|
|
|
container.register('connection:twitter', TwitterConnection);
|
|
container.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
|
|
```
|
|
|
|
@method optionsForType
|
|
@param {String} type
|
|
@param {Object} options
|
|
*/
|
|
optionsForType: function(type, options) {
|
|
if (this.parent) { illegalChildOperation('optionsForType'); }
|
|
|
|
this._typeOptions[type] = options;
|
|
},
|
|
|
|
/**
|
|
@method options
|
|
@param {String} fullName
|
|
@param {Object} options
|
|
*/
|
|
options: function(fullName, options) {
|
|
options = options || {};
|
|
var normalizedName = this.normalize(fullName);
|
|
this._options[normalizedName] = options;
|
|
},
|
|
|
|
/**
|
|
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 container = new Container();
|
|
|
|
container.register('router:main', Router);
|
|
container.register('controller:user', UserController);
|
|
container.register('controller:post', PostController);
|
|
|
|
container.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) {
|
|
Ember.assert('fullName must be a proper full name', validateFullName(fullName));
|
|
|
|
if (this.parent) { illegalChildOperation('typeInjection'); }
|
|
|
|
var fullNameType = fullName.split(':')[0];
|
|
if (fullNameType === type) {
|
|
throw new Error('Cannot inject a `' + fullName +
|
|
'` on other ' + type +
|
|
'(s). Register the `' + fullName +
|
|
'` as a different type and perform the typeInjection.');
|
|
}
|
|
|
|
addTypeInjection(this.typeInjections, type, property, 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 container = new Container();
|
|
|
|
container.register('source:main', Source);
|
|
container.register('model:user', User);
|
|
container.register('model:post', Post);
|
|
|
|
// injecting one fullName on another fullName
|
|
// eg. each user model gets a post model
|
|
container.injection('model:user', 'post', 'model:post');
|
|
|
|
// injecting one fullName on another type
|
|
container.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
|
|
```
|
|
|
|
@method injection
|
|
@param {String} factoryName
|
|
@param {String} property
|
|
@param {String} injectionName
|
|
*/
|
|
injection: function(fullName, property, injectionName) {
|
|
if (this.parent) { illegalChildOperation('injection'); }
|
|
|
|
validateFullName(injectionName);
|
|
var normalizedInjectionName = this.normalize(injectionName);
|
|
|
|
if (fullName.indexOf(':') === -1) {
|
|
return this.typeInjection(fullName, property, normalizedInjectionName);
|
|
}
|
|
|
|
Ember.assert('fullName must be a proper full name', validateFullName(fullName));
|
|
var normalizedName = this.normalize(fullName);
|
|
|
|
if (this.cache[normalizedName]) {
|
|
throw new Error("Attempted to register an injection for a type that has already been looked up. ('" +
|
|
normalizedName + "', '" +
|
|
property + "', '" +
|
|
injectionName + "')");
|
|
}
|
|
|
|
addInjection(initRules(this.injections, normalizedName), property, 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 container = new Container();
|
|
|
|
container.register('store:main', SomeStore);
|
|
|
|
container.factoryTypeInjection('model', 'store', 'store:main');
|
|
|
|
var store = container.lookup('store:main');
|
|
var UserFactory = container.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) {
|
|
if (this.parent) { illegalChildOperation('factoryTypeInjection'); }
|
|
|
|
addTypeInjection(this.factoryTypeInjections, type, property, this.normalize(fullName));
|
|
},
|
|
|
|
/**
|
|
Defines factory injection rules.
|
|
|
|
Similar to regular injection rules, but are run against factories, via
|
|
`Container#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 container = new Container();
|
|
|
|
container.register('store:main', Store);
|
|
container.register('store:secondary', OtherStore);
|
|
container.register('model:user', User);
|
|
container.register('model:post', Post);
|
|
|
|
// injecting one fullName on another type
|
|
container.factoryInjection('model', 'store', 'store:main');
|
|
|
|
// injecting one fullName on another fullName
|
|
container.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
|
|
```
|
|
|
|
@method factoryInjection
|
|
@param {String} factoryName
|
|
@param {String} property
|
|
@param {String} injectionName
|
|
*/
|
|
factoryInjection: function(fullName, property, injectionName) {
|
|
if (this.parent) { illegalChildOperation('injection'); }
|
|
|
|
var normalizedName = this.normalize(fullName);
|
|
var normalizedInjectionName = this.normalize(injectionName);
|
|
|
|
validateFullName(injectionName);
|
|
|
|
if (fullName.indexOf(':') === -1) {
|
|
return this.factoryTypeInjection(normalizedName, property, normalizedInjectionName);
|
|
}
|
|
|
|
Ember.assert('fullName must be a proper full name', validateFullName(fullName));
|
|
|
|
if (this.factoryCache[normalizedName]) {
|
|
throw new Error('Attempted to register a factoryInjection for a type that has already ' +
|
|
'been looked up. (\'' + normalizedName + '\', \'' + property + '\', \'' + injectionName + '\')');
|
|
}
|
|
|
|
addInjection(initRules(this.factoryInjections, normalizedName), property, normalizedInjectionName);
|
|
},
|
|
|
|
/**
|
|
A depth first traversal, destroying the container, its descendant containers and all
|
|
their managed objects.
|
|
|
|
@method destroy
|
|
*/
|
|
destroy: function() {
|
|
for (var i = 0, length = this.children.length; i < length; i++) {
|
|
this.children[i].destroy();
|
|
}
|
|
|
|
this.children = [];
|
|
|
|
eachDestroyable(this, function(item) {
|
|
item.destroy();
|
|
});
|
|
|
|
this.parent = undefined;
|
|
this.isDestroyed = true;
|
|
},
|
|
|
|
/**
|
|
@method reset
|
|
*/
|
|
reset: function() {
|
|
for (var i = 0, length = this.children.length; i < length; i++) {
|
|
resetCache(this.children[i]);
|
|
}
|
|
|
|
resetCache(this);
|
|
}
|
|
};
|
|
|
|
function resolve(container, normalizedName) {
|
|
var cached = container.resolveCache[normalizedName];
|
|
if (cached) { return cached; }
|
|
|
|
var resolved = container.resolver(normalizedName) || container.registry[normalizedName];
|
|
container.resolveCache[normalizedName] = resolved;
|
|
|
|
return resolved;
|
|
}
|
|
|
|
function has(container, fullName){
|
|
if (container.cache[fullName]) {
|
|
return true;
|
|
}
|
|
|
|
return container.resolve(fullName) !== undefined;
|
|
}
|
|
|
|
function lookup(container, fullName, options) {
|
|
options = options || {};
|
|
|
|
if (container.cache[fullName] && options.singleton !== false) {
|
|
return container.cache[fullName];
|
|
}
|
|
|
|
var value = instantiate(container, fullName);
|
|
|
|
if (value === undefined) { return; }
|
|
|
|
if (isSingleton(container, fullName) && options.singleton !== false) {
|
|
container.cache[fullName] = value;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
function illegalChildOperation(operation) {
|
|
throw new Error(operation + ' is not currently supported on child containers');
|
|
}
|
|
|
|
function isSingleton(container, fullName) {
|
|
var singleton = option(container, fullName, 'singleton');
|
|
|
|
return singleton !== false;
|
|
}
|
|
|
|
function buildInjections(container, injections) {
|
|
var hash = {};
|
|
|
|
if (!injections) { return hash; }
|
|
|
|
validateInjections(container, injections);
|
|
|
|
var injection;
|
|
|
|
for (var i = 0, length = injections.length; i < length; i++) {
|
|
injection = injections[i];
|
|
hash[injection.property] = lookup(container, injection.fullName);
|
|
}
|
|
|
|
return hash;
|
|
}
|
|
|
|
function validateInjections(container, injections) {
|
|
if (!injections) { return; }
|
|
|
|
var fullName;
|
|
|
|
for (var i = 0, length = injections.length; i < length; i++) {
|
|
fullName = injections[i].fullName;
|
|
|
|
if (!container.has(fullName)) {
|
|
throw new Error('Attempting to inject an unknown injection: `' + fullName + '`');
|
|
}
|
|
}
|
|
}
|
|
|
|
function option(container, fullName, optionName) {
|
|
var options = container._options[fullName];
|
|
|
|
if (options && options[optionName] !== undefined) {
|
|
return options[optionName];
|
|
}
|
|
|
|
var type = fullName.split(':')[0];
|
|
options = container._typeOptions[type];
|
|
|
|
if (options) {
|
|
return options[optionName];
|
|
}
|
|
}
|
|
|
|
function factoryFor(container, fullName) {
|
|
var cache = container.factoryCache;
|
|
if (cache[fullName]) {
|
|
return cache[fullName];
|
|
}
|
|
var factory = container.resolve(fullName);
|
|
if (factory === undefined) { return; }
|
|
|
|
var type = fullName.split(':')[0];
|
|
if (!factory || typeof factory.extend !== 'function' || (!Ember.MODEL_FACTORY_INJECTIONS && type === 'model')) {
|
|
if (factory && typeof factory._onLookup === 'function') {
|
|
factory._onLookup(fullName);
|
|
}
|
|
|
|
// TODO: think about a 'safe' merge style extension
|
|
// for now just fallback to create time injection
|
|
cache[fullName] = factory;
|
|
return factory;
|
|
} else {
|
|
var injections = injectionsFor(container, fullName);
|
|
var factoryInjections = factoryInjectionsFor(container, fullName);
|
|
|
|
factoryInjections._toString = container.makeToString(factory, fullName);
|
|
|
|
var injectedFactory = factory.extend(injections);
|
|
injectedFactory.reopenClass(factoryInjections);
|
|
|
|
if (factory && typeof factory._onLookup === 'function') {
|
|
factory._onLookup(fullName);
|
|
}
|
|
|
|
cache[fullName] = injectedFactory;
|
|
|
|
return injectedFactory;
|
|
}
|
|
}
|
|
|
|
function injectionsFor(container, fullName) {
|
|
var splitName = fullName.split(':');
|
|
var type = splitName[0];
|
|
var injections = [];
|
|
|
|
injections = injections.concat(container.typeInjections[type] || []);
|
|
injections = injections.concat(container.injections[fullName] || []);
|
|
|
|
injections = buildInjections(container, injections);
|
|
injections._debugContainerKey = fullName;
|
|
injections.container = container;
|
|
|
|
return injections;
|
|
}
|
|
|
|
function factoryInjectionsFor(container, fullName) {
|
|
var splitName = fullName.split(':');
|
|
var type = splitName[0];
|
|
var factoryInjections = [];
|
|
|
|
factoryInjections = factoryInjections.concat(container.factoryTypeInjections[type] || []);
|
|
factoryInjections = factoryInjections.concat(container.factoryInjections[fullName] || []);
|
|
|
|
factoryInjections = buildInjections(container, factoryInjections);
|
|
factoryInjections._debugContainerKey = fullName;
|
|
|
|
return factoryInjections;
|
|
}
|
|
|
|
function normalizeInjectionsHash(hash) {
|
|
var injections = [];
|
|
|
|
for (var key in hash) {
|
|
if (hash.hasOwnProperty(key)) {
|
|
Ember.assert("Expected a proper full name, given '" + hash[key] + "'", validateFullName(hash[key]));
|
|
|
|
addInjection(injections, key, hash[key]);
|
|
}
|
|
}
|
|
|
|
return injections;
|
|
}
|
|
|
|
function instantiate(container, fullName) {
|
|
var factory = factoryFor(container, fullName);
|
|
var lazyInjections, validationCache;
|
|
|
|
if (option(container, 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();
|
|
|
|
validateInjections(container, normalizeInjectionsHash(lazyInjections));
|
|
}
|
|
|
|
validationCache[fullName] = true;
|
|
|
|
|
|
if (typeof factory.extend === 'function') {
|
|
// assume the factory was extendable and is already injected
|
|
return factory.create();
|
|
} else {
|
|
// assume the factory was extendable
|
|
// to create time injections
|
|
// TODO: support new'ing for instantiation and merge injections for pure JS Functions
|
|
return factory.create(injectionsFor(container, fullName));
|
|
}
|
|
}
|
|
}
|
|
|
|
function eachDestroyable(container, callback) {
|
|
var cache = container.cache;
|
|
var keys = emberKeys(cache);
|
|
var key, value;
|
|
|
|
for (var i = 0, l = keys.length; i < l; i++) {
|
|
key = keys[i];
|
|
value = cache[key];
|
|
|
|
if (option(container, key, 'instantiate') !== false) {
|
|
callback(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
function resetCache(container) {
|
|
eachDestroyable(container, function(value) {
|
|
value.destroy();
|
|
});
|
|
|
|
container.cache.dict = dictionary(null);
|
|
}
|
|
|
|
function addTypeInjection(rules, type, property, fullName) {
|
|
var injections = rules[type];
|
|
|
|
if (!injections) {
|
|
injections = [];
|
|
rules[type] = injections;
|
|
}
|
|
|
|
injections.push({
|
|
property: property,
|
|
fullName: fullName
|
|
});
|
|
}
|
|
|
|
var VALID_FULL_NAME_REGEXP = /^[^:]+.+:[^:]+$/;
|
|
function validateFullName(fullName) {
|
|
if (!VALID_FULL_NAME_REGEXP.test(fullName)) {
|
|
throw new TypeError('Invalid Fullname, expected: `type:name` got: ' + fullName);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function initRules(rules, factoryName) {
|
|
return rules[factoryName] || (rules[factoryName] = []);
|
|
}
|
|
|
|
function addInjection(injections, property, injectionName) {
|
|
injections.push({
|
|
property: property,
|
|
fullName: injectionName
|
|
});
|
|
}
|
|
|
|
__exports__["default"] = Container;
|
|
});
|
|
enifed("dag-map",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
function visit(vertex, fn, visited, path) {
|
|
var name = vertex.name;
|
|
var vertices = vertex.incoming;
|
|
var names = vertex.incomingNames;
|
|
var len = names.length;
|
|
var i;
|
|
|
|
if (!visited) {
|
|
visited = {};
|
|
}
|
|
if (!path) {
|
|
path = [];
|
|
}
|
|
if (visited.hasOwnProperty(name)) {
|
|
return;
|
|
}
|
|
path.push(name);
|
|
visited[name] = true;
|
|
for (i = 0; i < len; i++) {
|
|
visit(vertices[names[i]], fn, visited, path);
|
|
}
|
|
fn(vertex, path);
|
|
path.pop();
|
|
}
|
|
|
|
|
|
/**
|
|
* DAG stands for Directed acyclic graph.
|
|
*
|
|
* It is used to build a graph of dependencies checking that there isn't circular
|
|
* dependencies. p.e Registering initializers with a certain precedence order.
|
|
*
|
|
* @class DAG
|
|
* @constructor
|
|
*/
|
|
function DAG() {
|
|
this.names = [];
|
|
this.vertices = Object.create(null);
|
|
}
|
|
|
|
/**
|
|
* DAG Vertex
|
|
*
|
|
* @class Vertex
|
|
* @constructor
|
|
*/
|
|
|
|
function Vertex(name) {
|
|
this.name = name;
|
|
this.incoming = {};
|
|
this.incomingNames = [];
|
|
this.hasOutgoing = false;
|
|
this.value = null;
|
|
}
|
|
|
|
/**
|
|
* Adds a vertex entry to the graph unless it is already added.
|
|
*
|
|
* @private
|
|
* @method add
|
|
* @param {String} name The name of the vertex to add
|
|
*/
|
|
DAG.prototype.add = function(name) {
|
|
if (!name) {
|
|
throw new Error("Can't add Vertex without name");
|
|
}
|
|
if (this.vertices[name] !== undefined) {
|
|
return this.vertices[name];
|
|
}
|
|
var vertex = new Vertex(name);
|
|
this.vertices[name] = vertex;
|
|
this.names.push(name);
|
|
return vertex;
|
|
};
|
|
|
|
/**
|
|
* Adds a vertex to the graph and sets its value.
|
|
*
|
|
* @private
|
|
* @method map
|
|
* @param {String} name The name of the vertex.
|
|
* @param value The value to put in the vertex.
|
|
*/
|
|
DAG.prototype.map = function(name, value) {
|
|
this.add(name).value = value;
|
|
};
|
|
|
|
/**
|
|
* Connects the vertices with the given names, adding them to the graph if
|
|
* necessary, only if this does not produce is any circular dependency.
|
|
*
|
|
* @private
|
|
* @method addEdge
|
|
* @param {String} fromName The name the vertex where the edge starts.
|
|
* @param {String} toName The name the vertex where the edge ends.
|
|
*/
|
|
DAG.prototype.addEdge = function(fromName, toName) {
|
|
if (!fromName || !toName || fromName === toName) {
|
|
return;
|
|
}
|
|
var from = this.add(fromName);
|
|
var to = this.add(toName);
|
|
if (to.incoming.hasOwnProperty(fromName)) {
|
|
return;
|
|
}
|
|
function checkCycle(vertex, path) {
|
|
if (vertex.name === toName) {
|
|
throw new Error("cycle detected: " + toName + " <- " + path.join(" <- "));
|
|
}
|
|
}
|
|
visit(from, checkCycle);
|
|
from.hasOutgoing = true;
|
|
to.incoming[fromName] = from;
|
|
to.incomingNames.push(fromName);
|
|
};
|
|
|
|
/**
|
|
* Visits all the vertex of the graph calling the given function with each one,
|
|
* ensuring that the vertices are visited respecting their precedence.
|
|
*
|
|
* @method topsort
|
|
* @param {Function} fn The function to be invoked on each vertex.
|
|
*/
|
|
DAG.prototype.topsort = function(fn) {
|
|
var visited = {};
|
|
var vertices = this.vertices;
|
|
var names = this.names;
|
|
var len = names.length;
|
|
var i, vertex;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
vertex = vertices[names[i]];
|
|
if (!vertex.hasOutgoing) {
|
|
visit(vertex, fn, visited);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Adds a vertex with the given name and value to the graph and joins it with the
|
|
* vertices referenced in _before_ and _after_. If there isn't vertices with those
|
|
* names, they are added too.
|
|
*
|
|
* If either _before_ or _after_ are falsy/empty, the added vertex will not have
|
|
* an incoming/outgoing edge.
|
|
*
|
|
* @method addEdges
|
|
* @param {String} name The name of the vertex to be added.
|
|
* @param value The value of that vertex.
|
|
* @param before An string or array of strings with the names of the vertices before
|
|
* which this vertex must be visited.
|
|
* @param after An string or array of strings with the names of the vertex after
|
|
* which this vertex must be visited.
|
|
*
|
|
*/
|
|
DAG.prototype.addEdges = function(name, value, before, after) {
|
|
var i;
|
|
this.map(name, value);
|
|
if (before) {
|
|
if (typeof before === 'string') {
|
|
this.addEdge(name, before);
|
|
} else {
|
|
for (i = 0; i < before.length; i++) {
|
|
this.addEdge(name, before[i]);
|
|
}
|
|
}
|
|
}
|
|
if (after) {
|
|
if (typeof after === 'string') {
|
|
this.addEdge(after, name);
|
|
} else {
|
|
for (i = 0; i < after.length; i++) {
|
|
this.addEdge(after[i], name);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
__exports__["default"] = DAG;
|
|
});
|
|
enifed("dag-map.umd",
|
|
["./dag-map"],
|
|
function(__dependency1__) {
|
|
"use strict";
|
|
var DAG = __dependency1__["default"];
|
|
|
|
/* global define:true module:true window: true */
|
|
if (typeof enifed === 'function' && enifed.amd) {
|
|
enifed(function() { return DAG; });
|
|
} else if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = DAG;
|
|
} else if (typeof this !== 'undefined') {
|
|
this['DAG'] = DAG;
|
|
}
|
|
});
|
|
enifed("ember-application",
|
|
["ember-metal/core","ember-runtime/system/lazy_load","ember-application/system/resolver","ember-application/system/application","ember-application/ext/controller"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
var runLoadHooks = __dependency2__.runLoadHooks;
|
|
|
|
/**
|
|
Ember Application
|
|
|
|
@module ember
|
|
@submodule ember-application
|
|
@requires ember-views, ember-routing
|
|
*/
|
|
|
|
var Resolver = __dependency3__.Resolver;
|
|
var DefaultResolver = __dependency3__["default"];
|
|
var Application = __dependency4__["default"];
|
|
// side effect of extending ControllerMixin
|
|
|
|
Ember.Application = Application;
|
|
Ember.Resolver = Resolver;
|
|
Ember.DefaultResolver = DefaultResolver;
|
|
|
|
runLoadHooks('Ember.Application', Application);
|
|
});
|
|
enifed("ember-application/ext/controller",
|
|
["ember-metal/core","ember-metal/property_get","ember-metal/error","ember-metal/utils","ember-metal/computed","ember-runtime/mixins/controller","ember-routing/system/controller_for","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-application
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.assert
|
|
var get = __dependency2__.get;
|
|
var EmberError = __dependency3__["default"];
|
|
var inspect = __dependency4__.inspect;
|
|
var computed = __dependency5__.computed;
|
|
var ControllerMixin = __dependency6__["default"];
|
|
var meta = __dependency4__.meta;
|
|
var controllerFor = __dependency7__["default"];
|
|
|
|
function verifyNeedsDependencies(controller, container, needs) {
|
|
var dependency, i, l;
|
|
var missing = [];
|
|
|
|
for (i=0, l=needs.length; i<l; i++) {
|
|
dependency = needs[i];
|
|
|
|
Ember.assert(inspect(controller) + "#needs must not specify dependencies with periods in their names (" +
|
|
dependency + ")", dependency.indexOf('.') === -1);
|
|
|
|
if (dependency.indexOf(':') === -1) {
|
|
dependency = "controller:" + dependency;
|
|
}
|
|
|
|
// Structure assert to still do verification but not string concat in production
|
|
if (!container.has(dependency)) {
|
|
missing.push(dependency);
|
|
}
|
|
}
|
|
if (missing.length) {
|
|
throw new EmberError(inspect(controller) + " needs [ " + missing.join(', ') +
|
|
" ] but " + (missing.length > 1 ? 'they' : 'it') + " could not be found");
|
|
}
|
|
}
|
|
|
|
var defaultControllersComputedProperty = computed(function() {
|
|
var controller = this;
|
|
|
|
return {
|
|
needs: get(controller, 'needs'),
|
|
container: get(controller, 'container'),
|
|
unknownProperty: function(controllerName) {
|
|
var needs = this.needs;
|
|
var dependency, i, l;
|
|
|
|
for (i=0, l=needs.length; i<l; i++) {
|
|
dependency = needs[i];
|
|
if (dependency === controllerName) {
|
|
return this.container.lookup('controller:' + controllerName);
|
|
}
|
|
}
|
|
|
|
var errorMessage = inspect(controller) + '#needs does not include `' +
|
|
controllerName + '`. To access the ' +
|
|
controllerName + ' controller from ' +
|
|
inspect(controller) + ', ' +
|
|
inspect(controller) +
|
|
' should have a `needs` property that is an array of the controllers it has access to.';
|
|
throw new ReferenceError(errorMessage);
|
|
},
|
|
setUnknownProperty: function (key, value) {
|
|
throw new Error("You cannot overwrite the value of `controllers." + key + "` of " + inspect(controller));
|
|
}
|
|
};
|
|
});
|
|
|
|
/**
|
|
@class ControllerMixin
|
|
@namespace Ember
|
|
*/
|
|
ControllerMixin.reopen({
|
|
concatenatedProperties: ['needs'],
|
|
|
|
/**
|
|
An array of other controller objects available inside
|
|
instances of this controller via the `controllers`
|
|
property:
|
|
|
|
For example, when you define a controller:
|
|
|
|
```javascript
|
|
App.CommentsController = Ember.ArrayController.extend({
|
|
needs: ['post']
|
|
});
|
|
```
|
|
|
|
The application's single instance of these other
|
|
controllers are accessible by name through the
|
|
`controllers` property:
|
|
|
|
```javascript
|
|
this.get('controllers.post'); // instance of App.PostController
|
|
```
|
|
|
|
Given that you have a nested controller (nested resource):
|
|
|
|
```javascript
|
|
App.CommentsNewController = Ember.ObjectController.extend({
|
|
});
|
|
```
|
|
|
|
When you define a controller that requires access to a nested one:
|
|
|
|
```javascript
|
|
App.IndexController = Ember.ObjectController.extend({
|
|
needs: ['commentsNew']
|
|
});
|
|
```
|
|
|
|
You will be able to get access to it:
|
|
|
|
```javascript
|
|
this.get('controllers.commentsNew'); // instance of App.CommentsNewController
|
|
```
|
|
|
|
This is only available for singleton controllers.
|
|
|
|
@property {Array} needs
|
|
@default []
|
|
*/
|
|
needs: [],
|
|
|
|
init: function() {
|
|
var needs = get(this, 'needs');
|
|
var length = get(needs, 'length');
|
|
|
|
if (length > 0) {
|
|
Ember.assert(' `' + inspect(this) + ' specifies `needs`, but does ' +
|
|
"not have a container. Please ensure this controller was " +
|
|
"instantiated with a container.",
|
|
this.container || meta(this, false).descs.controllers !== defaultControllersComputedProperty);
|
|
|
|
if (this.container) {
|
|
verifyNeedsDependencies(this, this.container, needs);
|
|
}
|
|
|
|
// if needs then initialize controllers proxy
|
|
get(this, 'controllers');
|
|
}
|
|
|
|
this._super.apply(this, arguments);
|
|
},
|
|
|
|
/**
|
|
@method controllerFor
|
|
@see {Ember.Route#controllerFor}
|
|
@deprecated Use `needs` instead
|
|
*/
|
|
controllerFor: function(controllerName) {
|
|
Ember.deprecate("Controller#controllerFor is deprecated, please use Controller#needs instead");
|
|
return controllerFor(get(this, 'container'), controllerName);
|
|
},
|
|
|
|
/**
|
|
Stores the instances of other controllers available from within
|
|
this controller. Any controller listed by name in the `needs`
|
|
property will be accessible by name through this property.
|
|
|
|
```javascript
|
|
App.CommentsController = Ember.ArrayController.extend({
|
|
needs: ['post'],
|
|
postTitle: function(){
|
|
var currentPost = this.get('controllers.post'); // instance of App.PostController
|
|
return currentPost.get('title');
|
|
}.property('controllers.post.title')
|
|
});
|
|
```
|
|
|
|
@see {Ember.ControllerMixin#needs}
|
|
@property {Object} controllers
|
|
@default null
|
|
*/
|
|
controllers: defaultControllersComputedProperty
|
|
});
|
|
|
|
__exports__["default"] = ControllerMixin;
|
|
});
|
|
enifed("ember-application/system/application",
|
|
["dag-map","container/container","ember-metal","ember-metal/property_get","ember-metal/property_set","ember-runtime/system/lazy_load","ember-runtime/system/namespace","ember-runtime/mixins/deferred","ember-application/system/resolver","ember-metal/platform","ember-metal/run_loop","ember-metal/utils","ember-runtime/controllers/controller","ember-metal/enumerable_utils","ember-runtime/controllers/object_controller","ember-runtime/controllers/array_controller","ember-views/views/select","ember-views/system/event_dispatcher","ember-views/system/jquery","ember-routing/system/route","ember-routing/system/router","ember-routing/location/hash_location","ember-routing/location/history_location","ember-routing/location/auto_location","ember-routing/location/none_location","ember-routing/system/cache","ember-extension-support/container_debug_adapter","ember-metal/core","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __dependency16__, __dependency17__, __dependency18__, __dependency19__, __dependency20__, __dependency21__, __dependency22__, __dependency23__, __dependency24__, __dependency25__, __dependency26__, __dependency27__, __dependency28__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-application
|
|
*/
|
|
var DAG = __dependency1__["default"];
|
|
var Container = __dependency2__["default"];
|
|
|
|
|
|
var Ember = __dependency3__["default"];
|
|
// Ember.FEATURES, Ember.deprecate, Ember.assert, Ember.libraries, LOG_VERSION, Namespace, BOOTED
|
|
var get = __dependency4__.get;
|
|
var set = __dependency5__.set;
|
|
var runLoadHooks = __dependency6__.runLoadHooks;
|
|
var Namespace = __dependency7__["default"];
|
|
var DeferredMixin = __dependency8__["default"];
|
|
var DefaultResolver = __dependency9__["default"];
|
|
var create = __dependency10__.create;
|
|
var run = __dependency11__["default"];
|
|
var canInvoke = __dependency12__.canInvoke;
|
|
var Controller = __dependency13__["default"];
|
|
var EnumerableUtils = __dependency14__["default"];
|
|
var ObjectController = __dependency15__["default"];
|
|
var ArrayController = __dependency16__["default"];
|
|
var SelectView = __dependency17__["default"];
|
|
var EventDispatcher = __dependency18__["default"];
|
|
var jQuery = __dependency19__["default"];
|
|
var Route = __dependency20__["default"];
|
|
var Router = __dependency21__["default"];
|
|
var HashLocation = __dependency22__["default"];
|
|
var HistoryLocation = __dependency23__["default"];
|
|
var AutoLocation = __dependency24__["default"];
|
|
var NoneLocation = __dependency25__["default"];
|
|
var BucketCache = __dependency26__["default"];
|
|
|
|
// this is technically incorrect (per @wycats)
|
|
// it should work properly with:
|
|
// `import ContainerDebugAdapter from 'ember-extension-support/container_debug_adapter';` but
|
|
// es6-module-transpiler 0.4.0 eagerly grabs the module (which is undefined)
|
|
|
|
var ContainerDebugAdapter = __dependency27__["default"];
|
|
|
|
var K = __dependency28__.K;
|
|
|
|
function props(obj) {
|
|
var properties = [];
|
|
|
|
for (var key in obj) {
|
|
properties.push(key);
|
|
}
|
|
|
|
return properties;
|
|
}
|
|
|
|
var librariesRegistered = false;
|
|
|
|
/**
|
|
An instance of `Ember.Application` is the starting point for every Ember
|
|
application. It helps to instantiate, initialize and coordinate the many
|
|
objects that make up your app.
|
|
|
|
Each Ember app has one and only one `Ember.Application` object. In fact, the
|
|
very first thing you should do in your application is create the instance:
|
|
|
|
```javascript
|
|
window.App = Ember.Application.create();
|
|
```
|
|
|
|
Typically, the application object is the only global variable. All other
|
|
classes in your app should be properties on the `Ember.Application` instance,
|
|
which highlights its first role: a global namespace.
|
|
|
|
For example, if you define a view class, it might look like this:
|
|
|
|
```javascript
|
|
App.MyView = Ember.View.extend();
|
|
```
|
|
|
|
By default, calling `Ember.Application.create()` will automatically initialize
|
|
your application by calling the `Ember.Application.initialize()` method. If
|
|
you need to delay initialization, you can call your app's `deferReadiness()`
|
|
method. When you are ready for your app to be initialized, call its
|
|
`advanceReadiness()` method.
|
|
|
|
You can define a `ready` method on the `Ember.Application` instance, which
|
|
will be run by Ember when the application is initialized.
|
|
|
|
Because `Ember.Application` inherits from `Ember.Namespace`, any classes
|
|
you create will have useful string representations when calling `toString()`.
|
|
See the `Ember.Namespace` documentation for more information.
|
|
|
|
While you can think of your `Ember.Application` as a container that holds the
|
|
other classes in your application, there are several other responsibilities
|
|
going on under-the-hood that you may want to understand.
|
|
|
|
### Event Delegation
|
|
|
|
Ember uses a technique called _event delegation_. This allows the framework
|
|
to set up a global, shared event listener instead of requiring each view to
|
|
do it manually. For example, instead of each view registering its own
|
|
`mousedown` listener on its associated element, Ember sets up a `mousedown`
|
|
listener on the `body`.
|
|
|
|
If a `mousedown` event occurs, Ember will look at the target of the event and
|
|
start walking up the DOM node tree, finding corresponding views and invoking
|
|
their `mouseDown` method as it goes.
|
|
|
|
`Ember.Application` has a number of default events that it listens for, as
|
|
well as a mapping from lowercase events to camel-cased view method names. For
|
|
example, the `keypress` event causes the `keyPress` method on the view to be
|
|
called, the `dblclick` event causes `doubleClick` to be called, and so on.
|
|
|
|
If there is a bubbling browser event that Ember does not listen for by
|
|
default, you can specify custom events and their corresponding view method
|
|
names by setting the application's `customEvents` property:
|
|
|
|
```javascript
|
|
var App = Ember.Application.create({
|
|
customEvents: {
|
|
// add support for the paste event
|
|
paste: 'paste'
|
|
}
|
|
});
|
|
```
|
|
|
|
By default, the application sets up these event listeners on the document
|
|
body. However, in cases where you are embedding an Ember application inside
|
|
an existing page, you may want it to set up the listeners on an element
|
|
inside the body.
|
|
|
|
For example, if only events inside a DOM element with the ID of `ember-app`
|
|
should be delegated, set your application's `rootElement` property:
|
|
|
|
```javascript
|
|
var App = Ember.Application.create({
|
|
rootElement: '#ember-app'
|
|
});
|
|
```
|
|
|
|
The `rootElement` can be either a DOM element or a jQuery-compatible selector
|
|
string. Note that *views appended to the DOM outside the root element will
|
|
not receive events.* If you specify a custom root element, make sure you only
|
|
append views inside it!
|
|
|
|
To learn more about the advantages of event delegation and the Ember view
|
|
layer, and a list of the event listeners that are setup by default, visit the
|
|
[Ember View Layer guide](http://emberjs.com/guides/understanding-ember/the-view-layer/#toc_event-delegation).
|
|
|
|
### Initializers
|
|
|
|
Libraries on top of Ember can add initializers, like so:
|
|
|
|
```javascript
|
|
Ember.Application.initializer({
|
|
name: 'api-adapter',
|
|
|
|
initialize: function(container, application) {
|
|
application.register('api-adapter:main', ApiAdapter);
|
|
}
|
|
});
|
|
```
|
|
|
|
Initializers provide an opportunity to access the container, which
|
|
organizes the different components of an Ember application. Additionally
|
|
they provide a chance to access the instantiated application. Beyond
|
|
being used for libraries, initializers are also a great way to organize
|
|
dependency injection or setup in your own application.
|
|
|
|
### Routing
|
|
|
|
In addition to creating your application's router, `Ember.Application` is
|
|
also responsible for telling the router when to start routing. Transitions
|
|
between routes can be logged with the `LOG_TRANSITIONS` flag, and more
|
|
detailed intra-transition logging can be logged with
|
|
the `LOG_TRANSITIONS_INTERNAL` flag:
|
|
|
|
```javascript
|
|
var App = Ember.Application.create({
|
|
LOG_TRANSITIONS: true, // basic logging of successful transitions
|
|
LOG_TRANSITIONS_INTERNAL: true // detailed logging of all routing steps
|
|
});
|
|
```
|
|
|
|
By default, the router will begin trying to translate the current URL into
|
|
application state once the browser emits the `DOMContentReady` event. If you
|
|
need to defer routing, you can call the application's `deferReadiness()`
|
|
method. Once routing can begin, call the `advanceReadiness()` method.
|
|
|
|
If there is any setup required before routing begins, you can implement a
|
|
`ready()` method on your app that will be invoked immediately before routing
|
|
begins.
|
|
|
|
@class Application
|
|
@namespace Ember
|
|
@extends Ember.Namespace
|
|
*/
|
|
|
|
var Application = Namespace.extend(DeferredMixin, {
|
|
_suppressDeferredDeprecation: true,
|
|
|
|
/**
|
|
The root DOM element of the Application. This can be specified as an
|
|
element or a
|
|
[jQuery-compatible selector string](http://api.jquery.com/category/selectors/).
|
|
|
|
This is the element that will be passed to the Application's,
|
|
`eventDispatcher`, which sets up the listeners for event delegation. Every
|
|
view in your application should be a child of the element you specify here.
|
|
|
|
@property rootElement
|
|
@type DOMElement
|
|
@default 'body'
|
|
*/
|
|
rootElement: 'body',
|
|
|
|
/**
|
|
The `Ember.EventDispatcher` responsible for delegating events to this
|
|
application's views.
|
|
|
|
The event dispatcher is created by the application at initialization time
|
|
and sets up event listeners on the DOM element described by the
|
|
application's `rootElement` property.
|
|
|
|
See the documentation for `Ember.EventDispatcher` for more information.
|
|
|
|
@property eventDispatcher
|
|
@type Ember.EventDispatcher
|
|
@default null
|
|
*/
|
|
eventDispatcher: null,
|
|
|
|
/**
|
|
The DOM events for which the event dispatcher should listen.
|
|
|
|
By default, the application's `Ember.EventDispatcher` listens
|
|
for a set of standard DOM events, such as `mousedown` and
|
|
`keyup`, and delegates them to your application's `Ember.View`
|
|
instances.
|
|
|
|
If you would like additional bubbling events to be delegated to your
|
|
views, set your `Ember.Application`'s `customEvents` property
|
|
to a hash containing the DOM event name as the key and the
|
|
corresponding view method name as the value. For example:
|
|
|
|
```javascript
|
|
var App = Ember.Application.create({
|
|
customEvents: {
|
|
// add support for the paste event
|
|
paste: 'paste'
|
|
}
|
|
});
|
|
```
|
|
|
|
@property customEvents
|
|
@type Object
|
|
@default null
|
|
*/
|
|
customEvents: null,
|
|
|
|
init: function() {
|
|
// Start off the number of deferrals at 1. This will be
|
|
// decremented by the Application's own `initialize` method.
|
|
this._readinessDeferrals = 1;
|
|
|
|
if (!this.$) {
|
|
this.$ = jQuery;
|
|
}
|
|
this.__container__ = this.buildContainer();
|
|
|
|
this.Router = this.defaultRouter();
|
|
|
|
this._super();
|
|
|
|
this.scheduleInitialize();
|
|
|
|
if (!librariesRegistered) {
|
|
librariesRegistered = true;
|
|
Ember.libraries.registerCoreLibrary('jQuery', jQuery().jquery);
|
|
}
|
|
|
|
if (Ember.LOG_VERSION) {
|
|
// we only need to see this once per Application#init
|
|
Ember.LOG_VERSION = false;
|
|
var libs = Ember.libraries._registry;
|
|
|
|
var nameLengths = EnumerableUtils.map(libs, function(item) {
|
|
return get(item, 'name.length');
|
|
});
|
|
|
|
var maxNameLength = Math.max.apply(this, nameLengths);
|
|
|
|
Ember.debug('-------------------------------');
|
|
for (var i = 0, l = libs.length; i < l; i++) {
|
|
var lib = libs[i];
|
|
var spaces = new Array(maxNameLength - lib.name.length + 1).join(' ');
|
|
Ember.debug([lib.name, spaces, ' : ', lib.version].join(''));
|
|
}
|
|
Ember.debug('-------------------------------');
|
|
}
|
|
},
|
|
|
|
/**
|
|
Build the container for the current application.
|
|
|
|
Also register a default application view in case the application
|
|
itself does not.
|
|
|
|
@private
|
|
@method buildContainer
|
|
@return {Ember.Container} the configured container
|
|
*/
|
|
buildContainer: function() {
|
|
var container = this.__container__ = Application.buildContainer(this);
|
|
|
|
return container;
|
|
},
|
|
|
|
/**
|
|
If the application has not opted out of routing and has not explicitly
|
|
defined a router, supply a default router for the application author
|
|
to configure.
|
|
|
|
This allows application developers to do:
|
|
|
|
```javascript
|
|
var App = Ember.Application.create();
|
|
|
|
App.Router.map(function() {
|
|
this.resource('posts');
|
|
});
|
|
```
|
|
|
|
@private
|
|
@method defaultRouter
|
|
@return {Ember.Router} the default router
|
|
*/
|
|
|
|
defaultRouter: function() {
|
|
if (this.Router === false) { return; }
|
|
var container = this.__container__;
|
|
|
|
if (this.Router) {
|
|
container.unregister('router:main');
|
|
container.register('router:main', this.Router);
|
|
}
|
|
|
|
return container.lookupFactory('router:main');
|
|
},
|
|
|
|
/**
|
|
Automatically initialize the application once the DOM has
|
|
become ready.
|
|
|
|
The initialization itself is scheduled on the actions queue
|
|
which ensures that application loading finishes before
|
|
booting.
|
|
|
|
If you are asynchronously loading code, you should call
|
|
`deferReadiness()` to defer booting, and then call
|
|
`advanceReadiness()` once all of your code has finished
|
|
loading.
|
|
|
|
@private
|
|
@method scheduleInitialize
|
|
*/
|
|
scheduleInitialize: function() {
|
|
if (!this.$ || this.$.isReady) {
|
|
run.schedule('actions', this, '_initialize');
|
|
} else {
|
|
this.$().ready(Ember.run.bind(this, '_initialize'));
|
|
}
|
|
},
|
|
|
|
/**
|
|
Use this to defer readiness until some condition is true.
|
|
|
|
Example:
|
|
|
|
```javascript
|
|
var App = Ember.Application.create();
|
|
|
|
App.deferReadiness();
|
|
// Ember.$ is a reference to the jQuery object/function
|
|
Ember.$.getJSON('/auth-token', function(token) {
|
|
App.token = token;
|
|
App.advanceReadiness();
|
|
});
|
|
```
|
|
|
|
This allows you to perform asynchronous setup logic and defer
|
|
booting your application until the setup has finished.
|
|
|
|
However, if the setup requires a loading UI, it might be better
|
|
to use the router for this purpose.
|
|
|
|
@method deferReadiness
|
|
*/
|
|
deferReadiness: function() {
|
|
Ember.assert("You must call deferReadiness on an instance of Ember.Application", this instanceof Application);
|
|
Ember.assert("You cannot defer readiness since the `ready()` hook has already been called.", this._readinessDeferrals > 0);
|
|
this._readinessDeferrals++;
|
|
},
|
|
|
|
/**
|
|
Call `advanceReadiness` after any asynchronous setup logic has completed.
|
|
Each call to `deferReadiness` must be matched by a call to `advanceReadiness`
|
|
or the application will never become ready and routing will not begin.
|
|
|
|
@method advanceReadiness
|
|
@see {Ember.Application#deferReadiness}
|
|
*/
|
|
advanceReadiness: function() {
|
|
Ember.assert("You must call advanceReadiness on an instance of Ember.Application", this instanceof Application);
|
|
this._readinessDeferrals--;
|
|
|
|
if (this._readinessDeferrals === 0) {
|
|
run.once(this, this.didBecomeReady);
|
|
}
|
|
},
|
|
|
|
/**
|
|
Registers a factory that can be used for dependency injection (with
|
|
`App.inject`) or for service lookup. Each factory is registered with
|
|
a full name including two parts: `type:name`.
|
|
|
|
A simple example:
|
|
|
|
```javascript
|
|
var App = Ember.Application.create();
|
|
|
|
App.Orange = Ember.Object.extend();
|
|
App.register('fruit:favorite', App.Orange);
|
|
```
|
|
|
|
Ember will resolve factories from the `App` namespace automatically.
|
|
For example `App.CarsController` will be discovered and returned if
|
|
an application requests `controller:cars`.
|
|
|
|
An example of registering a controller with a non-standard name:
|
|
|
|
```javascript
|
|
var App = Ember.Application.create();
|
|
var Session = Ember.Controller.extend();
|
|
|
|
App.register('controller:session', Session);
|
|
|
|
// The Session controller can now be treated like a normal controller,
|
|
// despite its non-standard name.
|
|
App.ApplicationController = Ember.Controller.extend({
|
|
needs: ['session']
|
|
});
|
|
```
|
|
|
|
Registered factories are **instantiated** by having `create`
|
|
called on them. Additionally they are **singletons**, each time
|
|
they are looked up they return the same instance.
|
|
|
|
Some examples modifying that default behavior:
|
|
|
|
```javascript
|
|
var App = Ember.Application.create();
|
|
|
|
App.Person = Ember.Object.extend();
|
|
App.Orange = Ember.Object.extend();
|
|
App.Email = Ember.Object.extend();
|
|
App.session = Ember.Object.create();
|
|
|
|
App.register('model:user', App.Person, { singleton: false });
|
|
App.register('fruit:favorite', App.Orange);
|
|
App.register('communication:main', App.Email, { singleton: false });
|
|
App.register('session', App.session, { instantiate: false });
|
|
```
|
|
|
|
@method register
|
|
@param fullName {String} type:name (e.g., 'model:user')
|
|
@param factory {Function} (e.g., App.Person)
|
|
@param options {Object} (optional) disable instantiation or singleton usage
|
|
**/
|
|
register: function() {
|
|
var container = this.__container__;
|
|
container.register.apply(container, arguments);
|
|
},
|
|
|
|
/**
|
|
Define a dependency injection onto a specific factory or all factories
|
|
of a type.
|
|
|
|
When Ember instantiates a controller, view, or other framework component
|
|
it can attach a dependency to that component. This is often used to
|
|
provide services to a set of framework components.
|
|
|
|
An example of providing a session object to all controllers:
|
|
|
|
```javascript
|
|
var App = Ember.Application.create();
|
|
var Session = Ember.Object.extend({ isAuthenticated: false });
|
|
|
|
// A factory must be registered before it can be injected
|
|
App.register('session:main', Session);
|
|
|
|
// Inject 'session:main' onto all factories of the type 'controller'
|
|
// with the name 'session'
|
|
App.inject('controller', 'session', 'session:main');
|
|
|
|
App.IndexController = Ember.Controller.extend({
|
|
isLoggedIn: Ember.computed.alias('session.isAuthenticated')
|
|
});
|
|
```
|
|
|
|
Injections can also be performed on specific factories.
|
|
|
|
```javascript
|
|
App.inject(<full_name or type>, <property name>, <full_name>)
|
|
App.inject('route', 'source', 'source:main')
|
|
App.inject('route:application', 'email', 'model:email')
|
|
```
|
|
|
|
It is important to note that injections can only be performed on
|
|
classes that are instantiated by Ember itself. Instantiating a class
|
|
directly (via `create` or `new`) bypasses the dependency injection
|
|
system.
|
|
|
|
**Note:** Ember-Data instantiates its models in a unique manner, and consequently
|
|
injections onto models (or all models) will not work as expected. Injections
|
|
on models can be enabled by setting `Ember.MODEL_FACTORY_INJECTIONS`
|
|
to `true`.
|
|
|
|
@method inject
|
|
@param factoryNameOrType {String}
|
|
@param property {String}
|
|
@param injectionName {String}
|
|
**/
|
|
inject: function() {
|
|
var container = this.__container__;
|
|
container.injection.apply(container, arguments);
|
|
},
|
|
|
|
/**
|
|
Calling initialize manually is not supported.
|
|
|
|
Please see Ember.Application#advanceReadiness and
|
|
Ember.Application#deferReadiness.
|
|
|
|
@private
|
|
@deprecated
|
|
@method initialize
|
|
**/
|
|
initialize: function() {
|
|
Ember.deprecate('Calling initialize manually is not supported. Please see Ember.Application#advanceReadiness and Ember.Application#deferReadiness');
|
|
},
|
|
|
|
/**
|
|
Initialize the application. This happens automatically.
|
|
|
|
Run any initializers and run the application load hook. These hooks may
|
|
choose to defer readiness. For example, an authentication hook might want
|
|
to defer readiness until the auth token has been retrieved.
|
|
|
|
@private
|
|
@method _initialize
|
|
*/
|
|
_initialize: function() {
|
|
if (this.isDestroyed) { return; }
|
|
|
|
// At this point, the App.Router must already be assigned
|
|
if (this.Router) {
|
|
var container = this.__container__;
|
|
container.unregister('router:main');
|
|
container.register('router:main', this.Router);
|
|
}
|
|
|
|
this.runInitializers();
|
|
runLoadHooks('application', this);
|
|
|
|
// At this point, any initializers or load hooks that would have wanted
|
|
// to defer readiness have fired. In general, advancing readiness here
|
|
// will proceed to didBecomeReady.
|
|
this.advanceReadiness();
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Reset the application. This is typically used only in tests. It cleans up
|
|
the application in the following order:
|
|
|
|
1. Deactivate existing routes
|
|
2. Destroy all objects in the container
|
|
3. Create a new application container
|
|
4. Re-route to the existing url
|
|
|
|
Typical Example:
|
|
|
|
```javascript
|
|
var App;
|
|
|
|
run(function() {
|
|
App = Ember.Application.create();
|
|
});
|
|
|
|
module('acceptance test', {
|
|
setup: function() {
|
|
App.reset();
|
|
}
|
|
});
|
|
|
|
test('first test', function() {
|
|
// App is freshly reset
|
|
});
|
|
|
|
test('second test', function() {
|
|
// App is again freshly reset
|
|
});
|
|
```
|
|
|
|
Advanced Example:
|
|
|
|
Occasionally you may want to prevent the app from initializing during
|
|
setup. This could enable extra configuration, or enable asserting prior
|
|
to the app becoming ready.
|
|
|
|
```javascript
|
|
var App;
|
|
|
|
run(function() {
|
|
App = Ember.Application.create();
|
|
});
|
|
|
|
module('acceptance test', {
|
|
setup: function() {
|
|
run(function() {
|
|
App.reset();
|
|
App.deferReadiness();
|
|
});
|
|
}
|
|
});
|
|
|
|
test('first test', function() {
|
|
ok(true, 'something before app is initialized');
|
|
|
|
run(function() {
|
|
App.advanceReadiness();
|
|
});
|
|
|
|
ok(true, 'something after app is initialized');
|
|
});
|
|
```
|
|
|
|
@method reset
|
|
**/
|
|
reset: function() {
|
|
this._readinessDeferrals = 1;
|
|
|
|
function handleReset() {
|
|
var router = this.__container__.lookup('router:main');
|
|
router.reset();
|
|
|
|
run(this.__container__, 'destroy');
|
|
|
|
this.buildContainer();
|
|
|
|
run.schedule('actions', this, '_initialize');
|
|
}
|
|
|
|
run.join(this, handleReset);
|
|
},
|
|
|
|
/**
|
|
@private
|
|
@method runInitializers
|
|
*/
|
|
runInitializers: function() {
|
|
var initializersByName = get(this.constructor, 'initializers');
|
|
var initializers = props(initializersByName);
|
|
var container = this.__container__;
|
|
var graph = new DAG();
|
|
var namespace = this;
|
|
var initializer;
|
|
|
|
for (var i = 0; i < initializers.length; i++) {
|
|
initializer = initializersByName[initializers[i]];
|
|
graph.addEdges(initializer.name, initializer.initialize, initializer.before, initializer.after);
|
|
}
|
|
|
|
graph.topsort(function (vertex) {
|
|
var initializer = vertex.value;
|
|
Ember.assert("No application initializer named '" + vertex.name + "'", !!initializer);
|
|
initializer(container, namespace);
|
|
});
|
|
},
|
|
|
|
/**
|
|
@private
|
|
@method didBecomeReady
|
|
*/
|
|
didBecomeReady: function() {
|
|
this.setupEventDispatcher();
|
|
this.ready(); // user hook
|
|
this.startRouting();
|
|
|
|
if (!Ember.testing) {
|
|
// Eagerly name all classes that are already loaded
|
|
Ember.Namespace.processAll();
|
|
Ember.BOOTED = true;
|
|
}
|
|
|
|
this.resolve(this);
|
|
},
|
|
|
|
/**
|
|
Setup up the event dispatcher to receive events on the
|
|
application's `rootElement` with any registered
|
|
`customEvents`.
|
|
|
|
@private
|
|
@method setupEventDispatcher
|
|
*/
|
|
setupEventDispatcher: function() {
|
|
var customEvents = get(this, 'customEvents');
|
|
var rootElement = get(this, 'rootElement');
|
|
var dispatcher = this.__container__.lookup('event_dispatcher:main');
|
|
|
|
set(this, 'eventDispatcher', dispatcher);
|
|
dispatcher.setup(customEvents, rootElement);
|
|
},
|
|
|
|
/**
|
|
If the application has a router, use it to route to the current URL, and
|
|
trigger a new call to `route` whenever the URL changes.
|
|
|
|
@private
|
|
@method startRouting
|
|
@property router {Ember.Router}
|
|
*/
|
|
startRouting: function() {
|
|
var router = this.__container__.lookup('router:main');
|
|
if (!router) { return; }
|
|
|
|
router.startRouting();
|
|
},
|
|
|
|
handleURL: function(url) {
|
|
var router = this.__container__.lookup('router:main');
|
|
|
|
router.handleURL(url);
|
|
},
|
|
|
|
/**
|
|
Called when the Application has become ready.
|
|
The call will be delayed until the DOM has become ready.
|
|
|
|
@event ready
|
|
*/
|
|
ready: K,
|
|
|
|
/**
|
|
@deprecated Use 'Resolver' instead
|
|
Set this to provide an alternate class to `Ember.DefaultResolver`
|
|
|
|
|
|
@property resolver
|
|
*/
|
|
resolver: null,
|
|
|
|
/**
|
|
Set this to provide an alternate class to `Ember.DefaultResolver`
|
|
|
|
@property resolver
|
|
*/
|
|
Resolver: null,
|
|
|
|
willDestroy: function() {
|
|
Ember.BOOTED = false;
|
|
// Ensure deactivation of routes before objects are destroyed
|
|
this.__container__.lookup('router:main').reset();
|
|
|
|
this.__container__.destroy();
|
|
},
|
|
|
|
initializer: function(options) {
|
|
this.constructor.initializer(options);
|
|
},
|
|
|
|
/**
|
|
@method then
|
|
@private
|
|
@deprecated
|
|
*/
|
|
then: function() {
|
|
Ember.deprecate('Do not use `.then` on an instance of Ember.Application. Please use the `.ready` hook instead.', false, { url: 'http://emberjs.com/guides/deprecations/#toc_deprecate-code-then-code-on-ember-application' });
|
|
|
|
this._super.apply(this, arguments);
|
|
}
|
|
});
|
|
|
|
Application.reopenClass({
|
|
initializers: create(null),
|
|
|
|
/**
|
|
Initializer receives an object which has the following attributes:
|
|
`name`, `before`, `after`, `initialize`. The only required attribute is
|
|
`initialize, all others are optional.
|
|
|
|
* `name` allows you to specify under which name the initializer is registered.
|
|
This must be a unique name, as trying to register two initializers with the
|
|
same name will result in an error.
|
|
|
|
```javascript
|
|
Ember.Application.initializer({
|
|
name: 'namedInitializer',
|
|
|
|
initialize: function(container, application) {
|
|
Ember.debug('Running namedInitializer!');
|
|
}
|
|
});
|
|
```
|
|
|
|
* `before` and `after` are used to ensure that this initializer is ran prior
|
|
or after the one identified by the value. This value can be a single string
|
|
or an array of strings, referencing the `name` of other initializers.
|
|
|
|
An example of ordering initializers, we create an initializer named `first`:
|
|
|
|
```javascript
|
|
Ember.Application.initializer({
|
|
name: 'first',
|
|
|
|
initialize: function(container, application) {
|
|
Ember.debug('First initializer!');
|
|
}
|
|
});
|
|
|
|
// DEBUG: First initializer!
|
|
```
|
|
|
|
We add another initializer named `second`, specifying that it should run
|
|
after the initializer named `first`:
|
|
|
|
```javascript
|
|
Ember.Application.initializer({
|
|
name: 'second',
|
|
after: 'first',
|
|
|
|
initialize: function(container, application) {
|
|
Ember.debug('Second initializer!');
|
|
}
|
|
});
|
|
|
|
// DEBUG: First initializer!
|
|
// DEBUG: Second initializer!
|
|
```
|
|
|
|
Afterwards we add a further initializer named `pre`, this time specifying
|
|
that it should run before the initializer named `first`:
|
|
|
|
```javascript
|
|
Ember.Application.initializer({
|
|
name: 'pre',
|
|
before: 'first',
|
|
|
|
initialize: function(container, application) {
|
|
Ember.debug('Pre initializer!');
|
|
}
|
|
});
|
|
|
|
// DEBUG: Pre initializer!
|
|
// DEBUG: First initializer!
|
|
// DEBUG: Second initializer!
|
|
```
|
|
|
|
Finally we add an initializer named `post`, specifying it should run after
|
|
both the `first` and the `second` initializers:
|
|
|
|
```javascript
|
|
Ember.Application.initializer({
|
|
name: 'post',
|
|
after: ['first', 'second'],
|
|
|
|
initialize: function(container, application) {
|
|
Ember.debug('Post initializer!');
|
|
}
|
|
});
|
|
|
|
// DEBUG: Pre initializer!
|
|
// DEBUG: First initializer!
|
|
// DEBUG: Second initializer!
|
|
// DEBUG: Post initializer!
|
|
```
|
|
|
|
* `initialize` is a callback function that receives two arguments, `container`
|
|
and `application` on which you can operate.
|
|
|
|
Example of using `container` to preload data into the store:
|
|
|
|
```javascript
|
|
Ember.Application.initializer({
|
|
name: 'preload-data',
|
|
|
|
initialize: function(container, application) {
|
|
var store = container.lookup('store:main');
|
|
|
|
store.pushPayload(preloadedData);
|
|
}
|
|
});
|
|
```
|
|
|
|
Example of using `application` to register an adapter:
|
|
|
|
```javascript
|
|
Ember.Application.initializer({
|
|
name: 'api-adapter',
|
|
|
|
initialize: function(container, application) {
|
|
application.register('api-adapter:main', ApiAdapter);
|
|
}
|
|
});
|
|
```
|
|
|
|
@method initializer
|
|
@param initializer {Object}
|
|
*/
|
|
initializer: function(initializer) {
|
|
// If this is the first initializer being added to a subclass, we are going to reopen the class
|
|
// to make sure we have a new `initializers` object, which extends from the parent class' using
|
|
// prototypal inheritance. Without this, attempting to add initializers to the subclass would
|
|
// pollute the parent class as well as other subclasses.
|
|
if (this.superclass.initializers !== undefined && this.superclass.initializers === this.initializers) {
|
|
this.reopenClass({
|
|
initializers: create(this.initializers)
|
|
});
|
|
}
|
|
|
|
Ember.assert("The initializer '" + initializer.name + "' has already been registered", !this.initializers[initializer.name]);
|
|
Ember.assert("An initializer cannot be registered without an initialize function", canInvoke(initializer, 'initialize'));
|
|
Ember.assert("An initializer cannot be registered without a name property", initializer.name !== undefined);
|
|
|
|
this.initializers[initializer.name] = initializer;
|
|
},
|
|
|
|
/**
|
|
This creates a container with the default Ember naming conventions.
|
|
|
|
It also configures the container:
|
|
|
|
* registered views are created every time they are looked up (they are
|
|
not singletons)
|
|
* registered templates are not factories; the registered value is
|
|
returned directly.
|
|
* the router receives the application as its `namespace` property
|
|
* all controllers receive the router as their `target` and `controllers`
|
|
properties
|
|
* all controllers receive the application as their `namespace` property
|
|
* the application view receives the application controller as its
|
|
`controller` property
|
|
* the application view receives the application template as its
|
|
`defaultTemplate` property
|
|
|
|
@private
|
|
@method buildContainer
|
|
@static
|
|
@param {Ember.Application} namespace the application to build the
|
|
container for.
|
|
@return {Ember.Container} the built container
|
|
*/
|
|
buildContainer: function(namespace) {
|
|
var container = new Container();
|
|
|
|
container.set = set;
|
|
container.resolver = resolverFor(namespace);
|
|
container.normalizeFullName = container.resolver.normalize;
|
|
container.describe = container.resolver.describe;
|
|
container.makeToString = container.resolver.makeToString;
|
|
|
|
container.optionsForType('component', { singleton: false });
|
|
container.optionsForType('view', { singleton: false });
|
|
container.optionsForType('template', { instantiate: false });
|
|
container.optionsForType('helper', { instantiate: false });
|
|
|
|
container.register('application:main', namespace, { instantiate: false });
|
|
|
|
container.register('controller:basic', Controller, { instantiate: false });
|
|
container.register('controller:object', ObjectController, { instantiate: false });
|
|
container.register('controller:array', ArrayController, { instantiate: false });
|
|
|
|
container.register('view:select', SelectView);
|
|
|
|
container.register('route:basic', Route, { instantiate: false });
|
|
container.register('event_dispatcher:main', EventDispatcher);
|
|
|
|
container.register('router:main', Router);
|
|
container.injection('router:main', 'namespace', 'application:main');
|
|
|
|
container.register('location:auto', AutoLocation);
|
|
container.register('location:hash', HashLocation);
|
|
container.register('location:history', HistoryLocation);
|
|
container.register('location:none', NoneLocation);
|
|
|
|
container.injection('controller', 'target', 'router:main');
|
|
container.injection('controller', 'namespace', 'application:main');
|
|
|
|
container.register('-bucket-cache:main', BucketCache);
|
|
container.injection('router', '_bucketCache', '-bucket-cache:main');
|
|
container.injection('route', '_bucketCache', '-bucket-cache:main');
|
|
container.injection('controller', '_bucketCache', '-bucket-cache:main');
|
|
|
|
container.injection('route', 'router', 'router:main');
|
|
container.injection('location', 'rootURL', '-location-setting:root-url');
|
|
|
|
// DEBUGGING
|
|
container.register('resolver-for-debugging:main', container.resolver.__resolver__, { instantiate: false });
|
|
container.injection('container-debug-adapter:main', 'resolver', 'resolver-for-debugging:main');
|
|
container.injection('data-adapter:main', 'containerDebugAdapter', 'container-debug-adapter:main');
|
|
// Custom resolver authors may want to register their own ContainerDebugAdapter with this key
|
|
|
|
container.register('container-debug-adapter:main', ContainerDebugAdapter);
|
|
|
|
return container;
|
|
}
|
|
});
|
|
|
|
/**
|
|
This function defines the default lookup rules for container lookups:
|
|
|
|
* templates are looked up on `Ember.TEMPLATES`
|
|
* other names are looked up on the application after classifying the name.
|
|
For example, `controller:post` looks up `App.PostController` by default.
|
|
* if the default lookup fails, look for registered classes on the container
|
|
|
|
This allows the application to register default injections in the container
|
|
that could be overridden by the normal naming convention.
|
|
|
|
@private
|
|
@method resolverFor
|
|
@param {Ember.Namespace} namespace the namespace to look for classes
|
|
@return {*} the resolved value for a given lookup
|
|
*/
|
|
function resolverFor(namespace) {
|
|
Ember.deprecate('Application.resolver is deprecated in favor of Application.Resolver', !namespace.get('resolver'));
|
|
|
|
var ResolverClass = namespace.get('resolver') || namespace.get('Resolver') || DefaultResolver;
|
|
var resolver = ResolverClass.create({
|
|
namespace: namespace
|
|
});
|
|
|
|
function resolve(fullName) {
|
|
return resolver.resolve(fullName);
|
|
}
|
|
|
|
resolve.describe = function(fullName) {
|
|
return resolver.lookupDescription(fullName);
|
|
};
|
|
|
|
resolve.makeToString = function(factory, fullName) {
|
|
return resolver.makeToString(factory, fullName);
|
|
};
|
|
|
|
resolve.normalize = function(fullName) {
|
|
if (resolver.normalize) {
|
|
return resolver.normalize(fullName);
|
|
} else {
|
|
Ember.deprecate('The Resolver should now provide a \'normalize\' function', false);
|
|
return fullName;
|
|
}
|
|
};
|
|
|
|
resolve.__resolver__ = resolver;
|
|
|
|
return resolve;
|
|
}
|
|
|
|
__exports__["default"] = Application;
|
|
});
|
|
enifed("ember-application/system/resolver",
|
|
["ember-metal/core","ember-metal/property_get","ember-metal/logger","ember-runtime/system/string","ember-runtime/system/object","ember-runtime/system/namespace","ember-htmlbars/helpers","ember-metal/dictionary","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-application
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.TEMPLATES, Ember.assert
|
|
var get = __dependency2__.get;
|
|
var Logger = __dependency3__["default"];
|
|
var classify = __dependency4__.classify;
|
|
var capitalize = __dependency4__.capitalize;
|
|
var decamelize = __dependency4__.decamelize;
|
|
var EmberObject = __dependency5__["default"];
|
|
var Namespace = __dependency6__["default"];
|
|
var helpers = __dependency7__["default"];
|
|
|
|
var Resolver = EmberObject.extend({
|
|
/**
|
|
This will be set to the Application instance when it is
|
|
created.
|
|
|
|
@property namespace
|
|
*/
|
|
namespace: null,
|
|
normalize: Ember.required(Function),
|
|
resolve: Ember.required(Function),
|
|
parseName: Ember.required(Function),
|
|
lookupDescription: Ember.required(Function),
|
|
makeToString: Ember.required(Function),
|
|
resolveOther: Ember.required(Function),
|
|
_logLookup: Ember.required(Function)
|
|
});
|
|
__exports__.Resolver = Resolver;
|
|
/**
|
|
The DefaultResolver defines the default lookup rules to resolve
|
|
container lookups before consulting the container for registered
|
|
items:
|
|
|
|
* templates are looked up on `Ember.TEMPLATES`
|
|
* other names are looked up on the application after converting
|
|
the name. For example, `controller:post` looks up
|
|
`App.PostController` by default.
|
|
* there are some nuances (see examples below)
|
|
|
|
### How Resolving Works
|
|
|
|
The container calls this object's `resolve` method with the
|
|
`fullName` argument.
|
|
|
|
It first parses the fullName into an object using `parseName`.
|
|
|
|
Then it checks for the presence of a type-specific instance
|
|
method of the form `resolve[Type]` and calls it if it exists.
|
|
For example if it was resolving 'template:post', it would call
|
|
the `resolveTemplate` method.
|
|
|
|
Its last resort is to call the `resolveOther` method.
|
|
|
|
The methods of this object are designed to be easy to override
|
|
in a subclass. For example, you could enhance how a template
|
|
is resolved like so:
|
|
|
|
```javascript
|
|
App = Ember.Application.create({
|
|
Resolver: Ember.DefaultResolver.extend({
|
|
resolveTemplate: function(parsedName) {
|
|
var resolvedTemplate = this._super(parsedName);
|
|
if (resolvedTemplate) { return resolvedTemplate; }
|
|
return Ember.TEMPLATES['not_found'];
|
|
}
|
|
})
|
|
});
|
|
```
|
|
|
|
Some examples of how names are resolved:
|
|
|
|
```
|
|
'template:post' //=> Ember.TEMPLATES['post']
|
|
'template:posts/byline' //=> Ember.TEMPLATES['posts/byline']
|
|
'template:posts.byline' //=> Ember.TEMPLATES['posts/byline']
|
|
'template:blogPost' //=> Ember.TEMPLATES['blogPost']
|
|
// OR
|
|
// Ember.TEMPLATES['blog_post']
|
|
'controller:post' //=> App.PostController
|
|
'controller:posts.index' //=> App.PostsIndexController
|
|
'controller:blog/post' //=> Blog.PostController
|
|
'controller:basic' //=> Ember.Controller
|
|
'route:post' //=> App.PostRoute
|
|
'route:posts.index' //=> App.PostsIndexRoute
|
|
'route:blog/post' //=> Blog.PostRoute
|
|
'route:basic' //=> Ember.Route
|
|
'view:post' //=> App.PostView
|
|
'view:posts.index' //=> App.PostsIndexView
|
|
'view:blog/post' //=> Blog.PostView
|
|
'view:basic' //=> Ember.View
|
|
'foo:post' //=> App.PostFoo
|
|
'model:post' //=> App.Post
|
|
```
|
|
|
|
@class DefaultResolver
|
|
@namespace Ember
|
|
@extends Ember.Object
|
|
*/
|
|
var dictionary = __dependency8__["default"];
|
|
|
|
__exports__["default"] = EmberObject.extend({
|
|
/**
|
|
This will be set to the Application instance when it is
|
|
created.
|
|
|
|
@property namespace
|
|
*/
|
|
namespace: null,
|
|
|
|
init: function() {
|
|
this._parseNameCache = dictionary(null);
|
|
},
|
|
normalize: function(fullName) {
|
|
var split = fullName.split(':', 2);
|
|
var type = split[0];
|
|
var name = split[1];
|
|
|
|
Ember.assert("Tried to normalize a container name without a colon (:) in it." +
|
|
" You probably tried to lookup a name that did not contain a type," +
|
|
" a colon, and a name. A proper lookup name would be `view:post`.", split.length === 2);
|
|
|
|
if (type !== 'template') {
|
|
var result = name;
|
|
|
|
if (result.indexOf('.') > -1) {
|
|
result = result.replace(/\.(.)/g, function(m) {
|
|
return m.charAt(1).toUpperCase();
|
|
});
|
|
}
|
|
|
|
if (name.indexOf('_') > -1) {
|
|
result = result.replace(/_(.)/g, function(m) {
|
|
return m.charAt(1).toUpperCase();
|
|
});
|
|
}
|
|
|
|
return type + ':' + result;
|
|
} else {
|
|
return fullName;
|
|
}
|
|
},
|
|
|
|
|
|
/**
|
|
This method is called via the container's resolver method.
|
|
It parses the provided `fullName` and then looks up and
|
|
returns the appropriate template or class.
|
|
|
|
@method resolve
|
|
@param {String} fullName the lookup string
|
|
@return {Object} the resolved factory
|
|
*/
|
|
resolve: function(fullName) {
|
|
var parsedName = this.parseName(fullName);
|
|
var resolveMethodName = parsedName.resolveMethodName;
|
|
var resolved;
|
|
|
|
if (!(parsedName.name && parsedName.type)) {
|
|
throw new TypeError('Invalid fullName: `' + fullName + '`, must be of the form `type:name` ');
|
|
}
|
|
|
|
if (this[resolveMethodName]) {
|
|
resolved = this[resolveMethodName](parsedName);
|
|
}
|
|
|
|
if (!resolved) {
|
|
resolved = this.resolveOther(parsedName);
|
|
}
|
|
|
|
if (parsedName.root && parsedName.root.LOG_RESOLVER) {
|
|
this._logLookup(resolved, parsedName);
|
|
}
|
|
|
|
return resolved;
|
|
},
|
|
/**
|
|
Convert the string name of the form 'type:name' to
|
|
a Javascript object with the parsed aspects of the name
|
|
broken out.
|
|
|
|
@protected
|
|
@param {String} fullName the lookup string
|
|
@method parseName
|
|
*/
|
|
|
|
parseName: function(fullName) {
|
|
return this._parseNameCache[fullName] || (
|
|
this._parseNameCache[fullName] = this._parseName(fullName)
|
|
);
|
|
},
|
|
|
|
_parseName: function(fullName) {
|
|
var nameParts = fullName.split(':');
|
|
var type = nameParts[0], fullNameWithoutType = nameParts[1];
|
|
var name = fullNameWithoutType;
|
|
var namespace = get(this, 'namespace');
|
|
var root = namespace;
|
|
|
|
if (type !== 'template' && name.indexOf('/') !== -1) {
|
|
var parts = name.split('/');
|
|
name = parts[parts.length - 1];
|
|
var namespaceName = capitalize(parts.slice(0, -1).join('.'));
|
|
root = Namespace.byName(namespaceName);
|
|
|
|
Ember.assert('You are looking for a ' + name + ' ' + type +
|
|
' in the ' + namespaceName +
|
|
' namespace, but the namespace could not be found', root);
|
|
}
|
|
|
|
return {
|
|
fullName: fullName,
|
|
type: type,
|
|
fullNameWithoutType: fullNameWithoutType,
|
|
name: name,
|
|
root: root,
|
|
resolveMethodName: 'resolve' + classify(type)
|
|
};
|
|
},
|
|
|
|
/**
|
|
Returns a human-readable description for a fullName. Used by the
|
|
Application namespace in assertions to describe the
|
|
precise name of the class that Ember is looking for, rather than
|
|
container keys.
|
|
|
|
@protected
|
|
@param {String} fullName the lookup string
|
|
@method lookupDescription
|
|
*/
|
|
lookupDescription: function(fullName) {
|
|
var parsedName = this.parseName(fullName);
|
|
var description;
|
|
|
|
if (parsedName.type === 'template') {
|
|
return 'template at ' + parsedName.fullNameWithoutType.replace(/\./g, '/');
|
|
}
|
|
|
|
description = parsedName.root + '.' + classify(parsedName.name).replace(/\./g, '');
|
|
|
|
if (parsedName.type !== 'model') {
|
|
description += classify(parsedName.type);
|
|
}
|
|
|
|
return description;
|
|
},
|
|
|
|
makeToString: function(factory, fullName) {
|
|
return factory.toString();
|
|
},
|
|
/**
|
|
Given a parseName object (output from `parseName`), apply
|
|
the conventions expected by `Ember.Router`
|
|
|
|
@protected
|
|
@param {Object} parsedName a parseName object with the parsed
|
|
fullName lookup string
|
|
@method useRouterNaming
|
|
*/
|
|
useRouterNaming: function(parsedName) {
|
|
parsedName.name = parsedName.name.replace(/\./g, '_');
|
|
if (parsedName.name === 'basic') {
|
|
parsedName.name = '';
|
|
}
|
|
},
|
|
/**
|
|
Look up the template in Ember.TEMPLATES
|
|
|
|
@protected
|
|
@param {Object} parsedName a parseName object with the parsed
|
|
fullName lookup string
|
|
@method resolveTemplate
|
|
*/
|
|
resolveTemplate: function(parsedName) {
|
|
var templateName = parsedName.fullNameWithoutType.replace(/\./g, '/');
|
|
|
|
if (Ember.TEMPLATES[templateName]) {
|
|
return Ember.TEMPLATES[templateName];
|
|
}
|
|
|
|
templateName = decamelize(templateName);
|
|
if (Ember.TEMPLATES[templateName]) {
|
|
return Ember.TEMPLATES[templateName];
|
|
}
|
|
},
|
|
|
|
/**
|
|
Lookup the view using `resolveOther`
|
|
|
|
@protected
|
|
@param {Object} parsedName a parseName object with the parsed
|
|
fullName lookup string
|
|
@method resolveView
|
|
*/
|
|
resolveView: function(parsedName) {
|
|
this.useRouterNaming(parsedName);
|
|
return this.resolveOther(parsedName);
|
|
},
|
|
|
|
/**
|
|
Lookup the controller using `resolveOther`
|
|
|
|
@protected
|
|
@param {Object} parsedName a parseName object with the parsed
|
|
fullName lookup string
|
|
@method resolveController
|
|
*/
|
|
resolveController: function(parsedName) {
|
|
this.useRouterNaming(parsedName);
|
|
return this.resolveOther(parsedName);
|
|
},
|
|
/**
|
|
Lookup the route using `resolveOther`
|
|
|
|
@protected
|
|
@param {Object} parsedName a parseName object with the parsed
|
|
fullName lookup string
|
|
@method resolveRoute
|
|
*/
|
|
resolveRoute: function(parsedName) {
|
|
this.useRouterNaming(parsedName);
|
|
return this.resolveOther(parsedName);
|
|
},
|
|
|
|
/**
|
|
Lookup the model on the Application namespace
|
|
|
|
@protected
|
|
@param {Object} parsedName a parseName object with the parsed
|
|
fullName lookup string
|
|
@method resolveModel
|
|
*/
|
|
resolveModel: function(parsedName) {
|
|
var className = classify(parsedName.name);
|
|
var factory = get(parsedName.root, className);
|
|
|
|
if (factory) { return factory; }
|
|
},
|
|
/**
|
|
Look up the specified object (from parsedName) on the appropriate
|
|
namespace (usually on the Application)
|
|
|
|
@protected
|
|
@param {Object} parsedName a parseName object with the parsed
|
|
fullName lookup string
|
|
@method resolveHelper
|
|
*/
|
|
resolveHelper: function(parsedName) {
|
|
return this.resolveOther(parsedName) || helpers[parsedName.fullNameWithoutType];
|
|
},
|
|
/**
|
|
Look up the specified object (from parsedName) on the appropriate
|
|
namespace (usually on the Application)
|
|
|
|
@protected
|
|
@param {Object} parsedName a parseName object with the parsed
|
|
fullName lookup string
|
|
@method resolveOther
|
|
*/
|
|
resolveOther: function(parsedName) {
|
|
var className = classify(parsedName.name) + classify(parsedName.type);
|
|
var factory = get(parsedName.root, className);
|
|
if (factory) { return factory; }
|
|
},
|
|
|
|
/**
|
|
@method _logLookup
|
|
@param {Boolean} found
|
|
@param {Object} parsedName
|
|
@private
|
|
*/
|
|
_logLookup: function(found, parsedName) {
|
|
var symbol, padding;
|
|
|
|
if (found) { symbol = '[✓]'; }
|
|
else { symbol = '[ ]'; }
|
|
|
|
if (parsedName.fullName.length > 60) {
|
|
padding = '.';
|
|
} else {
|
|
padding = new Array(60 - parsedName.fullName.length).join('.');
|
|
}
|
|
|
|
Logger.info(symbol, parsedName.fullName, padding, this.lookupDescription(parsedName.fullName));
|
|
}
|
|
});
|
|
});
|
|
enifed("ember-debug",
|
|
["ember-metal/core","ember-metal/error","ember-metal/logger","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
/*global __fail__*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
var EmberError = __dependency2__["default"];
|
|
var Logger = __dependency3__["default"];
|
|
|
|
/**
|
|
Ember Debug
|
|
|
|
@module ember
|
|
@submodule ember-debug
|
|
*/
|
|
|
|
/**
|
|
@class Ember
|
|
*/
|
|
|
|
/**
|
|
Define an assertion that will throw an exception if the condition is not
|
|
met. Ember build tools will remove any calls to `Ember.assert()` when
|
|
doing a production build. Example:
|
|
|
|
```javascript
|
|
// Test for truthiness
|
|
Ember.assert('Must pass a valid object', obj);
|
|
|
|
// Fail unconditionally
|
|
Ember.assert('This code path should never be run');
|
|
```
|
|
|
|
@method assert
|
|
@param {String} desc A description of the assertion. This will become
|
|
the text of the Error thrown if the assertion fails.
|
|
@param {Boolean} test Must be truthy for the assertion to pass. If
|
|
falsy, an exception will be thrown.
|
|
*/
|
|
Ember.assert = function(desc, test) {
|
|
var throwAssertion;
|
|
|
|
if (Ember.typeOf(test) === 'function') {
|
|
throwAssertion = !test();
|
|
} else {
|
|
throwAssertion = !test;
|
|
}
|
|
|
|
if (throwAssertion) {
|
|
throw new EmberError("Assertion Failed: " + desc);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
Display a warning with the provided message. Ember build tools will
|
|
remove any calls to `Ember.warn()` when doing a production build.
|
|
|
|
@method warn
|
|
@param {String} message A warning to display.
|
|
@param {Boolean} test An optional boolean. If falsy, the warning
|
|
will be displayed.
|
|
*/
|
|
Ember.warn = function(message, test) {
|
|
if (!test) {
|
|
Logger.warn("WARNING: "+message);
|
|
if ('trace' in Logger) {
|
|
Logger.trace();
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
Display a debug notice. Ember build tools will remove any calls to
|
|
`Ember.debug()` when doing a production build.
|
|
|
|
```javascript
|
|
Ember.debug('I\'m a debug notice!');
|
|
```
|
|
|
|
@method debug
|
|
@param {String} message A debug message to display.
|
|
*/
|
|
Ember.debug = function(message) {
|
|
Logger.debug("DEBUG: "+message);
|
|
};
|
|
|
|
/**
|
|
Display a deprecation warning with the provided message and a stack trace
|
|
(Chrome and Firefox only). Ember build tools will remove any calls to
|
|
`Ember.deprecate()` when doing a production build.
|
|
|
|
@method deprecate
|
|
@param {String} message A description of the deprecation.
|
|
@param {Boolean} test An optional boolean. If falsy, the deprecation
|
|
will be displayed.
|
|
@param {Object} options An optional object that can be used to pass
|
|
in a `url` to the transition guide on the emberjs.com website.
|
|
*/
|
|
Ember.deprecate = function(message, test, options) {
|
|
var noDeprecation;
|
|
|
|
if (typeof test === 'function') {
|
|
noDeprecation = test();
|
|
} else {
|
|
noDeprecation = test;
|
|
}
|
|
|
|
if (noDeprecation) { return; }
|
|
|
|
if (Ember.ENV.RAISE_ON_DEPRECATION) { throw new EmberError(message); }
|
|
|
|
var error;
|
|
|
|
// When using new Error, we can't do the arguments check for Chrome. Alternatives are welcome
|
|
try { __fail__.fail(); } catch (e) { error = e; }
|
|
|
|
if (arguments.length === 3) {
|
|
Ember.assert('options argument to Ember.deprecate should be an object', options && typeof options === 'object');
|
|
if (options.url) {
|
|
message += ' See ' + options.url + ' for more details.';
|
|
}
|
|
}
|
|
|
|
if (Ember.LOG_STACKTRACE_ON_DEPRECATION && error.stack) {
|
|
var stack;
|
|
var stackStr = '';
|
|
|
|
if (error['arguments']) {
|
|
// Chrome
|
|
stack = error.stack.replace(/^\s+at\s+/gm, '').
|
|
replace(/^([^\(]+?)([\n$])/gm, '{anonymous}($1)$2').
|
|
replace(/^Object.<anonymous>\s*\(([^\)]+)\)/gm, '{anonymous}($1)').split('\n');
|
|
stack.shift();
|
|
} else {
|
|
// Firefox
|
|
stack = error.stack.replace(/(?:\n@:0)?\s+$/m, '').
|
|
replace(/^\(/gm, '{anonymous}(').split('\n');
|
|
}
|
|
|
|
stackStr = "\n " + stack.slice(2).join("\n ");
|
|
message = message + stackStr;
|
|
}
|
|
|
|
Logger.warn("DEPRECATION: "+message);
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
Alias an old, deprecated method with its new counterpart.
|
|
|
|
Display a deprecation warning with the provided message and a stack trace
|
|
(Chrome and Firefox only) when the assigned method is called.
|
|
|
|
Ember build tools will not remove calls to `Ember.deprecateFunc()`, though
|
|
no warnings will be shown in production.
|
|
|
|
```javascript
|
|
Ember.oldMethod = Ember.deprecateFunc('Please use the new, updated method', Ember.newMethod);
|
|
```
|
|
|
|
@method deprecateFunc
|
|
@param {String} message A description of the deprecation.
|
|
@param {Function} func The new function called to replace its deprecated counterpart.
|
|
@return {Function} a new function that wrapped the original function with a deprecation warning
|
|
*/
|
|
Ember.deprecateFunc = function(message, func) {
|
|
return function() {
|
|
Ember.deprecate(message);
|
|
return func.apply(this, arguments);
|
|
};
|
|
};
|
|
|
|
|
|
/**
|
|
Run a function meant for debugging. Ember build tools will remove any calls to
|
|
`Ember.runInDebug()` when doing a production build.
|
|
|
|
```javascript
|
|
Ember.runInDebug(function() {
|
|
Ember.Handlebars.EachView.reopen({
|
|
didInsertElement: function() {
|
|
console.log('I\'m happy');
|
|
}
|
|
});
|
|
});
|
|
```
|
|
|
|
@method runInDebug
|
|
@param {Function} func The function to be executed.
|
|
@since 1.5.0
|
|
*/
|
|
Ember.runInDebug = function(func) {
|
|
func();
|
|
};
|
|
|
|
/**
|
|
Will call `Ember.warn()` if ENABLE_ALL_FEATURES, ENABLE_OPTIONAL_FEATURES, or
|
|
any specific FEATURES flag is truthy.
|
|
|
|
This method is called automatically in debug canary builds.
|
|
|
|
@private
|
|
@method _warnIfUsingStrippedFeatureFlags
|
|
@return {void}
|
|
*/
|
|
function _warnIfUsingStrippedFeatureFlags(FEATURES, featuresWereStripped) {
|
|
if (featuresWereStripped) {
|
|
Ember.warn('Ember.ENV.ENABLE_ALL_FEATURES is only available in canary builds.', !Ember.ENV.ENABLE_ALL_FEATURES);
|
|
Ember.warn('Ember.ENV.ENABLE_OPTIONAL_FEATURES is only available in canary builds.', !Ember.ENV.ENABLE_OPTIONAL_FEATURES);
|
|
|
|
for (var key in FEATURES) {
|
|
if (FEATURES.hasOwnProperty(key) && key !== 'isEnabled') {
|
|
Ember.warn('FEATURE["' + key + '"] is set as enabled, but FEATURE flags are only available in canary builds.', !FEATURES[key]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
__exports__._warnIfUsingStrippedFeatureFlags = _warnIfUsingStrippedFeatureFlags;if (!Ember.testing) {
|
|
// Complain if they're using FEATURE flags in builds other than canary
|
|
Ember.FEATURES['features-stripped-test'] = true;
|
|
var featuresWereStripped = true;
|
|
|
|
|
|
delete Ember.FEATURES['features-stripped-test'];
|
|
_warnIfUsingStrippedFeatureFlags(Ember.ENV.FEATURES, featuresWereStripped);
|
|
|
|
// Inform the developer about the Ember Inspector if not installed.
|
|
var isFirefox = typeof InstallTrigger !== 'undefined';
|
|
var isChrome = !!window.chrome && !window.opera;
|
|
|
|
if (typeof window !== 'undefined' && (isFirefox || isChrome) && window.addEventListener) {
|
|
window.addEventListener("load", function() {
|
|
if (document.documentElement && document.documentElement.dataset && !document.documentElement.dataset.emberExtension) {
|
|
var downloadURL;
|
|
|
|
if(isChrome) {
|
|
downloadURL = 'https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi';
|
|
} else if(isFirefox) {
|
|
downloadURL = 'https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/';
|
|
}
|
|
|
|
Ember.debug('For more advanced debugging, install the Ember Inspector from ' + downloadURL);
|
|
}
|
|
}, false);
|
|
}
|
|
}
|
|
|
|
/*
|
|
We are transitioning away from `ember.js` to `ember.debug.js` to make
|
|
it much clearer that it is only for local development purposes.
|
|
|
|
This flag value is changed by the tooling (by a simple string replacement)
|
|
so that if `ember.js` (which must be output for backwards compat reasons) is
|
|
used a nice helpful warning message will be printed out.
|
|
*/
|
|
var runningNonEmberDebugJS = true;
|
|
__exports__.runningNonEmberDebugJS = runningNonEmberDebugJS;if (runningNonEmberDebugJS) {
|
|
Ember.warn('Please use `ember.debug.js` instead of `ember.js` for development and debugging.');
|
|
}
|
|
});
|
|
enifed("ember-extension-support",
|
|
["ember-metal/core","ember-extension-support/data_adapter","ember-extension-support/container_debug_adapter"],
|
|
function(__dependency1__, __dependency2__, __dependency3__) {
|
|
"use strict";
|
|
/**
|
|
Ember Extension Support
|
|
|
|
@module ember
|
|
@submodule ember-extension-support
|
|
@requires ember-application
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
var DataAdapter = __dependency2__["default"];
|
|
var ContainerDebugAdapter = __dependency3__["default"];
|
|
|
|
Ember.DataAdapter = DataAdapter;
|
|
Ember.ContainerDebugAdapter = ContainerDebugAdapter;
|
|
});
|
|
enifed("ember-extension-support/container_debug_adapter",
|
|
["ember-metal/core","ember-runtime/system/native_array","ember-metal/utils","ember-runtime/system/string","ember-runtime/system/namespace","ember-runtime/system/object","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
var emberA = __dependency2__.A;
|
|
var typeOf = __dependency3__.typeOf;
|
|
var dasherize = __dependency4__.dasherize;
|
|
var classify = __dependency4__.classify;
|
|
var Namespace = __dependency5__["default"];
|
|
var EmberObject = __dependency6__["default"];
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-extension-support
|
|
*/
|
|
|
|
/**
|
|
The `ContainerDebugAdapter` helps the container and resolver interface
|
|
with tools that debug Ember such as the
|
|
[Ember Extension](https://github.com/tildeio/ember-extension)
|
|
for Chrome and Firefox.
|
|
|
|
This class can be extended by a custom resolver implementer
|
|
to override some of the methods with library-specific code.
|
|
|
|
The methods likely to be overridden are:
|
|
|
|
* `canCatalogEntriesByType`
|
|
* `catalogEntriesByType`
|
|
|
|
The adapter will need to be registered
|
|
in the application's container as `container-debug-adapter:main`
|
|
|
|
Example:
|
|
|
|
```javascript
|
|
Application.initializer({
|
|
name: "containerDebugAdapter",
|
|
|
|
initialize: function(container, application) {
|
|
application.register('container-debug-adapter:main', require('app/container-debug-adapter'));
|
|
}
|
|
});
|
|
```
|
|
|
|
@class ContainerDebugAdapter
|
|
@namespace Ember
|
|
@extends Ember.Object
|
|
@since 1.5.0
|
|
*/
|
|
__exports__["default"] = EmberObject.extend({
|
|
/**
|
|
The container of the application being debugged.
|
|
This property will be injected
|
|
on creation.
|
|
|
|
@property container
|
|
@default null
|
|
*/
|
|
container: null,
|
|
|
|
/**
|
|
The resolver instance of the application
|
|
being debugged. This property will be injected
|
|
on creation.
|
|
|
|
@property resolver
|
|
@default null
|
|
*/
|
|
resolver: null,
|
|
|
|
/**
|
|
Returns true if it is possible to catalog a list of available
|
|
classes in the resolver for a given type.
|
|
|
|
@method canCatalogEntriesByType
|
|
@param {String} type The type. e.g. "model", "controller", "route"
|
|
@return {boolean} whether a list is available for this type.
|
|
*/
|
|
canCatalogEntriesByType: function(type) {
|
|
if (type === 'model' || type === 'template') return false;
|
|
return true;
|
|
},
|
|
|
|
/**
|
|
Returns the available classes a given type.
|
|
|
|
@method catalogEntriesByType
|
|
@param {String} type The type. e.g. "model", "controller", "route"
|
|
@return {Array} An array of strings.
|
|
*/
|
|
catalogEntriesByType: function(type) {
|
|
var namespaces = emberA(Namespace.NAMESPACES), types = emberA();
|
|
var typeSuffixRegex = new RegExp(classify(type) + "$");
|
|
|
|
namespaces.forEach(function(namespace) {
|
|
if (namespace !== Ember) {
|
|
for (var key in namespace) {
|
|
if (!namespace.hasOwnProperty(key)) { continue; }
|
|
if (typeSuffixRegex.test(key)) {
|
|
var klass = namespace[key];
|
|
if (typeOf(klass) === 'class') {
|
|
types.push(dasherize(key.replace(typeSuffixRegex, '')));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
return types;
|
|
}
|
|
});
|
|
});
|
|
enifed("ember-extension-support/data_adapter",
|
|
["ember-metal/property_get","ember-metal/run_loop","ember-runtime/system/string","ember-runtime/system/namespace","ember-runtime/system/object","ember-runtime/system/native_array","ember-application/system/application","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) {
|
|
"use strict";
|
|
var get = __dependency1__.get;
|
|
var run = __dependency2__["default"];
|
|
var dasherize = __dependency3__.dasherize;
|
|
var Namespace = __dependency4__["default"];
|
|
var EmberObject = __dependency5__["default"];
|
|
var emberA = __dependency6__.A;
|
|
var Application = __dependency7__["default"];
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-extension-support
|
|
*/
|
|
|
|
/**
|
|
The `DataAdapter` helps a data persistence library
|
|
interface with tools that debug Ember such
|
|
as the [Ember Extension](https://github.com/tildeio/ember-extension)
|
|
for Chrome and Firefox.
|
|
|
|
This class will be extended by a persistence library
|
|
which will override some of the methods with
|
|
library-specific code.
|
|
|
|
The methods likely to be overridden are:
|
|
|
|
* `getFilters`
|
|
* `detect`
|
|
* `columnsForType`
|
|
* `getRecords`
|
|
* `getRecordColumnValues`
|
|
* `getRecordKeywords`
|
|
* `getRecordFilterValues`
|
|
* `getRecordColor`
|
|
* `observeRecord`
|
|
|
|
The adapter will need to be registered
|
|
in the application's container as `dataAdapter:main`
|
|
|
|
Example:
|
|
|
|
```javascript
|
|
Application.initializer({
|
|
name: "data-adapter",
|
|
|
|
initialize: function(container, application) {
|
|
application.register('data-adapter:main', DS.DataAdapter);
|
|
}
|
|
});
|
|
```
|
|
|
|
@class DataAdapter
|
|
@namespace Ember
|
|
@extends EmberObject
|
|
*/
|
|
__exports__["default"] = EmberObject.extend({
|
|
init: function() {
|
|
this._super();
|
|
this.releaseMethods = emberA();
|
|
},
|
|
|
|
/**
|
|
The container of the application being debugged.
|
|
This property will be injected
|
|
on creation.
|
|
|
|
@property container
|
|
@default null
|
|
@since 1.3.0
|
|
*/
|
|
container: null,
|
|
|
|
|
|
/**
|
|
The container-debug-adapter which is used
|
|
to list all models.
|
|
|
|
@property containerDebugAdapter
|
|
@default undefined
|
|
@since 1.5.0
|
|
**/
|
|
containerDebugAdapter: undefined,
|
|
|
|
/**
|
|
Number of attributes to send
|
|
as columns. (Enough to make the record
|
|
identifiable).
|
|
|
|
@private
|
|
@property attributeLimit
|
|
@default 3
|
|
@since 1.3.0
|
|
*/
|
|
attributeLimit: 3,
|
|
|
|
/**
|
|
Stores all methods that clear observers.
|
|
These methods will be called on destruction.
|
|
|
|
@private
|
|
@property releaseMethods
|
|
@since 1.3.0
|
|
*/
|
|
releaseMethods: emberA(),
|
|
|
|
/**
|
|
Specifies how records can be filtered.
|
|
Records returned will need to have a `filterValues`
|
|
property with a key for every name in the returned array.
|
|
|
|
@public
|
|
@method getFilters
|
|
@return {Array} List of objects defining filters.
|
|
The object should have a `name` and `desc` property.
|
|
*/
|
|
getFilters: function() {
|
|
return emberA();
|
|
},
|
|
|
|
/**
|
|
Fetch the model types and observe them for changes.
|
|
|
|
@public
|
|
@method watchModelTypes
|
|
|
|
@param {Function} typesAdded Callback to call to add types.
|
|
Takes an array of objects containing wrapped types (returned from `wrapModelType`).
|
|
|
|
@param {Function} typesUpdated Callback to call when a type has changed.
|
|
Takes an array of objects containing wrapped types.
|
|
|
|
@return {Function} Method to call to remove all observers
|
|
*/
|
|
watchModelTypes: function(typesAdded, typesUpdated) {
|
|
var modelTypes = this.getModelTypes();
|
|
var self = this;
|
|
var releaseMethods = emberA();
|
|
var typesToSend;
|
|
|
|
typesToSend = modelTypes.map(function(type) {
|
|
var klass = type.klass;
|
|
var wrapped = self.wrapModelType(klass, type.name);
|
|
releaseMethods.push(self.observeModelType(klass, typesUpdated));
|
|
return wrapped;
|
|
});
|
|
|
|
typesAdded(typesToSend);
|
|
|
|
var release = function() {
|
|
releaseMethods.forEach(function(fn) { fn(); });
|
|
self.releaseMethods.removeObject(release);
|
|
};
|
|
this.releaseMethods.pushObject(release);
|
|
return release;
|
|
},
|
|
|
|
_nameToClass: function(type) {
|
|
if (typeof type === 'string') {
|
|
type = this.container.lookupFactory('model:' + type);
|
|
}
|
|
return type;
|
|
},
|
|
|
|
/**
|
|
Fetch the records of a given type and observe them for changes.
|
|
|
|
@public
|
|
@method watchRecords
|
|
|
|
@param {Function} recordsAdded Callback to call to add records.
|
|
Takes an array of objects containing wrapped records.
|
|
The object should have the following properties:
|
|
columnValues: {Object} key and value of a table cell
|
|
object: {Object} the actual record object
|
|
|
|
@param {Function} recordsUpdated Callback to call when a record has changed.
|
|
Takes an array of objects containing wrapped records.
|
|
|
|
@param {Function} recordsRemoved Callback to call when a record has removed.
|
|
Takes the following parameters:
|
|
index: the array index where the records were removed
|
|
count: the number of records removed
|
|
|
|
@return {Function} Method to call to remove all observers
|
|
*/
|
|
watchRecords: function(type, recordsAdded, recordsUpdated, recordsRemoved) {
|
|
var self = this, releaseMethods = emberA(), records = this.getRecords(type), release;
|
|
|
|
var recordUpdated = function(updatedRecord) {
|
|
recordsUpdated([updatedRecord]);
|
|
};
|
|
|
|
var recordsToSend = records.map(function(record) {
|
|
releaseMethods.push(self.observeRecord(record, recordUpdated));
|
|
return self.wrapRecord(record);
|
|
});
|
|
|
|
|
|
var contentDidChange = function(array, idx, removedCount, addedCount) {
|
|
for (var i = idx; i < idx + addedCount; i++) {
|
|
var record = array.objectAt(i);
|
|
var wrapped = self.wrapRecord(record);
|
|
releaseMethods.push(self.observeRecord(record, recordUpdated));
|
|
recordsAdded([wrapped]);
|
|
}
|
|
|
|
if (removedCount) {
|
|
recordsRemoved(idx, removedCount);
|
|
}
|
|
};
|
|
|
|
var observer = { didChange: contentDidChange, willChange: function() { return this; } };
|
|
records.addArrayObserver(self, observer);
|
|
|
|
release = function() {
|
|
releaseMethods.forEach(function(fn) { fn(); });
|
|
records.removeArrayObserver(self, observer);
|
|
self.releaseMethods.removeObject(release);
|
|
};
|
|
|
|
recordsAdded(recordsToSend);
|
|
|
|
this.releaseMethods.pushObject(release);
|
|
return release;
|
|
},
|
|
|
|
/**
|
|
Clear all observers before destruction
|
|
@private
|
|
@method willDestroy
|
|
*/
|
|
willDestroy: function() {
|
|
this._super();
|
|
this.releaseMethods.forEach(function(fn) {
|
|
fn();
|
|
});
|
|
},
|
|
|
|
/**
|
|
Detect whether a class is a model.
|
|
|
|
Test that against the model class
|
|
of your persistence library
|
|
|
|
@private
|
|
@method detect
|
|
@param {Class} klass The class to test
|
|
@return boolean Whether the class is a model class or not
|
|
*/
|
|
detect: function(klass) {
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
Get the columns for a given model type.
|
|
|
|
@private
|
|
@method columnsForType
|
|
@param {Class} type The model type
|
|
@return {Array} An array of columns of the following format:
|
|
name: {String} name of the column
|
|
desc: {String} Humanized description (what would show in a table column name)
|
|
*/
|
|
columnsForType: function(type) {
|
|
return emberA();
|
|
},
|
|
|
|
/**
|
|
Adds observers to a model type class.
|
|
|
|
@private
|
|
@method observeModelType
|
|
@param {Class} type The model type class
|
|
@param {Function} typesUpdated Called when a type is modified.
|
|
@return {Function} The function to call to remove observers
|
|
*/
|
|
|
|
observeModelType: function(type, typesUpdated) {
|
|
var self = this;
|
|
var records = this.getRecords(type);
|
|
|
|
var onChange = function() {
|
|
typesUpdated([self.wrapModelType(type)]);
|
|
};
|
|
var observer = {
|
|
didChange: function() {
|
|
run.scheduleOnce('actions', this, onChange);
|
|
},
|
|
willChange: function() { return this; }
|
|
};
|
|
|
|
records.addArrayObserver(this, observer);
|
|
|
|
var release = function() {
|
|
records.removeArrayObserver(self, observer);
|
|
};
|
|
|
|
return release;
|
|
},
|
|
|
|
|
|
/**
|
|
Wraps a given model type and observes changes to it.
|
|
|
|
@private
|
|
@method wrapModelType
|
|
@param {Class} type A model class
|
|
@param {String} Optional name of the class
|
|
@return {Object} contains the wrapped type and the function to remove observers
|
|
Format:
|
|
type: {Object} the wrapped type
|
|
The wrapped type has the following format:
|
|
name: {String} name of the type
|
|
count: {Integer} number of records available
|
|
columns: {Columns} array of columns to describe the record
|
|
object: {Class} the actual Model type class
|
|
release: {Function} The function to remove observers
|
|
*/
|
|
wrapModelType: function(type, name) {
|
|
var records = this.getRecords(type);
|
|
var typeToSend;
|
|
|
|
typeToSend = {
|
|
name: name || type.toString(),
|
|
count: get(records, 'length'),
|
|
columns: this.columnsForType(type),
|
|
object: type
|
|
};
|
|
|
|
|
|
return typeToSend;
|
|
},
|
|
|
|
|
|
/**
|
|
Fetches all models defined in the application.
|
|
|
|
@private
|
|
@method getModelTypes
|
|
@return {Array} Array of model types
|
|
*/
|
|
getModelTypes: function() {
|
|
var self = this;
|
|
var containerDebugAdapter = this.get('containerDebugAdapter');
|
|
var types;
|
|
|
|
if (containerDebugAdapter.canCatalogEntriesByType('model')) {
|
|
types = containerDebugAdapter.catalogEntriesByType('model');
|
|
} else {
|
|
types = this._getObjectsOnNamespaces();
|
|
}
|
|
|
|
// New adapters return strings instead of classes
|
|
types = emberA(types).map(function(name) {
|
|
return {
|
|
klass: self._nameToClass(name),
|
|
name: name
|
|
};
|
|
});
|
|
types = emberA(types).filter(function(type) {
|
|
return self.detect(type.klass);
|
|
});
|
|
|
|
return emberA(types);
|
|
},
|
|
|
|
/**
|
|
Loops over all namespaces and all objects
|
|
attached to them
|
|
|
|
@private
|
|
@method _getObjectsOnNamespaces
|
|
@return {Array} Array of model type strings
|
|
*/
|
|
_getObjectsOnNamespaces: function() {
|
|
var namespaces = emberA(Namespace.NAMESPACES);
|
|
var types = emberA();
|
|
var self = this;
|
|
|
|
namespaces.forEach(function(namespace) {
|
|
for (var key in namespace) {
|
|
if (!namespace.hasOwnProperty(key)) { continue; }
|
|
// Even though we will filter again in `getModelTypes`,
|
|
// we should not call `lookupContainer` on non-models
|
|
// (especially when `Ember.MODEL_FACTORY_INJECTIONS` is `true`)
|
|
if (!self.detect(namespace[key])) { continue; }
|
|
var name = dasherize(key);
|
|
if (!(namespace instanceof Application) && namespace.toString()) {
|
|
name = namespace + '/' + name;
|
|
}
|
|
types.push(name);
|
|
}
|
|
});
|
|
return types;
|
|
},
|
|
|
|
/**
|
|
Fetches all loaded records for a given type.
|
|
|
|
@private
|
|
@method getRecords
|
|
@return {Array} An array of records.
|
|
This array will be observed for changes,
|
|
so it should update when new records are added/removed.
|
|
*/
|
|
getRecords: function(type) {
|
|
return emberA();
|
|
},
|
|
|
|
/**
|
|
Wraps a record and observers changes to it.
|
|
|
|
@private
|
|
@method wrapRecord
|
|
@param {Object} record The record instance.
|
|
@return {Object} The wrapped record. Format:
|
|
columnValues: {Array}
|
|
searchKeywords: {Array}
|
|
*/
|
|
wrapRecord: function(record) {
|
|
var recordToSend = { object: record };
|
|
|
|
recordToSend.columnValues = this.getRecordColumnValues(record);
|
|
recordToSend.searchKeywords = this.getRecordKeywords(record);
|
|
recordToSend.filterValues = this.getRecordFilterValues(record);
|
|
recordToSend.color = this.getRecordColor(record);
|
|
|
|
return recordToSend;
|
|
},
|
|
|
|
/**
|
|
Gets the values for each column.
|
|
|
|
@private
|
|
@method getRecordColumnValues
|
|
@return {Object} Keys should match column names defined
|
|
by the model type.
|
|
*/
|
|
getRecordColumnValues: function(record) {
|
|
return {};
|
|
},
|
|
|
|
/**
|
|
Returns keywords to match when searching records.
|
|
|
|
@private
|
|
@method getRecordKeywords
|
|
@return {Array} Relevant keywords for search.
|
|
*/
|
|
getRecordKeywords: function(record) {
|
|
return emberA();
|
|
},
|
|
|
|
/**
|
|
Returns the values of filters defined by `getFilters`.
|
|
|
|
@private
|
|
@method getRecordFilterValues
|
|
@param {Object} record The record instance
|
|
@return {Object} The filter values
|
|
*/
|
|
getRecordFilterValues: function(record) {
|
|
return {};
|
|
},
|
|
|
|
/**
|
|
Each record can have a color that represents its state.
|
|
|
|
@private
|
|
@method getRecordColor
|
|
@param {Object} record The record instance
|
|
@return {String} The record's color
|
|
Possible options: black, red, blue, green
|
|
*/
|
|
getRecordColor: function(record) {
|
|
return null;
|
|
},
|
|
|
|
/**
|
|
Observes all relevant properties and re-sends the wrapped record
|
|
when a change occurs.
|
|
|
|
@private
|
|
@method observerRecord
|
|
@param {Object} record The record instance
|
|
@param {Function} recordUpdated The callback to call when a record is updated.
|
|
@return {Function} The function to call to remove all observers.
|
|
*/
|
|
observeRecord: function(record, recordUpdated) {
|
|
return function(){};
|
|
}
|
|
});
|
|
});
|
|
enifed("ember-htmlbars",
|
|
["ember-metal/core","ember-template-compiler","ember-htmlbars/hooks/inline","ember-htmlbars/hooks/content","ember-htmlbars/hooks/component","ember-htmlbars/hooks/block","ember-htmlbars/hooks/element","ember-htmlbars/hooks/subexpr","ember-htmlbars/hooks/attribute","ember-htmlbars/hooks/concat","ember-htmlbars/hooks/get","ember-htmlbars/hooks/set","morph","ember-htmlbars/system/make-view-helper","ember-htmlbars/system/make_bound_helper","ember-htmlbars/helpers","ember-htmlbars/helpers/binding","ember-htmlbars/helpers/view","ember-htmlbars/helpers/yield","ember-htmlbars/helpers/with","ember-htmlbars/helpers/log","ember-htmlbars/helpers/debugger","ember-htmlbars/helpers/bind-attr","ember-htmlbars/helpers/if_unless","ember-htmlbars/helpers/loc","ember-htmlbars/helpers/partial","ember-htmlbars/helpers/template","ember-htmlbars/helpers/input","ember-htmlbars/helpers/text_area","ember-htmlbars/helpers/collection","ember-htmlbars/helpers/each","ember-htmlbars/helpers/unbound","ember-htmlbars/system/bootstrap","ember-htmlbars/compat","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __dependency16__, __dependency17__, __dependency18__, __dependency19__, __dependency20__, __dependency21__, __dependency22__, __dependency23__, __dependency24__, __dependency25__, __dependency26__, __dependency27__, __dependency28__, __dependency29__, __dependency30__, __dependency31__, __dependency32__, __dependency33__, __dependency34__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
|
|
var precompile = __dependency2__.precompile;
|
|
var compile = __dependency2__.compile;
|
|
var template = __dependency2__.template;
|
|
var registerPlugin = __dependency2__.registerPlugin;
|
|
|
|
var inline = __dependency3__["default"];
|
|
var content = __dependency4__["default"];
|
|
var component = __dependency5__["default"];
|
|
var block = __dependency6__["default"];
|
|
var element = __dependency7__["default"];
|
|
var subexpr = __dependency8__["default"];
|
|
var attribute = __dependency9__["default"];
|
|
var concat = __dependency10__["default"];
|
|
var get = __dependency11__["default"];
|
|
var set = __dependency12__["default"];
|
|
var DOMHelper = __dependency13__.DOMHelper;
|
|
var makeViewHelper = __dependency14__["default"];
|
|
var makeBoundHelper = __dependency15__["default"];
|
|
|
|
var registerHelper = __dependency16__.registerHelper;
|
|
var helpers = __dependency16__["default"];
|
|
var bindHelper = __dependency17__.bindHelper;
|
|
var viewHelper = __dependency18__.viewHelper;
|
|
var yieldHelper = __dependency19__.yieldHelper;
|
|
var withHelper = __dependency20__.withHelper;
|
|
var logHelper = __dependency21__.logHelper;
|
|
var debuggerHelper = __dependency22__.debuggerHelper;
|
|
var bindAttrHelper = __dependency23__.bindAttrHelper;
|
|
var bindAttrHelperDeprecated = __dependency23__.bindAttrHelperDeprecated;
|
|
var ifHelper = __dependency24__.ifHelper;
|
|
var unlessHelper = __dependency24__.unlessHelper;
|
|
var unboundIfHelper = __dependency24__.unboundIfHelper;
|
|
var boundIfHelper = __dependency24__.boundIfHelper;
|
|
var locHelper = __dependency25__.locHelper;
|
|
var partialHelper = __dependency26__.partialHelper;
|
|
var templateHelper = __dependency27__.templateHelper;
|
|
var inputHelper = __dependency28__.inputHelper;
|
|
var textareaHelper = __dependency29__.textareaHelper;
|
|
var collectionHelper = __dependency30__.collectionHelper;
|
|
var eachHelper = __dependency31__.eachHelper;
|
|
var unboundHelper = __dependency32__.unboundHelper;
|
|
|
|
// importing adds template bootstrapping
|
|
// initializer to enable embedded templates
|
|
|
|
// importing ember-htmlbars/compat updates the
|
|
// Ember.Handlebars global if htmlbars is enabled
|
|
|
|
registerHelper('bindHelper', bindHelper);
|
|
registerHelper('bind', bindHelper);
|
|
registerHelper('view', viewHelper);
|
|
registerHelper('yield', yieldHelper);
|
|
registerHelper('with', withHelper);
|
|
registerHelper('if', ifHelper);
|
|
registerHelper('unless', unlessHelper);
|
|
registerHelper('unboundIf', unboundIfHelper);
|
|
registerHelper('boundIf', boundIfHelper);
|
|
registerHelper('log', logHelper);
|
|
registerHelper('debugger', debuggerHelper);
|
|
registerHelper('loc', locHelper);
|
|
registerHelper('partial', partialHelper);
|
|
registerHelper('template', templateHelper);
|
|
registerHelper('bind-attr', bindAttrHelper);
|
|
registerHelper('bindAttr', bindAttrHelperDeprecated);
|
|
registerHelper('input', inputHelper);
|
|
registerHelper('textarea', textareaHelper);
|
|
registerHelper('collection', collectionHelper);
|
|
registerHelper('each', eachHelper);
|
|
registerHelper('unbound', unboundHelper);
|
|
registerHelper('concat', concat);
|
|
|
|
|
|
Ember.HTMLBars = {
|
|
_registerHelper: registerHelper,
|
|
template: template,
|
|
compile: compile,
|
|
precompile: precompile,
|
|
makeViewHelper: makeViewHelper,
|
|
makeBoundHelper: makeBoundHelper,
|
|
registerPlugin: registerPlugin
|
|
};
|
|
|
|
|
|
|
|
var defaultEnv = {
|
|
dom: new DOMHelper(),
|
|
|
|
hooks: {
|
|
get: get,
|
|
set: set,
|
|
inline: inline,
|
|
content: content,
|
|
block: block,
|
|
element: element,
|
|
subexpr: subexpr,
|
|
component: component,
|
|
attribute: attribute,
|
|
concat: concat
|
|
},
|
|
|
|
helpers: helpers,
|
|
useFragmentCache: true
|
|
};
|
|
__exports__.defaultEnv = defaultEnv;
|
|
});
|
|
enifed("ember-htmlbars/compat",
|
|
["ember-metal/core","ember-htmlbars/helpers","ember-htmlbars/compat/helper","ember-htmlbars/compat/handlebars-get","ember-htmlbars/compat/make-bound-helper","ember-htmlbars/compat/register-bound-helper","ember-htmlbars/system/make-view-helper","ember-htmlbars/utils/string","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
var helpers = __dependency2__["default"];
|
|
var compatRegisterHelper = __dependency3__.registerHandlebarsCompatibleHelper;
|
|
var compatHandlebarsHelper = __dependency3__.handlebarsHelper;
|
|
var compatHandlebarsGet = __dependency4__["default"];
|
|
var compatMakeBoundHelper = __dependency5__["default"];
|
|
var compatRegisterBoundHelper = __dependency6__["default"];
|
|
var makeViewHelper = __dependency7__["default"];
|
|
var SafeString = __dependency8__.SafeString;
|
|
var escapeExpression = __dependency8__.escapeExpression;
|
|
|
|
var EmberHandlebars;
|
|
|
|
EmberHandlebars = Ember.Handlebars = Ember.Handlebars || {};
|
|
EmberHandlebars.helpers = helpers;
|
|
EmberHandlebars.helper = compatHandlebarsHelper;
|
|
EmberHandlebars.registerHelper = compatRegisterHelper;
|
|
EmberHandlebars.registerBoundHelper = compatRegisterBoundHelper;
|
|
EmberHandlebars.makeBoundHelper = compatMakeBoundHelper;
|
|
EmberHandlebars.get = compatHandlebarsGet;
|
|
EmberHandlebars.makeViewHelper = makeViewHelper;
|
|
|
|
EmberHandlebars.SafeString = SafeString;
|
|
EmberHandlebars.Utils = {
|
|
escapeExpression: escapeExpression
|
|
};
|
|
|
|
|
|
__exports__["default"] = EmberHandlebars;
|
|
});
|
|
enifed("ember-htmlbars/compat/handlebars-get",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
/**
|
|
Lookup both on root and on window. If the path starts with
|
|
a keyword, the corresponding object will be looked up in the
|
|
template's data hash and used to resolve the path.
|
|
|
|
@method get
|
|
@for Ember.Handlebars
|
|
@param {Object} root The object to look up the property on
|
|
@param {String} path The path to be lookedup
|
|
@param {Object} options The template's option hash
|
|
@deprecated
|
|
*/
|
|
__exports__["default"] = function handlebarsGet(root, path, options) {
|
|
Ember.deprecate('Usage of Ember.Handlebars.get is deprecated, use a Component or Ember.Handlebars.makeBoundHelper instead.');
|
|
|
|
return options.data.view.getStream(path).value();
|
|
}
|
|
});
|
|
enifed("ember-htmlbars/compat/helper",
|
|
["ember-metal/merge","ember-htmlbars/helpers","ember-views/views/view","ember-views/views/component","ember-htmlbars/system/make-view-helper","ember-htmlbars/compat/make-bound-helper","ember-metal/streams/utils","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
var merge = __dependency1__["default"];
|
|
var helpers = __dependency2__["default"];
|
|
var View = __dependency3__["default"];
|
|
var Component = __dependency4__["default"];
|
|
var makeViewHelper = __dependency5__["default"];
|
|
var makeBoundHelper = __dependency6__["default"];
|
|
var isStream = __dependency7__.isStream;
|
|
|
|
var slice = [].slice;
|
|
|
|
function calculateCompatType(item) {
|
|
if (isStream(item)) {
|
|
return 'ID';
|
|
} else {
|
|
var itemType = typeof item;
|
|
|
|
return itemType.toUpperCase();
|
|
}
|
|
}
|
|
|
|
/**
|
|
Wraps an Handlebars helper with an HTMLBars helper for backwards compatibility.
|
|
|
|
@class HandlebarsCompatibleHelper
|
|
@constructor
|
|
@private
|
|
*/
|
|
function HandlebarsCompatibleHelper(fn) {
|
|
this.helperFunction = function helperFunc(params, hash, options, env) {
|
|
var param, blockResult, fnResult;
|
|
var context = this;
|
|
var handlebarsOptions = {
|
|
hash: { },
|
|
types: new Array(params.length),
|
|
hashTypes: { }
|
|
};
|
|
|
|
merge(handlebarsOptions, options);
|
|
merge(handlebarsOptions, env);
|
|
|
|
handlebarsOptions.hash = {};
|
|
|
|
if (options.isBlock) {
|
|
handlebarsOptions.fn = function() {
|
|
blockResult = options.template.render(context, env, options.morph.contextualElement);
|
|
};
|
|
}
|
|
|
|
for (var prop in hash) {
|
|
param = hash[prop];
|
|
|
|
handlebarsOptions.hashTypes[prop] = calculateCompatType(param);
|
|
|
|
if (isStream(param)) {
|
|
handlebarsOptions.hash[prop] = param._label;
|
|
} else {
|
|
handlebarsOptions.hash[prop] = param;
|
|
}
|
|
}
|
|
|
|
var args = new Array(params.length);
|
|
for (var i = 0, l = params.length; i < l; i++) {
|
|
param = params[i];
|
|
|
|
handlebarsOptions.types[i] = calculateCompatType(param);
|
|
|
|
if (isStream(param)) {
|
|
args[i] = param._label;
|
|
} else {
|
|
args[i] = param;
|
|
}
|
|
}
|
|
args.push(handlebarsOptions);
|
|
|
|
fnResult = fn.apply(this, args);
|
|
|
|
return options.isBlock ? blockResult : fnResult;
|
|
};
|
|
|
|
this.isHTMLBars = true;
|
|
}
|
|
|
|
HandlebarsCompatibleHelper.prototype = {
|
|
preprocessArguments: function() { }
|
|
};
|
|
|
|
function registerHandlebarsCompatibleHelper(name, value) {
|
|
var helper;
|
|
|
|
if (value && value.isHTMLBars) {
|
|
helper = value;
|
|
} else {
|
|
helper = new HandlebarsCompatibleHelper(value);
|
|
}
|
|
|
|
helpers[name] = helper;
|
|
}
|
|
|
|
__exports__.registerHandlebarsCompatibleHelper = registerHandlebarsCompatibleHelper;function handlebarsHelper(name, value) {
|
|
Ember.assert("You tried to register a component named '" + name +
|
|
"', but component names must include a '-'", !Component.detect(value) || name.match(/-/));
|
|
|
|
if (View.detect(value)) {
|
|
helpers[name] = makeViewHelper(value);
|
|
} else {
|
|
var boundHelperArgs = slice.call(arguments, 1);
|
|
var boundFn = makeBoundHelper.apply(this, boundHelperArgs);
|
|
|
|
helpers[name] = boundFn;
|
|
}
|
|
}
|
|
|
|
__exports__.handlebarsHelper = handlebarsHelper;__exports__["default"] = HandlebarsCompatibleHelper;
|
|
});
|
|
enifed("ember-htmlbars/compat/make-bound-helper",
|
|
["ember-metal/core","ember-metal/mixin","ember-htmlbars/system/helper","ember-metal/streams/stream","ember-metal/streams/utils","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.FEATURES, Ember.assert, Ember.Handlebars, Ember.lookup
|
|
var IS_BINDING = __dependency2__.IS_BINDING;
|
|
var Helper = __dependency3__["default"];
|
|
|
|
var Stream = __dependency4__["default"];
|
|
var readArray = __dependency5__.readArray;
|
|
var scanArray = __dependency5__.scanArray;
|
|
var scanHash = __dependency5__.scanHash;
|
|
var readHash = __dependency5__.readHash;
|
|
var isStream = __dependency5__.isStream;
|
|
|
|
/**
|
|
A helper function used by `registerBoundHelper`. Takes the
|
|
provided Handlebars helper function fn and returns it in wrapped
|
|
bound helper form.
|
|
|
|
The main use case for using this outside of `registerBoundHelper`
|
|
is for registering helpers on the container:
|
|
|
|
```js
|
|
var boundHelperFn = Ember.Handlebars.makeBoundHelper(function(word) {
|
|
return word.toUpperCase();
|
|
});
|
|
|
|
container.register('helper:my-bound-helper', boundHelperFn);
|
|
```
|
|
|
|
In the above example, if the helper function hadn't been wrapped in
|
|
`makeBoundHelper`, the registered helper would be unbound.
|
|
|
|
@method makeBoundHelper
|
|
@for Ember.Handlebars
|
|
@param {Function} function
|
|
@param {String} dependentKeys*
|
|
@since 1.2.0
|
|
@deprecated
|
|
*/
|
|
__exports__["default"] = function makeBoundHelper(fn, compatMode) {
|
|
var dependentKeys = [];
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
dependentKeys.push(arguments[i]);
|
|
}
|
|
|
|
function helperFunc(params, hash, options, env) {
|
|
var view = this;
|
|
var numParams = params.length;
|
|
var param;
|
|
|
|
Ember.assert("registerBoundHelper-generated helpers do not support use with Handlebars blocks.", !options.template);
|
|
|
|
for (var prop in hash) {
|
|
if (IS_BINDING.test(prop)) {
|
|
hash[prop.slice(0, -7)] = view.getStream(hash[prop]);
|
|
delete hash[prop];
|
|
}
|
|
}
|
|
|
|
function valueFn() {
|
|
var args = readArray(params);
|
|
var properties = new Array(params.length);
|
|
for (var i = 0, l = params.length; i < l; i++) {
|
|
param = params[i];
|
|
|
|
if (isStream(param)) {
|
|
properties[i] = param._label;
|
|
} else {
|
|
properties[i] = param;
|
|
}
|
|
}
|
|
|
|
args.push({
|
|
hash: readHash(hash),
|
|
data: { properties: properties }
|
|
});
|
|
return fn.apply(view, args);
|
|
}
|
|
|
|
// If none of the hash parameters are bound, act as an unbound helper.
|
|
// This prevents views from being unnecessarily created
|
|
var hasStream = scanArray(params) || scanHash(hash);
|
|
|
|
if (env.data.isUnbound || !hasStream){
|
|
return valueFn();
|
|
} else {
|
|
var lazyValue = new Stream(valueFn);
|
|
|
|
for (i = 0; i < numParams; i++) {
|
|
param = params[i];
|
|
if (isStream(param)) {
|
|
param.subscribe(lazyValue.notify, lazyValue);
|
|
}
|
|
}
|
|
|
|
for (prop in hash) {
|
|
param = hash[prop];
|
|
if (isStream(param)) {
|
|
param.subscribe(lazyValue.notify, lazyValue);
|
|
}
|
|
}
|
|
|
|
if (numParams > 0) {
|
|
var firstParam = params[0];
|
|
// Only bother with subscriptions if the first argument
|
|
// is a stream itself, and not a primitive.
|
|
if (isStream(firstParam)) {
|
|
var onDependentKeyNotify = function onDependentKeyNotify(stream) {
|
|
stream.value();
|
|
lazyValue.notify();
|
|
};
|
|
for (i = 0; i < dependentKeys.length; i++) {
|
|
var childParam = firstParam.get(dependentKeys[i]);
|
|
childParam.value();
|
|
childParam.subscribe(onDependentKeyNotify);
|
|
}
|
|
}
|
|
}
|
|
|
|
return lazyValue;
|
|
}
|
|
}
|
|
|
|
return new Helper(helperFunc);
|
|
}
|
|
});
|
|
enifed("ember-htmlbars/compat/register-bound-helper",
|
|
["ember-htmlbars/helpers","ember-htmlbars/compat/make-bound-helper","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
var helpers = __dependency1__["default"];
|
|
var makeBoundHelper = __dependency2__["default"];
|
|
|
|
var slice = [].slice;
|
|
|
|
/**
|
|
Register a bound handlebars helper. Bound helpers behave similarly to regular
|
|
handlebars helpers, with the added ability to re-render when the underlying data
|
|
changes.
|
|
|
|
## Simple example
|
|
|
|
```javascript
|
|
Ember.Handlebars.registerBoundHelper('capitalize', function(value) {
|
|
return Ember.String.capitalize(value);
|
|
});
|
|
```
|
|
|
|
The above bound helper can be used inside of templates as follows:
|
|
|
|
```handlebars
|
|
{{capitalize name}}
|
|
```
|
|
|
|
In this case, when the `name` property of the template's context changes,
|
|
the rendered value of the helper will update to reflect this change.
|
|
|
|
## Example with options
|
|
|
|
Like normal handlebars helpers, bound helpers have access to the options
|
|
passed into the helper call.
|
|
|
|
```javascript
|
|
Ember.Handlebars.registerBoundHelper('repeat', function(value, options) {
|
|
var count = options.hash.count;
|
|
var a = [];
|
|
while(a.length < count) {
|
|
a.push(value);
|
|
}
|
|
return a.join('');
|
|
});
|
|
```
|
|
|
|
This helper could be used in a template as follows:
|
|
|
|
```handlebars
|
|
{{repeat text count=3}}
|
|
```
|
|
|
|
## Example with bound options
|
|
|
|
Bound hash options are also supported. Example:
|
|
|
|
```handlebars
|
|
{{repeat text count=numRepeats}}
|
|
```
|
|
|
|
In this example, count will be bound to the value of
|
|
the `numRepeats` property on the context. If that property
|
|
changes, the helper will be re-rendered.
|
|
|
|
## Example with extra dependencies
|
|
|
|
The `Ember.Handlebars.registerBoundHelper` method takes a variable length
|
|
third parameter which indicates extra dependencies on the passed in value.
|
|
This allows the handlebars helper to update when these dependencies change.
|
|
|
|
```javascript
|
|
Ember.Handlebars.registerBoundHelper('capitalizeName', function(value) {
|
|
return value.get('name').toUpperCase();
|
|
}, 'name');
|
|
```
|
|
|
|
## Example with multiple bound properties
|
|
|
|
`Ember.Handlebars.registerBoundHelper` supports binding to
|
|
multiple properties, e.g.:
|
|
|
|
```javascript
|
|
Ember.Handlebars.registerBoundHelper('concatenate', function() {
|
|
var values = Array.prototype.slice.call(arguments, 0, -1);
|
|
return values.join('||');
|
|
});
|
|
```
|
|
|
|
Which allows for template syntax such as `{{concatenate prop1 prop2}}` or
|
|
`{{concatenate prop1 prop2 prop3}}`. If any of the properties change,
|
|
the helper will re-render. Note that dependency keys cannot be
|
|
using in conjunction with multi-property helpers, since it is ambiguous
|
|
which property the dependent keys would belong to.
|
|
|
|
## Use with unbound helper
|
|
|
|
The `{{unbound}}` helper can be used with bound helper invocations
|
|
to render them in their unbound form, e.g.
|
|
|
|
```handlebars
|
|
{{unbound capitalize name}}
|
|
```
|
|
|
|
In this example, if the name property changes, the helper
|
|
will not re-render.
|
|
|
|
## Use with blocks not supported
|
|
|
|
Bound helpers do not support use with Handlebars blocks or
|
|
the addition of child views of any kind.
|
|
|
|
@method registerBoundHelper
|
|
@for Ember.Handlebars
|
|
@param {String} name
|
|
@param {Function} function
|
|
@param {String} dependentKeys*
|
|
*/
|
|
__exports__["default"] = function registerBoundHelper(name, fn) {
|
|
var boundHelperArgs = slice.call(arguments, 1);
|
|
var boundFn = makeBoundHelper.apply(this, boundHelperArgs);
|
|
|
|
helpers[name] = boundFn;
|
|
}
|
|
});
|
|
enifed("ember-htmlbars/helpers",
|
|
["ember-metal/platform","ember-htmlbars/system/helper","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
var o_create = __dependency1__.create;
|
|
|
|
/**
|
|
@private
|
|
@property helpers
|
|
*/
|
|
var helpers = o_create(null);
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
var Helper = __dependency2__["default"];
|
|
|
|
/**
|
|
@private
|
|
@method _registerHelper
|
|
@for Ember.HTMLBars
|
|
@param {String} name
|
|
@param {Object|Function} helperFunc the helper function to add
|
|
*/
|
|
function registerHelper(name, helperFunc) {
|
|
var helper;
|
|
|
|
if (helperFunc && helperFunc.isHelper) {
|
|
helper = helperFunc;
|
|
} else {
|
|
helper = new Helper(helperFunc);
|
|
}
|
|
|
|
helpers[name] = helper;
|
|
}
|
|
|
|
__exports__.registerHelper = registerHelper;__exports__["default"] = helpers;
|
|
});
|
|
enifed("ember-htmlbars/helpers/bind-attr",
|
|
["ember-metal/core","ember-runtime/system/string","ember-views/attr_nodes/attr_node","ember-views/attr_nodes/legacy_bind","ember-metal/keys","ember-htmlbars/helpers","ember-metal/enumerable_utils","ember-metal/streams/utils","ember-views/streams/class_name_binding","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.assert
|
|
|
|
var fmt = __dependency2__.fmt;
|
|
var AttrNode = __dependency3__["default"];
|
|
var LegacyBindAttrNode = __dependency4__["default"];
|
|
var keys = __dependency5__["default"];
|
|
var helpers = __dependency6__["default"];
|
|
var map = __dependency7__.map;
|
|
var isStream = __dependency8__.isStream;
|
|
var concat = __dependency8__.concat;
|
|
var streamifyClassNameBinding = __dependency9__.streamifyClassNameBinding;
|
|
|
|
/**
|
|
`bind-attr` allows you to create a binding between DOM element attributes and
|
|
Ember objects. For example:
|
|
|
|
```handlebars
|
|
<img {{bind-attr src=imageUrl alt=imageTitle}}>
|
|
```
|
|
|
|
The above handlebars template will fill the `<img>`'s `src` attribute with
|
|
the value of the property referenced with `imageUrl` and its `alt`
|
|
attribute with the value of the property referenced with `imageTitle`.
|
|
|
|
If the rendering context of this template is the following object:
|
|
|
|
```javascript
|
|
{
|
|
imageUrl: 'http://lolcats.info/haz-a-funny',
|
|
imageTitle: 'A humorous image of a cat'
|
|
}
|
|
```
|
|
|
|
The resulting HTML output will be:
|
|
|
|
```html
|
|
<img src="http://lolcats.info/haz-a-funny" alt="A humorous image of a cat">
|
|
```
|
|
|
|
`bind-attr` cannot redeclare existing DOM element attributes. The use of `src`
|
|
in the following `bind-attr` example will be ignored and the hard coded value
|
|
of `src="/failwhale.gif"` will take precedence:
|
|
|
|
```handlebars
|
|
<img src="/failwhale.gif" {{bind-attr src=imageUrl alt=imageTitle}}>
|
|
```
|
|
|
|
### `bind-attr` and the `class` attribute
|
|
|
|
`bind-attr` supports a special syntax for handling a number of cases unique
|
|
to the `class` DOM element attribute. The `class` attribute combines
|
|
multiple discrete values into a single attribute as a space-delimited
|
|
list of strings. Each string can be:
|
|
|
|
* a string return value of an object's property.
|
|
* a boolean return value of an object's property
|
|
* a hard-coded value
|
|
|
|
A string return value works identically to other uses of `bind-attr`. The
|
|
return value of the property will become the value of the attribute. For
|
|
example, the following view and template:
|
|
|
|
```javascript
|
|
AView = View.extend({
|
|
someProperty: function() {
|
|
return "aValue";
|
|
}.property()
|
|
})
|
|
```
|
|
|
|
```handlebars
|
|
<img {{bind-attr class=view.someProperty}}>
|
|
```
|
|
|
|
Result in the following rendered output:
|
|
|
|
```html
|
|
<img class="aValue">
|
|
```
|
|
|
|
A boolean return value will insert a specified class name if the property
|
|
returns `true` and remove the class name if the property returns `false`.
|
|
|
|
A class name is provided via the syntax
|
|
`somePropertyName:class-name-if-true`.
|
|
|
|
```javascript
|
|
AView = View.extend({
|
|
someBool: true
|
|
})
|
|
```
|
|
|
|
```handlebars
|
|
<img {{bind-attr class="view.someBool:class-name-if-true"}}>
|
|
```
|
|
|
|
Result in the following rendered output:
|
|
|
|
```html
|
|
<img class="class-name-if-true">
|
|
```
|
|
|
|
An additional section of the binding can be provided if you want to
|
|
replace the existing class instead of removing it when the boolean
|
|
value changes:
|
|
|
|
```handlebars
|
|
<img {{bind-attr class="view.someBool:class-name-if-true:class-name-if-false"}}>
|
|
```
|
|
|
|
A hard-coded value can be used by prepending `:` to the desired
|
|
class name: `:class-name-to-always-apply`.
|
|
|
|
```handlebars
|
|
<img {{bind-attr class=":class-name-to-always-apply"}}>
|
|
```
|
|
|
|
Results in the following rendered output:
|
|
|
|
```html
|
|
<img class="class-name-to-always-apply">
|
|
```
|
|
|
|
All three strategies - string return value, boolean return value, and
|
|
hard-coded value – can be combined in a single declaration:
|
|
|
|
```handlebars
|
|
<img {{bind-attr class=":class-name-to-always-apply view.someBool:class-name-if-true view.someProperty"}}>
|
|
```
|
|
|
|
@method bind-attr
|
|
@for Ember.Handlebars.helpers
|
|
@param {Hash} options
|
|
@return {String} HTML string
|
|
*/
|
|
function bindAttrHelper(params, hash, options, env) {
|
|
var element = options.element;
|
|
|
|
Ember.assert("You must specify at least one hash argument to bind-attr", !!keys(hash).length);
|
|
|
|
var view = this;
|
|
|
|
// Handle classes differently, as we can bind multiple classes
|
|
var classNameBindings = hash['class'];
|
|
if (classNameBindings !== null && classNameBindings !== undefined) {
|
|
if (!isStream(classNameBindings)) {
|
|
classNameBindings = applyClassNameBindings(classNameBindings, view);
|
|
}
|
|
|
|
var classView = new AttrNode('class', classNameBindings);
|
|
classView._morph = env.dom.createAttrMorph(element, 'class');
|
|
|
|
Ember.assert(
|
|
'You cannot set `class` manually and via `{{bind-attr}}` helper on the same element. ' +
|
|
'Please use `{{bind-attr}}`\'s `:static-class` syntax instead.',
|
|
!element.getAttribute('class')
|
|
);
|
|
|
|
view.appendChild(classView);
|
|
}
|
|
|
|
var attrKeys = keys(hash);
|
|
|
|
var attr, path, lazyValue, attrView;
|
|
for (var i=0, l=attrKeys.length;i<l;i++) {
|
|
attr = attrKeys[i];
|
|
if (attr === 'class') {
|
|
continue;
|
|
}
|
|
path = hash[attr];
|
|
if (isStream(path)) {
|
|
lazyValue = path;
|
|
} else {
|
|
Ember.assert(
|
|
fmt("You must provide an expression as the value of bound attribute." +
|
|
" You specified: %@=%@", [attr, path]),
|
|
typeof path === 'string'
|
|
);
|
|
lazyValue = view.getStream(path);
|
|
}
|
|
|
|
attrView = new LegacyBindAttrNode(attr, lazyValue);
|
|
attrView._morph = env.dom.createAttrMorph(element, attr);
|
|
|
|
Ember.assert(
|
|
'You cannot set `' + attr + '` manually and via `{{bind-attr}}` helper on the same element.',
|
|
!element.getAttribute(attr)
|
|
);
|
|
|
|
view.appendChild(attrView);
|
|
}
|
|
}
|
|
|
|
function applyClassNameBindings(classNameBindings, view) {
|
|
var arrayOfClassNameBindings = classNameBindings.split(' ');
|
|
var boundClassNameBindings = map(arrayOfClassNameBindings, function(classNameBinding) {
|
|
return streamifyClassNameBinding(view, classNameBinding);
|
|
});
|
|
var concatenatedClassNames = concat(boundClassNameBindings, ' ');
|
|
return concatenatedClassNames;
|
|
}
|
|
|
|
/**
|
|
See `bind-attr`
|
|
|
|
@method bindAttr
|
|
@for Ember.Handlebars.helpers
|
|
@deprecated
|
|
@param {Function} context
|
|
@param {Hash} options
|
|
@return {String} HTML string
|
|
*/
|
|
function bindAttrHelperDeprecated() {
|
|
Ember.deprecate("The 'bindAttr' view helper is deprecated in favor of 'bind-attr'");
|
|
|
|
|
|
return helpers['bind-attr'].helperFunction.apply(this, arguments);
|
|
}
|
|
|
|
__exports__["default"] = bindAttrHelper;
|
|
|
|
__exports__.bindAttrHelper = bindAttrHelper;
|
|
__exports__.bindAttrHelperDeprecated = bindAttrHelperDeprecated;
|
|
});
|
|
enifed("ember-htmlbars/helpers/binding",
|
|
["ember-metal/is_none","ember-metal/run_loop","ember-metal/property_get","ember-metal/streams/simple","ember-views/views/bound_view","ember-metal/streams/utils","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
var isNone = __dependency1__["default"];
|
|
var run = __dependency2__["default"];
|
|
var get = __dependency3__.get;
|
|
var SimpleStream = __dependency4__["default"];
|
|
var BoundView = __dependency5__["default"];
|
|
var isStream = __dependency6__.isStream;
|
|
|
|
function exists(value) {
|
|
return !isNone(value);
|
|
}
|
|
|
|
// Binds a property into the DOM. This will create a hook in DOM that the
|
|
// KVO system will look for and update if the property changes.
|
|
function bind(property, hash, options, env, preserveContext, shouldDisplay, valueNormalizer, childProperties, _viewClass) {
|
|
var valueStream = isStream(property) ? property : this.getStream(property);
|
|
var lazyValue;
|
|
|
|
if (childProperties) {
|
|
lazyValue = new SimpleStream(valueStream);
|
|
|
|
var subscriber = function(childStream) {
|
|
childStream.value();
|
|
lazyValue.notify();
|
|
};
|
|
|
|
for (var i = 0; i < childProperties.length; i++) {
|
|
var childStream = valueStream.get(childProperties[i]);
|
|
childStream.value();
|
|
childStream.subscribe(subscriber);
|
|
}
|
|
} else {
|
|
lazyValue = valueStream;
|
|
}
|
|
|
|
// Set up observers for observable objects
|
|
var viewClass = _viewClass || BoundView;
|
|
var viewOptions = {
|
|
_morph: options.morph,
|
|
preserveContext: preserveContext,
|
|
shouldDisplayFunc: shouldDisplay,
|
|
valueNormalizerFunc: valueNormalizer,
|
|
displayTemplate: options.template,
|
|
inverseTemplate: options.inverse,
|
|
lazyValue: lazyValue,
|
|
previousContext: get(this, 'context'),
|
|
isEscaped: !hash.unescaped,
|
|
templateHash: hash,
|
|
helperName: options.helperName
|
|
};
|
|
|
|
if (options.keywords) {
|
|
viewOptions._keywords = options.keywords;
|
|
}
|
|
|
|
// Create the view that will wrap the output of this template/property
|
|
// and add it to the nearest view's childViews array.
|
|
// See the documentation of Ember._BoundView for more.
|
|
var bindView = this.createChildView(viewClass, viewOptions);
|
|
|
|
this.appendChild(bindView);
|
|
|
|
lazyValue.subscribe(this._wrapAsScheduled(function() {
|
|
run.scheduleOnce('render', bindView, 'rerenderIfNeeded');
|
|
}));
|
|
}
|
|
|
|
/**
|
|
`bind` can be used to display a value, then update that value if it
|
|
changes. For example, if you wanted to print the `title` property of
|
|
`content`:
|
|
|
|
```handlebars
|
|
{{bind "content.title"}}
|
|
```
|
|
|
|
This will return the `title` property as a string, then create a new observer
|
|
at the specified path. If it changes, it will update the value in DOM. Note
|
|
that if you need to support IE7 and IE8 you must modify the model objects
|
|
properties using `Ember.get()` and `Ember.set()` for this to work as it
|
|
relies on Ember's KVO system. For all other browsers this will be handled for
|
|
you automatically.
|
|
|
|
@private
|
|
@method bind
|
|
@for Ember.Handlebars.helpers
|
|
@param {String} property Property to bind
|
|
@param {Function} render Context to provide for rendering
|
|
@return {String} HTML string
|
|
*/
|
|
function bindHelper(params, hash, options, env) {
|
|
Ember.assert("You must pass exactly one argument to the bind helper", params.length === 1);
|
|
|
|
var property = params[0];
|
|
|
|
if (typeof property === 'string') {
|
|
property = this.getStream(property);
|
|
}
|
|
|
|
if (options.template) {
|
|
options.helperName = 'bind';
|
|
Ember.deprecate("The block form of bind, {{#bind foo}}{{/bind}}, has been deprecated and will be removed.");
|
|
bind.call(this, property, hash, options, env, false, exists);
|
|
} else {
|
|
Ember.deprecate("The `{{bind}}` helper has been deprecated and will be removed.");
|
|
|
|
return property;
|
|
}
|
|
}
|
|
|
|
__exports__.bind = bind;
|
|
__exports__.bindHelper = bindHelper;
|
|
});
|
|
enifed("ember-htmlbars/helpers/collection",
|
|
["ember-metal/core","ember-metal/mixin","ember-runtime/system/string","ember-metal/property_get","ember-htmlbars/helpers/view","ember-views/views/collection_view","ember-views/streams/utils","ember-metal/enumerable_utils","ember-views/streams/class_name_binding","ember-metal/binding","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.assert, Ember.deprecate
|
|
var IS_BINDING = __dependency2__.IS_BINDING;
|
|
var fmt = __dependency3__.fmt;
|
|
var get = __dependency4__.get;
|
|
var ViewHelper = __dependency5__.ViewHelper;
|
|
var CollectionView = __dependency6__["default"];
|
|
var readViewFactory = __dependency7__.readViewFactory;
|
|
var map = __dependency8__.map;
|
|
var streamifyClassNameBinding = __dependency9__.streamifyClassNameBinding;
|
|
var Binding = __dependency10__.Binding;
|
|
|
|
/**
|
|
`{{collection}}` is a `Ember.Handlebars` helper for adding instances of
|
|
`Ember.CollectionView` to a template. See [Ember.CollectionView](/api/classes/Ember.CollectionView.html)
|
|
for additional information on how a `CollectionView` functions.
|
|
|
|
`{{collection}}`'s primary use is as a block helper with a `contentBinding`
|
|
option pointing towards an `Ember.Array`-compatible object. An `Ember.View`
|
|
instance will be created for each item in its `content` property. Each view
|
|
will have its own `content` property set to the appropriate item in the
|
|
collection.
|
|
|
|
The provided block will be applied as the template for each item's view.
|
|
|
|
Given an empty `<body>` the following template:
|
|
|
|
```handlebars
|
|
{{! application.hbs }}
|
|
{{#collection content=model}}
|
|
Hi {{view.content.name}}
|
|
{{/collection}}
|
|
```
|
|
|
|
And the following application code
|
|
|
|
```javascript
|
|
App = Ember.Application.create();
|
|
App.ApplicationRoute = Ember.Route.extend({
|
|
model: function(){
|
|
return [{name: 'Yehuda'},{name: 'Tom'},{name: 'Peter'}];
|
|
}
|
|
});
|
|
```
|
|
|
|
The following HTML will result:
|
|
|
|
```html
|
|
<div class="ember-view">
|
|
<div class="ember-view">Hi Yehuda</div>
|
|
<div class="ember-view">Hi Tom</div>
|
|
<div class="ember-view">Hi Peter</div>
|
|
</div>
|
|
```
|
|
|
|
### Non-block version of collection
|
|
|
|
If you provide an `itemViewClass` option that has its own `template` you may
|
|
omit the block.
|
|
|
|
The following template:
|
|
|
|
```handlebars
|
|
{{! application.hbs }}
|
|
{{collection content=model itemViewClass="an-item"}}
|
|
```
|
|
|
|
And application code
|
|
|
|
```javascript
|
|
App = Ember.Application.create();
|
|
App.ApplicationRoute = Ember.Route.extend({
|
|
model: function(){
|
|
return [{name: 'Yehuda'},{name: 'Tom'},{name: 'Peter'}];
|
|
}
|
|
});
|
|
|
|
App.AnItemView = Ember.View.extend({
|
|
template: Ember.Handlebars.compile("Greetings {{view.content.name}}")
|
|
});
|
|
```
|
|
|
|
Will result in the HTML structure below
|
|
|
|
```html
|
|
<div class="ember-view">
|
|
<div class="ember-view">Greetings Yehuda</div>
|
|
<div class="ember-view">Greetings Tom</div>
|
|
<div class="ember-view">Greetings Peter</div>
|
|
</div>
|
|
```
|
|
|
|
### Specifying a CollectionView subclass
|
|
|
|
By default the `{{collection}}` helper will create an instance of
|
|
`Ember.CollectionView`. You can supply a `Ember.CollectionView` subclass to
|
|
the helper by passing it as the first argument:
|
|
|
|
```handlebars
|
|
{{#collection "my-custom-collection" content=model}}
|
|
Hi {{view.content.name}}
|
|
{{/collection}}
|
|
```
|
|
|
|
This example would look for the class `App.MyCustomCollection`.
|
|
|
|
### Forwarded `item.*`-named Options
|
|
|
|
As with the `{{view}}`, helper options passed to the `{{collection}}` will be
|
|
set on the resulting `Ember.CollectionView` as properties. Additionally,
|
|
options prefixed with `item` will be applied to the views rendered for each
|
|
item (note the camelcasing):
|
|
|
|
```handlebars
|
|
{{#collection content=model
|
|
itemTagName="p"
|
|
itemClassNames="greeting"}}
|
|
Howdy {{view.content.name}}
|
|
{{/collection}}
|
|
```
|
|
|
|
Will result in the following HTML structure:
|
|
|
|
```html
|
|
<div class="ember-view">
|
|
<p class="ember-view greeting">Howdy Yehuda</p>
|
|
<p class="ember-view greeting">Howdy Tom</p>
|
|
<p class="ember-view greeting">Howdy Peter</p>
|
|
</div>
|
|
```
|
|
|
|
@method collection
|
|
@for Ember.Handlebars.helpers
|
|
@param {String} path
|
|
@param {Hash} options
|
|
@return {String} HTML string
|
|
@deprecated Use `{{each}}` helper instead.
|
|
*/
|
|
function collectionHelper(params, hash, options, env) {
|
|
var path = params[0];
|
|
|
|
Ember.deprecate("Using the {{collection}} helper without specifying a class has been" +
|
|
" deprecated as the {{each}} helper now supports the same functionality.", path !== 'collection');
|
|
|
|
Ember.assert("You cannot pass more than one argument to the collection helper", params.length <= 1);
|
|
|
|
var data = env.data;
|
|
var template = options.template;
|
|
var inverse = options.inverse;
|
|
var view = data.view;
|
|
|
|
// This should be deterministic, and should probably come from a
|
|
// parent view and not the controller.
|
|
var controller = get(view, 'controller');
|
|
var container = (controller && controller.container ? controller.container : view.container);
|
|
|
|
// If passed a path string, convert that into an object.
|
|
// Otherwise, just default to the standard class.
|
|
var collectionClass;
|
|
if (path) {
|
|
collectionClass = readViewFactory(path, container);
|
|
Ember.assert(fmt("%@ #collection: Could not find collection class %@", [data.view, path]), !!collectionClass);
|
|
}
|
|
else {
|
|
collectionClass = CollectionView;
|
|
}
|
|
|
|
var itemHash = {};
|
|
var match;
|
|
|
|
// Extract item view class if provided else default to the standard class
|
|
var collectionPrototype = collectionClass.proto();
|
|
var itemViewClass;
|
|
|
|
if (hash.itemView) {
|
|
itemViewClass = readViewFactory(hash.itemView, container);
|
|
} else if (hash.itemViewClass) {
|
|
itemViewClass = readViewFactory(hash.itemViewClass, container);
|
|
} else {
|
|
itemViewClass = collectionPrototype.itemViewClass;
|
|
}
|
|
|
|
if (typeof itemViewClass === 'string') {
|
|
itemViewClass = container.lookupFactory('view:'+itemViewClass);
|
|
}
|
|
|
|
Ember.assert(fmt("%@ #collection: Could not find itemViewClass %@", [data.view, itemViewClass]), !!itemViewClass);
|
|
|
|
delete hash.itemViewClass;
|
|
delete hash.itemView;
|
|
|
|
// Go through options passed to the {{collection}} helper and extract options
|
|
// that configure item views instead of the collection itself.
|
|
for (var prop in hash) {
|
|
if (prop === 'itemController' || prop === 'itemClassBinding') {
|
|
continue;
|
|
}
|
|
if (hash.hasOwnProperty(prop)) {
|
|
match = prop.match(/^item(.)(.*)$/);
|
|
if (match) {
|
|
var childProp = match[1].toLowerCase() + match[2];
|
|
|
|
if (IS_BINDING.test(prop)) {
|
|
itemHash[childProp] = view._getBindingForStream(hash[prop]);
|
|
} else {
|
|
itemHash[childProp] = hash[prop];
|
|
}
|
|
delete hash[prop];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (template) {
|
|
itemHash.template = template;
|
|
delete options.template;
|
|
}
|
|
|
|
var emptyViewClass;
|
|
if (inverse) {
|
|
emptyViewClass = get(collectionPrototype, 'emptyViewClass');
|
|
emptyViewClass = emptyViewClass.extend({
|
|
template: inverse,
|
|
tagName: itemHash.tagName
|
|
});
|
|
} else if (hash.emptyViewClass) {
|
|
emptyViewClass = readViewFactory(hash.emptyViewClass, container);
|
|
}
|
|
if (emptyViewClass) { hash.emptyView = emptyViewClass; }
|
|
|
|
if (hash.keyword) {
|
|
itemHash._contextBinding = Binding.oneWay('_parentView.context');
|
|
} else {
|
|
itemHash._contextBinding = Binding.oneWay('content');
|
|
}
|
|
|
|
var viewOptions = ViewHelper.propertiesFromHTMLOptions(itemHash, {}, { data: data });
|
|
|
|
if (hash.itemClassBinding) {
|
|
var itemClassBindings = hash.itemClassBinding.split(' ');
|
|
viewOptions.classNameBindings = map(itemClassBindings, function(classBinding){
|
|
return streamifyClassNameBinding(view, classBinding);
|
|
});
|
|
}
|
|
|
|
hash.itemViewClass = itemViewClass;
|
|
hash._itemViewProps = viewOptions;
|
|
|
|
options.helperName = options.helperName || 'collection';
|
|
|
|
return env.helpers.view.helperFunction.call(this, [collectionClass], hash, options, env);
|
|
}
|
|
|
|
__exports__.collectionHelper = collectionHelper;
|
|
});
|
|
enifed("ember-htmlbars/helpers/debugger",
|
|
["ember-metal/logger","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
/*jshint debug:true*/
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
var Logger = __dependency1__["default"];
|
|
|
|
/**
|
|
Execute the `debugger` statement in the current context.
|
|
|
|
```handlebars
|
|
{{debugger}}
|
|
```
|
|
|
|
Before invoking the `debugger` statement, there
|
|
are a few helpful variables defined in the
|
|
body of this helper that you can inspect while
|
|
debugging that describe how and where this
|
|
helper was invoked:
|
|
|
|
- templateContext: this is most likely a controller
|
|
from which this template looks up / displays properties
|
|
- typeOfTemplateContext: a string description of
|
|
what the templateContext is
|
|
|
|
For example, if you're wondering why a value `{{foo}}`
|
|
isn't rendering as expected within a template, you
|
|
could place a `{{debugger}}` statement, and when
|
|
the `debugger;` breakpoint is hit, you can inspect
|
|
`templateContext`, determine if it's the object you
|
|
expect, and/or evaluate expressions in the console
|
|
to perform property lookups on the `templateContext`:
|
|
|
|
```
|
|
> templateContext.get('foo') // -> "<value of {{foo}}>"
|
|
```
|
|
|
|
@method debugger
|
|
@for Ember.Handlebars.helpers
|
|
@param {String} property
|
|
*/
|
|
function debuggerHelper() {
|
|
|
|
// These are helpful values you can inspect while debugging.
|
|
/* jshint unused: false */
|
|
var view = this;
|
|
Logger.info('Use `this` to access the view context.');
|
|
|
|
debugger;
|
|
}
|
|
|
|
__exports__.debuggerHelper = debuggerHelper;
|
|
});
|
|
enifed("ember-htmlbars/helpers/each",
|
|
["ember-metal/core","ember-views/views/each","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.assert;
|
|
var EachView = __dependency2__["default"];
|
|
|
|
/**
|
|
The `{{#each}}` helper loops over elements in a collection. It is an extension
|
|
of the base Handlebars `{{#each}}` helper.
|
|
|
|
The default behavior of `{{#each}}` is to yield its inner block once for every
|
|
item in an array.
|
|
|
|
```javascript
|
|
var developers = [{name: 'Yehuda'},{name: 'Tom'}, {name: 'Paul'}];
|
|
```
|
|
|
|
```handlebars
|
|
{{#each person in developers}}
|
|
{{person.name}}
|
|
{{! `this` is whatever it was outside the #each }}
|
|
{{/each}}
|
|
```
|
|
|
|
The same rules apply to arrays of primitives, but the items may need to be
|
|
references with `{{this}}`.
|
|
|
|
```javascript
|
|
var developerNames = ['Yehuda', 'Tom', 'Paul']
|
|
```
|
|
|
|
```handlebars
|
|
{{#each name in developerNames}}
|
|
{{name}}
|
|
{{/each}}
|
|
```
|
|
|
|
### {{else}} condition
|
|
|
|
`{{#each}}` can have a matching `{{else}}`. The contents of this block will render
|
|
if the collection is empty.
|
|
|
|
```
|
|
{{#each person in developers}}
|
|
{{person.name}}
|
|
{{else}}
|
|
<p>Sorry, nobody is available for this task.</p>
|
|
{{/each}}
|
|
```
|
|
|
|
### Specifying an alternative view for each item
|
|
|
|
`itemViewClass` can control which view will be used during the render of each
|
|
item's template.
|
|
|
|
The following template:
|
|
|
|
```handlebars
|
|
<ul>
|
|
{{#each developer in developers itemViewClass="person"}}
|
|
{{developer.name}}
|
|
{{/each}}
|
|
</ul>
|
|
```
|
|
|
|
Will use the following view for each item
|
|
|
|
```javascript
|
|
App.PersonView = Ember.View.extend({
|
|
tagName: 'li'
|
|
});
|
|
```
|
|
|
|
Resulting in HTML output that looks like the following:
|
|
|
|
```html
|
|
<ul>
|
|
<li class="ember-view">Yehuda</li>
|
|
<li class="ember-view">Tom</li>
|
|
<li class="ember-view">Paul</li>
|
|
</ul>
|
|
```
|
|
|
|
`itemViewClass` also enables a non-block form of `{{each}}`. The view
|
|
must {{#crossLink "Ember.View/toc_templates"}}provide its own template{{/crossLink}},
|
|
and then the block should be dropped. An example that outputs the same HTML
|
|
as the previous one:
|
|
|
|
```javascript
|
|
App.PersonView = Ember.View.extend({
|
|
tagName: 'li',
|
|
template: '{{developer.name}}'
|
|
});
|
|
```
|
|
|
|
```handlebars
|
|
<ul>
|
|
{{each developer in developers itemViewClass="person"}}
|
|
</ul>
|
|
```
|
|
|
|
### Specifying an alternative view for no items (else)
|
|
|
|
The `emptyViewClass` option provides the same flexibility to the `{{else}}`
|
|
case of the each helper.
|
|
|
|
```javascript
|
|
App.NoPeopleView = Ember.View.extend({
|
|
tagName: 'li',
|
|
template: 'No person is available, sorry'
|
|
});
|
|
```
|
|
|
|
```handlebars
|
|
<ul>
|
|
{{#each developer in developers emptyViewClass="no-people"}}
|
|
<li>{{developer.name}}</li>
|
|
{{/each}}
|
|
</ul>
|
|
```
|
|
|
|
### Wrapping each item in a controller
|
|
|
|
Controllers in Ember manage state and decorate data. In many cases,
|
|
providing a controller for each item in a list can be useful.
|
|
Specifically, an {{#crossLink "Ember.ObjectController"}}Ember.ObjectController{{/crossLink}}
|
|
should probably be used. Item controllers are passed the item they
|
|
will present as a `model` property, and an object controller will
|
|
proxy property lookups to `model` for us.
|
|
|
|
This allows state and decoration to be added to the controller
|
|
while any other property lookups are delegated to the model. An example:
|
|
|
|
```javascript
|
|
App.RecruitController = Ember.ObjectController.extend({
|
|
isAvailableForHire: function() {
|
|
return !this.get('isEmployed') && this.get('isSeekingWork');
|
|
}.property('isEmployed', 'isSeekingWork')
|
|
})
|
|
```
|
|
|
|
```handlebars
|
|
{{#each person in developers itemController="recruit"}}
|
|
{{person.name}} {{#if person.isAvailableForHire}}Hire me!{{/if}}
|
|
{{/each}}
|
|
```
|
|
|
|
@method each
|
|
@for Ember.Handlebars.helpers
|
|
@param [name] {String} name for item (used with `in`)
|
|
@param [path] {String} path
|
|
@param [options] {Object} Handlebars key/value pairs of options
|
|
@param [options.itemViewClass] {String} a path to a view class used for each item
|
|
@param [options.emptyViewClass] {String} a path to a view class used for each item
|
|
@param [options.itemController] {String} name of a controller to be created for each item
|
|
*/
|
|
function eachHelper(params, hash, options, env) {
|
|
var helperName = 'each';
|
|
var path = params[0] || this.getStream('');
|
|
|
|
Ember.assert(
|
|
"If you pass more than one argument to the each helper, " +
|
|
"it must be in the form #each foo in bar",
|
|
params.length <= 1
|
|
);
|
|
|
|
if (options.template && options.template.blockParams) {
|
|
hash.keyword = true;
|
|
}
|
|
|
|
Ember.deprecate(
|
|
"Using the context switching form of {{each}} is deprecated. " +
|
|
"Please use the keyword form (`{{#each foo in bar}}`) instead.",
|
|
hash.keyword === true || typeof hash.keyword === 'string',
|
|
{ url: 'http://emberjs.com/guides/deprecations/#toc_more-consistent-handlebars-scope' }
|
|
);
|
|
|
|
hash.dataSource = path;
|
|
options.helperName = options.helperName || helperName;
|
|
|
|
return env.helpers.collection.helperFunction.call(this, [EachView], hash, options, env);
|
|
}
|
|
|
|
__exports__.EachView = EachView;
|
|
__exports__.eachHelper = eachHelper;
|
|
});
|
|
enifed("ember-htmlbars/helpers/if_unless",
|
|
["ember-metal/core","ember-htmlbars/helpers/binding","ember-metal/property_get","ember-metal/utils","ember-views/streams/conditional_stream","ember-metal/streams/utils","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.assert
|
|
var bind = __dependency2__.bind;
|
|
|
|
var get = __dependency3__.get;
|
|
var isArray = __dependency4__.isArray;
|
|
var ConditionalStream = __dependency5__["default"];
|
|
var isStream = __dependency6__.isStream;
|
|
|
|
function shouldDisplayIfHelperContent(result) {
|
|
var truthy = result && get(result, 'isTruthy');
|
|
if (typeof truthy === 'boolean') { return truthy; }
|
|
|
|
if (isArray(result)) {
|
|
return get(result, 'length') !== 0;
|
|
} else {
|
|
return !!result;
|
|
}
|
|
}
|
|
|
|
var EMPTY_TEMPLATE = {
|
|
isHTMLBars: true,
|
|
render: function() {
|
|
return '';
|
|
}
|
|
};
|
|
/**
|
|
Use the `boundIf` helper to create a conditional that re-evaluates
|
|
whenever the truthiness of the bound value changes.
|
|
|
|
```handlebars
|
|
{{#boundIf "content.shouldDisplayTitle"}}
|
|
{{content.title}}
|
|
{{/boundIf}}
|
|
```
|
|
|
|
@private
|
|
@method boundIf
|
|
@for Ember.Handlebars.helpers
|
|
@param {String} property Property to bind
|
|
@param {Function} fn Context to provide for rendering
|
|
@return {String} HTML string
|
|
*/
|
|
function boundIfHelper(params, hash, options, env) {
|
|
options.helperName = options.helperName || 'boundIf';
|
|
return bind.call(this, params[0], hash, options, env, true, shouldDisplayIfHelperContent, shouldDisplayIfHelperContent, [
|
|
'isTruthy',
|
|
'length'
|
|
]);
|
|
}
|
|
|
|
/**
|
|
@private
|
|
|
|
Use the `unboundIf` helper to create a conditional that evaluates once.
|
|
|
|
```handlebars
|
|
{{#unboundIf "content.shouldDisplayTitle"}}
|
|
{{content.title}}
|
|
{{/unboundIf}}
|
|
```
|
|
|
|
@method unboundIf
|
|
@for Ember.Handlebars.helpers
|
|
@param {String} property Property to bind
|
|
@param {Function} fn Context to provide for rendering
|
|
@return {String} HTML string
|
|
@since 1.4.0
|
|
*/
|
|
function unboundIfHelper(params, hash, options, env) {
|
|
var template = options.template;
|
|
var value = params[0];
|
|
|
|
if (isStream(params[0])) {
|
|
value = params[0].value();
|
|
}
|
|
|
|
if (!shouldDisplayIfHelperContent(value)) {
|
|
template = options.inverse || EMPTY_TEMPLATE;
|
|
}
|
|
|
|
return template.render(this, env, options.morph.contextualElement);
|
|
}
|
|
|
|
function _inlineIfAssertion(params) {
|
|
Ember.assert("If helper in inline form expects between two and three arguments", params.length === 2 || params.length === 3);
|
|
}
|
|
|
|
/**
|
|
See [boundIf](/api/classes/Ember.Handlebars.helpers.html#method_boundIf)
|
|
and [unboundIf](/api/classes/Ember.Handlebars.helpers.html#method_unboundIf)
|
|
|
|
@method if
|
|
@for Ember.Handlebars.helpers
|
|
@param {Function} context
|
|
@param {Hash} options
|
|
@return {String} HTML string
|
|
*/
|
|
function ifHelper(params, hash, options, env) {
|
|
Ember.assert("If helper in block form expect exactly one argument", !options.template || params.length === 1);
|
|
|
|
options.inverse = options.inverse || EMPTY_TEMPLATE;
|
|
|
|
options.helperName = options.helperName || ('if ');
|
|
|
|
if (env.data.isUnbound) {
|
|
env.data.isUnbound = false;
|
|
return env.helpers.unboundIf.helperFunction.call(this, params, hash, options, env);
|
|
} else {
|
|
return env.helpers.boundIf.helperFunction.call(this, params, hash, options, env);
|
|
}
|
|
}
|
|
|
|
/**
|
|
@method unless
|
|
@for Ember.Handlebars.helpers
|
|
@param {Function} context
|
|
@param {Hash} options
|
|
@return {String} HTML string
|
|
*/
|
|
function unlessHelper(params, hash, options, env) {
|
|
Ember.assert("You must pass exactly one argument to the unless helper", params.length === 1);
|
|
Ember.assert("You must pass a block to the unless helper", !!options.template);
|
|
|
|
var template = options.template;
|
|
var inverse = options.inverse || EMPTY_TEMPLATE;
|
|
var helperName = 'unless';
|
|
|
|
options.template = inverse;
|
|
options.inverse = template;
|
|
|
|
options.helperName = options.helperName || helperName;
|
|
|
|
if (env.data.isUnbound) {
|
|
env.data.isUnbound = false;
|
|
return env.helpers.unboundIf.helperFunction.call(this, params, hash, options, env);
|
|
} else {
|
|
return env.helpers.boundIf.helperFunction.call(this, params, hash, options, env);
|
|
}
|
|
}
|
|
|
|
__exports__.ifHelper = ifHelper;
|
|
__exports__.boundIfHelper = boundIfHelper;
|
|
__exports__.unboundIfHelper = unboundIfHelper;
|
|
__exports__.unlessHelper = unlessHelper;
|
|
});
|
|
enifed("ember-htmlbars/helpers/input",
|
|
["ember-views/views/checkbox","ember-views/views/text_field","ember-metal/streams/utils","ember-metal/core","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
|
|
"use strict";
|
|
var Checkbox = __dependency1__["default"];
|
|
var TextField = __dependency2__["default"];
|
|
var read = __dependency3__.read;
|
|
|
|
var Ember = __dependency4__["default"];
|
|
// Ember.assert
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
/**
|
|
|
|
The `{{input}}` helper inserts an HTML `<input>` tag into the template,
|
|
with a `type` value of either `text` or `checkbox`. If no `type` is provided,
|
|
`text` will be the default value applied. The attributes of `{{input}}`
|
|
match those of the native HTML tag as closely as possible for these two types.
|
|
|
|
## Use as text field
|
|
An `{{input}}` with no `type` or a `type` of `text` will render an HTML text input.
|
|
The following HTML attributes can be set via the helper:
|
|
|
|
<table>
|
|
<tr><td>`readonly`</td><td>`required`</td><td>`autofocus`</td></tr>
|
|
<tr><td>`value`</td><td>`placeholder`</td><td>`disabled`</td></tr>
|
|
<tr><td>`size`</td><td>`tabindex`</td><td>`maxlength`</td></tr>
|
|
<tr><td>`name`</td><td>`min`</td><td>`max`</td></tr>
|
|
<tr><td>`pattern`</td><td>`accept`</td><td>`autocomplete`</td></tr>
|
|
<tr><td>`autosave`</td><td>`formaction`</td><td>`formenctype`</td></tr>
|
|
<tr><td>`formmethod`</td><td>`formnovalidate`</td><td>`formtarget`</td></tr>
|
|
<tr><td>`height`</td><td>`inputmode`</td><td>`multiple`</td></tr>
|
|
<tr><td>`step`</td><td>`width`</td><td>`form`</td></tr>
|
|
<tr><td>`selectionDirection`</td><td>`spellcheck`</td><td> </td></tr>
|
|
</table>
|
|
|
|
|
|
When set to a quoted string, these values will be directly applied to the HTML
|
|
element. When left unquoted, these values will be bound to a property on the
|
|
template's current rendering context (most typically a controller instance).
|
|
|
|
## Unbound:
|
|
|
|
```handlebars
|
|
{{input value="http://www.facebook.com"}}
|
|
```
|
|
|
|
|
|
```html
|
|
<input type="text" value="http://www.facebook.com"/>
|
|
```
|
|
|
|
## Bound:
|
|
|
|
```javascript
|
|
App.ApplicationController = Ember.Controller.extend({
|
|
firstName: "Stanley",
|
|
entryNotAllowed: true
|
|
});
|
|
```
|
|
|
|
|
|
```handlebars
|
|
{{input type="text" value=firstName disabled=entryNotAllowed size="50"}}
|
|
```
|
|
|
|
|
|
```html
|
|
<input type="text" value="Stanley" disabled="disabled" size="50"/>
|
|
```
|
|
|
|
## Actions
|
|
|
|
The helper can send multiple actions based on user events.
|
|
|
|
The action property defines the action which is sent when
|
|
the user presses the return key.
|
|
|
|
```handlebars
|
|
{{input action="submit"}}
|
|
```
|
|
|
|
The helper allows some user events to send actions.
|
|
|
|
* `enter`
|
|
* `insert-newline`
|
|
* `escape-press`
|
|
* `focus-in`
|
|
* `focus-out`
|
|
* `key-press`
|
|
|
|
|
|
For example, if you desire an action to be sent when the input is blurred,
|
|
you only need to setup the action name to the event name property.
|
|
|
|
```handlebars
|
|
{{input focus-in="alertMessage"}}
|
|
```
|
|
|
|
See more about [Text Support Actions](/api/classes/Ember.TextField.html)
|
|
|
|
## Extension
|
|
|
|
Internally, `{{input type="text"}}` creates an instance of `Ember.TextField`, passing
|
|
arguments from the helper to `Ember.TextField`'s `create` method. You can extend the
|
|
capabilities of text inputs in your applications by reopening this class. For example,
|
|
if you are building a Bootstrap project where `data-*` attributes are used, you
|
|
can add one to the `TextField`'s `attributeBindings` property:
|
|
|
|
|
|
```javascript
|
|
Ember.TextField.reopen({
|
|
attributeBindings: ['data-error']
|
|
});
|
|
```
|
|
|
|
Keep in mind when writing `Ember.TextField` subclasses that `Ember.TextField`
|
|
itself extends `Ember.Component`, meaning that it does NOT inherit
|
|
the `controller` of the parent view.
|
|
|
|
See more about [Ember components](/api/classes/Ember.Component.html)
|
|
|
|
|
|
## Use as checkbox
|
|
|
|
An `{{input}}` with a `type` of `checkbox` will render an HTML checkbox input.
|
|
The following HTML attributes can be set via the helper:
|
|
|
|
* `checked`
|
|
* `disabled`
|
|
* `tabindex`
|
|
* `indeterminate`
|
|
* `name`
|
|
* `autofocus`
|
|
* `form`
|
|
|
|
|
|
When set to a quoted string, these values will be directly applied to the HTML
|
|
element. When left unquoted, these values will be bound to a property on the
|
|
template's current rendering context (most typically a controller instance).
|
|
|
|
## Unbound:
|
|
|
|
```handlebars
|
|
{{input type="checkbox" name="isAdmin"}}
|
|
```
|
|
|
|
```html
|
|
<input type="checkbox" name="isAdmin" />
|
|
```
|
|
|
|
## Bound:
|
|
|
|
```javascript
|
|
App.ApplicationController = Ember.Controller.extend({
|
|
isAdmin: true
|
|
});
|
|
```
|
|
|
|
|
|
```handlebars
|
|
{{input type="checkbox" checked=isAdmin }}
|
|
```
|
|
|
|
|
|
```html
|
|
<input type="checkbox" checked="checked" />
|
|
```
|
|
|
|
## Extension
|
|
|
|
Internally, `{{input type="checkbox"}}` creates an instance of `Ember.Checkbox`, passing
|
|
arguments from the helper to `Ember.Checkbox`'s `create` method. You can extend the
|
|
capablilties of checkbox inputs in your applications by reopening this class. For example,
|
|
if you wanted to add a css class to all checkboxes in your application:
|
|
|
|
|
|
```javascript
|
|
Ember.Checkbox.reopen({
|
|
classNames: ['my-app-checkbox']
|
|
});
|
|
```
|
|
|
|
|
|
@method input
|
|
@for Ember.Handlebars.helpers
|
|
@param {Hash} options
|
|
*/
|
|
function inputHelper(params, hash, options, env) {
|
|
Ember.assert('You can only pass attributes to the `input` helper, not arguments', params.length === 0);
|
|
|
|
var onEvent = hash.on;
|
|
var inputType;
|
|
|
|
inputType = read(hash.type);
|
|
|
|
if (inputType === 'checkbox') {
|
|
delete hash.type;
|
|
|
|
Ember.assert("{{input type='checkbox'}} does not support setting `value=someBooleanValue`;" +
|
|
" you must use `checked=someBooleanValue` instead.", !hash.hasOwnProperty('value'));
|
|
|
|
env.helpers.view.helperFunction.call(this, [Checkbox], hash, options, env);
|
|
} else {
|
|
delete hash.on;
|
|
|
|
hash.onEvent = onEvent || 'enter';
|
|
env.helpers.view.helperFunction.call(this, [TextField], hash, options, env);
|
|
}
|
|
}
|
|
|
|
__exports__.inputHelper = inputHelper;
|
|
});
|
|
enifed("ember-htmlbars/helpers/loc",
|
|
["ember-metal/core","ember-runtime/system/string","ember-metal/streams/utils","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
var loc = __dependency2__.loc;
|
|
var isStream = __dependency3__.isStream;
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
/**
|
|
Calls [Ember.String.loc](/api/classes/Ember.String.html#method_loc) with the
|
|
provided string.
|
|
|
|
This is a convenient way to localize text within a template:
|
|
|
|
```javascript
|
|
Ember.STRINGS = {
|
|
'_welcome_': 'Bonjour'
|
|
};
|
|
```
|
|
|
|
```handlebars
|
|
<div class='message'>
|
|
{{loc '_welcome_'}}
|
|
</div>
|
|
```
|
|
|
|
```html
|
|
<div class='message'>
|
|
Bonjour
|
|
</div>
|
|
```
|
|
|
|
See [Ember.String.loc](/api/classes/Ember.String.html#method_loc) for how to
|
|
set up localized string references.
|
|
|
|
@method loc
|
|
@for Ember.Handlebars.helpers
|
|
@param {String} str The string to format
|
|
@see {Ember.String#loc}
|
|
*/
|
|
function locHelper(params, hash, options, env) {
|
|
Ember.assert('You cannot pass bindings to `loc` helper', (function ifParamsContainBindings() {
|
|
for (var i = 0, l = params.length; i < l; i++) {
|
|
if (isStream(params[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
})());
|
|
|
|
return loc.apply(this, params);
|
|
}
|
|
|
|
__exports__.locHelper = locHelper;
|
|
});
|
|
enifed("ember-htmlbars/helpers/log",
|
|
["ember-metal/logger","ember-metal/streams/utils","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
var Logger = __dependency1__["default"];
|
|
var read = __dependency2__.read;
|
|
|
|
/**
|
|
`log` allows you to output the value of variables in the current rendering
|
|
context. `log` also accepts primitive types such as strings or numbers.
|
|
|
|
```handlebars
|
|
{{log "myVariable:" myVariable }}
|
|
```
|
|
|
|
@method log
|
|
@for Ember.Handlebars.helpers
|
|
@param {String} property
|
|
*/
|
|
function logHelper(params, hash, options, env) {
|
|
var logger = Logger.log;
|
|
var values = [];
|
|
|
|
for (var i = 0; i < params.length; i++) {
|
|
values.push(read(params[i]));
|
|
}
|
|
|
|
logger.apply(logger, values);
|
|
}
|
|
|
|
__exports__.logHelper = logHelper;
|
|
});
|
|
enifed("ember-htmlbars/helpers/partial",
|
|
["ember-metal/core","ember-metal/is_none","./binding","ember-metal/streams/utils","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.assert
|
|
|
|
var isNone = __dependency2__["default"];
|
|
var bind = __dependency3__.bind;
|
|
var isStream = __dependency4__.isStream;
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
/**
|
|
The `partial` helper renders another template without
|
|
changing the template context:
|
|
|
|
```handlebars
|
|
{{foo}}
|
|
{{partial "nav"}}
|
|
```
|
|
|
|
The above example template will render a template named
|
|
"_nav", which has the same context as the parent template
|
|
it's rendered into, so if the "_nav" template also referenced
|
|
`{{foo}}`, it would print the same thing as the `{{foo}}`
|
|
in the above example.
|
|
|
|
If a "_nav" template isn't found, the `partial` helper will
|
|
fall back to a template named "nav".
|
|
|
|
## Bound template names
|
|
|
|
The parameter supplied to `partial` can also be a path
|
|
to a property containing a template name, e.g.:
|
|
|
|
```handlebars
|
|
{{partial someTemplateName}}
|
|
```
|
|
|
|
The above example will look up the value of `someTemplateName`
|
|
on the template context (e.g. a controller) and use that
|
|
value as the name of the template to render. If the resolved
|
|
value is falsy, nothing will be rendered. If `someTemplateName`
|
|
changes, the partial will be re-rendered using the new template
|
|
name.
|
|
|
|
|
|
@method partial
|
|
@for Ember.Handlebars.helpers
|
|
@param {String} partialName the name of the template to render minus the leading underscore
|
|
*/
|
|
|
|
function partialHelper(params, hash, options, env) {
|
|
options.helperName = options.helperName || 'partial';
|
|
|
|
var name = params[0];
|
|
|
|
if (isStream(name)) {
|
|
options.template = createPartialTemplate(name);
|
|
bind.call(this, name, hash, options, env, true, exists);
|
|
} else {
|
|
return renderPartial(name, this, env, options.morph.contextualElement);
|
|
}
|
|
}
|
|
|
|
__exports__.partialHelper = partialHelper;function exists(value) {
|
|
return !isNone(value);
|
|
}
|
|
|
|
function lookupPartial(view, templateName) {
|
|
var nameParts = templateName.split("/");
|
|
var lastPart = nameParts[nameParts.length - 1];
|
|
|
|
nameParts[nameParts.length - 1] = "_" + lastPart;
|
|
|
|
var underscoredName = nameParts.join('/');
|
|
var template = view.templateForName(underscoredName);
|
|
if (!template) {
|
|
template = view.templateForName(templateName);
|
|
}
|
|
|
|
Ember.assert('Unable to find partial with name "'+templateName+'"', !!template);
|
|
|
|
return template;
|
|
}
|
|
|
|
function renderPartial(name, view, env, contextualElement) {
|
|
var template = lookupPartial(view, name);
|
|
return template.render(view, env, contextualElement);
|
|
}
|
|
|
|
function createPartialTemplate(nameStream) {
|
|
return {
|
|
isHTMLBars: true,
|
|
render: function(view, env, contextualElement) {
|
|
return renderPartial(nameStream.value(), view, env, contextualElement);
|
|
}
|
|
};
|
|
}
|
|
});
|
|
enifed("ember-htmlbars/helpers/template",
|
|
["ember-metal/core","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.deprecate;
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
/**
|
|
@deprecated
|
|
@method template
|
|
@for Ember.Handlebars.helpers
|
|
@param {String} templateName the template to render
|
|
*/
|
|
function templateHelper(params, hash, options, env) {
|
|
Ember.deprecate("The `template` helper has been deprecated in favor of the `partial` helper." +
|
|
" Please use `partial` instead, which will work the same way.");
|
|
|
|
options.helperName = options.helperName || 'template';
|
|
|
|
return env.helpers.partial.helperFunction.call(this, params, hash, options, env);
|
|
}
|
|
|
|
__exports__.templateHelper = templateHelper;
|
|
});
|
|
enifed("ember-htmlbars/helpers/text_area",
|
|
["ember-metal/core","ember-views/views/text_area","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.assert
|
|
var TextArea = __dependency2__["default"];
|
|
|
|
/**
|
|
`{{textarea}}` inserts a new instance of `<textarea>` tag into the template.
|
|
The attributes of `{{textarea}}` match those of the native HTML tags as
|
|
closely as possible.
|
|
|
|
The following HTML attributes can be set:
|
|
|
|
* `value`
|
|
* `name`
|
|
* `rows`
|
|
* `cols`
|
|
* `placeholder`
|
|
* `disabled`
|
|
* `maxlength`
|
|
* `tabindex`
|
|
* `selectionEnd`
|
|
* `selectionStart`
|
|
* `selectionDirection`
|
|
* `wrap`
|
|
* `readonly`
|
|
* `autofocus`
|
|
* `form`
|
|
* `spellcheck`
|
|
* `required`
|
|
|
|
When set to a quoted string, these value will be directly applied to the HTML
|
|
element. When left unquoted, these values will be bound to a property on the
|
|
template's current rendering context (most typically a controller instance).
|
|
|
|
Unbound:
|
|
|
|
```handlebars
|
|
{{textarea value="Lots of static text that ISN'T bound"}}
|
|
```
|
|
|
|
Would result in the following HTML:
|
|
|
|
```html
|
|
<textarea class="ember-text-area">
|
|
Lots of static text that ISN'T bound
|
|
</textarea>
|
|
```
|
|
|
|
Bound:
|
|
|
|
In the following example, the `writtenWords` property on `App.ApplicationController`
|
|
will be updated live as the user types 'Lots of text that IS bound' into
|
|
the text area of their browser's window.
|
|
|
|
```javascript
|
|
App.ApplicationController = Ember.Controller.extend({
|
|
writtenWords: "Lots of text that IS bound"
|
|
});
|
|
```
|
|
|
|
```handlebars
|
|
{{textarea value=writtenWords}}
|
|
```
|
|
|
|
Would result in the following HTML:
|
|
|
|
```html
|
|
<textarea class="ember-text-area">
|
|
Lots of text that IS bound
|
|
</textarea>
|
|
```
|
|
|
|
If you wanted a one way binding between the text area and a div tag
|
|
somewhere else on your screen, you could use `Ember.computed.oneWay`:
|
|
|
|
```javascript
|
|
App.ApplicationController = Ember.Controller.extend({
|
|
writtenWords: "Lots of text that IS bound",
|
|
outputWrittenWords: Ember.computed.oneWay("writtenWords")
|
|
});
|
|
```
|
|
|
|
```handlebars
|
|
{{textarea value=writtenWords}}
|
|
|
|
<div>
|
|
{{outputWrittenWords}}
|
|
</div>
|
|
```
|
|
|
|
Would result in the following HTML:
|
|
|
|
```html
|
|
<textarea class="ember-text-area">
|
|
Lots of text that IS bound
|
|
</textarea>
|
|
|
|
<-- the following div will be updated in real time as you type -->
|
|
|
|
<div>
|
|
Lots of text that IS bound
|
|
</div>
|
|
```
|
|
|
|
Finally, this example really shows the power and ease of Ember when two
|
|
properties are bound to eachother via `Ember.computed.alias`. Type into
|
|
either text area box and they'll both stay in sync. Note that
|
|
`Ember.computed.alias` costs more in terms of performance, so only use it when
|
|
your really binding in both directions:
|
|
|
|
```javascript
|
|
App.ApplicationController = Ember.Controller.extend({
|
|
writtenWords: "Lots of text that IS bound",
|
|
twoWayWrittenWords: Ember.computed.alias("writtenWords")
|
|
});
|
|
```
|
|
|
|
```handlebars
|
|
{{textarea value=writtenWords}}
|
|
{{textarea value=twoWayWrittenWords}}
|
|
```
|
|
|
|
```html
|
|
<textarea id="ember1" class="ember-text-area">
|
|
Lots of text that IS bound
|
|
</textarea>
|
|
|
|
<-- both updated in real time -->
|
|
|
|
<textarea id="ember2" class="ember-text-area">
|
|
Lots of text that IS bound
|
|
</textarea>
|
|
```
|
|
|
|
## Actions
|
|
|
|
The helper can send multiple actions based on user events.
|
|
|
|
The action property defines the action which is send when
|
|
the user presses the return key.
|
|
|
|
```handlebars
|
|
{{input action="submit"}}
|
|
```
|
|
|
|
The helper allows some user events to send actions.
|
|
|
|
* `enter`
|
|
* `insert-newline`
|
|
* `escape-press`
|
|
* `focus-in`
|
|
* `focus-out`
|
|
* `key-press`
|
|
|
|
For example, if you desire an action to be sent when the input is blurred,
|
|
you only need to setup the action name to the event name property.
|
|
|
|
```handlebars
|
|
{{textarea focus-in="alertMessage"}}
|
|
```
|
|
|
|
See more about [Text Support Actions](/api/classes/Ember.TextArea.html)
|
|
|
|
## Extension
|
|
|
|
Internally, `{{textarea}}` creates an instance of `Ember.TextArea`, passing
|
|
arguments from the helper to `Ember.TextArea`'s `create` method. You can
|
|
extend the capabilities of text areas in your application by reopening this
|
|
class. For example, if you are building a Bootstrap project where `data-*`
|
|
attributes are used, you can globally add support for a `data-*` attribute
|
|
on all `{{textarea}}`s' in your app by reopening `Ember.TextArea` or
|
|
`Ember.TextSupport` and adding it to the `attributeBindings` concatenated
|
|
property:
|
|
|
|
```javascript
|
|
Ember.TextArea.reopen({
|
|
attributeBindings: ['data-error']
|
|
});
|
|
```
|
|
|
|
Keep in mind when writing `Ember.TextArea` subclasses that `Ember.TextArea`
|
|
itself extends `Ember.Component`, meaning that it does NOT inherit
|
|
the `controller` of the parent view.
|
|
|
|
See more about [Ember components](/api/classes/Ember.Component.html)
|
|
|
|
@method textarea
|
|
@for Ember.Handlebars.helpers
|
|
@param {Hash} options
|
|
*/
|
|
function textareaHelper(params, hash, options, env) {
|
|
Ember.assert('You can only pass attributes to the `textarea` helper, not arguments', params.length === 0);
|
|
|
|
return env.helpers.view.helperFunction.call(this, [TextArea], hash, options, env);
|
|
}
|
|
|
|
__exports__.textareaHelper = textareaHelper;
|
|
});
|
|
enifed("ember-htmlbars/helpers/unbound",
|
|
["ember-htmlbars/system/lookup-helper","ember-metal/streams/utils","ember-metal/error","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
var lookupHelper = __dependency1__["default"];
|
|
var read = __dependency2__.read;
|
|
var EmberError = __dependency3__["default"];
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
/**
|
|
`unbound` allows you to output a property without binding. *Important:* The
|
|
output will not be updated if the property changes. Use with caution.
|
|
|
|
```handlebars
|
|
<div>{{unbound somePropertyThatDoesntChange}}</div>
|
|
```
|
|
|
|
`unbound` can also be used in conjunction with a bound helper to
|
|
render it in its unbound form:
|
|
|
|
```handlebars
|
|
<div>{{unbound helperName somePropertyThatDoesntChange}}</div>
|
|
```
|
|
|
|
@method unbound
|
|
@for Ember.Handlebars.helpers
|
|
@param {String} property
|
|
@return {String} HTML string
|
|
*/
|
|
function unboundHelper(params, hash, options, env) {
|
|
var length = params.length;
|
|
var result;
|
|
|
|
options.helperName = options.helperName || 'unbound';
|
|
|
|
if (length === 1) {
|
|
result = read(params[0]);
|
|
} else if (length >= 2) {
|
|
env.data.isUnbound = true;
|
|
|
|
var helperName = params[0]._label;
|
|
var args = [];
|
|
|
|
for (var i = 1, l = params.length; i < l; i++) {
|
|
var value = read(params[i]);
|
|
|
|
args.push(value);
|
|
}
|
|
|
|
var helper = lookupHelper(helperName, this, env);
|
|
|
|
if (!helper) {
|
|
throw new EmberError('HTMLBars error: Could not find component or helper named ' + helperName + '.');
|
|
}
|
|
|
|
result = helper.helperFunction.call(this, args, hash, options, env);
|
|
|
|
delete env.data.isUnbound;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
__exports__.unboundHelper = unboundHelper;
|
|
});
|
|
enifed("ember-htmlbars/helpers/view",
|
|
["ember-metal/core","ember-runtime/system/object","ember-metal/property_get","ember-metal/streams/simple","ember-metal/keys","ember-metal/mixin","ember-metal/streams/utils","ember-views/streams/utils","ember-views/views/view","ember-metal/enumerable_utils","ember-views/streams/class_name_binding","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.warn, Ember.assert
|
|
var EmberObject = __dependency2__["default"];
|
|
var get = __dependency3__.get;
|
|
var SimpleStream = __dependency4__["default"];
|
|
var keys = __dependency5__["default"];
|
|
var IS_BINDING = __dependency6__.IS_BINDING;
|
|
var read = __dependency7__.read;
|
|
var isStream = __dependency7__.isStream;
|
|
var readViewFactory = __dependency8__.readViewFactory;
|
|
var View = __dependency9__["default"];
|
|
|
|
var map = __dependency10__.map;
|
|
var streamifyClassNameBinding = __dependency11__.streamifyClassNameBinding;
|
|
|
|
function makeBindings(hash, options, view) {
|
|
for (var prop in hash) {
|
|
var value = hash[prop];
|
|
|
|
// Classes are processed separately
|
|
if (prop === 'class' && isStream(value)) {
|
|
hash.classBinding = value._label;
|
|
delete hash['class'];
|
|
continue;
|
|
}
|
|
|
|
if (prop === 'classBinding') {
|
|
continue;
|
|
}
|
|
|
|
if (IS_BINDING.test(prop)) {
|
|
if (isStream(value)) {
|
|
Ember.warn("You're attempting to render a view by passing " +
|
|
prop + " " +
|
|
"to a view helper without a quoted value, " +
|
|
"but this syntax is ambiguous. You should either surround " +
|
|
prop + "'s value in quotes or remove `Binding` " +
|
|
"from " + prop + ".");
|
|
} else if (typeof value === 'string') {
|
|
hash[prop] = view._getBindingForStream(value);
|
|
}
|
|
} else {
|
|
if (isStream(value) && prop !== 'id') {
|
|
hash[prop + 'Binding'] = view._getBindingForStream(value);
|
|
delete hash[prop];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var ViewHelper = EmberObject.create({
|
|
propertiesFromHTMLOptions: function(hash, options, env) {
|
|
var view = env.data.view;
|
|
var classes = read(hash['class']);
|
|
|
|
var extensions = {
|
|
helperName: options.helperName || ''
|
|
};
|
|
|
|
if (hash.id) {
|
|
extensions.elementId = read(hash.id);
|
|
}
|
|
|
|
if (hash.tag) {
|
|
extensions.tagName = hash.tag;
|
|
}
|
|
|
|
if (classes) {
|
|
classes = classes.split(' ');
|
|
extensions.classNames = classes;
|
|
}
|
|
|
|
if (hash.classBinding) {
|
|
extensions.classNameBindings = hash.classBinding.split(' ');
|
|
}
|
|
|
|
if (hash.classNameBindings) {
|
|
if (extensions.classNameBindings === undefined) {
|
|
extensions.classNameBindings = [];
|
|
}
|
|
extensions.classNameBindings = extensions.classNameBindings.concat(hash.classNameBindings.split(' '));
|
|
}
|
|
|
|
if (hash.attributeBindings) {
|
|
Ember.assert("Setting 'attributeBindings' via template helpers is not allowed." +
|
|
" Please subclass Ember.View and set it there instead.");
|
|
extensions.attributeBindings = null;
|
|
}
|
|
|
|
// Set the proper context for all bindings passed to the helper. This applies to regular attribute bindings
|
|
// as well as class name bindings. If the bindings are local, make them relative to the current context
|
|
// instead of the view.
|
|
|
|
var hashKeys = keys(hash);
|
|
|
|
for (var i = 0, l = hashKeys.length; i < l; i++) {
|
|
var prop = hashKeys[i];
|
|
|
|
if (prop !== 'classNameBindings') {
|
|
extensions[prop] = hash[prop];
|
|
}
|
|
}
|
|
|
|
if (extensions.classNameBindings) {
|
|
extensions.classNameBindings = map(extensions.classNameBindings, function(classNameBinding){
|
|
var binding = streamifyClassNameBinding(view, classNameBinding);
|
|
if (isStream(binding)) {
|
|
return binding;
|
|
} else {
|
|
// returning a stream informs the classNameBindings logic
|
|
// in views/view that this value is already processed.
|
|
return new SimpleStream(binding);
|
|
}
|
|
});
|
|
}
|
|
|
|
return extensions;
|
|
},
|
|
|
|
helper: function(newView, hash, options, env) {
|
|
var data = env.data;
|
|
var template = options.template;
|
|
var newViewProto;
|
|
|
|
makeBindings(hash, options, env.data.view);
|
|
|
|
var viewOptions = this.propertiesFromHTMLOptions(hash, options, env);
|
|
var currentView = data.view;
|
|
|
|
if (View.detectInstance(newView)) {
|
|
newViewProto = newView;
|
|
} else {
|
|
newViewProto = newView.proto();
|
|
}
|
|
|
|
if (template) {
|
|
Ember.assert(
|
|
"You cannot provide a template block if you also specified a templateName",
|
|
!get(viewOptions, 'templateName') && !get(newViewProto, 'templateName')
|
|
);
|
|
viewOptions.template = template;
|
|
}
|
|
|
|
// We only want to override the `_context` computed property if there is
|
|
// no specified controller. See View#_context for more information.
|
|
if (!newViewProto.controller && !newViewProto.controllerBinding && !viewOptions.controller && !viewOptions.controllerBinding) {
|
|
viewOptions._context = get(currentView, 'context'); // TODO: is this right?!
|
|
}
|
|
|
|
viewOptions._morph = options.morph;
|
|
|
|
currentView.appendChild(newView, viewOptions);
|
|
},
|
|
|
|
instanceHelper: function(newView, hash, options, env) {
|
|
var data = env.data;
|
|
var template = options.template;
|
|
|
|
makeBindings(hash, options, env.data.view);
|
|
|
|
Ember.assert(
|
|
'Only a instance of a view may be passed to the ViewHelper.instanceHelper',
|
|
View.detectInstance(newView)
|
|
);
|
|
|
|
var viewOptions = this.propertiesFromHTMLOptions(hash, options, env);
|
|
var currentView = data.view;
|
|
|
|
if (template) {
|
|
Ember.assert(
|
|
"You cannot provide a template block if you also specified a templateName",
|
|
!get(viewOptions, 'templateName') && !get(newView, 'templateName')
|
|
);
|
|
viewOptions.template = template;
|
|
}
|
|
|
|
// We only want to override the `_context` computed property if there is
|
|
// no specified controller. See View#_context for more information.
|
|
if (!newView.controller && !newView.controllerBinding &&
|
|
!viewOptions.controller && !viewOptions.controllerBinding) {
|
|
viewOptions._context = get(currentView, 'context'); // TODO: is this right?!
|
|
}
|
|
|
|
viewOptions._morph = options.morph;
|
|
|
|
currentView.appendChild(newView, viewOptions);
|
|
}
|
|
});
|
|
__exports__.ViewHelper = ViewHelper;
|
|
/**
|
|
`{{view}}` inserts a new instance of an `Ember.View` into a template passing its
|
|
options to the `Ember.View`'s `create` method and using the supplied block as
|
|
the view's own template.
|
|
|
|
An empty `<body>` and the following template:
|
|
|
|
```handlebars
|
|
A span:
|
|
{{#view tagName="span"}}
|
|
hello.
|
|
{{/view}}
|
|
```
|
|
|
|
Will result in HTML structure:
|
|
|
|
```html
|
|
<body>
|
|
<!-- Note: the handlebars template script
|
|
also results in a rendered Ember.View
|
|
which is the outer <div> here -->
|
|
|
|
<div class="ember-view">
|
|
A span:
|
|
<span id="ember1" class="ember-view">
|
|
Hello.
|
|
</span>
|
|
</div>
|
|
</body>
|
|
```
|
|
|
|
### `parentView` setting
|
|
|
|
The `parentView` property of the new `Ember.View` instance created through
|
|
`{{view}}` will be set to the `Ember.View` instance of the template where
|
|
`{{view}}` was called.
|
|
|
|
```javascript
|
|
aView = Ember.View.create({
|
|
template: Ember.Handlebars.compile("{{#view}} my parent: {{parentView.elementId}} {{/view}}")
|
|
});
|
|
|
|
aView.appendTo('body');
|
|
```
|
|
|
|
Will result in HTML structure:
|
|
|
|
```html
|
|
<div id="ember1" class="ember-view">
|
|
<div id="ember2" class="ember-view">
|
|
my parent: ember1
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
### Setting CSS id and class attributes
|
|
|
|
The HTML `id` attribute can be set on the `{{view}}`'s resulting element with
|
|
the `id` option. This option will _not_ be passed to `Ember.View.create`.
|
|
|
|
```handlebars
|
|
{{#view tagName="span" id="a-custom-id"}}
|
|
hello.
|
|
{{/view}}
|
|
```
|
|
|
|
Results in the following HTML structure:
|
|
|
|
```html
|
|
<div class="ember-view">
|
|
<span id="a-custom-id" class="ember-view">
|
|
hello.
|
|
</span>
|
|
</div>
|
|
```
|
|
|
|
The HTML `class` attribute can be set on the `{{view}}`'s resulting element
|
|
with the `class` or `classNameBindings` options. The `class` option will
|
|
directly set the CSS `class` attribute and will not be passed to
|
|
`Ember.View.create`. `classNameBindings` will be passed to `create` and use
|
|
`Ember.View`'s class name binding functionality:
|
|
|
|
```handlebars
|
|
{{#view tagName="span" class="a-custom-class"}}
|
|
hello.
|
|
{{/view}}
|
|
```
|
|
|
|
Results in the following HTML structure:
|
|
|
|
```html
|
|
<div class="ember-view">
|
|
<span id="ember2" class="ember-view a-custom-class">
|
|
hello.
|
|
</span>
|
|
</div>
|
|
```
|
|
|
|
### Supplying a different view class
|
|
|
|
`{{view}}` can take an optional first argument before its supplied options to
|
|
specify a path to a custom view class.
|
|
|
|
```handlebars
|
|
{{#view "custom"}}{{! will look up App.CustomView }}
|
|
hello.
|
|
{{/view}}
|
|
```
|
|
|
|
The first argument can also be a relative path accessible from the current
|
|
context.
|
|
|
|
```javascript
|
|
MyApp = Ember.Application.create({});
|
|
MyApp.OuterView = Ember.View.extend({
|
|
innerViewClass: Ember.View.extend({
|
|
classNames: ['a-custom-view-class-as-property']
|
|
}),
|
|
template: Ember.Handlebars.compile('{{#view view.innerViewClass}} hi {{/view}}')
|
|
});
|
|
|
|
MyApp.OuterView.create().appendTo('body');
|
|
```
|
|
|
|
Will result in the following HTML:
|
|
|
|
```html
|
|
<div id="ember1" class="ember-view">
|
|
<div id="ember2" class="ember-view a-custom-view-class-as-property">
|
|
hi
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
### Blockless use
|
|
|
|
If you supply a custom `Ember.View` subclass that specifies its own template
|
|
or provide a `templateName` option to `{{view}}` it can be used without
|
|
supplying a block. Attempts to use both a `templateName` option and supply a
|
|
block will throw an error.
|
|
|
|
```javascript
|
|
var App = Ember.Application.create();
|
|
App.WithTemplateDefinedView = Ember.View.extend({
|
|
templateName: 'defined-template'
|
|
});
|
|
```
|
|
|
|
```handlebars
|
|
{{! application.hbs }}
|
|
{{view 'with-template-defined'}}
|
|
```
|
|
|
|
```handlebars
|
|
{{! defined-template.hbs }}
|
|
Some content for the defined template view.
|
|
```
|
|
|
|
### `viewName` property
|
|
|
|
You can supply a `viewName` option to `{{view}}`. The `Ember.View` instance
|
|
will be referenced as a property of its parent view by this name.
|
|
|
|
```javascript
|
|
aView = Ember.View.create({
|
|
template: Ember.Handlebars.compile('{{#view viewName="aChildByName"}} hi {{/view}}')
|
|
});
|
|
|
|
aView.appendTo('body');
|
|
aView.get('aChildByName') // the instance of Ember.View created by {{view}} helper
|
|
```
|
|
|
|
@method view
|
|
@for Ember.Handlebars.helpers
|
|
@param {String} path
|
|
@param {Hash} options
|
|
@return {String} HTML string
|
|
*/
|
|
function viewHelper(params, hash, options, env) {
|
|
Ember.assert("The view helper only takes a single argument", params.length <= 2);
|
|
|
|
var container = this.container || read(this._keywords.view).container;
|
|
var viewClass;
|
|
|
|
if (params.length === 0) {
|
|
if (container) {
|
|
viewClass = container.lookupFactory('view:toplevel');
|
|
} else {
|
|
viewClass = View;
|
|
}
|
|
} else {
|
|
var pathStream = params[0];
|
|
viewClass = readViewFactory(pathStream, container);
|
|
}
|
|
|
|
options.helperName = options.helperName || 'view';
|
|
|
|
return ViewHelper.helper(viewClass, hash, options, env);
|
|
}
|
|
|
|
__exports__.viewHelper = viewHelper;
|
|
});
|
|
enifed("ember-htmlbars/helpers/with",
|
|
["ember-metal/core","ember-metal/is_none","ember-htmlbars/helpers/binding","ember-views/views/with_view","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.assert
|
|
var isNone = __dependency2__["default"];
|
|
var bind = __dependency3__.bind;
|
|
var WithView = __dependency4__["default"];
|
|
|
|
/**
|
|
Use the `{{with}}` helper when you want to aliases the to a new name. It's helpful
|
|
for semantic clarity and to retain default scope or to reference from another
|
|
`{{with}}` block.
|
|
|
|
```handlebars
|
|
// posts might not be
|
|
{{#with user.posts as blogPosts}}
|
|
<div class="notice">
|
|
There are {{blogPosts.length}} blog posts written by {{user.name}}.
|
|
</div>
|
|
|
|
{{#each post in blogPosts}}
|
|
<li>{{post.title}}</li>
|
|
{{/each}}
|
|
{{/with}}
|
|
```
|
|
|
|
Without the `as` operator, it would be impossible to reference `user.name` in the example above.
|
|
|
|
NOTE: The alias should not reuse a name from the bound property path.
|
|
For example: `{{#with foo.bar as foo}}` is not supported because it attempts to alias using
|
|
the first part of the property path, `foo`. Instead, use `{{#with foo.bar as baz}}`.
|
|
|
|
### `controller` option
|
|
|
|
Adding `controller='something'` instructs the `{{with}}` helper to create and use an instance of
|
|
the specified controller wrapping the aliased keyword.
|
|
|
|
This is very similar to using an `itemController` option with the `{{each}}` helper.
|
|
|
|
```handlebars
|
|
{{#with users.posts as posts controller='userBlogPosts'}}
|
|
{{!- `posts` is wrapped in our controller instance }}
|
|
{{/with}}
|
|
```
|
|
|
|
In the above example, the `posts` keyword is now wrapped in the `userBlogPost` controller,
|
|
which provides an elegant way to decorate the context with custom
|
|
functions/properties.
|
|
|
|
@method with
|
|
@for Ember.Handlebars.helpers
|
|
@param {Function} context
|
|
@param {Hash} options
|
|
@return {String} HTML string
|
|
*/
|
|
function withHelper(params, hash, options, env) {
|
|
Ember.assert(
|
|
"{{#with foo}} must be called with a single argument or the use the " +
|
|
"{{#with foo as bar}} syntax",
|
|
params.length === 1
|
|
);
|
|
|
|
Ember.assert(
|
|
"The {{#with}} helper must be called with a block",
|
|
!!options.template
|
|
);
|
|
|
|
var preserveContext;
|
|
|
|
if (options.template.blockParams) {
|
|
preserveContext = true;
|
|
} else {
|
|
Ember.deprecate(
|
|
"Using the context switching form of `{{with}}` is deprecated. " +
|
|
"Please use the keyword form (`{{with foo as bar}}`) instead.",
|
|
false,
|
|
{ url: 'http://emberjs.com/guides/deprecations/#toc_more-consistent-handlebars-scope' }
|
|
);
|
|
preserveContext = false;
|
|
}
|
|
|
|
bind.call(this, params[0], hash, options, env, preserveContext, exists, undefined, undefined, WithView);
|
|
}
|
|
|
|
__exports__.withHelper = withHelper;function exists(value) {
|
|
return !isNone(value);
|
|
}
|
|
});
|
|
enifed("ember-htmlbars/helpers/yield",
|
|
["ember-metal/core","ember-metal/property_get","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
|
|
var get = __dependency2__.get;
|
|
|
|
/**
|
|
`{{yield}}` denotes an area of a template that will be rendered inside
|
|
of another template. It has two main uses:
|
|
|
|
### Use with `layout`
|
|
When used in a Handlebars template that is assigned to an `Ember.View`
|
|
instance's `layout` property Ember will render the layout template first,
|
|
inserting the view's own rendered output at the `{{yield}}` location.
|
|
|
|
An empty `<body>` and the following application code:
|
|
|
|
```javascript
|
|
AView = Ember.View.extend({
|
|
classNames: ['a-view-with-layout'],
|
|
layout: Ember.Handlebars.compile('<div class="wrapper">{{yield}}</div>'),
|
|
template: Ember.Handlebars.compile('<span>I am wrapped</span>')
|
|
});
|
|
|
|
aView = AView.create();
|
|
aView.appendTo('body');
|
|
```
|
|
|
|
Will result in the following HTML output:
|
|
|
|
```html
|
|
<body>
|
|
<div class='ember-view a-view-with-layout'>
|
|
<div class="wrapper">
|
|
<span>I am wrapped</span>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
```
|
|
|
|
The `yield` helper cannot be used outside of a template assigned to an
|
|
`Ember.View`'s `layout` property and will throw an error if attempted.
|
|
|
|
```javascript
|
|
BView = Ember.View.extend({
|
|
classNames: ['a-view-with-layout'],
|
|
template: Ember.Handlebars.compile('{{yield}}')
|
|
});
|
|
|
|
bView = BView.create();
|
|
bView.appendTo('body');
|
|
|
|
// throws
|
|
// Uncaught Error: assertion failed:
|
|
// You called yield in a template that was not a layout
|
|
```
|
|
|
|
### Use with Ember.Component
|
|
When designing components `{{yield}}` is used to denote where, inside the component's
|
|
template, an optional block passed to the component should render:
|
|
|
|
```handlebars
|
|
<!-- application.hbs -->
|
|
{{#labeled-textfield value=someProperty}}
|
|
First name:
|
|
{{/labeled-textfield}}
|
|
```
|
|
|
|
```handlebars
|
|
<!-- components/labeled-textfield.hbs -->
|
|
<label>
|
|
{{yield}} {{input value=value}}
|
|
</label>
|
|
```
|
|
|
|
Result:
|
|
|
|
```html
|
|
<label>
|
|
First name: <input type="text" />
|
|
</label>
|
|
```
|
|
|
|
@method yield
|
|
@for Ember.Handlebars.helpers
|
|
@param {Hash} options
|
|
@return {String} HTML string
|
|
*/
|
|
function yieldHelper(params, hash, options, env) {
|
|
var view = this;
|
|
|
|
// Yea gods
|
|
while (view && !get(view, 'layout')) {
|
|
if (view._contextView) {
|
|
view = view._contextView;
|
|
} else {
|
|
view = get(view, '_parentView');
|
|
}
|
|
}
|
|
|
|
Ember.assert("You called yield in a template that was not a layout", !!view);
|
|
|
|
return view._yield(null, env, options.morph, params);
|
|
}
|
|
|
|
__exports__.yieldHelper = yieldHelper;
|
|
});
|
|
enifed("ember-htmlbars/hooks/attribute",
|
|
["ember-views/attr_nodes/attr_node","ember-metal/error","ember-metal/streams/utils","ember-views/system/sanitize_attribute_value","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
var AttrNode = __dependency1__["default"];
|
|
var EmberError = __dependency2__["default"];
|
|
var isStream = __dependency3__.isStream;
|
|
var sanitizeAttributeValue = __dependency4__["default"];
|
|
|
|
var boundAttributesEnabled = false;
|
|
|
|
|
|
__exports__["default"] = function attribute(env, morph, element, attrName, attrValue) {
|
|
if (boundAttributesEnabled) {
|
|
var attrNode = new AttrNode(attrName, attrValue);
|
|
attrNode._morph = morph;
|
|
env.data.view.appendChild(attrNode);
|
|
} else {
|
|
if (isStream(attrValue)) {
|
|
throw new EmberError('Bound attributes are not yet supported in Ember.js');
|
|
} else {
|
|
var sanitizedValue = sanitizeAttributeValue(element, attrName, attrValue);
|
|
env.dom.setProperty(element, attrName, sanitizedValue);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
enifed("ember-htmlbars/hooks/block",
|
|
["ember-views/views/simple_bound_view","ember-metal/streams/utils","ember-htmlbars/system/lookup-helper","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
var appendSimpleBoundView = __dependency1__.appendSimpleBoundView;
|
|
var isStream = __dependency2__.isStream;
|
|
var lookupHelper = __dependency3__["default"];
|
|
|
|
__exports__["default"] = function block(env, morph, view, path, params, hash, template, inverse) {
|
|
var helper = lookupHelper(path, view, env);
|
|
|
|
Ember.assert("A helper named `"+path+"` could not be found", helper);
|
|
|
|
var options = {
|
|
morph: morph,
|
|
template: template,
|
|
inverse: inverse,
|
|
isBlock: true
|
|
};
|
|
var result = helper.helperFunction.call(view, params, hash, options, env);
|
|
|
|
if (isStream(result)) {
|
|
appendSimpleBoundView(view, morph, result);
|
|
} else {
|
|
morph.setContent(result);
|
|
}
|
|
}
|
|
});
|
|
enifed("ember-htmlbars/hooks/component",
|
|
["ember-metal/core","ember-htmlbars/system/lookup-helper","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
var lookupHelper = __dependency2__["default"];
|
|
|
|
__exports__["default"] = function component(env, morph, view, tagName, attrs, template) {
|
|
var helper = lookupHelper(tagName, view, env);
|
|
|
|
Ember.assert('You specified `' + tagName + '` in your template, but a component for `' + tagName + '` could not be found.', !!helper);
|
|
|
|
return helper.helperFunction.call(view, [], attrs, {morph: morph, template: template}, env);
|
|
}
|
|
});
|
|
enifed("ember-htmlbars/hooks/concat",
|
|
["ember-metal/streams/utils","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
var streamConcat = __dependency1__.concat;
|
|
|
|
__exports__["default"] = function concat(env, parts) {
|
|
return streamConcat(parts, '');
|
|
}
|
|
});
|
|
enifed("ember-htmlbars/hooks/content",
|
|
["ember-views/views/simple_bound_view","ember-metal/streams/utils","ember-htmlbars/system/lookup-helper","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
var appendSimpleBoundView = __dependency1__.appendSimpleBoundView;
|
|
var isStream = __dependency2__.isStream;
|
|
var lookupHelper = __dependency3__["default"];
|
|
|
|
__exports__["default"] = function content(env, morph, view, path) {
|
|
var helper = lookupHelper(path, view, env);
|
|
var result;
|
|
|
|
if (helper) {
|
|
var options = {
|
|
morph: morph,
|
|
isInline: true
|
|
};
|
|
result = helper.helperFunction.call(view, [], {}, options, env);
|
|
} else {
|
|
result = view.getStream(path);
|
|
}
|
|
|
|
if (isStream(result)) {
|
|
appendSimpleBoundView(view, morph, result);
|
|
} else {
|
|
morph.setContent(result);
|
|
}
|
|
}
|
|
});
|
|
enifed("ember-htmlbars/hooks/element",
|
|
["ember-metal/core","ember-metal/streams/utils","ember-htmlbars/system/lookup-helper","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
var read = __dependency2__.read;
|
|
var lookupHelper = __dependency3__["default"];
|
|
|
|
__exports__["default"] = function element(env, domElement, view, path, params, hash) { //jshint ignore:line
|
|
var helper = lookupHelper(path, view, env);
|
|
var valueOrLazyValue;
|
|
|
|
if (helper) {
|
|
var options = {
|
|
element: domElement
|
|
};
|
|
valueOrLazyValue = helper.helperFunction.call(view, params, hash, options, env);
|
|
} else {
|
|
valueOrLazyValue = view.getStream(path);
|
|
}
|
|
|
|
var value = read(valueOrLazyValue);
|
|
if (value) {
|
|
Ember.deprecate('Returning a string of attributes from a helper inside an element is deprecated.');
|
|
|
|
var parts = value.toString().split(/\s+/);
|
|
for (var i = 0, l = parts.length; i < l; i++) {
|
|
var attrParts = parts[i].split('=');
|
|
var attrName = attrParts[0];
|
|
var attrValue = attrParts[1];
|
|
|
|
attrValue = attrValue.replace(/^['"]/, '').replace(/['"]$/, '');
|
|
|
|
env.dom.setAttribute(domElement, attrName, attrValue);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
enifed("ember-htmlbars/hooks/get",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
__exports__["default"] = function get(env, view, path) {
|
|
return view.getStream(path);
|
|
}
|
|
});
|
|
enifed("ember-htmlbars/hooks/inline",
|
|
["ember-views/views/simple_bound_view","ember-metal/streams/utils","ember-htmlbars/system/lookup-helper","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
var appendSimpleBoundView = __dependency1__.appendSimpleBoundView;
|
|
var isStream = __dependency2__.isStream;
|
|
var lookupHelper = __dependency3__["default"];
|
|
|
|
__exports__["default"] = function inline(env, morph, view, path, params, hash) {
|
|
var helper = lookupHelper(path, view, env);
|
|
|
|
Ember.assert("A helper named '"+path+"' could not be found", helper);
|
|
|
|
var result = helper.helperFunction.call(view, params, hash, {morph: morph}, env);
|
|
|
|
if (isStream(result)) {
|
|
appendSimpleBoundView(view, morph, result);
|
|
} else {
|
|
morph.setContent(result);
|
|
}
|
|
}
|
|
});
|
|
enifed("ember-htmlbars/hooks/set",
|
|
["ember-metal/core","ember-metal/error","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
var EmberError = __dependency2__["default"];
|
|
|
|
__exports__["default"] = function set(env, view, name, value) {
|
|
|
|
view._keywords[name] = value;
|
|
}
|
|
});
|
|
enifed("ember-htmlbars/hooks/subexpr",
|
|
["ember-htmlbars/system/lookup-helper","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
var lookupHelper = __dependency1__["default"];
|
|
|
|
__exports__["default"] = function subexpr(env, view, path, params, hash) {
|
|
var helper = lookupHelper(path, view, env);
|
|
|
|
Ember.assert("A helper named '"+path+"' could not be found", helper);
|
|
|
|
var options = {
|
|
isInline: true
|
|
};
|
|
return helper.helperFunction.call(view, params, hash, options, env);
|
|
}
|
|
});
|
|
enifed("ember-htmlbars/system/bootstrap",
|
|
["ember-metal/core","ember-views/component_lookup","ember-views/system/jquery","ember-metal/error","ember-runtime/system/lazy_load","ember-template-compiler/system/compile","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) {
|
|
"use strict";
|
|
/*globals Handlebars */
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
var ComponentLookup = __dependency2__["default"];
|
|
var jQuery = __dependency3__["default"];
|
|
var EmberError = __dependency4__["default"];
|
|
var onLoad = __dependency5__.onLoad;
|
|
var htmlbarsCompile = __dependency6__["default"];
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-handlebars
|
|
*/
|
|
|
|
/**
|
|
Find templates stored in the head tag as script tags and make them available
|
|
to `Ember.CoreView` in the global `Ember.TEMPLATES` object. This will be run
|
|
as as jQuery DOM-ready callback.
|
|
|
|
Script tags with `text/x-handlebars` will be compiled
|
|
with Ember's Handlebars and are suitable for use as a view's template.
|
|
Those with type `text/x-raw-handlebars` will be compiled with regular
|
|
Handlebars and are suitable for use in views' computed properties.
|
|
|
|
@private
|
|
@method bootstrap
|
|
@for Ember.Handlebars
|
|
@static
|
|
@param ctx
|
|
*/
|
|
function bootstrap(ctx) {
|
|
var selectors = 'script[type="text/x-handlebars"], script[type="text/x-raw-handlebars"]';
|
|
|
|
jQuery(selectors, ctx)
|
|
.each(function() {
|
|
// Get a reference to the script tag
|
|
var script = jQuery(this);
|
|
|
|
var compile = (script.attr('type') === 'text/x-raw-handlebars') ?
|
|
jQuery.proxy(Handlebars.compile, Handlebars) :
|
|
htmlbarsCompile;
|
|
// Get the name of the script, used by Ember.View's templateName property.
|
|
// First look for data-template-name attribute, then fall back to its
|
|
// id if no name is found.
|
|
var templateName = script.attr('data-template-name') || script.attr('id') || 'application';
|
|
var template = compile(script.html());
|
|
|
|
// Check if template of same name already exists
|
|
if (Ember.TEMPLATES[templateName] !== undefined) {
|
|
throw new EmberError('Template named "' + templateName + '" already exists.');
|
|
}
|
|
|
|
// For templates which have a name, we save them and then remove them from the DOM
|
|
Ember.TEMPLATES[templateName] = template;
|
|
|
|
// Remove script tag from DOM
|
|
script.remove();
|
|
});
|
|
}
|
|
|
|
function _bootstrap() {
|
|
bootstrap( jQuery(document) );
|
|
}
|
|
|
|
function registerComponentLookup(container) {
|
|
container.register('component-lookup:main', ComponentLookup);
|
|
}
|
|
|
|
/*
|
|
We tie this to application.load to ensure that we've at least
|
|
attempted to bootstrap at the point that the application is loaded.
|
|
|
|
We also tie this to document ready since we're guaranteed that all
|
|
the inline templates are present at this point.
|
|
|
|
There's no harm to running this twice, since we remove the templates
|
|
from the DOM after processing.
|
|
*/
|
|
|
|
onLoad('Ember.Application', function(Application) {
|
|
|
|
|
|
Application.initializer({
|
|
name: 'domTemplates',
|
|
initialize: _bootstrap
|
|
});
|
|
|
|
Application.initializer({
|
|
name: 'registerComponentLookup',
|
|
after: 'domTemplates',
|
|
initialize: registerComponentLookup
|
|
});
|
|
|
|
|
|
});
|
|
|
|
__exports__["default"] = bootstrap;
|
|
});
|
|
enifed("ember-htmlbars/system/helper",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
/**
|
|
@class Helper
|
|
@namespace Ember.HTMLBars
|
|
*/
|
|
function Helper(helper) {
|
|
this.helperFunction = helper;
|
|
|
|
this.isHelper = true;
|
|
this.isHTMLBars = true;
|
|
}
|
|
|
|
__exports__["default"] = Helper;
|
|
});
|
|
enifed("ember-htmlbars/system/lookup-helper",
|
|
["ember-metal/core","ember-metal/cache","ember-htmlbars/system/make-view-helper","ember-htmlbars/compat/helper","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
var Cache = __dependency2__["default"];
|
|
var makeViewHelper = __dependency3__["default"];
|
|
var HandlebarsCompatibleHelper = __dependency4__["default"];
|
|
|
|
var ISNT_HELPER_CACHE = new Cache(1000, function(key) {
|
|
return key.indexOf('-') === -1;
|
|
});
|
|
__exports__.ISNT_HELPER_CACHE = ISNT_HELPER_CACHE;
|
|
/**
|
|
Used to lookup/resolve handlebars helpers. The lookup order is:
|
|
|
|
* Look for a registered helper
|
|
* If a dash exists in the name:
|
|
* Look for a helper registed in the container
|
|
* Use Ember.ComponentLookup to find an Ember.Component that resolves
|
|
to the given name
|
|
|
|
@private
|
|
@method resolveHelper
|
|
@param {Container} container
|
|
@param {String} name the name of the helper to lookup
|
|
@return {Handlebars Helper}
|
|
*/
|
|
__exports__["default"] = function lookupHelper(name, view, env) {
|
|
var helper = env.helpers[name];
|
|
if (helper) {
|
|
return helper;
|
|
}
|
|
|
|
var container = view.container;
|
|
|
|
if (!container || ISNT_HELPER_CACHE.get(name)) {
|
|
return;
|
|
}
|
|
|
|
var helperName = 'helper:' + name;
|
|
helper = container.lookup(helperName);
|
|
if (!helper) {
|
|
var componentLookup = container.lookup('component-lookup:main');
|
|
Ember.assert("Could not find 'component-lookup:main' on the provided container," +
|
|
" which is necessary for performing component lookups", componentLookup);
|
|
|
|
var Component = componentLookup.lookupFactory(name, container);
|
|
if (Component) {
|
|
helper = makeViewHelper(Component);
|
|
container.register(helperName, helper);
|
|
}
|
|
}
|
|
|
|
if (helper && !helper.isHTMLBars) {
|
|
helper = new HandlebarsCompatibleHelper(helper);
|
|
container.unregister(helperName);
|
|
container.register(helperName, helper);
|
|
}
|
|
|
|
return helper;
|
|
}
|
|
});
|
|
enifed("ember-htmlbars/system/make-view-helper",
|
|
["ember-metal/core","ember-htmlbars/system/helper","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.assert
|
|
var Helper = __dependency2__["default"];
|
|
|
|
/**
|
|
Returns a helper function that renders the provided ViewClass.
|
|
|
|
Used internally by Ember.Handlebars.helper and other methods
|
|
involving helper/component registration.
|
|
|
|
@private
|
|
@method makeViewHelper
|
|
@param {Function} ViewClass view class constructor
|
|
@since 1.2.0
|
|
*/
|
|
__exports__["default"] = function makeViewHelper(ViewClass) {
|
|
function helperFunc(params, hash, options, env) {
|
|
Ember.assert("You can only pass attributes (such as name=value) not bare " +
|
|
"values to a helper for a View found in '" + ViewClass.toString() + "'", params.length === 0);
|
|
|
|
return env.helpers.view.helperFunction.call(this, [ViewClass], hash, options, env);
|
|
}
|
|
|
|
return new Helper(helperFunc);
|
|
}
|
|
});
|
|
enifed("ember-htmlbars/system/make_bound_helper",
|
|
["ember-metal/core","ember-htmlbars/system/helper","ember-metal/streams/stream","ember-metal/streams/utils","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.FEATURES, Ember.assert, Ember.Handlebars, Ember.lookup
|
|
var Helper = __dependency2__["default"];
|
|
|
|
var Stream = __dependency3__["default"];
|
|
var readArray = __dependency4__.readArray;
|
|
var readHash = __dependency4__.readHash;
|
|
var subscribe = __dependency4__.subscribe;
|
|
var scanHash = __dependency4__.scanHash;
|
|
var scanArray = __dependency4__.scanArray;
|
|
|
|
/**
|
|
Create a bound helper. Accepts a function that receives the ordered and hash parameters
|
|
from the template. If a bound property was provided in the template it will be resolved to its
|
|
value and any changes to the bound property cause the helper function to be re-ran with the updated
|
|
values.
|
|
|
|
* `params` - An array of resolved ordered parameters.
|
|
* `hash` - An object containing the hash parameters.
|
|
|
|
For example:
|
|
|
|
* With an unqouted ordered parameter:
|
|
|
|
```javascript
|
|
{{x-capitalize foo}}
|
|
```
|
|
|
|
Assuming `foo` was set to `"bar"`, the bound helper would receive `["bar"]` as its first argument, and
|
|
an empty hash as its second.
|
|
|
|
* With a quoted ordered parameter:
|
|
|
|
```javascript
|
|
{{x-capitalize "foo"}}
|
|
```
|
|
|
|
The bound helper would receive `["foo"]` as its first argument, and an empty hash as its second.
|
|
|
|
* With an unquoted hash parameter:
|
|
|
|
```javascript
|
|
{{x-repeat "foo" count=repeatCount}}
|
|
```
|
|
|
|
Assuming that `repeatCount` resolved to 2, the bound helper would receive `["foo"]` as its first argument,
|
|
and { count: 2 } as its second.
|
|
|
|
@private
|
|
@method makeBoundHelper
|
|
@for Ember.HTMLBars
|
|
@param {Function} function
|
|
@since 1.10.0
|
|
*/
|
|
__exports__["default"] = function makeBoundHelper(fn) {
|
|
function helperFunc(params, hash, options, env) {
|
|
var view = this;
|
|
var numParams = params.length;
|
|
var param, prop;
|
|
|
|
Ember.assert("makeBoundHelper generated helpers do not support use with blocks", !options.template);
|
|
|
|
function valueFn() {
|
|
return fn.call(view, readArray(params), readHash(hash), options, env);
|
|
}
|
|
|
|
// If none of the hash parameters are bound, act as an unbound helper.
|
|
// This prevents views from being unnecessarily created
|
|
var hasStream = scanArray(params) || scanHash(hash);
|
|
|
|
if (env.data.isUnbound || !hasStream) {
|
|
return valueFn();
|
|
} else {
|
|
var lazyValue = new Stream(valueFn);
|
|
|
|
for (var i = 0; i < numParams; i++) {
|
|
param = params[i];
|
|
subscribe(param, lazyValue.notify, lazyValue);
|
|
}
|
|
|
|
for (prop in hash) {
|
|
param = hash[prop];
|
|
subscribe(param, lazyValue.notify, lazyValue);
|
|
}
|
|
|
|
return lazyValue;
|
|
}
|
|
}
|
|
|
|
return new Helper(helperFunc);
|
|
}
|
|
});
|
|
enifed("ember-htmlbars/templates/component",
|
|
["ember-template-compiler/system/template","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var template = __dependency1__["default"];
|
|
var t = (function() {
|
|
return {
|
|
isHTMLBars: true,
|
|
blockParams: 0,
|
|
cachedFragment: null,
|
|
hasRendered: false,
|
|
build: function build(dom) {
|
|
var el0 = dom.createDocumentFragment();
|
|
var el1 = dom.createTextNode("");
|
|
dom.appendChild(el0, el1);
|
|
var el1 = dom.createTextNode("");
|
|
dom.appendChild(el0, el1);
|
|
return el0;
|
|
},
|
|
render: function render(context, env, contextualElement) {
|
|
var dom = env.dom;
|
|
var hooks = env.hooks, content = hooks.content;
|
|
dom.detectNamespace(contextualElement);
|
|
var fragment;
|
|
if (env.useFragmentCache && dom.canClone) {
|
|
if (this.cachedFragment === null) {
|
|
fragment = this.build(dom);
|
|
if (this.hasRendered) {
|
|
this.cachedFragment = fragment;
|
|
} else {
|
|
this.hasRendered = true;
|
|
}
|
|
}
|
|
if (this.cachedFragment) {
|
|
fragment = dom.cloneNode(this.cachedFragment, true);
|
|
}
|
|
} else {
|
|
fragment = this.build(dom);
|
|
}
|
|
if (this.cachedFragment) { dom.repairClonedNode(fragment,[0,1]); }
|
|
var morph0 = dom.createMorphAt(fragment,0,1,contextualElement);
|
|
content(env, morph0, context, "yield");
|
|
return fragment;
|
|
}
|
|
};
|
|
}());
|
|
__exports__["default"] = template(t);
|
|
});
|
|
enifed("ember-htmlbars/templates/select-option",
|
|
["ember-template-compiler/system/template","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var template = __dependency1__["default"];
|
|
var t = (function() {
|
|
return {
|
|
isHTMLBars: true,
|
|
blockParams: 0,
|
|
cachedFragment: null,
|
|
hasRendered: false,
|
|
build: function build(dom) {
|
|
var el0 = dom.createDocumentFragment();
|
|
var el1 = dom.createTextNode("");
|
|
dom.appendChild(el0, el1);
|
|
var el1 = dom.createTextNode("");
|
|
dom.appendChild(el0, el1);
|
|
return el0;
|
|
},
|
|
render: function render(context, env, contextualElement) {
|
|
var dom = env.dom;
|
|
var hooks = env.hooks, content = hooks.content;
|
|
dom.detectNamespace(contextualElement);
|
|
var fragment;
|
|
if (env.useFragmentCache && dom.canClone) {
|
|
if (this.cachedFragment === null) {
|
|
fragment = this.build(dom);
|
|
if (this.hasRendered) {
|
|
this.cachedFragment = fragment;
|
|
} else {
|
|
this.hasRendered = true;
|
|
}
|
|
}
|
|
if (this.cachedFragment) {
|
|
fragment = dom.cloneNode(this.cachedFragment, true);
|
|
}
|
|
} else {
|
|
fragment = this.build(dom);
|
|
}
|
|
if (this.cachedFragment) { dom.repairClonedNode(fragment,[0,1]); }
|
|
var morph0 = dom.createMorphAt(fragment,0,1,contextualElement);
|
|
content(env, morph0, context, "view.label");
|
|
return fragment;
|
|
}
|
|
};
|
|
}());
|
|
__exports__["default"] = template(t);
|
|
});
|
|
enifed("ember-htmlbars/templates/select",
|
|
["ember-template-compiler/system/template","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var template = __dependency1__["default"];
|
|
var t = (function() {
|
|
var child0 = (function() {
|
|
return {
|
|
isHTMLBars: true,
|
|
blockParams: 0,
|
|
cachedFragment: null,
|
|
hasRendered: false,
|
|
build: function build(dom) {
|
|
var el0 = dom.createElement("option");
|
|
dom.setAttribute(el0,"value","");
|
|
return el0;
|
|
},
|
|
render: function render(context, env, contextualElement) {
|
|
var dom = env.dom;
|
|
var hooks = env.hooks, content = hooks.content;
|
|
dom.detectNamespace(contextualElement);
|
|
var fragment;
|
|
if (env.useFragmentCache && dom.canClone) {
|
|
if (this.cachedFragment === null) {
|
|
fragment = this.build(dom);
|
|
if (this.hasRendered) {
|
|
this.cachedFragment = fragment;
|
|
} else {
|
|
this.hasRendered = true;
|
|
}
|
|
}
|
|
if (this.cachedFragment) {
|
|
fragment = dom.cloneNode(this.cachedFragment, true);
|
|
}
|
|
} else {
|
|
fragment = this.build(dom);
|
|
}
|
|
var morph0 = dom.createMorphAt(fragment,-1,-1);
|
|
content(env, morph0, context, "view.prompt");
|
|
return fragment;
|
|
}
|
|
};
|
|
}());
|
|
var child1 = (function() {
|
|
var child0 = (function() {
|
|
return {
|
|
isHTMLBars: true,
|
|
blockParams: 0,
|
|
cachedFragment: null,
|
|
hasRendered: false,
|
|
build: function build(dom) {
|
|
var el0 = dom.createDocumentFragment();
|
|
var el1 = dom.createTextNode("");
|
|
dom.appendChild(el0, el1);
|
|
var el1 = dom.createTextNode("");
|
|
dom.appendChild(el0, el1);
|
|
return el0;
|
|
},
|
|
render: function render(context, env, contextualElement) {
|
|
var dom = env.dom;
|
|
var hooks = env.hooks, get = hooks.get, inline = hooks.inline;
|
|
dom.detectNamespace(contextualElement);
|
|
var fragment;
|
|
if (env.useFragmentCache && dom.canClone) {
|
|
if (this.cachedFragment === null) {
|
|
fragment = this.build(dom);
|
|
if (this.hasRendered) {
|
|
this.cachedFragment = fragment;
|
|
} else {
|
|
this.hasRendered = true;
|
|
}
|
|
}
|
|
if (this.cachedFragment) {
|
|
fragment = dom.cloneNode(this.cachedFragment, true);
|
|
}
|
|
} else {
|
|
fragment = this.build(dom);
|
|
}
|
|
if (this.cachedFragment) { dom.repairClonedNode(fragment,[0,1]); }
|
|
var morph0 = dom.createMorphAt(fragment,0,1,contextualElement);
|
|
inline(env, morph0, context, "view", [get(env, context, "view.groupView")], {"content": get(env, context, "group.content"), "label": get(env, context, "group.label")});
|
|
return fragment;
|
|
}
|
|
};
|
|
}());
|
|
return {
|
|
isHTMLBars: true,
|
|
blockParams: 0,
|
|
cachedFragment: null,
|
|
hasRendered: false,
|
|
build: function build(dom) {
|
|
var el0 = dom.createDocumentFragment();
|
|
var el1 = dom.createTextNode("");
|
|
dom.appendChild(el0, el1);
|
|
var el1 = dom.createTextNode("");
|
|
dom.appendChild(el0, el1);
|
|
return el0;
|
|
},
|
|
render: function render(context, env, contextualElement) {
|
|
var dom = env.dom;
|
|
var hooks = env.hooks, get = hooks.get, block = hooks.block;
|
|
dom.detectNamespace(contextualElement);
|
|
var fragment;
|
|
if (env.useFragmentCache && dom.canClone) {
|
|
if (this.cachedFragment === null) {
|
|
fragment = this.build(dom);
|
|
if (this.hasRendered) {
|
|
this.cachedFragment = fragment;
|
|
} else {
|
|
this.hasRendered = true;
|
|
}
|
|
}
|
|
if (this.cachedFragment) {
|
|
fragment = dom.cloneNode(this.cachedFragment, true);
|
|
}
|
|
} else {
|
|
fragment = this.build(dom);
|
|
}
|
|
if (this.cachedFragment) { dom.repairClonedNode(fragment,[0,1]); }
|
|
var morph0 = dom.createMorphAt(fragment,0,1,contextualElement);
|
|
block(env, morph0, context, "each", [get(env, context, "view.groupedContent")], {"keyword": "group"}, child0, null);
|
|
return fragment;
|
|
}
|
|
};
|
|
}());
|
|
var child2 = (function() {
|
|
var child0 = (function() {
|
|
return {
|
|
isHTMLBars: true,
|
|
blockParams: 0,
|
|
cachedFragment: null,
|
|
hasRendered: false,
|
|
build: function build(dom) {
|
|
var el0 = dom.createDocumentFragment();
|
|
var el1 = dom.createTextNode("");
|
|
dom.appendChild(el0, el1);
|
|
var el1 = dom.createTextNode("");
|
|
dom.appendChild(el0, el1);
|
|
return el0;
|
|
},
|
|
render: function render(context, env, contextualElement) {
|
|
var dom = env.dom;
|
|
var hooks = env.hooks, get = hooks.get, inline = hooks.inline;
|
|
dom.detectNamespace(contextualElement);
|
|
var fragment;
|
|
if (env.useFragmentCache && dom.canClone) {
|
|
if (this.cachedFragment === null) {
|
|
fragment = this.build(dom);
|
|
if (this.hasRendered) {
|
|
this.cachedFragment = fragment;
|
|
} else {
|
|
this.hasRendered = true;
|
|
}
|
|
}
|
|
if (this.cachedFragment) {
|
|
fragment = dom.cloneNode(this.cachedFragment, true);
|
|
}
|
|
} else {
|
|
fragment = this.build(dom);
|
|
}
|
|
if (this.cachedFragment) { dom.repairClonedNode(fragment,[0,1]); }
|
|
var morph0 = dom.createMorphAt(fragment,0,1,contextualElement);
|
|
inline(env, morph0, context, "view", [get(env, context, "view.optionView")], {"content": get(env, context, "item")});
|
|
return fragment;
|
|
}
|
|
};
|
|
}());
|
|
return {
|
|
isHTMLBars: true,
|
|
blockParams: 0,
|
|
cachedFragment: null,
|
|
hasRendered: false,
|
|
build: function build(dom) {
|
|
var el0 = dom.createDocumentFragment();
|
|
var el1 = dom.createTextNode("");
|
|
dom.appendChild(el0, el1);
|
|
var el1 = dom.createTextNode("");
|
|
dom.appendChild(el0, el1);
|
|
return el0;
|
|
},
|
|
render: function render(context, env, contextualElement) {
|
|
var dom = env.dom;
|
|
var hooks = env.hooks, get = hooks.get, block = hooks.block;
|
|
dom.detectNamespace(contextualElement);
|
|
var fragment;
|
|
if (env.useFragmentCache && dom.canClone) {
|
|
if (this.cachedFragment === null) {
|
|
fragment = this.build(dom);
|
|
if (this.hasRendered) {
|
|
this.cachedFragment = fragment;
|
|
} else {
|
|
this.hasRendered = true;
|
|
}
|
|
}
|
|
if (this.cachedFragment) {
|
|
fragment = dom.cloneNode(this.cachedFragment, true);
|
|
}
|
|
} else {
|
|
fragment = this.build(dom);
|
|
}
|
|
if (this.cachedFragment) { dom.repairClonedNode(fragment,[0,1]); }
|
|
var morph0 = dom.createMorphAt(fragment,0,1,contextualElement);
|
|
block(env, morph0, context, "each", [get(env, context, "view.content")], {"keyword": "item"}, child0, null);
|
|
return fragment;
|
|
}
|
|
};
|
|
}());
|
|
return {
|
|
isHTMLBars: true,
|
|
blockParams: 0,
|
|
cachedFragment: null,
|
|
hasRendered: false,
|
|
build: function build(dom) {
|
|
var el0 = dom.createDocumentFragment();
|
|
var el1 = dom.createTextNode("");
|
|
dom.appendChild(el0, el1);
|
|
var el1 = dom.createTextNode("");
|
|
dom.appendChild(el0, el1);
|
|
var el1 = dom.createTextNode("\n");
|
|
dom.appendChild(el0, el1);
|
|
return el0;
|
|
},
|
|
render: function render(context, env, contextualElement) {
|
|
var dom = env.dom;
|
|
var hooks = env.hooks, get = hooks.get, block = hooks.block;
|
|
dom.detectNamespace(contextualElement);
|
|
var fragment;
|
|
if (env.useFragmentCache && dom.canClone) {
|
|
if (this.cachedFragment === null) {
|
|
fragment = this.build(dom);
|
|
if (this.hasRendered) {
|
|
this.cachedFragment = fragment;
|
|
} else {
|
|
this.hasRendered = true;
|
|
}
|
|
}
|
|
if (this.cachedFragment) {
|
|
fragment = dom.cloneNode(this.cachedFragment, true);
|
|
}
|
|
} else {
|
|
fragment = this.build(dom);
|
|
}
|
|
if (this.cachedFragment) { dom.repairClonedNode(fragment,[0,1]); }
|
|
var morph0 = dom.createMorphAt(fragment,0,1,contextualElement);
|
|
var morph1 = dom.createMorphAt(fragment,1,2,contextualElement);
|
|
block(env, morph0, context, "if", [get(env, context, "view.prompt")], {}, child0, null);
|
|
block(env, morph1, context, "if", [get(env, context, "view.optionGroupPath")], {}, child1, child2);
|
|
return fragment;
|
|
}
|
|
};
|
|
}());
|
|
__exports__["default"] = template(t);
|
|
});
|
|
enifed("ember-htmlbars/utils/string",
|
|
["htmlbars-util","ember-runtime/system/string","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
// required so we can extend this object.
|
|
var SafeString = __dependency1__.SafeString;
|
|
var escapeExpression = __dependency1__.escapeExpression;
|
|
var EmberStringUtils = __dependency2__["default"];
|
|
|
|
/**
|
|
Mark a string as safe for unescaped output with Handlebars. If you
|
|
return HTML from a Handlebars helper, use this function to
|
|
ensure Handlebars does not escape the HTML.
|
|
|
|
```javascript
|
|
Ember.String.htmlSafe('<div>someString</div>')
|
|
```
|
|
|
|
@method htmlSafe
|
|
@for Ember.String
|
|
@static
|
|
@return {Handlebars.SafeString} a string that will not be html escaped by Handlebars
|
|
*/
|
|
function htmlSafe(str) {
|
|
if (str === null || str === undefined) {
|
|
return "";
|
|
}
|
|
|
|
if (typeof str !== 'string') {
|
|
str = ''+str;
|
|
}
|
|
return new SafeString(str);
|
|
}
|
|
|
|
EmberStringUtils.htmlSafe = htmlSafe;
|
|
if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) {
|
|
|
|
/**
|
|
Mark a string as being safe for unescaped output with Handlebars.
|
|
|
|
```javascript
|
|
'<div>someString</div>'.htmlSafe()
|
|
```
|
|
|
|
See [Ember.String.htmlSafe](/api/classes/Ember.String.html#method_htmlSafe).
|
|
|
|
@method htmlSafe
|
|
@for String
|
|
@return {Handlebars.SafeString} a string that will not be html escaped by Handlebars
|
|
*/
|
|
String.prototype.htmlSafe = function() {
|
|
return htmlSafe(this);
|
|
};
|
|
}
|
|
|
|
__exports__.SafeString = SafeString;
|
|
__exports__.htmlSafe = htmlSafe;
|
|
__exports__.escapeExpression = escapeExpression;
|
|
});
|
|
enifed("ember-metal-views",
|
|
["ember-metal-views/renderer","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var Renderer = __dependency1__["default"];
|
|
__exports__.Renderer = Renderer;
|
|
});
|
|
enifed("ember-metal-views/renderer",
|
|
["morph","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var DOMHelper = __dependency1__.DOMHelper;
|
|
|
|
function Renderer() {
|
|
this._uuid = 0;
|
|
this._views = new Array(2000);
|
|
this._queue = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
|
|
this._parents = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
|
|
this._elements = new Array(17);
|
|
this._inserts = {};
|
|
this._dom = new DOMHelper();
|
|
}
|
|
|
|
function Renderer_renderTree(_view, _parentView, _insertAt) {
|
|
var views = this._views;
|
|
views[0] = _view;
|
|
var insertAt = _insertAt === undefined ? -1 : _insertAt;
|
|
var index = 0;
|
|
var total = 1;
|
|
var levelBase = _parentView ? _parentView._level+1 : 0;
|
|
|
|
var root = _parentView == null ? _view : _parentView._root;
|
|
|
|
// if root view has a _morph assigned
|
|
var willInsert = !!root._morph;
|
|
|
|
var queue = this._queue;
|
|
queue[0] = 0;
|
|
var length = 1;
|
|
|
|
var parentIndex = -1;
|
|
var parents = this._parents;
|
|
var parent = _parentView || null;
|
|
var elements = this._elements;
|
|
var element = null;
|
|
var contextualElement = null;
|
|
var level = 0;
|
|
|
|
var view = _view;
|
|
var children, i, child;
|
|
while (length) {
|
|
elements[level] = element;
|
|
if (!view._morph) {
|
|
// ensure props we add are in same order
|
|
view._morph = null;
|
|
}
|
|
view._root = root;
|
|
this.uuid(view);
|
|
view._level = levelBase + level;
|
|
if (view._elementCreated) {
|
|
this.remove(view, false, true);
|
|
}
|
|
|
|
this.willCreateElement(view);
|
|
|
|
contextualElement = view._morph && view._morph.contextualElement;
|
|
if (!contextualElement && parent && parent._childViewsMorph) {
|
|
contextualElement = parent._childViewsMorph.contextualElement;
|
|
}
|
|
if (!contextualElement && view._didCreateElementWithoutMorph) {
|
|
// This code path is only used by createElement and rerender when createElement
|
|
// was previously called on a view.
|
|
contextualElement = document.body;
|
|
}
|
|
element = this.createElement(view, contextualElement);
|
|
|
|
parents[level++] = parentIndex;
|
|
parentIndex = index;
|
|
parent = view;
|
|
|
|
// enqueue for end
|
|
queue[length++] = index;
|
|
// enqueue children
|
|
children = this.childViews(view);
|
|
if (children) {
|
|
for (i=children.length-1;i>=0;i--) {
|
|
child = children[i];
|
|
index = total++;
|
|
views[index] = child;
|
|
queue[length++] = index;
|
|
view = child;
|
|
}
|
|
}
|
|
|
|
index = queue[--length];
|
|
view = views[index];
|
|
|
|
while (parentIndex === index) {
|
|
level--;
|
|
view._elementCreated = true;
|
|
this.didCreateElement(view);
|
|
if (willInsert) {
|
|
this.willInsertElement(view);
|
|
}
|
|
|
|
if (level === 0) {
|
|
length--;
|
|
break;
|
|
}
|
|
|
|
parentIndex = parents[level];
|
|
parent = parentIndex === -1 ? _parentView : views[parentIndex];
|
|
this.insertElement(view, parent, element, -1);
|
|
index = queue[--length];
|
|
view = views[index];
|
|
element = elements[level];
|
|
elements[level] = null;
|
|
}
|
|
}
|
|
|
|
this.insertElement(view, _parentView, element, insertAt);
|
|
|
|
for (i=total-1; i>=0; i--) {
|
|
if (willInsert) {
|
|
views[i]._elementInserted = true;
|
|
this.didInsertElement(views[i]);
|
|
}
|
|
views[i] = null;
|
|
}
|
|
|
|
return element;
|
|
}
|
|
|
|
Renderer.prototype.uuid = function Renderer_uuid(view) {
|
|
if (view._uuid === undefined) {
|
|
view._uuid = ++this._uuid;
|
|
view._renderer = this;
|
|
} // else assert(view._renderer === this)
|
|
return view._uuid;
|
|
};
|
|
|
|
Renderer.prototype.scheduleInsert =
|
|
function Renderer_scheduleInsert(view, morph) {
|
|
if (view._morph || view._elementCreated) {
|
|
throw new Error("You cannot insert a View that has already been rendered");
|
|
}
|
|
Ember.assert("You cannot insert a View without a morph", morph);
|
|
view._morph = morph;
|
|
var viewId = this.uuid(view);
|
|
this._inserts[viewId] = this.scheduleRender(this, function scheduledRenderTree() {
|
|
this._inserts[viewId] = null;
|
|
this.renderTree(view);
|
|
});
|
|
};
|
|
|
|
Renderer.prototype.appendTo =
|
|
function Renderer_appendTo(view, target) {
|
|
var morph = this._dom.appendMorph(target);
|
|
this.scheduleInsert(view, morph);
|
|
};
|
|
|
|
Renderer.prototype.replaceIn =
|
|
function Renderer_replaceIn(view, target) {
|
|
var morph = this._dom.createMorph(target, null, null);
|
|
this.scheduleInsert(view, morph);
|
|
};
|
|
|
|
function Renderer_remove(_view, shouldDestroy, reset) {
|
|
var viewId = this.uuid(_view);
|
|
|
|
if (this._inserts[viewId]) {
|
|
this.cancelRender(this._inserts[viewId]);
|
|
this._inserts[viewId] = undefined;
|
|
}
|
|
|
|
if (!_view._elementCreated) {
|
|
return;
|
|
}
|
|
|
|
var removeQueue = [];
|
|
var destroyQueue = [];
|
|
var morph = _view._morph;
|
|
var idx, len, view, queue, childViews, i, l;
|
|
|
|
removeQueue.push(_view);
|
|
|
|
for (idx=0; idx<removeQueue.length; idx++) {
|
|
view = removeQueue[idx];
|
|
|
|
if (!shouldDestroy && view._childViewsMorph) {
|
|
queue = removeQueue;
|
|
} else {
|
|
queue = destroyQueue;
|
|
}
|
|
|
|
this.beforeRemove(removeQueue[idx]);
|
|
|
|
childViews = view._childViews;
|
|
if (childViews) {
|
|
for (i=0,l=childViews.length; i<l; i++) {
|
|
queue.push(childViews[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (idx=0; idx<destroyQueue.length; idx++) {
|
|
view = destroyQueue[idx];
|
|
|
|
this.beforeRemove(destroyQueue[idx]);
|
|
|
|
childViews = view._childViews;
|
|
if (childViews) {
|
|
for (i=0,l=childViews.length; i<l; i++) {
|
|
destroyQueue.push(childViews[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// destroy DOM from root insertion
|
|
if (morph && !reset) {
|
|
morph.destroy();
|
|
}
|
|
|
|
for (idx=0, len=removeQueue.length; idx < len; idx++) {
|
|
this.afterRemove(removeQueue[idx], false);
|
|
}
|
|
|
|
for (idx=0, len=destroyQueue.length; idx < len; idx++) {
|
|
this.afterRemove(destroyQueue[idx], true);
|
|
}
|
|
|
|
if (reset) {
|
|
_view._morph = morph;
|
|
}
|
|
}
|
|
|
|
function Renderer_insertElement(view, parentView, element, index) {
|
|
if (element === null || element === undefined) {
|
|
return;
|
|
}
|
|
|
|
if (view._morph) {
|
|
view._morph.setContent(element);
|
|
} else if (parentView) {
|
|
if (index === -1) {
|
|
view._morph = parentView._childViewsMorph.append(element);
|
|
} else {
|
|
view._morph = parentView._childViewsMorph.insert(index, element);
|
|
}
|
|
}
|
|
}
|
|
|
|
function Renderer_beforeRemove(view) {
|
|
if (view._elementCreated) {
|
|
this.willDestroyElement(view);
|
|
}
|
|
if (view._elementInserted) {
|
|
this.willRemoveElement(view);
|
|
}
|
|
}
|
|
|
|
function Renderer_afterRemove(view, shouldDestroy) {
|
|
view._elementInserted = false;
|
|
view._morph = null;
|
|
view._childViewsMorph = null;
|
|
if (view._elementCreated) {
|
|
view._elementCreated = false;
|
|
this.didDestroyElement(view);
|
|
}
|
|
if (shouldDestroy) {
|
|
this.destroyView(view);
|
|
}
|
|
}
|
|
|
|
Renderer.prototype.remove = Renderer_remove;
|
|
Renderer.prototype.destroy = function (view) {
|
|
this.remove(view, true);
|
|
};
|
|
|
|
Renderer.prototype.renderTree = Renderer_renderTree;
|
|
Renderer.prototype.insertElement = Renderer_insertElement;
|
|
Renderer.prototype.beforeRemove = Renderer_beforeRemove;
|
|
Renderer.prototype.afterRemove = Renderer_afterRemove;
|
|
|
|
/// HOOKS
|
|
var noop = function () {};
|
|
|
|
Renderer.prototype.willCreateElement = noop; // inBuffer
|
|
Renderer.prototype.createElement = noop; // renderToBuffer or createElement
|
|
Renderer.prototype.didCreateElement = noop; // hasElement
|
|
Renderer.prototype.willInsertElement = noop; // will place into DOM
|
|
Renderer.prototype.didInsertElement = noop; // inDOM // placed into DOM
|
|
Renderer.prototype.willRemoveElement = noop; // removed from DOM willDestroyElement currently paired with didInsertElement
|
|
Renderer.prototype.willDestroyElement = noop; // willClearRender (currently balanced with render) this is now paired with createElement
|
|
Renderer.prototype.didDestroyElement = noop; // element destroyed so view.destroy shouldn't try to remove it removedFromDOM
|
|
Renderer.prototype.destroyView = noop;
|
|
Renderer.prototype.childViews = noop;
|
|
|
|
__exports__["default"] = Renderer;
|
|
});
|
|
enifed("ember-metal",
|
|
["ember-metal/core","ember-metal/merge","ember-metal/instrumentation","ember-metal/utils","ember-metal/error","ember-metal/enumerable_utils","ember-metal/cache","ember-metal/platform","ember-metal/array","ember-metal/logger","ember-metal/property_get","ember-metal/events","ember-metal/observer_set","ember-metal/property_events","ember-metal/properties","ember-metal/property_set","ember-metal/map","ember-metal/get_properties","ember-metal/set_properties","ember-metal/watch_key","ember-metal/chains","ember-metal/watch_path","ember-metal/watching","ember-metal/expand_properties","ember-metal/computed","ember-metal/computed_macros","ember-metal/observer","ember-metal/mixin","ember-metal/binding","ember-metal/run_loop","ember-metal/libraries","ember-metal/is_none","ember-metal/is_empty","ember-metal/is_blank","ember-metal/is_present","ember-metal/keys","backburner","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __dependency16__, __dependency17__, __dependency18__, __dependency19__, __dependency20__, __dependency21__, __dependency22__, __dependency23__, __dependency24__, __dependency25__, __dependency26__, __dependency27__, __dependency28__, __dependency29__, __dependency30__, __dependency31__, __dependency32__, __dependency33__, __dependency34__, __dependency35__, __dependency36__, __dependency37__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
Ember Metal
|
|
|
|
@module ember
|
|
@submodule ember-metal
|
|
*/
|
|
|
|
// BEGIN IMPORTS
|
|
var Ember = __dependency1__["default"];
|
|
var merge = __dependency2__["default"];
|
|
var instrument = __dependency3__.instrument;
|
|
var reset = __dependency3__.reset;
|
|
var subscribe = __dependency3__.subscribe;
|
|
var unsubscribe = __dependency3__.unsubscribe;
|
|
var EMPTY_META = __dependency4__.EMPTY_META;
|
|
var GUID_KEY = __dependency4__.GUID_KEY;
|
|
var META_DESC = __dependency4__.META_DESC;
|
|
var apply = __dependency4__.apply;
|
|
var applyStr = __dependency4__.applyStr;
|
|
var canInvoke = __dependency4__.canInvoke;
|
|
var generateGuid = __dependency4__.generateGuid;
|
|
var getMeta = __dependency4__.getMeta;
|
|
var guidFor = __dependency4__.guidFor;
|
|
var inspect = __dependency4__.inspect;
|
|
var isArray = __dependency4__.isArray;
|
|
var makeArray = __dependency4__.makeArray;
|
|
var meta = __dependency4__.meta;
|
|
var metaPath = __dependency4__.metaPath;
|
|
var setMeta = __dependency4__.setMeta;
|
|
var tryCatchFinally = __dependency4__.tryCatchFinally;
|
|
var tryFinally = __dependency4__.tryFinally;
|
|
var tryInvoke = __dependency4__.tryInvoke;
|
|
var typeOf = __dependency4__.typeOf;
|
|
var uuid = __dependency4__.uuid;
|
|
var wrap = __dependency4__.wrap;
|
|
var EmberError = __dependency5__["default"];
|
|
var EnumerableUtils = __dependency6__["default"];
|
|
var Cache = __dependency7__["default"];
|
|
var create = __dependency8__.create;
|
|
var hasPropertyAccessors = __dependency8__.hasPropertyAccessors;
|
|
var filter = __dependency9__.filter;
|
|
var forEach = __dependency9__.forEach;
|
|
var indexOf = __dependency9__.indexOf;
|
|
var map = __dependency9__.map;
|
|
var Logger = __dependency10__["default"];
|
|
|
|
var _getPath = __dependency11__._getPath;
|
|
var get = __dependency11__.get;
|
|
var getWithDefault = __dependency11__.getWithDefault;
|
|
var normalizeTuple = __dependency11__.normalizeTuple;
|
|
|
|
var accumulateListeners = __dependency12__.accumulateListeners;
|
|
var addListener = __dependency12__.addListener;
|
|
var hasListeners = __dependency12__.hasListeners;
|
|
var listenersFor = __dependency12__.listenersFor;
|
|
var on = __dependency12__.on;
|
|
var removeListener = __dependency12__.removeListener;
|
|
var sendEvent = __dependency12__.sendEvent;
|
|
var suspendListener = __dependency12__.suspendListener;
|
|
var suspendListeners = __dependency12__.suspendListeners;
|
|
var watchedEvents = __dependency12__.watchedEvents;
|
|
|
|
var ObserverSet = __dependency13__["default"];
|
|
|
|
var beginPropertyChanges = __dependency14__.beginPropertyChanges;
|
|
var changeProperties = __dependency14__.changeProperties;
|
|
var endPropertyChanges = __dependency14__.endPropertyChanges;
|
|
var overrideChains = __dependency14__.overrideChains;
|
|
var propertyDidChange = __dependency14__.propertyDidChange;
|
|
var propertyWillChange = __dependency14__.propertyWillChange;
|
|
|
|
var Descriptor = __dependency15__.Descriptor;
|
|
var defineProperty = __dependency15__.defineProperty;
|
|
var set = __dependency16__.set;
|
|
var trySet = __dependency16__.trySet;
|
|
|
|
var Map = __dependency17__.Map;
|
|
var MapWithDefault = __dependency17__.MapWithDefault;
|
|
var OrderedSet = __dependency17__.OrderedSet;
|
|
var getProperties = __dependency18__["default"];
|
|
var setProperties = __dependency19__["default"];
|
|
var watchKey = __dependency20__.watchKey;
|
|
var unwatchKey = __dependency20__.unwatchKey;
|
|
var ChainNode = __dependency21__.ChainNode;
|
|
var finishChains = __dependency21__.finishChains;
|
|
var flushPendingChains = __dependency21__.flushPendingChains;
|
|
var removeChainWatcher = __dependency21__.removeChainWatcher;
|
|
var watchPath = __dependency22__.watchPath;
|
|
var unwatchPath = __dependency22__.unwatchPath;
|
|
var destroy = __dependency23__.destroy;
|
|
var isWatching = __dependency23__.isWatching;
|
|
var rewatch = __dependency23__.rewatch;
|
|
var unwatch = __dependency23__.unwatch;
|
|
var watch = __dependency23__.watch;
|
|
var expandProperties = __dependency24__["default"];
|
|
var ComputedProperty = __dependency25__.ComputedProperty;
|
|
var computed = __dependency25__.computed;
|
|
var cacheFor = __dependency25__.cacheFor;
|
|
|
|
// side effect of defining the computed.* macros
|
|
|
|
var _suspendBeforeObserver = __dependency27__._suspendBeforeObserver;
|
|
var _suspendBeforeObservers = __dependency27__._suspendBeforeObservers;
|
|
var _suspendObserver = __dependency27__._suspendObserver;
|
|
var _suspendObservers = __dependency27__._suspendObservers;
|
|
var addBeforeObserver = __dependency27__.addBeforeObserver;
|
|
var addObserver = __dependency27__.addObserver;
|
|
var beforeObserversFor = __dependency27__.beforeObserversFor;
|
|
var observersFor = __dependency27__.observersFor;
|
|
var removeBeforeObserver = __dependency27__.removeBeforeObserver;
|
|
var removeObserver = __dependency27__.removeObserver;
|
|
var IS_BINDING = __dependency28__.IS_BINDING;
|
|
var Mixin = __dependency28__.Mixin;
|
|
var aliasMethod = __dependency28__.aliasMethod;
|
|
var beforeObserver = __dependency28__.beforeObserver;
|
|
var immediateObserver = __dependency28__.immediateObserver;
|
|
var mixin = __dependency28__.mixin;
|
|
var observer = __dependency28__.observer;
|
|
var required = __dependency28__.required;
|
|
var Binding = __dependency29__.Binding;
|
|
var bind = __dependency29__.bind;
|
|
var isGlobalPath = __dependency29__.isGlobalPath;
|
|
var oneWay = __dependency29__.oneWay;
|
|
var run = __dependency30__["default"];
|
|
var Libraries = __dependency31__["default"];
|
|
var isNone = __dependency32__["default"];
|
|
var isEmpty = __dependency33__["default"];
|
|
var isBlank = __dependency34__["default"];
|
|
var isPresent = __dependency35__["default"];
|
|
var keys = __dependency36__["default"];
|
|
var Backburner = __dependency37__["default"];
|
|
|
|
// END IMPORTS
|
|
|
|
// BEGIN EXPORTS
|
|
var EmberInstrumentation = Ember.Instrumentation = {};
|
|
EmberInstrumentation.instrument = instrument;
|
|
EmberInstrumentation.subscribe = subscribe;
|
|
EmberInstrumentation.unsubscribe = unsubscribe;
|
|
EmberInstrumentation.reset = reset;
|
|
|
|
Ember.instrument = instrument;
|
|
Ember.subscribe = subscribe;
|
|
|
|
Ember._Cache = Cache;
|
|
|
|
Ember.generateGuid = generateGuid;
|
|
Ember.GUID_KEY = GUID_KEY;
|
|
Ember.create = create;
|
|
Ember.keys = keys;
|
|
Ember.platform = {
|
|
defineProperty: defineProperty,
|
|
hasPropertyAccessors: hasPropertyAccessors
|
|
};
|
|
|
|
var EmberArrayPolyfills = Ember.ArrayPolyfills = {};
|
|
|
|
EmberArrayPolyfills.map = map;
|
|
EmberArrayPolyfills.forEach = forEach;
|
|
EmberArrayPolyfills.filter = filter;
|
|
EmberArrayPolyfills.indexOf = indexOf;
|
|
|
|
Ember.Error = EmberError;
|
|
Ember.guidFor = guidFor;
|
|
Ember.META_DESC = META_DESC;
|
|
Ember.EMPTY_META = EMPTY_META;
|
|
Ember.meta = meta;
|
|
Ember.getMeta = getMeta;
|
|
Ember.setMeta = setMeta;
|
|
Ember.metaPath = metaPath;
|
|
Ember.inspect = inspect;
|
|
Ember.typeOf = typeOf;
|
|
Ember.tryCatchFinally = tryCatchFinally;
|
|
Ember.isArray = isArray;
|
|
Ember.makeArray = makeArray;
|
|
Ember.canInvoke = canInvoke;
|
|
Ember.tryInvoke = tryInvoke;
|
|
Ember.tryFinally = tryFinally;
|
|
Ember.wrap = wrap;
|
|
Ember.apply = apply;
|
|
Ember.applyStr = applyStr;
|
|
Ember.uuid = uuid;
|
|
|
|
Ember.Logger = Logger;
|
|
|
|
Ember.get = get;
|
|
Ember.getWithDefault = getWithDefault;
|
|
Ember.normalizeTuple = normalizeTuple;
|
|
Ember._getPath = _getPath;
|
|
|
|
Ember.EnumerableUtils = EnumerableUtils;
|
|
|
|
Ember.on = on;
|
|
Ember.addListener = addListener;
|
|
Ember.removeListener = removeListener;
|
|
Ember._suspendListener = suspendListener;
|
|
Ember._suspendListeners = suspendListeners;
|
|
Ember.sendEvent = sendEvent;
|
|
Ember.hasListeners = hasListeners;
|
|
Ember.watchedEvents = watchedEvents;
|
|
Ember.listenersFor = listenersFor;
|
|
Ember.accumulateListeners = accumulateListeners;
|
|
|
|
Ember._ObserverSet = ObserverSet;
|
|
|
|
Ember.propertyWillChange = propertyWillChange;
|
|
Ember.propertyDidChange = propertyDidChange;
|
|
Ember.overrideChains = overrideChains;
|
|
Ember.beginPropertyChanges = beginPropertyChanges;
|
|
Ember.endPropertyChanges = endPropertyChanges;
|
|
Ember.changeProperties = changeProperties;
|
|
|
|
Ember.Descriptor = Descriptor;
|
|
Ember.defineProperty = defineProperty;
|
|
|
|
Ember.set = set;
|
|
Ember.trySet = trySet;
|
|
|
|
Ember.OrderedSet = OrderedSet;
|
|
Ember.Map = Map;
|
|
Ember.MapWithDefault = MapWithDefault;
|
|
|
|
Ember.getProperties = getProperties;
|
|
Ember.setProperties = setProperties;
|
|
|
|
Ember.watchKey = watchKey;
|
|
Ember.unwatchKey = unwatchKey;
|
|
|
|
Ember.flushPendingChains = flushPendingChains;
|
|
Ember.removeChainWatcher = removeChainWatcher;
|
|
Ember._ChainNode = ChainNode;
|
|
Ember.finishChains = finishChains;
|
|
|
|
Ember.watchPath = watchPath;
|
|
Ember.unwatchPath = unwatchPath;
|
|
|
|
Ember.watch = watch;
|
|
Ember.isWatching = isWatching;
|
|
Ember.unwatch = unwatch;
|
|
Ember.rewatch = rewatch;
|
|
Ember.destroy = destroy;
|
|
|
|
Ember.expandProperties = expandProperties;
|
|
|
|
Ember.ComputedProperty = ComputedProperty;
|
|
Ember.computed = computed;
|
|
Ember.cacheFor = cacheFor;
|
|
|
|
Ember.addObserver = addObserver;
|
|
Ember.observersFor = observersFor;
|
|
Ember.removeObserver = removeObserver;
|
|
Ember.addBeforeObserver = addBeforeObserver;
|
|
Ember._suspendBeforeObserver = _suspendBeforeObserver;
|
|
Ember._suspendBeforeObservers = _suspendBeforeObservers;
|
|
Ember._suspendObserver = _suspendObserver;
|
|
Ember._suspendObservers = _suspendObservers;
|
|
Ember.beforeObserversFor = beforeObserversFor;
|
|
Ember.removeBeforeObserver = removeBeforeObserver;
|
|
|
|
Ember.IS_BINDING = IS_BINDING;
|
|
Ember.required = required;
|
|
Ember.aliasMethod = aliasMethod;
|
|
Ember.observer = observer;
|
|
Ember.immediateObserver = immediateObserver;
|
|
Ember.beforeObserver = beforeObserver;
|
|
Ember.mixin = mixin;
|
|
Ember.Mixin = Mixin;
|
|
|
|
Ember.oneWay = oneWay;
|
|
Ember.bind = bind;
|
|
Ember.Binding = Binding;
|
|
Ember.isGlobalPath = isGlobalPath;
|
|
|
|
Ember.run = run;
|
|
|
|
/**
|
|
* @class Backburner
|
|
* @for Ember
|
|
* @private
|
|
*/
|
|
Ember.Backburner = Backburner;
|
|
|
|
Ember.libraries = new Libraries();
|
|
Ember.libraries.registerCoreLibrary('Ember', Ember.VERSION);
|
|
|
|
Ember.isNone = isNone;
|
|
Ember.isEmpty = isEmpty;
|
|
Ember.isBlank = isBlank;
|
|
|
|
|
|
Ember.isPresent = isPresent;
|
|
|
|
|
|
Ember.merge = merge;
|
|
|
|
/**
|
|
A function may be assigned to `Ember.onerror` to be called when Ember
|
|
internals encounter an error. This is useful for specialized error handling
|
|
and reporting code.
|
|
|
|
```javascript
|
|
Ember.onerror = function(error) {
|
|
Em.$.ajax('/report-error', 'POST', {
|
|
stack: error.stack,
|
|
otherInformation: 'whatever app state you want to provide'
|
|
});
|
|
};
|
|
```
|
|
|
|
Internally, `Ember.onerror` is used as Backburner's error handler.
|
|
|
|
@event onerror
|
|
@for Ember
|
|
@param {Exception} error the error object
|
|
*/
|
|
Ember.onerror = null;
|
|
// END EXPORTS
|
|
|
|
// do this for side-effects of updating Ember.assert, warn, etc when
|
|
// ember-debug is present
|
|
if (Ember.__loader.registry['ember-debug']) {
|
|
requireModule('ember-debug');
|
|
}
|
|
|
|
__exports__["default"] = Ember;
|
|
});
|
|
enifed("ember-metal/alias",
|
|
["ember-metal/property_get","ember-metal/property_set","ember-metal/core","ember-metal/error","ember-metal/properties","ember-metal/computed","ember-metal/platform","ember-metal/utils","ember-metal/dependent_keys","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __exports__) {
|
|
"use strict";
|
|
var get = __dependency1__.get;
|
|
var set = __dependency2__.set;
|
|
var Ember = __dependency3__["default"];
|
|
// Ember.assert
|
|
var EmberError = __dependency4__["default"];
|
|
var Descriptor = __dependency5__.Descriptor;
|
|
var defineProperty = __dependency5__.defineProperty;
|
|
var ComputedProperty = __dependency6__.ComputedProperty;
|
|
var create = __dependency7__.create;
|
|
var meta = __dependency8__.meta;
|
|
var inspect = __dependency8__.inspect;
|
|
var addDependentKeys = __dependency9__.addDependentKeys;
|
|
var removeDependentKeys = __dependency9__.removeDependentKeys;
|
|
|
|
__exports__["default"] = function alias(altKey) {
|
|
return new AliasedProperty(altKey);
|
|
}
|
|
|
|
function AliasedProperty(altKey) {
|
|
this.altKey = altKey;
|
|
this._dependentKeys = [ altKey ];
|
|
}
|
|
|
|
__exports__.AliasedProperty = AliasedProperty;AliasedProperty.prototype = create(Descriptor.prototype);
|
|
|
|
AliasedProperty.prototype.get = function AliasedProperty_get(obj, keyName) {
|
|
return get(obj, this.altKey);
|
|
};
|
|
|
|
AliasedProperty.prototype.set = function AliasedProperty_set(obj, keyName, value) {
|
|
return set(obj, this.altKey, value);
|
|
};
|
|
|
|
AliasedProperty.prototype.willWatch = function(obj, keyName) {
|
|
addDependentKeys(this, obj, keyName, meta(obj));
|
|
};
|
|
|
|
AliasedProperty.prototype.didUnwatch = function(obj, keyName) {
|
|
removeDependentKeys(this, obj, keyName, meta(obj));
|
|
};
|
|
|
|
AliasedProperty.prototype.setup = function(obj, keyName) {
|
|
Ember.assert("Setting alias '" + keyName + "' on self", this.altKey !== keyName);
|
|
var m = meta(obj);
|
|
if (m.watching[keyName]) {
|
|
addDependentKeys(this, obj, keyName, m);
|
|
}
|
|
};
|
|
|
|
AliasedProperty.prototype.teardown = function(obj, keyName) {
|
|
var m = meta(obj);
|
|
if (m.watching[keyName]) {
|
|
removeDependentKeys(this, obj, keyName, m);
|
|
}
|
|
};
|
|
|
|
AliasedProperty.prototype.readOnly = function() {
|
|
this.set = AliasedProperty_readOnlySet;
|
|
return this;
|
|
};
|
|
|
|
function AliasedProperty_readOnlySet(obj, keyName, value) {
|
|
throw new EmberError('Cannot set read-only property "' + keyName + '" on object: ' + inspect(obj));
|
|
}
|
|
|
|
AliasedProperty.prototype.oneWay = function() {
|
|
this.set = AliasedProperty_oneWaySet;
|
|
return this;
|
|
};
|
|
|
|
function AliasedProperty_oneWaySet(obj, keyName, value) {
|
|
defineProperty(obj, keyName, null);
|
|
return set(obj, keyName, value);
|
|
}
|
|
|
|
// Backwards compatibility with Ember Data
|
|
AliasedProperty.prototype._meta = undefined;
|
|
AliasedProperty.prototype.meta = ComputedProperty.prototype.meta;
|
|
});
|
|
enifed("ember-metal/array",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember-metal
|
|
*/
|
|
|
|
var ArrayPrototype = Array.prototype;
|
|
|
|
// Testing this is not ideal, but we want to use native functions
|
|
// if available, but not to use versions created by libraries like Prototype
|
|
var isNativeFunc = function(func) {
|
|
// This should probably work in all browsers likely to have ES5 array methods
|
|
return func && Function.prototype.toString.call(func).indexOf('[native code]') > -1;
|
|
};
|
|
|
|
var defineNativeShim = function(nativeFunc, shim) {
|
|
if (isNativeFunc(nativeFunc)) {
|
|
return nativeFunc;
|
|
}
|
|
return shim;
|
|
};
|
|
|
|
// From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/map
|
|
var map = defineNativeShim(ArrayPrototype.map, function(fun /*, thisp */) {
|
|
//"use strict";
|
|
|
|
if (this === void 0 || this === null || typeof fun !== "function") {
|
|
throw new TypeError();
|
|
}
|
|
|
|
var t = Object(this);
|
|
var len = t.length >>> 0;
|
|
var res = new Array(len);
|
|
var thisp = arguments[1];
|
|
|
|
for (var i = 0; i < len; i++) {
|
|
if (i in t) {
|
|
res[i] = fun.call(thisp, t[i], i, t);
|
|
}
|
|
}
|
|
|
|
return res;
|
|
});
|
|
|
|
// From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/foreach
|
|
var forEach = defineNativeShim(ArrayPrototype.forEach, function(fun /*, thisp */) {
|
|
//"use strict";
|
|
|
|
if (this === void 0 || this === null || typeof fun !== "function") {
|
|
throw new TypeError();
|
|
}
|
|
|
|
var t = Object(this);
|
|
var len = t.length >>> 0;
|
|
var thisp = arguments[1];
|
|
|
|
for (var i = 0; i < len; i++) {
|
|
if (i in t) {
|
|
fun.call(thisp, t[i], i, t);
|
|
}
|
|
}
|
|
});
|
|
|
|
var indexOf = defineNativeShim(ArrayPrototype.indexOf, function (obj, fromIndex) {
|
|
if (fromIndex === null || fromIndex === undefined) {
|
|
fromIndex = 0;
|
|
}
|
|
else if (fromIndex < 0) {
|
|
fromIndex = Math.max(0, this.length + fromIndex);
|
|
}
|
|
|
|
for (var i = fromIndex, j = this.length; i < j; i++) {
|
|
if (this[i] === obj) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
});
|
|
|
|
var lastIndexOf = defineNativeShim(ArrayPrototype.lastIndexOf, function(obj, fromIndex) {
|
|
var len = this.length;
|
|
var idx;
|
|
|
|
if (fromIndex === undefined) fromIndex = len-1;
|
|
else fromIndex = (fromIndex < 0) ? Math.ceil(fromIndex) : Math.floor(fromIndex);
|
|
if (fromIndex < 0) fromIndex += len;
|
|
|
|
for(idx = fromIndex;idx>=0;idx--) {
|
|
if (this[idx] === obj) return idx ;
|
|
}
|
|
return -1;
|
|
});
|
|
|
|
var filter = defineNativeShim(ArrayPrototype.filter, function (fn, context) {
|
|
var i, value;
|
|
var result = [];
|
|
var length = this.length;
|
|
|
|
for (i = 0; i < length; i++) {
|
|
if (this.hasOwnProperty(i)) {
|
|
value = this[i];
|
|
if (fn.call(context, value, i, this)) {
|
|
result.push(value);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
});
|
|
|
|
if (Ember.SHIM_ES5) {
|
|
ArrayPrototype.map = ArrayPrototype.map || map;
|
|
ArrayPrototype.forEach = ArrayPrototype.forEach || forEach;
|
|
ArrayPrototype.filter = ArrayPrototype.filter || filter;
|
|
ArrayPrototype.indexOf = ArrayPrototype.indexOf || indexOf;
|
|
ArrayPrototype.lastIndexOf = ArrayPrototype.lastIndexOf || lastIndexOf;
|
|
}
|
|
|
|
/**
|
|
Array polyfills to support ES5 features in older browsers.
|
|
|
|
@namespace Ember
|
|
@property ArrayPolyfills
|
|
*/
|
|
__exports__.map = map;
|
|
__exports__.forEach = forEach;
|
|
__exports__.filter = filter;
|
|
__exports__.indexOf = indexOf;
|
|
__exports__.lastIndexOf = lastIndexOf;
|
|
});
|
|
enifed("ember-metal/binding",
|
|
["ember-metal/core","ember-metal/property_get","ember-metal/property_set","ember-metal/utils","ember-metal/observer","ember-metal/run_loop","ember-metal/path_cache","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.Logger, Ember.LOG_BINDINGS, assert
|
|
var get = __dependency2__.get;
|
|
var trySet = __dependency3__.trySet;
|
|
var guidFor = __dependency4__.guidFor;
|
|
var addObserver = __dependency5__.addObserver;
|
|
var removeObserver = __dependency5__.removeObserver;
|
|
var _suspendObserver = __dependency5__._suspendObserver;
|
|
var run = __dependency6__["default"];
|
|
var isGlobalPath = __dependency7__.isGlobal;
|
|
|
|
|
|
// ES6TODO: where is Ember.lookup defined?
|
|
/**
|
|
@module ember-metal
|
|
*/
|
|
|
|
// ..........................................................
|
|
// CONSTANTS
|
|
//
|
|
|
|
/**
|
|
Debug parameter you can turn on. This will log all bindings that fire to
|
|
the console. This should be disabled in production code. Note that you
|
|
can also enable this from the console or temporarily.
|
|
|
|
@property LOG_BINDINGS
|
|
@for Ember
|
|
@type Boolean
|
|
@default false
|
|
*/
|
|
Ember.LOG_BINDINGS = false || !!Ember.ENV.LOG_BINDINGS;
|
|
|
|
/**
|
|
Returns true if the provided path is global (e.g., `MyApp.fooController.bar`)
|
|
instead of local (`foo.bar.baz`).
|
|
|
|
@method isGlobalPath
|
|
@for Ember
|
|
@private
|
|
@param {String} path
|
|
@return Boolean
|
|
*/
|
|
|
|
function getWithGlobals(obj, path) {
|
|
return get(isGlobalPath(path) ? Ember.lookup : obj, path);
|
|
}
|
|
|
|
// ..........................................................
|
|
// BINDING
|
|
//
|
|
|
|
function Binding(toPath, fromPath) {
|
|
this._direction = undefined;
|
|
this._from = fromPath;
|
|
this._to = toPath;
|
|
this._readyToSync = undefined;
|
|
this._oneWay = undefined;
|
|
}
|
|
|
|
/**
|
|
@class Binding
|
|
@namespace Ember
|
|
*/
|
|
|
|
Binding.prototype = {
|
|
/**
|
|
This copies the Binding so it can be connected to another object.
|
|
|
|
@method copy
|
|
@return {Ember.Binding} `this`
|
|
*/
|
|
copy: function () {
|
|
var copy = new Binding(this._to, this._from);
|
|
if (this._oneWay) { copy._oneWay = true; }
|
|
return copy;
|
|
},
|
|
|
|
// ..........................................................
|
|
// CONFIG
|
|
//
|
|
|
|
/**
|
|
This will set `from` property path to the specified value. It will not
|
|
attempt to resolve this property path to an actual object until you
|
|
connect the binding.
|
|
|
|
The binding will search for the property path starting at the root object
|
|
you pass when you `connect()` the binding. It follows the same rules as
|
|
`get()` - see that method for more information.
|
|
|
|
@method from
|
|
@param {String} path the property path to connect to
|
|
@return {Ember.Binding} `this`
|
|
*/
|
|
from: function(path) {
|
|
this._from = path;
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
This will set the `to` property path to the specified value. It will not
|
|
attempt to resolve this property path to an actual object until you
|
|
connect the binding.
|
|
|
|
The binding will search for the property path starting at the root object
|
|
you pass when you `connect()` the binding. It follows the same rules as
|
|
`get()` - see that method for more information.
|
|
|
|
@method to
|
|
@param {String|Tuple} path A property path or tuple
|
|
@return {Ember.Binding} `this`
|
|
*/
|
|
to: function(path) {
|
|
this._to = path;
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Configures the binding as one way. A one-way binding will relay changes
|
|
on the `from` side to the `to` side, but not the other way around. This
|
|
means that if you change the `to` side directly, the `from` side may have
|
|
a different value.
|
|
|
|
@method oneWay
|
|
@return {Ember.Binding} `this`
|
|
*/
|
|
oneWay: function() {
|
|
this._oneWay = true;
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
@method toString
|
|
@return {String} string representation of binding
|
|
*/
|
|
toString: function() {
|
|
var oneWay = this._oneWay ? '[oneWay]' : '';
|
|
return "Ember.Binding<" + guidFor(this) + ">(" + this._from + " -> " + this._to + ")" + oneWay;
|
|
},
|
|
|
|
// ..........................................................
|
|
// CONNECT AND SYNC
|
|
//
|
|
|
|
/**
|
|
Attempts to connect this binding instance so that it can receive and relay
|
|
changes. This method will raise an exception if you have not set the
|
|
from/to properties yet.
|
|
|
|
@method connect
|
|
@param {Object} obj The root object for this binding.
|
|
@return {Ember.Binding} `this`
|
|
*/
|
|
connect: function(obj) {
|
|
Ember.assert('Must pass a valid object to Ember.Binding.connect()', !!obj);
|
|
|
|
var fromPath = this._from;
|
|
var toPath = this._to;
|
|
trySet(obj, toPath, getWithGlobals(obj, fromPath));
|
|
|
|
// add an observer on the object to be notified when the binding should be updated
|
|
addObserver(obj, fromPath, this, this.fromDidChange);
|
|
|
|
// if the binding is a two-way binding, also set up an observer on the target
|
|
if (!this._oneWay) {
|
|
addObserver(obj, toPath, this, this.toDidChange);
|
|
}
|
|
|
|
this._readyToSync = true;
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Disconnects the binding instance. Changes will no longer be relayed. You
|
|
will not usually need to call this method.
|
|
|
|
@method disconnect
|
|
@param {Object} obj The root object you passed when connecting the binding.
|
|
@return {Ember.Binding} `this`
|
|
*/
|
|
disconnect: function(obj) {
|
|
Ember.assert('Must pass a valid object to Ember.Binding.disconnect()', !!obj);
|
|
|
|
var twoWay = !this._oneWay;
|
|
|
|
// remove an observer on the object so we're no longer notified of
|
|
// changes that should update bindings.
|
|
removeObserver(obj, this._from, this, this.fromDidChange);
|
|
|
|
// if the binding is two-way, remove the observer from the target as well
|
|
if (twoWay) {
|
|
removeObserver(obj, this._to, this, this.toDidChange);
|
|
}
|
|
|
|
this._readyToSync = false; // disable scheduled syncs...
|
|
return this;
|
|
},
|
|
|
|
// ..........................................................
|
|
// PRIVATE
|
|
//
|
|
|
|
/* called when the from side changes */
|
|
fromDidChange: function(target) {
|
|
this._scheduleSync(target, 'fwd');
|
|
},
|
|
|
|
/* called when the to side changes */
|
|
toDidChange: function(target) {
|
|
this._scheduleSync(target, 'back');
|
|
},
|
|
|
|
_scheduleSync: function(obj, dir) {
|
|
var existingDir = this._direction;
|
|
|
|
// if we haven't scheduled the binding yet, schedule it
|
|
if (existingDir === undefined) {
|
|
run.schedule('sync', this, this._sync, obj);
|
|
this._direction = dir;
|
|
}
|
|
|
|
// If both a 'back' and 'fwd' sync have been scheduled on the same object,
|
|
// default to a 'fwd' sync so that it remains deterministic.
|
|
if (existingDir === 'back' && dir === 'fwd') {
|
|
this._direction = 'fwd';
|
|
}
|
|
},
|
|
|
|
_sync: function(obj) {
|
|
var log = Ember.LOG_BINDINGS;
|
|
|
|
// don't synchronize destroyed objects or disconnected bindings
|
|
if (obj.isDestroyed || !this._readyToSync) { return; }
|
|
|
|
// get the direction of the binding for the object we are
|
|
// synchronizing from
|
|
var direction = this._direction;
|
|
|
|
var fromPath = this._from;
|
|
var toPath = this._to;
|
|
|
|
this._direction = undefined;
|
|
|
|
// if we're synchronizing from the remote object...
|
|
if (direction === 'fwd') {
|
|
var fromValue = getWithGlobals(obj, this._from);
|
|
if (log) {
|
|
Ember.Logger.log(' ', this.toString(), '->', fromValue, obj);
|
|
}
|
|
if (this._oneWay) {
|
|
trySet(obj, toPath, fromValue);
|
|
} else {
|
|
_suspendObserver(obj, toPath, this, this.toDidChange, function () {
|
|
trySet(obj, toPath, fromValue);
|
|
});
|
|
}
|
|
// if we're synchronizing *to* the remote object
|
|
} else if (direction === 'back') {
|
|
var toValue = get(obj, this._to);
|
|
if (log) {
|
|
Ember.Logger.log(' ', this.toString(), '<-', toValue, obj);
|
|
}
|
|
_suspendObserver(obj, fromPath, this, this.fromDidChange, function () {
|
|
trySet(isGlobalPath(fromPath) ? Ember.lookup : obj, fromPath, toValue);
|
|
});
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
function mixinProperties(to, from) {
|
|
for (var key in from) {
|
|
if (from.hasOwnProperty(key)) {
|
|
to[key] = from[key];
|
|
}
|
|
}
|
|
}
|
|
|
|
mixinProperties(Binding, {
|
|
|
|
/*
|
|
See `Ember.Binding.from`.
|
|
|
|
@method from
|
|
@static
|
|
*/
|
|
from: function(from) {
|
|
var C = this;
|
|
return new C(undefined, from);
|
|
},
|
|
|
|
/*
|
|
See `Ember.Binding.to`.
|
|
|
|
@method to
|
|
@static
|
|
*/
|
|
to: function(to) {
|
|
var C = this;
|
|
return new C(to, undefined);
|
|
},
|
|
|
|
/**
|
|
Creates a new Binding instance and makes it apply in a single direction.
|
|
A one-way binding will relay changes on the `from` side object (supplied
|
|
as the `from` argument) the `to` side, but not the other way around.
|
|
This means that if you change the "to" side directly, the "from" side may have
|
|
a different value.
|
|
|
|
See `Binding.oneWay`.
|
|
|
|
@method oneWay
|
|
@param {String} from from path.
|
|
@param {Boolean} [flag] (Optional) passing nothing here will make the
|
|
binding `oneWay`. You can instead pass `false` to disable `oneWay`, making the
|
|
binding two way again.
|
|
@return {Ember.Binding} `this`
|
|
*/
|
|
oneWay: function(from, flag) {
|
|
var C = this;
|
|
return new C(undefined, from).oneWay(flag);
|
|
}
|
|
|
|
});
|
|
/**
|
|
An `Ember.Binding` connects the properties of two objects so that whenever
|
|
the value of one property changes, the other property will be changed also.
|
|
|
|
## Automatic Creation of Bindings with `/^*Binding/`-named Properties
|
|
|
|
You do not usually create Binding objects directly but instead describe
|
|
bindings in your class or object definition using automatic binding
|
|
detection.
|
|
|
|
Properties ending in a `Binding` suffix will be converted to `Ember.Binding`
|
|
instances. The value of this property should be a string representing a path
|
|
to another object or a custom binding instance created using Binding helpers
|
|
(see "One Way Bindings"):
|
|
|
|
```
|
|
valueBinding: "MyApp.someController.title"
|
|
```
|
|
|
|
This will create a binding from `MyApp.someController.title` to the `value`
|
|
property of your object instance automatically. Now the two values will be
|
|
kept in sync.
|
|
|
|
## One Way Bindings
|
|
|
|
One especially useful binding customization you can use is the `oneWay()`
|
|
helper. This helper tells Ember that you are only interested in
|
|
receiving changes on the object you are binding from. For example, if you
|
|
are binding to a preference and you want to be notified if the preference
|
|
has changed, but your object will not be changing the preference itself, you
|
|
could do:
|
|
|
|
```
|
|
bigTitlesBinding: Ember.Binding.oneWay("MyApp.preferencesController.bigTitles")
|
|
```
|
|
|
|
This way if the value of `MyApp.preferencesController.bigTitles` changes the
|
|
`bigTitles` property of your object will change also. However, if you
|
|
change the value of your `bigTitles` property, it will not update the
|
|
`preferencesController`.
|
|
|
|
One way bindings are almost twice as fast to setup and twice as fast to
|
|
execute because the binding only has to worry about changes to one side.
|
|
|
|
You should consider using one way bindings anytime you have an object that
|
|
may be created frequently and you do not intend to change a property; only
|
|
to monitor it for changes (such as in the example above).
|
|
|
|
## Adding Bindings Manually
|
|
|
|
All of the examples above show you how to configure a custom binding, but the
|
|
result of these customizations will be a binding template, not a fully active
|
|
Binding instance. The binding will actually become active only when you
|
|
instantiate the object the binding belongs to. It is useful however, to
|
|
understand what actually happens when the binding is activated.
|
|
|
|
For a binding to function it must have at least a `from` property and a `to`
|
|
property. The `from` property path points to the object/key that you want to
|
|
bind from while the `to` path points to the object/key you want to bind to.
|
|
|
|
When you define a custom binding, you are usually describing the property
|
|
you want to bind from (such as `MyApp.someController.value` in the examples
|
|
above). When your object is created, it will automatically assign the value
|
|
you want to bind `to` based on the name of your binding key. In the
|
|
examples above, during init, Ember objects will effectively call
|
|
something like this on your binding:
|
|
|
|
```javascript
|
|
binding = Ember.Binding.from("valueBinding").to("value");
|
|
```
|
|
|
|
This creates a new binding instance based on the template you provide, and
|
|
sets the to path to the `value` property of the new object. Now that the
|
|
binding is fully configured with a `from` and a `to`, it simply needs to be
|
|
connected to become active. This is done through the `connect()` method:
|
|
|
|
```javascript
|
|
binding.connect(this);
|
|
```
|
|
|
|
Note that when you connect a binding you pass the object you want it to be
|
|
connected to. This object will be used as the root for both the from and
|
|
to side of the binding when inspecting relative paths. This allows the
|
|
binding to be automatically inherited by subclassed objects as well.
|
|
|
|
This also allows you to bind between objects using the paths you declare in
|
|
`from` and `to`:
|
|
|
|
```javascript
|
|
// Example 1
|
|
binding = Ember.Binding.from("App.someObject.value").to("value");
|
|
binding.connect(this);
|
|
|
|
// Example 2
|
|
binding = Ember.Binding.from("parentView.value").to("App.someObject.value");
|
|
binding.connect(this);
|
|
```
|
|
|
|
Now that the binding is connected, it will observe both the from and to side
|
|
and relay changes.
|
|
|
|
If you ever needed to do so (you almost never will, but it is useful to
|
|
understand this anyway), you could manually create an active binding by
|
|
using the `Ember.bind()` helper method. (This is the same method used by
|
|
to setup your bindings on objects):
|
|
|
|
```javascript
|
|
Ember.bind(MyApp.anotherObject, "value", "MyApp.someController.value");
|
|
```
|
|
|
|
Both of these code fragments have the same effect as doing the most friendly
|
|
form of binding creation like so:
|
|
|
|
```javascript
|
|
MyApp.anotherObject = Ember.Object.create({
|
|
valueBinding: "MyApp.someController.value",
|
|
|
|
// OTHER CODE FOR THIS OBJECT...
|
|
});
|
|
```
|
|
|
|
Ember's built in binding creation method makes it easy to automatically
|
|
create bindings for you. You should always use the highest-level APIs
|
|
available, even if you understand how it works underneath.
|
|
|
|
@class Binding
|
|
@namespace Ember
|
|
@since Ember 0.9
|
|
*/
|
|
// Ember.Binding = Binding; ES6TODO: where to put this?
|
|
|
|
|
|
/**
|
|
Global helper method to create a new binding. Just pass the root object
|
|
along with a `to` and `from` path to create and connect the binding.
|
|
|
|
@method bind
|
|
@for Ember
|
|
@param {Object} obj The root object of the transform.
|
|
@param {String} to The path to the 'to' side of the binding.
|
|
Must be relative to obj.
|
|
@param {String} from The path to the 'from' side of the binding.
|
|
Must be relative to obj or a global path.
|
|
@return {Ember.Binding} binding instance
|
|
*/
|
|
function bind(obj, to, from) {
|
|
return new Binding(to, from).connect(obj);
|
|
}
|
|
|
|
__exports__.bind = bind;/**
|
|
@method oneWay
|
|
@for Ember
|
|
@param {Object} obj The root object of the transform.
|
|
@param {String} to The path to the 'to' side of the binding.
|
|
Must be relative to obj.
|
|
@param {String} from The path to the 'from' side of the binding.
|
|
Must be relative to obj or a global path.
|
|
@return {Ember.Binding} binding instance
|
|
*/
|
|
function oneWay(obj, to, from) {
|
|
return new Binding(to, from).oneWay().connect(obj);
|
|
}
|
|
|
|
__exports__.oneWay = oneWay;__exports__.Binding = Binding;
|
|
__exports__.isGlobalPath = isGlobalPath;
|
|
});
|
|
enifed("ember-metal/cache",
|
|
["ember-metal/dictionary","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var dictionary = __dependency1__["default"];
|
|
__exports__["default"] = Cache;
|
|
|
|
function Cache(limit, func) {
|
|
this.store = dictionary(null);
|
|
this.size = 0;
|
|
this.misses = 0;
|
|
this.hits = 0;
|
|
this.limit = limit;
|
|
this.func = func;
|
|
}
|
|
|
|
var UNDEFINED = function() { };
|
|
|
|
Cache.prototype = {
|
|
set: function(key, value) {
|
|
if (this.limit > this.size) {
|
|
this.size ++;
|
|
if (value === undefined) {
|
|
this.store[key] = UNDEFINED;
|
|
} else {
|
|
this.store[key] = value;
|
|
}
|
|
}
|
|
|
|
return value;
|
|
},
|
|
|
|
get: function(key) {
|
|
var value = this.store[key];
|
|
|
|
if (value === undefined) {
|
|
this.misses ++;
|
|
value = this.set(key, this.func(key));
|
|
} else if (value === UNDEFINED) {
|
|
this.hits ++;
|
|
value = undefined;
|
|
} else {
|
|
this.hits ++;
|
|
// nothing to translate
|
|
}
|
|
|
|
return value;
|
|
},
|
|
|
|
purge: function() {
|
|
this.store = dictionary(null);
|
|
this.size = 0;
|
|
this.hits = 0;
|
|
this.misses = 0;
|
|
}
|
|
};
|
|
});
|
|
enifed("ember-metal/chains",
|
|
["ember-metal/core","ember-metal/property_get","ember-metal/utils","ember-metal/array","ember-metal/watch_key","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
// warn, assert, etc;
|
|
var get = __dependency2__.get;
|
|
var normalizeTuple = __dependency2__.normalizeTuple;
|
|
var metaFor = __dependency3__.meta;
|
|
var forEach = __dependency4__.forEach;
|
|
var watchKey = __dependency5__.watchKey;
|
|
var unwatchKey = __dependency5__.unwatchKey;
|
|
|
|
var warn = Ember.warn;
|
|
var FIRST_KEY = /^([^\.]+)/;
|
|
|
|
function firstKey(path) {
|
|
return path.match(FIRST_KEY)[0];
|
|
}
|
|
|
|
var pendingQueue = [];
|
|
|
|
// attempts to add the pendingQueue chains again. If some of them end up
|
|
// back in the queue and reschedule is true, schedules a timeout to try
|
|
// again.
|
|
function flushPendingChains() {
|
|
if (pendingQueue.length === 0) { return; } // nothing to do
|
|
|
|
var queue = pendingQueue;
|
|
pendingQueue = [];
|
|
|
|
forEach.call(queue, function(q) {
|
|
q[0].add(q[1]);
|
|
});
|
|
|
|
warn('Watching an undefined global, Ember expects watched globals to be' +
|
|
' setup by the time the run loop is flushed, check for typos', pendingQueue.length === 0);
|
|
}
|
|
|
|
__exports__.flushPendingChains = flushPendingChains;function addChainWatcher(obj, keyName, node) {
|
|
if (!obj || ('object' !== typeof obj)) { return; } // nothing to do
|
|
|
|
var m = metaFor(obj);
|
|
var nodes = m.chainWatchers;
|
|
|
|
if (!m.hasOwnProperty('chainWatchers')) {
|
|
nodes = m.chainWatchers = {};
|
|
}
|
|
|
|
if (!nodes[keyName]) {
|
|
nodes[keyName] = [];
|
|
}
|
|
nodes[keyName].push(node);
|
|
watchKey(obj, keyName, m);
|
|
}
|
|
|
|
function removeChainWatcher(obj, keyName, node) {
|
|
if (!obj || 'object' !== typeof obj) { return; } // nothing to do
|
|
|
|
var m = obj['__ember_meta__'];
|
|
if (m && !m.hasOwnProperty('chainWatchers')) { return; } // nothing to do
|
|
|
|
var nodes = m && m.chainWatchers;
|
|
|
|
if (nodes && nodes[keyName]) {
|
|
nodes = nodes[keyName];
|
|
for (var i = 0, l = nodes.length; i < l; i++) {
|
|
if (nodes[i] === node) {
|
|
nodes.splice(i, 1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
unwatchKey(obj, keyName, m);
|
|
}
|
|
|
|
// A ChainNode watches a single key on an object. If you provide a starting
|
|
// value for the key then the node won't actually watch it. For a root node
|
|
// pass null for parent and key and object for value.
|
|
function ChainNode(parent, key, value) {
|
|
this._parent = parent;
|
|
this._key = key;
|
|
|
|
// _watching is true when calling get(this._parent, this._key) will
|
|
// return the value of this node.
|
|
//
|
|
// It is false for the root of a chain (because we have no parent)
|
|
// and for global paths (because the parent node is the object with
|
|
// the observer on it)
|
|
this._watching = value===undefined;
|
|
|
|
this._value = value;
|
|
this._paths = {};
|
|
if (this._watching) {
|
|
this._object = parent.value();
|
|
if (this._object) {
|
|
addChainWatcher(this._object, this._key, this);
|
|
}
|
|
}
|
|
|
|
// Special-case: the EachProxy relies on immediate evaluation to
|
|
// establish its observers.
|
|
//
|
|
// TODO: Replace this with an efficient callback that the EachProxy
|
|
// can implement.
|
|
if (this._parent && this._parent._key === '@each') {
|
|
this.value();
|
|
}
|
|
}
|
|
|
|
var ChainNodePrototype = ChainNode.prototype;
|
|
|
|
function lazyGet(obj, key) {
|
|
if (!obj) return undefined;
|
|
|
|
var meta = obj['__ember_meta__'];
|
|
// check if object meant only to be a prototype
|
|
if (meta && meta.proto === obj) {
|
|
return undefined;
|
|
}
|
|
|
|
if (key === "@each") {
|
|
return get(obj, key);
|
|
}
|
|
|
|
// if a CP only return cached value
|
|
var desc = meta && meta.descs[key];
|
|
if (desc && desc._cacheable) {
|
|
if (key in meta.cache) {
|
|
return meta.cache[key];
|
|
} else {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
return get(obj, key);
|
|
}
|
|
|
|
ChainNodePrototype.value = function() {
|
|
if (this._value === undefined && this._watching) {
|
|
var obj = this._parent.value();
|
|
this._value = lazyGet(obj, this._key);
|
|
}
|
|
return this._value;
|
|
};
|
|
|
|
ChainNodePrototype.destroy = function() {
|
|
if (this._watching) {
|
|
var obj = this._object;
|
|
if (obj) {
|
|
removeChainWatcher(obj, this._key, this);
|
|
}
|
|
this._watching = false; // so future calls do nothing
|
|
}
|
|
};
|
|
|
|
// copies a top level object only
|
|
ChainNodePrototype.copy = function(obj) {
|
|
var ret = new ChainNode(null, null, obj);
|
|
var paths = this._paths;
|
|
var path;
|
|
|
|
for (path in paths) {
|
|
// this check will also catch non-number vals.
|
|
if (paths[path] <= 0) {
|
|
continue;
|
|
}
|
|
ret.add(path);
|
|
}
|
|
return ret;
|
|
};
|
|
|
|
// called on the root node of a chain to setup watchers on the specified
|
|
// path.
|
|
ChainNodePrototype.add = function(path) {
|
|
var obj, tuple, key, src, paths;
|
|
|
|
paths = this._paths;
|
|
paths[path] = (paths[path] || 0) + 1;
|
|
|
|
obj = this.value();
|
|
tuple = normalizeTuple(obj, path);
|
|
|
|
// the path was a local path
|
|
if (tuple[0] && tuple[0] === obj) {
|
|
path = tuple[1];
|
|
key = firstKey(path);
|
|
path = path.slice(key.length+1);
|
|
|
|
// global path, but object does not exist yet.
|
|
// put into a queue and try to connect later.
|
|
} else if (!tuple[0]) {
|
|
pendingQueue.push([this, path]);
|
|
tuple.length = 0;
|
|
return;
|
|
|
|
// global path, and object already exists
|
|
} else {
|
|
src = tuple[0];
|
|
key = path.slice(0, 0-(tuple[1].length+1));
|
|
path = tuple[1];
|
|
}
|
|
|
|
tuple.length = 0;
|
|
this.chain(key, path, src);
|
|
};
|
|
|
|
// called on the root node of a chain to teardown watcher on the specified
|
|
// path
|
|
ChainNodePrototype.remove = function(path) {
|
|
var obj, tuple, key, src, paths;
|
|
|
|
paths = this._paths;
|
|
if (paths[path] > 0) {
|
|
paths[path]--;
|
|
}
|
|
|
|
obj = this.value();
|
|
tuple = normalizeTuple(obj, path);
|
|
if (tuple[0] === obj) {
|
|
path = tuple[1];
|
|
key = firstKey(path);
|
|
path = path.slice(key.length+1);
|
|
} else {
|
|
src = tuple[0];
|
|
key = path.slice(0, 0-(tuple[1].length+1));
|
|
path = tuple[1];
|
|
}
|
|
|
|
tuple.length = 0;
|
|
this.unchain(key, path);
|
|
};
|
|
|
|
ChainNodePrototype.count = 0;
|
|
|
|
ChainNodePrototype.chain = function(key, path, src) {
|
|
var chains = this._chains;
|
|
var node;
|
|
if (!chains) {
|
|
chains = this._chains = {};
|
|
}
|
|
|
|
node = chains[key];
|
|
if (!node) {
|
|
node = chains[key] = new ChainNode(this, key, src);
|
|
}
|
|
node.count++; // count chains...
|
|
|
|
// chain rest of path if there is one
|
|
if (path) {
|
|
key = firstKey(path);
|
|
path = path.slice(key.length+1);
|
|
node.chain(key, path); // NOTE: no src means it will observe changes...
|
|
}
|
|
};
|
|
|
|
ChainNodePrototype.unchain = function(key, path) {
|
|
var chains = this._chains;
|
|
var node = chains[key];
|
|
|
|
// unchain rest of path first...
|
|
if (path && path.length > 1) {
|
|
var nextKey = firstKey(path);
|
|
var nextPath = path.slice(nextKey.length + 1);
|
|
node.unchain(nextKey, nextPath);
|
|
}
|
|
|
|
// delete node if needed.
|
|
node.count--;
|
|
if (node.count<=0) {
|
|
delete chains[node._key];
|
|
node.destroy();
|
|
}
|
|
|
|
};
|
|
|
|
ChainNodePrototype.willChange = function(events) {
|
|
var chains = this._chains;
|
|
if (chains) {
|
|
for(var key in chains) {
|
|
if (!chains.hasOwnProperty(key)) {
|
|
continue;
|
|
}
|
|
chains[key].willChange(events);
|
|
}
|
|
}
|
|
|
|
if (this._parent) {
|
|
this._parent.chainWillChange(this, this._key, 1, events);
|
|
}
|
|
};
|
|
|
|
ChainNodePrototype.chainWillChange = function(chain, path, depth, events) {
|
|
if (this._key) {
|
|
path = this._key + '.' + path;
|
|
}
|
|
|
|
if (this._parent) {
|
|
this._parent.chainWillChange(this, path, depth+1, events);
|
|
} else {
|
|
if (depth > 1) {
|
|
events.push(this.value(), path);
|
|
}
|
|
path = 'this.' + path;
|
|
if (this._paths[path] > 0) {
|
|
events.push(this.value(), path);
|
|
}
|
|
}
|
|
};
|
|
|
|
ChainNodePrototype.chainDidChange = function(chain, path, depth, events) {
|
|
if (this._key) {
|
|
path = this._key + '.' + path;
|
|
}
|
|
|
|
if (this._parent) {
|
|
this._parent.chainDidChange(this, path, depth+1, events);
|
|
} else {
|
|
if (depth > 1) {
|
|
events.push(this.value(), path);
|
|
}
|
|
path = 'this.' + path;
|
|
if (this._paths[path] > 0) {
|
|
events.push(this.value(), path);
|
|
}
|
|
}
|
|
};
|
|
|
|
ChainNodePrototype.didChange = function(events) {
|
|
// invalidate my own value first.
|
|
if (this._watching) {
|
|
var obj = this._parent.value();
|
|
if (obj !== this._object) {
|
|
removeChainWatcher(this._object, this._key, this);
|
|
this._object = obj;
|
|
addChainWatcher(obj, this._key, this);
|
|
}
|
|
this._value = undefined;
|
|
|
|
// Special-case: the EachProxy relies on immediate evaluation to
|
|
// establish its observers.
|
|
if (this._parent && this._parent._key === '@each') {
|
|
this.value();
|
|
}
|
|
}
|
|
|
|
// then notify chains...
|
|
var chains = this._chains;
|
|
if (chains) {
|
|
for(var key in chains) {
|
|
if (!chains.hasOwnProperty(key)) { continue; }
|
|
chains[key].didChange(events);
|
|
}
|
|
}
|
|
|
|
// if no events are passed in then we only care about the above wiring update
|
|
if (events === null) {
|
|
return;
|
|
}
|
|
|
|
// and finally tell parent about my path changing...
|
|
if (this._parent) {
|
|
this._parent.chainDidChange(this, this._key, 1, events);
|
|
}
|
|
};
|
|
|
|
function finishChains(obj) {
|
|
// We only create meta if we really have to
|
|
var m = obj['__ember_meta__'];
|
|
var chains, chainWatchers, chainNodes;
|
|
|
|
if (m) {
|
|
// finish any current chains node watchers that reference obj
|
|
chainWatchers = m.chainWatchers;
|
|
if (chainWatchers) {
|
|
for(var key in chainWatchers) {
|
|
if (!chainWatchers.hasOwnProperty(key)) {
|
|
continue;
|
|
}
|
|
|
|
chainNodes = chainWatchers[key];
|
|
if (chainNodes) {
|
|
for (var i=0,l=chainNodes.length;i<l;i++) {
|
|
chainNodes[i].didChange(null);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// copy chains from prototype
|
|
chains = m.chains;
|
|
if (chains && chains.value() !== obj) {
|
|
metaFor(obj).chains = chains = chains.copy(obj);
|
|
}
|
|
}
|
|
}
|
|
|
|
__exports__.finishChains = finishChains;__exports__.removeChainWatcher = removeChainWatcher;
|
|
__exports__.ChainNode = ChainNode;
|
|
});
|
|
enifed("ember-metal/computed",
|
|
["ember-metal/property_set","ember-metal/utils","ember-metal/expand_properties","ember-metal/error","ember-metal/properties","ember-metal/property_events","ember-metal/dependent_keys","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) {
|
|
"use strict";
|
|
var set = __dependency1__.set;
|
|
var meta = __dependency2__.meta;
|
|
var inspect = __dependency2__.inspect;
|
|
var expandProperties = __dependency3__["default"];
|
|
var EmberError = __dependency4__["default"];
|
|
var Descriptor = __dependency5__.Descriptor;
|
|
var defineProperty = __dependency5__.defineProperty;
|
|
var propertyWillChange = __dependency6__.propertyWillChange;
|
|
var propertyDidChange = __dependency6__.propertyDidChange;
|
|
var addDependentKeys = __dependency7__.addDependentKeys;
|
|
var removeDependentKeys = __dependency7__.removeDependentKeys;
|
|
|
|
/**
|
|
@module ember-metal
|
|
*/
|
|
|
|
var metaFor = meta;
|
|
var a_slice = [].slice;
|
|
|
|
function UNDEFINED() { }
|
|
|
|
// ..........................................................
|
|
// COMPUTED PROPERTY
|
|
//
|
|
|
|
/**
|
|
A computed property transforms an object's function into a property.
|
|
|
|
By default the function backing the computed property will only be called
|
|
once and the result will be cached. You can specify various properties
|
|
that your computed property depends on. This will force the cached
|
|
result to be recomputed if the dependencies are modified.
|
|
|
|
In the following example we declare a computed property (by calling
|
|
`.property()` on the fullName function) and setup the property
|
|
dependencies (depending on firstName and lastName). The fullName function
|
|
will be called once (regardless of how many times it is accessed) as long
|
|
as its dependencies have not changed. Once firstName or lastName are updated
|
|
any future calls (or anything bound) to fullName will incorporate the new
|
|
values.
|
|
|
|
```javascript
|
|
var Person = Ember.Object.extend({
|
|
// these will be supplied by `create`
|
|
firstName: null,
|
|
lastName: null,
|
|
|
|
fullName: function() {
|
|
var firstName = this.get('firstName');
|
|
var lastName = this.get('lastName');
|
|
|
|
return firstName + ' ' + lastName;
|
|
}.property('firstName', 'lastName')
|
|
});
|
|
|
|
var tom = Person.create({
|
|
firstName: 'Tom',
|
|
lastName: 'Dale'
|
|
});
|
|
|
|
tom.get('fullName') // 'Tom Dale'
|
|
```
|
|
|
|
You can also define what Ember should do when setting a computed property.
|
|
If you try to set a computed property, it will be invoked with the key and
|
|
value you want to set it to. You can also accept the previous value as the
|
|
third parameter.
|
|
|
|
```javascript
|
|
var Person = Ember.Object.extend({
|
|
// these will be supplied by `create`
|
|
firstName: null,
|
|
lastName: null,
|
|
|
|
fullName: function(key, value, oldValue) {
|
|
// getter
|
|
if (arguments.length === 1) {
|
|
var firstName = this.get('firstName');
|
|
var lastName = this.get('lastName');
|
|
|
|
return firstName + ' ' + lastName;
|
|
|
|
// setter
|
|
} else {
|
|
var name = value.split(' ');
|
|
|
|
this.set('firstName', name[0]);
|
|
this.set('lastName', name[1]);
|
|
|
|
return value;
|
|
}
|
|
}.property('firstName', 'lastName')
|
|
});
|
|
|
|
var person = Person.create();
|
|
|
|
person.set('fullName', 'Peter Wagenet');
|
|
person.get('firstName'); // 'Peter'
|
|
person.get('lastName'); // 'Wagenet'
|
|
```
|
|
|
|
@class ComputedProperty
|
|
@namespace Ember
|
|
@extends Ember.Descriptor
|
|
@constructor
|
|
*/
|
|
function ComputedProperty(func, opts) {
|
|
func.__ember_arity__ = func.length;
|
|
this.func = func;
|
|
|
|
this._dependentKeys = undefined;
|
|
this._suspended = undefined;
|
|
this._meta = undefined;
|
|
|
|
Ember.deprecate("Passing opts.cacheable to the CP constructor is deprecated. Invoke `volatile()` on the CP instead.", !opts || !opts.hasOwnProperty('cacheable'));
|
|
this._cacheable = (opts && opts.cacheable !== undefined) ? opts.cacheable : true; // TODO: Set always to `true` once this deprecation is gone.
|
|
this._dependentKeys = opts && opts.dependentKeys;
|
|
Ember.deprecate("Passing opts.readOnly to the CP constructor is deprecated. All CPs are writable by default. Yo can invoke `readOnly()` on the CP to change this.", !opts || !opts.hasOwnProperty('readOnly'));
|
|
this._readOnly = opts && (opts.readOnly !== undefined || !!opts.readOnly) || false; // TODO: Set always to `false` once this deprecation is gone.
|
|
}
|
|
|
|
ComputedProperty.prototype = new Descriptor();
|
|
|
|
var ComputedPropertyPrototype = ComputedProperty.prototype;
|
|
|
|
/**
|
|
Properties are cacheable by default. Computed property will automatically
|
|
cache the return value of your function until one of the dependent keys changes.
|
|
|
|
Call `volatile()` to set it into non-cached mode. When in this mode
|
|
the computed property will not automatically cache the return value.
|
|
|
|
However, if a property is properly observable, there is no reason to disable
|
|
caching.
|
|
|
|
@method cacheable
|
|
@param {Boolean} aFlag optional set to `false` to disable caching
|
|
@return {Ember.ComputedProperty} this
|
|
@chainable
|
|
@deprecated All computed properties are cacheble by default. Use `volatile()` instead to opt-out to caching.
|
|
*/
|
|
ComputedPropertyPrototype.cacheable = function(aFlag) {
|
|
Ember.deprecate('ComputedProperty.cacheable() is deprecated. All computed properties are cacheable by default.');
|
|
this._cacheable = aFlag !== false;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
Call on a computed property to set it into non-cached mode. When in this
|
|
mode the computed property will not automatically cache the return value.
|
|
|
|
```javascript
|
|
var outsideService = Ember.Object.extend({
|
|
value: function() {
|
|
return OutsideService.getValue();
|
|
}.property().volatile()
|
|
}).create();
|
|
```
|
|
|
|
@method volatile
|
|
@return {Ember.ComputedProperty} this
|
|
@chainable
|
|
*/
|
|
ComputedPropertyPrototype["volatile"] = function() {
|
|
this._cacheable = false;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
Call on a computed property to set it into read-only mode. When in this
|
|
mode the computed property will throw an error when set.
|
|
|
|
```javascript
|
|
var Person = Ember.Object.extend({
|
|
guid: function() {
|
|
return 'guid-guid-guid';
|
|
}.property().readOnly()
|
|
});
|
|
|
|
var person = Person.create();
|
|
|
|
person.set('guid', 'new-guid'); // will throw an exception
|
|
```
|
|
|
|
@method readOnly
|
|
@return {Ember.ComputedProperty} this
|
|
@chainable
|
|
*/
|
|
ComputedPropertyPrototype.readOnly = function(readOnly) {
|
|
Ember.deprecate('Passing arguments to ComputedProperty.readOnly() is deprecated.', arguments.length === 0);
|
|
this._readOnly = readOnly === undefined || !!readOnly; // Force to true once this deprecation is gone
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
Sets the dependent keys on this computed property. Pass any number of
|
|
arguments containing key paths that this computed property depends on.
|
|
|
|
```javascript
|
|
var President = Ember.Object.extend({
|
|
fullName: computed(function() {
|
|
return this.get('firstName') + ' ' + this.get('lastName');
|
|
|
|
// Tell Ember that this computed property depends on firstName
|
|
// and lastName
|
|
}).property('firstName', 'lastName')
|
|
});
|
|
|
|
var president = President.create({
|
|
firstName: 'Barack',
|
|
lastName: 'Obama'
|
|
});
|
|
|
|
president.get('fullName'); // 'Barack Obama'
|
|
```
|
|
|
|
@method property
|
|
@param {String} path* zero or more property paths
|
|
@return {Ember.ComputedProperty} this
|
|
@chainable
|
|
*/
|
|
ComputedPropertyPrototype.property = function() {
|
|
var args;
|
|
|
|
var addArg = function (property) {
|
|
args.push(property);
|
|
};
|
|
|
|
args = [];
|
|
for (var i = 0, l = arguments.length; i < l; i++) {
|
|
expandProperties(arguments[i], addArg);
|
|
}
|
|
|
|
this._dependentKeys = args;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
In some cases, you may want to annotate computed properties with additional
|
|
metadata about how they function or what values they operate on. For example,
|
|
computed property functions may close over variables that are then no longer
|
|
available for introspection.
|
|
|
|
You can pass a hash of these values to a computed property like this:
|
|
|
|
```
|
|
person: function() {
|
|
var personId = this.get('personId');
|
|
return App.Person.create({ id: personId });
|
|
}.property().meta({ type: App.Person })
|
|
```
|
|
|
|
The hash that you pass to the `meta()` function will be saved on the
|
|
computed property descriptor under the `_meta` key. Ember runtime
|
|
exposes a public API for retrieving these values from classes,
|
|
via the `metaForProperty()` function.
|
|
|
|
@method meta
|
|
@param {Hash} meta
|
|
@chainable
|
|
*/
|
|
|
|
ComputedPropertyPrototype.meta = function(meta) {
|
|
if (arguments.length === 0) {
|
|
return this._meta || {};
|
|
} else {
|
|
this._meta = meta;
|
|
return this;
|
|
}
|
|
};
|
|
|
|
/* impl descriptor API */
|
|
ComputedPropertyPrototype.didChange = function(obj, keyName) {
|
|
// _suspended is set via a CP.set to ensure we don't clear
|
|
// the cached value set by the setter
|
|
if (this._cacheable && this._suspended !== obj) {
|
|
var meta = metaFor(obj);
|
|
if (meta.cache[keyName] !== undefined) {
|
|
meta.cache[keyName] = undefined;
|
|
removeDependentKeys(this, obj, keyName, meta);
|
|
}
|
|
}
|
|
};
|
|
|
|
function finishChains(chainNodes)
|
|
{
|
|
for (var i=0, l=chainNodes.length; i<l; i++) {
|
|
chainNodes[i].didChange(null);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Access the value of the function backing the computed property.
|
|
If this property has already been cached, return the cached result.
|
|
Otherwise, call the function passing the property name as an argument.
|
|
|
|
```javascript
|
|
var Person = Ember.Object.extend({
|
|
fullName: function(keyName) {
|
|
// the keyName parameter is 'fullName' in this case.
|
|
return this.get('firstName') + ' ' + this.get('lastName');
|
|
}.property('firstName', 'lastName')
|
|
});
|
|
|
|
|
|
var tom = Person.create({
|
|
firstName: 'Tom',
|
|
lastName: 'Dale'
|
|
});
|
|
|
|
tom.get('fullName') // 'Tom Dale'
|
|
```
|
|
|
|
@method get
|
|
@param {String} keyName The key being accessed.
|
|
@return {Object} The return value of the function backing the CP.
|
|
*/
|
|
ComputedPropertyPrototype.get = function(obj, keyName) {
|
|
var ret, cache, meta, chainNodes;
|
|
if (this._cacheable) {
|
|
meta = metaFor(obj);
|
|
cache = meta.cache;
|
|
|
|
var result = cache[keyName];
|
|
|
|
if (result === UNDEFINED) {
|
|
return undefined;
|
|
} else if (result !== undefined) {
|
|
return result;
|
|
}
|
|
|
|
ret = this.func.call(obj, keyName);
|
|
if (ret === undefined) {
|
|
cache[keyName] = UNDEFINED;
|
|
} else {
|
|
cache[keyName] = ret;
|
|
}
|
|
|
|
chainNodes = meta.chainWatchers && meta.chainWatchers[keyName];
|
|
if (chainNodes) {
|
|
finishChains(chainNodes);
|
|
}
|
|
addDependentKeys(this, obj, keyName, meta);
|
|
} else {
|
|
ret = this.func.call(obj, keyName);
|
|
}
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
Set the value of a computed property. If the function that backs your
|
|
computed property does not accept arguments then the default action for
|
|
setting would be to define the property on the current object, and set
|
|
the value of the property to the value being set.
|
|
|
|
Generally speaking if you intend for your computed property to be set
|
|
your backing function should accept either two or three arguments.
|
|
|
|
```javascript
|
|
var Person = Ember.Object.extend({
|
|
// these will be supplied by `create`
|
|
firstName: null,
|
|
lastName: null,
|
|
|
|
fullName: function(key, value, oldValue) {
|
|
// getter
|
|
if (arguments.length === 1) {
|
|
var firstName = this.get('firstName');
|
|
var lastName = this.get('lastName');
|
|
|
|
return firstName + ' ' + lastName;
|
|
|
|
// setter
|
|
} else {
|
|
var name = value.split(' ');
|
|
|
|
this.set('firstName', name[0]);
|
|
this.set('lastName', name[1]);
|
|
|
|
return value;
|
|
}
|
|
}.property('firstName', 'lastName')
|
|
});
|
|
|
|
var person = Person.create();
|
|
|
|
person.set('fullName', 'Peter Wagenet');
|
|
person.get('firstName'); // 'Peter'
|
|
person.get('lastName'); // 'Wagenet'
|
|
```
|
|
|
|
@method set
|
|
@param {String} keyName The key being accessed.
|
|
@param {Object} newValue The new value being assigned.
|
|
@param {String} oldValue The old value being replaced.
|
|
@return {Object} The return value of the function backing the CP.
|
|
*/
|
|
ComputedPropertyPrototype.set = function computedPropertySetWithSuspend(obj, keyName, value) {
|
|
var oldSuspended = this._suspended;
|
|
|
|
this._suspended = obj;
|
|
|
|
try {
|
|
this._set(obj, keyName, value);
|
|
} finally {
|
|
this._suspended = oldSuspended;
|
|
}
|
|
};
|
|
|
|
ComputedPropertyPrototype._set = function computedPropertySet(obj, keyName, value) {
|
|
var cacheable = this._cacheable;
|
|
var func = this.func;
|
|
var meta = metaFor(obj, cacheable);
|
|
var cache = meta.cache;
|
|
var hadCachedValue = false;
|
|
|
|
var funcArgLength, cachedValue, ret;
|
|
|
|
if (this._readOnly) {
|
|
throw new EmberError('Cannot set read-only property "' + keyName + '" on object: ' + inspect(obj));
|
|
}
|
|
|
|
if (cacheable && cache[keyName] !== undefined) {
|
|
if(cache[keyName] !== UNDEFINED) {
|
|
cachedValue = cache[keyName];
|
|
}
|
|
|
|
hadCachedValue = true;
|
|
}
|
|
|
|
// Check if the CP has been wrapped. If it has, use the
|
|
// length from the wrapped function.
|
|
|
|
funcArgLength = func.wrappedFunction ? func.wrappedFunction.__ember_arity__ : func.__ember_arity__;
|
|
|
|
// For backwards-compatibility with computed properties
|
|
// that check for arguments.length === 2 to determine if
|
|
// they are being get or set, only pass the old cached
|
|
// value if the computed property opts into a third
|
|
// argument.
|
|
if (funcArgLength === 3) {
|
|
ret = func.call(obj, keyName, value, cachedValue);
|
|
} else if (funcArgLength === 2) {
|
|
ret = func.call(obj, keyName, value);
|
|
} else {
|
|
defineProperty(obj, keyName, null, cachedValue);
|
|
set(obj, keyName, value);
|
|
return;
|
|
}
|
|
|
|
if (hadCachedValue && cachedValue === ret) { return; }
|
|
|
|
var watched = meta.watching[keyName];
|
|
if (watched) {
|
|
propertyWillChange(obj, keyName);
|
|
}
|
|
|
|
if (hadCachedValue) {
|
|
cache[keyName] = undefined;
|
|
}
|
|
|
|
if (cacheable) {
|
|
if (!hadCachedValue) {
|
|
addDependentKeys(this, obj, keyName, meta);
|
|
}
|
|
if (ret === undefined) {
|
|
cache[keyName] = UNDEFINED;
|
|
} else {
|
|
cache[keyName] = ret;
|
|
}
|
|
}
|
|
|
|
if (watched) {
|
|
propertyDidChange(obj, keyName);
|
|
}
|
|
|
|
return ret;
|
|
};
|
|
|
|
/* called before property is overridden */
|
|
ComputedPropertyPrototype.teardown = function(obj, keyName) {
|
|
var meta = metaFor(obj);
|
|
|
|
if (keyName in meta.cache) {
|
|
removeDependentKeys(this, obj, keyName, meta);
|
|
}
|
|
|
|
if (this._cacheable) { delete meta.cache[keyName]; }
|
|
|
|
return null; // no value to restore
|
|
};
|
|
|
|
|
|
/**
|
|
This helper returns a new property descriptor that wraps the passed
|
|
computed property function. You can use this helper to define properties
|
|
with mixins or via `Ember.defineProperty()`.
|
|
|
|
The function you pass will be used to both get and set property values.
|
|
The function should accept two parameters, key and value. If value is not
|
|
undefined you should set the value first. In either case return the
|
|
current value of the property.
|
|
|
|
A computed property defined in this way might look like this:
|
|
|
|
```js
|
|
var Person = Ember.Object.extend({
|
|
firstName: 'Betty',
|
|
lastName: 'Jones',
|
|
|
|
fullName: Ember.computed('firstName', 'lastName', function(key, value) {
|
|
return this.get('firstName') + ' ' + this.get('lastName');
|
|
})
|
|
});
|
|
|
|
var client = Person.create();
|
|
|
|
client.get('fullName'); // 'Betty Jones'
|
|
|
|
client.set('lastName', 'Fuller');
|
|
client.get('fullName'); // 'Betty Fuller'
|
|
```
|
|
|
|
_Note: This is the preferred way to define computed properties when writing third-party
|
|
libraries that depend on or use Ember, since there is no guarantee that the user
|
|
will have prototype extensions enabled._
|
|
|
|
You might use this method if you disabled
|
|
[Prototype Extensions](http://emberjs.com/guides/configuring-ember/disabling-prototype-extensions/).
|
|
The alternative syntax might look like this
|
|
(if prototype extensions are enabled, which is the default behavior):
|
|
|
|
```js
|
|
fullName: function () {
|
|
return this.get('firstName') + ' ' + this.get('lastName');
|
|
}.property('firstName', 'lastName')
|
|
```
|
|
|
|
@method computed
|
|
@for Ember
|
|
@param {String} [dependentKeys*] Optional dependent keys that trigger this computed property.
|
|
@param {Function} func The computed property function.
|
|
@return {Ember.ComputedProperty} property descriptor instance
|
|
*/
|
|
function computed(func) {
|
|
var args;
|
|
|
|
if (arguments.length > 1) {
|
|
args = a_slice.call(arguments);
|
|
func = args.pop();
|
|
}
|
|
|
|
if (typeof func !== "function") {
|
|
throw new EmberError("Computed Property declared without a property function");
|
|
}
|
|
|
|
var cp = new ComputedProperty(func);
|
|
|
|
if (args) {
|
|
cp.property.apply(cp, args);
|
|
}
|
|
|
|
return cp;
|
|
}
|
|
|
|
/**
|
|
Returns the cached value for a property, if one exists.
|
|
This can be useful for peeking at the value of a computed
|
|
property that is generated lazily, without accidentally causing
|
|
it to be created.
|
|
|
|
@method cacheFor
|
|
@for Ember
|
|
@param {Object} obj the object whose property you want to check
|
|
@param {String} key the name of the property whose cached value you want
|
|
to return
|
|
@return {Object} the cached value
|
|
*/
|
|
function cacheFor(obj, key) {
|
|
var meta = obj['__ember_meta__'];
|
|
var cache = meta && meta.cache;
|
|
var ret = cache && cache[key];
|
|
|
|
if (ret === UNDEFINED) {
|
|
return undefined;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
cacheFor.set = function(cache, key, value) {
|
|
if (value === undefined) {
|
|
cache[key] = UNDEFINED;
|
|
} else {
|
|
cache[key] = value;
|
|
}
|
|
};
|
|
|
|
cacheFor.get = function(cache, key) {
|
|
var ret = cache[key];
|
|
if (ret === UNDEFINED) {
|
|
return undefined;
|
|
}
|
|
return ret;
|
|
};
|
|
|
|
cacheFor.remove = function(cache, key) {
|
|
cache[key] = undefined;
|
|
};
|
|
|
|
__exports__.ComputedProperty = ComputedProperty;
|
|
__exports__.computed = computed;
|
|
__exports__.cacheFor = cacheFor;
|
|
});
|
|
enifed("ember-metal/computed_macros",
|
|
["ember-metal/core","ember-metal/property_get","ember-metal/property_set","ember-metal/computed","ember-metal/is_empty","ember-metal/is_none","ember-metal/alias"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
var get = __dependency2__.get;
|
|
var set = __dependency3__.set;
|
|
var computed = __dependency4__.computed;
|
|
var isEmpty = __dependency5__["default"];
|
|
var isNone = __dependency6__["default"];
|
|
var alias = __dependency7__["default"];
|
|
|
|
/**
|
|
@module ember-metal
|
|
*/
|
|
|
|
var a_slice = [].slice;
|
|
|
|
function getProperties(self, propertyNames) {
|
|
var ret = {};
|
|
for(var i = 0; i < propertyNames.length; i++) {
|
|
ret[propertyNames[i]] = get(self, propertyNames[i]);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
function registerComputed(name, macro) {
|
|
computed[name] = function(dependentKey) {
|
|
var args = a_slice.call(arguments);
|
|
return computed(dependentKey, function() {
|
|
return macro.apply(this, args);
|
|
});
|
|
};
|
|
}
|
|
|
|
function registerComputedWithProperties(name, macro) {
|
|
computed[name] = function() {
|
|
var properties = a_slice.call(arguments);
|
|
|
|
var computedFunc = computed(function() {
|
|
return macro.apply(this, [getProperties(this, properties)]);
|
|
});
|
|
|
|
return computedFunc.property.apply(computedFunc, properties);
|
|
};
|
|
}
|
|
|
|
/**
|
|
A computed property that returns true if the value of the dependent
|
|
property is null, an empty string, empty array, or empty function.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
var ToDoList = Ember.Object.extend({
|
|
isDone: Ember.computed.empty('todos')
|
|
});
|
|
|
|
var todoList = ToDoList.create({
|
|
todos: ['Unit Test', 'Documentation', 'Release']
|
|
});
|
|
|
|
todoList.get('isDone'); // false
|
|
todoList.get('todos').clear();
|
|
todoList.get('isDone'); // true
|
|
```
|
|
|
|
@since 1.6.0
|
|
@method computed.empty
|
|
@for Ember
|
|
@param {String} dependentKey
|
|
@return {Ember.ComputedProperty} computed property which negate
|
|
the original value for property
|
|
*/
|
|
computed.empty = function (dependentKey) {
|
|
return computed(dependentKey + '.length', function () {
|
|
return isEmpty(get(this, dependentKey));
|
|
});
|
|
};
|
|
|
|
/**
|
|
A computed property that returns true if the value of the dependent
|
|
property is NOT null, an empty string, empty array, or empty function.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
var Hamster = Ember.Object.extend({
|
|
hasStuff: Ember.computed.notEmpty('backpack')
|
|
});
|
|
|
|
var hamster = Hamster.create({ backpack: ['Food', 'Sleeping Bag', 'Tent'] });
|
|
|
|
hamster.get('hasStuff'); // true
|
|
hamster.get('backpack').clear(); // []
|
|
hamster.get('hasStuff'); // false
|
|
```
|
|
|
|
@method computed.notEmpty
|
|
@for Ember
|
|
@param {String} dependentKey
|
|
@return {Ember.ComputedProperty} computed property which returns true if
|
|
original value for property is not empty.
|
|
*/
|
|
computed.notEmpty = function(dependentKey) {
|
|
return computed(dependentKey + '.length', function () {
|
|
return !isEmpty(get(this, dependentKey));
|
|
});
|
|
};
|
|
|
|
/**
|
|
A computed property that returns true if the value of the dependent
|
|
property is null or undefined. This avoids errors from JSLint complaining
|
|
about use of ==, which can be technically confusing.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
var Hamster = Ember.Object.extend({
|
|
isHungry: Ember.computed.none('food')
|
|
});
|
|
|
|
var hamster = Hamster.create();
|
|
|
|
hamster.get('isHungry'); // true
|
|
hamster.set('food', 'Banana');
|
|
hamster.get('isHungry'); // false
|
|
hamster.set('food', null);
|
|
hamster.get('isHungry'); // true
|
|
```
|
|
|
|
@method computed.none
|
|
@for Ember
|
|
@param {String} dependentKey
|
|
@return {Ember.ComputedProperty} computed property which
|
|
returns true if original value for property is null or undefined.
|
|
*/
|
|
registerComputed('none', function(dependentKey) {
|
|
return isNone(get(this, dependentKey));
|
|
});
|
|
|
|
/**
|
|
A computed property that returns the inverse boolean value
|
|
of the original value for the dependent property.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
var User = Ember.Object.extend({
|
|
isAnonymous: Ember.computed.not('loggedIn')
|
|
});
|
|
|
|
var user = User.create({loggedIn: false});
|
|
|
|
user.get('isAnonymous'); // true
|
|
user.set('loggedIn', true);
|
|
user.get('isAnonymous'); // false
|
|
```
|
|
|
|
@method computed.not
|
|
@for Ember
|
|
@param {String} dependentKey
|
|
@return {Ember.ComputedProperty} computed property which returns
|
|
inverse of the original value for property
|
|
*/
|
|
registerComputed('not', function(dependentKey) {
|
|
return !get(this, dependentKey);
|
|
});
|
|
|
|
/**
|
|
A computed property that converts the provided dependent property
|
|
into a boolean value.
|
|
|
|
```javascript
|
|
var Hamster = Ember.Object.extend({
|
|
hasBananas: Ember.computed.bool('numBananas')
|
|
});
|
|
|
|
var hamster = Hamster.create();
|
|
|
|
hamster.get('hasBananas'); // false
|
|
hamster.set('numBananas', 0);
|
|
hamster.get('hasBananas'); // false
|
|
hamster.set('numBananas', 1);
|
|
hamster.get('hasBananas'); // true
|
|
hamster.set('numBananas', null);
|
|
hamster.get('hasBananas'); // false
|
|
```
|
|
|
|
@method computed.bool
|
|
@for Ember
|
|
@param {String} dependentKey
|
|
@return {Ember.ComputedProperty} computed property which converts
|
|
to boolean the original value for property
|
|
*/
|
|
registerComputed('bool', function(dependentKey) {
|
|
return !!get(this, dependentKey);
|
|
});
|
|
|
|
/**
|
|
A computed property which matches the original value for the
|
|
dependent property against a given RegExp, returning `true`
|
|
if they values matches the RegExp and `false` if it does not.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
var User = Ember.Object.extend({
|
|
hasValidEmail: Ember.computed.match('email', /^.+@.+\..+$/)
|
|
});
|
|
|
|
var user = User.create({loggedIn: false});
|
|
|
|
user.get('hasValidEmail'); // false
|
|
user.set('email', '');
|
|
user.get('hasValidEmail'); // false
|
|
user.set('email', 'ember_hamster@example.com');
|
|
user.get('hasValidEmail'); // true
|
|
```
|
|
|
|
@method computed.match
|
|
@for Ember
|
|
@param {String} dependentKey
|
|
@param {RegExp} regexp
|
|
@return {Ember.ComputedProperty} computed property which match
|
|
the original value for property against a given RegExp
|
|
*/
|
|
registerComputed('match', function(dependentKey, regexp) {
|
|
var value = get(this, dependentKey);
|
|
return typeof value === 'string' ? regexp.test(value) : false;
|
|
});
|
|
|
|
/**
|
|
A computed property that returns true if the provided dependent property
|
|
is equal to the given value.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
var Hamster = Ember.Object.extend({
|
|
napTime: Ember.computed.equal('state', 'sleepy')
|
|
});
|
|
|
|
var hamster = Hamster.create();
|
|
|
|
hamster.get('napTime'); // false
|
|
hamster.set('state', 'sleepy');
|
|
hamster.get('napTime'); // true
|
|
hamster.set('state', 'hungry');
|
|
hamster.get('napTime'); // false
|
|
```
|
|
|
|
@method computed.equal
|
|
@for Ember
|
|
@param {String} dependentKey
|
|
@param {String|Number|Object} value
|
|
@return {Ember.ComputedProperty} computed property which returns true if
|
|
the original value for property is equal to the given value.
|
|
*/
|
|
registerComputed('equal', function(dependentKey, value) {
|
|
return get(this, dependentKey) === value;
|
|
});
|
|
|
|
/**
|
|
A computed property that returns true if the provided dependent property
|
|
is greater than the provided value.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
var Hamster = Ember.Object.extend({
|
|
hasTooManyBananas: Ember.computed.gt('numBananas', 10)
|
|
});
|
|
|
|
var hamster = Hamster.create();
|
|
|
|
hamster.get('hasTooManyBananas'); // false
|
|
hamster.set('numBananas', 3);
|
|
hamster.get('hasTooManyBananas'); // false
|
|
hamster.set('numBananas', 11);
|
|
hamster.get('hasTooManyBananas'); // true
|
|
```
|
|
|
|
@method computed.gt
|
|
@for Ember
|
|
@param {String} dependentKey
|
|
@param {Number} value
|
|
@return {Ember.ComputedProperty} computed property which returns true if
|
|
the original value for property is greater than given value.
|
|
*/
|
|
registerComputed('gt', function(dependentKey, value) {
|
|
return get(this, dependentKey) > value;
|
|
});
|
|
|
|
/**
|
|
A computed property that returns true if the provided dependent property
|
|
is greater than or equal to the provided value.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
var Hamster = Ember.Object.extend({
|
|
hasTooManyBananas: Ember.computed.gte('numBananas', 10)
|
|
});
|
|
|
|
var hamster = Hamster.create();
|
|
|
|
hamster.get('hasTooManyBananas'); // false
|
|
hamster.set('numBananas', 3);
|
|
hamster.get('hasTooManyBananas'); // false
|
|
hamster.set('numBananas', 10);
|
|
hamster.get('hasTooManyBananas'); // true
|
|
```
|
|
|
|
@method computed.gte
|
|
@for Ember
|
|
@param {String} dependentKey
|
|
@param {Number} value
|
|
@return {Ember.ComputedProperty} computed property which returns true if
|
|
the original value for property is greater or equal then given value.
|
|
*/
|
|
registerComputed('gte', function(dependentKey, value) {
|
|
return get(this, dependentKey) >= value;
|
|
});
|
|
|
|
/**
|
|
A computed property that returns true if the provided dependent property
|
|
is less than the provided value.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
var Hamster = Ember.Object.extend({
|
|
needsMoreBananas: Ember.computed.lt('numBananas', 3)
|
|
});
|
|
|
|
var hamster = Hamster.create();
|
|
|
|
hamster.get('needsMoreBananas'); // true
|
|
hamster.set('numBananas', 3);
|
|
hamster.get('needsMoreBananas'); // false
|
|
hamster.set('numBananas', 2);
|
|
hamster.get('needsMoreBananas'); // true
|
|
```
|
|
|
|
@method computed.lt
|
|
@for Ember
|
|
@param {String} dependentKey
|
|
@param {Number} value
|
|
@return {Ember.ComputedProperty} computed property which returns true if
|
|
the original value for property is less then given value.
|
|
*/
|
|
registerComputed('lt', function(dependentKey, value) {
|
|
return get(this, dependentKey) < value;
|
|
});
|
|
|
|
/**
|
|
A computed property that returns true if the provided dependent property
|
|
is less than or equal to the provided value.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
var Hamster = Ember.Object.extend({
|
|
needsMoreBananas: Ember.computed.lte('numBananas', 3)
|
|
});
|
|
|
|
var hamster = Hamster.create();
|
|
|
|
hamster.get('needsMoreBananas'); // true
|
|
hamster.set('numBananas', 5);
|
|
hamster.get('needsMoreBananas'); // false
|
|
hamster.set('numBananas', 3);
|
|
hamster.get('needsMoreBananas'); // true
|
|
```
|
|
|
|
@method computed.lte
|
|
@for Ember
|
|
@param {String} dependentKey
|
|
@param {Number} value
|
|
@return {Ember.ComputedProperty} computed property which returns true if
|
|
the original value for property is less or equal than given value.
|
|
*/
|
|
registerComputed('lte', function(dependentKey, value) {
|
|
return get(this, dependentKey) <= value;
|
|
});
|
|
|
|
/**
|
|
A computed property that performs a logical `and` on the
|
|
original values for the provided dependent properties.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
var Hamster = Ember.Object.extend({
|
|
readyForCamp: Ember.computed.and('hasTent', 'hasBackpack')
|
|
});
|
|
|
|
var hamster = Hamster.create();
|
|
|
|
hamster.get('readyForCamp'); // false
|
|
hamster.set('hasTent', true);
|
|
hamster.get('readyForCamp'); // false
|
|
hamster.set('hasBackpack', true);
|
|
hamster.get('readyForCamp'); // true
|
|
```
|
|
|
|
@method computed.and
|
|
@for Ember
|
|
@param {String} dependentKey*
|
|
@return {Ember.ComputedProperty} computed property which performs
|
|
a logical `and` on the values of all the original values for properties.
|
|
*/
|
|
registerComputedWithProperties('and', function(properties) {
|
|
for (var key in properties) {
|
|
if (properties.hasOwnProperty(key) && !properties[key]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
|
|
/**
|
|
A computed property which performs a logical `or` on the
|
|
original values for the provided dependent properties.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
var Hamster = Ember.Object.extend({
|
|
readyForRain: Ember.computed.or('hasJacket', 'hasUmbrella')
|
|
});
|
|
|
|
var hamster = Hamster.create();
|
|
|
|
hamster.get('readyForRain'); // false
|
|
hamster.set('hasJacket', true);
|
|
hamster.get('readyForRain'); // true
|
|
```
|
|
|
|
@method computed.or
|
|
@for Ember
|
|
@param {String} dependentKey*
|
|
@return {Ember.ComputedProperty} computed property which performs
|
|
a logical `or` on the values of all the original values for properties.
|
|
*/
|
|
registerComputedWithProperties('or', function(properties) {
|
|
for (var key in properties) {
|
|
if (properties.hasOwnProperty(key) && properties[key]) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
});
|
|
|
|
/**
|
|
A computed property that returns the first truthy value
|
|
from a list of dependent properties.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
var Hamster = Ember.Object.extend({
|
|
hasClothes: Ember.computed.any('hat', 'shirt')
|
|
});
|
|
|
|
var hamster = Hamster.create();
|
|
|
|
hamster.get('hasClothes'); // null
|
|
hamster.set('shirt', 'Hawaiian Shirt');
|
|
hamster.get('hasClothes'); // 'Hawaiian Shirt'
|
|
```
|
|
|
|
@method computed.any
|
|
@for Ember
|
|
@param {String} dependentKey*
|
|
@return {Ember.ComputedProperty} computed property which returns
|
|
the first truthy value of given list of properties.
|
|
*/
|
|
registerComputedWithProperties('any', function(properties) {
|
|
for (var key in properties) {
|
|
if (properties.hasOwnProperty(key) && properties[key]) {
|
|
return properties[key];
|
|
}
|
|
}
|
|
return null;
|
|
});
|
|
|
|
/**
|
|
A computed property that returns the array of values
|
|
for the provided dependent properties.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
var Hamster = Ember.Object.extend({
|
|
clothes: Ember.computed.collect('hat', 'shirt')
|
|
});
|
|
|
|
var hamster = Hamster.create();
|
|
|
|
hamster.get('clothes'); // [null, null]
|
|
hamster.set('hat', 'Camp Hat');
|
|
hamster.set('shirt', 'Camp Shirt');
|
|
hamster.get('clothes'); // ['Camp Hat', 'Camp Shirt']
|
|
```
|
|
|
|
@method computed.collect
|
|
@for Ember
|
|
@param {String} dependentKey*
|
|
@return {Ember.ComputedProperty} computed property which maps
|
|
values of all passed in properties to an array.
|
|
*/
|
|
registerComputedWithProperties('collect', function(properties) {
|
|
var res = Ember.A();
|
|
for (var key in properties) {
|
|
if (properties.hasOwnProperty(key)) {
|
|
if (isNone(properties[key])) {
|
|
res.push(null);
|
|
} else {
|
|
res.push(properties[key]);
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
});
|
|
|
|
/**
|
|
Creates a new property that is an alias for another property
|
|
on an object. Calls to `get` or `set` this property behave as
|
|
though they were called on the original property.
|
|
|
|
```javascript
|
|
var Person = Ember.Object.extend({
|
|
name: 'Alex Matchneer',
|
|
nomen: Ember.computed.alias('name')
|
|
});
|
|
|
|
var alex = Person.create();
|
|
|
|
alex.get('nomen'); // 'Alex Matchneer'
|
|
alex.get('name'); // 'Alex Matchneer'
|
|
|
|
alex.set('nomen', '@machty');
|
|
alex.get('name'); // '@machty'
|
|
```
|
|
|
|
@method computed.alias
|
|
@for Ember
|
|
@param {String} dependentKey
|
|
@return {Ember.ComputedProperty} computed property which creates an
|
|
alias to the original value for property.
|
|
*/
|
|
computed.alias = alias;
|
|
|
|
/**
|
|
Where `computed.alias` aliases `get` and `set`, and allows for bidirectional
|
|
data flow, `computed.oneWay` only provides an aliased `get`. The `set` will
|
|
not mutate the upstream property, rather causes the current property to
|
|
become the value set. This causes the downstream property to permanently
|
|
diverge from the upstream property.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
var User = Ember.Object.extend({
|
|
firstName: null,
|
|
lastName: null,
|
|
nickName: Ember.computed.oneWay('firstName')
|
|
});
|
|
|
|
var teddy = User.create({
|
|
firstName: 'Teddy',
|
|
lastName: 'Zeenny'
|
|
});
|
|
|
|
teddy.get('nickName'); // 'Teddy'
|
|
teddy.set('nickName', 'TeddyBear'); // 'TeddyBear'
|
|
teddy.get('firstName'); // 'Teddy'
|
|
```
|
|
|
|
@method computed.oneWay
|
|
@for Ember
|
|
@param {String} dependentKey
|
|
@return {Ember.ComputedProperty} computed property which creates a
|
|
one way computed property to the original value for property.
|
|
*/
|
|
computed.oneWay = function(dependentKey) {
|
|
return alias(dependentKey).oneWay();
|
|
};
|
|
|
|
/**
|
|
This is a more semantically meaningful alias of `computed.oneWay`,
|
|
whose name is somewhat ambiguous as to which direction the data flows.
|
|
|
|
@method computed.reads
|
|
@for Ember
|
|
@param {String} dependentKey
|
|
@return {Ember.ComputedProperty} computed property which creates a
|
|
one way computed property to the original value for property.
|
|
*/
|
|
computed.reads = computed.oneWay;
|
|
|
|
/**
|
|
Where `computed.oneWay` provides oneWay bindings, `computed.readOnly` provides
|
|
a readOnly one way binding. Very often when using `computed.oneWay` one does
|
|
not also want changes to propagate back up, as they will replace the value.
|
|
|
|
This prevents the reverse flow, and also throws an exception when it occurs.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
var User = Ember.Object.extend({
|
|
firstName: null,
|
|
lastName: null,
|
|
nickName: Ember.computed.readOnly('firstName')
|
|
});
|
|
|
|
var teddy = User.create({
|
|
firstName: 'Teddy',
|
|
lastName: 'Zeenny'
|
|
});
|
|
|
|
teddy.get('nickName'); // 'Teddy'
|
|
teddy.set('nickName', 'TeddyBear'); // throws Exception
|
|
// throw new Ember.Error('Cannot Set: nickName on: <User:ember27288>' );`
|
|
teddy.get('firstName'); // 'Teddy'
|
|
```
|
|
|
|
@method computed.readOnly
|
|
@for Ember
|
|
@param {String} dependentKey
|
|
@return {Ember.ComputedProperty} computed property which creates a
|
|
one way computed property to the original value for property.
|
|
@since 1.5.0
|
|
*/
|
|
computed.readOnly = function(dependentKey) {
|
|
return alias(dependentKey).readOnly();
|
|
};
|
|
/**
|
|
A computed property that acts like a standard getter and setter,
|
|
but returns the value at the provided `defaultPath` if the
|
|
property itself has not been set to a value
|
|
|
|
Example
|
|
|
|
```javascript
|
|
var Hamster = Ember.Object.extend({
|
|
wishList: Ember.computed.defaultTo('favoriteFood')
|
|
});
|
|
|
|
var hamster = Hamster.create({ favoriteFood: 'Banana' });
|
|
|
|
hamster.get('wishList'); // 'Banana'
|
|
hamster.set('wishList', 'More Unit Tests');
|
|
hamster.get('wishList'); // 'More Unit Tests'
|
|
hamster.get('favoriteFood'); // 'Banana'
|
|
```
|
|
|
|
@method computed.defaultTo
|
|
@for Ember
|
|
@param {String} defaultPath
|
|
@return {Ember.ComputedProperty} computed property which acts like
|
|
a standard getter and setter, but defaults to the value from `defaultPath`.
|
|
@deprecated Use `Ember.computed.oneWay` or custom CP with default instead.
|
|
*/
|
|
// ES6TODO: computed should have its own export path so you can do import {defaultTo} from computed
|
|
computed.defaultTo = function(defaultPath) {
|
|
return computed(function(key, newValue, cachedValue) {
|
|
Ember.deprecate('Usage of Ember.computed.defaultTo is deprecated, use `Ember.computed.oneWay` instead.');
|
|
|
|
if (arguments.length === 1) {
|
|
return get(this, defaultPath);
|
|
}
|
|
return newValue != null ? newValue : get(this, defaultPath);
|
|
});
|
|
};
|
|
|
|
/**
|
|
Creates a new property that is an alias for another property
|
|
on an object. Calls to `get` or `set` this property behave as
|
|
though they were called on the original property, but also
|
|
print a deprecation warning.
|
|
|
|
@method computed.deprecatingAlias
|
|
@for Ember
|
|
@param {String} dependentKey
|
|
@return {Ember.ComputedProperty} computed property which creates an
|
|
alias with a deprecation to the original value for property.
|
|
@since 1.7.0
|
|
*/
|
|
computed.deprecatingAlias = function(dependentKey) {
|
|
return computed(dependentKey, function(key, value) {
|
|
Ember.deprecate('Usage of `' + key + '` is deprecated, use `' + dependentKey + '` instead.');
|
|
|
|
if (arguments.length > 1) {
|
|
set(this, dependentKey, value);
|
|
return value;
|
|
} else {
|
|
return get(this, dependentKey);
|
|
}
|
|
});
|
|
};
|
|
});
|
|
enifed("ember-metal/core",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
/*globals Ember:true,ENV,EmberENV,MetamorphENV:true */
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-metal
|
|
*/
|
|
|
|
/**
|
|
All Ember methods and functions are defined inside of this namespace. You
|
|
generally should not add new properties to this namespace as it may be
|
|
overwritten by future versions of Ember.
|
|
|
|
You can also use the shorthand `Em` instead of `Ember`.
|
|
|
|
Ember-Runtime is a framework that provides core functions for Ember including
|
|
cross-platform functions, support for property observing and objects. Its
|
|
focus is on small size and performance. You can use this in place of or
|
|
along-side other cross-platform libraries such as jQuery.
|
|
|
|
The core Runtime framework is based on the jQuery API with a number of
|
|
performance optimizations.
|
|
|
|
@class Ember
|
|
@static
|
|
@version 1.10.1
|
|
*/
|
|
|
|
if ('undefined' === typeof Ember) {
|
|
// Create core object. Make it act like an instance of Ember.Namespace so that
|
|
// objects assigned to it are given a sane string representation.
|
|
Ember = {};
|
|
}
|
|
|
|
// Default imports, exports and lookup to the global object;
|
|
Ember.imports = Ember.imports || this;
|
|
Ember.lookup = Ember.lookup || this;
|
|
var exports = Ember.exports = Ember.exports || this;
|
|
|
|
// aliases needed to keep minifiers from removing the global context
|
|
exports.Em = exports.Ember = Ember;
|
|
|
|
// Make sure these are set whether Ember was already defined or not
|
|
|
|
Ember.isNamespace = true;
|
|
|
|
Ember.toString = function() { return "Ember"; };
|
|
|
|
|
|
/**
|
|
@property VERSION
|
|
@type String
|
|
@default '1.10.1'
|
|
@static
|
|
*/
|
|
Ember.VERSION = '1.10.1';
|
|
|
|
/**
|
|
Standard environmental variables. You can define these in a global `EmberENV`
|
|
variable before loading Ember to control various configuration settings.
|
|
|
|
For backwards compatibility with earlier versions of Ember the global `ENV`
|
|
variable will be used if `EmberENV` is not defined.
|
|
|
|
@property ENV
|
|
@type Hash
|
|
*/
|
|
|
|
if (Ember.ENV) {
|
|
// do nothing if Ember.ENV is already setup
|
|
} else if ('undefined' !== typeof EmberENV) {
|
|
Ember.ENV = EmberENV;
|
|
} else if('undefined' !== typeof ENV) {
|
|
Ember.ENV = ENV;
|
|
} else {
|
|
Ember.ENV = {};
|
|
}
|
|
|
|
Ember.config = Ember.config || {};
|
|
|
|
// We disable the RANGE API by default for performance reasons
|
|
if ('undefined' === typeof Ember.ENV.DISABLE_RANGE_API) {
|
|
Ember.ENV.DISABLE_RANGE_API = true;
|
|
}
|
|
|
|
if ("undefined" === typeof MetamorphENV) {
|
|
exports.MetamorphENV = {};
|
|
}
|
|
|
|
MetamorphENV.DISABLE_RANGE_API = Ember.ENV.DISABLE_RANGE_API;
|
|
|
|
/**
|
|
Hash of enabled Canary features. Add to this before creating your application.
|
|
|
|
You can also define `EmberENV.FEATURES` if you need to enable features flagged at runtime.
|
|
|
|
@class FEATURES
|
|
@namespace Ember
|
|
@static
|
|
@since 1.1.0
|
|
*/
|
|
|
|
Ember.FEATURES = Ember.ENV.FEATURES || {};
|
|
|
|
/**
|
|
Test that a feature is enabled. Parsed by Ember's build tools to leave
|
|
experimental features out of beta/stable builds.
|
|
|
|
You can define the following configuration options:
|
|
|
|
* `EmberENV.ENABLE_ALL_FEATURES` - force all features to be enabled.
|
|
* `EmberENV.ENABLE_OPTIONAL_FEATURES` - enable any features that have not been explicitly
|
|
enabled/disabled.
|
|
|
|
@method isEnabled
|
|
@param {String} feature
|
|
@return {Boolean}
|
|
@for Ember.FEATURES
|
|
@since 1.1.0
|
|
*/
|
|
|
|
Ember.FEATURES.isEnabled = function(feature) {
|
|
var featureValue = Ember.FEATURES[feature];
|
|
|
|
if (Ember.ENV.ENABLE_ALL_FEATURES) {
|
|
return true;
|
|
} else if (featureValue === true || featureValue === false || featureValue === undefined) {
|
|
return featureValue;
|
|
} else if (Ember.ENV.ENABLE_OPTIONAL_FEATURES) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
// ..........................................................
|
|
// BOOTSTRAP
|
|
//
|
|
|
|
/**
|
|
Determines whether Ember should enhance some built-in object prototypes to
|
|
provide a more friendly API. If enabled, a few methods will be added to
|
|
`Function`, `String`, and `Array`. `Object.prototype` will not be enhanced,
|
|
which is the one that causes most trouble for people.
|
|
|
|
In general we recommend leaving this option set to true since it rarely
|
|
conflicts with other code. If you need to turn it off however, you can
|
|
define an `EmberENV.EXTEND_PROTOTYPES` config to disable it.
|
|
|
|
@property EXTEND_PROTOTYPES
|
|
@type Boolean
|
|
@default true
|
|
@for Ember
|
|
*/
|
|
Ember.EXTEND_PROTOTYPES = Ember.ENV.EXTEND_PROTOTYPES;
|
|
|
|
if (typeof Ember.EXTEND_PROTOTYPES === 'undefined') {
|
|
Ember.EXTEND_PROTOTYPES = true;
|
|
}
|
|
|
|
/**
|
|
Determines whether Ember logs a full stack trace during deprecation warnings
|
|
|
|
@property LOG_STACKTRACE_ON_DEPRECATION
|
|
@type Boolean
|
|
@default true
|
|
*/
|
|
Ember.LOG_STACKTRACE_ON_DEPRECATION = (Ember.ENV.LOG_STACKTRACE_ON_DEPRECATION !== false);
|
|
|
|
/**
|
|
Determines whether Ember should add ECMAScript 5 Array shims to older browsers.
|
|
|
|
@property SHIM_ES5
|
|
@type Boolean
|
|
@default Ember.EXTEND_PROTOTYPES
|
|
*/
|
|
Ember.SHIM_ES5 = (Ember.ENV.SHIM_ES5 === false) ? false : Ember.EXTEND_PROTOTYPES;
|
|
|
|
/**
|
|
Determines whether Ember logs info about version of used libraries
|
|
|
|
@property LOG_VERSION
|
|
@type Boolean
|
|
@default true
|
|
*/
|
|
Ember.LOG_VERSION = (Ember.ENV.LOG_VERSION === false) ? false : true;
|
|
|
|
/**
|
|
Empty function. Useful for some operations. Always returns `this`.
|
|
|
|
@method K
|
|
@private
|
|
@return {Object}
|
|
*/
|
|
function K() { return this; }
|
|
__exports__.K = K;
|
|
Ember.K = K;
|
|
//TODO: ES6 GLOBAL TODO
|
|
|
|
// Stub out the methods defined by the ember-debug package in case it's not loaded
|
|
|
|
if ('undefined' === typeof Ember.assert) { Ember.assert = K; }
|
|
if ('undefined' === typeof Ember.warn) { Ember.warn = K; }
|
|
if ('undefined' === typeof Ember.debug) { Ember.debug = K; }
|
|
if ('undefined' === typeof Ember.runInDebug) { Ember.runInDebug = K; }
|
|
if ('undefined' === typeof Ember.deprecate) { Ember.deprecate = K; }
|
|
if ('undefined' === typeof Ember.deprecateFunc) {
|
|
Ember.deprecateFunc = function(_, func) { return func; };
|
|
}
|
|
|
|
__exports__["default"] = Ember;
|
|
});
|
|
enifed("ember-metal/dependent_keys",
|
|
["ember-metal/platform","ember-metal/watching","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
// Remove "use strict"; from transpiled module until
|
|
// https://bugs.webkit.org/show_bug.cgi?id=138038 is fixed
|
|
//
|
|
// REMOVE_USE_STRICT: true
|
|
|
|
var o_create = __dependency1__.create;
|
|
var watch = __dependency2__.watch;
|
|
var unwatch = __dependency2__.unwatch;
|
|
|
|
/**
|
|
@module ember-metal
|
|
*/
|
|
|
|
// ..........................................................
|
|
// DEPENDENT KEYS
|
|
//
|
|
|
|
// data structure:
|
|
// meta.deps = {
|
|
// 'depKey': {
|
|
// 'keyName': count,
|
|
// }
|
|
// }
|
|
|
|
/*
|
|
This function returns a map of unique dependencies for a
|
|
given object and key.
|
|
*/
|
|
function keysForDep(depsMeta, depKey) {
|
|
var keys = depsMeta[depKey];
|
|
if (!keys) {
|
|
// if there are no dependencies yet for a the given key
|
|
// create a new empty list of dependencies for the key
|
|
keys = depsMeta[depKey] = {};
|
|
} else if (!depsMeta.hasOwnProperty(depKey)) {
|
|
// otherwise if the dependency list is inherited from
|
|
// a superclass, clone the hash
|
|
keys = depsMeta[depKey] = o_create(keys);
|
|
}
|
|
return keys;
|
|
}
|
|
|
|
function metaForDeps(meta) {
|
|
return keysForDep(meta, 'deps');
|
|
}
|
|
|
|
function addDependentKeys(desc, obj, keyName, meta) {
|
|
// the descriptor has a list of dependent keys, so
|
|
// add all of its dependent keys.
|
|
var depKeys = desc._dependentKeys, depsMeta, idx, len, depKey, keys;
|
|
if (!depKeys) return;
|
|
|
|
depsMeta = metaForDeps(meta);
|
|
|
|
for(idx = 0, len = depKeys.length; idx < len; idx++) {
|
|
depKey = depKeys[idx];
|
|
// Lookup keys meta for depKey
|
|
keys = keysForDep(depsMeta, depKey);
|
|
// Increment the number of times depKey depends on keyName.
|
|
keys[keyName] = (keys[keyName] || 0) + 1;
|
|
// Watch the depKey
|
|
watch(obj, depKey, meta);
|
|
}
|
|
}
|
|
|
|
__exports__.addDependentKeys = addDependentKeys;function removeDependentKeys(desc, obj, keyName, meta) {
|
|
// the descriptor has a list of dependent keys, so
|
|
// remove all of its dependent keys.
|
|
var depKeys = desc._dependentKeys, depsMeta, idx, len, depKey, keys;
|
|
if (!depKeys) return;
|
|
|
|
depsMeta = metaForDeps(meta);
|
|
|
|
for(idx = 0, len = depKeys.length; idx < len; idx++) {
|
|
depKey = depKeys[idx];
|
|
// Lookup keys meta for depKey
|
|
keys = keysForDep(depsMeta, depKey);
|
|
// Decrement the number of times depKey depends on keyName.
|
|
keys[keyName] = (keys[keyName] || 0) - 1;
|
|
// Unwatch the depKey
|
|
unwatch(obj, depKey, meta);
|
|
}
|
|
}
|
|
|
|
__exports__.removeDependentKeys = removeDependentKeys;
|
|
});
|
|
enifed("ember-metal/deprecate_property",
|
|
["ember-metal/core","ember-metal/platform","ember-metal/properties","ember-metal/property_get","ember-metal/property_set","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember-metal
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
var hasPropertyAccessors = __dependency2__.hasPropertyAccessors;
|
|
var defineProperty = __dependency3__.defineProperty;
|
|
var get = __dependency4__.get;
|
|
var set = __dependency5__.set;
|
|
|
|
|
|
/**
|
|
Used internally to allow changing properties in a backwards compatible way, and print a helpful
|
|
deprecation warning.
|
|
|
|
@method deprecateProperty
|
|
@param {Object} object The object to add the deprecated property to.
|
|
@param {String} deprecatedKey The property to add (and print deprecation warnings upon accessing).
|
|
@param {String} newKey The property that will be aliased.
|
|
@private
|
|
@since 1.7.0
|
|
*/
|
|
|
|
function deprecateProperty(object, deprecatedKey, newKey) {
|
|
function deprecate() {
|
|
Ember.deprecate('Usage of `' + deprecatedKey + '` is deprecated, use `' + newKey + '` instead.');
|
|
}
|
|
|
|
if (hasPropertyAccessors) {
|
|
defineProperty(object, deprecatedKey, {
|
|
configurable: true,
|
|
enumerable: false,
|
|
set: function(value) { deprecate(); set(this, newKey, value); },
|
|
get: function() { deprecate(); return get(this, newKey); }
|
|
});
|
|
}
|
|
}
|
|
|
|
__exports__.deprecateProperty = deprecateProperty;
|
|
});
|
|
enifed("ember-metal/dictionary",
|
|
["ember-metal/platform","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var create = __dependency1__.create;
|
|
|
|
// the delete is meant to hint at runtimes that this object should remain in
|
|
// dictionary mode. This is clearly a runtime specific hack, but currently it
|
|
// appears worthwile in some usecases. Please note, these deletes do increase
|
|
// the cost of creation dramatically over a plain Object.create. And as this
|
|
// only makes sense for long-lived dictionaries that aren't instantiated often.
|
|
__exports__["default"] = function makeDictionary(parent) {
|
|
var dict = create(parent);
|
|
dict['_dict'] = null;
|
|
delete dict['_dict'];
|
|
return dict;
|
|
}
|
|
});
|
|
enifed("ember-metal/enumerable_utils",
|
|
["ember-metal/array","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var _filter = __dependency1__.filter;
|
|
var a_forEach = __dependency1__.forEach;
|
|
var _indexOf = __dependency1__.indexOf;
|
|
var _map = __dependency1__.map;
|
|
|
|
var splice = Array.prototype.splice;
|
|
|
|
/**
|
|
* Defines some convenience methods for working with Enumerables.
|
|
* `Ember.EnumerableUtils` uses `Ember.ArrayPolyfills` when necessary.
|
|
*
|
|
* @class EnumerableUtils
|
|
* @namespace Ember
|
|
* @static
|
|
* */
|
|
|
|
/**
|
|
* Calls the map function on the passed object with a specified callback. This
|
|
* uses `Ember.ArrayPolyfill`'s-map method when necessary.
|
|
*
|
|
* @method map
|
|
* @param {Object} obj The object that should be mapped
|
|
* @param {Function} callback The callback to execute
|
|
* @param {Object} thisArg Value to use as this when executing *callback*
|
|
*
|
|
* @return {Array} An array of mapped values.
|
|
*/
|
|
function map(obj, callback, thisArg) {
|
|
return obj.map ? obj.map(callback, thisArg) : _map.call(obj, callback, thisArg);
|
|
}
|
|
|
|
__exports__.map = map;/**
|
|
* Calls the forEach function on the passed object with a specified callback. This
|
|
* uses `Ember.ArrayPolyfill`'s-forEach method when necessary.
|
|
*
|
|
* @method forEach
|
|
* @param {Object} obj The object to call forEach on
|
|
* @param {Function} callback The callback to execute
|
|
* @param {Object} thisArg Value to use as this when executing *callback*
|
|
*
|
|
*/
|
|
function forEach(obj, callback, thisArg) {
|
|
return obj.forEach ? obj.forEach(callback, thisArg) : a_forEach.call(obj, callback, thisArg);
|
|
}
|
|
|
|
__exports__.forEach = forEach;/**
|
|
* Calls the filter function on the passed object with a specified callback. This
|
|
* uses `Ember.ArrayPolyfill`'s-filter method when necessary.
|
|
*
|
|
* @method filter
|
|
* @param {Object} obj The object to call filter on
|
|
* @param {Function} callback The callback to execute
|
|
* @param {Object} thisArg Value to use as this when executing *callback*
|
|
*
|
|
* @return {Array} An array containing the filtered values
|
|
* @since 1.4.0
|
|
*/
|
|
function filter(obj, callback, thisArg) {
|
|
return obj.filter ? obj.filter(callback, thisArg) : _filter.call(obj, callback, thisArg);
|
|
}
|
|
|
|
__exports__.filter = filter;/**
|
|
* Calls the indexOf function on the passed object with a specified callback. This
|
|
* uses `Ember.ArrayPolyfill`'s-indexOf method when necessary.
|
|
*
|
|
* @method indexOf
|
|
* @param {Object} obj The object to call indexOn on
|
|
* @param {Function} callback The callback to execute
|
|
* @param {Object} index The index to start searching from
|
|
*
|
|
*/
|
|
function indexOf(obj, element, index) {
|
|
return obj.indexOf ? obj.indexOf(element, index) : _indexOf.call(obj, element, index);
|
|
}
|
|
|
|
__exports__.indexOf = indexOf;/**
|
|
* Returns an array of indexes of the first occurrences of the passed elements
|
|
* on the passed object.
|
|
*
|
|
* ```javascript
|
|
* var array = [1, 2, 3, 4, 5];
|
|
* Ember.EnumerableUtils.indexesOf(array, [2, 5]); // [1, 4]
|
|
*
|
|
* var fubar = "Fubarr";
|
|
* Ember.EnumerableUtils.indexesOf(fubar, ['b', 'r']); // [2, 4]
|
|
* ```
|
|
*
|
|
* @method indexesOf
|
|
* @param {Object} obj The object to check for element indexes
|
|
* @param {Array} elements The elements to search for on *obj*
|
|
*
|
|
* @return {Array} An array of indexes.
|
|
*
|
|
*/
|
|
function indexesOf(obj, elements) {
|
|
return elements === undefined ? [] : map(elements, function(item) {
|
|
return indexOf(obj, item);
|
|
});
|
|
}
|
|
|
|
__exports__.indexesOf = indexesOf;/**
|
|
* Adds an object to an array. If the array already includes the object this
|
|
* method has no effect.
|
|
*
|
|
* @method addObject
|
|
* @param {Array} array The array the passed item should be added to
|
|
* @param {Object} item The item to add to the passed array
|
|
*
|
|
* @return 'undefined'
|
|
*/
|
|
function addObject(array, item) {
|
|
var index = indexOf(array, item);
|
|
if (index === -1) { array.push(item); }
|
|
}
|
|
|
|
__exports__.addObject = addObject;/**
|
|
* Removes an object from an array. If the array does not contain the passed
|
|
* object this method has no effect.
|
|
*
|
|
* @method removeObject
|
|
* @param {Array} array The array to remove the item from.
|
|
* @param {Object} item The item to remove from the passed array.
|
|
*
|
|
* @return 'undefined'
|
|
*/
|
|
function removeObject(array, item) {
|
|
var index = indexOf(array, item);
|
|
if (index !== -1) { array.splice(index, 1); }
|
|
}
|
|
|
|
__exports__.removeObject = removeObject;function _replace(array, idx, amt, objects) {
|
|
var args = [].concat(objects);
|
|
var ret = [];
|
|
// https://code.google.com/p/chromium/issues/detail?id=56588
|
|
var size = 60000;
|
|
var start = idx;
|
|
var ends = amt;
|
|
var count, chunk;
|
|
|
|
while (args.length) {
|
|
count = ends > size ? size : ends;
|
|
if (count <= 0) { count = 0; }
|
|
|
|
chunk = args.splice(0, size);
|
|
chunk = [start, count].concat(chunk);
|
|
|
|
start += size;
|
|
ends -= count;
|
|
|
|
ret = ret.concat(splice.apply(array, chunk));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
__exports__._replace = _replace;/**
|
|
* Replaces objects in an array with the passed objects.
|
|
*
|
|
* ```javascript
|
|
* var array = [1,2,3];
|
|
* Ember.EnumerableUtils.replace(array, 1, 2, [4, 5]); // [1, 4, 5]
|
|
*
|
|
* var array = [1,2,3];
|
|
* Ember.EnumerableUtils.replace(array, 1, 1, [4, 5]); // [1, 4, 5, 3]
|
|
*
|
|
* var array = [1,2,3];
|
|
* Ember.EnumerableUtils.replace(array, 10, 1, [4, 5]); // [1, 2, 3, 4, 5]
|
|
* ```
|
|
*
|
|
* @method replace
|
|
* @param {Array} array The array the objects should be inserted into.
|
|
* @param {Number} idx Starting index in the array to replace. If *idx* >=
|
|
* length, then append to the end of the array.
|
|
* @param {Number} amt Number of elements that should be removed from the array,
|
|
* starting at *idx*
|
|
* @param {Array} objects An array of zero or more objects that should be
|
|
* inserted into the array at *idx*
|
|
*
|
|
* @return {Array} The modified array.
|
|
*/
|
|
function replace(array, idx, amt, objects) {
|
|
if (array.replace) {
|
|
return array.replace(idx, amt, objects);
|
|
} else {
|
|
return _replace(array, idx, amt, objects);
|
|
}
|
|
}
|
|
|
|
__exports__.replace = replace;/**
|
|
* Calculates the intersection of two arrays. This method returns a new array
|
|
* filled with the records that the two passed arrays share with each other.
|
|
* If there is no intersection, an empty array will be returned.
|
|
*
|
|
* ```javascript
|
|
* var array1 = [1, 2, 3, 4, 5];
|
|
* var array2 = [1, 3, 5, 6, 7];
|
|
*
|
|
* Ember.EnumerableUtils.intersection(array1, array2); // [1, 3, 5]
|
|
*
|
|
* var array1 = [1, 2, 3];
|
|
* var array2 = [4, 5, 6];
|
|
*
|
|
* Ember.EnumerableUtils.intersection(array1, array2); // []
|
|
* ```
|
|
*
|
|
* @method intersection
|
|
* @param {Array} array1 The first array
|
|
* @param {Array} array2 The second array
|
|
*
|
|
* @return {Array} The intersection of the two passed arrays.
|
|
*/
|
|
function intersection(array1, array2) {
|
|
var result = [];
|
|
forEach(array1, function(element) {
|
|
if (indexOf(array2, element) >= 0) {
|
|
result.push(element);
|
|
}
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
__exports__.intersection = intersection;// TODO: this only exists to maintain the existing api, as we move forward it
|
|
// should only be part of the "global build" via some shim
|
|
__exports__["default"] = {
|
|
_replace: _replace,
|
|
addObject: addObject,
|
|
filter: filter,
|
|
forEach: forEach,
|
|
indexOf: indexOf,
|
|
indexesOf: indexesOf,
|
|
intersection: intersection,
|
|
map: map,
|
|
removeObject: removeObject,
|
|
replace: replace
|
|
};
|
|
});
|
|
enifed("ember-metal/error",
|
|
["ember-metal/platform","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var create = __dependency1__.create;
|
|
|
|
var errorProps = [
|
|
'description',
|
|
'fileName',
|
|
'lineNumber',
|
|
'message',
|
|
'name',
|
|
'number',
|
|
'stack'
|
|
];
|
|
|
|
/**
|
|
A subclass of the JavaScript Error object for use in Ember.
|
|
|
|
@class Error
|
|
@namespace Ember
|
|
@extends Error
|
|
@constructor
|
|
*/
|
|
function EmberError() {
|
|
var tmp = Error.apply(this, arguments);
|
|
|
|
// Adds a `stack` property to the given error object that will yield the
|
|
// stack trace at the time captureStackTrace was called.
|
|
// When collecting the stack trace all frames above the topmost call
|
|
// to this function, including that call, will be left out of the
|
|
// stack trace.
|
|
// This is useful because we can hide Ember implementation details
|
|
// that are not very helpful for the user.
|
|
if (Error.captureStackTrace) {
|
|
Error.captureStackTrace(this, Ember.Error);
|
|
}
|
|
// Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
|
|
for (var idx = 0; idx < errorProps.length; idx++) {
|
|
this[errorProps[idx]] = tmp[errorProps[idx]];
|
|
}
|
|
}
|
|
|
|
EmberError.prototype = create(Error.prototype);
|
|
|
|
__exports__["default"] = EmberError;
|
|
});
|
|
enifed("ember-metal/events",
|
|
["ember-metal/core","ember-metal/utils","ember-metal/platform","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
// Remove "use strict"; from transpiled module until
|
|
// https://bugs.webkit.org/show_bug.cgi?id=138038 is fixed
|
|
//
|
|
// REMOVE_USE_STRICT: true
|
|
|
|
/**
|
|
@module ember-metal
|
|
*/
|
|
var Ember = __dependency1__["default"];
|
|
var metaFor = __dependency2__.meta;
|
|
var tryFinally = __dependency2__.tryFinally;
|
|
var apply = __dependency2__.apply;
|
|
var applyStr = __dependency2__.applyStr;
|
|
var create = __dependency3__.create;
|
|
|
|
var a_slice = [].slice;
|
|
|
|
/* listener flags */
|
|
var ONCE = 1;
|
|
var SUSPENDED = 2;
|
|
|
|
|
|
/*
|
|
The event system uses a series of nested hashes to store listeners on an
|
|
object. When a listener is registered, or when an event arrives, these
|
|
hashes are consulted to determine which target and action pair to invoke.
|
|
|
|
The hashes are stored in the object's meta hash, and look like this:
|
|
|
|
// Object's meta hash
|
|
{
|
|
listeners: { // variable name: `listenerSet`
|
|
"foo:changed": [ // variable name: `actions`
|
|
target, method, flags
|
|
]
|
|
}
|
|
}
|
|
|
|
*/
|
|
|
|
function indexOf(array, target, method) {
|
|
var index = -1;
|
|
// hashes are added to the end of the event array
|
|
// so it makes sense to start searching at the end
|
|
// of the array and search in reverse
|
|
for (var i = array.length - 3 ; i >=0; i -= 3) {
|
|
if (target === array[i] && method === array[i + 1]) {
|
|
index = i; break;
|
|
}
|
|
}
|
|
return index;
|
|
}
|
|
|
|
function actionsFor(obj, eventName) {
|
|
var meta = metaFor(obj, true);
|
|
var actions;
|
|
var listeners = meta.listeners;
|
|
|
|
if (!listeners) {
|
|
listeners = meta.listeners = create(null);
|
|
listeners.__source__ = obj;
|
|
} else if (listeners.__source__ !== obj) {
|
|
// setup inherited copy of the listeners object
|
|
listeners = meta.listeners = create(listeners);
|
|
listeners.__source__ = obj;
|
|
}
|
|
|
|
actions = listeners[eventName];
|
|
|
|
// if there are actions, but the eventName doesn't exist in our listeners, then copy them from the prototype
|
|
if (actions && actions.__source__ !== obj) {
|
|
actions = listeners[eventName] = listeners[eventName].slice();
|
|
actions.__source__ = obj;
|
|
} else if (!actions) {
|
|
actions = listeners[eventName] = [];
|
|
actions.__source__ = obj;
|
|
}
|
|
|
|
return actions;
|
|
}
|
|
|
|
function accumulateListeners(obj, eventName, otherActions) {
|
|
var meta = obj['__ember_meta__'];
|
|
var actions = meta && meta.listeners && meta.listeners[eventName];
|
|
|
|
if (!actions) { return; }
|
|
|
|
var newActions = [];
|
|
|
|
for (var i = actions.length - 3; i >= 0; i -= 3) {
|
|
var target = actions[i];
|
|
var method = actions[i+1];
|
|
var flags = actions[i+2];
|
|
var actionIndex = indexOf(otherActions, target, method);
|
|
|
|
if (actionIndex === -1) {
|
|
otherActions.push(target, method, flags);
|
|
newActions.push(target, method, flags);
|
|
}
|
|
}
|
|
|
|
return newActions;
|
|
}
|
|
|
|
__exports__.accumulateListeners = accumulateListeners;/**
|
|
Add an event listener
|
|
|
|
@method addListener
|
|
@for Ember
|
|
@param obj
|
|
@param {String} eventName
|
|
@param {Object|Function} target A target object or a function
|
|
@param {Function|String} method A function or the name of a function to be called on `target`
|
|
@param {Boolean} once A flag whether a function should only be called once
|
|
*/
|
|
function addListener(obj, eventName, target, method, once) {
|
|
Ember.assert("You must pass at least an object and event name to Ember.addListener", !!obj && !!eventName);
|
|
|
|
if (!method && 'function' === typeof target) {
|
|
method = target;
|
|
target = null;
|
|
}
|
|
|
|
var actions = actionsFor(obj, eventName);
|
|
var actionIndex = indexOf(actions, target, method);
|
|
var flags = 0;
|
|
|
|
if (once) flags |= ONCE;
|
|
|
|
if (actionIndex !== -1) { return; }
|
|
|
|
actions.push(target, method, flags);
|
|
|
|
if ('function' === typeof obj.didAddListener) {
|
|
obj.didAddListener(eventName, target, method);
|
|
}
|
|
}
|
|
|
|
__exports__.addListener = addListener;/**
|
|
Remove an event listener
|
|
|
|
Arguments should match those passed to `Ember.addListener`.
|
|
|
|
@method removeListener
|
|
@for Ember
|
|
@param obj
|
|
@param {String} eventName
|
|
@param {Object|Function} target A target object or a function
|
|
@param {Function|String} method A function or the name of a function to be called on `target`
|
|
*/
|
|
function removeListener(obj, eventName, target, method) {
|
|
Ember.assert("You must pass at least an object and event name to Ember.removeListener", !!obj && !!eventName);
|
|
|
|
if (!method && 'function' === typeof target) {
|
|
method = target;
|
|
target = null;
|
|
}
|
|
|
|
function _removeListener(target, method) {
|
|
var actions = actionsFor(obj, eventName);
|
|
var actionIndex = indexOf(actions, target, method);
|
|
|
|
// action doesn't exist, give up silently
|
|
if (actionIndex === -1) { return; }
|
|
|
|
actions.splice(actionIndex, 3);
|
|
|
|
if ('function' === typeof obj.didRemoveListener) {
|
|
obj.didRemoveListener(eventName, target, method);
|
|
}
|
|
}
|
|
|
|
if (method) {
|
|
_removeListener(target, method);
|
|
} else {
|
|
var meta = obj['__ember_meta__'];
|
|
var actions = meta && meta.listeners && meta.listeners[eventName];
|
|
|
|
if (!actions) { return; }
|
|
for (var i = actions.length - 3; i >= 0; i -= 3) {
|
|
_removeListener(actions[i], actions[i+1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
Suspend listener during callback.
|
|
|
|
This should only be used by the target of the event listener
|
|
when it is taking an action that would cause the event, e.g.
|
|
an object might suspend its property change listener while it is
|
|
setting that property.
|
|
|
|
@method suspendListener
|
|
@for Ember
|
|
|
|
@private
|
|
@param obj
|
|
@param {String} eventName
|
|
@param {Object|Function} target A target object or a function
|
|
@param {Function|String} method A function or the name of a function to be called on `target`
|
|
@param {Function} callback
|
|
*/
|
|
function suspendListener(obj, eventName, target, method, callback) {
|
|
if (!method && 'function' === typeof target) {
|
|
method = target;
|
|
target = null;
|
|
}
|
|
|
|
var actions = actionsFor(obj, eventName);
|
|
var actionIndex = indexOf(actions, target, method);
|
|
|
|
if (actionIndex !== -1) {
|
|
actions[actionIndex+2] |= SUSPENDED; // mark the action as suspended
|
|
}
|
|
|
|
function tryable() { return callback.call(target); }
|
|
function finalizer() { if (actionIndex !== -1) { actions[actionIndex+2] &= ~SUSPENDED; } }
|
|
|
|
return tryFinally(tryable, finalizer);
|
|
}
|
|
|
|
__exports__.suspendListener = suspendListener;/**
|
|
Suspends multiple listeners during a callback.
|
|
|
|
@method suspendListeners
|
|
@for Ember
|
|
|
|
@private
|
|
@param obj
|
|
@param {Array} eventNames Array of event names
|
|
@param {Object|Function} target A target object or a function
|
|
@param {Function|String} method A function or the name of a function to be called on `target`
|
|
@param {Function} callback
|
|
*/
|
|
function suspendListeners(obj, eventNames, target, method, callback) {
|
|
if (!method && 'function' === typeof target) {
|
|
method = target;
|
|
target = null;
|
|
}
|
|
|
|
var suspendedActions = [];
|
|
var actionsList = [];
|
|
var eventName, actions, i, l;
|
|
|
|
for (i=0, l=eventNames.length; i<l; i++) {
|
|
eventName = eventNames[i];
|
|
actions = actionsFor(obj, eventName);
|
|
var actionIndex = indexOf(actions, target, method);
|
|
|
|
if (actionIndex !== -1) {
|
|
actions[actionIndex+2] |= SUSPENDED;
|
|
suspendedActions.push(actionIndex);
|
|
actionsList.push(actions);
|
|
}
|
|
}
|
|
|
|
function tryable() { return callback.call(target); }
|
|
|
|
function finalizer() {
|
|
for (var i = 0, l = suspendedActions.length; i < l; i++) {
|
|
var actionIndex = suspendedActions[i];
|
|
actionsList[i][actionIndex+2] &= ~SUSPENDED;
|
|
}
|
|
}
|
|
|
|
return tryFinally(tryable, finalizer);
|
|
}
|
|
|
|
__exports__.suspendListeners = suspendListeners;/**
|
|
Return a list of currently watched events
|
|
|
|
@private
|
|
@method watchedEvents
|
|
@for Ember
|
|
@param obj
|
|
*/
|
|
function watchedEvents(obj) {
|
|
var listeners = obj['__ember_meta__'].listeners, ret = [];
|
|
|
|
if (listeners) {
|
|
for (var eventName in listeners) {
|
|
if (eventName !== '__source__' &&
|
|
listeners[eventName]) {
|
|
ret.push(eventName);
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
__exports__.watchedEvents = watchedEvents;/**
|
|
Send an event. The execution of suspended listeners
|
|
is skipped, and once listeners are removed. A listener without
|
|
a target is executed on the passed object. If an array of actions
|
|
is not passed, the actions stored on the passed object are invoked.
|
|
|
|
@method sendEvent
|
|
@for Ember
|
|
@param obj
|
|
@param {String} eventName
|
|
@param {Array} params Optional parameters for each listener.
|
|
@param {Array} actions Optional array of actions (listeners).
|
|
@return true
|
|
*/
|
|
function sendEvent(obj, eventName, params, actions) {
|
|
// first give object a chance to handle it
|
|
if (obj !== Ember && 'function' === typeof obj.sendEvent) {
|
|
obj.sendEvent(eventName, params);
|
|
}
|
|
|
|
if (!actions) {
|
|
var meta = obj['__ember_meta__'];
|
|
actions = meta && meta.listeners && meta.listeners[eventName];
|
|
}
|
|
|
|
if (!actions) { return; }
|
|
|
|
for (var i = actions.length - 3; i >= 0; i -= 3) { // looping in reverse for once listeners
|
|
var target = actions[i], method = actions[i+1], flags = actions[i+2];
|
|
if (!method) { continue; }
|
|
if (flags & SUSPENDED) { continue; }
|
|
if (flags & ONCE) { removeListener(obj, eventName, target, method); }
|
|
if (!target) { target = obj; }
|
|
if ('string' === typeof method) {
|
|
if (params) {
|
|
applyStr(target, method, params);
|
|
} else {
|
|
target[method]();
|
|
}
|
|
} else {
|
|
if (params) {
|
|
apply(target, method, params);
|
|
} else {
|
|
method.call(target);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
__exports__.sendEvent = sendEvent;/**
|
|
@private
|
|
@method hasListeners
|
|
@for Ember
|
|
@param obj
|
|
@param {String} eventName
|
|
*/
|
|
function hasListeners(obj, eventName) {
|
|
var meta = obj['__ember_meta__'];
|
|
var actions = meta && meta.listeners && meta.listeners[eventName];
|
|
|
|
return !!(actions && actions.length);
|
|
}
|
|
|
|
__exports__.hasListeners = hasListeners;/**
|
|
@private
|
|
@method listenersFor
|
|
@for Ember
|
|
@param obj
|
|
@param {String} eventName
|
|
*/
|
|
function listenersFor(obj, eventName) {
|
|
var ret = [];
|
|
var meta = obj['__ember_meta__'];
|
|
var actions = meta && meta.listeners && meta.listeners[eventName];
|
|
|
|
if (!actions) { return ret; }
|
|
|
|
for (var i = 0, l = actions.length; i < l; i += 3) {
|
|
var target = actions[i];
|
|
var method = actions[i+1];
|
|
ret.push([target, method]);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
__exports__.listenersFor = listenersFor;/**
|
|
Define a property as a function that should be executed when
|
|
a specified event or events are triggered.
|
|
|
|
|
|
``` javascript
|
|
var Job = Ember.Object.extend({
|
|
logCompleted: Ember.on('completed', function() {
|
|
console.log('Job completed!');
|
|
})
|
|
});
|
|
|
|
var job = Job.create();
|
|
|
|
Ember.sendEvent(job, 'completed'); // Logs 'Job completed!'
|
|
```
|
|
|
|
@method on
|
|
@for Ember
|
|
@param {String} eventNames*
|
|
@param {Function} func
|
|
@return func
|
|
*/
|
|
function on(){
|
|
var func = a_slice.call(arguments, -1)[0];
|
|
var events = a_slice.call(arguments, 0, -1);
|
|
func.__ember_listens__ = events;
|
|
return func;
|
|
}
|
|
|
|
__exports__.on = on;__exports__.removeListener = removeListener;
|
|
});
|
|
enifed("ember-metal/expand_properties",
|
|
["ember-metal/core","ember-metal/error","ember-metal/enumerable_utils","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
var EmberError = __dependency2__["default"];
|
|
var forEach = __dependency3__.forEach;
|
|
|
|
/**
|
|
@module ember-metal
|
|
*/
|
|
|
|
var BRACE_EXPANSION = /^((?:[^\.]*\.)*)\{(.*)\}$/;
|
|
var SPLIT_REGEX = /\{|\}/;
|
|
|
|
/**
|
|
Expands `pattern`, invoking `callback` for each expansion.
|
|
|
|
The only pattern supported is brace-expansion, anything else will be passed
|
|
once to `callback` directly.
|
|
|
|
Example
|
|
|
|
```js
|
|
function echo(arg){ console.log(arg); }
|
|
|
|
Ember.expandProperties('foo.bar', echo); //=> 'foo.bar'
|
|
Ember.expandProperties('{foo,bar}', echo); //=> 'foo', 'bar'
|
|
Ember.expandProperties('foo.{bar,baz}', echo); //=> 'foo.bar', 'foo.baz'
|
|
Ember.expandProperties('{foo,bar}.baz', echo); //=> '{foo,bar}.baz'
|
|
Ember.expandProperties('foo.{bar,baz}.@each', echo) //=> 'foo.bar.@each', 'foo.baz.@each'
|
|
Ember.expandProperties('{foo,bar}.{spam,eggs}', echo) //=> 'foo.spam', 'foo.eggs', 'bar.spam', 'bar.eggs'
|
|
Ember.expandProperties('{foo}.bar.{baz}') //=> 'foo.bar.baz'
|
|
```
|
|
|
|
@method
|
|
@private
|
|
@param {String} pattern The property pattern to expand.
|
|
@param {Function} callback The callback to invoke. It is invoked once per
|
|
expansion, and is passed the expansion.
|
|
*/
|
|
__exports__["default"] = function expandProperties(pattern, callback) {
|
|
if (pattern.indexOf(' ') > -1) {
|
|
throw new EmberError('Brace expanded properties cannot contain spaces, ' +
|
|
'e.g. `user.{firstName, lastName}` should be `user.{firstName,lastName}`');
|
|
}
|
|
|
|
|
|
return newExpandProperties(pattern, callback);
|
|
}
|
|
|
|
function oldExpandProperties(pattern, callback) {
|
|
var match, prefix, list;
|
|
|
|
if (match = BRACE_EXPANSION.exec(pattern)) {
|
|
prefix = match[1];
|
|
list = match[2];
|
|
|
|
forEach(list.split(','), function (suffix) {
|
|
callback(prefix + suffix);
|
|
});
|
|
} else {
|
|
callback(pattern);
|
|
}
|
|
}
|
|
|
|
function newExpandProperties(pattern, callback) {
|
|
if ('string' === Ember.typeOf(pattern)) {
|
|
var parts = pattern.split(SPLIT_REGEX);
|
|
var properties = [parts];
|
|
|
|
forEach(parts, function(part, index) {
|
|
if (part.indexOf(',') >= 0) {
|
|
properties = duplicateAndReplace(properties, part.split(','), index);
|
|
}
|
|
});
|
|
|
|
forEach(properties, function(property) {
|
|
callback(property.join(''));
|
|
});
|
|
} else {
|
|
callback(pattern);
|
|
}
|
|
}
|
|
|
|
function duplicateAndReplace(properties, currentParts, index) {
|
|
var all = [];
|
|
|
|
forEach(properties, function(property) {
|
|
forEach(currentParts, function(part) {
|
|
var current = property.slice(0);
|
|
current[index] = part;
|
|
all.push(current);
|
|
});
|
|
});
|
|
|
|
return all;
|
|
}
|
|
});
|
|
enifed("ember-metal/get_properties",
|
|
["ember-metal/property_get","ember-metal/utils","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var get = __dependency1__.get;
|
|
var typeOf = __dependency2__.typeOf;
|
|
|
|
/**
|
|
To get multiple properties at once, call `Ember.getProperties`
|
|
with an object followed by a list of strings or an array:
|
|
|
|
```javascript
|
|
Ember.getProperties(record, 'firstName', 'lastName', 'zipCode');
|
|
// { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
|
|
```
|
|
|
|
is equivalent to:
|
|
|
|
```javascript
|
|
Ember.getProperties(record, ['firstName', 'lastName', 'zipCode']);
|
|
// { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
|
|
```
|
|
|
|
@method getProperties
|
|
@for Ember
|
|
@param {Object} obj
|
|
@param {String...|Array} list of keys to get
|
|
@return {Object}
|
|
*/
|
|
__exports__["default"] = function getProperties(obj) {
|
|
var ret = {};
|
|
var propertyNames = arguments;
|
|
var i = 1;
|
|
|
|
if (arguments.length === 2 && typeOf(arguments[1]) === 'array') {
|
|
i = 0;
|
|
propertyNames = arguments[1];
|
|
}
|
|
for(var len = propertyNames.length; i < len; i++) {
|
|
ret[propertyNames[i]] = get(obj, propertyNames[i]);
|
|
}
|
|
return ret;
|
|
}
|
|
});
|
|
enifed("ember-metal/injected_property",
|
|
["ember-metal/core","ember-metal/computed","ember-metal/alias","ember-metal/properties","ember-metal/platform","ember-metal/utils","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.assert
|
|
var ComputedProperty = __dependency2__.ComputedProperty;
|
|
var AliasedProperty = __dependency3__.AliasedProperty;
|
|
var Descriptor = __dependency4__.Descriptor;
|
|
var create = __dependency5__.create;
|
|
var meta = __dependency6__.meta;
|
|
|
|
/**
|
|
Read-only property that returns the result of a container lookup.
|
|
|
|
@class InjectedProperty
|
|
@namespace Ember
|
|
@extends Ember.Descriptor
|
|
@constructor
|
|
@param {String} type The container type the property will lookup
|
|
@param {String} name (optional) The name the property will lookup, defaults
|
|
to the property's name
|
|
*/
|
|
function InjectedProperty(type, name) {
|
|
this.type = type;
|
|
this.name = name;
|
|
|
|
this._super$Constructor(injectedPropertyGet);
|
|
AliasedPropertyPrototype.oneWay.call(this);
|
|
}
|
|
|
|
function injectedPropertyGet(keyName) {
|
|
var desc = meta(this).descs[keyName];
|
|
|
|
Ember.assert("Attempting to lookup an injected property on an object " +
|
|
"without a container, ensure that the object was " +
|
|
"instantiated via a container.", this.container);
|
|
|
|
return this.container.lookup(desc.type + ':' + (desc.name || keyName));
|
|
}
|
|
|
|
InjectedProperty.prototype = create(Descriptor.prototype);
|
|
|
|
var InjectedPropertyPrototype = InjectedProperty.prototype;
|
|
var ComputedPropertyPrototype = ComputedProperty.prototype;
|
|
var AliasedPropertyPrototype = AliasedProperty.prototype;
|
|
|
|
InjectedPropertyPrototype._super$Constructor = ComputedProperty;
|
|
|
|
InjectedPropertyPrototype.get = ComputedPropertyPrototype.get;
|
|
InjectedPropertyPrototype.readOnly = ComputedPropertyPrototype.readOnly;
|
|
|
|
InjectedPropertyPrototype.teardown = ComputedPropertyPrototype.teardown;
|
|
|
|
__exports__["default"] = InjectedProperty;
|
|
});
|
|
enifed("ember-metal/instrumentation",
|
|
["ember-metal/core","ember-metal/utils","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
var tryCatchFinally = __dependency2__.tryCatchFinally;
|
|
|
|
/**
|
|
The purpose of the Ember Instrumentation module is
|
|
to provide efficient, general-purpose instrumentation
|
|
for Ember.
|
|
|
|
Subscribe to a listener by using `Ember.subscribe`:
|
|
|
|
```javascript
|
|
Ember.subscribe("render", {
|
|
before: function(name, timestamp, payload) {
|
|
|
|
},
|
|
|
|
after: function(name, timestamp, payload) {
|
|
|
|
}
|
|
});
|
|
```
|
|
|
|
If you return a value from the `before` callback, that same
|
|
value will be passed as a fourth parameter to the `after`
|
|
callback.
|
|
|
|
Instrument a block of code by using `Ember.instrument`:
|
|
|
|
```javascript
|
|
Ember.instrument("render.handlebars", payload, function() {
|
|
// rendering logic
|
|
}, binding);
|
|
```
|
|
|
|
Event names passed to `Ember.instrument` are namespaced
|
|
by periods, from more general to more specific. Subscribers
|
|
can listen for events by whatever level of granularity they
|
|
are interested in.
|
|
|
|
In the above example, the event is `render.handlebars`,
|
|
and the subscriber listened for all events beginning with
|
|
`render`. It would receive callbacks for events named
|
|
`render`, `render.handlebars`, `render.container`, or
|
|
even `render.handlebars.layout`.
|
|
|
|
@class Instrumentation
|
|
@namespace Ember
|
|
@static
|
|
*/
|
|
var subscribers = [];
|
|
__exports__.subscribers = subscribers;var cache = {};
|
|
|
|
var populateListeners = function(name) {
|
|
var listeners = [];
|
|
var subscriber;
|
|
|
|
for (var i=0, l=subscribers.length; i<l; i++) {
|
|
subscriber = subscribers[i];
|
|
if (subscriber.regex.test(name)) {
|
|
listeners.push(subscriber.object);
|
|
}
|
|
}
|
|
|
|
cache[name] = listeners;
|
|
return listeners;
|
|
};
|
|
|
|
var time = (function() {
|
|
var perf = 'undefined' !== typeof window ? window.performance || {} : {};
|
|
var fn = perf.now || perf.mozNow || perf.webkitNow || perf.msNow || perf.oNow;
|
|
// fn.bind will be available in all the browsers that support the advanced window.performance... ;-)
|
|
return fn ? fn.bind(perf) : function() { return +new Date(); };
|
|
})();
|
|
|
|
/**
|
|
Notifies event's subscribers, calls `before` and `after` hooks.
|
|
|
|
@method instrument
|
|
@namespace Ember.Instrumentation
|
|
|
|
@param {String} [name] Namespaced event name.
|
|
@param {Object} payload
|
|
@param {Function} callback Function that you're instrumenting.
|
|
@param {Object} binding Context that instrument function is called with.
|
|
*/
|
|
function instrument(name, _payload, callback, binding) {
|
|
if (arguments.length <= 3 && typeof _payload === 'function') {
|
|
binding = callback;
|
|
callback = _payload;
|
|
_payload = undefined;
|
|
}
|
|
if (subscribers.length === 0) {
|
|
return callback.call(binding);
|
|
}
|
|
var payload = _payload || {};
|
|
var finalizer = _instrumentStart(name, function () {
|
|
return payload;
|
|
});
|
|
if (finalizer) {
|
|
var tryable = function _instrumenTryable() {
|
|
return callback.call(binding);
|
|
};
|
|
var catchable = function _instrumentCatchable(e) {
|
|
payload.exception = e;
|
|
};
|
|
return tryCatchFinally(tryable, catchable, finalizer);
|
|
} else {
|
|
return callback.call(binding);
|
|
}
|
|
}
|
|
|
|
__exports__.instrument = instrument;// private for now
|
|
function _instrumentStart(name, _payload) {
|
|
var listeners = cache[name];
|
|
|
|
if (!listeners) {
|
|
listeners = populateListeners(name);
|
|
}
|
|
|
|
if (listeners.length === 0) {
|
|
return;
|
|
}
|
|
|
|
var payload = _payload();
|
|
|
|
var STRUCTURED_PROFILE = Ember.STRUCTURED_PROFILE;
|
|
var timeName;
|
|
if (STRUCTURED_PROFILE) {
|
|
timeName = name + ": " + payload.object;
|
|
console.time(timeName);
|
|
}
|
|
|
|
var l = listeners.length;
|
|
var beforeValues = new Array(l);
|
|
var i, listener;
|
|
var timestamp = time();
|
|
for (i=0; i<l; i++) {
|
|
listener = listeners[i];
|
|
beforeValues[i] = listener.before(name, timestamp, payload);
|
|
}
|
|
|
|
return function _instrumentEnd() {
|
|
var i, l, listener;
|
|
var timestamp = time();
|
|
for (i=0, l=listeners.length; i<l; i++) {
|
|
listener = listeners[i];
|
|
listener.after(name, timestamp, payload, beforeValues[i]);
|
|
}
|
|
|
|
if (STRUCTURED_PROFILE) {
|
|
console.timeEnd(timeName);
|
|
}
|
|
};
|
|
}
|
|
|
|
__exports__._instrumentStart = _instrumentStart;/**
|
|
Subscribes to a particular event or instrumented block of code.
|
|
|
|
@method subscribe
|
|
@namespace Ember.Instrumentation
|
|
|
|
@param {String} [pattern] Namespaced event name.
|
|
@param {Object} [object] Before and After hooks.
|
|
|
|
@return {Subscriber}
|
|
*/
|
|
function subscribe(pattern, object) {
|
|
var paths = pattern.split("."), path, regex = [];
|
|
|
|
for (var i=0, l=paths.length; i<l; i++) {
|
|
path = paths[i];
|
|
if (path === "*") {
|
|
regex.push("[^\\.]*");
|
|
} else {
|
|
regex.push(path);
|
|
}
|
|
}
|
|
|
|
regex = regex.join("\\.");
|
|
regex = regex + "(\\..*)?";
|
|
|
|
var subscriber = {
|
|
pattern: pattern,
|
|
regex: new RegExp("^" + regex + "$"),
|
|
object: object
|
|
};
|
|
|
|
subscribers.push(subscriber);
|
|
cache = {};
|
|
|
|
return subscriber;
|
|
}
|
|
|
|
__exports__.subscribe = subscribe;/**
|
|
Unsubscribes from a particular event or instrumented block of code.
|
|
|
|
@method unsubscribe
|
|
@namespace Ember.Instrumentation
|
|
|
|
@param {Object} [subscriber]
|
|
*/
|
|
function unsubscribe(subscriber) {
|
|
var index;
|
|
|
|
for (var i=0, l=subscribers.length; i<l; i++) {
|
|
if (subscribers[i] === subscriber) {
|
|
index = i;
|
|
}
|
|
}
|
|
|
|
subscribers.splice(index, 1);
|
|
cache = {};
|
|
}
|
|
|
|
__exports__.unsubscribe = unsubscribe;/**
|
|
Resets `Ember.Instrumentation` by flushing list of subscribers.
|
|
|
|
@method reset
|
|
@namespace Ember.Instrumentation
|
|
*/
|
|
function reset() {
|
|
subscribers.length = 0;
|
|
cache = {};
|
|
}
|
|
|
|
__exports__.reset = reset;
|
|
});
|
|
enifed("ember-metal/is_blank",
|
|
["ember-metal/is_empty","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var isEmpty = __dependency1__["default"];
|
|
|
|
/**
|
|
A value is blank if it is empty or a whitespace string.
|
|
|
|
```javascript
|
|
Ember.isBlank(); // true
|
|
Ember.isBlank(null); // true
|
|
Ember.isBlank(undefined); // true
|
|
Ember.isBlank(''); // true
|
|
Ember.isBlank([]); // true
|
|
Ember.isBlank('\n\t'); // true
|
|
Ember.isBlank(' '); // true
|
|
Ember.isBlank({}); // false
|
|
Ember.isBlank('\n\t Hello'); // false
|
|
Ember.isBlank('Hello world'); // false
|
|
Ember.isBlank([1,2,3]); // false
|
|
```
|
|
|
|
@method isBlank
|
|
@for Ember
|
|
@param {Object} obj Value to test
|
|
@return {Boolean}
|
|
@since 1.5.0
|
|
*/
|
|
__exports__["default"] = function isBlank(obj) {
|
|
return isEmpty(obj) || (typeof obj === 'string' && obj.match(/\S/) === null);
|
|
}
|
|
});
|
|
enifed("ember-metal/is_empty",
|
|
["ember-metal/property_get","ember-metal/is_none","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var get = __dependency1__.get;
|
|
var isNone = __dependency2__["default"];
|
|
|
|
/**
|
|
Verifies that a value is `null` or an empty string, empty array,
|
|
or empty function.
|
|
|
|
Constrains the rules on `Ember.isNone` by returning true for empty
|
|
string and empty arrays.
|
|
|
|
```javascript
|
|
Ember.isEmpty(); // true
|
|
Ember.isEmpty(null); // true
|
|
Ember.isEmpty(undefined); // true
|
|
Ember.isEmpty(''); // true
|
|
Ember.isEmpty([]); // true
|
|
Ember.isEmpty({}); // false
|
|
Ember.isEmpty('Adam Hawkins'); // false
|
|
Ember.isEmpty([0,1,2]); // false
|
|
```
|
|
|
|
@method isEmpty
|
|
@for Ember
|
|
@param {Object} obj Value to test
|
|
@return {Boolean}
|
|
*/
|
|
function isEmpty(obj) {
|
|
var none = isNone(obj);
|
|
if (none) {
|
|
return none;
|
|
}
|
|
|
|
if (typeof obj.size === 'number') {
|
|
return !obj.size;
|
|
}
|
|
|
|
var objectType = typeof obj;
|
|
|
|
if (objectType === 'object') {
|
|
var size = get(obj, 'size');
|
|
if (typeof size === 'number') {
|
|
return !size;
|
|
}
|
|
}
|
|
|
|
if (typeof obj.length === 'number' && objectType !== 'function') {
|
|
return !obj.length;
|
|
}
|
|
|
|
if (objectType === 'object') {
|
|
var length = get(obj, 'length');
|
|
if (typeof length === 'number') {
|
|
return !length;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
__exports__["default"] = isEmpty;
|
|
});
|
|
enifed("ember-metal/is_none",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
/**
|
|
Returns true if the passed value is null or undefined. This avoids errors
|
|
from JSLint complaining about use of ==, which can be technically
|
|
confusing.
|
|
|
|
```javascript
|
|
Ember.isNone(); // true
|
|
Ember.isNone(null); // true
|
|
Ember.isNone(undefined); // true
|
|
Ember.isNone(''); // false
|
|
Ember.isNone([]); // false
|
|
Ember.isNone(function() {}); // false
|
|
```
|
|
|
|
@method isNone
|
|
@for Ember
|
|
@param {Object} obj Value to test
|
|
@return {Boolean}
|
|
*/
|
|
function isNone(obj) {
|
|
return obj === null || obj === undefined;
|
|
}
|
|
|
|
__exports__["default"] = isNone;
|
|
});
|
|
enifed("ember-metal/is_present",
|
|
["ember-metal/is_blank","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var isBlank = __dependency1__["default"];
|
|
var isPresent;
|
|
|
|
|
|
/**
|
|
A value is present if it not `isBlank`.
|
|
|
|
```javascript
|
|
Ember.isPresent(); // false
|
|
Ember.isPresent(null); // false
|
|
Ember.isPresent(undefined); // false
|
|
Ember.isPresent(''); // false
|
|
Ember.isPresent([]); // false
|
|
Ember.isPresent('\n\t'); // false
|
|
Ember.isPresent(' '); // false
|
|
Ember.isPresent({}); // true
|
|
Ember.isPresent('\n\t Hello'); // true
|
|
Ember.isPresent('Hello world'); // true
|
|
Ember.isPresent([1,2,3]); // true
|
|
```
|
|
|
|
@method isPresent
|
|
@for Ember
|
|
@param {Object} obj Value to test
|
|
@return {Boolean}
|
|
@since 1.8.0
|
|
*/
|
|
isPresent = function isPresent(obj) {
|
|
return !isBlank(obj);
|
|
};
|
|
|
|
|
|
__exports__["default"] = isPresent;
|
|
});
|
|
enifed("ember-metal/keys",
|
|
["ember-metal/platform","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var canDefineNonEnumerableProperties = __dependency1__.canDefineNonEnumerableProperties;
|
|
|
|
/**
|
|
Returns all of the keys defined on an object or hash. This is useful
|
|
when inspecting objects for debugging. On browsers that support it, this
|
|
uses the native `Object.keys` implementation.
|
|
|
|
@method keys
|
|
@for Ember
|
|
@param {Object} obj
|
|
@return {Array} Array containing keys of obj
|
|
*/
|
|
var keys = Object.keys;
|
|
|
|
if (!keys || !canDefineNonEnumerableProperties) {
|
|
// modified from
|
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
|
|
keys = (function () {
|
|
var hasOwnProperty = Object.prototype.hasOwnProperty,
|
|
hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
|
|
dontEnums = [
|
|
'toString',
|
|
'toLocaleString',
|
|
'valueOf',
|
|
'hasOwnProperty',
|
|
'isPrototypeOf',
|
|
'propertyIsEnumerable',
|
|
'constructor'
|
|
],
|
|
dontEnumsLength = dontEnums.length;
|
|
|
|
return function keys(obj) {
|
|
if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) {
|
|
throw new TypeError('Object.keys called on non-object');
|
|
}
|
|
|
|
var result = [];
|
|
var prop, i;
|
|
|
|
for (prop in obj) {
|
|
if (prop !== '_super' &&
|
|
prop.lastIndexOf('__',0) !== 0 &&
|
|
hasOwnProperty.call(obj, prop)) {
|
|
result.push(prop);
|
|
}
|
|
}
|
|
|
|
if (hasDontEnumBug) {
|
|
for (i = 0; i < dontEnumsLength; i++) {
|
|
if (hasOwnProperty.call(obj, dontEnums[i])) {
|
|
result.push(dontEnums[i]);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
};
|
|
}());
|
|
}
|
|
|
|
__exports__["default"] = keys;
|
|
});
|
|
enifed("ember-metal/libraries",
|
|
["ember-metal/core","ember-metal/enumerable_utils","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
var forEach = __dependency2__.forEach;
|
|
var indexOf = __dependency2__.indexOf;
|
|
|
|
/**
|
|
Helper class that allows you to register your library with Ember.
|
|
|
|
Singleton created at `Ember.libraries`.
|
|
|
|
@class Libraries
|
|
@constructor
|
|
@private
|
|
*/
|
|
function Libraries() {
|
|
this._registry = [];
|
|
this._coreLibIndex = 0;
|
|
}
|
|
|
|
Libraries.prototype = {
|
|
constructor: Libraries,
|
|
|
|
_getLibraryByName: function(name) {
|
|
var libs = this._registry;
|
|
var count = libs.length;
|
|
|
|
for (var i = 0; i < count; i++) {
|
|
if (libs[i].name === name) {
|
|
return libs[i];
|
|
}
|
|
}
|
|
},
|
|
|
|
register: function(name, version, isCoreLibrary) {
|
|
var index = this._registry.length;
|
|
|
|
if (!this._getLibraryByName(name)) {
|
|
if (isCoreLibrary) {
|
|
index = this._coreLibIndex++;
|
|
}
|
|
this._registry.splice(index, 0, { name: name, version: version });
|
|
} else {
|
|
Ember.warn('Library "' + name + '" is already registered with Ember.');
|
|
}
|
|
},
|
|
|
|
registerCoreLibrary: function(name, version) {
|
|
this.register(name, version, true);
|
|
},
|
|
|
|
deRegister: function(name) {
|
|
var lib = this._getLibraryByName(name);
|
|
var index;
|
|
|
|
if (lib) {
|
|
index = indexOf(this._registry, lib);
|
|
this._registry.splice(index, 1);
|
|
}
|
|
},
|
|
|
|
each: function(callback) {
|
|
Ember.deprecate('Using Ember.libraries.each() is deprecated. Access to a list of registered libraries is currently a private API. If you are not knowingly accessing this method, your out-of-date Ember Inspector may be doing so.');
|
|
forEach(this._registry, function(lib) {
|
|
callback(lib.name, lib.version);
|
|
});
|
|
}
|
|
};
|
|
|
|
__exports__["default"] = Libraries;
|
|
});
|
|
enifed("ember-metal/logger",
|
|
["ember-metal/core","ember-metal/error","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.imports
|
|
var EmberError = __dependency2__["default"];
|
|
|
|
function K() { return this; }
|
|
|
|
function consoleMethod(name) {
|
|
var consoleObj, logToConsole;
|
|
if (Ember.imports.console) {
|
|
consoleObj = Ember.imports.console;
|
|
} else if (typeof console !== 'undefined') {
|
|
consoleObj = console;
|
|
}
|
|
|
|
var method = typeof consoleObj === 'object' ? consoleObj[name] : null;
|
|
|
|
if (method) {
|
|
// Older IE doesn't support bind, but Chrome needs it
|
|
if (typeof method.bind === 'function') {
|
|
logToConsole = method.bind(consoleObj);
|
|
logToConsole.displayName = 'console.' + name;
|
|
return logToConsole;
|
|
} else if (typeof method.apply === 'function') {
|
|
logToConsole = function() {
|
|
method.apply(consoleObj, arguments);
|
|
};
|
|
logToConsole.displayName = 'console.' + name;
|
|
return logToConsole;
|
|
} else {
|
|
return function() {
|
|
var message = Array.prototype.join.call(arguments, ', ');
|
|
method(message);
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
function assertPolyfill(test, message) {
|
|
if (!test) {
|
|
try {
|
|
// attempt to preserve the stack
|
|
throw new EmberError("assertion failed: " + message);
|
|
} catch(error) {
|
|
setTimeout(function() {
|
|
throw error;
|
|
}, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
Inside Ember-Metal, simply uses the methods from `imports.console`.
|
|
Override this to provide more robust logging functionality.
|
|
|
|
@class Logger
|
|
@namespace Ember
|
|
*/
|
|
__exports__["default"] = {
|
|
/**
|
|
Logs the arguments to the console.
|
|
You can pass as many arguments as you want and they will be joined together with a space.
|
|
|
|
```javascript
|
|
var foo = 1;
|
|
Ember.Logger.log('log value of foo:', foo);
|
|
// "log value of foo: 1" will be printed to the console
|
|
```
|
|
|
|
@method log
|
|
@for Ember.Logger
|
|
@param {*} arguments
|
|
*/
|
|
log: consoleMethod('log') || K,
|
|
|
|
/**
|
|
Prints the arguments to the console with a warning icon.
|
|
You can pass as many arguments as you want and they will be joined together with a space.
|
|
|
|
```javascript
|
|
Ember.Logger.warn('Something happened!');
|
|
// "Something happened!" will be printed to the console with a warning icon.
|
|
```
|
|
|
|
@method warn
|
|
@for Ember.Logger
|
|
@param {*} arguments
|
|
*/
|
|
warn: consoleMethod('warn') || K,
|
|
|
|
/**
|
|
Prints the arguments to the console with an error icon, red text and a stack trace.
|
|
You can pass as many arguments as you want and they will be joined together with a space.
|
|
|
|
```javascript
|
|
Ember.Logger.error('Danger! Danger!');
|
|
// "Danger! Danger!" will be printed to the console in red text.
|
|
```
|
|
|
|
@method error
|
|
@for Ember.Logger
|
|
@param {*} arguments
|
|
*/
|
|
error: consoleMethod('error') || K,
|
|
|
|
/**
|
|
Logs the arguments to the console.
|
|
You can pass as many arguments as you want and they will be joined together with a space.
|
|
|
|
```javascript
|
|
var foo = 1;
|
|
Ember.Logger.info('log value of foo:', foo);
|
|
// "log value of foo: 1" will be printed to the console
|
|
```
|
|
|
|
@method info
|
|
@for Ember.Logger
|
|
@param {*} arguments
|
|
*/
|
|
info: consoleMethod('info') || K,
|
|
|
|
/**
|
|
Logs the arguments to the console in blue text.
|
|
You can pass as many arguments as you want and they will be joined together with a space.
|
|
|
|
```javascript
|
|
var foo = 1;
|
|
Ember.Logger.debug('log value of foo:', foo);
|
|
// "log value of foo: 1" will be printed to the console
|
|
```
|
|
|
|
@method debug
|
|
@for Ember.Logger
|
|
@param {*} arguments
|
|
*/
|
|
debug: consoleMethod('debug') || consoleMethod('info') || K,
|
|
|
|
/**
|
|
If the value passed into `Ember.Logger.assert` is not truthy it will throw an error with a stack trace.
|
|
|
|
```javascript
|
|
Ember.Logger.assert(true); // undefined
|
|
Ember.Logger.assert(true === false); // Throws an Assertion failed error.
|
|
```
|
|
|
|
@method assert
|
|
@for Ember.Logger
|
|
@param {Boolean} bool Value to test
|
|
*/
|
|
assert: consoleMethod('assert') || assertPolyfill
|
|
};
|
|
});
|
|
enifed("ember-metal/map",
|
|
["ember-metal/utils","ember-metal/array","ember-metal/platform","ember-metal/deprecate_property","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember-metal
|
|
*/
|
|
|
|
/*
|
|
JavaScript (before ES6) does not have a Map implementation. Objects,
|
|
which are often used as dictionaries, may only have Strings as keys.
|
|
|
|
Because Ember has a way to get a unique identifier for every object
|
|
via `Ember.guidFor`, we can implement a performant Map with arbitrary
|
|
keys. Because it is commonly used in low-level bookkeeping, Map is
|
|
implemented as a pure JavaScript object for performance.
|
|
|
|
This implementation follows the current iteration of the ES6 proposal for
|
|
maps (http://wiki.ecmascript.org/doku.php?id=harmony:simple_maps_and_sets),
|
|
with one exception: as we do not have the luxury of in-VM iteration, we implement a
|
|
forEach method for iteration.
|
|
|
|
Map is mocked out to look like an Ember object, so you can do
|
|
`Ember.Map.create()` for symmetry with other Ember classes.
|
|
*/
|
|
|
|
var guidFor = __dependency1__.guidFor;
|
|
var indexOf = __dependency2__.indexOf;
|
|
var create = __dependency3__.create;
|
|
var deprecateProperty = __dependency4__.deprecateProperty;
|
|
|
|
function missingFunction(fn) {
|
|
throw new TypeError('' + Object.prototype.toString.call(fn) + " is not a function");
|
|
}
|
|
|
|
function missingNew(name) {
|
|
throw new TypeError("Constructor " + name + "requires 'new'");
|
|
}
|
|
|
|
function copyNull(obj) {
|
|
var output = create(null);
|
|
|
|
for (var prop in obj) {
|
|
// hasOwnPropery is not needed because obj is Object.create(null);
|
|
output[prop] = obj[prop];
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
function copyMap(original, newObject) {
|
|
var keys = original.keys.copy();
|
|
var values = copyNull(original.values);
|
|
|
|
newObject.keys = keys;
|
|
newObject.values = values;
|
|
newObject.size = original.size;
|
|
|
|
return newObject;
|
|
}
|
|
|
|
/**
|
|
This class is used internally by Ember and Ember Data.
|
|
Please do not use it at this time. We plan to clean it up
|
|
and add many tests soon.
|
|
|
|
@class OrderedSet
|
|
@namespace Ember
|
|
@constructor
|
|
@private
|
|
*/
|
|
function OrderedSet() {
|
|
|
|
if (this instanceof OrderedSet) {
|
|
this.clear();
|
|
this._silenceRemoveDeprecation = false;
|
|
} else {
|
|
missingNew("OrderedSet");
|
|
}
|
|
}
|
|
|
|
/**
|
|
@method create
|
|
@static
|
|
@return {Ember.OrderedSet}
|
|
*/
|
|
OrderedSet.create = function() {
|
|
var Constructor = this;
|
|
|
|
return new Constructor();
|
|
};
|
|
|
|
OrderedSet.prototype = {
|
|
constructor: OrderedSet,
|
|
/**
|
|
@method clear
|
|
*/
|
|
clear: function() {
|
|
this.presenceSet = create(null);
|
|
this.list = [];
|
|
this.size = 0;
|
|
},
|
|
|
|
/**
|
|
@method add
|
|
@param obj
|
|
@param guid (optional, and for internal use)
|
|
@return {Ember.OrderedSet}
|
|
*/
|
|
add: function(obj, _guid) {
|
|
var guid = _guid || guidFor(obj);
|
|
var presenceSet = this.presenceSet;
|
|
var list = this.list;
|
|
|
|
if (presenceSet[guid] === true) {
|
|
return;
|
|
}
|
|
|
|
presenceSet[guid] = true;
|
|
this.size = list.push(obj);
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
@deprecated
|
|
|
|
@method remove
|
|
@param obj
|
|
@param _guid (optional and for internal use only)
|
|
@return {Boolean}
|
|
*/
|
|
remove: function(obj, _guid) {
|
|
Ember.deprecate('Calling `OrderedSet.prototype.remove` has been deprecated, please use `OrderedSet.prototype.delete` instead.', this._silenceRemoveDeprecation);
|
|
|
|
return this["delete"](obj, _guid);
|
|
},
|
|
|
|
/**
|
|
@since 1.8.0
|
|
@method delete
|
|
@param obj
|
|
@param _guid (optional and for internal use only)
|
|
@return {Boolean}
|
|
*/
|
|
"delete": function(obj, _guid) {
|
|
var guid = _guid || guidFor(obj);
|
|
var presenceSet = this.presenceSet;
|
|
var list = this.list;
|
|
|
|
if (presenceSet[guid] === true) {
|
|
delete presenceSet[guid];
|
|
var index = indexOf.call(list, obj);
|
|
if (index > -1) {
|
|
list.splice(index, 1);
|
|
}
|
|
this.size = list.length;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
@method isEmpty
|
|
@return {Boolean}
|
|
*/
|
|
isEmpty: function() {
|
|
return this.size === 0;
|
|
},
|
|
|
|
/**
|
|
@method has
|
|
@param obj
|
|
@return {Boolean}
|
|
*/
|
|
has: function(obj) {
|
|
if (this.size === 0) { return false; }
|
|
|
|
var guid = guidFor(obj);
|
|
var presenceSet = this.presenceSet;
|
|
|
|
return presenceSet[guid] === true;
|
|
},
|
|
|
|
/**
|
|
@method forEach
|
|
@param {Function} fn
|
|
@param self
|
|
*/
|
|
forEach: function(fn /*, thisArg*/) {
|
|
if (typeof fn !== 'function') {
|
|
missingFunction(fn);
|
|
}
|
|
|
|
if (this.size === 0) { return; }
|
|
|
|
var list = this.list;
|
|
var length = arguments.length;
|
|
var i;
|
|
|
|
if (length === 2) {
|
|
for (i = 0; i < list.length; i++) {
|
|
fn.call(arguments[1], list[i]);
|
|
}
|
|
} else {
|
|
for (i = 0; i < list.length; i++) {
|
|
fn(list[i]);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
@method toArray
|
|
@return {Array}
|
|
*/
|
|
toArray: function() {
|
|
return this.list.slice();
|
|
},
|
|
|
|
/**
|
|
@method copy
|
|
@return {Ember.OrderedSet}
|
|
*/
|
|
copy: function() {
|
|
var Constructor = this.constructor;
|
|
var set = new Constructor();
|
|
|
|
set._silenceRemoveDeprecation = this._silenceRemoveDeprecation;
|
|
set.presenceSet = copyNull(this.presenceSet);
|
|
set.list = this.toArray();
|
|
set.size = this.size;
|
|
|
|
return set;
|
|
}
|
|
};
|
|
|
|
deprecateProperty(OrderedSet.prototype, 'length', 'size');
|
|
|
|
/**
|
|
A Map stores values indexed by keys. Unlike JavaScript's
|
|
default Objects, the keys of a Map can be any JavaScript
|
|
object.
|
|
|
|
Internally, a Map has two data structures:
|
|
|
|
1. `keys`: an OrderedSet of all of the existing keys
|
|
2. `values`: a JavaScript Object indexed by the `Ember.guidFor(key)`
|
|
|
|
When a key/value pair is added for the first time, we
|
|
add the key to the `keys` OrderedSet, and create or
|
|
replace an entry in `values`. When an entry is deleted,
|
|
we delete its entry in `keys` and `values`.
|
|
|
|
@class Map
|
|
@namespace Ember
|
|
@private
|
|
@constructor
|
|
*/
|
|
function Map() {
|
|
if (this instanceof this.constructor) {
|
|
this.keys = OrderedSet.create();
|
|
this.keys._silenceRemoveDeprecation = true;
|
|
this.values = create(null);
|
|
this.size = 0;
|
|
} else {
|
|
missingNew("OrderedSet");
|
|
}
|
|
}
|
|
|
|
Ember.Map = Map;
|
|
|
|
/**
|
|
@method create
|
|
@static
|
|
*/
|
|
Map.create = function() {
|
|
var Constructor = this;
|
|
return new Constructor();
|
|
};
|
|
|
|
Map.prototype = {
|
|
constructor: Map,
|
|
|
|
/**
|
|
This property will change as the number of objects in the map changes.
|
|
|
|
@since 1.8.0
|
|
@property size
|
|
@type number
|
|
@default 0
|
|
*/
|
|
size: 0,
|
|
|
|
/**
|
|
Retrieve the value associated with a given key.
|
|
|
|
@method get
|
|
@param {*} key
|
|
@return {*} the value associated with the key, or `undefined`
|
|
*/
|
|
get: function(key) {
|
|
if (this.size === 0) { return; }
|
|
|
|
var values = this.values;
|
|
var guid = guidFor(key);
|
|
|
|
return values[guid];
|
|
},
|
|
|
|
/**
|
|
Adds a value to the map. If a value for the given key has already been
|
|
provided, the new value will replace the old value.
|
|
|
|
@method set
|
|
@param {*} key
|
|
@param {*} value
|
|
@return {Ember.Map}
|
|
*/
|
|
set: function(key, value) {
|
|
var keys = this.keys;
|
|
var values = this.values;
|
|
var guid = guidFor(key);
|
|
|
|
// ensure we don't store -0
|
|
var k = key === -0 ? 0 : key;
|
|
|
|
keys.add(k, guid);
|
|
|
|
values[guid] = value;
|
|
|
|
this.size = keys.size;
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
@deprecated see delete
|
|
Removes a value from the map for an associated key.
|
|
|
|
@method remove
|
|
@param {*} key
|
|
@return {Boolean} true if an item was removed, false otherwise
|
|
*/
|
|
remove: function(key) {
|
|
Ember.deprecate('Calling `Map.prototype.remove` has been deprecated, please use `Map.prototype.delete` instead.');
|
|
|
|
return this["delete"](key);
|
|
},
|
|
|
|
/**
|
|
Removes a value from the map for an associated key.
|
|
|
|
@since 1.8.0
|
|
@method delete
|
|
@param {*} key
|
|
@return {Boolean} true if an item was removed, false otherwise
|
|
*/
|
|
"delete": function(key) {
|
|
if (this.size === 0) { return false; }
|
|
// don't use ES6 "delete" because it will be annoying
|
|
// to use in browsers that are not ES6 friendly;
|
|
var keys = this.keys;
|
|
var values = this.values;
|
|
var guid = guidFor(key);
|
|
|
|
if (keys["delete"](key, guid)) {
|
|
delete values[guid];
|
|
this.size = keys.size;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
Check whether a key is present.
|
|
|
|
@method has
|
|
@param {*} key
|
|
@return {Boolean} true if the item was present, false otherwise
|
|
*/
|
|
has: function(key) {
|
|
return this.keys.has(key);
|
|
},
|
|
|
|
/**
|
|
Iterate over all the keys and values. Calls the function once
|
|
for each key, passing in value, key, and the map being iterated over,
|
|
in that order.
|
|
|
|
The keys are guaranteed to be iterated over in insertion order.
|
|
|
|
@method forEach
|
|
@param {Function} callback
|
|
@param {*} self if passed, the `this` value inside the
|
|
callback. By default, `this` is the map.
|
|
*/
|
|
forEach: function(callback /*, thisArg*/) {
|
|
if (typeof callback !== 'function') {
|
|
missingFunction(callback);
|
|
}
|
|
|
|
if (this.size === 0) { return; }
|
|
|
|
var length = arguments.length;
|
|
var map = this;
|
|
var cb, thisArg;
|
|
|
|
if (length === 2) {
|
|
thisArg = arguments[1];
|
|
cb = function(key) {
|
|
callback.call(thisArg, map.get(key), key, map);
|
|
};
|
|
} else {
|
|
cb = function(key) {
|
|
callback(map.get(key), key, map);
|
|
};
|
|
}
|
|
|
|
this.keys.forEach(cb);
|
|
},
|
|
|
|
/**
|
|
@method clear
|
|
*/
|
|
clear: function() {
|
|
this.keys.clear();
|
|
this.values = create(null);
|
|
this.size = 0;
|
|
},
|
|
|
|
/**
|
|
@method copy
|
|
@return {Ember.Map}
|
|
*/
|
|
copy: function() {
|
|
return copyMap(this, new Map());
|
|
}
|
|
};
|
|
|
|
deprecateProperty(Map.prototype, 'length', 'size');
|
|
|
|
/**
|
|
@class MapWithDefault
|
|
@namespace Ember
|
|
@extends Ember.Map
|
|
@private
|
|
@constructor
|
|
@param [options]
|
|
@param {*} [options.defaultValue]
|
|
*/
|
|
function MapWithDefault(options) {
|
|
this._super$constructor();
|
|
this.defaultValue = options.defaultValue;
|
|
}
|
|
|
|
/**
|
|
@method create
|
|
@static
|
|
@param [options]
|
|
@param {*} [options.defaultValue]
|
|
@return {Ember.MapWithDefault|Ember.Map} If options are passed, returns
|
|
`Ember.MapWithDefault` otherwise returns `Ember.Map`
|
|
*/
|
|
MapWithDefault.create = function(options) {
|
|
if (options) {
|
|
return new MapWithDefault(options);
|
|
} else {
|
|
return new Map();
|
|
}
|
|
};
|
|
|
|
MapWithDefault.prototype = create(Map.prototype);
|
|
MapWithDefault.prototype.constructor = MapWithDefault;
|
|
MapWithDefault.prototype._super$constructor = Map;
|
|
MapWithDefault.prototype._super$get = Map.prototype.get;
|
|
|
|
/**
|
|
Retrieve the value associated with a given key.
|
|
|
|
@method get
|
|
@param {*} key
|
|
@return {*} the value associated with the key, or the default value
|
|
*/
|
|
MapWithDefault.prototype.get = function(key) {
|
|
var hasValue = this.has(key);
|
|
|
|
if (hasValue) {
|
|
return this._super$get(key);
|
|
} else {
|
|
var defaultValue = this.defaultValue(key);
|
|
this.set(key, defaultValue);
|
|
return defaultValue;
|
|
}
|
|
};
|
|
|
|
/**
|
|
@method copy
|
|
@return {Ember.MapWithDefault}
|
|
*/
|
|
MapWithDefault.prototype.copy = function() {
|
|
var Constructor = this.constructor;
|
|
return copyMap(this, new Constructor({
|
|
defaultValue: this.defaultValue
|
|
}));
|
|
};
|
|
|
|
__exports__["default"] = Map;
|
|
|
|
__exports__.OrderedSet = OrderedSet;
|
|
__exports__.Map = Map;
|
|
__exports__.MapWithDefault = MapWithDefault;
|
|
});
|
|
enifed("ember-metal/merge",
|
|
["ember-metal/keys","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var keys = __dependency1__["default"];
|
|
|
|
/**
|
|
Merge the contents of two objects together into the first object.
|
|
|
|
```javascript
|
|
Ember.merge({first: 'Tom'}, {last: 'Dale'}); // {first: 'Tom', last: 'Dale'}
|
|
var a = {first: 'Yehuda'}, b = {last: 'Katz'};
|
|
Ember.merge(a, b); // a == {first: 'Yehuda', last: 'Katz'}, b == {last: 'Katz'}
|
|
```
|
|
|
|
@method merge
|
|
@for Ember
|
|
@param {Object} original The object to merge into
|
|
@param {Object} updates The object to copy properties from
|
|
@return {Object}
|
|
*/
|
|
__exports__["default"] = function merge(original, updates) {
|
|
if (!updates || typeof updates !== 'object') {
|
|
return original;
|
|
}
|
|
|
|
var props = keys(updates);
|
|
var prop;
|
|
var length = props.length;
|
|
|
|
for (var i = 0; i < length; i++) {
|
|
prop = props[i];
|
|
original[prop] = updates[prop];
|
|
}
|
|
|
|
return original;
|
|
}
|
|
});
|
|
enifed("ember-metal/mixin",
|
|
["ember-metal/core","ember-metal/merge","ember-metal/array","ember-metal/platform","ember-metal/property_get","ember-metal/property_set","ember-metal/utils","ember-metal/expand_properties","ember-metal/properties","ember-metal/computed","ember-metal/binding","ember-metal/observer","ember-metal/events","ember-metal/streams/utils","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __exports__) {
|
|
// Remove "use strict"; from transpiled module until
|
|
// https://bugs.webkit.org/show_bug.cgi?id=138038 is fixed
|
|
//
|
|
// REMOVE_USE_STRICT: true
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-metal
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
// warn, assert, wrap, et;
|
|
var merge = __dependency2__["default"];
|
|
var a_indexOf = __dependency3__.indexOf;
|
|
var a_forEach = __dependency3__.forEach;
|
|
var o_create = __dependency4__.create;
|
|
var get = __dependency5__.get;
|
|
var set = __dependency6__.set;
|
|
var trySet = __dependency6__.trySet;
|
|
var guidFor = __dependency7__.guidFor;
|
|
var metaFor = __dependency7__.meta;
|
|
var wrap = __dependency7__.wrap;
|
|
var makeArray = __dependency7__.makeArray;
|
|
var isArray = __dependency7__.isArray;
|
|
var expandProperties = __dependency8__["default"];
|
|
var Descriptor = __dependency9__.Descriptor;
|
|
var defineProperty = __dependency9__.defineProperty;
|
|
var ComputedProperty = __dependency10__.ComputedProperty;
|
|
var Binding = __dependency11__.Binding;
|
|
var addObserver = __dependency12__.addObserver;
|
|
var removeObserver = __dependency12__.removeObserver;
|
|
var addBeforeObserver = __dependency12__.addBeforeObserver;
|
|
var removeBeforeObserver = __dependency12__.removeBeforeObserver;
|
|
var _suspendObserver = __dependency12__._suspendObserver;
|
|
var addListener = __dependency13__.addListener;
|
|
var removeListener = __dependency13__.removeListener;
|
|
var isStream = __dependency14__.isStream;
|
|
|
|
var REQUIRED;
|
|
var a_slice = [].slice;
|
|
|
|
function superFunction(){
|
|
var func = this.__nextSuper;
|
|
var ret;
|
|
|
|
if (func) {
|
|
var length = arguments.length;
|
|
this.__nextSuper = null;
|
|
if (length === 0) {
|
|
ret = func.call(this);
|
|
} else if (length === 1) {
|
|
ret = func.call(this, arguments[0]);
|
|
} else if (length === 2) {
|
|
ret = func.call(this, arguments[0], arguments[1]);
|
|
} else {
|
|
ret = func.apply(this, arguments);
|
|
}
|
|
this.__nextSuper = func;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
// ensure we prime superFunction to mitigate
|
|
// v8 bug potentially incorrectly deopts this function: https://code.google.com/p/v8/issues/detail?id=3709
|
|
var primer = {
|
|
__nextSuper: function(a,b,c,d ) { }
|
|
};
|
|
|
|
superFunction.call(primer);
|
|
superFunction.call(primer, 1);
|
|
superFunction.call(primer, 1, 2);
|
|
superFunction.call(primer, 1, 2, 3);
|
|
|
|
function mixinsMeta(obj) {
|
|
var m = metaFor(obj, true);
|
|
var ret = m.mixins;
|
|
if (!ret) {
|
|
ret = m.mixins = {};
|
|
} else if (!m.hasOwnProperty('mixins')) {
|
|
ret = m.mixins = o_create(ret);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
function isMethod(obj) {
|
|
return 'function' === typeof obj &&
|
|
obj.isMethod !== false &&
|
|
obj !== Boolean &&
|
|
obj !== Object &&
|
|
obj !== Number &&
|
|
obj !== Array &&
|
|
obj !== Date &&
|
|
obj !== String;
|
|
}
|
|
|
|
var CONTINUE = {};
|
|
|
|
function mixinProperties(mixinsMeta, mixin) {
|
|
var guid;
|
|
|
|
if (mixin instanceof Mixin) {
|
|
guid = guidFor(mixin);
|
|
if (mixinsMeta[guid]) { return CONTINUE; }
|
|
mixinsMeta[guid] = mixin;
|
|
return mixin.properties;
|
|
} else {
|
|
return mixin; // apply anonymous mixin properties
|
|
}
|
|
}
|
|
|
|
function concatenatedMixinProperties(concatProp, props, values, base) {
|
|
var concats;
|
|
|
|
// reset before adding each new mixin to pickup concats from previous
|
|
concats = values[concatProp] || base[concatProp];
|
|
if (props[concatProp]) {
|
|
concats = concats ? concats.concat(props[concatProp]) : props[concatProp];
|
|
}
|
|
|
|
return concats;
|
|
}
|
|
|
|
function giveDescriptorSuper(meta, key, property, values, descs) {
|
|
var superProperty;
|
|
|
|
// Computed properties override methods, and do not call super to them
|
|
if (values[key] === undefined) {
|
|
// Find the original descriptor in a parent mixin
|
|
superProperty = descs[key];
|
|
}
|
|
|
|
// If we didn't find the original descriptor in a parent mixin, find
|
|
// it on the original object.
|
|
superProperty = superProperty || meta.descs[key];
|
|
|
|
if (superProperty === undefined || !(superProperty instanceof ComputedProperty)) {
|
|
return property;
|
|
}
|
|
|
|
// Since multiple mixins may inherit from the same parent, we need
|
|
// to clone the computed property so that other mixins do not receive
|
|
// the wrapped version.
|
|
property = o_create(property);
|
|
property.func = wrap(property.func, superProperty.func);
|
|
|
|
return property;
|
|
}
|
|
|
|
var sourceAvailable = (function() {
|
|
return this;
|
|
}).toString().indexOf('return this;') > -1;
|
|
|
|
function giveMethodSuper(obj, key, method, values, descs) {
|
|
var superMethod;
|
|
|
|
// Methods overwrite computed properties, and do not call super to them.
|
|
if (descs[key] === undefined) {
|
|
// Find the original method in a parent mixin
|
|
superMethod = values[key];
|
|
}
|
|
|
|
// If we didn't find the original value in a parent mixin, find it in
|
|
// the original object
|
|
superMethod = superMethod || obj[key];
|
|
|
|
// Only wrap the new method if the original method was a function
|
|
if (superMethod === undefined || 'function' !== typeof superMethod) {
|
|
return method;
|
|
}
|
|
|
|
var hasSuper;
|
|
if (sourceAvailable) {
|
|
hasSuper = method.__hasSuper;
|
|
|
|
if (hasSuper === undefined) {
|
|
hasSuper = method.toString().indexOf('_super') > -1;
|
|
method.__hasSuper = hasSuper;
|
|
}
|
|
}
|
|
|
|
if (sourceAvailable === false || hasSuper) {
|
|
return wrap(method, superMethod);
|
|
} else {
|
|
return method;
|
|
}
|
|
}
|
|
|
|
function applyConcatenatedProperties(obj, key, value, values) {
|
|
var baseValue = values[key] || obj[key];
|
|
|
|
if (baseValue) {
|
|
if ('function' === typeof baseValue.concat) {
|
|
if (value === null || value === undefined) {
|
|
return baseValue;
|
|
} else {
|
|
return baseValue.concat(value);
|
|
}
|
|
} else {
|
|
return makeArray(baseValue).concat(value);
|
|
}
|
|
} else {
|
|
return makeArray(value);
|
|
}
|
|
}
|
|
|
|
function applyMergedProperties(obj, key, value, values) {
|
|
var baseValue = values[key] || obj[key];
|
|
|
|
Ember.assert("You passed in `" + JSON.stringify(value) + "` as the value for `" + key +
|
|
"` but `" + key + "` cannot be an Array", !isArray(value));
|
|
|
|
if (!baseValue) { return value; }
|
|
|
|
var newBase = merge({}, baseValue);
|
|
var hasFunction = false;
|
|
|
|
for (var prop in value) {
|
|
if (!value.hasOwnProperty(prop)) { continue; }
|
|
|
|
var propValue = value[prop];
|
|
if (isMethod(propValue)) {
|
|
// TODO: support for Computed Properties, etc?
|
|
hasFunction = true;
|
|
newBase[prop] = giveMethodSuper(obj, prop, propValue, baseValue, {});
|
|
} else {
|
|
newBase[prop] = propValue;
|
|
}
|
|
}
|
|
|
|
if (hasFunction) {
|
|
newBase._super = superFunction;
|
|
}
|
|
|
|
return newBase;
|
|
}
|
|
|
|
function addNormalizedProperty(base, key, value, meta, descs, values, concats, mergings) {
|
|
if (value instanceof Descriptor) {
|
|
if (value === REQUIRED && descs[key]) { return CONTINUE; }
|
|
|
|
// Wrap descriptor function to implement
|
|
// __nextSuper() if needed
|
|
if (value.func) {
|
|
value = giveDescriptorSuper(meta, key, value, values, descs);
|
|
}
|
|
|
|
descs[key] = value;
|
|
values[key] = undefined;
|
|
} else {
|
|
if ((concats && a_indexOf.call(concats, key) >= 0) ||
|
|
key === 'concatenatedProperties' ||
|
|
key === 'mergedProperties') {
|
|
value = applyConcatenatedProperties(base, key, value, values);
|
|
} else if ((mergings && a_indexOf.call(mergings, key) >= 0)) {
|
|
value = applyMergedProperties(base, key, value, values);
|
|
} else if (isMethod(value)) {
|
|
value = giveMethodSuper(base, key, value, values, descs);
|
|
}
|
|
|
|
descs[key] = undefined;
|
|
values[key] = value;
|
|
}
|
|
}
|
|
|
|
function mergeMixins(mixins, m, descs, values, base, keys) {
|
|
var mixin, props, key, concats, mergings, meta;
|
|
|
|
function removeKeys(keyName) {
|
|
delete descs[keyName];
|
|
delete values[keyName];
|
|
}
|
|
|
|
for(var i=0, l=mixins.length; i<l; i++) {
|
|
mixin = mixins[i];
|
|
Ember.assert('Expected hash or Mixin instance, got ' + Object.prototype.toString.call(mixin),
|
|
typeof mixin === 'object' && mixin !== null && Object.prototype.toString.call(mixin) !== '[object Array]');
|
|
|
|
props = mixinProperties(m, mixin);
|
|
if (props === CONTINUE) { continue; }
|
|
|
|
if (props) {
|
|
meta = metaFor(base);
|
|
if (base.willMergeMixin) { base.willMergeMixin(props); }
|
|
concats = concatenatedMixinProperties('concatenatedProperties', props, values, base);
|
|
mergings = concatenatedMixinProperties('mergedProperties', props, values, base);
|
|
|
|
for (key in props) {
|
|
if (!props.hasOwnProperty(key)) { continue; }
|
|
keys.push(key);
|
|
addNormalizedProperty(base, key, props[key], meta, descs, values, concats, mergings);
|
|
}
|
|
|
|
// manually copy toString() because some JS engines do not enumerate it
|
|
if (props.hasOwnProperty('toString')) { base.toString = props.toString; }
|
|
} else if (mixin.mixins) {
|
|
mergeMixins(mixin.mixins, m, descs, values, base, keys);
|
|
if (mixin._without) { a_forEach.call(mixin._without, removeKeys); }
|
|
}
|
|
}
|
|
}
|
|
|
|
var IS_BINDING = /^.+Binding$/;
|
|
|
|
function detectBinding(obj, key, value, m) {
|
|
if (IS_BINDING.test(key)) {
|
|
var bindings = m.bindings;
|
|
if (!bindings) {
|
|
bindings = m.bindings = {};
|
|
} else if (!m.hasOwnProperty('bindings')) {
|
|
bindings = m.bindings = o_create(m.bindings);
|
|
}
|
|
bindings[key] = value;
|
|
}
|
|
}
|
|
|
|
function connectStreamBinding(obj, key, stream) {
|
|
var onNotify = function(stream) {
|
|
_suspendObserver(obj, key, null, didChange, function() {
|
|
trySet(obj, key, stream.value());
|
|
});
|
|
};
|
|
|
|
var didChange = function() {
|
|
stream.setValue(get(obj, key), onNotify);
|
|
};
|
|
|
|
// Initialize value
|
|
set(obj, key, stream.value());
|
|
|
|
addObserver(obj, key, null, didChange);
|
|
|
|
stream.subscribe(onNotify);
|
|
|
|
if (obj._streamBindingSubscriptions === undefined) {
|
|
obj._streamBindingSubscriptions = o_create(null);
|
|
}
|
|
|
|
obj._streamBindingSubscriptions[key] = onNotify;
|
|
}
|
|
|
|
function connectBindings(obj, m) {
|
|
// TODO Mixin.apply(instance) should disconnect binding if exists
|
|
var bindings = m.bindings;
|
|
var key, binding, to;
|
|
if (bindings) {
|
|
for (key in bindings) {
|
|
binding = bindings[key];
|
|
if (binding) {
|
|
to = key.slice(0, -7); // strip Binding off end
|
|
if (isStream(binding)) {
|
|
connectStreamBinding(obj, to, binding);
|
|
continue;
|
|
} else if (binding instanceof Binding) {
|
|
binding = binding.copy(); // copy prototypes' instance
|
|
binding.to(to);
|
|
} else { // binding is string path
|
|
binding = new Binding(to, binding);
|
|
}
|
|
binding.connect(obj);
|
|
obj[key] = binding;
|
|
}
|
|
}
|
|
// mark as applied
|
|
m.bindings = {};
|
|
}
|
|
}
|
|
|
|
function finishPartial(obj, m) {
|
|
connectBindings(obj, m || metaFor(obj));
|
|
return obj;
|
|
}
|
|
|
|
function followAlias(obj, desc, m, descs, values) {
|
|
var altKey = desc.methodName;
|
|
var value;
|
|
if (descs[altKey] || values[altKey]) {
|
|
value = values[altKey];
|
|
desc = descs[altKey];
|
|
} else if (m.descs[altKey]) {
|
|
desc = m.descs[altKey];
|
|
value = undefined;
|
|
} else {
|
|
desc = undefined;
|
|
value = obj[altKey];
|
|
}
|
|
|
|
return { desc: desc, value: value };
|
|
}
|
|
|
|
function updateObserversAndListeners(obj, key, observerOrListener, pathsKey, updateMethod) {
|
|
var paths = observerOrListener[pathsKey];
|
|
|
|
if (paths) {
|
|
for (var i=0, l=paths.length; i<l; i++) {
|
|
updateMethod(obj, paths[i], null, key);
|
|
}
|
|
}
|
|
}
|
|
|
|
function replaceObserversAndListeners(obj, key, observerOrListener) {
|
|
var prev = obj[key];
|
|
|
|
if ('function' === typeof prev) {
|
|
updateObserversAndListeners(obj, key, prev, '__ember_observesBefore__', removeBeforeObserver);
|
|
updateObserversAndListeners(obj, key, prev, '__ember_observes__', removeObserver);
|
|
updateObserversAndListeners(obj, key, prev, '__ember_listens__', removeListener);
|
|
}
|
|
|
|
if ('function' === typeof observerOrListener) {
|
|
updateObserversAndListeners(obj, key, observerOrListener, '__ember_observesBefore__', addBeforeObserver);
|
|
updateObserversAndListeners(obj, key, observerOrListener, '__ember_observes__', addObserver);
|
|
updateObserversAndListeners(obj, key, observerOrListener, '__ember_listens__', addListener);
|
|
}
|
|
}
|
|
|
|
function applyMixin(obj, mixins, partial) {
|
|
var descs = {};
|
|
var values = {};
|
|
var m = metaFor(obj);
|
|
var keys = [];
|
|
var key, value, desc;
|
|
|
|
obj._super = superFunction;
|
|
|
|
// Go through all mixins and hashes passed in, and:
|
|
//
|
|
// * Handle concatenated properties
|
|
// * Handle merged properties
|
|
// * Set up _super wrapping if necessary
|
|
// * Set up computed property descriptors
|
|
// * Copying `toString` in broken browsers
|
|
mergeMixins(mixins, mixinsMeta(obj), descs, values, obj, keys);
|
|
|
|
for(var i = 0, l = keys.length; i < l; i++) {
|
|
key = keys[i];
|
|
if (key === 'constructor' || !values.hasOwnProperty(key)) { continue; }
|
|
|
|
desc = descs[key];
|
|
value = values[key];
|
|
|
|
if (desc === REQUIRED) { continue; }
|
|
|
|
while (desc && desc instanceof Alias) {
|
|
var followed = followAlias(obj, desc, m, descs, values);
|
|
desc = followed.desc;
|
|
value = followed.value;
|
|
}
|
|
|
|
if (desc === undefined && value === undefined) { continue; }
|
|
|
|
replaceObserversAndListeners(obj, key, value);
|
|
detectBinding(obj, key, value, m);
|
|
defineProperty(obj, key, desc, value, m);
|
|
}
|
|
|
|
if (!partial) { // don't apply to prototype
|
|
finishPartial(obj, m);
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
/**
|
|
@method mixin
|
|
@for Ember
|
|
@param obj
|
|
@param mixins*
|
|
@return obj
|
|
*/
|
|
function mixin(obj) {
|
|
var args = a_slice.call(arguments, 1);
|
|
applyMixin(obj, args, false);
|
|
return obj;
|
|
}
|
|
|
|
__exports__.mixin = mixin;/**
|
|
The `Ember.Mixin` class allows you to create mixins, whose properties can be
|
|
added to other classes. For instance,
|
|
|
|
```javascript
|
|
App.Editable = Ember.Mixin.create({
|
|
edit: function() {
|
|
console.log('starting to edit');
|
|
this.set('isEditing', true);
|
|
},
|
|
isEditing: false
|
|
});
|
|
|
|
// Mix mixins into classes by passing them as the first arguments to
|
|
// .extend.
|
|
App.CommentView = Ember.View.extend(App.Editable, {
|
|
template: Ember.Handlebars.compile('{{#if view.isEditing}}...{{else}}...{{/if}}')
|
|
});
|
|
|
|
commentView = App.CommentView.create();
|
|
commentView.edit(); // outputs 'starting to edit'
|
|
```
|
|
|
|
Note that Mixins are created with `Ember.Mixin.create`, not
|
|
`Ember.Mixin.extend`.
|
|
|
|
Note that mixins extend a constructor's prototype so arrays and object literals
|
|
defined as properties will be shared amongst objects that implement the mixin.
|
|
If you want to define a property in a mixin that is not shared, you can define
|
|
it either as a computed property or have it be created on initialization of the object.
|
|
|
|
```javascript
|
|
//filters array will be shared amongst any object implementing mixin
|
|
App.Filterable = Ember.Mixin.create({
|
|
filters: Ember.A()
|
|
});
|
|
|
|
//filters will be a separate array for every object implementing the mixin
|
|
App.Filterable = Ember.Mixin.create({
|
|
filters: Ember.computed(function(){return Ember.A();})
|
|
});
|
|
|
|
//filters will be created as a separate array during the object's initialization
|
|
App.Filterable = Ember.Mixin.create({
|
|
init: function() {
|
|
this._super();
|
|
this.set("filters", Ember.A());
|
|
}
|
|
});
|
|
```
|
|
|
|
@class Mixin
|
|
@namespace Ember
|
|
*/
|
|
__exports__["default"] = Mixin;
|
|
function Mixin(args, properties) {
|
|
this.properties = properties;
|
|
|
|
var length = args && args.length;
|
|
|
|
if (length > 0) {
|
|
var m = new Array(length);
|
|
|
|
for (var i = 0; i < length; i++) {
|
|
var x = args[i];
|
|
if (x instanceof Mixin) {
|
|
m[i] = x;
|
|
} else {
|
|
m[i] = new Mixin(undefined, x);
|
|
}
|
|
}
|
|
|
|
this.mixins = m;
|
|
} else {
|
|
this.mixins = undefined;
|
|
}
|
|
this.ownerConstructor = undefined;
|
|
}
|
|
|
|
Mixin._apply = applyMixin;
|
|
|
|
Mixin.applyPartial = function(obj) {
|
|
var args = a_slice.call(arguments, 1);
|
|
return applyMixin(obj, args, true);
|
|
};
|
|
|
|
Mixin.finishPartial = finishPartial;
|
|
|
|
// ES6TODO: this relies on a global state?
|
|
Ember.anyUnprocessedMixins = false;
|
|
|
|
/**
|
|
@method create
|
|
@static
|
|
@param arguments*
|
|
*/
|
|
Mixin.create = function() {
|
|
// ES6TODO: this relies on a global state?
|
|
Ember.anyUnprocessedMixins = true;
|
|
var M = this;
|
|
var length = arguments.length;
|
|
var args = new Array(length);
|
|
for (var i = 0; i < length; i++) {
|
|
args[i] = arguments[i];
|
|
}
|
|
return new M(args, undefined);
|
|
};
|
|
|
|
var MixinPrototype = Mixin.prototype;
|
|
|
|
/**
|
|
@method reopen
|
|
@param arguments*
|
|
*/
|
|
MixinPrototype.reopen = function() {
|
|
var mixin;
|
|
|
|
if (this.properties) {
|
|
mixin = new Mixin(undefined, this.properties);
|
|
this.properties = undefined;
|
|
this.mixins = [mixin];
|
|
} else if (!this.mixins) {
|
|
this.mixins = [];
|
|
}
|
|
|
|
var len = arguments.length;
|
|
var mixins = this.mixins;
|
|
var idx;
|
|
|
|
for(idx=0; idx < len; idx++) {
|
|
mixin = arguments[idx];
|
|
Ember.assert('Expected hash or Mixin instance, got ' + Object.prototype.toString.call(mixin),
|
|
typeof mixin === 'object' && mixin !== null &&
|
|
Object.prototype.toString.call(mixin) !== '[object Array]');
|
|
|
|
if (mixin instanceof Mixin) {
|
|
mixins.push(mixin);
|
|
} else {
|
|
mixins.push(new Mixin(undefined, mixin));
|
|
}
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
@method apply
|
|
@param obj
|
|
@return applied object
|
|
*/
|
|
MixinPrototype.apply = function(obj) {
|
|
return applyMixin(obj, [this], false);
|
|
};
|
|
|
|
MixinPrototype.applyPartial = function(obj) {
|
|
return applyMixin(obj, [this], true);
|
|
};
|
|
|
|
function _detect(curMixin, targetMixin, seen) {
|
|
var guid = guidFor(curMixin);
|
|
|
|
if (seen[guid]) { return false; }
|
|
seen[guid] = true;
|
|
|
|
if (curMixin === targetMixin) { return true; }
|
|
var mixins = curMixin.mixins;
|
|
var loc = mixins ? mixins.length : 0;
|
|
while (--loc >= 0) {
|
|
if (_detect(mixins[loc], targetMixin, seen)) { return true; }
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
@method detect
|
|
@param obj
|
|
@return {Boolean}
|
|
*/
|
|
MixinPrototype.detect = function(obj) {
|
|
if (!obj) { return false; }
|
|
if (obj instanceof Mixin) { return _detect(obj, this, {}); }
|
|
var m = obj['__ember_meta__'];
|
|
var mixins = m && m.mixins;
|
|
if (mixins) {
|
|
return !!mixins[guidFor(this)];
|
|
}
|
|
return false;
|
|
};
|
|
|
|
MixinPrototype.without = function() {
|
|
var ret = new Mixin([this]);
|
|
ret._without = a_slice.call(arguments);
|
|
return ret;
|
|
};
|
|
|
|
function _keys(ret, mixin, seen) {
|
|
if (seen[guidFor(mixin)]) { return; }
|
|
seen[guidFor(mixin)] = true;
|
|
|
|
if (mixin.properties) {
|
|
var props = mixin.properties;
|
|
for (var key in props) {
|
|
if (props.hasOwnProperty(key)) { ret[key] = true; }
|
|
}
|
|
} else if (mixin.mixins) {
|
|
a_forEach.call(mixin.mixins, function(x) { _keys(ret, x, seen); });
|
|
}
|
|
}
|
|
|
|
MixinPrototype.keys = function() {
|
|
var keys = {};
|
|
var seen = {};
|
|
var ret = [];
|
|
_keys(keys, this, seen);
|
|
for(var key in keys) {
|
|
if (keys.hasOwnProperty(key)) {
|
|
ret.push(key);
|
|
}
|
|
}
|
|
return ret;
|
|
};
|
|
|
|
// returns the mixins currently applied to the specified object
|
|
// TODO: Make Ember.mixin
|
|
Mixin.mixins = function(obj) {
|
|
var m = obj['__ember_meta__'];
|
|
var mixins = m && m.mixins;
|
|
var ret = [];
|
|
|
|
if (!mixins) { return ret; }
|
|
|
|
for (var key in mixins) {
|
|
var mixin = mixins[key];
|
|
|
|
// skip primitive mixins since these are always anonymous
|
|
if (!mixin.properties) { ret.push(mixin); }
|
|
}
|
|
|
|
return ret;
|
|
};
|
|
|
|
REQUIRED = new Descriptor();
|
|
REQUIRED.toString = function() { return '(Required Property)'; };
|
|
|
|
/**
|
|
Denotes a required property for a mixin
|
|
|
|
@method required
|
|
@for Ember
|
|
*/
|
|
function required() {
|
|
return REQUIRED;
|
|
}
|
|
|
|
__exports__.required = required;function Alias(methodName) {
|
|
this.methodName = methodName;
|
|
}
|
|
|
|
Alias.prototype = new Descriptor();
|
|
|
|
/**
|
|
Makes a method available via an additional name.
|
|
|
|
```javascript
|
|
App.Person = Ember.Object.extend({
|
|
name: function() {
|
|
return 'Tomhuda Katzdale';
|
|
},
|
|
moniker: Ember.aliasMethod('name')
|
|
});
|
|
|
|
var goodGuy = App.Person.create();
|
|
|
|
goodGuy.name(); // 'Tomhuda Katzdale'
|
|
goodGuy.moniker(); // 'Tomhuda Katzdale'
|
|
```
|
|
|
|
@method aliasMethod
|
|
@for Ember
|
|
@param {String} methodName name of the method to alias
|
|
@return {Ember.Descriptor}
|
|
*/
|
|
function aliasMethod(methodName) {
|
|
return new Alias(methodName);
|
|
}
|
|
|
|
__exports__.aliasMethod = aliasMethod;// ..........................................................
|
|
// OBSERVER HELPER
|
|
//
|
|
|
|
/**
|
|
Specify a method that observes property changes.
|
|
|
|
```javascript
|
|
Ember.Object.extend({
|
|
valueObserver: Ember.observer('value', function() {
|
|
// Executes whenever the "value" property changes
|
|
})
|
|
});
|
|
```
|
|
|
|
In the future this method may become asynchronous. If you want to ensure
|
|
synchronous behavior, use `immediateObserver`.
|
|
|
|
Also available as `Function.prototype.observes` if prototype extensions are
|
|
enabled.
|
|
|
|
@method observer
|
|
@for Ember
|
|
@param {String} propertyNames*
|
|
@param {Function} func
|
|
@return func
|
|
*/
|
|
function observer() {
|
|
var func = a_slice.call(arguments, -1)[0];
|
|
var paths;
|
|
|
|
var addWatchedProperty = function (path) { paths.push(path); };
|
|
var _paths = a_slice.call(arguments, 0, -1);
|
|
|
|
if (typeof func !== "function") {
|
|
// revert to old, soft-deprecated argument ordering
|
|
|
|
func = arguments[0];
|
|
_paths = a_slice.call(arguments, 1);
|
|
}
|
|
|
|
paths = [];
|
|
|
|
for (var i=0; i<_paths.length; ++i) {
|
|
expandProperties(_paths[i], addWatchedProperty);
|
|
}
|
|
|
|
if (typeof func !== "function") {
|
|
throw new Ember.Error("Ember.observer called without a function");
|
|
}
|
|
|
|
func.__ember_observes__ = paths;
|
|
return func;
|
|
}
|
|
|
|
__exports__.observer = observer;/**
|
|
Specify a method that observes property changes.
|
|
|
|
```javascript
|
|
Ember.Object.extend({
|
|
valueObserver: Ember.immediateObserver('value', function() {
|
|
// Executes whenever the "value" property changes
|
|
})
|
|
});
|
|
```
|
|
|
|
In the future, `Ember.observer` may become asynchronous. In this event,
|
|
`Ember.immediateObserver` will maintain the synchronous behavior.
|
|
|
|
Also available as `Function.prototype.observesImmediately` if prototype extensions are
|
|
enabled.
|
|
|
|
@method immediateObserver
|
|
@for Ember
|
|
@param {String} propertyNames*
|
|
@param {Function} func
|
|
@return func
|
|
*/
|
|
function immediateObserver() {
|
|
for (var i=0, l=arguments.length; i<l; i++) {
|
|
var arg = arguments[i];
|
|
Ember.assert("Immediate observers must observe internal properties only, not properties on other objects.",
|
|
typeof arg !== "string" || arg.indexOf('.') === -1);
|
|
}
|
|
|
|
return observer.apply(this, arguments);
|
|
}
|
|
|
|
__exports__.immediateObserver = immediateObserver;/**
|
|
When observers fire, they are called with the arguments `obj`, `keyName`.
|
|
|
|
Note, `@each.property` observer is called per each add or replace of an element
|
|
and it's not called with a specific enumeration item.
|
|
|
|
A `beforeObserver` fires before a property changes.
|
|
|
|
A `beforeObserver` is an alternative form of `.observesBefore()`.
|
|
|
|
```javascript
|
|
App.PersonView = Ember.View.extend({
|
|
friends: [{ name: 'Tom' }, { name: 'Stefan' }, { name: 'Kris' }],
|
|
|
|
valueWillChange: Ember.beforeObserver('content.value', function(obj, keyName) {
|
|
this.changingFrom = obj.get(keyName);
|
|
}),
|
|
|
|
valueDidChange: Ember.observer('content.value', function(obj, keyName) {
|
|
// only run if updating a value already in the DOM
|
|
if (this.get('state') === 'inDOM') {
|
|
var color = obj.get(keyName) > this.changingFrom ? 'green' : 'red';
|
|
// logic
|
|
}
|
|
}),
|
|
|
|
friendsDidChange: Ember.observer('friends.@each.name', function(obj, keyName) {
|
|
// some logic
|
|
// obj.get(keyName) returns friends array
|
|
})
|
|
});
|
|
```
|
|
|
|
Also available as `Function.prototype.observesBefore` if prototype extensions are
|
|
enabled.
|
|
|
|
@method beforeObserver
|
|
@for Ember
|
|
@param {String} propertyNames*
|
|
@param {Function} func
|
|
@return func
|
|
*/
|
|
function beforeObserver() {
|
|
var func = a_slice.call(arguments, -1)[0];
|
|
var paths;
|
|
|
|
var addWatchedProperty = function(path) { paths.push(path); };
|
|
|
|
var _paths = a_slice.call(arguments, 0, -1);
|
|
|
|
if (typeof func !== "function") {
|
|
// revert to old, soft-deprecated argument ordering
|
|
|
|
func = arguments[0];
|
|
_paths = a_slice.call(arguments, 1);
|
|
}
|
|
|
|
paths = [];
|
|
|
|
for (var i=0; i<_paths.length; ++i) {
|
|
expandProperties(_paths[i], addWatchedProperty);
|
|
}
|
|
|
|
if (typeof func !== "function") {
|
|
throw new Ember.Error("Ember.beforeObserver called without a function");
|
|
}
|
|
|
|
func.__ember_observesBefore__ = paths;
|
|
return func;
|
|
}
|
|
|
|
__exports__.beforeObserver = beforeObserver;__exports__.IS_BINDING = IS_BINDING;
|
|
__exports__.Mixin = Mixin;
|
|
});
|
|
enifed("ember-metal/observer",
|
|
["ember-metal/watching","ember-metal/array","ember-metal/events","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
var watch = __dependency1__.watch;
|
|
var unwatch = __dependency1__.unwatch;
|
|
var map = __dependency2__.map;
|
|
var listenersFor = __dependency3__.listenersFor;
|
|
var addListener = __dependency3__.addListener;
|
|
var removeListener = __dependency3__.removeListener;
|
|
var suspendListeners = __dependency3__.suspendListeners;
|
|
var suspendListener = __dependency3__.suspendListener;
|
|
/**
|
|
@module ember-metal
|
|
*/
|
|
|
|
var AFTER_OBSERVERS = ':change';
|
|
var BEFORE_OBSERVERS = ':before';
|
|
|
|
function changeEvent(keyName) {
|
|
return keyName + AFTER_OBSERVERS;
|
|
}
|
|
|
|
function beforeEvent(keyName) {
|
|
return keyName + BEFORE_OBSERVERS;
|
|
}
|
|
|
|
/**
|
|
@method addObserver
|
|
@for Ember
|
|
@param obj
|
|
@param {String} path
|
|
@param {Object|Function} targetOrMethod
|
|
@param {Function|String} [method]
|
|
*/
|
|
function addObserver(obj, _path, target, method) {
|
|
addListener(obj, changeEvent(_path), target, method);
|
|
watch(obj, _path);
|
|
|
|
return this;
|
|
}
|
|
|
|
__exports__.addObserver = addObserver;function observersFor(obj, path) {
|
|
return listenersFor(obj, changeEvent(path));
|
|
}
|
|
|
|
__exports__.observersFor = observersFor;/**
|
|
@method removeObserver
|
|
@for Ember
|
|
@param obj
|
|
@param {String} path
|
|
@param {Object|Function} target
|
|
@param {Function|String} [method]
|
|
*/
|
|
function removeObserver(obj, path, target, method) {
|
|
unwatch(obj, path);
|
|
removeListener(obj, changeEvent(path), target, method);
|
|
|
|
return this;
|
|
}
|
|
|
|
__exports__.removeObserver = removeObserver;/**
|
|
@method addBeforeObserver
|
|
@for Ember
|
|
@param obj
|
|
@param {String} path
|
|
@param {Object|Function} target
|
|
@param {Function|String} [method]
|
|
*/
|
|
function addBeforeObserver(obj, path, target, method) {
|
|
addListener(obj, beforeEvent(path), target, method);
|
|
watch(obj, path);
|
|
|
|
return this;
|
|
}
|
|
|
|
__exports__.addBeforeObserver = addBeforeObserver;// Suspend observer during callback.
|
|
//
|
|
// This should only be used by the target of the observer
|
|
// while it is setting the observed path.
|
|
function _suspendBeforeObserver(obj, path, target, method, callback) {
|
|
return suspendListener(obj, beforeEvent(path), target, method, callback);
|
|
}
|
|
|
|
__exports__._suspendBeforeObserver = _suspendBeforeObserver;function _suspendObserver(obj, path, target, method, callback) {
|
|
return suspendListener(obj, changeEvent(path), target, method, callback);
|
|
}
|
|
|
|
__exports__._suspendObserver = _suspendObserver;function _suspendBeforeObservers(obj, paths, target, method, callback) {
|
|
var events = map.call(paths, beforeEvent);
|
|
return suspendListeners(obj, events, target, method, callback);
|
|
}
|
|
|
|
__exports__._suspendBeforeObservers = _suspendBeforeObservers;function _suspendObservers(obj, paths, target, method, callback) {
|
|
var events = map.call(paths, changeEvent);
|
|
return suspendListeners(obj, events, target, method, callback);
|
|
}
|
|
|
|
__exports__._suspendObservers = _suspendObservers;function beforeObserversFor(obj, path) {
|
|
return listenersFor(obj, beforeEvent(path));
|
|
}
|
|
|
|
__exports__.beforeObserversFor = beforeObserversFor;/**
|
|
@method removeBeforeObserver
|
|
@for Ember
|
|
@param obj
|
|
@param {String} path
|
|
@param {Object|Function} target
|
|
@param {Function|String} [method]
|
|
*/
|
|
function removeBeforeObserver(obj, path, target, method) {
|
|
unwatch(obj, path);
|
|
removeListener(obj, beforeEvent(path), target, method);
|
|
|
|
return this;
|
|
}
|
|
|
|
__exports__.removeBeforeObserver = removeBeforeObserver;
|
|
});
|
|
enifed("ember-metal/observer_set",
|
|
["ember-metal/utils","ember-metal/events","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var guidFor = __dependency1__.guidFor;
|
|
var sendEvent = __dependency2__.sendEvent;
|
|
|
|
/*
|
|
this.observerSet = {
|
|
[senderGuid]: { // variable name: `keySet`
|
|
[keyName]: listIndex
|
|
}
|
|
},
|
|
this.observers = [
|
|
{
|
|
sender: obj,
|
|
keyName: keyName,
|
|
eventName: eventName,
|
|
listeners: [
|
|
[target, method, flags]
|
|
]
|
|
},
|
|
...
|
|
]
|
|
*/
|
|
__exports__["default"] = ObserverSet;
|
|
function ObserverSet() {
|
|
this.clear();
|
|
}
|
|
|
|
|
|
ObserverSet.prototype.add = function(sender, keyName, eventName) {
|
|
var observerSet = this.observerSet;
|
|
var observers = this.observers;
|
|
var senderGuid = guidFor(sender);
|
|
var keySet = observerSet[senderGuid];
|
|
var index;
|
|
|
|
if (!keySet) {
|
|
observerSet[senderGuid] = keySet = {};
|
|
}
|
|
index = keySet[keyName];
|
|
if (index === undefined) {
|
|
index = observers.push({
|
|
sender: sender,
|
|
keyName: keyName,
|
|
eventName: eventName,
|
|
listeners: []
|
|
}) - 1;
|
|
keySet[keyName] = index;
|
|
}
|
|
return observers[index].listeners;
|
|
};
|
|
|
|
ObserverSet.prototype.flush = function() {
|
|
var observers = this.observers;
|
|
var i, len, observer, sender;
|
|
this.clear();
|
|
for (i=0, len=observers.length; i < len; ++i) {
|
|
observer = observers[i];
|
|
sender = observer.sender;
|
|
if (sender.isDestroying || sender.isDestroyed) { continue; }
|
|
sendEvent(sender, observer.eventName, [sender, observer.keyName], observer.listeners);
|
|
}
|
|
};
|
|
|
|
ObserverSet.prototype.clear = function() {
|
|
this.observerSet = {};
|
|
this.observers = [];
|
|
};
|
|
});
|
|
enifed("ember-metal/path_cache",
|
|
["ember-metal/cache","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var Cache = __dependency1__["default"];
|
|
|
|
var IS_GLOBAL = /^([A-Z$]|([0-9][A-Z$]))/;
|
|
var IS_GLOBAL_PATH = /^([A-Z$]|([0-9][A-Z$])).*[\.]/;
|
|
var HAS_THIS = 'this.';
|
|
|
|
var isGlobalCache = new Cache(1000, function(key) { return IS_GLOBAL.test(key); });
|
|
var isGlobalPathCache = new Cache(1000, function(key) { return IS_GLOBAL_PATH.test(key); });
|
|
var hasThisCache = new Cache(1000, function(key) { return key.lastIndexOf(HAS_THIS, 0) === 0; });
|
|
var firstDotIndexCache = new Cache(1000, function(key) { return key.indexOf('.'); });
|
|
|
|
var firstKeyCache = new Cache(1000, function(path) {
|
|
var index = firstDotIndexCache.get(path);
|
|
if (index === -1) {
|
|
return path;
|
|
} else {
|
|
return path.slice(0, index);
|
|
}
|
|
});
|
|
|
|
var tailPathCache = new Cache(1000, function(path) {
|
|
var index = firstDotIndexCache.get(path);
|
|
if (index !== -1) {
|
|
return path.slice(index + 1);
|
|
}
|
|
});
|
|
|
|
var caches = {
|
|
isGlobalCache: isGlobalCache,
|
|
isGlobalPathCache: isGlobalPathCache,
|
|
hasThisCache: hasThisCache,
|
|
firstDotIndexCache: firstDotIndexCache,
|
|
firstKeyCache: firstKeyCache,
|
|
tailPathCache: tailPathCache
|
|
};
|
|
__exports__.caches = caches;
|
|
function isGlobal(path) {
|
|
return isGlobalCache.get(path);
|
|
}
|
|
|
|
__exports__.isGlobal = isGlobal;function isGlobalPath(path) {
|
|
return isGlobalPathCache.get(path);
|
|
}
|
|
|
|
__exports__.isGlobalPath = isGlobalPath;function hasThis(path) {
|
|
return hasThisCache.get(path);
|
|
}
|
|
|
|
__exports__.hasThis = hasThis;function isPath(path) {
|
|
return firstDotIndexCache.get(path) !== -1;
|
|
}
|
|
|
|
__exports__.isPath = isPath;function getFirstKey(path) {
|
|
return firstKeyCache.get(path);
|
|
}
|
|
|
|
__exports__.getFirstKey = getFirstKey;function getTailPath(path) {
|
|
return tailPathCache.get(path);
|
|
}
|
|
|
|
__exports__.getTailPath = getTailPath;
|
|
});
|
|
enifed("ember-metal/platform",
|
|
["ember-metal/platform/define_property","ember-metal/platform/define_properties","ember-metal/platform/create","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
var hasES5CompliantDefineProperty = __dependency1__.hasES5CompliantDefineProperty;
|
|
var defineProperty = __dependency1__.defineProperty;
|
|
var defineProperties = __dependency2__["default"];
|
|
var create = __dependency3__["default"];
|
|
|
|
/**
|
|
@module ember-metal
|
|
*/
|
|
|
|
var hasPropertyAccessors = hasES5CompliantDefineProperty;
|
|
var canDefineNonEnumerableProperties = hasES5CompliantDefineProperty;
|
|
|
|
/**
|
|
Platform specific methods and feature detectors needed by the framework.
|
|
|
|
@class platform
|
|
@namespace Ember
|
|
@static
|
|
*/
|
|
|
|
__exports__.create = create;
|
|
__exports__.defineProperty = defineProperty;
|
|
__exports__.defineProperties = defineProperties;
|
|
__exports__.hasPropertyAccessors = hasPropertyAccessors;
|
|
__exports__.canDefineNonEnumerableProperties = canDefineNonEnumerableProperties;
|
|
});
|
|
enifed("ember-metal/platform/create",
|
|
["ember-metal/platform/define_properties","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
// Remove "use strict"; from transpiled module until
|
|
// https://bugs.webkit.org/show_bug.cgi?id=138038 is fixed
|
|
//
|
|
// REMOVE_USE_STRICT: true
|
|
//
|
|
|
|
var defineProperties = __dependency1__["default"];
|
|
|
|
/**
|
|
@class platform
|
|
@namespace Ember
|
|
@static
|
|
*/
|
|
|
|
/**
|
|
Identical to `Object.create()`. Implements if not available natively.
|
|
|
|
@since 1.8.0
|
|
@method create
|
|
@for Ember
|
|
*/
|
|
var create;
|
|
// ES5 15.2.3.5
|
|
// http://es5.github.com/#x15.2.3.5
|
|
if (!(Object.create && !Object.create(null).hasOwnProperty)) {
|
|
/* jshint scripturl:true, proto:true */
|
|
// Contributed by Brandon Benvie, October, 2012
|
|
var createEmpty;
|
|
var supportsProto = !({'__proto__':null} instanceof Object);
|
|
// the following produces false positives
|
|
// in Opera Mini => not a reliable check
|
|
// Object.prototype.__proto__ === null
|
|
if (supportsProto || typeof document === 'undefined') {
|
|
createEmpty = function () {
|
|
return { "__proto__": null };
|
|
};
|
|
} else {
|
|
// In old IE __proto__ can't be used to manually set `null`, nor does
|
|
// any other method exist to make an object that inherits from nothing,
|
|
// aside from Object.prototype itself. Instead, create a new global
|
|
// object and *steal* its Object.prototype and strip it bare. This is
|
|
// used as the prototype to create nullary objects.
|
|
createEmpty = function () {
|
|
var iframe = document.createElement('iframe');
|
|
var parent = document.body || document.documentElement;
|
|
iframe.style.display = 'none';
|
|
parent.appendChild(iframe);
|
|
iframe.src = 'javascript:';
|
|
var empty = iframe.contentWindow.Object.prototype;
|
|
parent.removeChild(iframe);
|
|
iframe = null;
|
|
delete empty.constructor;
|
|
delete empty.hasOwnProperty;
|
|
delete empty.propertyIsEnumerable;
|
|
delete empty.isPrototypeOf;
|
|
delete empty.toLocaleString;
|
|
delete empty.toString;
|
|
delete empty.valueOf;
|
|
|
|
function Empty() {}
|
|
Empty.prototype = empty;
|
|
// short-circuit future calls
|
|
createEmpty = function () {
|
|
return new Empty();
|
|
};
|
|
return new Empty();
|
|
};
|
|
}
|
|
|
|
create = Object.create = function create(prototype, properties) {
|
|
|
|
var object;
|
|
function Type() {} // An empty constructor.
|
|
|
|
if (prototype === null) {
|
|
object = createEmpty();
|
|
} else {
|
|
if (typeof prototype !== "object" && typeof prototype !== "function") {
|
|
// In the native implementation `parent` can be `null`
|
|
// OR *any* `instanceof Object` (Object|Function|Array|RegExp|etc)
|
|
// Use `typeof` tho, b/c in old IE, DOM elements are not `instanceof Object`
|
|
// like they are in modern browsers. Using `Object.create` on DOM elements
|
|
// is...err...probably inappropriate, but the native version allows for it.
|
|
throw new TypeError("Object prototype may only be an Object or null"); // same msg as Chrome
|
|
}
|
|
|
|
Type.prototype = prototype;
|
|
|
|
object = new Type();
|
|
}
|
|
|
|
if (properties !== undefined) {
|
|
defineProperties(object, properties);
|
|
}
|
|
|
|
return object;
|
|
};
|
|
} else {
|
|
create = Object.create;
|
|
}
|
|
|
|
__exports__["default"] = create;
|
|
});
|
|
enifed("ember-metal/platform/define_properties",
|
|
["ember-metal/platform/define_property","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var defineProperty = __dependency1__.defineProperty;
|
|
|
|
var defineProperties = Object.defineProperties;
|
|
|
|
// ES5 15.2.3.7
|
|
// http://es5.github.com/#x15.2.3.7
|
|
if (!defineProperties) {
|
|
defineProperties = function defineProperties(object, properties) {
|
|
for (var property in properties) {
|
|
if (properties.hasOwnProperty(property) && property !== "__proto__") {
|
|
defineProperty(object, property, properties[property]);
|
|
}
|
|
}
|
|
return object;
|
|
};
|
|
|
|
Object.defineProperties = defineProperties;
|
|
}
|
|
|
|
__exports__["default"] = defineProperties;
|
|
});
|
|
enifed("ember-metal/platform/define_property",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
/*globals Node */
|
|
|
|
/**
|
|
@class platform
|
|
@namespace Ember
|
|
@static
|
|
*/
|
|
|
|
/**
|
|
Set to true if the platform supports native getters and setters.
|
|
|
|
@property hasPropertyAccessors
|
|
@final
|
|
*/
|
|
|
|
/**
|
|
Identical to `Object.defineProperty()`. Implements as much functionality
|
|
as possible if not available natively.
|
|
|
|
@method defineProperty
|
|
@param {Object} obj The object to modify
|
|
@param {String} keyName property name to modify
|
|
@param {Object} desc descriptor hash
|
|
@return {void}
|
|
*/
|
|
var defineProperty = (function checkCompliance(defineProperty) {
|
|
if (!defineProperty) return;
|
|
try {
|
|
var a = 5;
|
|
var obj = {};
|
|
defineProperty(obj, 'a', {
|
|
configurable: true,
|
|
enumerable: true,
|
|
get: function () {
|
|
return a;
|
|
},
|
|
set: function (v) {
|
|
a = v;
|
|
}
|
|
});
|
|
if (obj.a !== 5) return;
|
|
obj.a = 10;
|
|
if (a !== 10) return;
|
|
|
|
// check non-enumerability
|
|
defineProperty(obj, 'a', {
|
|
configurable: true,
|
|
enumerable: false,
|
|
writable: true,
|
|
value: true
|
|
});
|
|
for (var key in obj) {
|
|
if (key === 'a') return;
|
|
}
|
|
|
|
// Detects a bug in Android <3.2 where you cannot redefine a property using
|
|
// Object.defineProperty once accessors have already been set.
|
|
if (obj.a !== true) return;
|
|
|
|
// Detects a bug in Android <3 where redefining a property without a value changes the value
|
|
// Object.defineProperty once accessors have already been set.
|
|
defineProperty(obj, 'a', {
|
|
enumerable: false
|
|
});
|
|
if (obj.a !== true) return;
|
|
|
|
// defineProperty is compliant
|
|
return defineProperty;
|
|
} catch (e) {
|
|
// IE8 defines Object.defineProperty but calling it on an Object throws
|
|
return;
|
|
}
|
|
})(Object.defineProperty);
|
|
|
|
var hasES5CompliantDefineProperty = !!defineProperty;
|
|
|
|
if (hasES5CompliantDefineProperty && typeof document !== 'undefined') {
|
|
// This is for Safari 5.0, which supports Object.defineProperty, but not
|
|
// on DOM nodes.
|
|
var canDefinePropertyOnDOM = (function() {
|
|
try {
|
|
defineProperty(document.createElement('div'), 'definePropertyOnDOM', {});
|
|
return true;
|
|
} catch(e) { }
|
|
|
|
return false;
|
|
})();
|
|
|
|
if (!canDefinePropertyOnDOM) {
|
|
defineProperty = function(obj, keyName, desc) {
|
|
var isNode;
|
|
|
|
if (typeof Node === "object") {
|
|
isNode = obj instanceof Node;
|
|
} else {
|
|
isNode = typeof obj === "object" && typeof obj.nodeType === "number" && typeof obj.nodeName === "string";
|
|
}
|
|
|
|
if (isNode) {
|
|
// TODO: Should we have a warning here?
|
|
return (obj[keyName] = desc.value);
|
|
} else {
|
|
return Object.defineProperty(obj, keyName, desc);
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
if (!hasES5CompliantDefineProperty) {
|
|
defineProperty = function defineProperty(obj, keyName, desc) {
|
|
if (!desc.get) { obj[keyName] = desc.value; }
|
|
};
|
|
}
|
|
|
|
__exports__.hasES5CompliantDefineProperty = hasES5CompliantDefineProperty;
|
|
__exports__.defineProperty = defineProperty;
|
|
});
|
|
enifed("ember-metal/properties",
|
|
["ember-metal/core","ember-metal/utils","ember-metal/platform","ember-metal/property_events","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember-metal
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
var metaFor = __dependency2__.meta;
|
|
var objectDefineProperty = __dependency3__.defineProperty;
|
|
var hasPropertyAccessors = __dependency3__.hasPropertyAccessors;
|
|
var overrideChains = __dependency4__.overrideChains;
|
|
// ..........................................................
|
|
// DESCRIPTOR
|
|
//
|
|
|
|
/**
|
|
Objects of this type can implement an interface to respond to requests to
|
|
get and set. The default implementation handles simple properties.
|
|
|
|
You generally won't need to create or subclass this directly.
|
|
|
|
@class Descriptor
|
|
@namespace Ember
|
|
@private
|
|
@constructor
|
|
*/
|
|
function Descriptor() {}
|
|
|
|
__exports__.Descriptor = Descriptor;// ..........................................................
|
|
// DEFINING PROPERTIES API
|
|
//
|
|
|
|
function MANDATORY_SETTER_FUNCTION(name) {
|
|
return function SETTER_FUNCTION(value) {
|
|
Ember.assert("You must use Ember.set() to set the `" + name + "` property (of " + this + ") to `" + value + "`.", false);
|
|
};
|
|
}
|
|
|
|
__exports__.MANDATORY_SETTER_FUNCTION = MANDATORY_SETTER_FUNCTION;function DEFAULT_GETTER_FUNCTION(name) {
|
|
return function GETTER_FUNCTION() {
|
|
var meta = this['__ember_meta__'];
|
|
return meta && meta.values[name];
|
|
};
|
|
}
|
|
|
|
__exports__.DEFAULT_GETTER_FUNCTION = DEFAULT_GETTER_FUNCTION;/**
|
|
NOTE: This is a low-level method used by other parts of the API. You almost
|
|
never want to call this method directly. Instead you should use
|
|
`Ember.mixin()` to define new properties.
|
|
|
|
Defines a property on an object. This method works much like the ES5
|
|
`Object.defineProperty()` method except that it can also accept computed
|
|
properties and other special descriptors.
|
|
|
|
Normally this method takes only three parameters. However if you pass an
|
|
instance of `Ember.Descriptor` as the third param then you can pass an
|
|
optional value as the fourth parameter. This is often more efficient than
|
|
creating new descriptor hashes for each property.
|
|
|
|
## Examples
|
|
|
|
```javascript
|
|
// ES5 compatible mode
|
|
Ember.defineProperty(contact, 'firstName', {
|
|
writable: true,
|
|
configurable: false,
|
|
enumerable: true,
|
|
value: 'Charles'
|
|
});
|
|
|
|
// define a simple property
|
|
Ember.defineProperty(contact, 'lastName', undefined, 'Jolley');
|
|
|
|
// define a computed property
|
|
Ember.defineProperty(contact, 'fullName', Ember.computed(function() {
|
|
return this.firstName+' '+this.lastName;
|
|
}).property('firstName', 'lastName'));
|
|
```
|
|
|
|
@private
|
|
@method defineProperty
|
|
@for Ember
|
|
@param {Object} obj the object to define this property on. This may be a prototype.
|
|
@param {String} keyName the name of the property
|
|
@param {Ember.Descriptor} [desc] an instance of `Ember.Descriptor` (typically a
|
|
computed property) or an ES5 descriptor.
|
|
You must provide this or `data` but not both.
|
|
@param {*} [data] something other than a descriptor, that will
|
|
become the explicit value of this property.
|
|
*/
|
|
function defineProperty(obj, keyName, desc, data, meta) {
|
|
var descs, existingDesc, watching, value;
|
|
|
|
if (!meta) meta = metaFor(obj);
|
|
descs = meta.descs;
|
|
existingDesc = meta.descs[keyName];
|
|
var watchEntry = meta.watching[keyName];
|
|
|
|
watching = watchEntry !== undefined && watchEntry > 0;
|
|
|
|
if (existingDesc instanceof Descriptor) {
|
|
existingDesc.teardown(obj, keyName);
|
|
}
|
|
|
|
if (desc instanceof Descriptor) {
|
|
value = desc;
|
|
|
|
descs[keyName] = desc;
|
|
|
|
if (watching && hasPropertyAccessors) {
|
|
objectDefineProperty(obj, keyName, {
|
|
configurable: true,
|
|
enumerable: true,
|
|
writable: true,
|
|
value: undefined // make enumerable
|
|
});
|
|
} else {
|
|
obj[keyName] = undefined; // make enumerable
|
|
}
|
|
if (desc.setup) { desc.setup(obj, keyName); }
|
|
} else {
|
|
descs[keyName] = undefined; // shadow descriptor in proto
|
|
if (desc == null) {
|
|
value = data;
|
|
|
|
|
|
if (watching && hasPropertyAccessors) {
|
|
meta.values[keyName] = data;
|
|
objectDefineProperty(obj, keyName, {
|
|
configurable: true,
|
|
enumerable: true,
|
|
set: MANDATORY_SETTER_FUNCTION(keyName),
|
|
get: DEFAULT_GETTER_FUNCTION(keyName)
|
|
});
|
|
} else {
|
|
obj[keyName] = data;
|
|
}
|
|
} else {
|
|
value = desc;
|
|
|
|
// compatibility with ES5
|
|
objectDefineProperty(obj, keyName, desc);
|
|
}
|
|
}
|
|
|
|
// if key is being watched, override chains that
|
|
// were initialized with the prototype
|
|
if (watching) { overrideChains(obj, keyName, meta); }
|
|
|
|
// The `value` passed to the `didDefineProperty` hook is
|
|
// either the descriptor or data, whichever was passed.
|
|
if (obj.didDefineProperty) { obj.didDefineProperty(obj, keyName, value); }
|
|
|
|
return this;
|
|
}
|
|
|
|
__exports__.defineProperty = defineProperty;
|
|
});
|
|
enifed("ember-metal/property_events",
|
|
["ember-metal/utils","ember-metal/events","ember-metal/observer_set","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
var guidFor = __dependency1__.guidFor;
|
|
var tryFinally = __dependency1__.tryFinally;
|
|
var sendEvent = __dependency2__.sendEvent;
|
|
var accumulateListeners = __dependency2__.accumulateListeners;
|
|
var ObserverSet = __dependency3__["default"];
|
|
|
|
var beforeObserverSet = new ObserverSet();
|
|
var observerSet = new ObserverSet();
|
|
var deferred = 0;
|
|
|
|
// ..........................................................
|
|
// PROPERTY CHANGES
|
|
//
|
|
|
|
/**
|
|
This function is called just before an object property is about to change.
|
|
It will notify any before observers and prepare caches among other things.
|
|
|
|
Normally you will not need to call this method directly but if for some
|
|
reason you can't directly watch a property you can invoke this method
|
|
manually along with `Ember.propertyDidChange()` which you should call just
|
|
after the property value changes.
|
|
|
|
@method propertyWillChange
|
|
@for Ember
|
|
@param {Object} obj The object with the property that will change
|
|
@param {String} keyName The property key (or path) that will change.
|
|
@return {void}
|
|
*/
|
|
function propertyWillChange(obj, keyName) {
|
|
var m = obj['__ember_meta__'];
|
|
var watching = (m && m.watching[keyName] > 0) || keyName === 'length';
|
|
var proto = m && m.proto;
|
|
var desc = m && m.descs[keyName];
|
|
|
|
if (!watching) {
|
|
return;
|
|
}
|
|
|
|
if (proto === obj) {
|
|
return;
|
|
}
|
|
|
|
if (desc && desc.willChange) {
|
|
desc.willChange(obj, keyName);
|
|
}
|
|
|
|
dependentKeysWillChange(obj, keyName, m);
|
|
chainsWillChange(obj, keyName, m);
|
|
notifyBeforeObservers(obj, keyName);
|
|
}
|
|
|
|
/**
|
|
This function is called just after an object property has changed.
|
|
It will notify any observers and clear caches among other things.
|
|
|
|
Normally you will not need to call this method directly but if for some
|
|
reason you can't directly watch a property you can invoke this method
|
|
manually along with `Ember.propertyWillChange()` which you should call just
|
|
before the property value changes.
|
|
|
|
@method propertyDidChange
|
|
@for Ember
|
|
@param {Object} obj The object with the property that will change
|
|
@param {String} keyName The property key (or path) that will change.
|
|
@return {void}
|
|
*/
|
|
function propertyDidChange(obj, keyName) {
|
|
var m = obj['__ember_meta__'];
|
|
var watching = (m && m.watching[keyName] > 0) || keyName === 'length';
|
|
var proto = m && m.proto;
|
|
var desc = m && m.descs[keyName];
|
|
|
|
if (proto === obj) {
|
|
return;
|
|
}
|
|
|
|
// shouldn't this mean that we're watching this key?
|
|
if (desc && desc.didChange) {
|
|
desc.didChange(obj, keyName);
|
|
}
|
|
|
|
if (!watching && keyName !== 'length') {
|
|
return;
|
|
}
|
|
|
|
if (m && m.deps && m.deps[keyName]) {
|
|
dependentKeysDidChange(obj, keyName, m);
|
|
}
|
|
|
|
chainsDidChange(obj, keyName, m, false);
|
|
notifyObservers(obj, keyName);
|
|
}
|
|
|
|
var WILL_SEEN, DID_SEEN;
|
|
// called whenever a property is about to change to clear the cache of any dependent keys (and notify those properties of changes, etc...)
|
|
function dependentKeysWillChange(obj, depKey, meta) {
|
|
if (obj.isDestroying) { return; }
|
|
|
|
var deps;
|
|
if (meta && meta.deps && (deps = meta.deps[depKey])) {
|
|
var seen = WILL_SEEN;
|
|
var top = !seen;
|
|
|
|
if (top) {
|
|
seen = WILL_SEEN = {};
|
|
}
|
|
|
|
iterDeps(propertyWillChange, obj, deps, depKey, seen, meta);
|
|
|
|
if (top) {
|
|
WILL_SEEN = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// called whenever a property has just changed to update dependent keys
|
|
function dependentKeysDidChange(obj, depKey, meta) {
|
|
if (obj.isDestroying) { return; }
|
|
|
|
var deps;
|
|
if (meta && meta.deps && (deps = meta.deps[depKey])) {
|
|
var seen = DID_SEEN;
|
|
var top = !seen;
|
|
|
|
if (top) {
|
|
seen = DID_SEEN = {};
|
|
}
|
|
|
|
iterDeps(propertyDidChange, obj, deps, depKey, seen, meta);
|
|
|
|
if (top) {
|
|
DID_SEEN = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
function keysOf(obj) {
|
|
var keys = [];
|
|
|
|
for (var key in obj) {
|
|
keys.push(key);
|
|
}
|
|
|
|
return keys;
|
|
}
|
|
|
|
function iterDeps(method, obj, deps, depKey, seen, meta) {
|
|
var keys, key, i, desc;
|
|
var guid = guidFor(obj);
|
|
var current = seen[guid];
|
|
|
|
if (!current) {
|
|
current = seen[guid] = {};
|
|
}
|
|
|
|
if (current[depKey]) {
|
|
return;
|
|
}
|
|
|
|
current[depKey] = true;
|
|
|
|
if (deps) {
|
|
keys = keysOf(deps);
|
|
var descs = meta.descs;
|
|
for (i=0; i<keys.length; i++) {
|
|
key = keys[i];
|
|
desc = descs[key];
|
|
|
|
if (desc && desc._suspended === obj) {
|
|
continue;
|
|
}
|
|
|
|
method(obj, key);
|
|
}
|
|
}
|
|
}
|
|
|
|
function chainsWillChange(obj, keyName, m) {
|
|
if (!(m.hasOwnProperty('chainWatchers') &&
|
|
m.chainWatchers[keyName])) {
|
|
return;
|
|
}
|
|
|
|
var nodes = m.chainWatchers[keyName];
|
|
var events = [];
|
|
var i, l;
|
|
|
|
for(i = 0, l = nodes.length; i < l; i++) {
|
|
nodes[i].willChange(events);
|
|
}
|
|
|
|
for (i = 0, l = events.length; i < l; i += 2) {
|
|
propertyWillChange(events[i], events[i+1]);
|
|
}
|
|
}
|
|
|
|
function chainsDidChange(obj, keyName, m, suppressEvents) {
|
|
if (!(m && m.hasOwnProperty('chainWatchers') &&
|
|
m.chainWatchers[keyName])) {
|
|
return;
|
|
}
|
|
|
|
var nodes = m.chainWatchers[keyName];
|
|
var events = suppressEvents ? null : [];
|
|
var i, l;
|
|
|
|
for(i = 0, l = nodes.length; i < l; i++) {
|
|
nodes[i].didChange(events);
|
|
}
|
|
|
|
if (suppressEvents) {
|
|
return;
|
|
}
|
|
|
|
for (i = 0, l = events.length; i < l; i += 2) {
|
|
propertyDidChange(events[i], events[i+1]);
|
|
}
|
|
}
|
|
|
|
function overrideChains(obj, keyName, m) {
|
|
chainsDidChange(obj, keyName, m, true);
|
|
}
|
|
|
|
/**
|
|
@method beginPropertyChanges
|
|
@chainable
|
|
@private
|
|
*/
|
|
function beginPropertyChanges() {
|
|
deferred++;
|
|
}
|
|
|
|
/**
|
|
@method endPropertyChanges
|
|
@private
|
|
*/
|
|
function endPropertyChanges() {
|
|
deferred--;
|
|
if (deferred<=0) {
|
|
beforeObserverSet.clear();
|
|
observerSet.flush();
|
|
}
|
|
}
|
|
|
|
/**
|
|
Make a series of property changes together in an
|
|
exception-safe way.
|
|
|
|
```javascript
|
|
Ember.changeProperties(function() {
|
|
obj1.set('foo', mayBlowUpWhenSet);
|
|
obj2.set('bar', baz);
|
|
});
|
|
```
|
|
|
|
@method changeProperties
|
|
@param {Function} callback
|
|
@param [binding]
|
|
*/
|
|
function changeProperties(callback, binding) {
|
|
beginPropertyChanges();
|
|
tryFinally(callback, endPropertyChanges, binding);
|
|
}
|
|
|
|
function notifyBeforeObservers(obj, keyName) {
|
|
if (obj.isDestroying) { return; }
|
|
|
|
var eventName = keyName + ':before';
|
|
var listeners, added;
|
|
if (deferred) {
|
|
listeners = beforeObserverSet.add(obj, keyName, eventName);
|
|
added = accumulateListeners(obj, eventName, listeners);
|
|
sendEvent(obj, eventName, [obj, keyName], added);
|
|
} else {
|
|
sendEvent(obj, eventName, [obj, keyName]);
|
|
}
|
|
}
|
|
|
|
function notifyObservers(obj, keyName) {
|
|
if (obj.isDestroying) { return; }
|
|
|
|
var eventName = keyName + ':change';
|
|
var listeners;
|
|
if (deferred) {
|
|
listeners = observerSet.add(obj, keyName, eventName);
|
|
accumulateListeners(obj, eventName, listeners);
|
|
} else {
|
|
sendEvent(obj, eventName, [obj, keyName]);
|
|
}
|
|
}
|
|
|
|
__exports__.propertyWillChange = propertyWillChange;
|
|
__exports__.propertyDidChange = propertyDidChange;
|
|
__exports__.overrideChains = overrideChains;
|
|
__exports__.beginPropertyChanges = beginPropertyChanges;
|
|
__exports__.endPropertyChanges = endPropertyChanges;
|
|
__exports__.changeProperties = changeProperties;
|
|
});
|
|
enifed("ember-metal/property_get",
|
|
["ember-metal/core","ember-metal/error","ember-metal/path_cache","ember-metal/platform","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember-metal
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
var EmberError = __dependency2__["default"];
|
|
var isGlobalPath = __dependency3__.isGlobalPath;
|
|
var isPath = __dependency3__.isPath;
|
|
var pathHasThis = __dependency3__.hasThis;
|
|
var hasPropertyAccessors = __dependency4__.hasPropertyAccessors;
|
|
|
|
var FIRST_KEY = /^([^\.]+)/;
|
|
|
|
// ..........................................................
|
|
// GET AND SET
|
|
//
|
|
// If we are on a platform that supports accessors we can use those.
|
|
// Otherwise simulate accessors by looking up the property directly on the
|
|
// object.
|
|
|
|
/**
|
|
Gets the value of a property on an object. If the property is computed,
|
|
the function will be invoked. If the property is not defined but the
|
|
object implements the `unknownProperty` method then that will be invoked.
|
|
|
|
If you plan to run on IE8 and older browsers then you should use this
|
|
method anytime you want to retrieve a property on an object that you don't
|
|
know for sure is private. (Properties beginning with an underscore '_'
|
|
are considered private.)
|
|
|
|
On all newer browsers, you only need to use this method to retrieve
|
|
properties if the property might not be defined on the object and you want
|
|
to respect the `unknownProperty` handler. Otherwise you can ignore this
|
|
method.
|
|
|
|
Note that if the object itself is `undefined`, this method will throw
|
|
an error.
|
|
|
|
@method get
|
|
@for Ember
|
|
@param {Object} obj The object to retrieve from.
|
|
@param {String} keyName The property key to retrieve
|
|
@return {Object} the property value or `null`.
|
|
*/
|
|
var get = function get(obj, keyName) {
|
|
// Helpers that operate with 'this' within an #each
|
|
if (keyName === '') {
|
|
return obj;
|
|
}
|
|
|
|
if (!keyName && 'string' === typeof obj) {
|
|
keyName = obj;
|
|
obj = null;
|
|
}
|
|
|
|
Ember.assert("Cannot call get with "+ keyName +" key.", !!keyName);
|
|
Ember.assert("Cannot call get with '"+ keyName +"' on an undefined object.", obj !== undefined);
|
|
|
|
if (obj === null) {
|
|
var value = _getPath(obj, keyName);
|
|
Ember.deprecate(
|
|
"Ember.get fetched '"+keyName+"' from the global context. This behavior will change in the future (issue #3852)",
|
|
!value || (obj && obj !== Ember.lookup) || isPath(keyName) || isGlobalPath(keyName+".") // Add a . to ensure simple paths are matched.
|
|
);
|
|
return value;
|
|
}
|
|
|
|
var meta = obj['__ember_meta__'];
|
|
var desc = meta && meta.descs[keyName];
|
|
var ret;
|
|
|
|
if (desc === undefined && isPath(keyName)) {
|
|
return _getPath(obj, keyName);
|
|
}
|
|
|
|
if (desc) {
|
|
return desc.get(obj, keyName);
|
|
} else {
|
|
|
|
if (hasPropertyAccessors && meta && meta.watching[keyName] > 0) {
|
|
ret = meta.values[keyName];
|
|
} else {
|
|
ret = obj[keyName];
|
|
}
|
|
|
|
if (ret === undefined &&
|
|
'object' === typeof obj && !(keyName in obj) && 'function' === typeof obj.unknownProperty) {
|
|
return obj.unknownProperty(keyName);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
};
|
|
|
|
/**
|
|
Normalizes a target/path pair to reflect that actual target/path that should
|
|
be observed, etc. This takes into account passing in global property
|
|
paths (i.e. a path beginning with a capital letter not defined on the
|
|
target).
|
|
|
|
@private
|
|
@method normalizeTuple
|
|
@for Ember
|
|
@param {Object} target The current target. May be `null`.
|
|
@param {String} path A path on the target or a global property path.
|
|
@return {Array} a temporary array with the normalized target/path pair.
|
|
*/
|
|
function normalizeTuple(target, path) {
|
|
var hasThis = pathHasThis(path);
|
|
var isGlobal = !hasThis && isGlobalPath(path);
|
|
var key;
|
|
|
|
if (!target || isGlobal) target = Ember.lookup;
|
|
if (hasThis) path = path.slice(5);
|
|
|
|
Ember.deprecate(
|
|
"normalizeTuple will return '"+path+"' as a non-global. This behavior will change in the future (issue #3852)",
|
|
target === Ember.lookup || !target || hasThis || isGlobal || !isGlobalPath(path+'.')
|
|
);
|
|
|
|
if (target === Ember.lookup) {
|
|
key = path.match(FIRST_KEY)[0];
|
|
target = get(target, key);
|
|
path = path.slice(key.length+1);
|
|
}
|
|
|
|
// must return some kind of path to be valid else other things will break.
|
|
if (!path || path.length===0) throw new EmberError('Path cannot be empty');
|
|
|
|
return [ target, path ];
|
|
}
|
|
|
|
function _getPath(root, path) {
|
|
var hasThis, parts, tuple, idx, len;
|
|
|
|
// If there is no root and path is a key name, return that
|
|
// property from the global object.
|
|
// E.g. get('Ember') -> Ember
|
|
if (root === null && !isPath(path)) {
|
|
return get(Ember.lookup, path);
|
|
}
|
|
|
|
// detect complicated paths and normalize them
|
|
hasThis = pathHasThis(path);
|
|
|
|
if (!root || hasThis) {
|
|
tuple = normalizeTuple(root, path);
|
|
root = tuple[0];
|
|
path = tuple[1];
|
|
tuple.length = 0;
|
|
}
|
|
|
|
parts = path.split(".");
|
|
len = parts.length;
|
|
for (idx = 0; root != null && idx < len; idx++) {
|
|
root = get(root, parts[idx], true);
|
|
if (root && root.isDestroyed) { return undefined; }
|
|
}
|
|
return root;
|
|
}
|
|
|
|
function getWithDefault(root, key, defaultValue) {
|
|
var value = get(root, key);
|
|
|
|
if (value === undefined) { return defaultValue; }
|
|
return value;
|
|
}
|
|
|
|
__exports__.getWithDefault = getWithDefault;__exports__["default"] = get;
|
|
__exports__.get = get;
|
|
__exports__.normalizeTuple = normalizeTuple;
|
|
__exports__._getPath = _getPath;
|
|
});
|
|
enifed("ember-metal/property_set",
|
|
["ember-metal/core","ember-metal/property_get","ember-metal/property_events","ember-metal/properties","ember-metal/error","ember-metal/path_cache","ember-metal/platform","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
var getPath = __dependency2__._getPath;
|
|
var propertyWillChange = __dependency3__.propertyWillChange;
|
|
var propertyDidChange = __dependency3__.propertyDidChange;
|
|
var defineProperty = __dependency4__.defineProperty;
|
|
var EmberError = __dependency5__["default"];
|
|
var isPath = __dependency6__.isPath;
|
|
var hasPropertyAccessors = __dependency7__.hasPropertyAccessors;
|
|
|
|
var IS_GLOBAL = /^([A-Z$]|([0-9][A-Z$]))/;
|
|
|
|
/**
|
|
Sets the value of a property on an object, respecting computed properties
|
|
and notifying observers and other listeners of the change. If the
|
|
property is not defined but the object implements the `setUnknownProperty`
|
|
method then that will be invoked as well.
|
|
|
|
@method set
|
|
@for Ember
|
|
@param {Object} obj The object to modify.
|
|
@param {String} keyName The property key to set
|
|
@param {Object} value The value to set
|
|
@return {Object} the passed value.
|
|
*/
|
|
var set = function set(obj, keyName, value, tolerant) {
|
|
if (typeof obj === 'string') {
|
|
Ember.assert("Path '" + obj + "' must be global if no obj is given.", IS_GLOBAL.test(obj));
|
|
value = keyName;
|
|
keyName = obj;
|
|
obj = null;
|
|
}
|
|
|
|
Ember.assert("Cannot call set with "+ keyName +" key.", !!keyName);
|
|
|
|
if (!obj) {
|
|
return setPath(obj, keyName, value, tolerant);
|
|
}
|
|
|
|
var meta = obj['__ember_meta__'];
|
|
var desc = meta && meta.descs[keyName];
|
|
var isUnknown, currentValue;
|
|
|
|
if (desc === undefined && isPath(keyName)) {
|
|
return setPath(obj, keyName, value, tolerant);
|
|
}
|
|
|
|
Ember.assert("You need to provide an object and key to `set`.", !!obj && keyName !== undefined);
|
|
Ember.assert('calling set on destroyed object', !obj.isDestroyed);
|
|
|
|
if (desc !== undefined) {
|
|
desc.set(obj, keyName, value);
|
|
} else {
|
|
|
|
if (typeof obj === 'object' && obj !== null && value !== undefined && obj[keyName] === value) {
|
|
return value;
|
|
}
|
|
|
|
isUnknown = 'object' === typeof obj && !(keyName in obj);
|
|
|
|
// setUnknownProperty is called if `obj` is an object,
|
|
// the property does not already exist, and the
|
|
// `setUnknownProperty` method exists on the object
|
|
if (isUnknown && 'function' === typeof obj.setUnknownProperty) {
|
|
obj.setUnknownProperty(keyName, value);
|
|
} else if (meta && meta.watching[keyName] > 0) {
|
|
if (meta.proto !== obj) {
|
|
|
|
if (hasPropertyAccessors) {
|
|
currentValue = meta.values[keyName];
|
|
} else {
|
|
currentValue = obj[keyName];
|
|
}
|
|
}
|
|
// only trigger a change if the value has changed
|
|
if (value !== currentValue) {
|
|
propertyWillChange(obj, keyName);
|
|
|
|
if (hasPropertyAccessors) {
|
|
if (
|
|
(currentValue === undefined && !(keyName in obj)) ||
|
|
!Object.prototype.propertyIsEnumerable.call(obj, keyName)
|
|
) {
|
|
defineProperty(obj, keyName, null, value); // setup mandatory setter
|
|
} else {
|
|
meta.values[keyName] = value;
|
|
}
|
|
} else {
|
|
obj[keyName] = value;
|
|
}
|
|
propertyDidChange(obj, keyName);
|
|
}
|
|
} else {
|
|
obj[keyName] = value;
|
|
}
|
|
}
|
|
return value;
|
|
};
|
|
|
|
function setPath(root, path, value, tolerant) {
|
|
var keyName;
|
|
|
|
// get the last part of the path
|
|
keyName = path.slice(path.lastIndexOf('.') + 1);
|
|
|
|
// get the first part of the part
|
|
path = (path === keyName) ? keyName : path.slice(0, path.length-(keyName.length+1));
|
|
|
|
// unless the path is this, look up the first part to
|
|
// get the root
|
|
if (path !== 'this') {
|
|
root = getPath(root, path);
|
|
}
|
|
|
|
if (!keyName || keyName.length === 0) {
|
|
throw new EmberError('Property set failed: You passed an empty path');
|
|
}
|
|
|
|
if (!root) {
|
|
if (tolerant) { return; }
|
|
else { throw new EmberError('Property set failed: object in path "'+path+'" could not be found or was destroyed.'); }
|
|
}
|
|
|
|
return set(root, keyName, value);
|
|
}
|
|
|
|
/**
|
|
Error-tolerant form of `Ember.set`. Will not blow up if any part of the
|
|
chain is `undefined`, `null`, or destroyed.
|
|
|
|
This is primarily used when syncing bindings, which may try to update after
|
|
an object has been destroyed.
|
|
|
|
@method trySet
|
|
@for Ember
|
|
@param {Object} obj The object to modify.
|
|
@param {String} path The property path to set
|
|
@param {Object} value The value to set
|
|
*/
|
|
function trySet(root, path, value) {
|
|
return set(root, path, value, true);
|
|
}
|
|
|
|
__exports__.trySet = trySet;__exports__.set = set;
|
|
});
|
|
enifed("ember-metal/run_loop",
|
|
["ember-metal/core","ember-metal/utils","ember-metal/array","ember-metal/property_events","backburner","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
var apply = __dependency2__.apply;
|
|
var GUID_KEY = __dependency2__.GUID_KEY;
|
|
var indexOf = __dependency3__.indexOf;
|
|
var beginPropertyChanges = __dependency4__.beginPropertyChanges;
|
|
var endPropertyChanges = __dependency4__.endPropertyChanges;
|
|
var Backburner = __dependency5__["default"];
|
|
|
|
function onBegin(current) {
|
|
run.currentRunLoop = current;
|
|
}
|
|
|
|
function onEnd(current, next) {
|
|
run.currentRunLoop = next;
|
|
}
|
|
|
|
// ES6TODO: should Backburner become es6?
|
|
var backburner = new Backburner(['sync', 'actions', 'destroy'], {
|
|
GUID_KEY: GUID_KEY,
|
|
sync: {
|
|
before: beginPropertyChanges,
|
|
after: endPropertyChanges
|
|
},
|
|
defaultQueue: 'actions',
|
|
onBegin: onBegin,
|
|
onEnd: onEnd,
|
|
onErrorTarget: Ember,
|
|
onErrorMethod: 'onerror'
|
|
});
|
|
var slice = [].slice;
|
|
|
|
// ..........................................................
|
|
// run - this is ideally the only public API the dev sees
|
|
//
|
|
|
|
/**
|
|
Runs the passed target and method inside of a RunLoop, ensuring any
|
|
deferred actions including bindings and views updates are flushed at the
|
|
end.
|
|
|
|
Normally you should not need to invoke this method yourself. However if
|
|
you are implementing raw event handlers when interfacing with other
|
|
libraries or plugins, you should probably wrap all of your code inside this
|
|
call.
|
|
|
|
```javascript
|
|
run(function() {
|
|
// code to be executed within a RunLoop
|
|
});
|
|
```
|
|
|
|
@class run
|
|
@namespace Ember
|
|
@static
|
|
@constructor
|
|
@param {Object} [target] target of method to call
|
|
@param {Function|String} method Method to invoke.
|
|
May be a function or a string. If you pass a string
|
|
then it will be looked up on the passed target.
|
|
@param {Object} [args*] Any additional arguments you wish to pass to the method.
|
|
@return {Object} return value from invoking the passed function.
|
|
*/
|
|
__exports__["default"] = run;
|
|
function run() {
|
|
return backburner.run.apply(backburner, arguments);
|
|
}
|
|
|
|
/**
|
|
If no run-loop is present, it creates a new one. If a run loop is
|
|
present it will queue itself to run on the existing run-loops action
|
|
queue.
|
|
|
|
Please note: This is not for normal usage, and should be used sparingly.
|
|
|
|
If invoked when not within a run loop:
|
|
|
|
```javascript
|
|
run.join(function() {
|
|
// creates a new run-loop
|
|
});
|
|
```
|
|
|
|
Alternatively, if called within an existing run loop:
|
|
|
|
```javascript
|
|
run(function() {
|
|
// creates a new run-loop
|
|
run.join(function() {
|
|
// joins with the existing run-loop, and queues for invocation on
|
|
// the existing run-loops action queue.
|
|
});
|
|
});
|
|
```
|
|
|
|
@method join
|
|
@namespace Ember
|
|
@param {Object} [target] target of method to call
|
|
@param {Function|String} method Method to invoke.
|
|
May be a function or a string. If you pass a string
|
|
then it will be looked up on the passed target.
|
|
@param {Object} [args*] Any additional arguments you wish to pass to the method.
|
|
@return {Object} Return value from invoking the passed function. Please note,
|
|
when called within an existing loop, no return value is possible.
|
|
*/
|
|
run.join = function() {
|
|
return backburner.join.apply(backburner, arguments);
|
|
};
|
|
|
|
/**
|
|
Allows you to specify which context to call the specified function in while
|
|
adding the execution of that function to the Ember run loop. This ability
|
|
makes this method a great way to asynchronusly integrate third-party libraries
|
|
into your Ember application.
|
|
|
|
`run.bind` takes two main arguments, the desired context and the function to
|
|
invoke in that context. Any additional arguments will be supplied as arguments
|
|
to the function that is passed in.
|
|
|
|
Let's use the creation of a TinyMCE component as an example. Currently,
|
|
TinyMCE provides a setup configuration option we can use to do some processing
|
|
after the TinyMCE instance is initialized but before it is actually rendered.
|
|
We can use that setup option to do some additional setup for our component.
|
|
The component itself could look something like the following:
|
|
|
|
```javascript
|
|
App.RichTextEditorComponent = Ember.Component.extend({
|
|
initializeTinyMCE: function(){
|
|
tinymce.init({
|
|
selector: '#' + this.$().prop('id'),
|
|
setup: Ember.run.bind(this, this.setupEditor)
|
|
});
|
|
}.on('didInsertElement'),
|
|
|
|
setupEditor: function(editor) {
|
|
this.set('editor', editor);
|
|
editor.on('change', function(){ console.log('content changed!')} );
|
|
}
|
|
});
|
|
```
|
|
|
|
In this example, we use Ember.run.bind to bind the setupEditor message to the
|
|
context of the App.RichTextEditorComponent and to have the invocation of that
|
|
method be safely handled and excuted by the Ember run loop.
|
|
|
|
@method bind
|
|
@namespace Ember
|
|
@param {Object} [target] target of method to call
|
|
@param {Function|String} method Method to invoke.
|
|
May be a function or a string. If you pass a string
|
|
then it will be looked up on the passed target.
|
|
@param {Object} [args*] Any additional arguments you wish to pass to the method.
|
|
@return {Function} returns a new function that will always have a particular context
|
|
@since 1.4.0
|
|
*/
|
|
run.bind = function(target, method /* args */) {
|
|
var args = slice.call(arguments);
|
|
return function() {
|
|
return run.join.apply(run, args.concat(slice.call(arguments)));
|
|
};
|
|
};
|
|
|
|
run.backburner = backburner;
|
|
run.currentRunLoop = null;
|
|
run.queues = backburner.queueNames;
|
|
|
|
/**
|
|
Begins a new RunLoop. Any deferred actions invoked after the begin will
|
|
be buffered until you invoke a matching call to `run.end()`. This is
|
|
a lower-level way to use a RunLoop instead of using `run()`.
|
|
|
|
```javascript
|
|
run.begin();
|
|
// code to be executed within a RunLoop
|
|
run.end();
|
|
```
|
|
|
|
@method begin
|
|
@return {void}
|
|
*/
|
|
run.begin = function() {
|
|
backburner.begin();
|
|
};
|
|
|
|
/**
|
|
Ends a RunLoop. This must be called sometime after you call
|
|
`run.begin()` to flush any deferred actions. This is a lower-level way
|
|
to use a RunLoop instead of using `run()`.
|
|
|
|
```javascript
|
|
run.begin();
|
|
// code to be executed within a RunLoop
|
|
run.end();
|
|
```
|
|
|
|
@method end
|
|
@return {void}
|
|
*/
|
|
run.end = function() {
|
|
backburner.end();
|
|
};
|
|
|
|
/**
|
|
Array of named queues. This array determines the order in which queues
|
|
are flushed at the end of the RunLoop. You can define your own queues by
|
|
simply adding the queue name to this array. Normally you should not need
|
|
to inspect or modify this property.
|
|
|
|
@property queues
|
|
@type Array
|
|
@default ['sync', 'actions', 'destroy']
|
|
*/
|
|
|
|
/**
|
|
Adds the passed target/method and any optional arguments to the named
|
|
queue to be executed at the end of the RunLoop. If you have not already
|
|
started a RunLoop when calling this method one will be started for you
|
|
automatically.
|
|
|
|
At the end of a RunLoop, any methods scheduled in this way will be invoked.
|
|
Methods will be invoked in an order matching the named queues defined in
|
|
the `run.queues` property.
|
|
|
|
```javascript
|
|
run.schedule('sync', this, function() {
|
|
// this will be executed in the first RunLoop queue, when bindings are synced
|
|
console.log("scheduled on sync queue");
|
|
});
|
|
|
|
run.schedule('actions', this, function() {
|
|
// this will be executed in the 'actions' queue, after bindings have synced.
|
|
console.log("scheduled on actions queue");
|
|
});
|
|
|
|
// Note the functions will be run in order based on the run queues order.
|
|
// Output would be:
|
|
// scheduled on sync queue
|
|
// scheduled on actions queue
|
|
```
|
|
|
|
@method schedule
|
|
@param {String} queue The name of the queue to schedule against.
|
|
Default queues are 'sync' and 'actions'
|
|
@param {Object} [target] target object to use as the context when invoking a method.
|
|
@param {String|Function} method The method to invoke. If you pass a string it
|
|
will be resolved on the target object at the time the scheduled item is
|
|
invoked allowing you to change the target function.
|
|
@param {Object} [arguments*] Optional arguments to be passed to the queued method.
|
|
@return {void}
|
|
*/
|
|
run.schedule = function(queue, target, method) {
|
|
checkAutoRun();
|
|
backburner.schedule.apply(backburner, arguments);
|
|
};
|
|
|
|
// Used by global test teardown
|
|
run.hasScheduledTimers = function() {
|
|
return backburner.hasTimers();
|
|
};
|
|
|
|
// Used by global test teardown
|
|
run.cancelTimers = function () {
|
|
backburner.cancelTimers();
|
|
};
|
|
|
|
/**
|
|
Immediately flushes any events scheduled in the 'sync' queue. Bindings
|
|
use this queue so this method is a useful way to immediately force all
|
|
bindings in the application to sync.
|
|
|
|
You should call this method anytime you need any changed state to propagate
|
|
throughout the app immediately without repainting the UI (which happens
|
|
in the later 'render' queue added by the `ember-views` package).
|
|
|
|
```javascript
|
|
run.sync();
|
|
```
|
|
|
|
@method sync
|
|
@return {void}
|
|
*/
|
|
run.sync = function() {
|
|
if (backburner.currentInstance) {
|
|
backburner.currentInstance.queues.sync.flush();
|
|
}
|
|
};
|
|
|
|
/**
|
|
Invokes the passed target/method and optional arguments after a specified
|
|
period of time. The last parameter of this method must always be a number
|
|
of milliseconds.
|
|
|
|
You should use this method whenever you need to run some action after a
|
|
period of time instead of using `setTimeout()`. This method will ensure that
|
|
items that expire during the same script execution cycle all execute
|
|
together, which is often more efficient than using a real setTimeout.
|
|
|
|
```javascript
|
|
run.later(myContext, function() {
|
|
// code here will execute within a RunLoop in about 500ms with this == myContext
|
|
}, 500);
|
|
```
|
|
|
|
@method later
|
|
@param {Object} [target] target of method to invoke
|
|
@param {Function|String} method The method to invoke.
|
|
If you pass a string it will be resolved on the
|
|
target at the time the method is invoked.
|
|
@param {Object} [args*] Optional arguments to pass to the timeout.
|
|
@param {Number} wait Number of milliseconds to wait.
|
|
@return {Object} Timer information for use in cancelling, see `run.cancel`.
|
|
*/
|
|
run.later = function(/*target, method*/) {
|
|
return backburner.later.apply(backburner, arguments);
|
|
};
|
|
|
|
/**
|
|
Schedule a function to run one time during the current RunLoop. This is equivalent
|
|
to calling `scheduleOnce` with the "actions" queue.
|
|
|
|
@method once
|
|
@param {Object} [target] The target of the method to invoke.
|
|
@param {Function|String} method The method to invoke.
|
|
If you pass a string it will be resolved on the
|
|
target at the time the method is invoked.
|
|
@param {Object} [args*] Optional arguments to pass to the timeout.
|
|
@return {Object} Timer information for use in cancelling, see `run.cancel`.
|
|
*/
|
|
run.once = function(/*target, method */) {
|
|
checkAutoRun();
|
|
var length = arguments.length;
|
|
var args = new Array(length);
|
|
args[0] = 'actions';
|
|
for (var i = 0; i < length; i++) {
|
|
args[i + 1] = arguments[i];
|
|
}
|
|
return apply(backburner, backburner.scheduleOnce, args);
|
|
};
|
|
|
|
/**
|
|
Schedules a function to run one time in a given queue of the current RunLoop.
|
|
Calling this method with the same queue/target/method combination will have
|
|
no effect (past the initial call).
|
|
|
|
Note that although you can pass optional arguments these will not be
|
|
considered when looking for duplicates. New arguments will replace previous
|
|
calls.
|
|
|
|
```javascript
|
|
run(function() {
|
|
var sayHi = function() { console.log('hi'); }
|
|
run.scheduleOnce('afterRender', myContext, sayHi);
|
|
run.scheduleOnce('afterRender', myContext, sayHi);
|
|
// sayHi will only be executed once, in the afterRender queue of the RunLoop
|
|
});
|
|
```
|
|
|
|
Also note that passing an anonymous function to `run.scheduleOnce` will
|
|
not prevent additional calls with an identical anonymous function from
|
|
scheduling the items multiple times, e.g.:
|
|
|
|
```javascript
|
|
function scheduleIt() {
|
|
run.scheduleOnce('actions', myContext, function() { console.log("Closure"); });
|
|
}
|
|
scheduleIt();
|
|
scheduleIt();
|
|
// "Closure" will print twice, even though we're using `run.scheduleOnce`,
|
|
// because the function we pass to it is anonymous and won't match the
|
|
// previously scheduled operation.
|
|
```
|
|
|
|
Available queues, and their order, can be found at `run.queues`
|
|
|
|
@method scheduleOnce
|
|
@param {String} [queue] The name of the queue to schedule against. Default queues are 'sync' and 'actions'.
|
|
@param {Object} [target] The target of the method to invoke.
|
|
@param {Function|String} method The method to invoke.
|
|
If you pass a string it will be resolved on the
|
|
target at the time the method is invoked.
|
|
@param {Object} [args*] Optional arguments to pass to the timeout.
|
|
@return {Object} Timer information for use in cancelling, see `run.cancel`.
|
|
*/
|
|
run.scheduleOnce = function(/*queue, target, method*/) {
|
|
checkAutoRun();
|
|
return backburner.scheduleOnce.apply(backburner, arguments);
|
|
};
|
|
|
|
/**
|
|
Schedules an item to run from within a separate run loop, after
|
|
control has been returned to the system. This is equivalent to calling
|
|
`run.later` with a wait time of 1ms.
|
|
|
|
```javascript
|
|
run.next(myContext, function() {
|
|
// code to be executed in the next run loop,
|
|
// which will be scheduled after the current one
|
|
});
|
|
```
|
|
|
|
Multiple operations scheduled with `run.next` will coalesce
|
|
into the same later run loop, along with any other operations
|
|
scheduled by `run.later` that expire right around the same
|
|
time that `run.next` operations will fire.
|
|
|
|
Note that there are often alternatives to using `run.next`.
|
|
For instance, if you'd like to schedule an operation to happen
|
|
after all DOM element operations have completed within the current
|
|
run loop, you can make use of the `afterRender` run loop queue (added
|
|
by the `ember-views` package, along with the preceding `render` queue
|
|
where all the DOM element operations happen). Example:
|
|
|
|
```javascript
|
|
App.MyCollectionView = Ember.CollectionView.extend({
|
|
didInsertElement: function() {
|
|
run.scheduleOnce('afterRender', this, 'processChildElements');
|
|
},
|
|
processChildElements: function() {
|
|
// ... do something with collectionView's child view
|
|
// elements after they've finished rendering, which
|
|
// can't be done within the CollectionView's
|
|
// `didInsertElement` hook because that gets run
|
|
// before the child elements have been added to the DOM.
|
|
}
|
|
});
|
|
```
|
|
|
|
One benefit of the above approach compared to using `run.next` is
|
|
that you will be able to perform DOM/CSS operations before unprocessed
|
|
elements are rendered to the screen, which may prevent flickering or
|
|
other artifacts caused by delaying processing until after rendering.
|
|
|
|
The other major benefit to the above approach is that `run.next`
|
|
introduces an element of non-determinism, which can make things much
|
|
harder to test, due to its reliance on `setTimeout`; it's much harder
|
|
to guarantee the order of scheduled operations when they are scheduled
|
|
outside of the current run loop, i.e. with `run.next`.
|
|
|
|
@method next
|
|
@param {Object} [target] target of method to invoke
|
|
@param {Function|String} method The method to invoke.
|
|
If you pass a string it will be resolved on the
|
|
target at the time the method is invoked.
|
|
@param {Object} [args*] Optional arguments to pass to the timeout.
|
|
@return {Object} Timer information for use in cancelling, see `run.cancel`.
|
|
*/
|
|
run.next = function() {
|
|
var args = slice.call(arguments);
|
|
args.push(1);
|
|
return apply(backburner, backburner.later, args);
|
|
};
|
|
|
|
/**
|
|
Cancels a scheduled item. Must be a value returned by `run.later()`,
|
|
`run.once()`, `run.next()`, `run.debounce()`, or
|
|
`run.throttle()`.
|
|
|
|
```javascript
|
|
var runNext = run.next(myContext, function() {
|
|
// will not be executed
|
|
});
|
|
run.cancel(runNext);
|
|
|
|
var runLater = run.later(myContext, function() {
|
|
// will not be executed
|
|
}, 500);
|
|
run.cancel(runLater);
|
|
|
|
var runOnce = run.once(myContext, function() {
|
|
// will not be executed
|
|
});
|
|
run.cancel(runOnce);
|
|
|
|
var throttle = run.throttle(myContext, function() {
|
|
// will not be executed
|
|
}, 1, false);
|
|
run.cancel(throttle);
|
|
|
|
var debounce = run.debounce(myContext, function() {
|
|
// will not be executed
|
|
}, 1);
|
|
run.cancel(debounce);
|
|
|
|
var debounceImmediate = run.debounce(myContext, function() {
|
|
// will be executed since we passed in true (immediate)
|
|
}, 100, true);
|
|
// the 100ms delay until this method can be called again will be cancelled
|
|
run.cancel(debounceImmediate);
|
|
```
|
|
|
|
@method cancel
|
|
@param {Object} timer Timer object to cancel
|
|
@return {Boolean} true if cancelled or false/undefined if it wasn't found
|
|
*/
|
|
run.cancel = function(timer) {
|
|
return backburner.cancel(timer);
|
|
};
|
|
|
|
/**
|
|
Delay calling the target method until the debounce period has elapsed
|
|
with no additional debounce calls. If `debounce` is called again before
|
|
the specified time has elapsed, the timer is reset and the entire period
|
|
must pass again before the target method is called.
|
|
|
|
This method should be used when an event may be called multiple times
|
|
but the action should only be called once when the event is done firing.
|
|
A common example is for scroll events where you only want updates to
|
|
happen once scrolling has ceased.
|
|
|
|
```javascript
|
|
var myFunc = function() { console.log(this.name + ' ran.'); };
|
|
var myContext = {name: 'debounce'};
|
|
|
|
run.debounce(myContext, myFunc, 150);
|
|
|
|
// less than 150ms passes
|
|
|
|
run.debounce(myContext, myFunc, 150);
|
|
|
|
// 150ms passes
|
|
// myFunc is invoked with context myContext
|
|
// console logs 'debounce ran.' one time.
|
|
```
|
|
|
|
Immediate allows you to run the function immediately, but debounce
|
|
other calls for this function until the wait time has elapsed. If
|
|
`debounce` is called again before the specified time has elapsed,
|
|
the timer is reset and the entire period must pass again before
|
|
the method can be called again.
|
|
|
|
```javascript
|
|
var myFunc = function() { console.log(this.name + ' ran.'); };
|
|
var myContext = {name: 'debounce'};
|
|
|
|
run.debounce(myContext, myFunc, 150, true);
|
|
|
|
// console logs 'debounce ran.' one time immediately.
|
|
// 100ms passes
|
|
|
|
run.debounce(myContext, myFunc, 150, true);
|
|
|
|
// 150ms passes and nothing else is logged to the console and
|
|
// the debouncee is no longer being watched
|
|
|
|
run.debounce(myContext, myFunc, 150, true);
|
|
|
|
// console logs 'debounce ran.' one time immediately.
|
|
// 150ms passes and nothing else is logged to the console and
|
|
// the debouncee is no longer being watched
|
|
|
|
```
|
|
|
|
@method debounce
|
|
@param {Object} [target] target of method to invoke
|
|
@param {Function|String} method The method to invoke.
|
|
May be a function or a string. If you pass a string
|
|
then it will be looked up on the passed target.
|
|
@param {Object} [args*] Optional arguments to pass to the timeout.
|
|
@param {Number} wait Number of milliseconds to wait.
|
|
@param {Boolean} immediate Trigger the function on the leading instead
|
|
of the trailing edge of the wait interval. Defaults to false.
|
|
@return {Array} Timer information for use in cancelling, see `run.cancel`.
|
|
*/
|
|
run.debounce = function() {
|
|
return backburner.debounce.apply(backburner, arguments);
|
|
};
|
|
|
|
/**
|
|
Ensure that the target method is never called more frequently than
|
|
the specified spacing period. The target method is called immediately.
|
|
|
|
```javascript
|
|
var myFunc = function() { console.log(this.name + ' ran.'); };
|
|
var myContext = {name: 'throttle'};
|
|
|
|
run.throttle(myContext, myFunc, 150);
|
|
// myFunc is invoked with context myContext
|
|
// console logs 'throttle ran.'
|
|
|
|
// 50ms passes
|
|
run.throttle(myContext, myFunc, 150);
|
|
|
|
// 50ms passes
|
|
run.throttle(myContext, myFunc, 150);
|
|
|
|
// 150ms passes
|
|
run.throttle(myContext, myFunc, 150);
|
|
// myFunc is invoked with context myContext
|
|
// console logs 'throttle ran.'
|
|
```
|
|
|
|
@method throttle
|
|
@param {Object} [target] target of method to invoke
|
|
@param {Function|String} method The method to invoke.
|
|
May be a function or a string. If you pass a string
|
|
then it will be looked up on the passed target.
|
|
@param {Object} [args*] Optional arguments to pass to the timeout.
|
|
@param {Number} spacing Number of milliseconds to space out requests.
|
|
@param {Boolean} immediate Trigger the function on the leading instead
|
|
of the trailing edge of the wait interval. Defaults to true.
|
|
@return {Array} Timer information for use in cancelling, see `run.cancel`.
|
|
*/
|
|
run.throttle = function() {
|
|
return backburner.throttle.apply(backburner, arguments);
|
|
};
|
|
|
|
// Make sure it's not an autorun during testing
|
|
function checkAutoRun() {
|
|
if (!run.currentRunLoop) {
|
|
Ember.assert("You have turned on testing mode, which disabled the run-loop's autorun." +
|
|
" You will need to wrap any code with asynchronous side-effects in a run", !Ember.testing);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Add a new named queue after the specified queue.
|
|
|
|
The queue to add will only be added once.
|
|
|
|
@method _addQueue
|
|
@param {String} name the name of the queue to add.
|
|
@param {String} after the name of the queue to add after.
|
|
@private
|
|
*/
|
|
run._addQueue = function(name, after) {
|
|
if (indexOf.call(run.queues, name) === -1) {
|
|
run.queues.splice(indexOf.call(run.queues, after)+1, 0, name);
|
|
}
|
|
};
|
|
});
|
|
enifed("ember-metal/set_properties",
|
|
["ember-metal/property_events","ember-metal/property_set","ember-metal/keys","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
var changeProperties = __dependency1__.changeProperties;
|
|
var set = __dependency2__.set;
|
|
var keys = __dependency3__["default"];
|
|
|
|
/**
|
|
Set a list of properties on an object. These properties are set inside
|
|
a single `beginPropertyChanges` and `endPropertyChanges` batch, so
|
|
observers will be buffered.
|
|
|
|
```javascript
|
|
var anObject = Ember.Object.create();
|
|
|
|
anObject.setProperties({
|
|
firstName: 'Stanley',
|
|
lastName: 'Stuart',
|
|
age: 21
|
|
});
|
|
```
|
|
|
|
@method setProperties
|
|
@param obj
|
|
@param {Object} properties
|
|
@return obj
|
|
*/
|
|
__exports__["default"] = function setProperties(obj, properties) {
|
|
if (!properties || typeof properties !== "object") { return obj; }
|
|
changeProperties(function() {
|
|
var props = keys(properties);
|
|
var propertyName;
|
|
|
|
for (var i = 0, l = props.length; i < l; i++) {
|
|
propertyName = props[i];
|
|
|
|
set(obj, propertyName, properties[propertyName]);
|
|
}
|
|
});
|
|
return obj;
|
|
}
|
|
});
|
|
enifed("ember-metal/streams/simple",
|
|
["ember-metal/merge","ember-metal/streams/stream","ember-metal/platform","ember-metal/streams/utils","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
|
|
"use strict";
|
|
var merge = __dependency1__["default"];
|
|
var Stream = __dependency2__["default"];
|
|
var create = __dependency3__.create;
|
|
var read = __dependency4__.read;
|
|
var isStream = __dependency4__.isStream;
|
|
|
|
function SimpleStream(source) {
|
|
this.init();
|
|
this.source = source;
|
|
|
|
if (isStream(source)) {
|
|
source.subscribe(this._didChange, this);
|
|
}
|
|
}
|
|
|
|
SimpleStream.prototype = create(Stream.prototype);
|
|
|
|
merge(SimpleStream.prototype, {
|
|
valueFn: function() {
|
|
return read(this.source);
|
|
},
|
|
|
|
setValue: function(value) {
|
|
var source = this.source;
|
|
|
|
if (isStream(source)) {
|
|
source.setValue(value);
|
|
}
|
|
},
|
|
|
|
setSource: function(nextSource) {
|
|
var prevSource = this.source;
|
|
if (nextSource !== prevSource) {
|
|
if (isStream(prevSource)) {
|
|
prevSource.unsubscribe(this._didChange, this);
|
|
}
|
|
|
|
if (isStream(nextSource)) {
|
|
nextSource.subscribe(this._didChange, this);
|
|
}
|
|
|
|
this.source = nextSource;
|
|
this.notify();
|
|
}
|
|
},
|
|
|
|
_didChange: function() {
|
|
this.notify();
|
|
},
|
|
|
|
_super$destroy: Stream.prototype.destroy,
|
|
|
|
destroy: function() {
|
|
if (this._super$destroy()) {
|
|
if (isStream(this.source)) {
|
|
this.source.unsubscribe(this._didChange, this);
|
|
}
|
|
this.source = undefined;
|
|
return true;
|
|
}
|
|
}
|
|
});
|
|
|
|
__exports__["default"] = SimpleStream;
|
|
});
|
|
enifed("ember-metal/streams/stream",
|
|
["ember-metal/platform","ember-metal/path_cache","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var create = __dependency1__.create;
|
|
var getFirstKey = __dependency2__.getFirstKey;
|
|
var getTailPath = __dependency2__.getTailPath;
|
|
|
|
function Stream(fn) {
|
|
this.init();
|
|
this.valueFn = fn;
|
|
}
|
|
|
|
Stream.prototype = {
|
|
isStream: true,
|
|
|
|
init: function() {
|
|
this.state = 'dirty';
|
|
this.cache = undefined;
|
|
this.subscribers = undefined;
|
|
this.children = undefined;
|
|
this._label = undefined;
|
|
},
|
|
|
|
get: function(path) {
|
|
var firstKey = getFirstKey(path);
|
|
var tailPath = getTailPath(path);
|
|
|
|
if (this.children === undefined) {
|
|
this.children = create(null);
|
|
}
|
|
|
|
var keyStream = this.children[firstKey];
|
|
|
|
if (keyStream === undefined) {
|
|
keyStream = this._makeChildStream(firstKey, path);
|
|
this.children[firstKey] = keyStream;
|
|
}
|
|
|
|
if (tailPath === undefined) {
|
|
return keyStream;
|
|
} else {
|
|
return keyStream.get(tailPath);
|
|
}
|
|
},
|
|
|
|
value: function() {
|
|
if (this.state === 'clean') {
|
|
return this.cache;
|
|
} else if (this.state === 'dirty') {
|
|
this.state = 'clean';
|
|
return this.cache = this.valueFn();
|
|
}
|
|
// TODO: Ensure value is never called on a destroyed stream
|
|
// so that we can uncomment this assertion.
|
|
//
|
|
// Ember.assert("Stream error: value was called in an invalid state: " + this.state);
|
|
},
|
|
|
|
valueFn: function() {
|
|
throw new Error("Stream error: valueFn not implemented");
|
|
},
|
|
|
|
setValue: function() {
|
|
throw new Error("Stream error: setValue not implemented");
|
|
},
|
|
|
|
notify: function() {
|
|
this.notifyExcept();
|
|
},
|
|
|
|
notifyExcept: function(callbackToSkip, contextToSkip) {
|
|
if (this.state === 'clean') {
|
|
this.state = 'dirty';
|
|
this._notifySubscribers(callbackToSkip, contextToSkip);
|
|
}
|
|
},
|
|
|
|
subscribe: function(callback, context) {
|
|
if (this.subscribers === undefined) {
|
|
this.subscribers = [callback, context];
|
|
} else {
|
|
this.subscribers.push(callback, context);
|
|
}
|
|
},
|
|
|
|
unsubscribe: function(callback, context) {
|
|
var subscribers = this.subscribers;
|
|
|
|
if (subscribers !== undefined) {
|
|
for (var i = 0, l = subscribers.length; i < l; i += 2) {
|
|
if (subscribers[i] === callback && subscribers[i+1] === context) {
|
|
subscribers.splice(i, 2);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
_notifySubscribers: function(callbackToSkip, contextToSkip) {
|
|
var subscribers = this.subscribers;
|
|
|
|
if (subscribers !== undefined) {
|
|
for (var i = 0, l = subscribers.length; i < l; i += 2) {
|
|
var callback = subscribers[i];
|
|
var context = subscribers[i+1];
|
|
|
|
if (callback === callbackToSkip && context === contextToSkip) {
|
|
continue;
|
|
}
|
|
|
|
if (context === undefined) {
|
|
callback(this);
|
|
} else {
|
|
callback.call(context, this);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
destroy: function() {
|
|
if (this.state !== 'destroyed') {
|
|
this.state = 'destroyed';
|
|
|
|
var children = this.children;
|
|
for (var key in children) {
|
|
children[key].destroy();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
},
|
|
|
|
isGlobal: function() {
|
|
var stream = this;
|
|
while (stream !== undefined) {
|
|
if (stream._isRoot) {
|
|
return stream._isGlobal;
|
|
}
|
|
stream = stream.source;
|
|
}
|
|
}
|
|
};
|
|
|
|
__exports__["default"] = Stream;
|
|
});
|
|
enifed("ember-metal/streams/stream_binding",
|
|
["ember-metal/platform","ember-metal/merge","ember-metal/run_loop","ember-metal/streams/stream","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
|
|
"use strict";
|
|
var create = __dependency1__.create;
|
|
var merge = __dependency2__["default"];
|
|
var run = __dependency3__["default"];
|
|
var Stream = __dependency4__["default"];
|
|
|
|
function StreamBinding(stream) {
|
|
Ember.assert("StreamBinding error: tried to bind to object that is not a stream", stream && stream.isStream);
|
|
|
|
this.init();
|
|
this.stream = stream;
|
|
this.senderCallback = undefined;
|
|
this.senderContext = undefined;
|
|
this.senderValue = undefined;
|
|
|
|
stream.subscribe(this._onNotify, this);
|
|
}
|
|
|
|
StreamBinding.prototype = create(Stream.prototype);
|
|
|
|
merge(StreamBinding.prototype, {
|
|
valueFn: function() {
|
|
return this.stream.value();
|
|
},
|
|
|
|
_onNotify: function() {
|
|
this._scheduleSync(undefined, undefined, this);
|
|
},
|
|
|
|
setValue: function(value, callback, context) {
|
|
this._scheduleSync(value, callback, context);
|
|
},
|
|
|
|
_scheduleSync: function(value, callback, context) {
|
|
if (this.senderCallback === undefined && this.senderContext === undefined) {
|
|
this.senderCallback = callback;
|
|
this.senderContext = context;
|
|
this.senderValue = value;
|
|
run.schedule('sync', this, this._sync);
|
|
} else if (this.senderContext !== this) {
|
|
this.senderCallback = callback;
|
|
this.senderContext = context;
|
|
this.senderValue = value;
|
|
}
|
|
},
|
|
|
|
_sync: function() {
|
|
if (this.state === 'destroyed') {
|
|
return;
|
|
}
|
|
|
|
if (this.senderContext !== this) {
|
|
this.stream.setValue(this.senderValue);
|
|
}
|
|
|
|
var senderCallback = this.senderCallback;
|
|
var senderContext = this.senderContext;
|
|
this.senderCallback = undefined;
|
|
this.senderContext = undefined;
|
|
this.senderValue = undefined;
|
|
|
|
// Force StreamBindings to always notify
|
|
this.state = 'clean';
|
|
|
|
this.notifyExcept(senderCallback, senderContext);
|
|
},
|
|
|
|
_super$destroy: Stream.prototype.destroy,
|
|
|
|
destroy: function() {
|
|
if (this._super$destroy()) {
|
|
this.stream.unsubscribe(this._onNotify, this);
|
|
return true;
|
|
}
|
|
}
|
|
});
|
|
|
|
__exports__["default"] = StreamBinding;
|
|
});
|
|
enifed("ember-metal/streams/utils",
|
|
["./stream","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var Stream = __dependency1__["default"];
|
|
|
|
/**
|
|
Check whether an object is a stream or not
|
|
|
|
@private
|
|
@function isStream
|
|
@param {Object|Stream} object object to check whether it is a stream
|
|
@return {Boolean} `true` if the object is a stream, `false` otherwise
|
|
*/
|
|
function isStream(object) {
|
|
return object && object.isStream;
|
|
}
|
|
|
|
__exports__.isStream = isStream;/**
|
|
A method of subscribing to a stream which is safe for use with a non-stream
|
|
object. If a non-stream object is passed, the function does nothing.
|
|
|
|
@private
|
|
@function subscribe
|
|
@param {Object|Stream} object object or stream to potentially subscribe to
|
|
@param {Function} callback function to run when stream value changes
|
|
@param {Object} [context] the callback will be executed with this context if it
|
|
is provided
|
|
*/
|
|
function subscribe(object, callback, context) {
|
|
if (object && object.isStream) {
|
|
object.subscribe(callback, context);
|
|
}
|
|
}
|
|
|
|
__exports__.subscribe = subscribe;/**
|
|
A method of unsubscribing from a stream which is safe for use with a non-stream
|
|
object. If a non-stream object is passed, the function does nothing.
|
|
|
|
@private
|
|
@function unsubscribe
|
|
@param {Object|Stream} object object or stream to potentially unsubscribe from
|
|
@param {Function} callback function originally passed to `subscribe()`
|
|
@param {Object} [context] object originally passed to `subscribe()`
|
|
*/
|
|
function unsubscribe(object, callback, context) {
|
|
if (object && object.isStream) {
|
|
object.unsubscribe(callback, context);
|
|
}
|
|
}
|
|
|
|
__exports__.unsubscribe = unsubscribe;/**
|
|
Retrieve the value of a stream, or in the case a non-stream object is passed,
|
|
return the object itself.
|
|
|
|
@private
|
|
@function read
|
|
@param {Object|Stream} object object to return the value of
|
|
@return the stream's current value, or the non-stream object itself
|
|
*/
|
|
function read(object) {
|
|
if (object && object.isStream) {
|
|
return object.value();
|
|
} else {
|
|
return object;
|
|
}
|
|
}
|
|
|
|
__exports__.read = read;/**
|
|
Map an array, replacing any streams with their values.
|
|
|
|
@private
|
|
@function readArray
|
|
@param {Array} array The array to read values from
|
|
@return {Array} a new array of the same length with the values of non-stream
|
|
objects mapped from their original positions untouched, and
|
|
the values of stream objects retaining their original position
|
|
and replaced with the stream's current value.
|
|
*/
|
|
function readArray(array) {
|
|
var length = array.length;
|
|
var ret = new Array(length);
|
|
for (var i = 0; i < length; i++) {
|
|
ret[i] = read(array[i]);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
__exports__.readArray = readArray;/**
|
|
Map a hash, replacing any stream property values with the current value of that
|
|
stream.
|
|
|
|
@private
|
|
@function readHash
|
|
@param {Object} object The hash to read keys and values from
|
|
@return {Object} a new object with the same keys as the passed object. The
|
|
property values in the new object are the original values in
|
|
the case of non-stream objects, and the streams' current
|
|
values in the case of stream objects.
|
|
*/
|
|
function readHash(object) {
|
|
var ret = {};
|
|
for (var key in object) {
|
|
ret[key] = read(object[key]);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
__exports__.readHash = readHash;/**
|
|
Check whether an array contains any stream values
|
|
|
|
@private
|
|
@function scanArray
|
|
@param {Array} array array given to a handlebars helper
|
|
@return {Boolean} `true` if the array contains a stream/bound value, `false`
|
|
otherwise
|
|
*/
|
|
function scanArray(array) {
|
|
var length = array.length;
|
|
var containsStream = false;
|
|
|
|
for (var i = 0; i < length; i++){
|
|
if (isStream(array[i])) {
|
|
containsStream = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return containsStream;
|
|
}
|
|
|
|
__exports__.scanArray = scanArray;/**
|
|
Check whether a hash has any stream property values
|
|
|
|
@private
|
|
@function scanHash
|
|
@param {Object} hash "hash" argument given to a handlebars helper
|
|
@return {Boolean} `true` if the object contains a stream/bound value, `false`
|
|
otherwise
|
|
*/
|
|
function scanHash(hash) {
|
|
var containsStream = false;
|
|
|
|
for (var prop in hash) {
|
|
if (isStream(hash[prop])) {
|
|
containsStream = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return containsStream;
|
|
}
|
|
|
|
__exports__.scanHash = scanHash;/**
|
|
Join an array, with any streams replaced by their current values
|
|
|
|
@private
|
|
@function concat
|
|
@param {Array} array An array containing zero or more stream objects and
|
|
zero or more non-stream objects
|
|
@param {String} separator string to be used to join array elements
|
|
@return {String} String with array elements concatenated and joined by the
|
|
provided separator, and any stream array members having been
|
|
replaced by the current value of the stream
|
|
*/
|
|
function concat(array, separator) {
|
|
// TODO: Create subclass ConcatStream < Stream. Defer
|
|
// subscribing to streams until the value() is called.
|
|
var hasStream = scanArray(array);
|
|
if (hasStream) {
|
|
var i, l;
|
|
var stream = new Stream(function() {
|
|
return readArray(array).join(separator);
|
|
});
|
|
|
|
for (i = 0, l=array.length; i < l; i++) {
|
|
subscribe(array[i], stream.notify, stream);
|
|
}
|
|
|
|
return stream;
|
|
} else {
|
|
return array.join(separator);
|
|
}
|
|
}
|
|
|
|
__exports__.concat = concat;/**
|
|
Generate a new stream by providing a source stream and a function that can
|
|
be used to transform the stream's value. In the case of a non-stream object,
|
|
returns the result of the function.
|
|
|
|
The value to transform would typically be available to the function you pass
|
|
to `chain()` via scope. For example:
|
|
|
|
```javascript
|
|
var source = ...; // stream returning a number
|
|
// or a numeric (non-stream) object
|
|
var result = chain(source, function(){
|
|
var currentValue = read(source);
|
|
return currentValue + 1;
|
|
});
|
|
```
|
|
|
|
In the example, result is a stream if source is a stream, or a number of
|
|
source was numeric.
|
|
|
|
@private
|
|
@function chain
|
|
@param {Object|Stream} value A stream or non-stream object
|
|
@param {Function} fn function to be run when the stream value changes, or to
|
|
be run once in the case of a non-stream object
|
|
@return {Object|Stream} In the case of a stream `value` parameter, a new
|
|
stream that will be updated with the return value of
|
|
the provided function `fn`. In the case of a
|
|
non-stream object, the return value of the provided
|
|
function `fn`.
|
|
*/
|
|
function chain(value, fn) {
|
|
if (isStream(value)) {
|
|
var stream = new Stream(fn);
|
|
subscribe(value, stream.notify, stream);
|
|
return stream;
|
|
} else {
|
|
return fn();
|
|
}
|
|
}
|
|
|
|
__exports__.chain = chain;
|
|
});
|
|
enifed("ember-metal/utils",
|
|
["ember-metal/core","ember-metal/platform","ember-metal/array","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
// Remove "use strict"; from transpiled module until
|
|
// https://bugs.webkit.org/show_bug.cgi?id=138038 is fixed
|
|
//
|
|
// REMOVE_USE_STRICT: true
|
|
|
|
var Ember = __dependency1__["default"];
|
|
var o_defineProperty = __dependency2__.defineProperty;
|
|
var canDefineNonEnumerableProperties = __dependency2__.canDefineNonEnumerableProperties;
|
|
var hasPropertyAccessors = __dependency2__.hasPropertyAccessors;
|
|
var o_create = __dependency2__.create;
|
|
|
|
var forEach = __dependency3__.forEach;
|
|
|
|
/**
|
|
@module ember-metal
|
|
*/
|
|
|
|
/**
|
|
Previously we used `Ember.$.uuid`, however `$.uuid` has been removed from
|
|
jQuery master. We'll just bootstrap our own uuid now.
|
|
|
|
@private
|
|
@return {Number} the uuid
|
|
*/
|
|
var _uuid = 0;
|
|
|
|
/**
|
|
Generates a universally unique identifier. This method
|
|
is used internally by Ember for assisting with
|
|
the generation of GUID's and other unique identifiers
|
|
such as `bind-attr` data attributes.
|
|
|
|
@public
|
|
@return {Number} [description]
|
|
*/
|
|
function uuid() {
|
|
return ++_uuid;
|
|
}
|
|
|
|
__exports__.uuid = uuid;/**
|
|
Prefix used for guids through out Ember.
|
|
@private
|
|
@property GUID_PREFIX
|
|
@for Ember
|
|
@type String
|
|
@final
|
|
*/
|
|
var GUID_PREFIX = 'ember';
|
|
|
|
// Used for guid generation...
|
|
var numberCache = [];
|
|
var stringCache = {};
|
|
|
|
/**
|
|
Strongly hint runtimes to intern the provided string.
|
|
|
|
When do I need to use this function?
|
|
|
|
For the most part, never. Pre-mature optimization is bad, and often the
|
|
runtime does exactly what you need it to, and more often the trade-off isn't
|
|
worth it.
|
|
|
|
Why?
|
|
|
|
Runtimes store strings in at least 2 different representations:
|
|
Ropes and Symbols (interned strings). The Rope provides a memory efficient
|
|
data-structure for strings created from concatenation or some other string
|
|
manipulation like splitting.
|
|
|
|
Unfortunately checking equality of different ropes can be quite costly as
|
|
runtimes must resort to clever string comparison algorithims. These
|
|
algorithims typically cost in proportion to the length of the string.
|
|
Luckily, this is where the Symbols (interned strings) shine. As Symbols are
|
|
unique by their string content, equality checks can be done by pointer
|
|
comparison.
|
|
|
|
How do I know if my string is a rope or symbol?
|
|
|
|
Typically (warning general sweeping statement, but truthy in runtimes at
|
|
present) static strings created as part of the JS source are interned.
|
|
Strings often used for comparisons can be interned at runtime if some
|
|
criteria are met. One of these criteria can be the size of the entire rope.
|
|
For example, in chrome 38 a rope longer then 12 characters will not
|
|
intern, nor will segments of that rope.
|
|
|
|
Some numbers: http://jsperf.com/eval-vs-keys/8
|
|
|
|
Known Trickâ„¢
|
|
|
|
@private
|
|
@return {String} interned version of the provided string
|
|
*/
|
|
function intern(str) {
|
|
var obj = {};
|
|
obj[str] = 1;
|
|
for (var key in obj) {
|
|
if (key === str) return key;
|
|
}
|
|
return str;
|
|
}
|
|
|
|
/**
|
|
A unique key used to assign guids and other private metadata to objects.
|
|
If you inspect an object in your browser debugger you will often see these.
|
|
They can be safely ignored.
|
|
|
|
On browsers that support it, these properties are added with enumeration
|
|
disabled so they won't show up when you iterate over your properties.
|
|
|
|
@private
|
|
@property GUID_KEY
|
|
@for Ember
|
|
@type String
|
|
@final
|
|
*/
|
|
var GUID_KEY = intern('__ember' + (+ new Date()));
|
|
|
|
var GUID_DESC = {
|
|
writable: false,
|
|
configurable: false,
|
|
enumerable: false,
|
|
value: null
|
|
};
|
|
|
|
/**
|
|
Generates a new guid, optionally saving the guid to the object that you
|
|
pass in. You will rarely need to use this method. Instead you should
|
|
call `Ember.guidFor(obj)`, which return an existing guid if available.
|
|
|
|
@private
|
|
@method generateGuid
|
|
@for Ember
|
|
@param {Object} [obj] Object the guid will be used for. If passed in, the guid will
|
|
be saved on the object and reused whenever you pass the same object
|
|
again.
|
|
|
|
If no object is passed, just generate a new guid.
|
|
@param {String} [prefix] Prefix to place in front of the guid. Useful when you want to
|
|
separate the guid into separate namespaces.
|
|
@return {String} the guid
|
|
*/
|
|
function generateGuid(obj, prefix) {
|
|
if (!prefix) prefix = GUID_PREFIX;
|
|
var ret = (prefix + uuid());
|
|
if (obj) {
|
|
if (obj[GUID_KEY] === null) {
|
|
obj[GUID_KEY] = ret;
|
|
} else {
|
|
GUID_DESC.value = ret;
|
|
o_defineProperty(obj, GUID_KEY, GUID_DESC);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
__exports__.generateGuid = generateGuid;/**
|
|
Returns a unique id for the object. If the object does not yet have a guid,
|
|
one will be assigned to it. You can call this on any object,
|
|
`Ember.Object`-based or not, but be aware that it will add a `_guid`
|
|
property.
|
|
|
|
You can also use this method on DOM Element objects.
|
|
|
|
@private
|
|
@method guidFor
|
|
@for Ember
|
|
@param {Object} obj any object, string, number, Element, or primitive
|
|
@return {String} the unique guid for this instance.
|
|
*/
|
|
function guidFor(obj) {
|
|
|
|
// special cases where we don't want to add a key to object
|
|
if (obj === undefined) return "(undefined)";
|
|
if (obj === null) return "(null)";
|
|
|
|
var ret;
|
|
var type = typeof obj;
|
|
|
|
// Don't allow prototype changes to String etc. to change the guidFor
|
|
switch(type) {
|
|
case 'number':
|
|
ret = numberCache[obj];
|
|
if (!ret) ret = numberCache[obj] = 'nu'+obj;
|
|
return ret;
|
|
|
|
case 'string':
|
|
ret = stringCache[obj];
|
|
if (!ret) ret = stringCache[obj] = 'st' + uuid();
|
|
return ret;
|
|
|
|
case 'boolean':
|
|
return obj ? '(true)' : '(false)';
|
|
|
|
default:
|
|
if (obj[GUID_KEY]) return obj[GUID_KEY];
|
|
if (obj === Object) return '(Object)';
|
|
if (obj === Array) return '(Array)';
|
|
ret = GUID_PREFIX + uuid();
|
|
|
|
if (obj[GUID_KEY] === null) {
|
|
obj[GUID_KEY] = ret;
|
|
} else {
|
|
GUID_DESC.value = ret;
|
|
o_defineProperty(obj, GUID_KEY, GUID_DESC);
|
|
}
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
__exports__.guidFor = guidFor;// ..........................................................
|
|
// META
|
|
//
|
|
|
|
var META_DESC = {
|
|
writable: true,
|
|
configurable: false,
|
|
enumerable: false,
|
|
value: null
|
|
};
|
|
|
|
function Meta(obj) {
|
|
this.descs = {};
|
|
this.watching = {};
|
|
this.cache = {};
|
|
this.cacheMeta = {};
|
|
this.source = obj;
|
|
this.deps = undefined;
|
|
this.listeners = undefined;
|
|
this.mixins = undefined;
|
|
this.bindings = undefined;
|
|
this.chains = undefined;
|
|
this.values = undefined;
|
|
this.proto = undefined;
|
|
}
|
|
|
|
Meta.prototype = {
|
|
chainWatchers: null
|
|
};
|
|
|
|
if (!canDefineNonEnumerableProperties) {
|
|
// on platforms that don't support enumerable false
|
|
// make meta fail jQuery.isPlainObject() to hide from
|
|
// jQuery.extend() by having a property that fails
|
|
// hasOwnProperty check.
|
|
Meta.prototype.__preventPlainObject__ = true;
|
|
|
|
// Without non-enumerable properties, meta objects will be output in JSON
|
|
// unless explicitly suppressed
|
|
Meta.prototype.toJSON = function () { };
|
|
}
|
|
|
|
// Placeholder for non-writable metas.
|
|
var EMPTY_META = new Meta(null);
|
|
|
|
|
|
if (hasPropertyAccessors) {
|
|
EMPTY_META.values = {};
|
|
}
|
|
|
|
|
|
/**
|
|
Retrieves the meta hash for an object. If `writable` is true ensures the
|
|
hash is writable for this object as well.
|
|
|
|
The meta object contains information about computed property descriptors as
|
|
well as any watched properties and other information. You generally will
|
|
not access this information directly but instead work with higher level
|
|
methods that manipulate this hash indirectly.
|
|
|
|
@method meta
|
|
@for Ember
|
|
@private
|
|
|
|
@param {Object} obj The object to retrieve meta for
|
|
@param {Boolean} [writable=true] Pass `false` if you do not intend to modify
|
|
the meta hash, allowing the method to avoid making an unnecessary copy.
|
|
@return {Object} the meta hash for an object
|
|
*/
|
|
function meta(obj, writable) {
|
|
var ret = obj['__ember_meta__'];
|
|
if (writable===false) return ret || EMPTY_META;
|
|
|
|
if (!ret) {
|
|
if (canDefineNonEnumerableProperties) o_defineProperty(obj, '__ember_meta__', META_DESC);
|
|
|
|
ret = new Meta(obj);
|
|
|
|
|
|
if (hasPropertyAccessors) {
|
|
ret.values = {};
|
|
}
|
|
|
|
|
|
obj['__ember_meta__'] = ret;
|
|
|
|
// make sure we don't accidentally try to create constructor like desc
|
|
ret.descs.constructor = null;
|
|
|
|
} else if (ret.source !== obj) {
|
|
if (canDefineNonEnumerableProperties) o_defineProperty(obj, '__ember_meta__', META_DESC);
|
|
|
|
ret = o_create(ret);
|
|
ret.descs = o_create(ret.descs);
|
|
ret.watching = o_create(ret.watching);
|
|
ret.cache = {};
|
|
ret.cacheMeta = {};
|
|
ret.source = obj;
|
|
|
|
|
|
if (hasPropertyAccessors) {
|
|
ret.values = o_create(ret.values);
|
|
}
|
|
|
|
|
|
obj['__ember_meta__'] = ret;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
function getMeta(obj, property) {
|
|
var _meta = meta(obj, false);
|
|
return _meta[property];
|
|
}
|
|
|
|
__exports__.getMeta = getMeta;function setMeta(obj, property, value) {
|
|
var _meta = meta(obj, true);
|
|
_meta[property] = value;
|
|
return value;
|
|
}
|
|
|
|
__exports__.setMeta = setMeta;/**
|
|
@deprecated
|
|
@private
|
|
|
|
In order to store defaults for a class, a prototype may need to create
|
|
a default meta object, which will be inherited by any objects instantiated
|
|
from the class's constructor.
|
|
|
|
However, the properties of that meta object are only shallow-cloned,
|
|
so if a property is a hash (like the event system's `listeners` hash),
|
|
it will by default be shared across all instances of that class.
|
|
|
|
This method allows extensions to deeply clone a series of nested hashes or
|
|
other complex objects. For instance, the event system might pass
|
|
`['listeners', 'foo:change', 'ember157']` to `prepareMetaPath`, which will
|
|
walk down the keys provided.
|
|
|
|
For each key, if the key does not exist, it is created. If it already
|
|
exists and it was inherited from its constructor, the constructor's
|
|
key is cloned.
|
|
|
|
You can also pass false for `writable`, which will simply return
|
|
undefined if `prepareMetaPath` discovers any part of the path that
|
|
shared or undefined.
|
|
|
|
@method metaPath
|
|
@for Ember
|
|
@param {Object} obj The object whose meta we are examining
|
|
@param {Array} path An array of keys to walk down
|
|
@param {Boolean} writable whether or not to create a new meta
|
|
(or meta property) if one does not already exist or if it's
|
|
shared with its constructor
|
|
*/
|
|
function metaPath(obj, path, writable) {
|
|
Ember.deprecate("Ember.metaPath is deprecated and will be removed from future releases.");
|
|
var _meta = meta(obj, writable);
|
|
var keyName, value;
|
|
|
|
for (var i=0, l=path.length; i<l; i++) {
|
|
keyName = path[i];
|
|
value = _meta[keyName];
|
|
|
|
if (!value) {
|
|
if (!writable) { return undefined; }
|
|
value = _meta[keyName] = { __ember_source__: obj };
|
|
} else if (value.__ember_source__ !== obj) {
|
|
if (!writable) { return undefined; }
|
|
value = _meta[keyName] = o_create(value);
|
|
value.__ember_source__ = obj;
|
|
}
|
|
|
|
_meta = value;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
__exports__.metaPath = metaPath;/**
|
|
Wraps the passed function so that `this._super` will point to the superFunc
|
|
when the function is invoked. This is the primitive we use to implement
|
|
calls to super.
|
|
|
|
@private
|
|
@method wrap
|
|
@for Ember
|
|
@param {Function} func The function to call
|
|
@param {Function} superFunc The super function.
|
|
@return {Function} wrapped function.
|
|
*/
|
|
|
|
function wrap(func, superFunc) {
|
|
function superWrapper() {
|
|
var ret;
|
|
var sup = this && this.__nextSuper;
|
|
var length = arguments.length;
|
|
|
|
if (this) {
|
|
this.__nextSuper = superFunc;
|
|
}
|
|
|
|
if (length === 0) {
|
|
ret = func.call(this);
|
|
} else if (length === 1) {
|
|
ret = func.call(this, arguments[0]);
|
|
} else if (length === 2) {
|
|
ret = func.call(this, arguments[0], arguments[1]);
|
|
} else {
|
|
var args = new Array(length);
|
|
for (var i = 0; i < length; i++) {
|
|
args[i] = arguments[i];
|
|
}
|
|
ret = apply(this, func, args);
|
|
}
|
|
|
|
if (this) {
|
|
this.__nextSuper = sup;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
superWrapper.wrappedFunction = func;
|
|
superWrapper.wrappedFunction.__ember_arity__ = func.length;
|
|
superWrapper.__ember_observes__ = func.__ember_observes__;
|
|
superWrapper.__ember_observesBefore__ = func.__ember_observesBefore__;
|
|
superWrapper.__ember_listens__ = func.__ember_listens__;
|
|
|
|
return superWrapper;
|
|
}
|
|
|
|
__exports__.wrap = wrap;var EmberArray;
|
|
|
|
/**
|
|
Returns true if the passed object is an array or Array-like.
|
|
|
|
Ember Array Protocol:
|
|
|
|
- the object has an objectAt property
|
|
- the object is a native Array
|
|
- the object is an Object, and has a length property
|
|
|
|
Unlike `Ember.typeOf` this method returns true even if the passed object is
|
|
not formally array but appears to be array-like (i.e. implements `Ember.Array`)
|
|
|
|
```javascript
|
|
Ember.isArray(); // false
|
|
Ember.isArray([]); // true
|
|
Ember.isArray(Ember.ArrayProxy.create({ content: [] })); // true
|
|
```
|
|
|
|
@method isArray
|
|
@for Ember
|
|
@param {Object} obj The object to test
|
|
@return {Boolean} true if the passed object is an array or Array-like
|
|
*/
|
|
// ES6TODO: Move up to runtime? This is only use in ember-metal by concatenatedProperties
|
|
function isArray(obj) {
|
|
var modulePath, type;
|
|
|
|
if (typeof EmberArray === "undefined") {
|
|
modulePath = 'ember-runtime/mixins/array';
|
|
if (Ember.__loader.registry[modulePath]) {
|
|
EmberArray = Ember.__loader.require(modulePath)['default'];
|
|
}
|
|
}
|
|
|
|
if (!obj || obj.setInterval) { return false; }
|
|
if (Array.isArray && Array.isArray(obj)) { return true; }
|
|
if (EmberArray && EmberArray.detect(obj)) { return true; }
|
|
|
|
type = typeOf(obj);
|
|
if ('array' === type) { return true; }
|
|
if ((obj.length !== undefined) && 'object' === type) { return true; }
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
Forces the passed object to be part of an array. If the object is already
|
|
an array or array-like, it will return the object. Otherwise, it will add the object to
|
|
an array. If obj is `null` or `undefined`, it will return an empty array.
|
|
|
|
```javascript
|
|
Ember.makeArray(); // []
|
|
Ember.makeArray(null); // []
|
|
Ember.makeArray(undefined); // []
|
|
Ember.makeArray('lindsay'); // ['lindsay']
|
|
Ember.makeArray([1, 2, 42]); // [1, 2, 42]
|
|
|
|
var controller = Ember.ArrayProxy.create({ content: [] });
|
|
|
|
Ember.makeArray(controller) === controller; // true
|
|
```
|
|
|
|
@method makeArray
|
|
@for Ember
|
|
@param {Object} obj the object
|
|
@return {Array}
|
|
*/
|
|
function makeArray(obj) {
|
|
if (obj === null || obj === undefined) { return []; }
|
|
return isArray(obj) ? obj : [obj];
|
|
}
|
|
|
|
__exports__.makeArray = makeArray;/**
|
|
Checks to see if the `methodName` exists on the `obj`.
|
|
|
|
```javascript
|
|
var foo = { bar: function() { return 'bar'; }, baz: null };
|
|
|
|
Ember.canInvoke(foo, 'bar'); // true
|
|
Ember.canInvoke(foo, 'baz'); // false
|
|
Ember.canInvoke(foo, 'bat'); // false
|
|
```
|
|
|
|
@method canInvoke
|
|
@for Ember
|
|
@param {Object} obj The object to check for the method
|
|
@param {String} methodName The method name to check for
|
|
@return {Boolean}
|
|
*/
|
|
function canInvoke(obj, methodName) {
|
|
return !!(obj && typeof obj[methodName] === 'function');
|
|
}
|
|
|
|
/**
|
|
Checks to see if the `methodName` exists on the `obj`,
|
|
and if it does, invokes it with the arguments passed.
|
|
|
|
```javascript
|
|
var d = new Date('03/15/2013');
|
|
|
|
Ember.tryInvoke(d, 'getTime'); // 1363320000000
|
|
Ember.tryInvoke(d, 'setFullYear', [2014]); // 1394856000000
|
|
Ember.tryInvoke(d, 'noSuchMethod', [2014]); // undefined
|
|
```
|
|
|
|
@method tryInvoke
|
|
@for Ember
|
|
@param {Object} obj The object to check for the method
|
|
@param {String} methodName The method name to check for
|
|
@param {Array} [args] The arguments to pass to the method
|
|
@return {*} the return value of the invoked method or undefined if it cannot be invoked
|
|
*/
|
|
function tryInvoke(obj, methodName, args) {
|
|
if (canInvoke(obj, methodName)) {
|
|
return args ? applyStr(obj, methodName, args) : applyStr(obj, methodName);
|
|
}
|
|
}
|
|
|
|
__exports__.tryInvoke = tryInvoke;// https://github.com/emberjs/ember.js/pull/1617
|
|
var needsFinallyFix = (function() {
|
|
var count = 0;
|
|
try{
|
|
try { }
|
|
finally {
|
|
count++;
|
|
throw new Error('needsFinallyFixTest');
|
|
}
|
|
} catch (e) {}
|
|
|
|
return count !== 1;
|
|
})();
|
|
|
|
/**
|
|
Provides try/finally functionality, while working
|
|
around Safari's double finally bug.
|
|
|
|
```javascript
|
|
var tryable = function() {
|
|
someResource.lock();
|
|
runCallback(); // May throw error.
|
|
};
|
|
|
|
var finalizer = function() {
|
|
someResource.unlock();
|
|
};
|
|
|
|
Ember.tryFinally(tryable, finalizer);
|
|
```
|
|
|
|
@method tryFinally
|
|
@for Ember
|
|
@param {Function} tryable The function to run the try callback
|
|
@param {Function} finalizer The function to run the finally callback
|
|
@param {Object} [binding] The optional calling object. Defaults to 'this'
|
|
@return {*} The return value is the that of the finalizer,
|
|
unless that value is undefined, in which case it is the return value
|
|
of the tryable
|
|
*/
|
|
|
|
var tryFinally;
|
|
if (needsFinallyFix) {
|
|
tryFinally = function(tryable, finalizer, binding) {
|
|
var result, finalResult, finalError;
|
|
|
|
binding = binding || this;
|
|
|
|
try {
|
|
result = tryable.call(binding);
|
|
} finally {
|
|
try {
|
|
finalResult = finalizer.call(binding);
|
|
} catch (e) {
|
|
finalError = e;
|
|
}
|
|
}
|
|
|
|
if (finalError) { throw finalError; }
|
|
|
|
return (finalResult === undefined) ? result : finalResult;
|
|
};
|
|
} else {
|
|
tryFinally = function(tryable, finalizer, binding) {
|
|
var result, finalResult;
|
|
|
|
binding = binding || this;
|
|
|
|
try {
|
|
result = tryable.call(binding);
|
|
} finally {
|
|
finalResult = finalizer.call(binding);
|
|
}
|
|
|
|
return (finalResult === undefined) ? result : finalResult;
|
|
};
|
|
}
|
|
|
|
/**
|
|
Provides try/catch/finally functionality, while working
|
|
around Safari's double finally bug.
|
|
|
|
```javascript
|
|
var tryable = function() {
|
|
for (i = 0, l = listeners.length; i < l; i++) {
|
|
listener = listeners[i];
|
|
beforeValues[i] = listener.before(name, time(), payload);
|
|
}
|
|
|
|
return callback.call(binding);
|
|
};
|
|
|
|
var catchable = function(e) {
|
|
payload = payload || {};
|
|
payload.exception = e;
|
|
};
|
|
|
|
var finalizer = function() {
|
|
for (i = 0, l = listeners.length; i < l; i++) {
|
|
listener = listeners[i];
|
|
listener.after(name, time(), payload, beforeValues[i]);
|
|
}
|
|
};
|
|
|
|
Ember.tryCatchFinally(tryable, catchable, finalizer);
|
|
```
|
|
|
|
@method tryCatchFinally
|
|
@for Ember
|
|
@param {Function} tryable The function to run the try callback
|
|
@param {Function} catchable The function to run the catchable callback
|
|
@param {Function} finalizer The function to run the finally callback
|
|
@param {Object} [binding] The optional calling object. Defaults to 'this'
|
|
@return {*} The return value is the that of the finalizer,
|
|
unless that value is undefined, in which case it is the return value
|
|
of the tryable.
|
|
*/
|
|
var tryCatchFinally;
|
|
if (needsFinallyFix) {
|
|
tryCatchFinally = function(tryable, catchable, finalizer, binding) {
|
|
var result, finalResult, finalError;
|
|
|
|
binding = binding || this;
|
|
|
|
try {
|
|
result = tryable.call(binding);
|
|
} catch(error) {
|
|
result = catchable.call(binding, error);
|
|
} finally {
|
|
try {
|
|
finalResult = finalizer.call(binding);
|
|
} catch (e) {
|
|
finalError = e;
|
|
}
|
|
}
|
|
|
|
if (finalError) { throw finalError; }
|
|
|
|
return (finalResult === undefined) ? result : finalResult;
|
|
};
|
|
} else {
|
|
tryCatchFinally = function(tryable, catchable, finalizer, binding) {
|
|
var result, finalResult;
|
|
|
|
binding = binding || this;
|
|
|
|
try {
|
|
result = tryable.call(binding);
|
|
} catch(error) {
|
|
result = catchable.call(binding, error);
|
|
} finally {
|
|
finalResult = finalizer.call(binding);
|
|
}
|
|
|
|
return (finalResult === undefined) ? result : finalResult;
|
|
};
|
|
}
|
|
|
|
// ........................................
|
|
// TYPING & ARRAY MESSAGING
|
|
//
|
|
|
|
var TYPE_MAP = {};
|
|
var t = "Boolean Number String Function Array Date RegExp Object".split(" ");
|
|
forEach.call(t, function(name) {
|
|
TYPE_MAP[ "[object " + name + "]" ] = name.toLowerCase();
|
|
});
|
|
|
|
var toString = Object.prototype.toString;
|
|
|
|
var EmberObject;
|
|
|
|
/**
|
|
Returns a consistent type for the passed item.
|
|
|
|
Use this instead of the built-in `typeof` to get the type of an item.
|
|
It will return the same result across all browsers and includes a bit
|
|
more detail. Here is what will be returned:
|
|
|
|
| Return Value | Meaning |
|
|
|---------------|------------------------------------------------------|
|
|
| 'string' | String primitive or String object. |
|
|
| 'number' | Number primitive or Number object. |
|
|
| 'boolean' | Boolean primitive or Boolean object. |
|
|
| 'null' | Null value |
|
|
| 'undefined' | Undefined value |
|
|
| 'function' | A function |
|
|
| 'array' | An instance of Array |
|
|
| 'regexp' | An instance of RegExp |
|
|
| 'date' | An instance of Date |
|
|
| 'class' | An Ember class (created using Ember.Object.extend()) |
|
|
| 'instance' | An Ember object instance |
|
|
| 'error' | An instance of the Error object |
|
|
| 'object' | A JavaScript object not inheriting from Ember.Object |
|
|
|
|
Examples:
|
|
|
|
```javascript
|
|
Ember.typeOf(); // 'undefined'
|
|
Ember.typeOf(null); // 'null'
|
|
Ember.typeOf(undefined); // 'undefined'
|
|
Ember.typeOf('michael'); // 'string'
|
|
Ember.typeOf(new String('michael')); // 'string'
|
|
Ember.typeOf(101); // 'number'
|
|
Ember.typeOf(new Number(101)); // 'number'
|
|
Ember.typeOf(true); // 'boolean'
|
|
Ember.typeOf(new Boolean(true)); // 'boolean'
|
|
Ember.typeOf(Ember.makeArray); // 'function'
|
|
Ember.typeOf([1, 2, 90]); // 'array'
|
|
Ember.typeOf(/abc/); // 'regexp'
|
|
Ember.typeOf(new Date()); // 'date'
|
|
Ember.typeOf(Ember.Object.extend()); // 'class'
|
|
Ember.typeOf(Ember.Object.create()); // 'instance'
|
|
Ember.typeOf(new Error('teamocil')); // 'error'
|
|
|
|
// 'normal' JavaScript object
|
|
Ember.typeOf({ a: 'b' }); // 'object'
|
|
```
|
|
|
|
@method typeOf
|
|
@for Ember
|
|
@param {Object} item the item to check
|
|
@return {String} the type
|
|
*/
|
|
function typeOf(item) {
|
|
var ret, modulePath;
|
|
|
|
// ES6TODO: Depends on Ember.Object which is defined in runtime.
|
|
if (typeof EmberObject === "undefined") {
|
|
modulePath = 'ember-runtime/system/object';
|
|
if (Ember.__loader.registry[modulePath]) {
|
|
EmberObject = Ember.__loader.require(modulePath)['default'];
|
|
}
|
|
}
|
|
|
|
ret = (item === null || item === undefined) ? String(item) : TYPE_MAP[toString.call(item)] || 'object';
|
|
|
|
if (ret === 'function') {
|
|
if (EmberObject && EmberObject.detect(item)) ret = 'class';
|
|
} else if (ret === 'object') {
|
|
if (item instanceof Error) ret = 'error';
|
|
else if (EmberObject && item instanceof EmberObject) ret = 'instance';
|
|
else if (item instanceof Date) ret = 'date';
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
Convenience method to inspect an object. This method will attempt to
|
|
convert the object into a useful string description.
|
|
|
|
It is a pretty simple implementation. If you want something more robust,
|
|
use something like JSDump: https://github.com/NV/jsDump
|
|
|
|
@method inspect
|
|
@for Ember
|
|
@param {Object} obj The object you want to inspect.
|
|
@return {String} A description of the object
|
|
@since 1.4.0
|
|
*/
|
|
function inspect(obj) {
|
|
var type = typeOf(obj);
|
|
if (type === 'array') {
|
|
return '[' + obj + ']';
|
|
}
|
|
if (type !== 'object') {
|
|
return obj + '';
|
|
}
|
|
|
|
var v;
|
|
var ret = [];
|
|
for(var key in obj) {
|
|
if (obj.hasOwnProperty(key)) {
|
|
v = obj[key];
|
|
if (v === 'toString') { continue; } // ignore useless items
|
|
if (typeOf(v) === 'function') { v = "function() { ... }"; }
|
|
|
|
if (v && typeof v.toString !== 'function') {
|
|
ret.push(key + ": " + toString.call(v));
|
|
} else {
|
|
ret.push(key + ": " + v);
|
|
}
|
|
}
|
|
}
|
|
return "{" + ret.join(", ") + "}";
|
|
}
|
|
|
|
__exports__.inspect = inspect;// The following functions are intentionally minified to keep the functions
|
|
// below Chrome's function body size inlining limit of 600 chars.
|
|
|
|
function apply(t /* target */, m /* method */, a /* args */) {
|
|
var l = a && a.length;
|
|
if (!a || !l) { return m.call(t); }
|
|
switch (l) {
|
|
case 1: return m.call(t, a[0]);
|
|
case 2: return m.call(t, a[0], a[1]);
|
|
case 3: return m.call(t, a[0], a[1], a[2]);
|
|
case 4: return m.call(t, a[0], a[1], a[2], a[3]);
|
|
case 5: return m.call(t, a[0], a[1], a[2], a[3], a[4]);
|
|
default: return m.apply(t, a);
|
|
}
|
|
}
|
|
|
|
__exports__.apply = apply;function applyStr(t /* target */, m /* method */, a /* args */) {
|
|
var l = a && a.length;
|
|
if (!a || !l) { return t[m](); }
|
|
switch (l) {
|
|
case 1: return t[m](a[0]);
|
|
case 2: return t[m](a[0], a[1]);
|
|
case 3: return t[m](a[0], a[1], a[2]);
|
|
case 4: return t[m](a[0], a[1], a[2], a[3]);
|
|
case 5: return t[m](a[0], a[1], a[2], a[3], a[4]);
|
|
default: return t[m].apply(t, a);
|
|
}
|
|
}
|
|
|
|
__exports__.applyStr = applyStr;__exports__.GUID_KEY = GUID_KEY;
|
|
__exports__.META_DESC = META_DESC;
|
|
__exports__.EMPTY_META = EMPTY_META;
|
|
__exports__.meta = meta;
|
|
__exports__.typeOf = typeOf;
|
|
__exports__.tryCatchFinally = tryCatchFinally;
|
|
__exports__.isArray = isArray;
|
|
__exports__.canInvoke = canInvoke;
|
|
__exports__.tryFinally = tryFinally;
|
|
});
|
|
enifed("ember-metal/watch_key",
|
|
["ember-metal/core","ember-metal/utils","ember-metal/platform","ember-metal/properties","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
var metaFor = __dependency2__.meta;
|
|
var typeOf = __dependency2__.typeOf;
|
|
var o_defineProperty = __dependency3__.defineProperty;
|
|
var hasPropertyAccessors = __dependency3__.hasPropertyAccessors;
|
|
var MANDATORY_SETTER_FUNCTION = __dependency4__.MANDATORY_SETTER_FUNCTION;
|
|
var DEFAULT_GETTER_FUNCTION = __dependency4__.DEFAULT_GETTER_FUNCTION;
|
|
|
|
function watchKey(obj, keyName, meta) {
|
|
// can't watch length on Array - it is special...
|
|
if (keyName === 'length' && typeOf(obj) === 'array') { return; }
|
|
|
|
var m = meta || metaFor(obj), watching = m.watching;
|
|
|
|
// activate watching first time
|
|
if (!watching[keyName]) {
|
|
watching[keyName] = 1;
|
|
|
|
var desc = m.descs[keyName];
|
|
if (desc && desc.willWatch) { desc.willWatch(obj, keyName); }
|
|
|
|
if ('function' === typeof obj.willWatchProperty) {
|
|
obj.willWatchProperty(keyName);
|
|
}
|
|
|
|
|
|
if (hasPropertyAccessors) {
|
|
handleMandatorySetter(m, obj, keyName);
|
|
}
|
|
|
|
} else {
|
|
watching[keyName] = (watching[keyName] || 0) + 1;
|
|
}
|
|
}
|
|
|
|
__exports__.watchKey = watchKey;
|
|
|
|
var handleMandatorySetter = function handleMandatorySetter(m, obj, keyName) {
|
|
var descriptor = Object.getOwnPropertyDescriptor && Object.getOwnPropertyDescriptor(obj, keyName);
|
|
var configurable = descriptor ? descriptor.configurable : true;
|
|
var isWritable = descriptor ? descriptor.writable : true;
|
|
var hasValue = descriptor ? 'value' in descriptor : true;
|
|
|
|
// this x in Y deopts, so keeping it in this function is better;
|
|
if (configurable && isWritable && hasValue && keyName in obj) {
|
|
m.values[keyName] = obj[keyName];
|
|
o_defineProperty(obj, keyName, {
|
|
configurable: true,
|
|
enumerable: Object.prototype.propertyIsEnumerable.call(obj, keyName),
|
|
set: MANDATORY_SETTER_FUNCTION(keyName),
|
|
get: DEFAULT_GETTER_FUNCTION(keyName)
|
|
});
|
|
}
|
|
};
|
|
|
|
|
|
function unwatchKey(obj, keyName, meta) {
|
|
var m = meta || metaFor(obj);
|
|
var watching = m.watching;
|
|
|
|
if (watching[keyName] === 1) {
|
|
watching[keyName] = 0;
|
|
|
|
var desc = m.descs[keyName];
|
|
if (desc && desc.didUnwatch) { desc.didUnwatch(obj, keyName); }
|
|
|
|
if ('function' === typeof obj.didUnwatchProperty) {
|
|
obj.didUnwatchProperty(keyName);
|
|
}
|
|
|
|
|
|
if (hasPropertyAccessors && keyName in obj) {
|
|
o_defineProperty(obj, keyName, {
|
|
configurable: true,
|
|
enumerable: Object.prototype.propertyIsEnumerable.call(obj, keyName),
|
|
set: function(val) {
|
|
// redefine to set as enumerable
|
|
o_defineProperty(obj, keyName, {
|
|
configurable: true,
|
|
writable: true,
|
|
enumerable: true,
|
|
value: val
|
|
});
|
|
delete m.values[keyName];
|
|
},
|
|
get: DEFAULT_GETTER_FUNCTION(keyName)
|
|
});
|
|
}
|
|
|
|
} else if (watching[keyName] > 1) {
|
|
watching[keyName]--;
|
|
}
|
|
}
|
|
|
|
__exports__.unwatchKey = unwatchKey;
|
|
});
|
|
enifed("ember-metal/watch_path",
|
|
["ember-metal/utils","ember-metal/chains","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var metaFor = __dependency1__.meta;
|
|
var typeOf = __dependency1__.typeOf;
|
|
var ChainNode = __dependency2__.ChainNode;
|
|
|
|
// get the chains for the current object. If the current object has
|
|
// chains inherited from the proto they will be cloned and reconfigured for
|
|
// the current object.
|
|
function chainsFor(obj, meta) {
|
|
var m = meta || metaFor(obj);
|
|
var ret = m.chains;
|
|
if (!ret) {
|
|
ret = m.chains = new ChainNode(null, null, obj);
|
|
} else if (ret.value() !== obj) {
|
|
ret = m.chains = ret.copy(obj);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
function watchPath(obj, keyPath, meta) {
|
|
// can't watch length on Array - it is special...
|
|
if (keyPath === 'length' && typeOf(obj) === 'array') { return; }
|
|
|
|
var m = meta || metaFor(obj);
|
|
var watching = m.watching;
|
|
|
|
if (!watching[keyPath]) { // activate watching first time
|
|
watching[keyPath] = 1;
|
|
chainsFor(obj, m).add(keyPath);
|
|
} else {
|
|
watching[keyPath] = (watching[keyPath] || 0) + 1;
|
|
}
|
|
}
|
|
|
|
__exports__.watchPath = watchPath;function unwatchPath(obj, keyPath, meta) {
|
|
var m = meta || metaFor(obj);
|
|
var watching = m.watching;
|
|
|
|
if (watching[keyPath] === 1) {
|
|
watching[keyPath] = 0;
|
|
chainsFor(obj, m).remove(keyPath);
|
|
} else if (watching[keyPath] > 1) {
|
|
watching[keyPath]--;
|
|
}
|
|
}
|
|
|
|
__exports__.unwatchPath = unwatchPath;
|
|
});
|
|
enifed("ember-metal/watching",
|
|
["ember-metal/utils","ember-metal/chains","ember-metal/watch_key","ember-metal/watch_path","ember-metal/path_cache","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember-metal
|
|
*/
|
|
|
|
var typeOf = __dependency1__.typeOf;
|
|
var removeChainWatcher = __dependency2__.removeChainWatcher;
|
|
var flushPendingChains = __dependency2__.flushPendingChains;
|
|
var watchKey = __dependency3__.watchKey;
|
|
var unwatchKey = __dependency3__.unwatchKey;
|
|
var watchPath = __dependency4__.watchPath;
|
|
var unwatchPath = __dependency4__.unwatchPath;
|
|
|
|
var isPath = __dependency5__.isPath;
|
|
|
|
/**
|
|
Starts watching a property on an object. Whenever the property changes,
|
|
invokes `Ember.propertyWillChange` and `Ember.propertyDidChange`. This is the
|
|
primitive used by observers and dependent keys; usually you will never call
|
|
this method directly but instead use higher level methods like
|
|
`Ember.addObserver()`
|
|
|
|
@private
|
|
@method watch
|
|
@for Ember
|
|
@param obj
|
|
@param {String} keyName
|
|
*/
|
|
function watch(obj, _keyPath, m) {
|
|
// can't watch length on Array - it is special...
|
|
if (_keyPath === 'length' && typeOf(obj) === 'array') { return; }
|
|
|
|
if (!isPath(_keyPath)) {
|
|
watchKey(obj, _keyPath, m);
|
|
} else {
|
|
watchPath(obj, _keyPath, m);
|
|
}
|
|
}
|
|
|
|
__exports__.watch = watch;
|
|
|
|
function isWatching(obj, key) {
|
|
var meta = obj['__ember_meta__'];
|
|
return (meta && meta.watching[key]) > 0;
|
|
}
|
|
|
|
__exports__.isWatching = isWatching;watch.flushPending = flushPendingChains;
|
|
|
|
function unwatch(obj, _keyPath, m) {
|
|
// can't watch length on Array - it is special...
|
|
if (_keyPath === 'length' && typeOf(obj) === 'array') { return; }
|
|
|
|
if (!isPath(_keyPath)) {
|
|
unwatchKey(obj, _keyPath, m);
|
|
} else {
|
|
unwatchPath(obj, _keyPath, m);
|
|
}
|
|
}
|
|
|
|
__exports__.unwatch = unwatch;var NODE_STACK = [];
|
|
|
|
/**
|
|
Tears down the meta on an object so that it can be garbage collected.
|
|
Multiple calls will have no effect.
|
|
|
|
@method destroy
|
|
@for Ember
|
|
@param {Object} obj the object to destroy
|
|
@return {void}
|
|
*/
|
|
function destroy(obj) {
|
|
var meta = obj['__ember_meta__'], node, nodes, key, nodeObject;
|
|
if (meta) {
|
|
obj['__ember_meta__'] = null;
|
|
// remove chainWatchers to remove circular references that would prevent GC
|
|
node = meta.chains;
|
|
if (node) {
|
|
NODE_STACK.push(node);
|
|
// process tree
|
|
while (NODE_STACK.length > 0) {
|
|
node = NODE_STACK.pop();
|
|
// push children
|
|
nodes = node._chains;
|
|
if (nodes) {
|
|
for (key in nodes) {
|
|
if (nodes.hasOwnProperty(key)) {
|
|
NODE_STACK.push(nodes[key]);
|
|
}
|
|
}
|
|
}
|
|
// remove chainWatcher in node object
|
|
if (node._watching) {
|
|
nodeObject = node._object;
|
|
if (nodeObject) {
|
|
removeChainWatcher(nodeObject, node._key, node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
__exports__.destroy = destroy;
|
|
});
|
|
enifed("ember-routing-htmlbars",
|
|
["ember-metal/core","ember-htmlbars/helpers","ember-routing-htmlbars/helpers/outlet","ember-routing-htmlbars/helpers/render","ember-routing-htmlbars/helpers/link-to","ember-routing-htmlbars/helpers/action","ember-routing-htmlbars/helpers/query-params","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
Ember Routing HTMLBars Helpers
|
|
|
|
@module ember
|
|
@submodule ember-routing-htmlbars
|
|
@requires ember-routing
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
|
|
var registerHelper = __dependency2__.registerHelper;
|
|
|
|
var outletHelper = __dependency3__.outletHelper;
|
|
var renderHelper = __dependency4__.renderHelper;
|
|
var linkToHelper = __dependency5__.linkToHelper;
|
|
var deprecatedLinkToHelper = __dependency5__.deprecatedLinkToHelper;
|
|
var actionHelper = __dependency6__.actionHelper;
|
|
var queryParamsHelper = __dependency7__.queryParamsHelper;
|
|
|
|
registerHelper('outlet', outletHelper);
|
|
registerHelper('render', renderHelper);
|
|
registerHelper('link-to', linkToHelper);
|
|
registerHelper('linkTo', deprecatedLinkToHelper);
|
|
registerHelper('action', actionHelper);
|
|
registerHelper('query-params', queryParamsHelper);
|
|
|
|
__exports__["default"] = Ember;
|
|
});
|
|
enifed("ember-routing-htmlbars/helpers/action",
|
|
["ember-metal/core","ember-metal/utils","ember-metal/run_loop","ember-views/streams/utils","ember-views/system/utils","ember-views/system/action_manager","ember-metal/array","ember-metal/streams/utils","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-routing-htmlbars
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
// Handlebars, uuid, FEATURES, assert, deprecate
|
|
var uuid = __dependency2__.uuid;
|
|
var run = __dependency3__["default"];
|
|
var readUnwrappedModel = __dependency4__.readUnwrappedModel;
|
|
var isSimpleClick = __dependency5__.isSimpleClick;
|
|
var ActionManager = __dependency6__["default"];
|
|
var indexOf = __dependency7__.indexOf;
|
|
var isStream = __dependency8__.isStream;
|
|
|
|
function actionArgs(parameters, actionName) {
|
|
var ret, i, l;
|
|
|
|
if (actionName === undefined) {
|
|
ret = new Array(parameters.length);
|
|
for (i=0, l=parameters.length;i<l;i++) {
|
|
ret[i] = readUnwrappedModel(parameters[i]);
|
|
}
|
|
} else {
|
|
ret = new Array(parameters.length + 1);
|
|
ret[0] = actionName;
|
|
for (i=0, l=parameters.length;i<l; i++) {
|
|
ret[i + 1] = readUnwrappedModel(parameters[i]);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
var ActionHelper = {};
|
|
|
|
// registeredActions is re-exported for compatibility with older plugins
|
|
// that were using this undocumented API.
|
|
ActionHelper.registeredActions = ActionManager.registeredActions;
|
|
|
|
__exports__.ActionHelper = ActionHelper;
|
|
|
|
var keys = ["alt", "shift", "meta", "ctrl"];
|
|
|
|
var POINTER_EVENT_TYPE_REGEX = /^click|mouse|touch/;
|
|
|
|
var isAllowedEvent = function(event, allowedKeys) {
|
|
if (typeof allowedKeys === "undefined") {
|
|
if (POINTER_EVENT_TYPE_REGEX.test(event.type)) {
|
|
return isSimpleClick(event);
|
|
} else {
|
|
allowedKeys = '';
|
|
}
|
|
}
|
|
|
|
if (allowedKeys.indexOf("any") >= 0) {
|
|
return true;
|
|
}
|
|
|
|
for (var i=0, l=keys.length;i<l;i++) {
|
|
if (event[keys[i] + "Key"] && allowedKeys.indexOf(keys[i]) === -1) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
var keyEvents = ['keyUp', 'keyPress', 'keyDown'];
|
|
|
|
function ignoreKeyEvent(eventName, event, keyCode) {
|
|
var any = 'any';
|
|
keyCode = keyCode || any;
|
|
return indexOf.call(keyEvents, eventName) !== -1 && keyCode !== any && keyCode !== event.which.toString();
|
|
}
|
|
|
|
ActionHelper.registerAction = function(actionNameOrStream, options, allowedKeys) {
|
|
var actionId = uuid();
|
|
var eventName = options.eventName;
|
|
var parameters = options.parameters;
|
|
|
|
ActionManager.registeredActions[actionId] = {
|
|
eventName: eventName,
|
|
handler: function handleRegisteredAction(event) {
|
|
if (!isAllowedEvent(event, allowedKeys)) { return true; }
|
|
|
|
if (options.preventDefault !== false) {
|
|
event.preventDefault();
|
|
}
|
|
|
|
if (options.bubbles === false) {
|
|
event.stopPropagation();
|
|
}
|
|
|
|
var target = options.target.value();
|
|
|
|
|
|
var actionName;
|
|
|
|
if (isStream(actionNameOrStream)) {
|
|
actionName = actionNameOrStream.value();
|
|
|
|
Ember.assert("You specified a quoteless path to the {{action}} helper " +
|
|
"which did not resolve to an action name (a string). " +
|
|
"Perhaps you meant to use a quoted actionName? (e.g. {{action 'save'}}).",
|
|
typeof actionName === 'string');
|
|
} else {
|
|
actionName = actionNameOrStream;
|
|
}
|
|
|
|
run(function runRegisteredAction() {
|
|
if (target.send) {
|
|
target.send.apply(target, actionArgs(parameters, actionName));
|
|
} else {
|
|
Ember.assert("The action '" + actionName + "' did not exist on " + target, typeof target[actionName] === 'function');
|
|
target[actionName].apply(target, actionArgs(parameters));
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
options.view.on('willClearRender', function() {
|
|
delete ActionManager.registeredActions[actionId];
|
|
});
|
|
|
|
return actionId;
|
|
};
|
|
|
|
/**
|
|
The `{{action}}` helper provides a useful shortcut for registering an HTML
|
|
element within a template for a single DOM event and forwarding that
|
|
interaction to the template's controller or specified `target` option.
|
|
|
|
If the controller does not implement the specified action, the event is sent
|
|
to the current route, and it bubbles up the route hierarchy from there.
|
|
|
|
For more advanced event handling see [Ember.Component](/api/classes/Ember.Component.html)
|
|
|
|
|
|
### Use
|
|
Given the following application Handlebars template on the page
|
|
|
|
```handlebars
|
|
<div {{action 'anActionName'}}>
|
|
click me
|
|
</div>
|
|
```
|
|
|
|
And application code
|
|
|
|
```javascript
|
|
App.ApplicationController = Ember.Controller.extend({
|
|
actions: {
|
|
anActionName: function() {
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
Will result in the following rendered HTML
|
|
|
|
```html
|
|
<div class="ember-view">
|
|
<div data-ember-action="1">
|
|
click me
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
Clicking "click me" will trigger the `anActionName` action of the
|
|
`App.ApplicationController`. In this case, no additional parameters will be passed.
|
|
|
|
If you provide additional parameters to the helper:
|
|
|
|
```handlebars
|
|
<button {{action 'edit' post}}>Edit</button>
|
|
```
|
|
|
|
Those parameters will be passed along as arguments to the JavaScript
|
|
function implementing the action.
|
|
|
|
### Event Propagation
|
|
|
|
Events triggered through the action helper will automatically have
|
|
`.preventDefault()` called on them. You do not need to do so in your event
|
|
handlers. If you need to allow event propagation (to handle file inputs for
|
|
example) you can supply the `preventDefault=false` option to the `{{action}}` helper:
|
|
|
|
```handlebars
|
|
<div {{action "sayHello" preventDefault=false}}>
|
|
<input type="file" />
|
|
<input type="checkbox" />
|
|
</div>
|
|
```
|
|
|
|
To disable bubbling, pass `bubbles=false` to the helper:
|
|
|
|
```handlebars
|
|
<button {{action 'edit' post bubbles=false}}>Edit</button>
|
|
```
|
|
|
|
If you need the default handler to trigger you should either register your
|
|
own event handler, or use event methods on your view class. See [Ember.View](/api/classes/Ember.View.html)
|
|
'Responding to Browser Events' for more information.
|
|
|
|
### Specifying DOM event type
|
|
|
|
By default the `{{action}}` helper registers for DOM `click` events. You can
|
|
supply an `on` option to the helper to specify a different DOM event name:
|
|
|
|
```handlebars
|
|
<div {{action "anActionName" on="doubleClick"}}>
|
|
click me
|
|
</div>
|
|
```
|
|
|
|
See `Ember.View` 'Responding to Browser Events' for a list of
|
|
acceptable DOM event names.
|
|
|
|
### Specifying whitelisted modifier keys
|
|
|
|
By default the `{{action}}` helper will ignore click event with pressed modifier
|
|
keys. You can supply an `allowedKeys` option to specify which keys should not be ignored.
|
|
|
|
```handlebars
|
|
<div {{action "anActionName" allowedKeys="alt"}}>
|
|
click me
|
|
</div>
|
|
```
|
|
|
|
This way the `{{action}}` will fire when clicking with the alt key pressed down.
|
|
|
|
Alternatively, supply "any" to the `allowedKeys` option to accept any combination of modifier keys.
|
|
|
|
```handlebars
|
|
<div {{action "anActionName" allowedKeys="any"}}>
|
|
click me with any key pressed
|
|
</div>
|
|
```
|
|
|
|
### Specifying a Target
|
|
|
|
There are several possible target objects for `{{action}}` helpers:
|
|
|
|
In a typical Ember application, where templates are managed through use of the
|
|
`{{outlet}}` helper, actions will bubble to the current controller, then
|
|
to the current route, and then up the route hierarchy.
|
|
|
|
Alternatively, a `target` option can be provided to the helper to change
|
|
which object will receive the method call. This option must be a path
|
|
to an object, accessible in the current context:
|
|
|
|
```handlebars
|
|
{{! the application template }}
|
|
<div {{action "anActionName" target=view}}>
|
|
click me
|
|
</div>
|
|
```
|
|
|
|
```javascript
|
|
App.ApplicationView = Ember.View.extend({
|
|
actions: {
|
|
anActionName: function(){}
|
|
}
|
|
});
|
|
|
|
```
|
|
|
|
### Additional Parameters
|
|
|
|
You may specify additional parameters to the `{{action}}` helper. These
|
|
parameters are passed along as the arguments to the JavaScript function
|
|
implementing the action.
|
|
|
|
```handlebars
|
|
{{#each person in people}}
|
|
<div {{action "edit" person}}>
|
|
click me
|
|
</div>
|
|
{{/each}}
|
|
```
|
|
|
|
Clicking "click me" will trigger the `edit` method on the current controller
|
|
with the value of `person` as a parameter.
|
|
|
|
@method action
|
|
@for Ember.Handlebars.helpers
|
|
@param {String} actionName
|
|
@param {Object} [context]*
|
|
@param {Hash} options
|
|
*/
|
|
function actionHelper(params, hash, options, env) {
|
|
|
|
var target;
|
|
if (!hash.target) {
|
|
target = this.getStream('controller');
|
|
} else if (isStream(hash.target)) {
|
|
target = hash.target;
|
|
} else {
|
|
target = this.getStream(hash.target);
|
|
}
|
|
|
|
// Ember.assert("You specified a quoteless path to the {{action}} helper which did not resolve to an action name (a string). Perhaps you meant to use a quoted actionName? (e.g. {{action 'save'}}).", !params[0].isStream);
|
|
// Ember.deprecate("You specified a quoteless path to the {{action}} helper which did not resolve to an action name (a string). Perhaps you meant to use a quoted actionName? (e.g. {{action 'save'}}).", params[0].isStream);
|
|
|
|
var actionOptions = {
|
|
eventName: hash.on || "click",
|
|
parameters: params.slice(1),
|
|
view: this,
|
|
bubbles: hash.bubbles,
|
|
preventDefault: hash.preventDefault,
|
|
target: target,
|
|
withKeyCode: hash.withKeyCode
|
|
};
|
|
|
|
var actionId = ActionHelper.registerAction(params[0], actionOptions, hash.allowedKeys);
|
|
env.dom.setAttribute(options.element, 'data-ember-action', actionId);
|
|
}
|
|
|
|
__exports__.actionHelper = actionHelper;
|
|
});
|
|
enifed("ember-routing-htmlbars/helpers/link-to",
|
|
["ember-metal/core","ember-routing-views/views/link","ember-metal/streams/utils","ember-runtime/mixins/controller","ember-htmlbars/utils/string","ember-htmlbars","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-routing-handlebars
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
// assert
|
|
var LinkView = __dependency2__.LinkView;
|
|
var read = __dependency3__.read;
|
|
var isStream = __dependency3__.isStream;
|
|
var ControllerMixin = __dependency4__["default"];
|
|
var escapeExpression = __dependency5__.escapeExpression;
|
|
|
|
// We need the HTMLBars view helper from ensure ember-htmlbars.
|
|
// This ensures it is loaded first:
|
|
|
|
/**
|
|
The `{{link-to}}` helper renders a link to the supplied
|
|
`routeName` passing an optionally supplied model to the
|
|
route as its `model` context of the route. The block
|
|
for `{{link-to}}` becomes the innerHTML of the rendered
|
|
element:
|
|
|
|
```handlebars
|
|
{{#link-to 'photoGallery'}}
|
|
Great Hamster Photos
|
|
{{/link-to}}
|
|
```
|
|
|
|
You can also use an inline form of `{{link-to}}` helper by
|
|
passing the link text as the first argument
|
|
to the helper:
|
|
|
|
```handlebars
|
|
{{link-to 'Great Hamster Photos' 'photoGallery'}}
|
|
```
|
|
|
|
Both will result in:
|
|
|
|
```html
|
|
<a href="/hamster-photos">
|
|
Great Hamster Photos
|
|
</a>
|
|
```
|
|
|
|
### Supplying a tagName
|
|
By default `{{link-to}}` renders an `<a>` element. This can
|
|
be overridden for a single use of `{{link-to}}` by supplying
|
|
a `tagName` option:
|
|
|
|
```handlebars
|
|
{{#link-to 'photoGallery' tagName="li"}}
|
|
Great Hamster Photos
|
|
{{/link-to}}
|
|
```
|
|
|
|
```html
|
|
<li>
|
|
Great Hamster Photos
|
|
</li>
|
|
```
|
|
|
|
To override this option for your entire application, see
|
|
"Overriding Application-wide Defaults".
|
|
|
|
### Disabling the `link-to` helper
|
|
By default `{{link-to}}` is enabled.
|
|
any passed value to `disabled` helper property will disable the `link-to` helper.
|
|
|
|
static use: the `disabled` option:
|
|
|
|
```handlebars
|
|
{{#link-to 'photoGallery' disabled=true}}
|
|
Great Hamster Photos
|
|
{{/link-to}}
|
|
```
|
|
|
|
dynamic use: the `disabledWhen` option:
|
|
|
|
```handlebars
|
|
{{#link-to 'photoGallery' disabledWhen=controller.someProperty}}
|
|
Great Hamster Photos
|
|
{{/link-to}}
|
|
```
|
|
|
|
any passed value to `disabled` will disable it except `undefined`.
|
|
to ensure that only `true` disable the `link-to` helper you can
|
|
override the global behaviour of `Ember.LinkView`.
|
|
|
|
```javascript
|
|
Ember.LinkView.reopen({
|
|
disabled: Ember.computed(function(key, value) {
|
|
if (value !== undefined) {
|
|
this.set('_isDisabled', value === true);
|
|
}
|
|
return value === true ? get(this, 'disabledClass') : false;
|
|
})
|
|
});
|
|
```
|
|
|
|
see "Overriding Application-wide Defaults" for more.
|
|
|
|
### Handling `href`
|
|
`{{link-to}}` will use your application's Router to
|
|
fill the element's `href` property with a url that
|
|
matches the path to the supplied `routeName` for your
|
|
routers's configured `Location` scheme, which defaults
|
|
to Ember.HashLocation.
|
|
|
|
### Handling current route
|
|
`{{link-to}}` will apply a CSS class name of 'active'
|
|
when the application's current route matches
|
|
the supplied routeName. For example, if the application's
|
|
current route is 'photoGallery.recent' the following
|
|
use of `{{link-to}}`:
|
|
|
|
```handlebars
|
|
{{#link-to 'photoGallery.recent'}}
|
|
Great Hamster Photos from the last week
|
|
{{/link-to}}
|
|
```
|
|
|
|
will result in
|
|
|
|
```html
|
|
<a href="/hamster-photos/this-week" class="active">
|
|
Great Hamster Photos
|
|
</a>
|
|
```
|
|
|
|
The CSS class name used for active classes can be customized
|
|
for a single use of `{{link-to}}` by passing an `activeClass`
|
|
option:
|
|
|
|
```handlebars
|
|
{{#link-to 'photoGallery.recent' activeClass="current-url"}}
|
|
Great Hamster Photos from the last week
|
|
{{/link-to}}
|
|
```
|
|
|
|
```html
|
|
<a href="/hamster-photos/this-week" class="current-url">
|
|
Great Hamster Photos
|
|
</a>
|
|
```
|
|
|
|
To override this option for your entire application, see
|
|
"Overriding Application-wide Defaults".
|
|
|
|
### Supplying a model
|
|
An optional model argument can be used for routes whose
|
|
paths contain dynamic segments. This argument will become
|
|
the model context of the linked route:
|
|
|
|
```javascript
|
|
App.Router.map(function() {
|
|
this.resource("photoGallery", {path: "hamster-photos/:photo_id"});
|
|
});
|
|
```
|
|
|
|
```handlebars
|
|
{{#link-to 'photoGallery' aPhoto}}
|
|
{{aPhoto.title}}
|
|
{{/link-to}}
|
|
```
|
|
|
|
```html
|
|
<a href="/hamster-photos/42">
|
|
Tomster
|
|
</a>
|
|
```
|
|
|
|
### Supplying multiple models
|
|
For deep-linking to route paths that contain multiple
|
|
dynamic segments, multiple model arguments can be used.
|
|
As the router transitions through the route path, each
|
|
supplied model argument will become the context for the
|
|
route with the dynamic segments:
|
|
|
|
```javascript
|
|
App.Router.map(function() {
|
|
this.resource("photoGallery", {path: "hamster-photos/:photo_id"}, function() {
|
|
this.route("comment", {path: "comments/:comment_id"});
|
|
});
|
|
});
|
|
```
|
|
This argument will become the model context of the linked route:
|
|
|
|
```handlebars
|
|
{{#link-to 'photoGallery.comment' aPhoto comment}}
|
|
{{comment.body}}
|
|
{{/link-to}}
|
|
```
|
|
|
|
```html
|
|
<a href="/hamster-photos/42/comment/718">
|
|
A+++ would snuggle again.
|
|
</a>
|
|
```
|
|
|
|
### Supplying an explicit dynamic segment value
|
|
If you don't have a model object available to pass to `{{link-to}}`,
|
|
an optional string or integer argument can be passed for routes whose
|
|
paths contain dynamic segments. This argument will become the value
|
|
of the dynamic segment:
|
|
|
|
```javascript
|
|
App.Router.map(function() {
|
|
this.resource("photoGallery", {path: "hamster-photos/:photo_id"});
|
|
});
|
|
```
|
|
|
|
```handlebars
|
|
{{#link-to 'photoGallery' aPhotoId}}
|
|
{{aPhoto.title}}
|
|
{{/link-to}}
|
|
```
|
|
|
|
```html
|
|
<a href="/hamster-photos/42">
|
|
Tomster
|
|
</a>
|
|
```
|
|
|
|
When transitioning into the linked route, the `model` hook will
|
|
be triggered with parameters including this passed identifier.
|
|
|
|
### Allowing Default Action
|
|
|
|
By default the `{{link-to}}` helper prevents the default browser action
|
|
by calling `preventDefault()` as this sort of action bubbling is normally
|
|
handled internally and we do not want to take the browser to a new URL (for
|
|
example).
|
|
|
|
If you need to override this behavior specify `preventDefault=false` in
|
|
your template:
|
|
|
|
```handlebars
|
|
{{#link-to 'photoGallery' aPhotoId preventDefault=false}}
|
|
{{aPhotoId.title}}
|
|
{{/link-to}}
|
|
```
|
|
|
|
### Overriding attributes
|
|
You can override any given property of the Ember.LinkView
|
|
that is generated by the `{{link-to}}` helper by passing
|
|
key/value pairs, like so:
|
|
|
|
```handlebars
|
|
{{#link-to aPhoto tagName='li' title='Following this link will change your life' classNames='pic sweet'}}
|
|
Uh-mazing!
|
|
{{/link-to}}
|
|
```
|
|
|
|
See [Ember.LinkView](/api/classes/Ember.LinkView.html) for a
|
|
complete list of overrideable properties. Be sure to also
|
|
check out inherited properties of `LinkView`.
|
|
|
|
### Overriding Application-wide Defaults
|
|
``{{link-to}}`` creates an instance of Ember.LinkView
|
|
for rendering. To override options for your entire
|
|
application, reopen Ember.LinkView and supply the
|
|
desired values:
|
|
|
|
``` javascript
|
|
Ember.LinkView.reopen({
|
|
activeClass: "is-active",
|
|
tagName: 'li'
|
|
})
|
|
```
|
|
|
|
It is also possible to override the default event in
|
|
this manner:
|
|
|
|
``` javascript
|
|
Ember.LinkView.reopen({
|
|
eventName: 'customEventName'
|
|
});
|
|
```
|
|
|
|
@method link-to
|
|
@for Ember.Handlebars.helpers
|
|
@param {String} routeName
|
|
@param {Object} [context]*
|
|
@param [options] {Object} Handlebars key/value pairs of options, you can override any property of Ember.LinkView
|
|
@return {String} HTML string
|
|
@see {Ember.LinkView}
|
|
*/
|
|
function linkToHelper(params, hash, options, env) {
|
|
var shouldEscape = !hash.unescaped;
|
|
var queryParamsObject;
|
|
|
|
Ember.assert("You must provide one or more parameters to the link-to helper.", params.length);
|
|
|
|
var lastParam = params[params.length - 1];
|
|
|
|
if (lastParam && lastParam.isQueryParams) {
|
|
hash.queryParamsObject = queryParamsObject = params.pop();
|
|
}
|
|
|
|
if (hash.disabledWhen) {
|
|
hash.disabled = hash.disabledWhen;
|
|
delete hash.disabledWhen;
|
|
}
|
|
|
|
if (!options.template) {
|
|
var linkTitle = params.shift();
|
|
|
|
if (isStream(linkTitle)) {
|
|
hash.linkTitle = { stream: linkTitle };
|
|
}
|
|
|
|
options.template = {
|
|
isHTMLBars: true,
|
|
render: function() {
|
|
var value = read(linkTitle);
|
|
if (value) {
|
|
return shouldEscape ? escapeExpression(value) : value;
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
for (var i = 0; i < params.length; i++) {
|
|
if (isStream(params[i])) {
|
|
var lazyValue = params[i];
|
|
|
|
if (!lazyValue._isController) {
|
|
while (ControllerMixin.detect(lazyValue.value())) {
|
|
lazyValue = lazyValue.get('model');
|
|
}
|
|
}
|
|
|
|
params[i] = lazyValue;
|
|
}
|
|
}
|
|
|
|
hash.params = params;
|
|
|
|
options.helperName = options.helperName || 'link-to';
|
|
|
|
return env.helpers.view.helperFunction.call(this, [LinkView], hash, options, env);
|
|
}
|
|
|
|
/**
|
|
See [link-to](/api/classes/Ember.Handlebars.helpers.html#method_link-to)
|
|
|
|
@method linkTo
|
|
@for Ember.Handlebars.helpers
|
|
@deprecated
|
|
@param {String} routeName
|
|
@param {Object} [context]*
|
|
@return {String} HTML string
|
|
*/
|
|
function deprecatedLinkToHelper(params, hash, options, env) {
|
|
Ember.deprecate("The 'linkTo' view helper is deprecated in favor of 'link-to'");
|
|
|
|
return env.helpers['link-to'].helperFunction.call(this, params, hash, options, env);
|
|
}
|
|
|
|
__exports__.deprecatedLinkToHelper = deprecatedLinkToHelper;
|
|
__exports__.linkToHelper = linkToHelper;
|
|
});
|
|
enifed("ember-routing-htmlbars/helpers/outlet",
|
|
["ember-metal/core","ember-metal/property_set","ember-routing-views/views/outlet","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-routing-htmlbars
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
// assert
|
|
var set = __dependency2__.set;
|
|
var OutletView = __dependency3__.OutletView;
|
|
|
|
/**
|
|
The `outlet` helper is a placeholder that the router will fill in with
|
|
the appropriate template based on the current state of the application.
|
|
|
|
``` handlebars
|
|
{{outlet}}
|
|
```
|
|
|
|
By default, a template based on Ember's naming conventions will be rendered
|
|
into the `outlet` (e.g. `App.PostsRoute` will render the `posts` template).
|
|
|
|
You can render a different template by using the `render()` method in the
|
|
route's `renderTemplate` hook. The following will render the `favoritePost`
|
|
template into the `outlet`.
|
|
|
|
``` javascript
|
|
App.PostsRoute = Ember.Route.extend({
|
|
renderTemplate: function() {
|
|
this.render('favoritePost');
|
|
}
|
|
});
|
|
```
|
|
|
|
You can create custom named outlets for more control.
|
|
|
|
``` handlebars
|
|
{{outlet 'favoritePost'}}
|
|
{{outlet 'posts'}}
|
|
```
|
|
|
|
Then you can define what template is rendered into each outlet in your
|
|
route.
|
|
|
|
|
|
``` javascript
|
|
App.PostsRoute = Ember.Route.extend({
|
|
renderTemplate: function() {
|
|
this.render('favoritePost', { outlet: 'favoritePost' });
|
|
this.render('posts', { outlet: 'posts' });
|
|
}
|
|
});
|
|
```
|
|
|
|
You can specify the view that the outlet uses to contain and manage the
|
|
templates rendered into it.
|
|
|
|
``` handlebars
|
|
{{outlet view='sectionContainer'}}
|
|
```
|
|
|
|
``` javascript
|
|
App.SectionContainer = Ember.ContainerView.extend({
|
|
tagName: 'section',
|
|
classNames: ['special']
|
|
});
|
|
```
|
|
|
|
@method outlet
|
|
@for Ember.Handlebars.helpers
|
|
@param {String} property the property on the controller
|
|
that holds the view for this outlet
|
|
@return {String} HTML string
|
|
*/
|
|
function outletHelper(params, hash, options, env) {
|
|
var outletSource;
|
|
var viewName;
|
|
var viewClass;
|
|
var viewFullName;
|
|
|
|
Ember.assert(
|
|
"Using {{outlet}} with an unquoted name is not supported.",
|
|
params.length === 0 || typeof params[0] === 'string'
|
|
);
|
|
|
|
var property = params[0] || 'main';
|
|
|
|
outletSource = this;
|
|
while (!outletSource.get('template.isTop')) {
|
|
outletSource = outletSource.get('_parentView');
|
|
}
|
|
set(this, 'outletSource', outletSource);
|
|
|
|
// provide controller override
|
|
viewName = hash.view;
|
|
|
|
if (viewName) {
|
|
viewFullName = 'view:' + viewName;
|
|
Ember.assert(
|
|
"Using a quoteless view parameter with {{outlet}} is not supported." +
|
|
" Please update to quoted usage '{{outlet ... view=\"" + viewName + "\"}}.",
|
|
typeof hash.view === 'string'
|
|
);
|
|
Ember.assert(
|
|
"The view name you supplied '" + viewName + "' did not resolve to a view.",
|
|
this.container.has(viewFullName)
|
|
);
|
|
}
|
|
|
|
viewClass = viewName ? this.container.lookupFactory(viewFullName) : hash.viewClass || OutletView;
|
|
|
|
hash.currentViewBinding = '_view.outletSource._outlets.' + property;
|
|
|
|
options.helperName = options.helperName || 'outlet';
|
|
|
|
return env.helpers.view.helperFunction.call(this, [viewClass], hash, options, env);
|
|
}
|
|
|
|
__exports__.outletHelper = outletHelper;
|
|
});
|
|
enifed("ember-routing-htmlbars/helpers/query-params",
|
|
["ember-metal/core","ember-routing/system/query_params","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-routing-htmlbars
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
// assert
|
|
var QueryParams = __dependency2__["default"];
|
|
|
|
/**
|
|
This is a sub-expression to be used in conjunction with the link-to helper.
|
|
It will supply url query parameters to the target route.
|
|
|
|
Example
|
|
|
|
{{#link-to 'posts' (query-params direction="asc")}}Sort{{/link-to}}
|
|
|
|
@method query-params
|
|
@for Ember.Handlebars.helpers
|
|
@param {Object} hash takes a hash of query parameters
|
|
@return {String} HTML string
|
|
*/
|
|
function queryParamsHelper(params, hash) {
|
|
Ember.assert("The `query-params` helper only accepts hash parameters, e.g. (query-params queryParamPropertyName='foo') as opposed to just (query-params 'foo')", params.length === 0);
|
|
|
|
return QueryParams.create({
|
|
values: hash
|
|
});
|
|
}
|
|
|
|
__exports__.queryParamsHelper = queryParamsHelper;
|
|
});
|
|
enifed("ember-routing-htmlbars/helpers/render",
|
|
["ember-metal/core","ember-metal/error","ember-runtime/system/string","ember-routing/system/generate_controller","ember-htmlbars/helpers/view","ember-metal/streams/utils","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-routing-htmlbars
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
// assert, deprecate
|
|
var EmberError = __dependency2__["default"];
|
|
var camelize = __dependency3__.camelize;
|
|
var generateControllerFactory = __dependency4__.generateControllerFactory;
|
|
var generateController = __dependency4__["default"];
|
|
var ViewHelper = __dependency5__.ViewHelper;
|
|
var isStream = __dependency6__.isStream;
|
|
|
|
/**
|
|
Calling ``{{render}}`` from within a template will insert another
|
|
template that matches the provided name. The inserted template will
|
|
access its properties on its own controller (rather than the controller
|
|
of the parent template).
|
|
|
|
If a view class with the same name exists, the view class also will be used.
|
|
|
|
Note: A given controller may only be used *once* in your app in this manner.
|
|
A singleton instance of the controller will be created for you.
|
|
|
|
Example:
|
|
|
|
```javascript
|
|
App.NavigationController = Ember.Controller.extend({
|
|
who: "world"
|
|
});
|
|
```
|
|
|
|
```handlebars
|
|
<!-- navigation.hbs -->
|
|
Hello, {{who}}.
|
|
```
|
|
|
|
```handlebars
|
|
<!-- application.hbs -->
|
|
<h1>My great app</h1>
|
|
{{render "navigation"}}
|
|
```
|
|
|
|
```html
|
|
<h1>My great app</h1>
|
|
<div class='ember-view'>
|
|
Hello, world.
|
|
</div>
|
|
```
|
|
|
|
Optionally you may provide a second argument: a property path
|
|
that will be bound to the `model` property of the controller.
|
|
|
|
If a `model` property path is specified, then a new instance of the
|
|
controller will be created and `{{render}}` can be used multiple times
|
|
with the same name.
|
|
|
|
For example if you had this `author` template.
|
|
|
|
```handlebars
|
|
<div class="author">
|
|
Written by {{firstName}} {{lastName}}.
|
|
Total Posts: {{postCount}}
|
|
</div>
|
|
```
|
|
|
|
You could render it inside the `post` template using the `render` helper.
|
|
|
|
```handlebars
|
|
<div class="post">
|
|
<h1>{{title}}</h1>
|
|
<div>{{body}}</div>
|
|
{{render "author" author}}
|
|
</div>
|
|
```
|
|
|
|
@method render
|
|
@for Ember.Handlebars.helpers
|
|
@param {String} name
|
|
@param {Object?} context
|
|
@param {Hash} options
|
|
@return {String} HTML string
|
|
*/
|
|
function renderHelper(params, hash, options, env) {
|
|
var container, router, controller, view, initialContext;
|
|
|
|
var name = params[0];
|
|
var context = params[1];
|
|
|
|
container = this._keywords.controller.value().container;
|
|
router = container.lookup('router:main');
|
|
|
|
Ember.assert(
|
|
"The first argument of {{render}} must be quoted, e.g. {{render \"sidebar\"}}.",
|
|
typeof name === 'string'
|
|
);
|
|
|
|
Ember.assert(
|
|
"The second argument of {{render}} must be a path, e.g. {{render \"post\" post}}.",
|
|
params.length < 2 || isStream(params[1])
|
|
);
|
|
|
|
|
|
if (params.length === 1) {
|
|
// use the singleton controller
|
|
Ember.assert("You can only use the {{render}} helper once without a model object as its" +
|
|
" second argument, as in {{render \"post\" post}}.", !router || !router._lookupActiveView(name));
|
|
} else if (params.length === 2) {
|
|
// create a new controller
|
|
initialContext = context.value();
|
|
} else {
|
|
throw new EmberError("You must pass a templateName to render");
|
|
}
|
|
|
|
// # legacy namespace
|
|
name = name.replace(/\//g, '.');
|
|
// \ legacy slash as namespace support
|
|
|
|
|
|
view = container.lookup('view:' + name) || container.lookup('view:default');
|
|
|
|
// provide controller override
|
|
var controllerName = hash.controller || name;
|
|
var controllerFullName = 'controller:' + controllerName;
|
|
|
|
Ember.assert("The controller name you supplied '" + controllerName +
|
|
"' did not resolve to a controller.", !hash.controller || container.has(controllerFullName));
|
|
|
|
var parentController = this._keywords.controller.value();
|
|
|
|
// choose name
|
|
if (params.length > 1) {
|
|
var factory = container.lookupFactory(controllerFullName) ||
|
|
generateControllerFactory(container, controllerName, initialContext);
|
|
|
|
controller = factory.create({
|
|
modelBinding: context, // TODO: Use a StreamBinding
|
|
parentController: parentController,
|
|
target: parentController
|
|
});
|
|
|
|
view.one('willDestroyElement', function() {
|
|
controller.destroy();
|
|
});
|
|
} else {
|
|
controller = container.lookup(controllerFullName) ||
|
|
generateController(container, controllerName);
|
|
|
|
controller.setProperties({
|
|
target: parentController,
|
|
parentController: parentController
|
|
});
|
|
}
|
|
|
|
hash.viewName = camelize(name);
|
|
|
|
var templateName = 'template:' + name;
|
|
Ember.assert("You used `{{render '" + name + "'}}`, but '" + name + "' can not be found as either" +
|
|
" a template or a view.", container.has("view:" + name) || container.has(templateName) || !!options.template);
|
|
hash.template = container.lookup(templateName);
|
|
|
|
hash.controller = controller;
|
|
|
|
if (router && !initialContext) {
|
|
router._connectActiveView(name, view);
|
|
}
|
|
|
|
options.helperName = options.helperName || ('render "' + name + '"');
|
|
|
|
ViewHelper.instanceHelper(view, hash, options, env);
|
|
}
|
|
|
|
__exports__.renderHelper = renderHelper;
|
|
});
|
|
enifed("ember-routing-views",
|
|
["ember-metal/core","ember-routing-views/views/link","ember-routing-views/views/outlet","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
Ember Routing Views
|
|
|
|
@module ember
|
|
@submodule ember-routing-views
|
|
@requires ember-routing
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
|
|
var LinkView = __dependency2__.LinkView;
|
|
var OutletView = __dependency3__.OutletView;
|
|
|
|
Ember.LinkView = LinkView;
|
|
Ember.OutletView = OutletView;
|
|
|
|
__exports__["default"] = Ember;
|
|
});
|
|
enifed("ember-routing-views/views/link",
|
|
["ember-metal/core","ember-metal/property_get","ember-metal/merge","ember-metal/run_loop","ember-metal/computed","ember-runtime/system/string","ember-metal/keys","ember-views/system/utils","ember-views/views/component","ember-routing/utils","ember-metal/streams/utils","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-routing-views
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
// FEATURES, Logger, assert
|
|
|
|
var get = __dependency2__.get;
|
|
var merge = __dependency3__["default"];
|
|
var run = __dependency4__["default"];
|
|
var computed = __dependency5__.computed;
|
|
var fmt = __dependency6__.fmt;
|
|
var keys = __dependency7__["default"];
|
|
var isSimpleClick = __dependency8__.isSimpleClick;
|
|
var EmberComponent = __dependency9__["default"];
|
|
var routeArgs = __dependency10__.routeArgs;
|
|
var read = __dependency11__.read;
|
|
var subscribe = __dependency11__.subscribe;
|
|
|
|
var numberOfContextsAcceptedByHandler = function(handler, handlerInfos) {
|
|
var req = 0;
|
|
for (var i = 0, l = handlerInfos.length; i < l; i++) {
|
|
req = req + handlerInfos[i].names.length;
|
|
if (handlerInfos[i].handler === handler)
|
|
break;
|
|
}
|
|
|
|
return req;
|
|
};
|
|
|
|
/**
|
|
`Ember.LinkView` renders an element whose `click` event triggers a
|
|
transition of the application's instance of `Ember.Router` to
|
|
a supplied route by name.
|
|
|
|
Instances of `LinkView` will most likely be created through
|
|
the `link-to` Handlebars helper, but properties of this class
|
|
can be overridden to customize application-wide behavior.
|
|
|
|
@class LinkView
|
|
@namespace Ember
|
|
@extends Ember.View
|
|
@see {Handlebars.helpers.link-to}
|
|
**/
|
|
var LinkView = Ember.LinkView = EmberComponent.extend({
|
|
tagName: 'a',
|
|
|
|
/**
|
|
@deprecated Use current-when instead.
|
|
@property currentWhen
|
|
*/
|
|
currentWhen: null,
|
|
|
|
/**
|
|
Used to determine when this LinkView is active.
|
|
|
|
@property currentWhen
|
|
*/
|
|
'current-when': null,
|
|
|
|
/**
|
|
Sets the `title` attribute of the `LinkView`'s HTML element.
|
|
|
|
@property title
|
|
@default null
|
|
**/
|
|
title: null,
|
|
|
|
/**
|
|
Sets the `rel` attribute of the `LinkView`'s HTML element.
|
|
|
|
@property rel
|
|
@default null
|
|
**/
|
|
rel: null,
|
|
|
|
/**
|
|
Sets the `tabindex` attribute of the `LinkView`'s HTML element.
|
|
|
|
@property tabindex
|
|
@default null
|
|
**/
|
|
tabindex: null,
|
|
|
|
/**
|
|
Sets the `target` attribute of the `LinkView`'s HTML element.
|
|
|
|
@since 1.8.0
|
|
@property target
|
|
@default null
|
|
**/
|
|
target: null,
|
|
|
|
/**
|
|
The CSS class to apply to `LinkView`'s element when its `active`
|
|
property is `true`.
|
|
|
|
@property activeClass
|
|
@type String
|
|
@default active
|
|
**/
|
|
activeClass: 'active',
|
|
|
|
/**
|
|
The CSS class to apply to `LinkView`'s element when its `loading`
|
|
property is `true`.
|
|
|
|
@property loadingClass
|
|
@type String
|
|
@default loading
|
|
**/
|
|
loadingClass: 'loading',
|
|
|
|
/**
|
|
The CSS class to apply to a `LinkView`'s element when its `disabled`
|
|
property is `true`.
|
|
|
|
@property disabledClass
|
|
@type String
|
|
@default disabled
|
|
**/
|
|
disabledClass: 'disabled',
|
|
_isDisabled: false,
|
|
|
|
/**
|
|
Determines whether the `LinkView` will trigger routing via
|
|
the `replaceWith` routing strategy.
|
|
|
|
@property replace
|
|
@type Boolean
|
|
@default false
|
|
**/
|
|
replace: false,
|
|
|
|
/**
|
|
By default the `{{link-to}}` helper will bind to the `href` and
|
|
`title` attributes. It's discouraged that you override these defaults,
|
|
however you can push onto the array if needed.
|
|
|
|
@property attributeBindings
|
|
@type Array | String
|
|
@default ['href', 'title', 'rel', 'tabindex', 'target']
|
|
**/
|
|
attributeBindings: ['href', 'title', 'rel', 'tabindex'],
|
|
|
|
/**
|
|
By default the `{{link-to}}` helper will bind to the `active`, `loading`, and
|
|
`disabled` classes. It is discouraged to override these directly.
|
|
|
|
@property classNameBindings
|
|
@type Array
|
|
@default ['active', 'loading', 'disabled']
|
|
**/
|
|
classNameBindings: ['active', 'loading', 'disabled'],
|
|
|
|
/**
|
|
By default the `{{link-to}}` helper responds to the `click` event. You
|
|
can override this globally by setting this property to your custom
|
|
event name.
|
|
|
|
This is particularly useful on mobile when one wants to avoid the 300ms
|
|
click delay using some sort of custom `tap` event.
|
|
|
|
@property eventName
|
|
@type String
|
|
@default click
|
|
*/
|
|
eventName: 'click',
|
|
|
|
// this is doc'ed here so it shows up in the events
|
|
// section of the API documentation, which is where
|
|
// people will likely go looking for it.
|
|
/**
|
|
Triggers the `LinkView`'s routing behavior. If
|
|
`eventName` is changed to a value other than `click`
|
|
the routing behavior will trigger on that custom event
|
|
instead.
|
|
|
|
@event click
|
|
**/
|
|
|
|
/**
|
|
An overridable method called when LinkView objects are instantiated.
|
|
|
|
Example:
|
|
|
|
```javascript
|
|
App.MyLinkView = Ember.LinkView.extend({
|
|
init: function() {
|
|
this._super();
|
|
Ember.Logger.log('Event is ' + this.get('eventName'));
|
|
}
|
|
});
|
|
```
|
|
|
|
NOTE: If you do override `init` for a framework class like `Ember.View` or
|
|
`Ember.ArrayController`, be sure to call `this._super()` in your
|
|
`init` declaration! If you don't, Ember may not have an opportunity to
|
|
do important setup work, and you'll see strange behavior in your
|
|
application.
|
|
|
|
@method init
|
|
*/
|
|
init: function() {
|
|
this._super.apply(this, arguments);
|
|
|
|
Ember.deprecate('Using currentWhen with {{link-to}} is deprecated in favor of `current-when`.', !this.currentWhen);
|
|
|
|
// Map desired event name to invoke function
|
|
var eventName = get(this, 'eventName');
|
|
this.on(eventName, this, this._invoke);
|
|
},
|
|
|
|
/**
|
|
This method is invoked by observers installed during `init` that fire
|
|
whenever the params change
|
|
|
|
@private
|
|
@method _paramsChanged
|
|
@since 1.3.0
|
|
*/
|
|
_paramsChanged: function() {
|
|
this.notifyPropertyChange('resolvedParams');
|
|
},
|
|
|
|
/**
|
|
This is called to setup observers that will trigger a rerender.
|
|
|
|
@private
|
|
@method _setupPathObservers
|
|
@since 1.3.0
|
|
**/
|
|
_setupPathObservers: function(){
|
|
var params = this.params;
|
|
|
|
var scheduledRerender = this._wrapAsScheduled(this.rerender);
|
|
var scheduledParamsChanged = this._wrapAsScheduled(this._paramsChanged);
|
|
|
|
if (this.linkTitle) {
|
|
var linkTitle = this.linkTitle.stream || this.linkTitle;
|
|
subscribe(linkTitle, scheduledRerender, this);
|
|
}
|
|
|
|
for (var i = 0; i < params.length; i++) {
|
|
subscribe(params[i], scheduledParamsChanged, this);
|
|
}
|
|
|
|
var queryParamsObject = this.queryParamsObject;
|
|
if (queryParamsObject) {
|
|
var values = queryParamsObject.values;
|
|
for (var k in values) {
|
|
if (!values.hasOwnProperty(k)) {
|
|
continue;
|
|
}
|
|
|
|
subscribe(values[k], scheduledParamsChanged, this);
|
|
}
|
|
}
|
|
},
|
|
|
|
afterRender: function(){
|
|
this._super.apply(this, arguments);
|
|
this._setupPathObservers();
|
|
},
|
|
|
|
/**
|
|
|
|
Accessed as a classname binding to apply the `LinkView`'s `disabledClass`
|
|
CSS `class` to the element when the link is disabled.
|
|
|
|
When `true` interactions with the element will not trigger route changes.
|
|
@property disabled
|
|
*/
|
|
disabled: computed(function computeLinkViewDisabled(key, value) {
|
|
if (value !== undefined) { this.set('_isDisabled', value); }
|
|
|
|
return value ? get(this, 'disabledClass') : false;
|
|
}),
|
|
|
|
/**
|
|
Accessed as a classname binding to apply the `LinkView`'s `activeClass`
|
|
CSS `class` to the element when the link is active.
|
|
|
|
A `LinkView` is considered active when its `currentWhen` property is `true`
|
|
or the application's current route is the route the `LinkView` would trigger
|
|
transitions into.
|
|
|
|
The `currentWhen` property can match against multiple routes by separating
|
|
route names using the ` ` (space) character.
|
|
|
|
@property active
|
|
**/
|
|
active: computed('loadedParams', function computeLinkViewActive() {
|
|
if (get(this, 'loading')) { return false; }
|
|
|
|
var router = get(this, 'router');
|
|
var loadedParams = get(this, 'loadedParams');
|
|
var contexts = loadedParams.models;
|
|
var currentWhen = this['current-when'] || this.currentWhen;
|
|
var isCurrentWhenSpecified = Boolean(currentWhen);
|
|
currentWhen = currentWhen || loadedParams.targetRouteName;
|
|
|
|
function isActiveForRoute(routeName) {
|
|
var handlers = router.router.recognizer.handlersFor(routeName);
|
|
var leafName = handlers[handlers.length-1].handler;
|
|
var maximumContexts = numberOfContextsAcceptedByHandler(routeName, handlers);
|
|
|
|
// NOTE: any ugliness in the calculation of activeness is largely
|
|
// due to the fact that we support automatic normalizing of
|
|
// `resource` -> `resource.index`, even though there might be
|
|
// dynamic segments / query params defined on `resource.index`
|
|
// which complicates (and makes somewhat ambiguous) the calculation
|
|
// of activeness for links that link to `resource` instead of
|
|
// directly to `resource.index`.
|
|
|
|
// if we don't have enough contexts revert back to full route name
|
|
// this is because the leaf route will use one of the contexts
|
|
if (contexts.length > maximumContexts) {
|
|
routeName = leafName;
|
|
}
|
|
|
|
var args = routeArgs(routeName, contexts, null);
|
|
var isActive = router.isActive.apply(router, args);
|
|
if (!isActive) { return false; }
|
|
|
|
var emptyQueryParams = Ember.isEmpty(Ember.keys(loadedParams.queryParams));
|
|
|
|
if (!isCurrentWhenSpecified && !emptyQueryParams && isActive) {
|
|
var visibleQueryParams = {};
|
|
merge(visibleQueryParams, loadedParams.queryParams);
|
|
router._prepareQueryParams(loadedParams.targetRouteName, loadedParams.models, visibleQueryParams);
|
|
isActive = shallowEqual(visibleQueryParams, router.router.state.queryParams);
|
|
}
|
|
|
|
return isActive;
|
|
}
|
|
|
|
|
|
currentWhen = currentWhen.split(' ');
|
|
for (var i = 0, len = currentWhen.length; i < len; i++) {
|
|
if (isActiveForRoute(currentWhen[i])) {
|
|
return get(this, 'activeClass');
|
|
}
|
|
}
|
|
}),
|
|
|
|
/**
|
|
Accessed as a classname binding to apply the `LinkView`'s `loadingClass`
|
|
CSS `class` to the element when the link is loading.
|
|
|
|
A `LinkView` is considered loading when it has at least one
|
|
parameter whose value is currently null or undefined. During
|
|
this time, clicking the link will perform no transition and
|
|
emit a warning that the link is still in a loading state.
|
|
|
|
@property loading
|
|
**/
|
|
loading: computed('loadedParams', function computeLinkViewLoading() {
|
|
if (!get(this, 'loadedParams')) { return get(this, 'loadingClass'); }
|
|
}),
|
|
|
|
/**
|
|
Returns the application's main router from the container.
|
|
|
|
@private
|
|
@property router
|
|
**/
|
|
router: computed(function() {
|
|
var controller = get(this, 'controller');
|
|
if (controller && controller.container) {
|
|
return controller.container.lookup('router:main');
|
|
}
|
|
}),
|
|
|
|
/**
|
|
Event handler that invokes the link, activating the associated route.
|
|
|
|
@private
|
|
@method _invoke
|
|
@param {Event} event
|
|
*/
|
|
_invoke: function(event) {
|
|
if (!isSimpleClick(event)) { return true; }
|
|
|
|
if (this.preventDefault !== false) {
|
|
|
|
var targetAttribute = get(this, 'target');
|
|
if (!targetAttribute || targetAttribute === '_self') {
|
|
event.preventDefault();
|
|
}
|
|
}
|
|
|
|
if (this.bubbles === false) { event.stopPropagation(); }
|
|
|
|
if (get(this, '_isDisabled')) { return false; }
|
|
|
|
if (get(this, 'loading')) {
|
|
Ember.Logger.warn("This link-to is in an inactive loading state because at least one of its parameters presently has a null/undefined value, or the provided route name is invalid.");
|
|
return false;
|
|
}
|
|
|
|
|
|
var targetAttribute2 = get(this, 'target');
|
|
if (targetAttribute2 && targetAttribute2 !== '_self') {
|
|
return false;
|
|
}
|
|
|
|
|
|
var router = get(this, 'router');
|
|
var loadedParams = get(this, 'loadedParams');
|
|
|
|
var transition = router._doTransition(loadedParams.targetRouteName, loadedParams.models, loadedParams.queryParams);
|
|
if (get(this, 'replace')) {
|
|
transition.method('replace');
|
|
}
|
|
|
|
// Schedule eager URL update, but after we've given the transition
|
|
// a chance to synchronously redirect.
|
|
// We need to always generate the URL instead of using the href because
|
|
// the href will include any rootURL set, but the router expects a URL
|
|
// without it! Note that we don't use the first level router because it
|
|
// calls location.formatURL(), which also would add the rootURL!
|
|
var args = routeArgs(loadedParams.targetRouteName, loadedParams.models, transition.state.queryParams);
|
|
var url = router.router.generate.apply(router.router, args);
|
|
|
|
run.scheduleOnce('routerTransitions', this, this._eagerUpdateUrl, transition, url);
|
|
},
|
|
|
|
/**
|
|
@private
|
|
@method _eagerUpdateUrl
|
|
@param transition
|
|
@param href
|
|
*/
|
|
_eagerUpdateUrl: function(transition, href) {
|
|
if (!transition.isActive || !transition.urlMethod) {
|
|
// transition was aborted, already ran to completion,
|
|
// or it has a null url-updated method.
|
|
return;
|
|
}
|
|
|
|
if (href.indexOf('#') === 0) {
|
|
href = href.slice(1);
|
|
}
|
|
|
|
// Re-use the routerjs hooks set up by the Ember router.
|
|
var routerjs = get(this, 'router.router');
|
|
if (transition.urlMethod === 'update') {
|
|
routerjs.updateURL(href);
|
|
} else if (transition.urlMethod === 'replace') {
|
|
routerjs.replaceURL(href);
|
|
}
|
|
|
|
// Prevent later update url refire.
|
|
transition.method(null);
|
|
},
|
|
|
|
/**
|
|
Computed property that returns an array of the
|
|
resolved parameters passed to the `link-to` helper,
|
|
e.g.:
|
|
|
|
```hbs
|
|
{{link-to a b '123' c}}
|
|
```
|
|
|
|
will generate a `resolvedParams` of:
|
|
|
|
```js
|
|
[aObject, bObject, '123', cObject]
|
|
```
|
|
|
|
@private
|
|
@property
|
|
@return {Array}
|
|
*/
|
|
resolvedParams: computed('router.url', function() {
|
|
var params = this.params;
|
|
var targetRouteName;
|
|
var models = [];
|
|
var onlyQueryParamsSupplied = (params.length === 0);
|
|
|
|
if (onlyQueryParamsSupplied) {
|
|
var appController = this.container.lookup('controller:application');
|
|
targetRouteName = get(appController, 'currentRouteName');
|
|
} else {
|
|
targetRouteName = read(params[0]);
|
|
|
|
for (var i = 1; i < params.length; i++) {
|
|
models.push(read(params[i]));
|
|
}
|
|
}
|
|
|
|
var suppliedQueryParams = getResolvedQueryParams(this, targetRouteName);
|
|
|
|
return {
|
|
targetRouteName: targetRouteName,
|
|
models: models,
|
|
queryParams: suppliedQueryParams
|
|
};
|
|
}),
|
|
|
|
/**
|
|
Computed property that returns the current route name,
|
|
dynamic segments, and query params. Returns falsy if
|
|
for null/undefined params to indicate that the link view
|
|
is still in a loading state.
|
|
|
|
@private
|
|
@property
|
|
@return {Array} An array with the route name and any dynamic segments
|
|
**/
|
|
loadedParams: computed('resolvedParams', function computeLinkViewRouteArgs() {
|
|
var router = get(this, 'router');
|
|
if (!router) { return; }
|
|
|
|
var resolvedParams = get(this, 'resolvedParams');
|
|
var namedRoute = resolvedParams.targetRouteName;
|
|
|
|
if (!namedRoute) { return; }
|
|
|
|
Ember.assert(fmt("The attempt to link-to route '%@' failed. " +
|
|
"The router did not find '%@' in its possible routes: '%@'",
|
|
[namedRoute, namedRoute, keys(router.router.recognizer.names).join("', '")]),
|
|
router.hasRoute(namedRoute));
|
|
|
|
if (!paramsAreLoaded(resolvedParams.models)) { return; }
|
|
|
|
return resolvedParams;
|
|
}),
|
|
|
|
queryParamsObject: null,
|
|
|
|
/**
|
|
Sets the element's `href` attribute to the url for
|
|
the `LinkView`'s targeted route.
|
|
|
|
If the `LinkView`'s `tagName` is changed to a value other
|
|
than `a`, this property will be ignored.
|
|
|
|
@property href
|
|
**/
|
|
href: computed('loadedParams', function computeLinkViewHref() {
|
|
if (get(this, 'tagName') !== 'a') { return; }
|
|
|
|
var router = get(this, 'router');
|
|
var loadedParams = get(this, 'loadedParams');
|
|
|
|
if (!loadedParams) {
|
|
return get(this, 'loadingHref');
|
|
}
|
|
|
|
var visibleQueryParams = {};
|
|
merge(visibleQueryParams, loadedParams.queryParams);
|
|
router._prepareQueryParams(loadedParams.targetRouteName, loadedParams.models, visibleQueryParams);
|
|
|
|
var args = routeArgs(loadedParams.targetRouteName, loadedParams.models, visibleQueryParams);
|
|
var result = router.generate.apply(router, args);
|
|
return result;
|
|
}),
|
|
|
|
/**
|
|
The default href value to use while a link-to is loading.
|
|
Only applies when tagName is 'a'
|
|
|
|
@property loadingHref
|
|
@type String
|
|
@default #
|
|
*/
|
|
loadingHref: '#'
|
|
});
|
|
|
|
LinkView.toString = function() { return "LinkView"; };
|
|
|
|
|
|
LinkView.reopen({
|
|
attributeBindings: ['target'],
|
|
|
|
/**
|
|
Sets the `target` attribute of the `LinkView`'s anchor element.
|
|
|
|
@property target
|
|
@default null
|
|
**/
|
|
target: null
|
|
});
|
|
|
|
|
|
function getResolvedQueryParams(linkView, targetRouteName) {
|
|
var queryParamsObject = linkView.queryParamsObject;
|
|
var resolvedQueryParams = {};
|
|
|
|
if (!queryParamsObject) { return resolvedQueryParams; }
|
|
|
|
var values = queryParamsObject.values;
|
|
for (var key in values) {
|
|
if (!values.hasOwnProperty(key)) { continue; }
|
|
resolvedQueryParams[key] = read(values[key]);
|
|
}
|
|
|
|
return resolvedQueryParams;
|
|
}
|
|
|
|
function paramsAreLoaded(params) {
|
|
for (var i = 0, len = params.length; i < len; ++i) {
|
|
var param = params[i];
|
|
if (param === null || typeof param === 'undefined') {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function shallowEqual(a, b) {
|
|
var k;
|
|
for (k in a) {
|
|
if (a.hasOwnProperty(k) && a[k] !== b[k]) { return false; }
|
|
}
|
|
for (k in b) {
|
|
if (b.hasOwnProperty(k) && a[k] !== b[k]) { return false; }
|
|
}
|
|
return true;
|
|
}
|
|
|
|
__exports__.LinkView = LinkView;
|
|
});
|
|
enifed("ember-routing-views/views/outlet",
|
|
["ember-views/views/container_view","ember-views/views/metamorph_view","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-routing-views
|
|
*/
|
|
|
|
var ContainerView = __dependency1__["default"];
|
|
var _Metamorph = __dependency2__._Metamorph;
|
|
|
|
var OutletView = ContainerView.extend(_Metamorph);
|
|
__exports__.OutletView = OutletView;
|
|
});
|
|
enifed("ember-routing",
|
|
["ember-metal/core","ember-routing/ext/run_loop","ember-routing/ext/controller","ember-routing/ext/view","ember-routing/location/api","ember-routing/location/none_location","ember-routing/location/hash_location","ember-routing/location/history_location","ember-routing/location/auto_location","ember-routing/system/generate_controller","ember-routing/system/controller_for","ember-routing/system/dsl","ember-routing/system/router","ember-routing/system/route","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
Ember Routing
|
|
|
|
@module ember
|
|
@submodule ember-routing
|
|
@requires ember-views
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
|
|
// ES6TODO: Cleanup modules with side-effects below
|
|
|
|
var EmberLocation = __dependency5__["default"];
|
|
var NoneLocation = __dependency6__["default"];
|
|
var HashLocation = __dependency7__["default"];
|
|
var HistoryLocation = __dependency8__["default"];
|
|
var AutoLocation = __dependency9__["default"];
|
|
|
|
var generateControllerFactory = __dependency10__.generateControllerFactory;
|
|
var generateController = __dependency10__["default"];
|
|
var controllerFor = __dependency11__["default"];
|
|
var RouterDSL = __dependency12__["default"];
|
|
var Router = __dependency13__["default"];
|
|
var Route = __dependency14__["default"];
|
|
|
|
Ember.Location = EmberLocation;
|
|
Ember.AutoLocation = AutoLocation;
|
|
Ember.HashLocation = HashLocation;
|
|
Ember.HistoryLocation = HistoryLocation;
|
|
Ember.NoneLocation = NoneLocation;
|
|
|
|
Ember.controllerFor = controllerFor;
|
|
Ember.generateControllerFactory = generateControllerFactory;
|
|
Ember.generateController = generateController;
|
|
Ember.RouterDSL = RouterDSL;
|
|
Ember.Router = Router;
|
|
Ember.Route = Route;
|
|
|
|
__exports__["default"] = Ember;
|
|
});
|
|
enifed("ember-routing/ext/controller",
|
|
["ember-metal/core","ember-metal/property_get","ember-metal/property_set","ember-metal/computed","ember-metal/utils","ember-metal/merge","ember-runtime/mixins/controller","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
// FEATURES, deprecate
|
|
var get = __dependency2__.get;
|
|
var set = __dependency3__.set;
|
|
var computed = __dependency4__.computed;
|
|
var typeOf = __dependency5__.typeOf;
|
|
var meta = __dependency5__.meta;
|
|
var merge = __dependency6__["default"];
|
|
|
|
var ControllerMixin = __dependency7__["default"];
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-routing
|
|
*/
|
|
|
|
ControllerMixin.reopen({
|
|
concatenatedProperties: ['queryParams', '_pCacheMeta'],
|
|
|
|
init: function() {
|
|
this._super.apply(this, arguments);
|
|
listenForQueryParamChanges(this);
|
|
},
|
|
|
|
/**
|
|
Defines which query parameters the controller accepts.
|
|
If you give the names ['category','page'] it will bind
|
|
the values of these query parameters to the variables
|
|
`this.category` and `this.page`
|
|
|
|
@property queryParams
|
|
@public
|
|
*/
|
|
queryParams: null,
|
|
|
|
/**
|
|
@property _qpDelegate
|
|
@private
|
|
*/
|
|
_qpDelegate: null,
|
|
|
|
/**
|
|
@property _normalizedQueryParams
|
|
@private
|
|
*/
|
|
_normalizedQueryParams: computed(function() {
|
|
var m = meta(this);
|
|
if (m.proto !== this) {
|
|
return get(m.proto, '_normalizedQueryParams');
|
|
}
|
|
|
|
var queryParams = get(this, 'queryParams');
|
|
if (queryParams._qpMap) {
|
|
return queryParams._qpMap;
|
|
}
|
|
|
|
var qpMap = queryParams._qpMap = {};
|
|
|
|
for (var i = 0, len = queryParams.length; i < len; ++i) {
|
|
accumulateQueryParamDescriptors(queryParams[i], qpMap);
|
|
}
|
|
|
|
return qpMap;
|
|
}),
|
|
|
|
/**
|
|
@property _cacheMeta
|
|
@private
|
|
*/
|
|
_cacheMeta: computed(function() {
|
|
var m = meta(this);
|
|
if (m.proto !== this) {
|
|
return get(m.proto, '_cacheMeta');
|
|
}
|
|
|
|
var cacheMeta = {};
|
|
var qpMap = get(this, '_normalizedQueryParams');
|
|
for (var prop in qpMap) {
|
|
if (!qpMap.hasOwnProperty(prop)) { continue; }
|
|
|
|
var qp = qpMap[prop];
|
|
var scope = qp.scope;
|
|
var parts;
|
|
|
|
if (scope === 'controller') {
|
|
parts = [];
|
|
}
|
|
|
|
cacheMeta[prop] = {
|
|
parts: parts, // provided by route if 'model' scope
|
|
values: null, // provided by route
|
|
scope: scope,
|
|
prefix: "",
|
|
def: get(this, prop)
|
|
};
|
|
}
|
|
|
|
return cacheMeta;
|
|
}),
|
|
|
|
/**
|
|
@method _updateCacheParams
|
|
@private
|
|
*/
|
|
_updateCacheParams: function(params) {
|
|
var cacheMeta = get(this, '_cacheMeta');
|
|
for (var prop in cacheMeta) {
|
|
if (!cacheMeta.hasOwnProperty(prop)) { continue; }
|
|
var propMeta = cacheMeta[prop];
|
|
propMeta.values = params;
|
|
|
|
var cacheKey = this._calculateCacheKey(propMeta.prefix, propMeta.parts, propMeta.values);
|
|
var cache = this._bucketCache;
|
|
|
|
if (cache) {
|
|
var value = cache.lookup(cacheKey, prop, propMeta.def);
|
|
set(this, prop, value);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
@method _qpChanged
|
|
@private
|
|
*/
|
|
_qpChanged: function(controller, _prop) {
|
|
var prop = _prop.substr(0, _prop.length-3);
|
|
var cacheMeta = get(controller, '_cacheMeta');
|
|
var propCache = cacheMeta[prop];
|
|
var cacheKey = controller._calculateCacheKey(propCache.prefix || "", propCache.parts, propCache.values);
|
|
var value = get(controller, prop);
|
|
|
|
// 1. Update model-dep cache
|
|
var cache = this._bucketCache;
|
|
if (cache) {
|
|
controller._bucketCache.stash(cacheKey, prop, value);
|
|
}
|
|
|
|
// 2. Notify a delegate (e.g. to fire a qp transition)
|
|
var delegate = controller._qpDelegate;
|
|
if (delegate) {
|
|
delegate(controller, prop);
|
|
}
|
|
},
|
|
|
|
/**
|
|
@method _calculateCacheKey
|
|
@private
|
|
*/
|
|
_calculateCacheKey: function(prefix, _parts, values) {
|
|
var parts = _parts || [], suffixes = "";
|
|
for (var i = 0, len = parts.length; i < len; ++i) {
|
|
var part = parts[i];
|
|
var value = get(values, part);
|
|
suffixes += "::" + part + ":" + value;
|
|
}
|
|
return prefix + suffixes.replace(ALL_PERIODS_REGEX, '-');
|
|
},
|
|
|
|
/**
|
|
Transition the application into another route. The route may
|
|
be either a single route or route path:
|
|
|
|
```javascript
|
|
aController.transitionToRoute('blogPosts');
|
|
aController.transitionToRoute('blogPosts.recentEntries');
|
|
```
|
|
|
|
Optionally supply a model for the route in question. The model
|
|
will be serialized into the URL using the `serialize` hook of
|
|
the route:
|
|
|
|
```javascript
|
|
aController.transitionToRoute('blogPost', aPost);
|
|
```
|
|
|
|
If a literal is passed (such as a number or a string), it will
|
|
be treated as an identifier instead. In this case, the `model`
|
|
hook of the route will be triggered:
|
|
|
|
```javascript
|
|
aController.transitionToRoute('blogPost', 1);
|
|
```
|
|
|
|
Multiple models will be applied last to first recursively up the
|
|
resource tree.
|
|
|
|
```javascript
|
|
App.Router.map(function() {
|
|
this.resource('blogPost', {path:':blogPostId'}, function(){
|
|
this.resource('blogComment', {path: ':blogCommentId'});
|
|
});
|
|
});
|
|
|
|
aController.transitionToRoute('blogComment', aPost, aComment);
|
|
aController.transitionToRoute('blogComment', 1, 13);
|
|
```
|
|
|
|
It is also possible to pass a URL (a string that starts with a
|
|
`/`). This is intended for testing and debugging purposes and
|
|
should rarely be used in production code.
|
|
|
|
```javascript
|
|
aController.transitionToRoute('/');
|
|
aController.transitionToRoute('/blog/post/1/comment/13');
|
|
aController.transitionToRoute('/blog/posts?sort=title');
|
|
```
|
|
|
|
An options hash with a `queryParams` property may be provided as
|
|
the final argument to add query parameters to the destination URL.
|
|
|
|
```javascript
|
|
aController.transitionToRoute('blogPost', 1, {
|
|
queryParams: {showComments: 'true'}
|
|
});
|
|
|
|
// if you just want to transition the query parameters without changing the route
|
|
aController.transitionToRoute({queryParams: {sort: 'date'}});
|
|
```
|
|
|
|
See also [replaceRoute](/api/classes/Ember.ControllerMixin.html#method_replaceRoute).
|
|
|
|
@param {String} name the name of the route or a URL
|
|
@param {...Object} models the model(s) or identifier(s) to be used
|
|
while transitioning to the route.
|
|
@param {Object} [options] optional hash with a queryParams property
|
|
containing a mapping of query parameters
|
|
@for Ember.ControllerMixin
|
|
@method transitionToRoute
|
|
*/
|
|
transitionToRoute: function() {
|
|
// target may be either another controller or a router
|
|
var target = get(this, 'target');
|
|
var method = target.transitionToRoute || target.transitionTo;
|
|
return method.apply(target, arguments);
|
|
},
|
|
|
|
/**
|
|
@deprecated
|
|
@for Ember.ControllerMixin
|
|
@method transitionTo
|
|
*/
|
|
transitionTo: function() {
|
|
Ember.deprecate("transitionTo is deprecated. Please use transitionToRoute.");
|
|
return this.transitionToRoute.apply(this, arguments);
|
|
},
|
|
|
|
/**
|
|
Transition into another route while replacing the current URL, if possible.
|
|
This will replace the current history entry instead of adding a new one.
|
|
Beside that, it is identical to `transitionToRoute` in all other respects.
|
|
|
|
```javascript
|
|
aController.replaceRoute('blogPosts');
|
|
aController.replaceRoute('blogPosts.recentEntries');
|
|
```
|
|
|
|
Optionally supply a model for the route in question. The model
|
|
will be serialized into the URL using the `serialize` hook of
|
|
the route:
|
|
|
|
```javascript
|
|
aController.replaceRoute('blogPost', aPost);
|
|
```
|
|
|
|
If a literal is passed (such as a number or a string), it will
|
|
be treated as an identifier instead. In this case, the `model`
|
|
hook of the route will be triggered:
|
|
|
|
```javascript
|
|
aController.replaceRoute('blogPost', 1);
|
|
```
|
|
|
|
Multiple models will be applied last to first recursively up the
|
|
resource tree.
|
|
|
|
```javascript
|
|
App.Router.map(function() {
|
|
this.resource('blogPost', {path:':blogPostId'}, function(){
|
|
this.resource('blogComment', {path: ':blogCommentId'});
|
|
});
|
|
});
|
|
|
|
aController.replaceRoute('blogComment', aPost, aComment);
|
|
aController.replaceRoute('blogComment', 1, 13);
|
|
```
|
|
|
|
It is also possible to pass a URL (a string that starts with a
|
|
`/`). This is intended for testing and debugging purposes and
|
|
should rarely be used in production code.
|
|
|
|
```javascript
|
|
aController.replaceRoute('/');
|
|
aController.replaceRoute('/blog/post/1/comment/13');
|
|
```
|
|
|
|
@param {String} name the name of the route or a URL
|
|
@param {...Object} models the model(s) or identifier(s) to be used
|
|
while transitioning to the route.
|
|
@for Ember.ControllerMixin
|
|
@method replaceRoute
|
|
*/
|
|
replaceRoute: function() {
|
|
// target may be either another controller or a router
|
|
var target = get(this, 'target');
|
|
var method = target.replaceRoute || target.replaceWith;
|
|
return method.apply(target, arguments);
|
|
},
|
|
|
|
/**
|
|
@deprecated
|
|
@for Ember.ControllerMixin
|
|
@method replaceWith
|
|
*/
|
|
replaceWith: function() {
|
|
Ember.deprecate("replaceWith is deprecated. Please use replaceRoute.");
|
|
return this.replaceRoute.apply(this, arguments);
|
|
}
|
|
});
|
|
|
|
var ALL_PERIODS_REGEX = /\./g;
|
|
|
|
function accumulateQueryParamDescriptors(_desc, accum) {
|
|
var desc = _desc;
|
|
var tmp;
|
|
if (typeOf(desc) === 'string') {
|
|
tmp = {};
|
|
tmp[desc] = { as: null };
|
|
desc = tmp;
|
|
}
|
|
|
|
for (var key in desc) {
|
|
if (!desc.hasOwnProperty(key)) { return; }
|
|
|
|
var singleDesc = desc[key];
|
|
if (typeOf(singleDesc) === 'string') {
|
|
singleDesc = { as: singleDesc };
|
|
}
|
|
|
|
tmp = accum[key] || { as: null, scope: 'model' };
|
|
merge(tmp, singleDesc);
|
|
|
|
accum[key] = tmp;
|
|
}
|
|
}
|
|
|
|
function listenForQueryParamChanges(controller) {
|
|
var qpMap = get(controller, '_normalizedQueryParams');
|
|
for (var prop in qpMap) {
|
|
if (!qpMap.hasOwnProperty(prop)) { continue; }
|
|
controller.addObserver(prop + '.[]', controller, controller._qpChanged);
|
|
}
|
|
}
|
|
|
|
|
|
__exports__["default"] = ControllerMixin;
|
|
});
|
|
enifed("ember-routing/ext/run_loop",
|
|
["ember-metal/run_loop"],
|
|
function(__dependency1__) {
|
|
"use strict";
|
|
var run = __dependency1__["default"];
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-views
|
|
*/
|
|
|
|
// Add a new named queue after the 'actions' queue (where RSVP promises
|
|
// resolve), which is used in router transitions to prevent unnecessary
|
|
// loading state entry if all context promises resolve on the
|
|
// 'actions' queue first.
|
|
run._addQueue('routerTransitions', 'actions');
|
|
});
|
|
enifed("ember-routing/ext/view",
|
|
["ember-metal/property_get","ember-metal/property_set","ember-metal/run_loop","ember-views/views/view","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
|
|
"use strict";
|
|
var get = __dependency1__.get;
|
|
var set = __dependency2__.set;
|
|
var run = __dependency3__["default"];
|
|
var EmberView = __dependency4__["default"];
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-routing
|
|
*/
|
|
|
|
EmberView.reopen({
|
|
|
|
/**
|
|
Sets the private `_outlets` object on the view.
|
|
|
|
@method init
|
|
*/
|
|
init: function() {
|
|
this._outlets = {};
|
|
this._super();
|
|
},
|
|
|
|
/**
|
|
Manually fill any of a view's `{{outlet}}` areas with the
|
|
supplied view.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
var MyView = Ember.View.extend({
|
|
template: Ember.Handlebars.compile('Child view: {{outlet "main"}} ')
|
|
});
|
|
var myView = MyView.create();
|
|
myView.appendTo('body');
|
|
// The html for myView now looks like:
|
|
// <div id="ember228" class="ember-view">Child view: </div>
|
|
|
|
var FooView = Ember.View.extend({
|
|
template: Ember.Handlebars.compile('<h1>Foo</h1> ')
|
|
});
|
|
var fooView = FooView.create();
|
|
myView.connectOutlet('main', fooView);
|
|
// The html for myView now looks like:
|
|
// <div id="ember228" class="ember-view">Child view:
|
|
// <div id="ember234" class="ember-view"><h1>Foo</h1> </div>
|
|
// </div>
|
|
```
|
|
@method connectOutlet
|
|
@param {String} outletName A unique name for the outlet
|
|
@param {Object} view An Ember.View
|
|
*/
|
|
connectOutlet: function(outletName, view) {
|
|
if (this._pendingDisconnections) {
|
|
delete this._pendingDisconnections[outletName];
|
|
}
|
|
|
|
if (this._hasEquivalentView(outletName, view)) {
|
|
view.destroy();
|
|
return;
|
|
}
|
|
|
|
var outlets = get(this, '_outlets');
|
|
var container = get(this, 'container');
|
|
var router = container && container.lookup('router:main');
|
|
var renderedName = get(view, 'renderedName');
|
|
|
|
set(outlets, outletName, view);
|
|
|
|
if (router && renderedName) {
|
|
router._connectActiveView(renderedName, view);
|
|
}
|
|
},
|
|
|
|
/**
|
|
Determines if the view has already been created by checking if
|
|
the view has the same constructor, template, and context as the
|
|
view in the `_outlets` object.
|
|
|
|
@private
|
|
@method _hasEquivalentView
|
|
@param {String} outletName The name of the outlet we are checking
|
|
@param {Object} view An Ember.View
|
|
@return {Boolean}
|
|
*/
|
|
_hasEquivalentView: function(outletName, view) {
|
|
var existingView = get(this, '_outlets.'+outletName);
|
|
return existingView &&
|
|
existingView.constructor === view.constructor &&
|
|
existingView.get('template') === view.get('template') &&
|
|
existingView.get('context') === view.get('context');
|
|
},
|
|
|
|
/**
|
|
Removes an outlet from the view.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
var MyView = Ember.View.extend({
|
|
template: Ember.Handlebars.compile('Child view: {{outlet "main"}} ')
|
|
});
|
|
var myView = MyView.create();
|
|
myView.appendTo('body');
|
|
// myView's html:
|
|
// <div id="ember228" class="ember-view">Child view: </div>
|
|
|
|
var FooView = Ember.View.extend({
|
|
template: Ember.Handlebars.compile('<h1>Foo</h1> ')
|
|
});
|
|
var fooView = FooView.create();
|
|
myView.connectOutlet('main', fooView);
|
|
// myView's html:
|
|
// <div id="ember228" class="ember-view">Child view:
|
|
// <div id="ember234" class="ember-view"><h1>Foo</h1> </div>
|
|
// </div>
|
|
|
|
myView.disconnectOutlet('main');
|
|
// myView's html:
|
|
// <div id="ember228" class="ember-view">Child view: </div>
|
|
```
|
|
|
|
@method disconnectOutlet
|
|
@param {String} outletName The name of the outlet to be removed
|
|
*/
|
|
disconnectOutlet: function(outletName) {
|
|
if (!this._pendingDisconnections) {
|
|
this._pendingDisconnections = {};
|
|
}
|
|
this._pendingDisconnections[outletName] = true;
|
|
run.once(this, '_finishDisconnections');
|
|
},
|
|
|
|
/**
|
|
Gets an outlet that is pending disconnection and then
|
|
nullifys the object on the `_outlet` object.
|
|
|
|
@private
|
|
@method _finishDisconnections
|
|
*/
|
|
_finishDisconnections: function() {
|
|
if (this.isDestroyed) return; // _outlets will be gone anyway
|
|
var outlets = get(this, '_outlets');
|
|
var pendingDisconnections = this._pendingDisconnections;
|
|
this._pendingDisconnections = null;
|
|
|
|
for (var outletName in pendingDisconnections) {
|
|
set(outlets, outletName, null);
|
|
}
|
|
}
|
|
});
|
|
|
|
__exports__["default"] = EmberView;
|
|
});
|
|
enifed("ember-routing/location/api",
|
|
["ember-metal/core","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
// deprecate, assert
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-routing
|
|
*/
|
|
|
|
/**
|
|
Ember.Location returns an instance of the correct implementation of
|
|
the `location` API.
|
|
|
|
## Implementations
|
|
|
|
You can pass an implementation name (`hash`, `history`, `none`) to force a
|
|
particular implementation to be used in your application.
|
|
|
|
### HashLocation
|
|
|
|
Using `HashLocation` results in URLs with a `#` (hash sign) separating the
|
|
server side URL portion of the URL from the portion that is used by Ember.
|
|
This relies upon the `hashchange` event existing in the browser.
|
|
|
|
Example:
|
|
|
|
```javascript
|
|
App.Router.map(function() {
|
|
this.resource('posts', function() {
|
|
this.route('new');
|
|
});
|
|
});
|
|
|
|
App.Router.reopen({
|
|
location: 'hash'
|
|
});
|
|
```
|
|
|
|
This will result in a posts.new url of `/#/posts/new`.
|
|
|
|
### HistoryLocation
|
|
|
|
Using `HistoryLocation` results in URLs that are indistinguishable from a
|
|
standard URL. This relies upon the browser's `history` API.
|
|
|
|
Example:
|
|
|
|
```javascript
|
|
App.Router.map(function() {
|
|
this.resource('posts', function() {
|
|
this.route('new');
|
|
});
|
|
});
|
|
|
|
App.Router.reopen({
|
|
location: 'history'
|
|
});
|
|
```
|
|
|
|
This will result in a posts.new url of `/posts/new`.
|
|
|
|
Keep in mind that your server must serve the Ember app at all the routes you
|
|
define.
|
|
|
|
### AutoLocation
|
|
|
|
Using `AutoLocation`, the router will use the best Location class supported by
|
|
the browser it is running in.
|
|
|
|
Browsers that support the `history` API will use `HistoryLocation`, those that
|
|
do not, but still support the `hashchange` event will use `HashLocation`, and
|
|
in the rare case neither is supported will use `NoneLocation`.
|
|
|
|
Example:
|
|
|
|
```javascript
|
|
App.Router.map(function() {
|
|
this.resource('posts', function() {
|
|
this.route('new');
|
|
});
|
|
});
|
|
|
|
App.Router.reopen({
|
|
location: 'auto'
|
|
});
|
|
```
|
|
|
|
This will result in a posts.new url of `/posts/new` for modern browsers that
|
|
support the `history` api or `/#/posts/new` for older ones, like Internet
|
|
Explorer 9 and below.
|
|
|
|
When a user visits a link to your application, they will be automatically
|
|
upgraded or downgraded to the appropriate `Location` class, with the URL
|
|
transformed accordingly, if needed.
|
|
|
|
Keep in mind that since some of your users will use `HistoryLocation`, your
|
|
server must serve the Ember app at all the routes you define.
|
|
|
|
### NoneLocation
|
|
|
|
Using `NoneLocation` causes Ember to not store the applications URL state
|
|
in the actual URL. This is generally used for testing purposes, and is one
|
|
of the changes made when calling `App.setupForTesting()`.
|
|
|
|
## Location API
|
|
|
|
Each location implementation must provide the following methods:
|
|
|
|
* implementation: returns the string name used to reference the implementation.
|
|
* getURL: returns the current URL.
|
|
* setURL(path): sets the current URL.
|
|
* replaceURL(path): replace the current URL (optional).
|
|
* onUpdateURL(callback): triggers the callback when the URL changes.
|
|
* formatURL(url): formats `url` to be placed into `href` attribute.
|
|
|
|
Calling setURL or replaceURL will not trigger onUpdateURL callbacks.
|
|
|
|
@class Location
|
|
@namespace Ember
|
|
@static
|
|
*/
|
|
__exports__["default"] = {
|
|
/**
|
|
This is deprecated in favor of using the container to lookup the location
|
|
implementation as desired.
|
|
|
|
For example:
|
|
|
|
```javascript
|
|
// Given a location registered as follows:
|
|
container.register('location:history-test', HistoryTestLocation);
|
|
|
|
// You could create a new instance via:
|
|
container.lookup('location:history-test');
|
|
```
|
|
|
|
@method create
|
|
@param {Object} options
|
|
@return {Object} an instance of an implementation of the `location` API
|
|
@deprecated Use the container to lookup the location implementation that you
|
|
need.
|
|
*/
|
|
create: function(options) {
|
|
var implementation = options && options.implementation;
|
|
Ember.assert("Ember.Location.create: you must specify a 'implementation' option", !!implementation);
|
|
|
|
var implementationClass = this.implementations[implementation];
|
|
Ember.assert("Ember.Location.create: " + implementation + " is not a valid implementation", !!implementationClass);
|
|
|
|
return implementationClass.create.apply(implementationClass, arguments);
|
|
},
|
|
|
|
/**
|
|
This is deprecated in favor of using the container to register the
|
|
location implementation as desired.
|
|
|
|
Example:
|
|
|
|
```javascript
|
|
Application.initializer({
|
|
name: "history-test-location",
|
|
|
|
initialize: function(container, application) {
|
|
application.register('location:history-test', HistoryTestLocation);
|
|
}
|
|
});
|
|
```
|
|
|
|
@method registerImplementation
|
|
@param {String} name
|
|
@param {Object} implementation of the `location` API
|
|
@deprecated Register your custom location implementation with the
|
|
container directly.
|
|
*/
|
|
registerImplementation: function(name, implementation) {
|
|
Ember.deprecate('Using the Ember.Location.registerImplementation is no longer supported.' +
|
|
' Register your custom location implementation with the container instead.', false);
|
|
|
|
this.implementations[name] = implementation;
|
|
},
|
|
|
|
implementations: {},
|
|
_location: window.location,
|
|
|
|
/**
|
|
Returns the current `location.hash` by parsing location.href since browsers
|
|
inconsistently URL-decode `location.hash`.
|
|
|
|
https://bugzilla.mozilla.org/show_bug.cgi?id=483304
|
|
|
|
@private
|
|
@method getHash
|
|
@since 1.4.0
|
|
*/
|
|
_getHash: function () {
|
|
// AutoLocation has it at _location, HashLocation at .location.
|
|
// Being nice and not changing
|
|
var href = (this._location || this.location).href;
|
|
var hashIndex = href.indexOf('#');
|
|
|
|
if (hashIndex === -1) {
|
|
return '';
|
|
} else {
|
|
return href.substr(hashIndex);
|
|
}
|
|
}
|
|
};
|
|
});
|
|
enifed("ember-routing/location/auto_location",
|
|
["ember-metal/core","ember-metal/property_set","ember-routing/location/api","ember-routing/location/history_location","ember-routing/location/hash_location","ember-routing/location/none_location","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
// FEATURES
|
|
var set = __dependency2__.set;
|
|
|
|
var EmberLocation = __dependency3__["default"];
|
|
var HistoryLocation = __dependency4__["default"];
|
|
var HashLocation = __dependency5__["default"];
|
|
var NoneLocation = __dependency6__["default"];
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-routing
|
|
*/
|
|
|
|
/**
|
|
Ember.AutoLocation will select the best location option based off browser
|
|
support with the priority order: history, hash, none.
|
|
|
|
Clean pushState paths accessed by hashchange-only browsers will be redirected
|
|
to the hash-equivalent and vice versa so future transitions are consistent.
|
|
|
|
Keep in mind that since some of your users will use `HistoryLocation`, your
|
|
server must serve the Ember app at all the routes you define.
|
|
|
|
@class AutoLocation
|
|
@namespace Ember
|
|
@static
|
|
*/
|
|
__exports__["default"] = {
|
|
|
|
/**
|
|
@private
|
|
|
|
This property is used by router:main to know whether to cancel the routing
|
|
setup process, which is needed while we redirect the browser.
|
|
|
|
@since 1.5.1
|
|
@property cancelRouterSetup
|
|
@default false
|
|
*/
|
|
cancelRouterSetup: false,
|
|
|
|
/**
|
|
@private
|
|
|
|
Will be pre-pended to path upon state change.
|
|
|
|
@since 1.5.1
|
|
@property rootURL
|
|
@default '/'
|
|
*/
|
|
rootURL: '/',
|
|
|
|
/**
|
|
@private
|
|
|
|
Attached for mocking in tests
|
|
|
|
@since 1.5.1
|
|
@property _window
|
|
@default window
|
|
*/
|
|
_window: window,
|
|
|
|
/**
|
|
@private
|
|
|
|
Attached for mocking in tests
|
|
|
|
@property location
|
|
@default window.location
|
|
*/
|
|
_location: window.location,
|
|
|
|
/**
|
|
@private
|
|
|
|
Attached for mocking in tests
|
|
|
|
@since 1.5.1
|
|
@property _history
|
|
@default window.history
|
|
*/
|
|
_history: window.history,
|
|
|
|
/**
|
|
@private
|
|
|
|
Attached for mocking in tests
|
|
|
|
@since 1.5.1
|
|
@property _HistoryLocation
|
|
@default Ember.HistoryLocation
|
|
*/
|
|
_HistoryLocation: HistoryLocation,
|
|
|
|
/**
|
|
@private
|
|
|
|
Attached for mocking in tests
|
|
|
|
@since 1.5.1
|
|
@property _HashLocation
|
|
@default Ember.HashLocation
|
|
*/
|
|
_HashLocation: HashLocation,
|
|
|
|
/**
|
|
@private
|
|
|
|
Attached for mocking in tests
|
|
|
|
@since 1.5.1
|
|
@property _NoneLocation
|
|
@default Ember.NoneLocation
|
|
*/
|
|
_NoneLocation: NoneLocation,
|
|
|
|
/**
|
|
@private
|
|
|
|
Returns location.origin or builds it if device doesn't support it.
|
|
|
|
@method _getOrigin
|
|
*/
|
|
_getOrigin: function () {
|
|
var location = this._location;
|
|
var origin = location.origin;
|
|
|
|
// Older browsers, especially IE, don't have origin
|
|
if (!origin) {
|
|
origin = location.protocol + '//' + location.hostname;
|
|
|
|
if (location.port) {
|
|
origin += ':' + location.port;
|
|
}
|
|
}
|
|
|
|
return origin;
|
|
},
|
|
|
|
/**
|
|
@private
|
|
|
|
We assume that if the history object has a pushState method, the host should
|
|
support HistoryLocation.
|
|
|
|
@method _getSupportsHistory
|
|
*/
|
|
_getSupportsHistory: function () {
|
|
// Boosted from Modernizr: https://github.com/Modernizr/Modernizr/blob/master/feature-detects/history.js
|
|
// The stock browser on Android 2.2 & 2.3 returns positive on history support
|
|
// Unfortunately support is really buggy and there is no clean way to detect
|
|
// these bugs, so we fall back to a user agent sniff :(
|
|
var userAgent = this._window.navigator.userAgent;
|
|
|
|
// We only want Android 2, stock browser, and not Chrome which identifies
|
|
// itself as 'Mobile Safari' as well
|
|
if (userAgent.indexOf('Android 2') !== -1 &&
|
|
userAgent.indexOf('Mobile Safari') !== -1 &&
|
|
userAgent.indexOf('Chrome') === -1) {
|
|
return false;
|
|
}
|
|
|
|
return !!(this._history && 'pushState' in this._history);
|
|
},
|
|
|
|
/**
|
|
@private
|
|
|
|
IE8 running in IE7 compatibility mode gives false positive, so we must also
|
|
check documentMode.
|
|
|
|
@method _getSupportsHashChange
|
|
*/
|
|
_getSupportsHashChange: function () {
|
|
var _window = this._window;
|
|
var documentMode = _window.document.documentMode;
|
|
|
|
return ('onhashchange' in _window && (documentMode === undefined || documentMode > 7 ));
|
|
},
|
|
|
|
/**
|
|
@private
|
|
|
|
Redirects the browser using location.replace, prepending the locatin.origin
|
|
to prevent phishing attempts
|
|
|
|
@method _replacePath
|
|
*/
|
|
_replacePath: function (path) {
|
|
this._location.replace(this._getOrigin() + path);
|
|
},
|
|
|
|
/**
|
|
@since 1.5.1
|
|
@private
|
|
@method _getRootURL
|
|
*/
|
|
_getRootURL: function () {
|
|
return this.rootURL;
|
|
},
|
|
|
|
/**
|
|
@private
|
|
|
|
Returns the current `location.pathname`, normalized for IE inconsistencies.
|
|
|
|
@method _getPath
|
|
*/
|
|
_getPath: function () {
|
|
var pathname = this._location.pathname;
|
|
// Various versions of IE/Opera don't always return a leading slash
|
|
if (pathname.charAt(0) !== '/') {
|
|
pathname = '/' + pathname;
|
|
}
|
|
|
|
return pathname;
|
|
},
|
|
|
|
/**
|
|
@private
|
|
|
|
Returns normalized location.hash as an alias to Ember.Location._getHash
|
|
|
|
@since 1.5.1
|
|
@method _getHash
|
|
*/
|
|
_getHash: EmberLocation._getHash,
|
|
|
|
/**
|
|
@private
|
|
|
|
Returns location.search
|
|
|
|
@since 1.5.1
|
|
@method _getQuery
|
|
*/
|
|
_getQuery: function () {
|
|
return this._location.search;
|
|
},
|
|
|
|
/**
|
|
@private
|
|
|
|
Returns the full pathname including query and hash
|
|
|
|
@method _getFullPath
|
|
*/
|
|
_getFullPath: function () {
|
|
return this._getPath() + this._getQuery() + this._getHash();
|
|
},
|
|
|
|
/**
|
|
@private
|
|
|
|
Returns the current path as it should appear for HistoryLocation supported
|
|
browsers. This may very well differ from the real current path (e.g. if it
|
|
starts off as a hashed URL)
|
|
|
|
@method _getHistoryPath
|
|
*/
|
|
_getHistoryPath: function () {
|
|
var rootURL = this._getRootURL();
|
|
var path = this._getPath();
|
|
var hash = this._getHash();
|
|
var query = this._getQuery();
|
|
var rootURLIndex = path.indexOf(rootURL);
|
|
var routeHash, hashParts;
|
|
|
|
Ember.assert('Path ' + path + ' does not start with the provided rootURL ' + rootURL, rootURLIndex === 0);
|
|
|
|
// By convention, Ember.js routes using HashLocation are required to start
|
|
// with `#/`. Anything else should NOT be considered a route and should
|
|
// be passed straight through, without transformation.
|
|
if (hash.substr(0, 2) === '#/') {
|
|
// There could be extra hash segments after the route
|
|
hashParts = hash.substr(1).split('#');
|
|
// The first one is always the route url
|
|
routeHash = hashParts.shift();
|
|
|
|
// If the path already has a trailing slash, remove the one
|
|
// from the hashed route so we don't double up.
|
|
if (path.slice(-1) === '/') {
|
|
routeHash = routeHash.substr(1);
|
|
}
|
|
|
|
// This is the "expected" final order
|
|
path += routeHash;
|
|
path += query;
|
|
|
|
if (hashParts.length) {
|
|
path += '#' + hashParts.join('#');
|
|
}
|
|
} else {
|
|
path += query;
|
|
path += hash;
|
|
}
|
|
|
|
return path;
|
|
},
|
|
|
|
/**
|
|
@private
|
|
|
|
Returns the current path as it should appear for HashLocation supported
|
|
browsers. This may very well differ from the real current path.
|
|
|
|
@method _getHashPath
|
|
*/
|
|
_getHashPath: function () {
|
|
var rootURL = this._getRootURL();
|
|
var path = rootURL;
|
|
var historyPath = this._getHistoryPath();
|
|
var routePath = historyPath.substr(rootURL.length);
|
|
|
|
if (routePath !== '') {
|
|
if (routePath.charAt(0) !== '/') {
|
|
routePath = '/' + routePath;
|
|
}
|
|
|
|
path += '#' + routePath;
|
|
}
|
|
|
|
return path;
|
|
},
|
|
|
|
/**
|
|
Selects the best location option based off browser support and returns an
|
|
instance of that Location class.
|
|
|
|
@see Ember.AutoLocation
|
|
@method create
|
|
*/
|
|
create: function (options) {
|
|
if (options && options.rootURL) {
|
|
Ember.assert('rootURL must end with a trailing forward slash e.g. "/app/"',
|
|
options.rootURL.charAt(options.rootURL.length-1) === '/');
|
|
this.rootURL = options.rootURL;
|
|
}
|
|
|
|
var historyPath, hashPath;
|
|
var cancelRouterSetup = false;
|
|
var implementationClass = this._NoneLocation;
|
|
var currentPath = this._getFullPath();
|
|
|
|
if (this._getSupportsHistory()) {
|
|
historyPath = this._getHistoryPath();
|
|
|
|
// Since we support history paths, let's be sure we're using them else
|
|
// switch the location over to it.
|
|
if (currentPath === historyPath) {
|
|
implementationClass = this._HistoryLocation;
|
|
} else {
|
|
|
|
if (currentPath.substr(0, 2) === '/#') {
|
|
this._history.replaceState({ path: historyPath }, null, historyPath);
|
|
implementationClass = this._HistoryLocation;
|
|
} else {
|
|
cancelRouterSetup = true;
|
|
this._replacePath(historyPath);
|
|
}
|
|
}
|
|
|
|
} else if (this._getSupportsHashChange()) {
|
|
hashPath = this._getHashPath();
|
|
|
|
// Be sure we're using a hashed path, otherwise let's switch over it to so
|
|
// we start off clean and consistent. We'll count an index path with no
|
|
// hash as "good enough" as well.
|
|
if (currentPath === hashPath || (currentPath === '/' && hashPath === '/#/')) {
|
|
implementationClass = this._HashLocation;
|
|
} else {
|
|
// Our URL isn't in the expected hash-supported format, so we want to
|
|
// cancel the router setup and replace the URL to start off clean
|
|
cancelRouterSetup = true;
|
|
this._replacePath(hashPath);
|
|
}
|
|
}
|
|
|
|
var implementation = implementationClass.create.apply(implementationClass, arguments);
|
|
|
|
if (cancelRouterSetup) {
|
|
set(implementation, 'cancelRouterSetup', true);
|
|
}
|
|
|
|
return implementation;
|
|
}
|
|
};
|
|
});
|
|
enifed("ember-routing/location/hash_location",
|
|
["ember-metal/core","ember-metal/property_get","ember-metal/property_set","ember-metal/run_loop","ember-metal/utils","ember-runtime/system/object","ember-routing/location/api","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
var get = __dependency2__.get;
|
|
var set = __dependency3__.set;
|
|
var run = __dependency4__["default"];
|
|
var guidFor = __dependency5__.guidFor;
|
|
|
|
var EmberObject = __dependency6__["default"];
|
|
var EmberLocation = __dependency7__["default"];
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-routing
|
|
*/
|
|
|
|
/**
|
|
`Ember.HashLocation` implements the location API using the browser's
|
|
hash. At present, it relies on a `hashchange` event existing in the
|
|
browser.
|
|
|
|
@class HashLocation
|
|
@namespace Ember
|
|
@extends Ember.Object
|
|
*/
|
|
__exports__["default"] = EmberObject.extend({
|
|
implementation: 'hash',
|
|
|
|
init: function() {
|
|
set(this, 'location', get(this, '_location') || window.location);
|
|
},
|
|
|
|
/**
|
|
@private
|
|
|
|
Returns normalized location.hash
|
|
|
|
@since 1.5.1
|
|
@method getHash
|
|
*/
|
|
getHash: EmberLocation._getHash,
|
|
|
|
/**
|
|
Returns the normalized URL, constructed from `location.hash`.
|
|
|
|
e.g. `#/foo` => `/foo` as well as `#/foo#bar` => `/foo#bar`.
|
|
|
|
By convention, hashed paths must begin with a forward slash, otherwise they
|
|
are not treated as a path so we can distinguish intent.
|
|
|
|
@private
|
|
@method getURL
|
|
*/
|
|
getURL: function() {
|
|
var originalPath = this.getHash().substr(1);
|
|
var outPath = originalPath;
|
|
|
|
if (outPath.charAt(0) !== '/') {
|
|
outPath = '/';
|
|
|
|
// Only add the # if the path isn't empty.
|
|
// We do NOT want `/#` since the ampersand
|
|
// is only included (conventionally) when
|
|
// the location.hash has a value
|
|
if (originalPath) {
|
|
outPath += '#' + originalPath;
|
|
}
|
|
}
|
|
|
|
return outPath;
|
|
},
|
|
|
|
/**
|
|
Set the `location.hash` and remembers what was set. This prevents
|
|
`onUpdateURL` callbacks from triggering when the hash was set by
|
|
`HashLocation`.
|
|
|
|
@private
|
|
@method setURL
|
|
@param path {String}
|
|
*/
|
|
setURL: function(path) {
|
|
get(this, 'location').hash = path;
|
|
set(this, 'lastSetURL', path);
|
|
},
|
|
|
|
/**
|
|
Uses location.replace to update the url without a page reload
|
|
or history modification.
|
|
|
|
@private
|
|
@method replaceURL
|
|
@param path {String}
|
|
*/
|
|
replaceURL: function(path) {
|
|
get(this, 'location').replace('#' + path);
|
|
set(this, 'lastSetURL', path);
|
|
},
|
|
|
|
/**
|
|
Register a callback to be invoked when the hash changes. These
|
|
callbacks will execute when the user presses the back or forward
|
|
button, but not after `setURL` is invoked.
|
|
|
|
@private
|
|
@method onUpdateURL
|
|
@param callback {Function}
|
|
*/
|
|
onUpdateURL: function(callback) {
|
|
var self = this;
|
|
var guid = guidFor(this);
|
|
|
|
Ember.$(window).on('hashchange.ember-location-'+guid, function() {
|
|
run(function() {
|
|
var path = self.getURL();
|
|
if (get(self, 'lastSetURL') === path) { return; }
|
|
|
|
set(self, 'lastSetURL', null);
|
|
|
|
callback(path);
|
|
});
|
|
});
|
|
},
|
|
|
|
/**
|
|
Given a URL, formats it to be placed into the page as part
|
|
of an element's `href` attribute.
|
|
|
|
This is used, for example, when using the {{action}} helper
|
|
to generate a URL based on an event.
|
|
|
|
@private
|
|
@method formatURL
|
|
@param url {String}
|
|
*/
|
|
formatURL: function(url) {
|
|
return '#' + url;
|
|
},
|
|
|
|
/**
|
|
Cleans up the HashLocation event listener.
|
|
|
|
@private
|
|
@method willDestroy
|
|
*/
|
|
willDestroy: function() {
|
|
var guid = guidFor(this);
|
|
|
|
Ember.$(window).off('hashchange.ember-location-'+guid);
|
|
}
|
|
});
|
|
});
|
|
enifed("ember-routing/location/history_location",
|
|
["ember-metal/property_get","ember-metal/property_set","ember-metal/utils","ember-runtime/system/object","ember-routing/location/api","ember-views/system/jquery","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) {
|
|
"use strict";
|
|
var get = __dependency1__.get;
|
|
var set = __dependency2__.set;
|
|
var guidFor = __dependency3__.guidFor;
|
|
|
|
var EmberObject = __dependency4__["default"];
|
|
var EmberLocation = __dependency5__["default"];
|
|
var jQuery = __dependency6__["default"];
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-routing
|
|
*/
|
|
|
|
var popstateFired = false;
|
|
var supportsHistoryState = window.history && 'state' in window.history;
|
|
|
|
/**
|
|
Ember.HistoryLocation implements the location API using the browser's
|
|
history.pushState API.
|
|
|
|
@class HistoryLocation
|
|
@namespace Ember
|
|
@extends Ember.Object
|
|
*/
|
|
__exports__["default"] = EmberObject.extend({
|
|
implementation: 'history',
|
|
|
|
init: function() {
|
|
set(this, 'location', get(this, 'location') || window.location);
|
|
set(this, 'baseURL', jQuery('base').attr('href') || '');
|
|
},
|
|
|
|
/**
|
|
Used to set state on first call to setURL
|
|
|
|
@private
|
|
@method initState
|
|
*/
|
|
initState: function() {
|
|
set(this, 'history', get(this, 'history') || window.history);
|
|
this.replaceState(this.formatURL(this.getURL()));
|
|
},
|
|
|
|
/**
|
|
Will be pre-pended to path upon state change
|
|
|
|
@property rootURL
|
|
@default '/'
|
|
*/
|
|
rootURL: '/',
|
|
|
|
/**
|
|
Returns the current `location.pathname` without `rootURL` or `baseURL`
|
|
|
|
@private
|
|
@method getURL
|
|
@return url {String}
|
|
*/
|
|
getURL: function() {
|
|
var rootURL = get(this, 'rootURL');
|
|
var location = get(this, 'location');
|
|
var path = location.pathname;
|
|
var baseURL = get(this, 'baseURL');
|
|
|
|
rootURL = rootURL.replace(/\/$/, '');
|
|
baseURL = baseURL.replace(/\/$/, '');
|
|
|
|
var url = path.replace(baseURL, '').replace(rootURL, '');
|
|
var search = location.search || '';
|
|
|
|
url += search;
|
|
url += this.getHash();
|
|
|
|
return url;
|
|
},
|
|
|
|
/**
|
|
Uses `history.pushState` to update the url without a page reload.
|
|
|
|
@private
|
|
@method setURL
|
|
@param path {String}
|
|
*/
|
|
setURL: function(path) {
|
|
var state = this.getState();
|
|
path = this.formatURL(path);
|
|
|
|
if (!state || state.path !== path) {
|
|
this.pushState(path);
|
|
}
|
|
},
|
|
|
|
/**
|
|
Uses `history.replaceState` to update the url without a page reload
|
|
or history modification.
|
|
|
|
@private
|
|
@method replaceURL
|
|
@param path {String}
|
|
*/
|
|
replaceURL: function(path) {
|
|
var state = this.getState();
|
|
path = this.formatURL(path);
|
|
|
|
if (!state || state.path !== path) {
|
|
this.replaceState(path);
|
|
}
|
|
},
|
|
|
|
/**
|
|
Get the current `history.state`. Checks for if a polyfill is
|
|
required and if so fetches this._historyState. The state returned
|
|
from getState may be null if an iframe has changed a window's
|
|
history.
|
|
|
|
@private
|
|
@method getState
|
|
@return state {Object}
|
|
*/
|
|
getState: function() {
|
|
return supportsHistoryState ? get(this, 'history').state : this._historyState;
|
|
},
|
|
|
|
/**
|
|
Pushes a new state.
|
|
|
|
@private
|
|
@method pushState
|
|
@param path {String}
|
|
*/
|
|
pushState: function(path) {
|
|
var state = { path: path };
|
|
|
|
get(this, 'history').pushState(state, null, path);
|
|
|
|
// store state if browser doesn't support `history.state`
|
|
if (!supportsHistoryState) {
|
|
this._historyState = state;
|
|
}
|
|
|
|
// used for webkit workaround
|
|
this._previousURL = this.getURL();
|
|
},
|
|
|
|
/**
|
|
Replaces the current state.
|
|
|
|
@private
|
|
@method replaceState
|
|
@param path {String}
|
|
*/
|
|
replaceState: function(path) {
|
|
var state = { path: path };
|
|
get(this, 'history').replaceState(state, null, path);
|
|
|
|
// store state if browser doesn't support `history.state`
|
|
if (!supportsHistoryState) {
|
|
this._historyState = state;
|
|
}
|
|
|
|
// used for webkit workaround
|
|
this._previousURL = this.getURL();
|
|
},
|
|
|
|
/**
|
|
Register a callback to be invoked whenever the browser
|
|
history changes, including using forward and back buttons.
|
|
|
|
@private
|
|
@method onUpdateURL
|
|
@param callback {Function}
|
|
*/
|
|
onUpdateURL: function(callback) {
|
|
var guid = guidFor(this);
|
|
var self = this;
|
|
|
|
jQuery(window).on('popstate.ember-location-'+guid, function(e) {
|
|
// Ignore initial page load popstate event in Chrome
|
|
if (!popstateFired) {
|
|
popstateFired = true;
|
|
if (self.getURL() === self._previousURL) { return; }
|
|
}
|
|
callback(self.getURL());
|
|
});
|
|
},
|
|
|
|
/**
|
|
Used when using `{{action}}` helper. The url is always appended to the rootURL.
|
|
|
|
@private
|
|
@method formatURL
|
|
@param url {String}
|
|
@return formatted url {String}
|
|
*/
|
|
formatURL: function(url) {
|
|
var rootURL = get(this, 'rootURL');
|
|
var baseURL = get(this, 'baseURL');
|
|
|
|
if (url !== '') {
|
|
rootURL = rootURL.replace(/\/$/, '');
|
|
baseURL = baseURL.replace(/\/$/, '');
|
|
} else if(baseURL.match(/^\//) && rootURL.match(/^\//)) {
|
|
baseURL = baseURL.replace(/\/$/, '');
|
|
}
|
|
|
|
return baseURL + rootURL + url;
|
|
},
|
|
|
|
/**
|
|
Cleans up the HistoryLocation event listener.
|
|
|
|
@private
|
|
@method willDestroy
|
|
*/
|
|
willDestroy: function() {
|
|
var guid = guidFor(this);
|
|
|
|
jQuery(window).off('popstate.ember-location-'+guid);
|
|
},
|
|
|
|
/**
|
|
@private
|
|
|
|
Returns normalized location.hash
|
|
|
|
@method getHash
|
|
*/
|
|
getHash: EmberLocation._getHash
|
|
});
|
|
});
|
|
enifed("ember-routing/location/none_location",
|
|
["ember-metal/property_get","ember-metal/property_set","ember-runtime/system/object","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
var get = __dependency1__.get;
|
|
var set = __dependency2__.set;
|
|
var EmberObject = __dependency3__["default"];
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-routing
|
|
*/
|
|
|
|
/**
|
|
Ember.NoneLocation does not interact with the browser. It is useful for
|
|
testing, or when you need to manage state with your Router, but temporarily
|
|
don't want it to muck with the URL (for example when you embed your
|
|
application in a larger page).
|
|
|
|
@class NoneLocation
|
|
@namespace Ember
|
|
@extends Ember.Object
|
|
*/
|
|
__exports__["default"] = EmberObject.extend({
|
|
implementation: 'none',
|
|
path: '',
|
|
|
|
/**
|
|
Returns the current path.
|
|
|
|
@private
|
|
@method getURL
|
|
@return {String} path
|
|
*/
|
|
getURL: function() {
|
|
return get(this, 'path');
|
|
},
|
|
|
|
/**
|
|
Set the path and remembers what was set. Using this method
|
|
to change the path will not invoke the `updateURL` callback.
|
|
|
|
@private
|
|
@method setURL
|
|
@param path {String}
|
|
*/
|
|
setURL: function(path) {
|
|
set(this, 'path', path);
|
|
},
|
|
|
|
/**
|
|
Register a callback to be invoked when the path changes. These
|
|
callbacks will execute when the user presses the back or forward
|
|
button, but not after `setURL` is invoked.
|
|
|
|
@private
|
|
@method onUpdateURL
|
|
@param callback {Function}
|
|
*/
|
|
onUpdateURL: function(callback) {
|
|
this.updateCallback = callback;
|
|
},
|
|
|
|
/**
|
|
Sets the path and calls the `updateURL` callback.
|
|
|
|
@private
|
|
@method handleURL
|
|
@param callback {Function}
|
|
*/
|
|
handleURL: function(url) {
|
|
set(this, 'path', url);
|
|
this.updateCallback(url);
|
|
},
|
|
|
|
/**
|
|
Given a URL, formats it to be placed into the page as part
|
|
of an element's `href` attribute.
|
|
|
|
This is used, for example, when using the {{action}} helper
|
|
to generate a URL based on an event.
|
|
|
|
@private
|
|
@method formatURL
|
|
@param url {String}
|
|
@return {String} url
|
|
*/
|
|
formatURL: function(url) {
|
|
// The return value is not overly meaningful, but we do not want to throw
|
|
// errors when test code renders templates containing {{action href=true}}
|
|
// helpers.
|
|
return url;
|
|
}
|
|
});
|
|
});
|
|
enifed("ember-routing/system/cache",
|
|
["ember-runtime/system/object","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var EmberObject = __dependency1__["default"];
|
|
|
|
__exports__["default"] = EmberObject.extend({
|
|
init: function() {
|
|
this.cache = {};
|
|
},
|
|
has: function(bucketKey) {
|
|
return bucketKey in this.cache;
|
|
},
|
|
stash: function(bucketKey, key, value) {
|
|
var bucket = this.cache[bucketKey];
|
|
if (!bucket) {
|
|
bucket = this.cache[bucketKey] = {};
|
|
}
|
|
bucket[key] = value;
|
|
},
|
|
lookup: function(bucketKey, prop, defaultValue) {
|
|
var cache = this.cache;
|
|
if (!(bucketKey in cache)) {
|
|
return defaultValue;
|
|
}
|
|
var bucket = cache[bucketKey];
|
|
if (prop in bucket) {
|
|
return bucket[prop];
|
|
} else {
|
|
return defaultValue;
|
|
}
|
|
},
|
|
cache: null
|
|
});
|
|
});
|
|
enifed("ember-routing/system/controller_for",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-routing
|
|
*/
|
|
|
|
/**
|
|
|
|
Finds a controller instance.
|
|
|
|
@for Ember
|
|
@method controllerFor
|
|
@private
|
|
*/
|
|
__exports__["default"] = function controllerFor(container, controllerName, lookupOptions) {
|
|
return container.lookup('controller:' + controllerName, lookupOptions);
|
|
}
|
|
});
|
|
enifed("ember-routing/system/dsl",
|
|
["ember-metal/core","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
// FEATURES, assert
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-routing
|
|
*/
|
|
|
|
function DSL(name) {
|
|
this.parent = name;
|
|
this.matches = [];
|
|
}
|
|
__exports__["default"] = DSL;
|
|
|
|
DSL.prototype = {
|
|
route: function(name, options, callback) {
|
|
if (arguments.length === 2 && typeof options === 'function') {
|
|
callback = options;
|
|
options = {};
|
|
}
|
|
|
|
if (arguments.length === 1) {
|
|
options = {};
|
|
}
|
|
|
|
var type = options.resetNamespace === true ? 'resource' : 'route';
|
|
Ember.assert("'basic' cannot be used as a " + type + " name.", name !== 'basic');
|
|
|
|
|
|
if (callback) {
|
|
var fullName = getFullName(this, name, options.resetNamespace);
|
|
var dsl = new DSL(fullName);
|
|
createRoute(dsl, 'loading');
|
|
createRoute(dsl, 'error', { path: "/_unused_dummy_error_path_route_" + name + "/:error" });
|
|
|
|
callback.call(dsl);
|
|
|
|
createRoute(this, name, options, dsl.generate());
|
|
} else {
|
|
createRoute(this, name, options);
|
|
}
|
|
},
|
|
|
|
push: function(url, name, callback) {
|
|
var parts = name.split('.');
|
|
if (url === "" || url === "/" || parts[parts.length-1] === "index") { this.explicitIndex = true; }
|
|
|
|
this.matches.push([url, name, callback]);
|
|
},
|
|
|
|
resource: function(name, options, callback) {
|
|
if (arguments.length === 2 && typeof options === 'function') {
|
|
callback = options;
|
|
options = {};
|
|
}
|
|
|
|
if (arguments.length === 1) {
|
|
options = {};
|
|
}
|
|
|
|
options.resetNamespace = true;
|
|
this.route(name, options, callback);
|
|
},
|
|
|
|
generate: function() {
|
|
var dslMatches = this.matches;
|
|
|
|
if (!this.explicitIndex) {
|
|
this.route("index", { path: "/" });
|
|
}
|
|
|
|
return function(match) {
|
|
for (var i=0, l=dslMatches.length; i<l; i++) {
|
|
var dslMatch = dslMatches[i];
|
|
match(dslMatch[0]).to(dslMatch[1], dslMatch[2]);
|
|
}
|
|
};
|
|
}
|
|
};
|
|
|
|
function canNest(dsl) {
|
|
return dsl.parent && dsl.parent !== 'application';
|
|
}
|
|
|
|
function getFullName(dsl, name, resetNamespace) {
|
|
if (canNest(dsl) && resetNamespace !== true) {
|
|
return dsl.parent + "." + name;
|
|
} else {
|
|
return name;
|
|
}
|
|
}
|
|
|
|
function createRoute(dsl, name, options, callback) {
|
|
options = options || {};
|
|
|
|
var fullName = getFullName(dsl, name, options.resetNamespace);
|
|
|
|
if (typeof options.path !== 'string') {
|
|
options.path = "/" + name;
|
|
}
|
|
|
|
dsl.push(options.path, fullName, callback);
|
|
}
|
|
|
|
DSL.map = function(callback) {
|
|
var dsl = new DSL();
|
|
callback.call(dsl);
|
|
return dsl;
|
|
};
|
|
});
|
|
enifed("ember-routing/system/generate_controller",
|
|
["ember-metal/core","ember-metal/property_get","ember-metal/utils","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
// Logger
|
|
var get = __dependency2__.get;
|
|
var isArray = __dependency3__.isArray;
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-routing
|
|
*/
|
|
|
|
/**
|
|
Generates a controller factory
|
|
|
|
The type of the generated controller factory is derived
|
|
from the context. If the context is an array an array controller
|
|
is generated, if an object, an object controller otherwise, a basic
|
|
controller is generated.
|
|
|
|
You can customize your generated controllers by defining
|
|
`App.ObjectController` or `App.ArrayController`.
|
|
|
|
@for Ember
|
|
@method generateControllerFactory
|
|
@private
|
|
*/
|
|
|
|
function generateControllerFactory(container, controllerName, context) {
|
|
var Factory, fullName, factoryName, controllerType;
|
|
|
|
if (context && isArray(context)) {
|
|
controllerType = 'array';
|
|
} else if (context) {
|
|
controllerType = 'object';
|
|
} else {
|
|
controllerType = 'basic';
|
|
}
|
|
|
|
factoryName = 'controller:' + controllerType;
|
|
|
|
Factory = container.lookupFactory(factoryName).extend({
|
|
isGenerated: true,
|
|
toString: function() {
|
|
return "(generated " + controllerName + " controller)";
|
|
}
|
|
});
|
|
|
|
fullName = 'controller:' + controllerName;
|
|
|
|
container.register(fullName, Factory);
|
|
|
|
return Factory;
|
|
}
|
|
|
|
__exports__.generateControllerFactory = generateControllerFactory;/**
|
|
Generates and instantiates a controller.
|
|
|
|
The type of the generated controller factory is derived
|
|
from the context. If the context is an array an array controller
|
|
is generated, if an object, an object controller otherwise, a basic
|
|
controller is generated.
|
|
|
|
@for Ember
|
|
@method generateController
|
|
@private
|
|
@since 1.3.0
|
|
*/
|
|
__exports__["default"] = function generateController(container, controllerName, context) {
|
|
generateControllerFactory(container, controllerName, context);
|
|
var fullName = 'controller:' + controllerName;
|
|
var instance = container.lookup(fullName);
|
|
|
|
if (get(instance, 'namespace.LOG_ACTIVE_GENERATION')) {
|
|
Ember.Logger.info("generated -> " + fullName, { fullName: fullName });
|
|
}
|
|
|
|
return instance;
|
|
}
|
|
});
|
|
enifed("ember-routing/system/query_params",
|
|
["ember-runtime/system/object","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var EmberObject = __dependency1__["default"];
|
|
|
|
__exports__["default"] = EmberObject.extend({
|
|
isQueryParams: true,
|
|
values: null
|
|
});
|
|
});
|
|
enifed("ember-routing/system/route",
|
|
["ember-metal/core","ember-metal/error","ember-metal/property_get","ember-metal/property_set","ember-metal/get_properties","ember-metal/enumerable_utils","ember-metal/is_none","ember-metal/computed","ember-metal/merge","ember-metal/utils","ember-metal/run_loop","ember-metal/keys","ember-runtime/copy","ember-runtime/system/string","ember-runtime/system/object","ember-runtime/mixins/evented","ember-runtime/mixins/action_handler","ember-routing/system/generate_controller","ember-routing/utils","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __dependency16__, __dependency17__, __dependency18__, __dependency19__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
// FEATURES, A, deprecate, assert, Logger
|
|
var EmberError = __dependency2__["default"];
|
|
var get = __dependency3__.get;
|
|
var set = __dependency4__.set;
|
|
var getProperties = __dependency5__["default"];
|
|
var forEach = __dependency6__.forEach;
|
|
var replace = __dependency6__.replace;
|
|
var isNone = __dependency7__["default"];
|
|
var computed = __dependency8__.computed;
|
|
var merge = __dependency9__["default"];
|
|
var isArray = __dependency10__.isArray;
|
|
var typeOf = __dependency10__.typeOf;
|
|
var run = __dependency11__["default"];
|
|
var keys = __dependency12__["default"];
|
|
var copy = __dependency13__["default"];
|
|
var classify = __dependency14__.classify;
|
|
var EmberObject = __dependency15__["default"];
|
|
var Evented = __dependency16__["default"];
|
|
var ActionHandler = __dependency17__["default"];
|
|
var generateController = __dependency18__["default"];
|
|
var stashParamNames = __dependency19__.stashParamNames;
|
|
|
|
var slice = Array.prototype.slice;
|
|
|
|
function K() { return this; }
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-routing
|
|
*/
|
|
|
|
/**
|
|
The `Ember.Route` class is used to define individual routes. Refer to
|
|
the [routing guide](http://emberjs.com/guides/routing/) for documentation.
|
|
|
|
@class Route
|
|
@namespace Ember
|
|
@extends Ember.Object
|
|
@uses Ember.ActionHandler
|
|
*/
|
|
var Route = EmberObject.extend(ActionHandler, {
|
|
/**
|
|
Configuration hash for this route's queryParams. The possible
|
|
configuration options and their defaults are as follows
|
|
(assuming a query param whose URL key is `page`):
|
|
|
|
```javascript
|
|
queryParams: {
|
|
page: {
|
|
// By default, controller query param properties don't
|
|
// cause a full transition when they are changed, but
|
|
// rather only cause the URL to update. Setting
|
|
// `refreshModel` to true will cause an "in-place"
|
|
// transition to occur, whereby the model hooks for
|
|
// this route (and any child routes) will re-fire, allowing
|
|
// you to reload models (e.g., from the server) using the
|
|
// updated query param values.
|
|
refreshModel: false,
|
|
|
|
// By default, changes to controller query param properties
|
|
// cause the URL to update via `pushState`, which means an
|
|
// item will be added to the browser's history, allowing
|
|
// you to use the back button to restore the app to the
|
|
// previous state before the query param property was changed.
|
|
// Setting `replace` to true will use `replaceState` (or its
|
|
// hash location equivalent), which causes no browser history
|
|
// item to be added. This options name and default value are
|
|
// the same as the `link-to` helper's `replace` option.
|
|
replace: false
|
|
}
|
|
}
|
|
```
|
|
|
|
@property queryParams
|
|
@for Ember.Route
|
|
@type Hash
|
|
*/
|
|
queryParams: {},
|
|
|
|
/**
|
|
@private
|
|
|
|
@property _qp
|
|
*/
|
|
_qp: computed(function() {
|
|
var controllerName = this.controllerName || this.routeName;
|
|
var controllerClass = this.container.lookupFactory('controller:' + controllerName);
|
|
|
|
if (!controllerClass) {
|
|
return defaultQPMeta;
|
|
}
|
|
|
|
var controllerProto = controllerClass.proto();
|
|
var qpProps = get(controllerProto, '_normalizedQueryParams');
|
|
var cacheMeta = get(controllerProto, '_cacheMeta');
|
|
|
|
var qps = [], map = {}, self = this;
|
|
for (var propName in qpProps) {
|
|
if (!qpProps.hasOwnProperty(propName)) { continue; }
|
|
|
|
var desc = qpProps[propName];
|
|
var urlKey = desc.as || this.serializeQueryParamKey(propName);
|
|
var defaultValue = get(controllerProto, propName);
|
|
|
|
if (isArray(defaultValue)) {
|
|
defaultValue = Ember.A(defaultValue.slice());
|
|
}
|
|
|
|
var type = typeOf(defaultValue);
|
|
var defaultValueSerialized = this.serializeQueryParam(defaultValue, urlKey, type);
|
|
var fprop = controllerName + ':' + propName;
|
|
var qp = {
|
|
def: defaultValue,
|
|
sdef: defaultValueSerialized,
|
|
type: type,
|
|
urlKey: urlKey,
|
|
prop: propName,
|
|
fprop: fprop,
|
|
ctrl: controllerName,
|
|
cProto: controllerProto,
|
|
svalue: defaultValueSerialized,
|
|
cacheType: desc.scope,
|
|
route: this,
|
|
cacheMeta: cacheMeta[propName]
|
|
};
|
|
|
|
map[propName] = map[urlKey] = map[fprop] = qp;
|
|
qps.push(qp);
|
|
}
|
|
|
|
return {
|
|
qps: qps,
|
|
map: map,
|
|
states: {
|
|
active: function(controller, prop) {
|
|
return self._activeQPChanged(controller, map[prop]);
|
|
},
|
|
allowOverrides: function(controller, prop) {
|
|
return self._updatingQPChanged(controller, map[prop]);
|
|
},
|
|
changingKeys: function(controller, prop) {
|
|
return self._updateSerializedQPValue(controller, map[prop]);
|
|
}
|
|
}
|
|
};
|
|
}),
|
|
|
|
/**
|
|
@private
|
|
|
|
@property _names
|
|
*/
|
|
_names: null,
|
|
|
|
/**
|
|
@private
|
|
|
|
@method _stashNames
|
|
*/
|
|
_stashNames: function(_handlerInfo, dynamicParent) {
|
|
var handlerInfo = _handlerInfo;
|
|
if (this._names) { return; }
|
|
var names = this._names = handlerInfo._names;
|
|
|
|
if (!names.length) {
|
|
handlerInfo = dynamicParent;
|
|
names = handlerInfo && handlerInfo._names || [];
|
|
}
|
|
|
|
var qps = get(this, '_qp.qps');
|
|
var len = qps.length;
|
|
|
|
var namePaths = new Array(names.length);
|
|
for (var a = 0, nlen = names.length; a < nlen; ++a) {
|
|
namePaths[a] = handlerInfo.name + '.' + names[a];
|
|
}
|
|
|
|
for (var i = 0; i < len; ++i) {
|
|
var qp = qps[i];
|
|
var cacheMeta = qp.cacheMeta;
|
|
if (cacheMeta.scope === 'model') {
|
|
cacheMeta.parts = namePaths;
|
|
}
|
|
cacheMeta.prefix = qp.ctrl;
|
|
}
|
|
},
|
|
|
|
/**
|
|
@private
|
|
|
|
@property _updateSerializedQPValue
|
|
*/
|
|
_updateSerializedQPValue: function(controller, qp) {
|
|
var value = get(controller, qp.prop);
|
|
qp.svalue = this.serializeQueryParam(value, qp.urlKey, qp.type);
|
|
},
|
|
|
|
/**
|
|
@private
|
|
|
|
@property _activeQPChanged
|
|
*/
|
|
_activeQPChanged: function(controller, qp) {
|
|
var value = get(controller, qp.prop);
|
|
this.router._queuedQPChanges[qp.fprop] = value;
|
|
run.once(this, this._fireQueryParamTransition);
|
|
},
|
|
|
|
/**
|
|
@private
|
|
@method _updatingQPChanged
|
|
*/
|
|
_updatingQPChanged: function(controller, qp) {
|
|
var router = this.router;
|
|
if (!router._qpUpdates) {
|
|
router._qpUpdates = {};
|
|
}
|
|
router._qpUpdates[qp.urlKey] = true;
|
|
},
|
|
|
|
mergedProperties: ['events', 'queryParams'],
|
|
|
|
/**
|
|
Retrieves parameters, for current route using the state.params
|
|
variable and getQueryParamsFor, using the supplied routeName.
|
|
|
|
@method paramsFor
|
|
@param {String} routename
|
|
|
|
*/
|
|
paramsFor: function(name) {
|
|
var route = this.container.lookup('route:' + name);
|
|
|
|
if (!route) {
|
|
return {};
|
|
}
|
|
|
|
var transition = this.router.router.activeTransition;
|
|
var state = transition ? transition.state : this.router.router.state;
|
|
|
|
var params = {};
|
|
merge(params, state.params[name]);
|
|
merge(params, getQueryParamsFor(route, state));
|
|
|
|
return params;
|
|
},
|
|
|
|
/**
|
|
Serializes the query parameter key
|
|
|
|
@method serializeQueryParamKey
|
|
@param {String} controllerPropertyName
|
|
*/
|
|
serializeQueryParamKey: function(controllerPropertyName) {
|
|
return controllerPropertyName;
|
|
},
|
|
|
|
/**
|
|
Serializes value of the query parameter based on defaultValueType
|
|
|
|
@method serializeQueryParam
|
|
@param {Object} value
|
|
@param {String} urlKey
|
|
@param {String} defaultValueType
|
|
*/
|
|
serializeQueryParam: function(value, urlKey, defaultValueType) {
|
|
// urlKey isn't used here, but anyone overriding
|
|
// can use it to provide serialization specific
|
|
// to a certain query param.
|
|
if (defaultValueType === 'array') {
|
|
return JSON.stringify(value);
|
|
}
|
|
return '' + value;
|
|
},
|
|
|
|
/**
|
|
Deserializes value of the query parameter based on defaultValueType
|
|
|
|
@method deserializeQueryParam
|
|
@param {Object} value
|
|
@param {String} urlKey
|
|
@param {String} defaultValueType
|
|
*/
|
|
deserializeQueryParam: function(value, urlKey, defaultValueType) {
|
|
// urlKey isn't used here, but anyone overriding
|
|
// can use it to provide deserialization specific
|
|
// to a certain query param.
|
|
|
|
// Use the defaultValueType of the default value (the initial value assigned to a
|
|
// controller query param property), to intelligently deserialize and cast.
|
|
if (defaultValueType === 'boolean') {
|
|
return (value === 'true') ? true : false;
|
|
} else if (defaultValueType === 'number') {
|
|
return (Number(value)).valueOf();
|
|
} else if (defaultValueType === 'array') {
|
|
return Ember.A(JSON.parse(value));
|
|
}
|
|
return value;
|
|
},
|
|
|
|
|
|
/**
|
|
@private
|
|
@property _fireQueryParamTransition
|
|
*/
|
|
_fireQueryParamTransition: function() {
|
|
this.transitionTo({ queryParams: this.router._queuedQPChanges });
|
|
this.router._queuedQPChanges = {};
|
|
},
|
|
|
|
/**
|
|
@private
|
|
|
|
@property _optionsForQueryParam
|
|
*/
|
|
_optionsForQueryParam: function(qp) {
|
|
return get(this, 'queryParams.' + qp.urlKey) || get(this, 'queryParams.' + qp.prop) || {};
|
|
},
|
|
|
|
/**
|
|
A hook you can use to reset controller values either when the model
|
|
changes or the route is exiting.
|
|
|
|
```javascript
|
|
App.ArticlesRoute = Ember.Route.extend({
|
|
// ...
|
|
|
|
resetController: function (controller, isExiting, transition) {
|
|
if (isExiting) {
|
|
controller.set('page', 1);
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
@method resetController
|
|
@param {Controller} controller instance
|
|
@param {Boolean} isExiting
|
|
@param {Object} transition
|
|
@since 1.7.0
|
|
*/
|
|
resetController: K,
|
|
|
|
/**
|
|
@private
|
|
|
|
@method exit
|
|
*/
|
|
exit: function() {
|
|
this.deactivate();
|
|
|
|
this.trigger('deactivate');
|
|
|
|
this.teardownViews();
|
|
},
|
|
|
|
/**
|
|
@private
|
|
|
|
@method _reset
|
|
@since 1.7.0
|
|
*/
|
|
_reset: function(isExiting, transition) {
|
|
var controller = this.controller;
|
|
|
|
controller._qpDelegate = get(this, '_qp.states.inactive');
|
|
|
|
this.resetController(controller, isExiting, transition);
|
|
},
|
|
|
|
/**
|
|
@private
|
|
|
|
@method enter
|
|
*/
|
|
enter: function() {
|
|
this.activate();
|
|
|
|
this.trigger('activate');
|
|
|
|
},
|
|
|
|
/**
|
|
The name of the view to use by default when rendering this routes template.
|
|
|
|
When rendering a template, the route will, by default, determine the
|
|
template and view to use from the name of the route itself. If you need to
|
|
define a specific view, set this property.
|
|
|
|
This is useful when multiple routes would benefit from using the same view
|
|
because it doesn't require a custom `renderTemplate` method. For example,
|
|
the following routes will all render using the `App.PostsListView` view:
|
|
|
|
```javascript
|
|
var PostsList = Ember.Route.extend({
|
|
viewName: 'postsList'
|
|
});
|
|
|
|
App.PostsIndexRoute = PostsList.extend();
|
|
App.PostsArchivedRoute = PostsList.extend();
|
|
```
|
|
|
|
@property viewName
|
|
@type String
|
|
@default null
|
|
@since 1.4.0
|
|
*/
|
|
viewName: null,
|
|
|
|
/**
|
|
The name of the template to use by default when rendering this routes
|
|
template.
|
|
|
|
This is similar with `viewName`, but is useful when you just want a custom
|
|
template without a view.
|
|
|
|
```javascript
|
|
var PostsList = Ember.Route.extend({
|
|
templateName: 'posts/list'
|
|
});
|
|
|
|
App.PostsIndexRoute = PostsList.extend();
|
|
App.PostsArchivedRoute = PostsList.extend();
|
|
```
|
|
|
|
@property templateName
|
|
@type String
|
|
@default null
|
|
@since 1.4.0
|
|
*/
|
|
templateName: null,
|
|
|
|
/**
|
|
The name of the controller to associate with this route.
|
|
|
|
By default, Ember will lookup a route's controller that matches the name
|
|
of the route (i.e. `App.PostController` for `App.PostRoute`). However,
|
|
if you would like to define a specific controller to use, you can do so
|
|
using this property.
|
|
|
|
This is useful in many ways, as the controller specified will be:
|
|
|
|
* passed to the `setupController` method.
|
|
* used as the controller for the view being rendered by the route.
|
|
* returned from a call to `controllerFor` for the route.
|
|
|
|
@property controllerName
|
|
@type String
|
|
@default null
|
|
@since 1.4.0
|
|
*/
|
|
controllerName: null,
|
|
|
|
/**
|
|
The `willTransition` action is fired at the beginning of any
|
|
attempted transition with a `Transition` object as the sole
|
|
argument. This action can be used for aborting, redirecting,
|
|
or decorating the transition from the currently active routes.
|
|
|
|
A good example is preventing navigation when a form is
|
|
half-filled out:
|
|
|
|
```javascript
|
|
App.ContactFormRoute = Ember.Route.extend({
|
|
actions: {
|
|
willTransition: function(transition) {
|
|
if (this.controller.get('userHasEnteredData')) {
|
|
this.controller.displayNavigationConfirm();
|
|
transition.abort();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
You can also redirect elsewhere by calling
|
|
`this.transitionTo('elsewhere')` from within `willTransition`.
|
|
Note that `willTransition` will not be fired for the
|
|
redirecting `transitionTo`, since `willTransition` doesn't
|
|
fire when there is already a transition underway. If you want
|
|
subsequent `willTransition` actions to fire for the redirecting
|
|
transition, you must first explicitly call
|
|
`transition.abort()`.
|
|
|
|
@event willTransition
|
|
@param {Transition} transition
|
|
*/
|
|
|
|
/**
|
|
The `didTransition` action is fired after a transition has
|
|
successfully been completed. This occurs after the normal model
|
|
hooks (`beforeModel`, `model`, `afterModel`, `setupController`)
|
|
have resolved. The `didTransition` action has no arguments,
|
|
however, it can be useful for tracking page views or resetting
|
|
state on the controller.
|
|
|
|
```javascript
|
|
App.LoginRoute = Ember.Route.extend({
|
|
actions: {
|
|
didTransition: function() {
|
|
this.controller.get('errors.base').clear();
|
|
return true; // Bubble the didTransition event
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
@event didTransition
|
|
@since 1.2.0
|
|
*/
|
|
|
|
/**
|
|
The `loading` action is fired on the route when a route's `model`
|
|
hook returns a promise that is not already resolved. The current
|
|
`Transition` object is the first parameter and the route that
|
|
triggered the loading event is the second parameter.
|
|
|
|
```javascript
|
|
App.ApplicationRoute = Ember.Route.extend({
|
|
actions: {
|
|
loading: function(transition, route) {
|
|
var view = Ember.View.create({
|
|
classNames: ['app-loading']
|
|
})
|
|
.append();
|
|
|
|
this.router.one('didTransition', function() {
|
|
view.destroy();
|
|
});
|
|
|
|
return true; // Bubble the loading event
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
@event loading
|
|
@param {Transition} transition
|
|
@param {Ember.Route} route The route that triggered the loading event
|
|
@since 1.2.0
|
|
*/
|
|
|
|
/**
|
|
When attempting to transition into a route, any of the hooks
|
|
may return a promise that rejects, at which point an `error`
|
|
action will be fired on the partially-entered routes, allowing
|
|
for per-route error handling logic, or shared error handling
|
|
logic defined on a parent route.
|
|
|
|
Here is an example of an error handler that will be invoked
|
|
for rejected promises from the various hooks on the route,
|
|
as well as any unhandled errors from child routes:
|
|
|
|
```javascript
|
|
App.AdminRoute = Ember.Route.extend({
|
|
beforeModel: function() {
|
|
return Ember.RSVP.reject('bad things!');
|
|
},
|
|
|
|
actions: {
|
|
error: function(error, transition) {
|
|
// Assuming we got here due to the error in `beforeModel`,
|
|
// we can expect that error === "bad things!",
|
|
// but a promise model rejecting would also
|
|
// call this hook, as would any errors encountered
|
|
// in `afterModel`.
|
|
|
|
// The `error` hook is also provided the failed
|
|
// `transition`, which can be stored and later
|
|
// `.retry()`d if desired.
|
|
|
|
this.transitionTo('login');
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
`error` actions that bubble up all the way to `ApplicationRoute`
|
|
will fire a default error handler that logs the error. You can
|
|
specify your own global default error handler by overriding the
|
|
`error` handler on `ApplicationRoute`:
|
|
|
|
```javascript
|
|
App.ApplicationRoute = Ember.Route.extend({
|
|
actions: {
|
|
error: function(error, transition) {
|
|
this.controllerFor('banner').displayError(error.message);
|
|
}
|
|
}
|
|
});
|
|
```
|
|
@event error
|
|
@param {Error} error
|
|
@param {Transition} transition
|
|
*/
|
|
|
|
/**
|
|
The controller associated with this route.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
App.FormRoute = Ember.Route.extend({
|
|
actions: {
|
|
willTransition: function(transition) {
|
|
if (this.controller.get('userHasEnteredData') &&
|
|
!confirm('Are you sure you want to abandon progress?')) {
|
|
transition.abort();
|
|
} else {
|
|
// Bubble the `willTransition` action so that
|
|
// parent routes can decide whether or not to abort.
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
@property controller
|
|
@type Ember.Controller
|
|
@since 1.6.0
|
|
*/
|
|
|
|
_actions: {
|
|
|
|
queryParamsDidChange: function(changed, totalPresent, removed) {
|
|
var qpMap = get(this, '_qp').map;
|
|
|
|
var totalChanged = keys(changed).concat(keys(removed));
|
|
for (var i = 0, len = totalChanged.length; i < len; ++i) {
|
|
var qp = qpMap[totalChanged[i]];
|
|
if (qp && get(this._optionsForQueryParam(qp), 'refreshModel')) {
|
|
this.refresh();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
},
|
|
|
|
finalizeQueryParamChange: function(params, finalParams, transition) {
|
|
if (this.routeName !== 'application') { return true; }
|
|
|
|
// Transition object is absent for intermediate transitions.
|
|
if (!transition) { return; }
|
|
|
|
var handlerInfos = transition.state.handlerInfos;
|
|
var router = this.router;
|
|
var qpMeta = router._queryParamsFor(handlerInfos[handlerInfos.length-1].name);
|
|
var changes = router._qpUpdates;
|
|
var replaceUrl;
|
|
|
|
stashParamNames(router, handlerInfos);
|
|
|
|
for (var i = 0, len = qpMeta.qps.length; i < len; ++i) {
|
|
var qp = qpMeta.qps[i];
|
|
var route = qp.route;
|
|
var controller = route.controller;
|
|
var presentKey = qp.urlKey in params && qp.urlKey;
|
|
|
|
// Do a reverse lookup to see if the changed query
|
|
// param URL key corresponds to a QP property on
|
|
// this controller.
|
|
var value, svalue;
|
|
if (changes && qp.urlKey in changes) {
|
|
// Value updated in/before setupController
|
|
value = get(controller, qp.prop);
|
|
svalue = route.serializeQueryParam(value, qp.urlKey, qp.type);
|
|
} else {
|
|
if (presentKey) {
|
|
svalue = params[presentKey];
|
|
value = route.deserializeQueryParam(svalue, qp.urlKey, qp.type);
|
|
} else {
|
|
// No QP provided; use default value.
|
|
svalue = qp.sdef;
|
|
value = copyDefaultValue(qp.def);
|
|
}
|
|
}
|
|
|
|
controller._qpDelegate = get(this, '_qp.states.inactive');
|
|
|
|
var thisQueryParamChanged = (svalue !== qp.svalue);
|
|
if (thisQueryParamChanged) {
|
|
if (transition.queryParamsOnly && replaceUrl !== false) {
|
|
var options = route._optionsForQueryParam(qp);
|
|
var replaceConfigValue = get(options, 'replace');
|
|
if (replaceConfigValue) {
|
|
replaceUrl = true;
|
|
} else if (replaceConfigValue === false) {
|
|
// Explicit pushState wins over any other replaceStates.
|
|
replaceUrl = false;
|
|
}
|
|
}
|
|
|
|
set(controller, qp.prop, value);
|
|
}
|
|
|
|
// Stash current serialized value of controller.
|
|
qp.svalue = svalue;
|
|
|
|
var thisQueryParamHasDefaultValue = (qp.sdef === svalue);
|
|
if (!thisQueryParamHasDefaultValue) {
|
|
finalParams.push({
|
|
value: svalue,
|
|
visible: true,
|
|
key: presentKey || qp.urlKey
|
|
});
|
|
}
|
|
}
|
|
|
|
if (replaceUrl) {
|
|
transition.method('replace');
|
|
}
|
|
|
|
forEach(qpMeta.qps, function(qp) {
|
|
var routeQpMeta = get(qp.route, '_qp');
|
|
var finalizedController = qp.route.controller;
|
|
finalizedController._qpDelegate = get(routeQpMeta, 'states.active');
|
|
});
|
|
|
|
router._qpUpdates = null;
|
|
}
|
|
},
|
|
|
|
/**
|
|
@deprecated
|
|
|
|
Please use `actions` instead.
|
|
@method events
|
|
*/
|
|
events: null,
|
|
|
|
/**
|
|
This hook is executed when the router completely exits this route. It is
|
|
not executed when the model for the route changes.
|
|
|
|
@method deactivate
|
|
*/
|
|
deactivate: K,
|
|
|
|
/**
|
|
This hook is executed when the router enters the route. It is not executed
|
|
when the model for the route changes.
|
|
|
|
@method activate
|
|
*/
|
|
activate: K,
|
|
|
|
/**
|
|
Transition the application into another route. The route may
|
|
be either a single route or route path:
|
|
|
|
```javascript
|
|
this.transitionTo('blogPosts');
|
|
this.transitionTo('blogPosts.recentEntries');
|
|
```
|
|
|
|
Optionally supply a model for the route in question. The model
|
|
will be serialized into the URL using the `serialize` hook of
|
|
the route:
|
|
|
|
```javascript
|
|
this.transitionTo('blogPost', aPost);
|
|
```
|
|
|
|
If a literal is passed (such as a number or a string), it will
|
|
be treated as an identifier instead. In this case, the `model`
|
|
hook of the route will be triggered:
|
|
|
|
```javascript
|
|
this.transitionTo('blogPost', 1);
|
|
```
|
|
|
|
Multiple models will be applied last to first recursively up the
|
|
resource tree.
|
|
|
|
```javascript
|
|
App.Router.map(function() {
|
|
this.resource('blogPost', { path:':blogPostId' }, function() {
|
|
this.resource('blogComment', { path: ':blogCommentId' });
|
|
});
|
|
});
|
|
|
|
this.transitionTo('blogComment', aPost, aComment);
|
|
this.transitionTo('blogComment', 1, 13);
|
|
```
|
|
|
|
It is also possible to pass a URL (a string that starts with a
|
|
`/`). This is intended for testing and debugging purposes and
|
|
should rarely be used in production code.
|
|
|
|
```javascript
|
|
this.transitionTo('/');
|
|
this.transitionTo('/blog/post/1/comment/13');
|
|
this.transitionTo('/blog/posts?sort=title');
|
|
```
|
|
|
|
An options hash with a `queryParams` property may be provided as
|
|
the final argument to add query parameters to the destination URL.
|
|
|
|
```javascript
|
|
this.transitionTo('blogPost', 1, {
|
|
queryParams: {showComments: 'true'}
|
|
});
|
|
|
|
// if you just want to transition the query parameters without changing the route
|
|
this.transitionTo({queryParams: {sort: 'date'}});
|
|
```
|
|
|
|
See also 'replaceWith'.
|
|
|
|
Simple Transition Example
|
|
|
|
```javascript
|
|
App.Router.map(function() {
|
|
this.route('index');
|
|
this.route('secret');
|
|
this.route('fourOhFour', { path: '*:' });
|
|
});
|
|
|
|
App.IndexRoute = Ember.Route.extend({
|
|
actions: {
|
|
moveToSecret: function(context) {
|
|
if (authorized()) {
|
|
this.transitionTo('secret', context);
|
|
} else {
|
|
this.transitionTo('fourOhFour');
|
|
}
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
Transition to a nested route
|
|
|
|
```javascript
|
|
App.Router.map(function() {
|
|
this.resource('articles', { path: '/articles' }, function() {
|
|
this.route('new');
|
|
});
|
|
});
|
|
|
|
App.IndexRoute = Ember.Route.extend({
|
|
actions: {
|
|
transitionToNewArticle: function() {
|
|
this.transitionTo('articles.new');
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
Multiple Models Example
|
|
|
|
```javascript
|
|
App.Router.map(function() {
|
|
this.route('index');
|
|
|
|
this.resource('breakfast', { path: ':breakfastId' }, function() {
|
|
this.resource('cereal', { path: ':cerealId' });
|
|
});
|
|
});
|
|
|
|
App.IndexRoute = Ember.Route.extend({
|
|
actions: {
|
|
moveToChocolateCereal: function() {
|
|
var cereal = { cerealId: 'ChocolateYumminess' };
|
|
var breakfast = { breakfastId: 'CerealAndMilk' };
|
|
|
|
this.transitionTo('cereal', breakfast, cereal);
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
Nested Route with Query String Example
|
|
|
|
```javascript
|
|
App.Router.map(function() {
|
|
this.resource('fruits', function() {
|
|
this.route('apples');
|
|
});
|
|
});
|
|
|
|
App.IndexRoute = Ember.Route.extend({
|
|
actions: {
|
|
transitionToApples: function() {
|
|
this.transitionTo('fruits.apples', {queryParams: {color: 'red'}});
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
@method transitionTo
|
|
@param {String} name the name of the route or a URL
|
|
@param {...Object} models the model(s) or identifier(s) to be used while
|
|
transitioning to the route.
|
|
@param {Object} [options] optional hash with a queryParams property
|
|
containing a mapping of query parameters
|
|
@return {Transition} the transition object associated with this
|
|
attempted transition
|
|
*/
|
|
transitionTo: function(name, context) {
|
|
var router = this.router;
|
|
return router.transitionTo.apply(router, arguments);
|
|
},
|
|
|
|
/**
|
|
Perform a synchronous transition into another route without attempting
|
|
to resolve promises, update the URL, or abort any currently active
|
|
asynchronous transitions (i.e. regular transitions caused by
|
|
`transitionTo` or URL changes).
|
|
|
|
This method is handy for performing intermediate transitions on the
|
|
way to a final destination route, and is called internally by the
|
|
default implementations of the `error` and `loading` handlers.
|
|
|
|
@method intermediateTransitionTo
|
|
@param {String} name the name of the route
|
|
@param {...Object} models the model(s) to be used while transitioning
|
|
to the route.
|
|
@since 1.2.0
|
|
*/
|
|
intermediateTransitionTo: function() {
|
|
var router = this.router;
|
|
router.intermediateTransitionTo.apply(router, arguments);
|
|
},
|
|
|
|
/**
|
|
Refresh the model on this route and any child routes, firing the
|
|
`beforeModel`, `model`, and `afterModel` hooks in a similar fashion
|
|
to how routes are entered when transitioning in from other route.
|
|
The current route params (e.g. `article_id`) will be passed in
|
|
to the respective model hooks, and if a different model is returned,
|
|
`setupController` and associated route hooks will re-fire as well.
|
|
|
|
An example usage of this method is re-querying the server for the
|
|
latest information using the same parameters as when the route
|
|
was first entered.
|
|
|
|
Note that this will cause `model` hooks to fire even on routes
|
|
that were provided a model object when the route was initially
|
|
entered.
|
|
|
|
@method refresh
|
|
@return {Transition} the transition object associated with this
|
|
attempted transition
|
|
@since 1.4.0
|
|
*/
|
|
refresh: function() {
|
|
return this.router.router.refresh(this);
|
|
},
|
|
|
|
/**
|
|
Transition into another route while replacing the current URL, if possible.
|
|
This will replace the current history entry instead of adding a new one.
|
|
Beside that, it is identical to `transitionTo` in all other respects. See
|
|
'transitionTo' for additional information regarding multiple models.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
App.Router.map(function() {
|
|
this.route('index');
|
|
this.route('secret');
|
|
});
|
|
|
|
App.SecretRoute = Ember.Route.extend({
|
|
afterModel: function() {
|
|
if (!authorized()){
|
|
this.replaceWith('index');
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
@method replaceWith
|
|
@param {String} name the name of the route or a URL
|
|
@param {...Object} models the model(s) or identifier(s) to be used while
|
|
transitioning to the route.
|
|
@return {Transition} the transition object associated with this
|
|
attempted transition
|
|
*/
|
|
replaceWith: function() {
|
|
var router = this.router;
|
|
return router.replaceWith.apply(router, arguments);
|
|
},
|
|
|
|
/**
|
|
Sends an action to the router, which will delegate it to the currently
|
|
active route hierarchy per the bubbling rules explained under `actions`.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
App.Router.map(function() {
|
|
this.route('index');
|
|
});
|
|
|
|
App.ApplicationRoute = Ember.Route.extend({
|
|
actions: {
|
|
track: function(arg) {
|
|
console.log(arg, 'was clicked');
|
|
}
|
|
}
|
|
});
|
|
|
|
App.IndexRoute = Ember.Route.extend({
|
|
actions: {
|
|
trackIfDebug: function(arg) {
|
|
if (debug) {
|
|
this.send('track', arg);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
@method send
|
|
@param {String} name the name of the action to trigger
|
|
@param {...*} args
|
|
*/
|
|
send: function() {
|
|
if (this.router || !Ember.testing) {
|
|
this.router.send.apply(this.router, arguments);
|
|
} else {
|
|
var name = arguments[0];
|
|
var args = slice.call(arguments, 1);
|
|
var action = this._actions[name];
|
|
if (action) {
|
|
return this._actions[name].apply(this, args);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
This hook is the entry point for router.js
|
|
|
|
@private
|
|
@method setup
|
|
*/
|
|
setup: function(context, transition) {
|
|
var controllerName = this.controllerName || this.routeName;
|
|
var controller = this.controllerFor(controllerName, true);
|
|
|
|
if (!controller) {
|
|
controller = this.generateController(controllerName, context);
|
|
}
|
|
|
|
// Assign the route's controller so that it can more easily be
|
|
// referenced in action handlers
|
|
this.controller = controller;
|
|
|
|
if (this.setupControllers) {
|
|
Ember.deprecate("Ember.Route.setupControllers is deprecated. Please use Ember.Route.setupController(controller, model) instead.");
|
|
this.setupControllers(controller, context);
|
|
} else {
|
|
var states = get(this, '_qp.states');
|
|
if (transition) {
|
|
// Update the model dep values used to calculate cache keys.
|
|
stashParamNames(this.router, transition.state.handlerInfos);
|
|
controller._qpDelegate = states.changingKeys;
|
|
controller._updateCacheParams(transition.params);
|
|
}
|
|
controller._qpDelegate = states.allowOverrides;
|
|
|
|
if (transition) {
|
|
var qpValues = getQueryParamsFor(this, transition.state);
|
|
controller.setProperties(qpValues);
|
|
}
|
|
|
|
this.setupController(controller, context, transition);
|
|
}
|
|
|
|
if (this.renderTemplates) {
|
|
Ember.deprecate("Ember.Route.renderTemplates is deprecated. Please use Ember.Route.renderTemplate(controller, model) instead.");
|
|
this.renderTemplates(context);
|
|
} else {
|
|
this.renderTemplate(controller, context);
|
|
}
|
|
},
|
|
|
|
/**
|
|
This hook is the first of the route entry validation hooks
|
|
called when an attempt is made to transition into a route
|
|
or one of its children. It is called before `model` and
|
|
`afterModel`, and is appropriate for cases when:
|
|
|
|
1) A decision can be made to redirect elsewhere without
|
|
needing to resolve the model first.
|
|
2) Any async operations need to occur first before the
|
|
model is attempted to be resolved.
|
|
|
|
This hook is provided the current `transition` attempt
|
|
as a parameter, which can be used to `.abort()` the transition,
|
|
save it for a later `.retry()`, or retrieve values set
|
|
on it from a previous hook. You can also just call
|
|
`this.transitionTo` to another route to implicitly
|
|
abort the `transition`.
|
|
|
|
You can return a promise from this hook to pause the
|
|
transition until the promise resolves (or rejects). This could
|
|
be useful, for instance, for retrieving async code from
|
|
the server that is required to enter a route.
|
|
|
|
```javascript
|
|
App.PostRoute = Ember.Route.extend({
|
|
beforeModel: function(transition) {
|
|
if (!App.Post) {
|
|
return Ember.$.getScript('/models/post.js');
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
If `App.Post` doesn't exist in the above example,
|
|
`beforeModel` will use jQuery's `getScript`, which
|
|
returns a promise that resolves after the server has
|
|
successfully retrieved and executed the code from the
|
|
server. Note that if an error were to occur, it would
|
|
be passed to the `error` hook on `Ember.Route`, but
|
|
it's also possible to handle errors specific to
|
|
`beforeModel` right from within the hook (to distinguish
|
|
from the shared error handling behavior of the `error`
|
|
hook):
|
|
|
|
```javascript
|
|
App.PostRoute = Ember.Route.extend({
|
|
beforeModel: function(transition) {
|
|
if (!App.Post) {
|
|
var self = this;
|
|
return Ember.$.getScript('post.js').then(null, function(e) {
|
|
self.transitionTo('help');
|
|
|
|
// Note that the above transitionTo will implicitly
|
|
// halt the transition. If you were to return
|
|
// nothing from this promise reject handler,
|
|
// according to promise semantics, that would
|
|
// convert the reject into a resolve and the
|
|
// transition would continue. To propagate the
|
|
// error so that it'd be handled by the `error`
|
|
// hook, you would have to
|
|
return Ember.RSVP.reject(e);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
@method beforeModel
|
|
@param {Transition} transition
|
|
@return {Promise} if the value returned from this hook is
|
|
a promise, the transition will pause until the transition
|
|
resolves. Otherwise, non-promise return values are not
|
|
utilized in any way.
|
|
*/
|
|
beforeModel: K,
|
|
|
|
/**
|
|
This hook is called after this route's model has resolved.
|
|
It follows identical async/promise semantics to `beforeModel`
|
|
but is provided the route's resolved model in addition to
|
|
the `transition`, and is therefore suited to performing
|
|
logic that can only take place after the model has already
|
|
resolved.
|
|
|
|
```javascript
|
|
App.PostsRoute = Ember.Route.extend({
|
|
afterModel: function(posts, transition) {
|
|
if (posts.get('length') === 1) {
|
|
this.transitionTo('post.show', posts.get('firstObject'));
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
Refer to documentation for `beforeModel` for a description
|
|
of transition-pausing semantics when a promise is returned
|
|
from this hook.
|
|
|
|
@method afterModel
|
|
@param {Object} resolvedModel the value returned from `model`,
|
|
or its resolved value if it was a promise
|
|
@param {Transition} transition
|
|
@return {Promise} if the value returned from this hook is
|
|
a promise, the transition will pause until the transition
|
|
resolves. Otherwise, non-promise return values are not
|
|
utilized in any way.
|
|
*/
|
|
afterModel: K,
|
|
|
|
/**
|
|
A hook you can implement to optionally redirect to another route.
|
|
|
|
If you call `this.transitionTo` from inside of this hook, this route
|
|
will not be entered in favor of the other hook.
|
|
|
|
`redirect` and `afterModel` behave very similarly and are
|
|
called almost at the same time, but they have an important
|
|
distinction in the case that, from one of these hooks, a
|
|
redirect into a child route of this route occurs: redirects
|
|
from `afterModel` essentially invalidate the current attempt
|
|
to enter this route, and will result in this route's `beforeModel`,
|
|
`model`, and `afterModel` hooks being fired again within
|
|
the new, redirecting transition. Redirects that occur within
|
|
the `redirect` hook, on the other hand, will _not_ cause
|
|
these hooks to be fired again the second time around; in
|
|
other words, by the time the `redirect` hook has been called,
|
|
both the resolved model and attempted entry into this route
|
|
are considered to be fully validated.
|
|
|
|
@method redirect
|
|
@param {Object} model the model for this route
|
|
@param {Transition} transition the transition object associated with the current transition
|
|
*/
|
|
redirect: K,
|
|
|
|
/**
|
|
Called when the context is changed by router.js.
|
|
|
|
@private
|
|
@method contextDidChange
|
|
*/
|
|
contextDidChange: function() {
|
|
this.currentModel = this.context;
|
|
},
|
|
|
|
/**
|
|
A hook you can implement to convert the URL into the model for
|
|
this route.
|
|
|
|
```javascript
|
|
App.Router.map(function() {
|
|
this.resource('post', { path: '/posts/:post_id' });
|
|
});
|
|
```
|
|
|
|
The model for the `post` route is `store.find('post', params.post_id)`.
|
|
|
|
By default, if your route has a dynamic segment ending in `_id`:
|
|
|
|
* The model class is determined from the segment (`post_id`'s
|
|
class is `App.Post`)
|
|
* The find method is called on the model class with the value of
|
|
the dynamic segment.
|
|
|
|
Note that for routes with dynamic segments, this hook is not always
|
|
executed. If the route is entered through a transition (e.g. when
|
|
using the `link-to` Handlebars helper or the `transitionTo` method
|
|
of routes), and a model context is already provided this hook
|
|
is not called.
|
|
|
|
A model context does not include a primitive string or number,
|
|
which does cause the model hook to be called.
|
|
|
|
Routes without dynamic segments will always execute the model hook.
|
|
|
|
```javascript
|
|
// no dynamic segment, model hook always called
|
|
this.transitionTo('posts');
|
|
|
|
// model passed in, so model hook not called
|
|
thePost = store.find('post', 1);
|
|
this.transitionTo('post', thePost);
|
|
|
|
// integer passed in, model hook is called
|
|
this.transitionTo('post', 1);
|
|
```
|
|
|
|
|
|
This hook follows the asynchronous/promise semantics
|
|
described in the documentation for `beforeModel`. In particular,
|
|
if a promise returned from `model` fails, the error will be
|
|
handled by the `error` hook on `Ember.Route`.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
App.PostRoute = Ember.Route.extend({
|
|
model: function(params) {
|
|
return this.store.find('post', params.post_id);
|
|
}
|
|
});
|
|
```
|
|
|
|
@method model
|
|
@param {Object} params the parameters extracted from the URL
|
|
@param {Transition} transition
|
|
@return {Object|Promise} the model for this route. If
|
|
a promise is returned, the transition will pause until
|
|
the promise resolves, and the resolved value of the promise
|
|
will be used as the model for this route.
|
|
*/
|
|
model: function(params, transition) {
|
|
var match, name, sawParams, value;
|
|
|
|
var queryParams = get(this, '_qp.map');
|
|
|
|
for (var prop in params) {
|
|
if (prop === 'queryParams' || (queryParams && prop in queryParams)) {
|
|
continue;
|
|
}
|
|
|
|
if (match = prop.match(/^(.*)_id$/)) {
|
|
name = match[1];
|
|
value = params[prop];
|
|
}
|
|
sawParams = true;
|
|
}
|
|
|
|
if (!name && sawParams) { return copy(params); }
|
|
else if (!name) {
|
|
if (transition.resolveIndex < 1) { return; }
|
|
|
|
var parentModel = transition.state.handlerInfos[transition.resolveIndex-1].context;
|
|
|
|
return parentModel;
|
|
}
|
|
|
|
return this.findModel(name, value);
|
|
},
|
|
|
|
/**
|
|
@private
|
|
@method deserialize
|
|
@param {Object} params the parameters extracted from the URL
|
|
@param {Transition} transition
|
|
@return {Object|Promise} the model for this route.
|
|
|
|
Router.js hook.
|
|
*/
|
|
deserialize: function(params, transition) {
|
|
return this.model(this.paramsFor(this.routeName), transition);
|
|
},
|
|
|
|
/**
|
|
|
|
@method findModel
|
|
@param {String} type the model type
|
|
@param {Object} value the value passed to find
|
|
*/
|
|
findModel: function(){
|
|
var store = get(this, 'store');
|
|
return store.find.apply(store, arguments);
|
|
},
|
|
|
|
/**
|
|
Store property provides a hook for data persistence libraries to inject themselves.
|
|
|
|
By default, this store property provides the exact same functionality previously
|
|
in the model hook.
|
|
|
|
Currently, the required interface is:
|
|
|
|
`store.find(modelName, findArguments)`
|
|
|
|
@method store
|
|
@param {Object} store
|
|
*/
|
|
store: computed(function(){
|
|
var container = this.container;
|
|
var routeName = this.routeName;
|
|
var namespace = get(this, 'router.namespace');
|
|
|
|
return {
|
|
find: function(name, value) {
|
|
var modelClass = container.lookupFactory('model:' + name);
|
|
|
|
Ember.assert("You used the dynamic segment " + name + "_id in your route " +
|
|
routeName + ", but " + namespace + "." + classify(name) +
|
|
" did not exist and you did not override your route's `model` " +
|
|
"hook.", !!modelClass);
|
|
|
|
if (!modelClass) { return; }
|
|
|
|
Ember.assert(classify(name) + ' has no method `find`.', typeof modelClass.find === 'function');
|
|
|
|
return modelClass.find(value);
|
|
}
|
|
};
|
|
}),
|
|
|
|
/**
|
|
A hook you can implement to convert the route's model into parameters
|
|
for the URL.
|
|
|
|
```javascript
|
|
App.Router.map(function() {
|
|
this.resource('post', { path: '/posts/:post_id' });
|
|
});
|
|
|
|
App.PostRoute = Ember.Route.extend({
|
|
model: function(params) {
|
|
// the server returns `{ id: 12 }`
|
|
return Ember.$.getJSON('/posts/' + params.post_id);
|
|
},
|
|
|
|
serialize: function(model) {
|
|
// this will make the URL `/posts/12`
|
|
return { post_id: model.id };
|
|
}
|
|
});
|
|
```
|
|
|
|
The default `serialize` method will insert the model's `id` into the
|
|
route's dynamic segment (in this case, `:post_id`) if the segment contains '_id'.
|
|
If the route has multiple dynamic segments or does not contain '_id', `serialize`
|
|
will return `Ember.getProperties(model, params)`
|
|
|
|
This method is called when `transitionTo` is called with a context
|
|
in order to populate the URL.
|
|
|
|
@method serialize
|
|
@param {Object} model the route's model
|
|
@param {Array} params an Array of parameter names for the current
|
|
route (in the example, `['post_id']`.
|
|
@return {Object} the serialized parameters
|
|
*/
|
|
serialize: function(model, params) {
|
|
if (params.length < 1) { return; }
|
|
if (!model) { return; }
|
|
|
|
var name = params[0], object = {};
|
|
|
|
if (params.length === 1) {
|
|
if (name in model) {
|
|
object[name] = get(model, name);
|
|
} else if (/_id$/.test(name)) {
|
|
object[name] = get(model, "id");
|
|
}
|
|
} else {
|
|
object = getProperties(model, params);
|
|
}
|
|
|
|
return object;
|
|
},
|
|
|
|
/**
|
|
A hook you can use to setup the controller for the current route.
|
|
|
|
This method is called with the controller for the current route and the
|
|
model supplied by the `model` hook.
|
|
|
|
By default, the `setupController` hook sets the `model` property of
|
|
the controller to the `model`.
|
|
|
|
If you implement the `setupController` hook in your Route, it will
|
|
prevent this default behavior. If you want to preserve that behavior
|
|
when implementing your `setupController` function, make sure to call
|
|
`_super`:
|
|
|
|
```javascript
|
|
App.PhotosRoute = Ember.Route.extend({
|
|
model: function() {
|
|
return this.store.find('photo');
|
|
},
|
|
|
|
setupController: function (controller, model) {
|
|
// Call _super for default behavior
|
|
this._super(controller, model);
|
|
// Implement your custom setup after
|
|
this.controllerFor('application').set('showingPhotos', true);
|
|
}
|
|
});
|
|
```
|
|
|
|
This means that your template will get a proxy for the model as its
|
|
context, and you can act as though the model itself was the context.
|
|
|
|
The provided controller will be one resolved based on the name
|
|
of this route.
|
|
|
|
If no explicit controller is defined, Ember will automatically create
|
|
an appropriate controller for the model.
|
|
|
|
* if the model is an `Ember.Array` (including record arrays from Ember
|
|
Data), the controller is an `Ember.ArrayController`.
|
|
* otherwise, the controller is an `Ember.ObjectController`.
|
|
|
|
As an example, consider the router:
|
|
|
|
```javascript
|
|
App.Router.map(function() {
|
|
this.resource('post', { path: '/posts/:post_id' });
|
|
});
|
|
```
|
|
|
|
For the `post` route, a controller named `App.PostController` would
|
|
be used if it is defined. If it is not defined, an `Ember.ObjectController`
|
|
instance would be used.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
App.PostRoute = Ember.Route.extend({
|
|
setupController: function(controller, model) {
|
|
controller.set('model', model);
|
|
}
|
|
});
|
|
```
|
|
|
|
@method setupController
|
|
@param {Controller} controller instance
|
|
@param {Object} model
|
|
*/
|
|
setupController: function(controller, context, transition) {
|
|
if (controller && (context !== undefined)) {
|
|
set(controller, 'model', context);
|
|
}
|
|
},
|
|
|
|
/**
|
|
Returns the controller for a particular route or name.
|
|
|
|
The controller instance must already have been created, either through entering the
|
|
associated route or using `generateController`.
|
|
|
|
```javascript
|
|
App.PostRoute = Ember.Route.extend({
|
|
setupController: function(controller, post) {
|
|
this._super(controller, post);
|
|
this.controllerFor('posts').set('currentPost', post);
|
|
}
|
|
});
|
|
```
|
|
|
|
@method controllerFor
|
|
@param {String} name the name of the route or controller
|
|
@return {Ember.Controller}
|
|
*/
|
|
controllerFor: function(name, _skipAssert) {
|
|
var container = this.container;
|
|
var route = container.lookup('route:'+name);
|
|
var controller;
|
|
|
|
if (route && route.controllerName) {
|
|
name = route.controllerName;
|
|
}
|
|
|
|
controller = container.lookup('controller:' + name);
|
|
|
|
// NOTE: We're specifically checking that skipAssert is true, because according
|
|
// to the old API the second parameter was model. We do not want people who
|
|
// passed a model to skip the assertion.
|
|
Ember.assert("The controller named '"+name+"' could not be found. Make sure " +
|
|
"that this route exists and has already been entered at least " +
|
|
"once. If you are accessing a controller not associated with a " +
|
|
"route, make sure the controller class is explicitly defined.",
|
|
controller || _skipAssert === true);
|
|
|
|
return controller;
|
|
},
|
|
|
|
/**
|
|
Generates a controller for a route.
|
|
|
|
If the optional model is passed then the controller type is determined automatically,
|
|
e.g., an ArrayController for arrays.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
App.PostRoute = Ember.Route.extend({
|
|
setupController: function(controller, post) {
|
|
this._super(controller, post);
|
|
this.generateController('posts', post);
|
|
}
|
|
});
|
|
```
|
|
|
|
@method generateController
|
|
@param {String} name the name of the controller
|
|
@param {Object} model the model to infer the type of the controller (optional)
|
|
*/
|
|
generateController: function(name, model) {
|
|
var container = this.container;
|
|
|
|
model = model || this.modelFor(name);
|
|
|
|
return generateController(container, name, model);
|
|
},
|
|
|
|
/**
|
|
Returns the model of a parent (or any ancestor) route
|
|
in a route hierarchy. During a transition, all routes
|
|
must resolve a model object, and if a route
|
|
needs access to a parent route's model in order to
|
|
resolve a model (or just reuse the model from a parent),
|
|
it can call `this.modelFor(theNameOfParentRoute)` to
|
|
retrieve it.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
App.Router.map(function() {
|
|
this.resource('post', { path: '/post/:post_id' }, function() {
|
|
this.resource('comments');
|
|
});
|
|
});
|
|
|
|
App.CommentsRoute = Ember.Route.extend({
|
|
afterModel: function() {
|
|
this.set('post', this.modelFor('post'));
|
|
}
|
|
});
|
|
```
|
|
|
|
@method modelFor
|
|
@param {String} name the name of the route
|
|
@return {Object} the model object
|
|
*/
|
|
modelFor: function(name) {
|
|
var route = this.container.lookup('route:' + name);
|
|
var transition = this.router ? this.router.router.activeTransition : null;
|
|
|
|
// If we are mid-transition, we want to try and look up
|
|
// resolved parent contexts on the current transitionEvent.
|
|
if (transition) {
|
|
var modelLookupName = (route && route.routeName) || name;
|
|
if (transition.resolvedModels.hasOwnProperty(modelLookupName)) {
|
|
return transition.resolvedModels[modelLookupName];
|
|
}
|
|
}
|
|
|
|
return route && route.currentModel;
|
|
},
|
|
|
|
/**
|
|
A hook you can use to render the template for the current route.
|
|
|
|
This method is called with the controller for the current route and the
|
|
model supplied by the `model` hook. By default, it renders the route's
|
|
template, configured with the controller for the route.
|
|
|
|
This method can be overridden to set up and render additional or
|
|
alternative templates.
|
|
|
|
```javascript
|
|
App.PostsRoute = Ember.Route.extend({
|
|
renderTemplate: function(controller, model) {
|
|
var favController = this.controllerFor('favoritePost');
|
|
|
|
// Render the `favoritePost` template into
|
|
// the outlet `posts`, and display the `favoritePost`
|
|
// controller.
|
|
this.render('favoritePost', {
|
|
outlet: 'posts',
|
|
controller: favController
|
|
});
|
|
}
|
|
});
|
|
```
|
|
|
|
@method renderTemplate
|
|
@param {Object} controller the route's controller
|
|
@param {Object} model the route's model
|
|
*/
|
|
renderTemplate: function(controller, model) {
|
|
this.render();
|
|
},
|
|
|
|
/**
|
|
`render` is used to render a template into a region of another template
|
|
(indicated by an `{{outlet}}`). `render` is used both during the entry
|
|
phase of routing (via the `renderTemplate` hook) and later in response to
|
|
user interaction.
|
|
|
|
For example, given the following minimal router and templates:
|
|
|
|
```javascript
|
|
Router.map(function() {
|
|
this.resource('photos');
|
|
});
|
|
```
|
|
|
|
```handlebars
|
|
<!-- application.hbs -->
|
|
<div class='something-in-the-app-hbs'>
|
|
{{outlet "anOutletName"}}
|
|
</div>
|
|
```
|
|
|
|
```handlebars
|
|
<!-- photos.hbs -->
|
|
<h1>Photos</h1>
|
|
```
|
|
|
|
You can render `photos.hbs` into the `"anOutletName"` outlet of
|
|
`application.hbs` by calling `render`:
|
|
|
|
```javascript
|
|
// posts route
|
|
Ember.Route.extend({
|
|
renderTemplate: function(){
|
|
this.render('photos', {
|
|
into: 'application',
|
|
outlet: 'anOutletName'
|
|
})
|
|
}
|
|
});
|
|
```
|
|
|
|
`render` additionally allows you to supply which `view`, `controller`, and
|
|
`model` objects should be loaded and associated with the rendered template.
|
|
|
|
|
|
```javascript
|
|
// posts route
|
|
Ember.Route.extend({
|
|
renderTemplate: function(controller, model){
|
|
this.render('posts', { // the template to render, referenced by name
|
|
into: 'application', // the template to render into, referenced by name
|
|
outlet: 'anOutletName', // the outlet inside `options.template` to render into.
|
|
view: 'aViewName', // the view to use for this template, referenced by name
|
|
controller: 'someControllerName', // the controller to use for this template, referenced by name
|
|
model: model // the model to set on `options.controller`.
|
|
})
|
|
}
|
|
});
|
|
```
|
|
|
|
The string values provided for the template name, view, and controller
|
|
will eventually pass through to the resolver for lookup. See
|
|
Ember.Resolver for how these are mapped to JavaScript objects in your
|
|
application.
|
|
|
|
Not all options need to be passed to `render`. Default values will be used
|
|
based on the name of the route specified in the router or the Route's
|
|
`controllerName`, `viewName` and `templateName` properties.
|
|
|
|
For example:
|
|
|
|
```javascript
|
|
// router
|
|
Router.map(function() {
|
|
this.route('index');
|
|
this.resource('post', { path: '/posts/:post_id' });
|
|
});
|
|
```
|
|
|
|
```javascript
|
|
// post route
|
|
PostRoute = App.Route.extend({
|
|
renderTemplate: function() {
|
|
this.render(); // all defaults apply
|
|
}
|
|
});
|
|
```
|
|
|
|
The name of the `PostRoute`, defined by the router, is `post`.
|
|
|
|
The following equivalent default options will be applied when
|
|
the Route calls `render`:
|
|
|
|
```javascript
|
|
//
|
|
this.render('post', { // the template name associated with 'post' Route
|
|
into: 'application', // the parent route to 'post' Route
|
|
outlet: 'main', // {{outlet}} and {{outlet 'main' are synonymous}},
|
|
view: 'post', // the view associated with the 'post' Route
|
|
controller: 'post', // the controller associated with the 'post' Route
|
|
})
|
|
```
|
|
|
|
By default the controller's `model` will be the route's model, so it does not
|
|
need to be passed unless you wish to change which model is being used.
|
|
|
|
@method render
|
|
@param {String} name the name of the template to render
|
|
@param {Object} [options] the options
|
|
@param {String} [options.into] the template to render into,
|
|
referenced by name. Defaults to the parent template
|
|
@param {String} [options.outlet] the outlet inside `options.template` to render into.
|
|
Defaults to 'main'
|
|
@param {String} [options.controller] the controller to use for this template,
|
|
referenced by name. Defaults to the Route's paired controller
|
|
@param {String} [options.model] the model object to set on `options.controller`
|
|
Defaults to the return value of the Route's model hook
|
|
*/
|
|
render: function(_name, options) {
|
|
Ember.assert("The name in the given arguments is undefined", arguments.length > 0 ? !isNone(arguments[0]) : true);
|
|
|
|
var namePassed = typeof _name === 'string' && !!_name;
|
|
var name;
|
|
|
|
if (typeof _name === 'object' && !options) {
|
|
name = this.routeName;
|
|
options = _name;
|
|
} else {
|
|
name = _name;
|
|
}
|
|
|
|
var templateName;
|
|
|
|
if (name) {
|
|
name = name.replace(/\//g, '.');
|
|
templateName = name;
|
|
} else {
|
|
name = this.routeName;
|
|
templateName = this.templateName || name;
|
|
}
|
|
|
|
var renderOptions = buildRenderOptions(this, namePassed, name, options);
|
|
|
|
var LOG_VIEW_LOOKUPS = get(this.router, 'namespace.LOG_VIEW_LOOKUPS');
|
|
var viewName = options && options.view || namePassed && name || this.viewName || name;
|
|
var view, template;
|
|
|
|
var ViewClass = this.container.lookupFactory('view:' + viewName);
|
|
if (ViewClass) {
|
|
view = setupView(ViewClass, renderOptions);
|
|
if (!get(view, 'template')) {
|
|
view.set('template', this.container.lookup('template:' + templateName));
|
|
}
|
|
if (LOG_VIEW_LOOKUPS) {
|
|
Ember.Logger.info("Rendering " + renderOptions.name + " with " + view, { fullName: 'view:' + renderOptions.name });
|
|
}
|
|
} else {
|
|
template = this.container.lookup('template:' + templateName);
|
|
if (!template) {
|
|
Ember.assert("Could not find \"" + name + "\" template or view.", arguments.length === 0 || Ember.isEmpty(arguments[0]));
|
|
if (LOG_VIEW_LOOKUPS) {
|
|
Ember.Logger.info("Could not find \"" + name + "\" template or view. Nothing will be rendered", { fullName: 'template:' + name });
|
|
}
|
|
return;
|
|
}
|
|
var defaultView = renderOptions.into ? 'view:default' : 'view:toplevel';
|
|
ViewClass = this.container.lookupFactory(defaultView);
|
|
view = setupView(ViewClass, renderOptions);
|
|
if (!get(view, 'template')) {
|
|
view.set('template', template);
|
|
}
|
|
if (LOG_VIEW_LOOKUPS) {
|
|
Ember.Logger.info("Rendering " + renderOptions.name + " with default view " + view, { fullName: 'view:' + renderOptions.name });
|
|
}
|
|
}
|
|
|
|
if (renderOptions.outlet === 'main') { this.lastRenderedTemplate = name; }
|
|
appendView(this, view, renderOptions);
|
|
},
|
|
|
|
/**
|
|
Disconnects a view that has been rendered into an outlet.
|
|
|
|
You may pass any or all of the following options to `disconnectOutlet`:
|
|
|
|
* `outlet`: the name of the outlet to clear (default: 'main')
|
|
* `parentView`: the name of the view containing the outlet to clear
|
|
(default: the view rendered by the parent route)
|
|
|
|
Example:
|
|
|
|
```javascript
|
|
App.ApplicationRoute = App.Route.extend({
|
|
actions: {
|
|
showModal: function(evt) {
|
|
this.render(evt.modalName, {
|
|
outlet: 'modal',
|
|
into: 'application'
|
|
});
|
|
},
|
|
hideModal: function(evt) {
|
|
this.disconnectOutlet({
|
|
outlet: 'modal',
|
|
parentView: 'application'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
Alternatively, you can pass the `outlet` name directly as a string.
|
|
|
|
Example:
|
|
|
|
```javascript
|
|
hideModal: function(evt) {
|
|
this.disconnectOutlet('modal');
|
|
}
|
|
```
|
|
|
|
@method disconnectOutlet
|
|
@param {Object|String} options the options hash or outlet name
|
|
*/
|
|
disconnectOutlet: function(options) {
|
|
if (!options || typeof options === "string") {
|
|
var outletName = options;
|
|
options = {};
|
|
options.outlet = outletName;
|
|
}
|
|
options.parentView = options.parentView ? options.parentView.replace(/\//g, '.') : parentTemplate(this);
|
|
options.outlet = options.outlet || 'main';
|
|
|
|
var parentView = this.router._lookupActiveView(options.parentView);
|
|
if (parentView) { parentView.disconnectOutlet(options.outlet); }
|
|
},
|
|
|
|
willDestroy: function() {
|
|
this.teardownViews();
|
|
},
|
|
|
|
/**
|
|
@private
|
|
|
|
@method teardownViews
|
|
*/
|
|
teardownViews: function() {
|
|
// Tear down the top level view
|
|
if (this.teardownTopLevelView) { this.teardownTopLevelView(); }
|
|
|
|
// Tear down any outlets rendered with 'into'
|
|
var teardownOutletViews = this.teardownOutletViews || [];
|
|
forEach(teardownOutletViews, function(teardownOutletView) {
|
|
teardownOutletView();
|
|
});
|
|
|
|
delete this.teardownTopLevelView;
|
|
delete this.teardownOutletViews;
|
|
delete this.lastRenderedTemplate;
|
|
}
|
|
});
|
|
|
|
|
|
// TODO add mixin directly to `Route` class definition above, once this
|
|
// feature is merged:
|
|
Route.reopen(Evented);
|
|
|
|
|
|
var defaultQPMeta = {
|
|
qps: [],
|
|
map: {},
|
|
states: {}
|
|
};
|
|
|
|
function parentRoute(route) {
|
|
var handlerInfo = handlerInfoFor(route, route.router.router.state.handlerInfos, -1);
|
|
return handlerInfo && handlerInfo.handler;
|
|
}
|
|
|
|
function handlerInfoFor(route, handlerInfos, _offset) {
|
|
if (!handlerInfos) { return; }
|
|
|
|
var offset = _offset || 0;
|
|
var current;
|
|
for (var i=0, l=handlerInfos.length; i<l; i++) {
|
|
current = handlerInfos[i].handler;
|
|
if (current === route) { return handlerInfos[i+offset]; }
|
|
}
|
|
}
|
|
|
|
function parentTemplate(route) {
|
|
var parent = parentRoute(route);
|
|
var template;
|
|
|
|
if (!parent) { return; }
|
|
|
|
if (template = parent.lastRenderedTemplate) {
|
|
return template;
|
|
} else {
|
|
return parentTemplate(parent);
|
|
}
|
|
}
|
|
|
|
function buildRenderOptions(route, namePassed, name, options) {
|
|
var controller = options && options.controller;
|
|
|
|
if (!controller) {
|
|
if (namePassed) {
|
|
controller = route.container.lookup('controller:' + name) || route.controllerName || route.routeName;
|
|
} else {
|
|
controller = route.controllerName || route.container.lookup('controller:' + name);
|
|
}
|
|
}
|
|
|
|
if (typeof controller === 'string') {
|
|
var controllerName = controller;
|
|
controller = route.container.lookup('controller:' + controllerName);
|
|
if (!controller) {
|
|
throw new EmberError("You passed `controller: '" + controllerName + "'` into the `render` method, but no such controller could be found.");
|
|
}
|
|
}
|
|
|
|
if (options && options.model) {
|
|
controller.set('model', options.model);
|
|
}
|
|
|
|
var renderOptions = {
|
|
into: options && options.into ? options.into.replace(/\//g, '.') : parentTemplate(route),
|
|
outlet: (options && options.outlet) || 'main',
|
|
name: name,
|
|
controller: controller
|
|
};
|
|
|
|
Ember.assert("An outlet ("+renderOptions.outlet+") was specified but was not found.", renderOptions.outlet === 'main' || renderOptions.into);
|
|
|
|
return renderOptions;
|
|
}
|
|
|
|
function setupView(ViewClass, options) {
|
|
return ViewClass.create({
|
|
_debugTemplateName: options.name,
|
|
renderedName: options.name,
|
|
controller: options.controller
|
|
});
|
|
}
|
|
|
|
function appendView(route, view, options) {
|
|
if (options.into) {
|
|
var parentView = route.router._lookupActiveView(options.into);
|
|
var teardownOutletView = generateOutletTeardown(parentView, options.outlet);
|
|
if (!route.teardownOutletViews) { route.teardownOutletViews = []; }
|
|
replace(route.teardownOutletViews, 0, 0, [teardownOutletView]);
|
|
parentView.connectOutlet(options.outlet, view);
|
|
} else {
|
|
var rootElement = get(route.router, 'namespace.rootElement');
|
|
// tear down view if one is already rendered
|
|
if (route.teardownTopLevelView) {
|
|
route.teardownTopLevelView();
|
|
}
|
|
route.router._connectActiveView(options.name, view);
|
|
route.teardownTopLevelView = generateTopLevelTeardown(view);
|
|
view.appendTo(rootElement);
|
|
}
|
|
}
|
|
|
|
function generateTopLevelTeardown(view) {
|
|
return function() {
|
|
view.destroy();
|
|
};
|
|
}
|
|
|
|
function generateOutletTeardown(parentView, outlet) {
|
|
return function() {
|
|
parentView.disconnectOutlet(outlet);
|
|
};
|
|
}
|
|
|
|
function getFullQueryParams(router, state) {
|
|
if (state.fullQueryParams) { return state.fullQueryParams; }
|
|
|
|
state.fullQueryParams = {};
|
|
merge(state.fullQueryParams, state.queryParams);
|
|
|
|
var targetRouteName = state.handlerInfos[state.handlerInfos.length-1].name;
|
|
router._deserializeQueryParams(targetRouteName, state.fullQueryParams);
|
|
return state.fullQueryParams;
|
|
}
|
|
|
|
function getQueryParamsFor(route, state) {
|
|
state.queryParamsFor = state.queryParamsFor || {};
|
|
var name = route.routeName;
|
|
|
|
if (state.queryParamsFor[name]) { return state.queryParamsFor[name]; }
|
|
|
|
var fullQueryParams = getFullQueryParams(route.router, state);
|
|
|
|
var params = state.queryParamsFor[name] = {};
|
|
|
|
// Copy over all the query params for this route/controller into params hash.
|
|
var qpMeta = get(route, '_qp');
|
|
var qps = qpMeta.qps;
|
|
for (var i = 0, len = qps.length; i < len; ++i) {
|
|
// Put deserialized qp on params hash.
|
|
var qp = qps[i];
|
|
|
|
var qpValueWasPassedIn = (qp.prop in fullQueryParams);
|
|
params[qp.prop] = qpValueWasPassedIn ?
|
|
fullQueryParams[qp.prop] :
|
|
copyDefaultValue(qp.def);
|
|
}
|
|
|
|
return params;
|
|
}
|
|
|
|
function copyDefaultValue(value) {
|
|
if (isArray(value)) {
|
|
return Ember.A(value.slice());
|
|
}
|
|
return value;
|
|
}
|
|
|
|
__exports__["default"] = Route;
|
|
});
|
|
enifed("ember-routing/system/router",
|
|
["ember-metal/core","ember-metal/error","ember-metal/property_get","ember-metal/property_set","ember-metal/properties","ember-metal/computed","ember-metal/merge","ember-metal/run_loop","ember-runtime/system/string","ember-runtime/system/object","ember-runtime/mixins/evented","ember-routing/system/dsl","ember-views/views/view","ember-routing/location/api","ember-views/views/metamorph_view","ember-routing/utils","ember-metal/platform","router","router/transition","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __dependency16__, __dependency17__, __dependency18__, __dependency19__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
// FEATURES, Logger, assert
|
|
var EmberError = __dependency2__["default"];
|
|
var get = __dependency3__.get;
|
|
var set = __dependency4__.set;
|
|
var defineProperty = __dependency5__.defineProperty;
|
|
var computed = __dependency6__.computed;
|
|
var merge = __dependency7__["default"];
|
|
var run = __dependency8__["default"];
|
|
|
|
var fmt = __dependency9__.fmt;
|
|
var EmberObject = __dependency10__["default"];
|
|
var Evented = __dependency11__["default"];
|
|
var EmberRouterDSL = __dependency12__["default"];
|
|
var EmberView = __dependency13__["default"];
|
|
var EmberLocation = __dependency14__["default"];
|
|
var _MetamorphView = __dependency15__["default"];
|
|
var routeArgs = __dependency16__.routeArgs;
|
|
var getActiveTargetName = __dependency16__.getActiveTargetName;
|
|
var stashParamNames = __dependency16__.stashParamNames;
|
|
var create = __dependency17__.create;
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-routing
|
|
*/
|
|
|
|
var Router = __dependency18__["default"];
|
|
|
|
function K() { return this; }
|
|
|
|
var slice = [].slice;
|
|
|
|
/**
|
|
The `Ember.Router` class manages the application state and URLs. Refer to
|
|
the [routing guide](http://emberjs.com/guides/routing/) for documentation.
|
|
|
|
@class Router
|
|
@namespace Ember
|
|
@extends Ember.Object
|
|
@uses Ember.Evented
|
|
*/
|
|
var EmberRouter = EmberObject.extend(Evented, {
|
|
/**
|
|
The `location` property determines the type of URL's that your
|
|
application will use.
|
|
|
|
The following location types are currently available:
|
|
|
|
* `hash`
|
|
* `history`
|
|
* `none`
|
|
|
|
@property location
|
|
@default 'hash'
|
|
@see {Ember.Location}
|
|
*/
|
|
location: 'hash',
|
|
|
|
/**
|
|
Represents the URL of the root of the application, often '/'. This prefix is
|
|
assumed on all routes defined on this router.
|
|
|
|
@property rootURL
|
|
@default '/'
|
|
*/
|
|
rootURL: '/',
|
|
|
|
init: function() {
|
|
this.router = this.constructor.router || this.constructor.map(K);
|
|
this._activeViews = {};
|
|
this._setupLocation();
|
|
this._qpCache = {};
|
|
this._queuedQPChanges = {};
|
|
|
|
if (get(this, 'namespace.LOG_TRANSITIONS_INTERNAL')) {
|
|
this.router.log = Ember.Logger.debug;
|
|
}
|
|
},
|
|
|
|
/**
|
|
Represents the current URL.
|
|
|
|
@method url
|
|
@return {String} The current URL.
|
|
*/
|
|
url: computed(function() {
|
|
return get(this, 'location').getURL();
|
|
}),
|
|
|
|
/**
|
|
Initializes the current router instance and sets up the change handling
|
|
event listeners used by the instances `location` implementation.
|
|
|
|
A property named `initialURL` will be used to determine the initial URL.
|
|
If no value is found `/` will be used.
|
|
|
|
@method startRouting
|
|
@private
|
|
*/
|
|
startRouting: function() {
|
|
this.router = this.router || this.constructor.map(K);
|
|
|
|
var router = this.router;
|
|
var location = get(this, 'location');
|
|
var container = this.container;
|
|
var self = this;
|
|
var initialURL = get(this, 'initialURL');
|
|
var initialTransition;
|
|
|
|
// Allow the Location class to cancel the router setup while it refreshes
|
|
// the page
|
|
if (get(location, 'cancelRouterSetup')) {
|
|
return;
|
|
}
|
|
|
|
this._setupRouter(router, location);
|
|
|
|
container.register('view:default', _MetamorphView);
|
|
container.register('view:toplevel', EmberView.extend());
|
|
|
|
location.onUpdateURL(function(url) {
|
|
self.handleURL(url);
|
|
});
|
|
|
|
if (typeof initialURL === "undefined") {
|
|
initialURL = location.getURL();
|
|
}
|
|
initialTransition = this.handleURL(initialURL);
|
|
if (initialTransition && initialTransition.error) {
|
|
throw initialTransition.error;
|
|
}
|
|
},
|
|
|
|
/**
|
|
Handles updating the paths and notifying any listeners of the URL
|
|
change.
|
|
|
|
Triggers the router level `didTransition` hook.
|
|
|
|
@method didTransition
|
|
@private
|
|
@since 1.2.0
|
|
*/
|
|
didTransition: function(infos) {
|
|
updatePaths(this);
|
|
|
|
this._cancelLoadingEvent();
|
|
|
|
this.notifyPropertyChange('url');
|
|
|
|
// Put this in the runloop so url will be accurate. Seems
|
|
// less surprising than didTransition being out of sync.
|
|
run.once(this, this.trigger, 'didTransition');
|
|
|
|
if (get(this, 'namespace').LOG_TRANSITIONS) {
|
|
Ember.Logger.log("Transitioned into '" + EmberRouter._routePath(infos) + "'");
|
|
}
|
|
},
|
|
|
|
handleURL: function(url) {
|
|
// Until we have an ember-idiomatic way of accessing #hashes, we need to
|
|
// remove it because router.js doesn't know how to handle it.
|
|
url = url.split(/#(.+)?/)[0];
|
|
return this._doURLTransition('handleURL', url);
|
|
},
|
|
|
|
_doURLTransition: function(routerJsMethod, url) {
|
|
var transition = this.router[routerJsMethod](url || '/');
|
|
listenForTransitionErrors(transition);
|
|
return transition;
|
|
},
|
|
|
|
transitionTo: function() {
|
|
var args = slice.call(arguments);
|
|
var queryParams;
|
|
if (resemblesURL(args[0])) {
|
|
return this._doURLTransition('transitionTo', args[0]);
|
|
}
|
|
|
|
var possibleQueryParams = args[args.length-1];
|
|
if (possibleQueryParams && possibleQueryParams.hasOwnProperty('queryParams')) {
|
|
queryParams = args.pop().queryParams;
|
|
} else {
|
|
queryParams = {};
|
|
}
|
|
|
|
var targetRouteName = args.shift();
|
|
return this._doTransition(targetRouteName, args, queryParams);
|
|
},
|
|
|
|
intermediateTransitionTo: function() {
|
|
this.router.intermediateTransitionTo.apply(this.router, arguments);
|
|
|
|
updatePaths(this);
|
|
|
|
var infos = this.router.currentHandlerInfos;
|
|
if (get(this, 'namespace').LOG_TRANSITIONS) {
|
|
Ember.Logger.log("Intermediate-transitioned into '" + EmberRouter._routePath(infos) + "'");
|
|
}
|
|
},
|
|
|
|
replaceWith: function() {
|
|
return this.transitionTo.apply(this, arguments).method('replace');
|
|
},
|
|
|
|
generate: function() {
|
|
var url = this.router.generate.apply(this.router, arguments);
|
|
return this.location.formatURL(url);
|
|
},
|
|
|
|
/**
|
|
Determines if the supplied route is currently active.
|
|
|
|
@method isActive
|
|
@param routeName
|
|
@return {Boolean}
|
|
@private
|
|
*/
|
|
isActive: function(routeName) {
|
|
var router = this.router;
|
|
return router.isActive.apply(router, arguments);
|
|
},
|
|
|
|
/**
|
|
An alternative form of `isActive` that doesn't require
|
|
manual concatenation of the arguments into a single
|
|
array.
|
|
|
|
@method isActiveIntent
|
|
@param routeName
|
|
@param models
|
|
@param queryParams
|
|
@return {Boolean}
|
|
@private
|
|
@since 1.7.0
|
|
*/
|
|
isActiveIntent: function(routeName, models, queryParams) {
|
|
var router = this.router;
|
|
return router.isActive.apply(router, arguments);
|
|
},
|
|
|
|
send: function(name, context) {
|
|
this.router.trigger.apply(this.router, arguments);
|
|
},
|
|
|
|
/**
|
|
Does this router instance have the given route.
|
|
|
|
@method hasRoute
|
|
@return {Boolean}
|
|
@private
|
|
*/
|
|
hasRoute: function(route) {
|
|
return this.router.hasRoute(route);
|
|
},
|
|
|
|
/**
|
|
Resets the state of the router by clearing the current route
|
|
handlers and deactivating them.
|
|
|
|
@private
|
|
@method reset
|
|
*/
|
|
reset: function() {
|
|
this.router.reset();
|
|
},
|
|
|
|
_lookupActiveView: function(templateName) {
|
|
var active = this._activeViews[templateName];
|
|
return active && active[0];
|
|
},
|
|
|
|
_connectActiveView: function(templateName, view) {
|
|
var existing = this._activeViews[templateName];
|
|
|
|
if (existing) {
|
|
existing[0].off('willDestroyElement', this, existing[1]);
|
|
}
|
|
|
|
function disconnectActiveView() {
|
|
delete this._activeViews[templateName];
|
|
}
|
|
|
|
this._activeViews[templateName] = [view, disconnectActiveView];
|
|
view.one('willDestroyElement', this, disconnectActiveView);
|
|
},
|
|
|
|
_setupLocation: function() {
|
|
var location = get(this, 'location');
|
|
var rootURL = get(this, 'rootURL');
|
|
|
|
if (rootURL && this.container && !this.container.has('-location-setting:root-url')) {
|
|
this.container.register('-location-setting:root-url', rootURL, {
|
|
instantiate: false
|
|
});
|
|
}
|
|
|
|
if ('string' === typeof location && this.container) {
|
|
var resolvedLocation = this.container.lookup('location:' + location);
|
|
|
|
if ('undefined' !== typeof resolvedLocation) {
|
|
location = set(this, 'location', resolvedLocation);
|
|
} else {
|
|
// Allow for deprecated registration of custom location API's
|
|
var options = {
|
|
implementation: location
|
|
};
|
|
|
|
location = set(this, 'location', EmberLocation.create(options));
|
|
}
|
|
}
|
|
|
|
if (location !== null && typeof location === 'object') {
|
|
if (rootURL && typeof rootURL === 'string') {
|
|
location.rootURL = rootURL;
|
|
}
|
|
|
|
// ensure that initState is called AFTER the rootURL is set on
|
|
// the location instance
|
|
if (typeof location.initState === 'function') {
|
|
location.initState();
|
|
}
|
|
}
|
|
},
|
|
|
|
_getHandlerFunction: function() {
|
|
var seen = create(null);
|
|
var container = this.container;
|
|
var DefaultRoute = container.lookupFactory('route:basic');
|
|
var self = this;
|
|
|
|
return function(name) {
|
|
var routeName = 'route:' + name;
|
|
var handler = container.lookup(routeName);
|
|
|
|
if (seen[name]) {
|
|
return handler;
|
|
}
|
|
|
|
seen[name] = true;
|
|
|
|
if (!handler) {
|
|
container.register(routeName, DefaultRoute.extend());
|
|
handler = container.lookup(routeName);
|
|
|
|
if (get(self, 'namespace.LOG_ACTIVE_GENERATION')) {
|
|
Ember.Logger.info("generated -> " + routeName, { fullName: routeName });
|
|
}
|
|
}
|
|
|
|
handler.routeName = name;
|
|
return handler;
|
|
};
|
|
},
|
|
|
|
_setupRouter: function(router, location) {
|
|
var lastURL, emberRouter = this;
|
|
|
|
router.getHandler = this._getHandlerFunction();
|
|
|
|
var doUpdateURL = function() {
|
|
location.setURL(lastURL);
|
|
};
|
|
|
|
router.updateURL = function(path) {
|
|
lastURL = path;
|
|
run.once(doUpdateURL);
|
|
};
|
|
|
|
if (location.replaceURL) {
|
|
var doReplaceURL = function() {
|
|
location.replaceURL(lastURL);
|
|
};
|
|
|
|
router.replaceURL = function(path) {
|
|
lastURL = path;
|
|
run.once(doReplaceURL);
|
|
};
|
|
}
|
|
|
|
router.didTransition = function(infos) {
|
|
emberRouter.didTransition(infos);
|
|
};
|
|
},
|
|
|
|
_serializeQueryParams: function(targetRouteName, queryParams) {
|
|
var groupedByUrlKey = {};
|
|
|
|
forEachQueryParam(this, targetRouteName, queryParams, function(key, value, qp) {
|
|
var urlKey = qp.urlKey;
|
|
if (!groupedByUrlKey[urlKey]) {
|
|
groupedByUrlKey[urlKey] = [];
|
|
}
|
|
groupedByUrlKey[urlKey].push({
|
|
qp: qp,
|
|
value: value
|
|
});
|
|
delete queryParams[key];
|
|
});
|
|
|
|
for (var key in groupedByUrlKey) {
|
|
var qps = groupedByUrlKey[key];
|
|
Ember.assert(fmt("You're not allowed to have more than one controller " +
|
|
"property map to the same query param key, but both " +
|
|
"`%@` and `%@` map to `%@`. You can fix this by mapping " +
|
|
"one of the controller properties to a different query " +
|
|
"param key via the `as` config option, e.g. `%@: { as: 'other-%@' }`",
|
|
[qps[0].qp.fprop, qps[1] ? qps[1].qp.fprop : "", qps[0].qp.urlKey, qps[0].qp.prop, qps[0].qp.prop]), qps.length <= 1);
|
|
var qp = qps[0].qp;
|
|
queryParams[qp.urlKey] = qp.route.serializeQueryParam(qps[0].value, qp.urlKey, qp.type);
|
|
}
|
|
},
|
|
|
|
_deserializeQueryParams: function(targetRouteName, queryParams) {
|
|
forEachQueryParam(this, targetRouteName, queryParams, function(key, value, qp) {
|
|
delete queryParams[key];
|
|
queryParams[qp.prop] = qp.route.deserializeQueryParam(value, qp.urlKey, qp.type);
|
|
});
|
|
},
|
|
|
|
_pruneDefaultQueryParamValues: function(targetRouteName, queryParams) {
|
|
var qps = this._queryParamsFor(targetRouteName);
|
|
for (var key in queryParams) {
|
|
var qp = qps.map[key];
|
|
if (qp && qp.sdef === queryParams[key]) {
|
|
delete queryParams[key];
|
|
}
|
|
}
|
|
},
|
|
|
|
_doTransition: function(_targetRouteName, models, _queryParams) {
|
|
var targetRouteName = _targetRouteName || getActiveTargetName(this.router);
|
|
Ember.assert("The route " + targetRouteName + " was not found", targetRouteName && this.router.hasRoute(targetRouteName));
|
|
|
|
var queryParams = {};
|
|
merge(queryParams, _queryParams);
|
|
this._prepareQueryParams(targetRouteName, models, queryParams);
|
|
|
|
var transitionArgs = routeArgs(targetRouteName, models, queryParams);
|
|
var transitionPromise = this.router.transitionTo.apply(this.router, transitionArgs);
|
|
|
|
listenForTransitionErrors(transitionPromise);
|
|
|
|
return transitionPromise;
|
|
},
|
|
|
|
_prepareQueryParams: function(targetRouteName, models, queryParams) {
|
|
this._hydrateUnsuppliedQueryParams(targetRouteName, models, queryParams);
|
|
this._serializeQueryParams(targetRouteName, queryParams);
|
|
this._pruneDefaultQueryParamValues(targetRouteName, queryParams);
|
|
},
|
|
|
|
/**
|
|
Returns a merged query params meta object for a given route.
|
|
Useful for asking a route what its known query params are.
|
|
*/
|
|
_queryParamsFor: function(leafRouteName) {
|
|
if (this._qpCache[leafRouteName]) {
|
|
return this._qpCache[leafRouteName];
|
|
}
|
|
|
|
var map = {}, qps = [];
|
|
this._qpCache[leafRouteName] = {
|
|
map: map,
|
|
qps: qps
|
|
};
|
|
|
|
var routerjs = this.router;
|
|
var recogHandlerInfos = routerjs.recognizer.handlersFor(leafRouteName);
|
|
|
|
for (var i = 0, len = recogHandlerInfos.length; i < len; ++i) {
|
|
var recogHandler = recogHandlerInfos[i];
|
|
var route = routerjs.getHandler(recogHandler.handler);
|
|
var qpMeta = get(route, '_qp');
|
|
|
|
if (!qpMeta) { continue; }
|
|
|
|
merge(map, qpMeta.map);
|
|
qps.push.apply(qps, qpMeta.qps);
|
|
}
|
|
|
|
return {
|
|
qps: qps,
|
|
map: map
|
|
};
|
|
},
|
|
|
|
/*
|
|
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
|
|
});
|
|
},
|
|
*/
|
|
|
|
_hydrateUnsuppliedQueryParams: function(leafRouteName, contexts, queryParams) {
|
|
var state = calculatePostTransitionState(this, leafRouteName, contexts);
|
|
var handlerInfos = state.handlerInfos;
|
|
var appCache = this._bucketCache;
|
|
|
|
stashParamNames(this, handlerInfos);
|
|
|
|
for (var i = 0, len = handlerInfos.length; i < len; ++i) {
|
|
var route = handlerInfos[i].handler;
|
|
var qpMeta = get(route, '_qp');
|
|
|
|
for (var j = 0, qpLen = qpMeta.qps.length; j < qpLen; ++j) {
|
|
var qp = qpMeta.qps[j];
|
|
var presentProp = qp.prop in queryParams && qp.prop ||
|
|
qp.fprop in queryParams && qp.fprop;
|
|
|
|
if (presentProp) {
|
|
if (presentProp !== qp.fprop) {
|
|
queryParams[qp.fprop] = queryParams[presentProp];
|
|
delete queryParams[presentProp];
|
|
}
|
|
} else {
|
|
var controllerProto = qp.cProto;
|
|
var cacheMeta = get(controllerProto, '_cacheMeta');
|
|
|
|
var cacheKey = controllerProto._calculateCacheKey(qp.ctrl, cacheMeta[qp.prop].parts, state.params);
|
|
queryParams[qp.fprop] = appCache.lookup(cacheKey, qp.prop, qp.def);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
_scheduleLoadingEvent: function(transition, originRoute) {
|
|
this._cancelLoadingEvent();
|
|
this._loadingStateTimer = run.scheduleOnce('routerTransitions', this, '_fireLoadingEvent', transition, originRoute);
|
|
},
|
|
|
|
_fireLoadingEvent: function(transition, originRoute) {
|
|
if (!this.router.activeTransition) {
|
|
// Don't fire an event if we've since moved on from
|
|
// the transition that put us in a loading state.
|
|
return;
|
|
}
|
|
|
|
transition.trigger(true, 'loading', transition, originRoute);
|
|
},
|
|
|
|
_cancelLoadingEvent: function () {
|
|
if (this._loadingStateTimer) {
|
|
run.cancel(this._loadingStateTimer);
|
|
}
|
|
this._loadingStateTimer = null;
|
|
}
|
|
});
|
|
|
|
/*
|
|
Helper function for iterating root-ward, starting
|
|
from (but not including) the provided `originRoute`.
|
|
|
|
Returns true if the last callback fired requested
|
|
to bubble upward.
|
|
|
|
@private
|
|
*/
|
|
function forEachRouteAbove(originRoute, transition, callback) {
|
|
var handlerInfos = transition.state.handlerInfos;
|
|
var originRouteFound = false;
|
|
var handlerInfo, route;
|
|
|
|
for (var i = handlerInfos.length - 1; i >= 0; --i) {
|
|
handlerInfo = handlerInfos[i];
|
|
route = handlerInfo.handler;
|
|
|
|
if (!originRouteFound) {
|
|
if (originRoute === route) {
|
|
originRouteFound = true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (callback(route, handlerInfos[i + 1].handler) !== true) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// These get invoked when an action bubbles above ApplicationRoute
|
|
// and are not meant to be overridable.
|
|
var defaultActionHandlers = {
|
|
|
|
willResolveModel: function(transition, originRoute) {
|
|
originRoute.router._scheduleLoadingEvent(transition, originRoute);
|
|
},
|
|
|
|
error: function(error, transition, originRoute) {
|
|
// Attempt to find an appropriate error substate to enter.
|
|
var router = originRoute.router;
|
|
|
|
var tryTopLevel = forEachRouteAbove(originRoute, transition, function(route, childRoute) {
|
|
var childErrorRouteName = findChildRouteName(route, childRoute, 'error');
|
|
if (childErrorRouteName) {
|
|
router.intermediateTransitionTo(childErrorRouteName, error);
|
|
return;
|
|
}
|
|
return true;
|
|
});
|
|
|
|
if (tryTopLevel) {
|
|
// Check for top-level error state to enter.
|
|
if (routeHasBeenDefined(originRoute.router, 'application_error')) {
|
|
router.intermediateTransitionTo('application_error', error);
|
|
return;
|
|
}
|
|
}
|
|
|
|
logError(error, 'Error while processing route: ' + transition.targetName);
|
|
},
|
|
|
|
loading: function(transition, originRoute) {
|
|
// Attempt to find an appropriate loading substate to enter.
|
|
var router = originRoute.router;
|
|
|
|
var tryTopLevel = forEachRouteAbove(originRoute, transition, function(route, childRoute) {
|
|
var childLoadingRouteName = findChildRouteName(route, childRoute, 'loading');
|
|
|
|
if (childLoadingRouteName) {
|
|
router.intermediateTransitionTo(childLoadingRouteName);
|
|
return;
|
|
}
|
|
|
|
// Don't bubble above pivot route.
|
|
if (transition.pivotHandler !== route) {
|
|
return true;
|
|
}
|
|
});
|
|
|
|
if (tryTopLevel) {
|
|
// Check for top-level loading state to enter.
|
|
if (routeHasBeenDefined(originRoute.router, 'application_loading')) {
|
|
router.intermediateTransitionTo('application_loading');
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
function logError(error, initialMessage) {
|
|
var errorArgs = [];
|
|
|
|
if (initialMessage) { errorArgs.push(initialMessage); }
|
|
|
|
if (error) {
|
|
if (error.message) { errorArgs.push(error.message); }
|
|
if (error.stack) { errorArgs.push(error.stack); }
|
|
|
|
if (typeof error === "string") { errorArgs.push(error); }
|
|
}
|
|
|
|
Ember.Logger.error.apply(this, errorArgs);
|
|
}
|
|
|
|
function findChildRouteName(parentRoute, originatingChildRoute, name) {
|
|
var router = parentRoute.router;
|
|
var childName;
|
|
var targetChildRouteName = originatingChildRoute.routeName.split('.').pop();
|
|
var namespace = parentRoute.routeName === 'application' ? '' : parentRoute.routeName + '.';
|
|
|
|
|
|
// Second, try general loading state, e.g. 'loading'
|
|
childName = namespace + name;
|
|
if (routeHasBeenDefined(router, childName)) {
|
|
return childName;
|
|
}
|
|
}
|
|
|
|
function routeHasBeenDefined(router, name) {
|
|
var container = router.container;
|
|
return router.hasRoute(name) &&
|
|
(container.has('template:' + name) || container.has('route:' + name));
|
|
}
|
|
|
|
function triggerEvent(handlerInfos, ignoreFailure, args) {
|
|
var name = args.shift();
|
|
|
|
if (!handlerInfos) {
|
|
if (ignoreFailure) { return; }
|
|
throw new EmberError("Can't trigger action '" + name + "' because your app hasn't finished transitioning into its first route. To trigger an action on destination routes during a transition, you can call `.send()` on the `Transition` object passed to the `model/beforeModel/afterModel` hooks.");
|
|
}
|
|
|
|
var eventWasHandled = false;
|
|
var handlerInfo, handler;
|
|
|
|
for (var i = handlerInfos.length - 1; i >= 0; i--) {
|
|
handlerInfo = handlerInfos[i];
|
|
handler = handlerInfo.handler;
|
|
|
|
if (handler._actions && handler._actions[name]) {
|
|
if (handler._actions[name].apply(handler, args) === true) {
|
|
eventWasHandled = true;
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (defaultActionHandlers[name]) {
|
|
defaultActionHandlers[name].apply(null, args);
|
|
return;
|
|
}
|
|
|
|
if (!eventWasHandled && !ignoreFailure) {
|
|
throw new EmberError("Nothing handled the action '" + name + "'. If you did handle the action, this error can be caused by returning true from an action handler in a controller, causing the action to bubble.");
|
|
}
|
|
}
|
|
|
|
function calculatePostTransitionState(emberRouter, leafRouteName, contexts) {
|
|
var routerjs = emberRouter.router;
|
|
var state = routerjs.applyIntent(leafRouteName, contexts);
|
|
var handlerInfos = state.handlerInfos;
|
|
var params = state.params;
|
|
|
|
for (var i = 0, len = handlerInfos.length; i < len; ++i) {
|
|
var handlerInfo = handlerInfos[i];
|
|
if (!handlerInfo.isResolved) {
|
|
handlerInfo = handlerInfo.becomeResolved(null, handlerInfo.context);
|
|
}
|
|
params[handlerInfo.name] = handlerInfo.params;
|
|
}
|
|
return state;
|
|
}
|
|
|
|
function updatePaths(router) {
|
|
var appController = router.container.lookup('controller:application');
|
|
|
|
if (!appController) {
|
|
// appController might not exist when top-level loading/error
|
|
// substates have been entered since ApplicationRoute hasn't
|
|
// actually been entered at that point.
|
|
return;
|
|
}
|
|
|
|
var infos = router.router.currentHandlerInfos;
|
|
var path = EmberRouter._routePath(infos);
|
|
|
|
if (!('currentPath' in appController)) {
|
|
defineProperty(appController, 'currentPath');
|
|
}
|
|
|
|
set(appController, 'currentPath', path);
|
|
|
|
if (!('currentRouteName' in appController)) {
|
|
defineProperty(appController, 'currentRouteName');
|
|
}
|
|
|
|
set(appController, 'currentRouteName', infos[infos.length - 1].name);
|
|
}
|
|
|
|
EmberRouter.reopenClass({
|
|
router: null,
|
|
|
|
/**
|
|
The `Router.map` function allows you to define mappings from URLs to routes
|
|
and resources in your application. These mappings are defined within the
|
|
supplied callback function using `this.resource` and `this.route`.
|
|
|
|
```javascript
|
|
App.Router.map(function({
|
|
this.route('about');
|
|
this.resource('article');
|
|
}));
|
|
```
|
|
|
|
For more detailed examples please see
|
|
[the guides](http://emberjs.com/guides/routing/defining-your-routes/).
|
|
|
|
@method map
|
|
@param callback
|
|
*/
|
|
map: function(callback) {
|
|
var router = this.router;
|
|
if (!router) {
|
|
router = new Router();
|
|
|
|
|
|
router._triggerWillChangeContext = K;
|
|
router._triggerWillLeave = K;
|
|
|
|
|
|
router.callbacks = [];
|
|
router.triggerEvent = triggerEvent;
|
|
this.reopenClass({ router: router });
|
|
}
|
|
|
|
var dsl = EmberRouterDSL.map(function() {
|
|
this.resource('application', { path: "/" }, function() {
|
|
for (var i=0; i < router.callbacks.length; i++) {
|
|
router.callbacks[i].call(this);
|
|
}
|
|
callback.call(this);
|
|
});
|
|
});
|
|
|
|
router.callbacks.push(callback);
|
|
router.map(dsl.generate());
|
|
return router;
|
|
},
|
|
|
|
_routePath: function(handlerInfos) {
|
|
var path = [];
|
|
|
|
// We have to handle coalescing resource names that
|
|
// are prefixed with their parent's names, e.g.
|
|
// ['foo', 'foo.bar.baz'] => 'foo.bar.baz', not 'foo.foo.bar.baz'
|
|
|
|
function intersectionMatches(a1, a2) {
|
|
for (var i = 0, len = a1.length; i < len; ++i) {
|
|
if (a1[i] !== a2[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
var name, nameParts, oldNameParts;
|
|
for (var i=1, l=handlerInfos.length; i<l; i++) {
|
|
name = handlerInfos[i].name;
|
|
nameParts = name.split(".");
|
|
oldNameParts = slice.call(path);
|
|
|
|
while (oldNameParts.length) {
|
|
if (intersectionMatches(oldNameParts, nameParts)) {
|
|
break;
|
|
}
|
|
oldNameParts.shift();
|
|
}
|
|
|
|
path.push.apply(path, nameParts.slice(oldNameParts.length));
|
|
}
|
|
|
|
return path.join(".");
|
|
}
|
|
});
|
|
|
|
function listenForTransitionErrors(transition) {
|
|
transition.then(null, function(error) {
|
|
if (!error || !error.name) { return; }
|
|
|
|
Ember.assert("The URL '" + error.message + "' did not match any routes in your application", error.name !== "UnrecognizedURLError");
|
|
|
|
return error;
|
|
}, 'Ember: Process errors from Router');
|
|
}
|
|
|
|
function resemblesURL(str) {
|
|
return typeof str === 'string' && ( str === '' || str.charAt(0) === '/');
|
|
}
|
|
|
|
function forEachQueryParam(router, targetRouteName, queryParams, callback) {
|
|
var qpCache = router._queryParamsFor(targetRouteName);
|
|
|
|
for (var key in queryParams) {
|
|
if (!queryParams.hasOwnProperty(key)) { continue; }
|
|
var value = queryParams[key];
|
|
var qp = qpCache.map[key];
|
|
|
|
if (qp) {
|
|
callback(key, value, qp);
|
|
}
|
|
}
|
|
}
|
|
|
|
__exports__["default"] = EmberRouter;
|
|
});
|
|
enifed("ember-routing/utils",
|
|
["ember-metal/utils","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var typeOf = __dependency1__.typeOf;
|
|
|
|
function routeArgs(targetRouteName, models, queryParams) {
|
|
var args = [];
|
|
if (typeOf(targetRouteName) === 'string') {
|
|
args.push('' + targetRouteName);
|
|
}
|
|
args.push.apply(args, models);
|
|
args.push({ queryParams: queryParams });
|
|
return args;
|
|
}
|
|
|
|
__exports__.routeArgs = routeArgs;function getActiveTargetName(router) {
|
|
var handlerInfos = router.activeTransition ?
|
|
router.activeTransition.state.handlerInfos :
|
|
router.state.handlerInfos;
|
|
return handlerInfos[handlerInfos.length - 1].name;
|
|
}
|
|
|
|
__exports__.getActiveTargetName = getActiveTargetName;function stashParamNames(router, handlerInfos) {
|
|
if (handlerInfos._namesStashed) { return; }
|
|
|
|
// This helper exists because router.js/route-recognizer.js awkwardly
|
|
// keeps separate a handlerInfo's list of parameter names depending
|
|
// on whether a URL transition or named transition is happening.
|
|
// Hopefully we can remove this in the future.
|
|
var targetRouteName = handlerInfos[handlerInfos.length-1].name;
|
|
var recogHandlers = router.router.recognizer.handlersFor(targetRouteName);
|
|
var dynamicParent = null;
|
|
|
|
for (var i = 0, len = handlerInfos.length; i < len; ++i) {
|
|
var handlerInfo = handlerInfos[i];
|
|
var names = recogHandlers[i].names;
|
|
|
|
if (names.length) {
|
|
dynamicParent = handlerInfo;
|
|
}
|
|
|
|
handlerInfo._names = names;
|
|
|
|
var route = handlerInfo.handler;
|
|
route._stashNames(handlerInfo, dynamicParent);
|
|
}
|
|
|
|
handlerInfos._namesStashed = true;
|
|
}
|
|
|
|
__exports__.stashParamNames = stashParamNames;
|
|
});
|
|
enifed("ember-runtime",
|
|
["ember-metal","ember-runtime/core","ember-runtime/compare","ember-runtime/copy","ember-runtime/inject","ember-runtime/system/namespace","ember-runtime/system/object","ember-runtime/system/tracked_array","ember-runtime/system/subarray","ember-runtime/system/container","ember-runtime/system/array_proxy","ember-runtime/system/object_proxy","ember-runtime/system/core_object","ember-runtime/system/each_proxy","ember-runtime/system/native_array","ember-runtime/system/set","ember-runtime/system/string","ember-runtime/system/deferred","ember-runtime/system/lazy_load","ember-runtime/mixins/array","ember-runtime/mixins/comparable","ember-runtime/mixins/copyable","ember-runtime/mixins/enumerable","ember-runtime/mixins/freezable","ember-runtime/mixins/-proxy","ember-runtime/mixins/observable","ember-runtime/mixins/action_handler","ember-runtime/mixins/deferred","ember-runtime/mixins/mutable_enumerable","ember-runtime/mixins/mutable_array","ember-runtime/mixins/target_action_support","ember-runtime/mixins/evented","ember-runtime/mixins/promise_proxy","ember-runtime/mixins/sortable","ember-runtime/computed/array_computed","ember-runtime/computed/reduce_computed","ember-runtime/computed/reduce_computed_macros","ember-runtime/controllers/array_controller","ember-runtime/controllers/object_controller","ember-runtime/controllers/controller","ember-runtime/mixins/controller","ember-runtime/system/service","ember-runtime/ext/rsvp","ember-runtime/ext/string","ember-runtime/ext/function","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __dependency16__, __dependency17__, __dependency18__, __dependency19__, __dependency20__, __dependency21__, __dependency22__, __dependency23__, __dependency24__, __dependency25__, __dependency26__, __dependency27__, __dependency28__, __dependency29__, __dependency30__, __dependency31__, __dependency32__, __dependency33__, __dependency34__, __dependency35__, __dependency36__, __dependency37__, __dependency38__, __dependency39__, __dependency40__, __dependency41__, __dependency42__, __dependency43__, __dependency44__, __dependency45__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
Ember Runtime
|
|
|
|
@module ember
|
|
@submodule ember-runtime
|
|
@requires ember-metal
|
|
*/
|
|
|
|
// BEGIN IMPORTS
|
|
var Ember = __dependency1__["default"];
|
|
var isEqual = __dependency2__.isEqual;
|
|
var compare = __dependency3__["default"];
|
|
var copy = __dependency4__["default"];
|
|
var inject = __dependency5__["default"];
|
|
|
|
var Namespace = __dependency6__["default"];
|
|
var EmberObject = __dependency7__["default"];
|
|
var TrackedArray = __dependency8__["default"];
|
|
var SubArray = __dependency9__["default"];
|
|
var Container = __dependency10__["default"];
|
|
var ArrayProxy = __dependency11__["default"];
|
|
var ObjectProxy = __dependency12__["default"];
|
|
var CoreObject = __dependency13__["default"];
|
|
var EachArray = __dependency14__.EachArray;
|
|
var EachProxy = __dependency14__.EachProxy;
|
|
|
|
var NativeArray = __dependency15__["default"];
|
|
var Set = __dependency16__["default"];
|
|
var EmberStringUtils = __dependency17__["default"];
|
|
var Deferred = __dependency18__["default"];
|
|
var onLoad = __dependency19__.onLoad;
|
|
var runLoadHooks = __dependency19__.runLoadHooks;
|
|
|
|
var EmberArray = __dependency20__["default"];
|
|
var Comparable = __dependency21__["default"];
|
|
var Copyable = __dependency22__["default"];
|
|
var Enumerable = __dependency23__["default"];
|
|
var Freezable = __dependency24__.Freezable;
|
|
var FROZEN_ERROR = __dependency24__.FROZEN_ERROR;
|
|
var _ProxyMixin = __dependency25__["default"];
|
|
|
|
var Observable = __dependency26__["default"];
|
|
var ActionHandler = __dependency27__["default"];
|
|
var DeferredMixin = __dependency28__["default"];
|
|
var MutableEnumerable = __dependency29__["default"];
|
|
var MutableArray = __dependency30__["default"];
|
|
var TargetActionSupport = __dependency31__["default"];
|
|
var Evented = __dependency32__["default"];
|
|
var PromiseProxyMixin = __dependency33__["default"];
|
|
var SortableMixin = __dependency34__["default"];
|
|
var arrayComputed = __dependency35__.arrayComputed;
|
|
var ArrayComputedProperty = __dependency35__.ArrayComputedProperty;
|
|
|
|
var reduceComputed = __dependency36__.reduceComputed;
|
|
var ReduceComputedProperty = __dependency36__.ReduceComputedProperty;
|
|
|
|
var sum = __dependency37__.sum;
|
|
var min = __dependency37__.min;
|
|
var max = __dependency37__.max;
|
|
var map = __dependency37__.map;
|
|
var sort = __dependency37__.sort;
|
|
var setDiff = __dependency37__.setDiff;
|
|
var mapBy = __dependency37__.mapBy;
|
|
var mapProperty = __dependency37__.mapProperty;
|
|
var filter = __dependency37__.filter;
|
|
var filterBy = __dependency37__.filterBy;
|
|
var filterProperty = __dependency37__.filterProperty;
|
|
var uniq = __dependency37__.uniq;
|
|
var union = __dependency37__.union;
|
|
var intersect = __dependency37__.intersect;
|
|
|
|
var ArrayController = __dependency38__["default"];
|
|
var ObjectController = __dependency39__["default"];
|
|
var Controller = __dependency40__["default"];
|
|
var ControllerMixin = __dependency41__["default"];
|
|
|
|
var Service = __dependency42__["default"];
|
|
|
|
var RSVP = __dependency43__["default"];
|
|
// just for side effect of extending Ember.RSVP
|
|
// just for side effect of extending String.prototype
|
|
// just for side effect of extending Function.prototype
|
|
// END IMPORTS
|
|
|
|
// BEGIN EXPORTS
|
|
Ember.compare = compare;
|
|
Ember.copy = copy;
|
|
Ember.isEqual = isEqual;
|
|
|
|
|
|
Ember.inject = inject;
|
|
|
|
|
|
Ember.Array = EmberArray;
|
|
|
|
Ember.Comparable = Comparable;
|
|
Ember.Copyable = Copyable;
|
|
|
|
Ember.SortableMixin = SortableMixin;
|
|
|
|
Ember.Freezable = Freezable;
|
|
Ember.FROZEN_ERROR = FROZEN_ERROR;
|
|
|
|
Ember.DeferredMixin = DeferredMixin;
|
|
|
|
Ember.MutableEnumerable = MutableEnumerable;
|
|
Ember.MutableArray = MutableArray;
|
|
|
|
Ember.TargetActionSupport = TargetActionSupport;
|
|
Ember.Evented = Evented;
|
|
|
|
Ember.PromiseProxyMixin = PromiseProxyMixin;
|
|
|
|
Ember.Observable = Observable;
|
|
|
|
Ember.arrayComputed = arrayComputed;
|
|
Ember.ArrayComputedProperty = ArrayComputedProperty;
|
|
Ember.reduceComputed = reduceComputed;
|
|
Ember.ReduceComputedProperty = ReduceComputedProperty;
|
|
|
|
// ES6TODO: this seems a less than ideal way/place to add properties to Ember.computed
|
|
var EmComputed = Ember.computed;
|
|
|
|
EmComputed.sum = sum;
|
|
EmComputed.min = min;
|
|
EmComputed.max = max;
|
|
EmComputed.map = map;
|
|
EmComputed.sort = sort;
|
|
EmComputed.setDiff = setDiff;
|
|
EmComputed.mapBy = mapBy;
|
|
EmComputed.mapProperty = mapProperty;
|
|
EmComputed.filter = filter;
|
|
EmComputed.filterBy = filterBy;
|
|
EmComputed.filterProperty = filterProperty;
|
|
EmComputed.uniq = uniq;
|
|
EmComputed.union = union;
|
|
EmComputed.intersect = intersect;
|
|
|
|
Ember.String = EmberStringUtils;
|
|
Ember.Object = EmberObject;
|
|
Ember.TrackedArray = TrackedArray;
|
|
Ember.SubArray = SubArray;
|
|
Ember.Container = Container;
|
|
Ember.Namespace = Namespace;
|
|
Ember.Enumerable = Enumerable;
|
|
Ember.ArrayProxy = ArrayProxy;
|
|
Ember.ObjectProxy = ObjectProxy;
|
|
Ember.ActionHandler = ActionHandler;
|
|
Ember.CoreObject = CoreObject;
|
|
Ember.EachArray = EachArray;
|
|
Ember.EachProxy = EachProxy;
|
|
Ember.NativeArray = NativeArray;
|
|
// ES6TODO: Currently we must rely on the global from ember-metal/core to avoid circular deps
|
|
// Ember.A = A;
|
|
Ember.Set = Set;
|
|
Ember.Deferred = Deferred;
|
|
Ember.onLoad = onLoad;
|
|
Ember.runLoadHooks = runLoadHooks;
|
|
|
|
Ember.ArrayController = ArrayController;
|
|
Ember.ObjectController = ObjectController;
|
|
Ember.Controller = Controller;
|
|
Ember.ControllerMixin = ControllerMixin;
|
|
|
|
|
|
Ember.Service = Service;
|
|
|
|
|
|
Ember._ProxyMixin = _ProxyMixin;
|
|
|
|
Ember.RSVP = RSVP;
|
|
// END EXPORTS
|
|
|
|
__exports__["default"] = Ember;
|
|
});
|
|
enifed("ember-runtime/compare",
|
|
["ember-metal/utils","ember-runtime/mixins/comparable","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var typeOf = __dependency1__.typeOf;
|
|
var Comparable = __dependency2__["default"];
|
|
|
|
var TYPE_ORDER = {
|
|
'undefined': 0,
|
|
'null': 1,
|
|
'boolean': 2,
|
|
'number': 3,
|
|
'string': 4,
|
|
'array': 5,
|
|
'object': 6,
|
|
'instance': 7,
|
|
'function': 8,
|
|
'class': 9,
|
|
'date': 10
|
|
};
|
|
|
|
//
|
|
// the spaceship operator
|
|
//
|
|
function spaceship(a, b) {
|
|
var diff = a - b;
|
|
return (diff > 0) - (diff < 0);
|
|
}
|
|
|
|
/**
|
|
This will compare two javascript values of possibly different types.
|
|
It will tell you which one is greater than the other by returning:
|
|
|
|
- -1 if the first is smaller than the second,
|
|
- 0 if both are equal,
|
|
- 1 if the first is greater than the second.
|
|
|
|
The order is calculated based on `Ember.ORDER_DEFINITION`, if types are different.
|
|
In case they have the same type an appropriate comparison for this type is made.
|
|
|
|
```javascript
|
|
Ember.compare('hello', 'hello'); // 0
|
|
Ember.compare('abc', 'dfg'); // -1
|
|
Ember.compare(2, 1); // 1
|
|
```
|
|
|
|
@method compare
|
|
@for Ember
|
|
@param {Object} v First value to compare
|
|
@param {Object} w Second value to compare
|
|
@return {Number} -1 if v < w, 0 if v = w and 1 if v > w.
|
|
*/
|
|
__exports__["default"] = function compare(v, w) {
|
|
if (v === w) {
|
|
return 0;
|
|
}
|
|
|
|
var type1 = typeOf(v);
|
|
var type2 = typeOf(w);
|
|
|
|
if (Comparable) {
|
|
if (type1 === 'instance' && Comparable.detect(v) && v.constructor.compare) {
|
|
return v.constructor.compare(v, w);
|
|
}
|
|
|
|
if (type2 === 'instance' && Comparable.detect(w) && w.constructor.compare) {
|
|
return w.constructor.compare(w, v) * -1;
|
|
}
|
|
}
|
|
|
|
var res = spaceship(TYPE_ORDER[type1], TYPE_ORDER[type2]);
|
|
|
|
if (res !== 0) {
|
|
return res;
|
|
}
|
|
|
|
// types are equal - so we have to check values now
|
|
switch (type1) {
|
|
case 'boolean':
|
|
case 'number':
|
|
return spaceship(v,w);
|
|
|
|
case 'string':
|
|
return spaceship(v.localeCompare(w), 0);
|
|
|
|
case 'array':
|
|
var vLen = v.length;
|
|
var wLen = w.length;
|
|
var len = Math.min(vLen, wLen);
|
|
|
|
for (var i = 0; i < len; i++) {
|
|
var r = compare(v[i], w[i]);
|
|
if (r !== 0) {
|
|
return r;
|
|
}
|
|
}
|
|
|
|
// all elements are equal now
|
|
// shorter array should be ordered first
|
|
return spaceship(vLen, wLen);
|
|
|
|
case 'instance':
|
|
if (Comparable && Comparable.detect(v)) {
|
|
return v.compare(v, w);
|
|
}
|
|
return 0;
|
|
|
|
case 'date':
|
|
return spaceship(v.getTime(), w.getTime());
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
});
|
|
enifed("ember-runtime/computed/array_computed",
|
|
["ember-metal/core","ember-runtime/computed/reduce_computed","ember-metal/enumerable_utils","ember-metal/platform","ember-metal/observer","ember-metal/error","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
var ReduceComputedProperty = __dependency2__.ReduceComputedProperty;
|
|
var forEach = __dependency3__.forEach;
|
|
var o_create = __dependency4__.create;
|
|
var addObserver = __dependency5__.addObserver;
|
|
var EmberError = __dependency6__["default"];
|
|
|
|
var a_slice = [].slice;
|
|
|
|
function ArrayComputedProperty() {
|
|
var cp = this;
|
|
|
|
ReduceComputedProperty.apply(this, arguments);
|
|
|
|
this.func = (function(reduceFunc) {
|
|
return function (propertyName) {
|
|
if (!cp._hasInstanceMeta(this, propertyName)) {
|
|
// When we recompute an array computed property, we need already
|
|
// retrieved arrays to be updated; we can't simply empty the cache and
|
|
// hope the array is re-retrieved.
|
|
forEach(cp._dependentKeys, function(dependentKey) {
|
|
addObserver(this, dependentKey, function() {
|
|
cp.recomputeOnce.call(this, propertyName);
|
|
});
|
|
}, this);
|
|
}
|
|
|
|
return reduceFunc.apply(this, arguments);
|
|
};
|
|
})(this.func);
|
|
|
|
return this;
|
|
}
|
|
|
|
ArrayComputedProperty.prototype = o_create(ReduceComputedProperty.prototype);
|
|
|
|
ArrayComputedProperty.prototype.initialValue = function () {
|
|
return Ember.A();
|
|
};
|
|
|
|
ArrayComputedProperty.prototype.resetValue = function (array) {
|
|
array.clear();
|
|
return array;
|
|
};
|
|
|
|
// This is a stopgap to keep the reference counts correct with lazy CPs.
|
|
ArrayComputedProperty.prototype.didChange = function (obj, keyName) {
|
|
return;
|
|
};
|
|
|
|
/**
|
|
Creates a computed property which operates on dependent arrays and
|
|
is updated with "one at a time" semantics. When items are added or
|
|
removed from the dependent array(s) an array computed only operates
|
|
on the change instead of re-evaluating the entire array. This should
|
|
return an array, if you'd like to use "one at a time" semantics and
|
|
compute some value other then an array look at
|
|
`Ember.reduceComputed`.
|
|
|
|
If there are more than one arguments the first arguments are
|
|
considered to be dependent property keys. The last argument is
|
|
required to be an options object. The options object can have the
|
|
following three properties.
|
|
|
|
`initialize` - An optional initialize function. Typically this will be used
|
|
to set up state on the instanceMeta object.
|
|
|
|
`removedItem` - A function that is called each time an element is
|
|
removed from the array.
|
|
|
|
`addedItem` - A function that is called each time an element is
|
|
added to the array.
|
|
|
|
|
|
The `initialize` function has the following signature:
|
|
|
|
```javascript
|
|
function(array, changeMeta, instanceMeta)
|
|
```
|
|
|
|
`array` - The initial value of the arrayComputed, an empty array.
|
|
|
|
`changeMeta` - An object which contains meta information about the
|
|
computed. It contains the following properties:
|
|
|
|
- `property` the computed property
|
|
- `propertyName` the name of the property on the object
|
|
|
|
`instanceMeta` - An object that can be used to store meta
|
|
information needed for calculating your computed. For example a
|
|
unique computed might use this to store the number of times a given
|
|
element is found in the dependent array.
|
|
|
|
|
|
The `removedItem` and `addedItem` functions both have the following signature:
|
|
|
|
```javascript
|
|
function(accumulatedValue, item, changeMeta, instanceMeta)
|
|
```
|
|
|
|
`accumulatedValue` - The value returned from the last time
|
|
`removedItem` or `addedItem` was called or an empty array.
|
|
|
|
`item` - the element added or removed from the array
|
|
|
|
`changeMeta` - An object which contains meta information about the
|
|
change. It contains the following properties:
|
|
|
|
- `property` the computed property
|
|
- `propertyName` the name of the property on the object
|
|
- `index` the index of the added or removed item
|
|
- `item` the added or removed item: this is exactly the same as
|
|
the second arg
|
|
- `arrayChanged` the array that triggered the change. Can be
|
|
useful when depending on multiple arrays.
|
|
|
|
For property changes triggered on an item property change (when
|
|
depKey is something like `someArray.@each.someProperty`),
|
|
`changeMeta` will also contain the following property:
|
|
|
|
- `previousValues` an object whose keys are the properties that changed on
|
|
the item, and whose values are the item's previous values.
|
|
|
|
`previousValues` is important Ember coalesces item property changes via
|
|
Ember.run.once. This means that by the time removedItem gets called, item has
|
|
the new values, but you may need the previous value (eg for sorting &
|
|
filtering).
|
|
|
|
`instanceMeta` - An object that can be used to store meta
|
|
information needed for calculating your computed. For example a
|
|
unique computed might use this to store the number of times a given
|
|
element is found in the dependent array.
|
|
|
|
The `removedItem` and `addedItem` functions should return the accumulated
|
|
value. It is acceptable to not return anything (ie return undefined)
|
|
to invalidate the computation. This is generally not a good idea for
|
|
arrayComputed but it's used in eg max and min.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
Ember.computed.map = function(dependentKey, callback) {
|
|
var options = {
|
|
addedItem: function(array, item, changeMeta, instanceMeta) {
|
|
var mapped = callback(item);
|
|
array.insertAt(changeMeta.index, mapped);
|
|
return array;
|
|
},
|
|
removedItem: function(array, item, changeMeta, instanceMeta) {
|
|
array.removeAt(changeMeta.index, 1);
|
|
return array;
|
|
}
|
|
};
|
|
|
|
return Ember.arrayComputed(dependentKey, options);
|
|
};
|
|
```
|
|
|
|
@method arrayComputed
|
|
@for Ember
|
|
@param {String} [dependentKeys*]
|
|
@param {Object} options
|
|
@return {Ember.ComputedProperty}
|
|
*/
|
|
function arrayComputed (options) {
|
|
var args;
|
|
|
|
if (arguments.length > 1) {
|
|
args = a_slice.call(arguments, 0, -1);
|
|
options = a_slice.call(arguments, -1)[0];
|
|
}
|
|
|
|
if (typeof options !== 'object') {
|
|
throw new EmberError('Array Computed Property declared without an options hash');
|
|
}
|
|
|
|
var cp = new ArrayComputedProperty(options);
|
|
|
|
if (args) {
|
|
cp.property.apply(cp, args);
|
|
}
|
|
|
|
return cp;
|
|
}
|
|
|
|
__exports__.arrayComputed = arrayComputed;
|
|
__exports__.ArrayComputedProperty = ArrayComputedProperty;
|
|
});
|
|
enifed("ember-runtime/computed/reduce_computed",
|
|
["ember-metal/core","ember-metal/property_get","ember-metal/utils","ember-metal/error","ember-metal/property_events","ember-metal/expand_properties","ember-metal/observer","ember-metal/computed","ember-metal/platform","ember-metal/enumerable_utils","ember-runtime/system/tracked_array","ember-runtime/mixins/array","ember-metal/run_loop","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.assert
|
|
var e_get = __dependency2__.get;
|
|
var guidFor = __dependency3__.guidFor;
|
|
var metaFor = __dependency3__.meta;
|
|
var EmberError = __dependency4__["default"];
|
|
var propertyWillChange = __dependency5__.propertyWillChange;
|
|
var propertyDidChange = __dependency5__.propertyDidChange;
|
|
var expandProperties = __dependency6__["default"];
|
|
var addObserver = __dependency7__.addObserver;
|
|
var removeObserver = __dependency7__.removeObserver;
|
|
var addBeforeObserver = __dependency7__.addBeforeObserver;
|
|
var removeBeforeObserver = __dependency7__.removeBeforeObserver;
|
|
var ComputedProperty = __dependency8__.ComputedProperty;
|
|
var cacheFor = __dependency8__.cacheFor;
|
|
var o_create = __dependency9__.create;
|
|
var forEach = __dependency10__.forEach;
|
|
var TrackedArray = __dependency11__["default"];
|
|
var EmberArray = __dependency12__["default"];
|
|
var run = __dependency13__["default"];
|
|
var isArray = __dependency3__.isArray;
|
|
|
|
var cacheSet = cacheFor.set;
|
|
var cacheGet = cacheFor.get;
|
|
var cacheRemove = cacheFor.remove;
|
|
var a_slice = [].slice;
|
|
// Here we explicitly don't allow `@each.foo`; it would require some special
|
|
// testing, but there's no particular reason why it should be disallowed.
|
|
var eachPropertyPattern = /^(.*)\.@each\.(.*)/;
|
|
var doubleEachPropertyPattern = /(.*\.@each){2,}/;
|
|
var arrayBracketPattern = /\.\[\]$/;
|
|
|
|
function get(obj, key) {
|
|
if (key === '@this') {
|
|
return obj;
|
|
}
|
|
|
|
return e_get(obj, key);
|
|
}
|
|
|
|
/*
|
|
Tracks changes to dependent arrays, as well as to properties of items in
|
|
dependent arrays.
|
|
|
|
@class DependentArraysObserver
|
|
*/
|
|
function DependentArraysObserver(callbacks, cp, instanceMeta, context, propertyName, sugarMeta) {
|
|
// user specified callbacks for `addedItem` and `removedItem`
|
|
this.callbacks = callbacks;
|
|
|
|
// the computed property: remember these are shared across instances
|
|
this.cp = cp;
|
|
|
|
// the ReduceComputedPropertyInstanceMeta this DependentArraysObserver is
|
|
// associated with
|
|
this.instanceMeta = instanceMeta;
|
|
|
|
// A map of array guids to dependentKeys, for the given context. We track
|
|
// this because we want to set up the computed property potentially before the
|
|
// dependent array even exists, but when the array observer fires, we lack
|
|
// enough context to know what to update: we can recover that context by
|
|
// getting the dependentKey.
|
|
this.dependentKeysByGuid = {};
|
|
|
|
// a map of dependent array guids -> TrackedArray instances. We use
|
|
// this to lazily recompute indexes for item property observers.
|
|
this.trackedArraysByGuid = {};
|
|
|
|
// We suspend observers to ignore replacements from `reset` when totally
|
|
// recomputing. Unfortunately we cannot properly suspend the observers
|
|
// because we only have the key; instead we make the observers no-ops
|
|
this.suspended = false;
|
|
|
|
// This is used to coalesce item changes from property observers within a
|
|
// single item.
|
|
this.changedItems = {};
|
|
// This is used to coalesce item changes for multiple items that depend on
|
|
// some shared state.
|
|
this.changedItemCount = 0;
|
|
}
|
|
|
|
function ItemPropertyObserverContext (dependentArray, index, trackedArray) {
|
|
Ember.assert('Internal error: trackedArray is null or undefined', trackedArray);
|
|
|
|
this.dependentArray = dependentArray;
|
|
this.index = index;
|
|
this.item = dependentArray.objectAt(index);
|
|
this.trackedArray = trackedArray;
|
|
this.beforeObserver = null;
|
|
this.observer = null;
|
|
this.destroyed = false;
|
|
}
|
|
|
|
DependentArraysObserver.prototype = {
|
|
setValue: function (newValue) {
|
|
this.instanceMeta.setValue(newValue, true);
|
|
},
|
|
|
|
getValue: function () {
|
|
return this.instanceMeta.getValue();
|
|
},
|
|
|
|
setupObservers: function (dependentArray, dependentKey) {
|
|
this.dependentKeysByGuid[guidFor(dependentArray)] = dependentKey;
|
|
|
|
dependentArray.addArrayObserver(this, {
|
|
willChange: 'dependentArrayWillChange',
|
|
didChange: 'dependentArrayDidChange'
|
|
});
|
|
|
|
if (this.cp._itemPropertyKeys[dependentKey]) {
|
|
this.setupPropertyObservers(dependentKey, this.cp._itemPropertyKeys[dependentKey]);
|
|
}
|
|
},
|
|
|
|
teardownObservers: function (dependentArray, dependentKey) {
|
|
var itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey] || [];
|
|
|
|
delete this.dependentKeysByGuid[guidFor(dependentArray)];
|
|
|
|
this.teardownPropertyObservers(dependentKey, itemPropertyKeys);
|
|
|
|
dependentArray.removeArrayObserver(this, {
|
|
willChange: 'dependentArrayWillChange',
|
|
didChange: 'dependentArrayDidChange'
|
|
});
|
|
},
|
|
|
|
suspendArrayObservers: function (callback, binding) {
|
|
var oldSuspended = this.suspended;
|
|
this.suspended = true;
|
|
callback.call(binding);
|
|
this.suspended = oldSuspended;
|
|
},
|
|
|
|
setupPropertyObservers: function (dependentKey, itemPropertyKeys) {
|
|
var dependentArray = get(this.instanceMeta.context, dependentKey);
|
|
var length = get(dependentArray, 'length');
|
|
var observerContexts = new Array(length);
|
|
|
|
this.resetTransformations(dependentKey, observerContexts);
|
|
|
|
forEach(dependentArray, function (item, index) {
|
|
var observerContext = this.createPropertyObserverContext(dependentArray, index, this.trackedArraysByGuid[dependentKey]);
|
|
observerContexts[index] = observerContext;
|
|
|
|
forEach(itemPropertyKeys, function (propertyKey) {
|
|
addBeforeObserver(item, propertyKey, this, observerContext.beforeObserver);
|
|
addObserver(item, propertyKey, this, observerContext.observer);
|
|
}, this);
|
|
}, this);
|
|
},
|
|
|
|
teardownPropertyObservers: function (dependentKey, itemPropertyKeys) {
|
|
var dependentArrayObserver = this;
|
|
var trackedArray = this.trackedArraysByGuid[dependentKey];
|
|
var beforeObserver, observer, item;
|
|
|
|
if (!trackedArray) { return; }
|
|
|
|
trackedArray.apply(function (observerContexts, offset, operation) {
|
|
if (operation === TrackedArray.DELETE) { return; }
|
|
|
|
forEach(observerContexts, function (observerContext) {
|
|
observerContext.destroyed = true;
|
|
beforeObserver = observerContext.beforeObserver;
|
|
observer = observerContext.observer;
|
|
item = observerContext.item;
|
|
|
|
forEach(itemPropertyKeys, function (propertyKey) {
|
|
removeBeforeObserver(item, propertyKey, dependentArrayObserver, beforeObserver);
|
|
removeObserver(item, propertyKey, dependentArrayObserver, observer);
|
|
});
|
|
});
|
|
});
|
|
},
|
|
|
|
createPropertyObserverContext: function (dependentArray, index, trackedArray) {
|
|
var observerContext = new ItemPropertyObserverContext(dependentArray, index, trackedArray);
|
|
|
|
this.createPropertyObserver(observerContext);
|
|
|
|
return observerContext;
|
|
},
|
|
|
|
createPropertyObserver: function (observerContext) {
|
|
var dependentArrayObserver = this;
|
|
|
|
observerContext.beforeObserver = function (obj, keyName) {
|
|
return dependentArrayObserver.itemPropertyWillChange(obj, keyName, observerContext.dependentArray, observerContext);
|
|
};
|
|
|
|
observerContext.observer = function (obj, keyName) {
|
|
return dependentArrayObserver.itemPropertyDidChange(obj, keyName, observerContext.dependentArray, observerContext);
|
|
};
|
|
},
|
|
|
|
resetTransformations: function (dependentKey, observerContexts) {
|
|
this.trackedArraysByGuid[dependentKey] = new TrackedArray(observerContexts);
|
|
},
|
|
|
|
trackAdd: function (dependentKey, index, newItems) {
|
|
var trackedArray = this.trackedArraysByGuid[dependentKey];
|
|
|
|
if (trackedArray) {
|
|
trackedArray.addItems(index, newItems);
|
|
}
|
|
},
|
|
|
|
trackRemove: function (dependentKey, index, removedCount) {
|
|
var trackedArray = this.trackedArraysByGuid[dependentKey];
|
|
|
|
if (trackedArray) {
|
|
return trackedArray.removeItems(index, removedCount);
|
|
}
|
|
|
|
return [];
|
|
},
|
|
|
|
updateIndexes: function (trackedArray, array) {
|
|
var length = get(array, 'length');
|
|
// OPTIMIZE: we could stop updating once we hit the object whose observer
|
|
// fired; ie partially apply the transformations
|
|
trackedArray.apply(function (observerContexts, offset, operation, operationIndex) {
|
|
// we don't even have observer contexts for removed items, even if we did,
|
|
// they no longer have any index in the array
|
|
if (operation === TrackedArray.DELETE) { return; }
|
|
if (operationIndex === 0 && operation === TrackedArray.RETAIN && observerContexts.length === length && offset === 0) {
|
|
// If we update many items we don't want to walk the array each time: we
|
|
// only need to update the indexes at most once per run loop.
|
|
return;
|
|
}
|
|
|
|
forEach(observerContexts, function (context, index) {
|
|
context.index = index + offset;
|
|
});
|
|
});
|
|
},
|
|
|
|
dependentArrayWillChange: function (dependentArray, index, removedCount, addedCount) {
|
|
if (this.suspended) { return; }
|
|
|
|
var removedItem = this.callbacks.removedItem;
|
|
var changeMeta;
|
|
var guid = guidFor(dependentArray);
|
|
var dependentKey = this.dependentKeysByGuid[guid];
|
|
var itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey] || [];
|
|
var length = get(dependentArray, 'length');
|
|
var normalizedIndex = normalizeIndex(index, length, 0);
|
|
var normalizedRemoveCount = normalizeRemoveCount(normalizedIndex, length, removedCount);
|
|
var item, itemIndex, sliceIndex, observerContexts;
|
|
|
|
observerContexts = this.trackRemove(dependentKey, normalizedIndex, normalizedRemoveCount);
|
|
|
|
function removeObservers(propertyKey) {
|
|
observerContexts[sliceIndex].destroyed = true;
|
|
removeBeforeObserver(item, propertyKey, this, observerContexts[sliceIndex].beforeObserver);
|
|
removeObserver(item, propertyKey, this, observerContexts[sliceIndex].observer);
|
|
}
|
|
|
|
for (sliceIndex = normalizedRemoveCount - 1; sliceIndex >= 0; --sliceIndex) {
|
|
itemIndex = normalizedIndex + sliceIndex;
|
|
if (itemIndex >= length) { break; }
|
|
|
|
item = dependentArray.objectAt(itemIndex);
|
|
|
|
forEach(itemPropertyKeys, removeObservers, this);
|
|
|
|
changeMeta = new ChangeMeta(dependentArray, item, itemIndex, this.instanceMeta.propertyName, this.cp, normalizedRemoveCount);
|
|
this.setValue(removedItem.call(
|
|
this.instanceMeta.context, this.getValue(), item, changeMeta, this.instanceMeta.sugarMeta));
|
|
}
|
|
this.callbacks.flushedChanges.call(this.instanceMeta.context, this.getValue(), this.instanceMeta.sugarMeta);
|
|
},
|
|
|
|
dependentArrayDidChange: function (dependentArray, index, removedCount, addedCount) {
|
|
if (this.suspended) { return; }
|
|
|
|
var addedItem = this.callbacks.addedItem;
|
|
var guid = guidFor(dependentArray);
|
|
var dependentKey = this.dependentKeysByGuid[guid];
|
|
var observerContexts = new Array(addedCount);
|
|
var itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey];
|
|
var length = get(dependentArray, 'length');
|
|
var normalizedIndex = normalizeIndex(index, length, addedCount);
|
|
var endIndex = normalizedIndex + addedCount;
|
|
var changeMeta, observerContext;
|
|
|
|
forEach(dependentArray.slice(normalizedIndex, endIndex), function (item, sliceIndex) {
|
|
if (itemPropertyKeys) {
|
|
observerContext = this.createPropertyObserverContext(dependentArray, normalizedIndex + sliceIndex,
|
|
this.trackedArraysByGuid[dependentKey]);
|
|
observerContexts[sliceIndex] = observerContext;
|
|
|
|
forEach(itemPropertyKeys, function (propertyKey) {
|
|
addBeforeObserver(item, propertyKey, this, observerContext.beforeObserver);
|
|
addObserver(item, propertyKey, this, observerContext.observer);
|
|
}, this);
|
|
}
|
|
|
|
changeMeta = new ChangeMeta(dependentArray, item, normalizedIndex + sliceIndex, this.instanceMeta.propertyName, this.cp, addedCount);
|
|
this.setValue(addedItem.call(
|
|
this.instanceMeta.context, this.getValue(), item, changeMeta, this.instanceMeta.sugarMeta));
|
|
}, this);
|
|
this.callbacks.flushedChanges.call(this.instanceMeta.context, this.getValue(), this.instanceMeta.sugarMeta);
|
|
this.trackAdd(dependentKey, normalizedIndex, observerContexts);
|
|
},
|
|
|
|
itemPropertyWillChange: function (obj, keyName, array, observerContext) {
|
|
var guid = guidFor(obj);
|
|
|
|
if (!this.changedItems[guid]) {
|
|
this.changedItems[guid] = {
|
|
array: array,
|
|
observerContext: observerContext,
|
|
obj: obj,
|
|
previousValues: {}
|
|
};
|
|
}
|
|
|
|
++this.changedItemCount;
|
|
this.changedItems[guid].previousValues[keyName] = get(obj, keyName);
|
|
},
|
|
|
|
itemPropertyDidChange: function (obj, keyName, array, observerContext) {
|
|
if (--this.changedItemCount === 0) {
|
|
this.flushChanges();
|
|
}
|
|
},
|
|
|
|
flushChanges: function () {
|
|
var changedItems = this.changedItems;
|
|
var key, c, changeMeta;
|
|
|
|
for (key in changedItems) {
|
|
c = changedItems[key];
|
|
if (c.observerContext.destroyed) { continue; }
|
|
|
|
this.updateIndexes(c.observerContext.trackedArray, c.observerContext.dependentArray);
|
|
|
|
changeMeta = new ChangeMeta(c.array, c.obj, c.observerContext.index, this.instanceMeta.propertyName, this.cp, changedItems.length, c.previousValues);
|
|
this.setValue(
|
|
this.callbacks.removedItem.call(this.instanceMeta.context, this.getValue(), c.obj, changeMeta, this.instanceMeta.sugarMeta));
|
|
this.setValue(
|
|
this.callbacks.addedItem.call(this.instanceMeta.context, this.getValue(), c.obj, changeMeta, this.instanceMeta.sugarMeta));
|
|
}
|
|
|
|
this.changedItems = {};
|
|
this.callbacks.flushedChanges.call(this.instanceMeta.context, this.getValue(), this.instanceMeta.sugarMeta);
|
|
}
|
|
};
|
|
|
|
function normalizeIndex(index, length, newItemsOffset) {
|
|
if (index < 0) {
|
|
return Math.max(0, length + index);
|
|
} else if (index < length) {
|
|
return index;
|
|
} else /* index > length */ {
|
|
return Math.min(length - newItemsOffset, index);
|
|
}
|
|
}
|
|
|
|
function normalizeRemoveCount(index, length, removedCount) {
|
|
return Math.min(removedCount, length - index);
|
|
}
|
|
|
|
function ChangeMeta(dependentArray, item, index, propertyName, property, changedCount, previousValues){
|
|
this.arrayChanged = dependentArray;
|
|
this.index = index;
|
|
this.item = item;
|
|
this.propertyName = propertyName;
|
|
this.property = property;
|
|
this.changedCount = changedCount;
|
|
|
|
if (previousValues) {
|
|
// previous values only available for item property changes
|
|
this.previousValues = previousValues;
|
|
}
|
|
}
|
|
|
|
function addItems(dependentArray, callbacks, cp, propertyName, meta) {
|
|
forEach(dependentArray, function (item, index) {
|
|
meta.setValue( callbacks.addedItem.call(
|
|
this, meta.getValue(), item, new ChangeMeta(dependentArray, item, index, propertyName, cp, dependentArray.length), meta.sugarMeta));
|
|
}, this);
|
|
callbacks.flushedChanges.call(this, meta.getValue(), meta.sugarMeta);
|
|
}
|
|
|
|
function reset(cp, propertyName) {
|
|
var hadMeta = cp._hasInstanceMeta(this, propertyName);
|
|
var meta = cp._instanceMeta(this, propertyName);
|
|
|
|
if (hadMeta) { meta.setValue(cp.resetValue(meta.getValue())); }
|
|
|
|
if (cp.options.initialize) {
|
|
cp.options.initialize.call(this, meta.getValue(), {
|
|
property: cp,
|
|
propertyName: propertyName
|
|
}, meta.sugarMeta);
|
|
}
|
|
}
|
|
|
|
function partiallyRecomputeFor(obj, dependentKey) {
|
|
if (arrayBracketPattern.test(dependentKey)) {
|
|
return false;
|
|
}
|
|
|
|
var value = get(obj, dependentKey);
|
|
return EmberArray.detect(value);
|
|
}
|
|
|
|
function ReduceComputedPropertyInstanceMeta(context, propertyName, initialValue) {
|
|
this.context = context;
|
|
this.propertyName = propertyName;
|
|
this.cache = metaFor(context).cache;
|
|
this.dependentArrays = {};
|
|
this.sugarMeta = {};
|
|
this.initialValue = initialValue;
|
|
}
|
|
|
|
ReduceComputedPropertyInstanceMeta.prototype = {
|
|
getValue: function () {
|
|
var value = cacheGet(this.cache, this.propertyName);
|
|
|
|
if (value !== undefined) {
|
|
return value;
|
|
} else {
|
|
return this.initialValue;
|
|
}
|
|
},
|
|
|
|
setValue: function(newValue, triggerObservers) {
|
|
// This lets sugars force a recomputation, handy for very simple
|
|
// implementations of eg max.
|
|
if (newValue === cacheGet(this.cache, this.propertyName)) {
|
|
return;
|
|
}
|
|
|
|
if (triggerObservers) {
|
|
propertyWillChange(this.context, this.propertyName);
|
|
}
|
|
|
|
if (newValue === undefined) {
|
|
cacheRemove(this.cache, this.propertyName);
|
|
} else {
|
|
cacheSet(this.cache, this.propertyName, newValue);
|
|
}
|
|
|
|
if (triggerObservers) {
|
|
propertyDidChange(this.context, this.propertyName);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
A computed property whose dependent keys are arrays and which is updated with
|
|
"one at a time" semantics.
|
|
|
|
@class ReduceComputedProperty
|
|
@namespace Ember
|
|
@extends Ember.ComputedProperty
|
|
@constructor
|
|
*/
|
|
|
|
__exports__.ReduceComputedProperty = ReduceComputedProperty;
|
|
// TODO: default export
|
|
|
|
function ReduceComputedProperty(options) {
|
|
var cp = this;
|
|
|
|
this.options = options;
|
|
this._dependentKeys = null;
|
|
this._cacheable = true;
|
|
// A map of dependentKey -> [itemProperty, ...] that tracks what properties of
|
|
// items in the array we must track to update this property.
|
|
this._itemPropertyKeys = {};
|
|
this._previousItemPropertyKeys = {};
|
|
|
|
this.readOnly();
|
|
|
|
this.recomputeOnce = function(propertyName) {
|
|
// What we really want to do is coalesce by <cp, propertyName>.
|
|
// We need a form of `scheduleOnce` that accepts an arbitrary token to
|
|
// coalesce by, in addition to the target and method.
|
|
run.once(this, recompute, propertyName);
|
|
};
|
|
|
|
var recompute = function(propertyName) {
|
|
var meta = cp._instanceMeta(this, propertyName);
|
|
var callbacks = cp._callbacks();
|
|
|
|
reset.call(this, cp, propertyName);
|
|
|
|
meta.dependentArraysObserver.suspendArrayObservers(function () {
|
|
forEach(cp._dependentKeys, function (dependentKey) {
|
|
Ember.assert(
|
|
'dependent array ' + dependentKey + ' must be an `Ember.Array`. ' +
|
|
'If you are not extending arrays, you will need to wrap native arrays with `Ember.A`',
|
|
!(isArray(get(this, dependentKey)) && !EmberArray.detect(get(this, dependentKey))));
|
|
|
|
if (!partiallyRecomputeFor(this, dependentKey)) { return; }
|
|
|
|
var dependentArray = get(this, dependentKey);
|
|
var previousDependentArray = meta.dependentArrays[dependentKey];
|
|
|
|
if (dependentArray === previousDependentArray) {
|
|
// The array may be the same, but our item property keys may have
|
|
// changed, so we set them up again. We can't easily tell if they've
|
|
// changed: the array may be the same object, but with different
|
|
// contents.
|
|
if (cp._previousItemPropertyKeys[dependentKey]) {
|
|
delete cp._previousItemPropertyKeys[dependentKey];
|
|
meta.dependentArraysObserver.setupPropertyObservers(dependentKey, cp._itemPropertyKeys[dependentKey]);
|
|
}
|
|
} else {
|
|
meta.dependentArrays[dependentKey] = dependentArray;
|
|
|
|
if (previousDependentArray) {
|
|
meta.dependentArraysObserver.teardownObservers(previousDependentArray, dependentKey);
|
|
}
|
|
|
|
if (dependentArray) {
|
|
meta.dependentArraysObserver.setupObservers(dependentArray, dependentKey);
|
|
}
|
|
}
|
|
}, this);
|
|
}, this);
|
|
|
|
forEach(cp._dependentKeys, function(dependentKey) {
|
|
if (!partiallyRecomputeFor(this, dependentKey)) { return; }
|
|
|
|
var dependentArray = get(this, dependentKey);
|
|
|
|
if (dependentArray) {
|
|
addItems.call(this, dependentArray, callbacks, cp, propertyName, meta);
|
|
}
|
|
}, this);
|
|
};
|
|
|
|
|
|
this.func = function (propertyName) {
|
|
Ember.assert('Computed reduce values require at least one dependent key', cp._dependentKeys);
|
|
|
|
recompute.call(this, propertyName);
|
|
|
|
return cp._instanceMeta(this, propertyName).getValue();
|
|
};
|
|
}
|
|
|
|
ReduceComputedProperty.prototype = o_create(ComputedProperty.prototype);
|
|
|
|
function defaultCallback(computedValue) {
|
|
return computedValue;
|
|
}
|
|
|
|
ReduceComputedProperty.prototype._callbacks = function () {
|
|
if (!this.callbacks) {
|
|
var options = this.options;
|
|
|
|
this.callbacks = {
|
|
removedItem: options.removedItem || defaultCallback,
|
|
addedItem: options.addedItem || defaultCallback,
|
|
flushedChanges: options.flushedChanges || defaultCallback
|
|
};
|
|
}
|
|
|
|
return this.callbacks;
|
|
};
|
|
|
|
ReduceComputedProperty.prototype._hasInstanceMeta = function (context, propertyName) {
|
|
return !!metaFor(context).cacheMeta[propertyName];
|
|
};
|
|
|
|
ReduceComputedProperty.prototype._instanceMeta = function (context, propertyName) {
|
|
var cacheMeta = metaFor(context).cacheMeta;
|
|
var meta = cacheMeta[propertyName];
|
|
|
|
if (!meta) {
|
|
meta = cacheMeta[propertyName] = new ReduceComputedPropertyInstanceMeta(context, propertyName, this.initialValue());
|
|
meta.dependentArraysObserver = new DependentArraysObserver(this._callbacks(), this, meta, context, propertyName, meta.sugarMeta);
|
|
}
|
|
|
|
return meta;
|
|
};
|
|
|
|
ReduceComputedProperty.prototype.initialValue = function () {
|
|
if (typeof this.options.initialValue === 'function') {
|
|
return this.options.initialValue();
|
|
}
|
|
else {
|
|
return this.options.initialValue;
|
|
}
|
|
};
|
|
|
|
ReduceComputedProperty.prototype.resetValue = function (value) {
|
|
return this.initialValue();
|
|
};
|
|
|
|
ReduceComputedProperty.prototype.itemPropertyKey = function (dependentArrayKey, itemPropertyKey) {
|
|
this._itemPropertyKeys[dependentArrayKey] = this._itemPropertyKeys[dependentArrayKey] || [];
|
|
this._itemPropertyKeys[dependentArrayKey].push(itemPropertyKey);
|
|
};
|
|
|
|
ReduceComputedProperty.prototype.clearItemPropertyKeys = function (dependentArrayKey) {
|
|
if (this._itemPropertyKeys[dependentArrayKey]) {
|
|
this._previousItemPropertyKeys[dependentArrayKey] = this._itemPropertyKeys[dependentArrayKey];
|
|
this._itemPropertyKeys[dependentArrayKey] = [];
|
|
}
|
|
};
|
|
|
|
ReduceComputedProperty.prototype.property = function () {
|
|
var cp = this;
|
|
var args = a_slice.call(arguments);
|
|
var propertyArgs = {};
|
|
var match, dependentArrayKey;
|
|
|
|
forEach(args, function (dependentKey) {
|
|
if (doubleEachPropertyPattern.test(dependentKey)) {
|
|
throw new EmberError('Nested @each properties not supported: ' + dependentKey);
|
|
} else if (match = eachPropertyPattern.exec(dependentKey)) {
|
|
dependentArrayKey = match[1];
|
|
|
|
var itemPropertyKeyPattern = match[2];
|
|
var addItemPropertyKey = function (itemPropertyKey) {
|
|
cp.itemPropertyKey(dependentArrayKey, itemPropertyKey);
|
|
};
|
|
|
|
expandProperties(itemPropertyKeyPattern, addItemPropertyKey);
|
|
propertyArgs[guidFor(dependentArrayKey)] = dependentArrayKey;
|
|
} else {
|
|
propertyArgs[guidFor(dependentKey)] = dependentKey;
|
|
}
|
|
});
|
|
|
|
var propertyArgsToArray = [];
|
|
for (var guid in propertyArgs) {
|
|
propertyArgsToArray.push(propertyArgs[guid]);
|
|
}
|
|
|
|
return ComputedProperty.prototype.property.apply(this, propertyArgsToArray);
|
|
};
|
|
|
|
/**
|
|
Creates a computed property which operates on dependent arrays and
|
|
is updated with "one at a time" semantics. When items are added or
|
|
removed from the dependent array(s) a reduce computed only operates
|
|
on the change instead of re-evaluating the entire array.
|
|
|
|
If there are more than one arguments the first arguments are
|
|
considered to be dependent property keys. The last argument is
|
|
required to be an options object. The options object can have the
|
|
following four properties:
|
|
|
|
`initialValue` - A value or function that will be used as the initial
|
|
value for the computed. If this property is a function the result of calling
|
|
the function will be used as the initial value. This property is required.
|
|
|
|
`initialize` - An optional initialize function. Typically this will be used
|
|
to set up state on the instanceMeta object.
|
|
|
|
`removedItem` - A function that is called each time an element is removed
|
|
from the array.
|
|
|
|
`addedItem` - A function that is called each time an element is added to
|
|
the array.
|
|
|
|
|
|
The `initialize` function has the following signature:
|
|
|
|
```javascript
|
|
function(initialValue, changeMeta, instanceMeta)
|
|
```
|
|
|
|
`initialValue` - The value of the `initialValue` property from the
|
|
options object.
|
|
|
|
`changeMeta` - An object which contains meta information about the
|
|
computed. It contains the following properties:
|
|
|
|
- `property` the computed property
|
|
- `propertyName` the name of the property on the object
|
|
|
|
`instanceMeta` - An object that can be used to store meta
|
|
information needed for calculating your computed. For example a
|
|
unique computed might use this to store the number of times a given
|
|
element is found in the dependent array.
|
|
|
|
|
|
The `removedItem` and `addedItem` functions both have the following signature:
|
|
|
|
```javascript
|
|
function(accumulatedValue, item, changeMeta, instanceMeta)
|
|
```
|
|
|
|
`accumulatedValue` - The value returned from the last time
|
|
`removedItem` or `addedItem` was called or `initialValue`.
|
|
|
|
`item` - the element added or removed from the array
|
|
|
|
`changeMeta` - An object which contains meta information about the
|
|
change. It contains the following properties:
|
|
|
|
- `property` the computed property
|
|
- `propertyName` the name of the property on the object
|
|
- `index` the index of the added or removed item
|
|
- `item` the added or removed item: this is exactly the same as
|
|
the second arg
|
|
- `arrayChanged` the array that triggered the change. Can be
|
|
useful when depending on multiple arrays.
|
|
|
|
For property changes triggered on an item property change (when
|
|
depKey is something like `someArray.@each.someProperty`),
|
|
`changeMeta` will also contain the following property:
|
|
|
|
- `previousValues` an object whose keys are the properties that changed on
|
|
the item, and whose values are the item's previous values.
|
|
|
|
`previousValues` is important Ember coalesces item property changes via
|
|
Ember.run.once. This means that by the time removedItem gets called, item has
|
|
the new values, but you may need the previous value (eg for sorting &
|
|
filtering).
|
|
|
|
`instanceMeta` - An object that can be used to store meta
|
|
information needed for calculating your computed. For example a
|
|
unique computed might use this to store the number of times a given
|
|
element is found in the dependent array.
|
|
|
|
The `removedItem` and `addedItem` functions should return the accumulated
|
|
value. It is acceptable to not return anything (ie return undefined)
|
|
to invalidate the computation. This is generally not a good idea for
|
|
arrayComputed but it's used in eg max and min.
|
|
|
|
Note that observers will be fired if either of these functions return a value
|
|
that differs from the accumulated value. When returning an object that
|
|
mutates in response to array changes, for example an array that maps
|
|
everything from some other array (see `Ember.computed.map`), it is usually
|
|
important that the *same* array be returned to avoid accidentally triggering observers.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
Ember.computed.max = function(dependentKey) {
|
|
return Ember.reduceComputed(dependentKey, {
|
|
initialValue: -Infinity,
|
|
|
|
addedItem: function(accumulatedValue, item, changeMeta, instanceMeta) {
|
|
return Math.max(accumulatedValue, item);
|
|
},
|
|
|
|
removedItem: function(accumulatedValue, item, changeMeta, instanceMeta) {
|
|
if (item < accumulatedValue) {
|
|
return accumulatedValue;
|
|
}
|
|
}
|
|
});
|
|
};
|
|
```
|
|
|
|
Dependent keys may refer to `@this` to observe changes to the object itself,
|
|
which must be array-like, rather than a property of the object. This is
|
|
mostly useful for array proxies, to ensure objects are retrieved via
|
|
`objectAtContent`. This is how you could sort items by properties defined on an item controller.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
App.PeopleController = Ember.ArrayController.extend({
|
|
itemController: 'person',
|
|
|
|
sortedPeople: Ember.computed.sort('@this.@each.reversedName', function(personA, personB) {
|
|
// `reversedName` isn't defined on Person, but we have access to it via
|
|
// the item controller App.PersonController. If we'd used
|
|
// `content.@each.reversedName` above, we would be getting the objects
|
|
// directly and not have access to `reversedName`.
|
|
//
|
|
var reversedNameA = get(personA, 'reversedName');
|
|
var reversedNameB = get(personB, 'reversedName');
|
|
|
|
return Ember.compare(reversedNameA, reversedNameB);
|
|
})
|
|
});
|
|
|
|
App.PersonController = Ember.ObjectController.extend({
|
|
reversedName: function() {
|
|
return reverse(get(this, 'name'));
|
|
}.property('name')
|
|
});
|
|
```
|
|
|
|
Dependent keys whose values are not arrays are treated as regular
|
|
dependencies: when they change, the computed property is completely
|
|
recalculated. It is sometimes useful to have dependent arrays with similar
|
|
semantics. Dependent keys which end in `.[]` do not use "one at a time"
|
|
semantics. When an item is added or removed from such a dependency, the
|
|
computed property is completely recomputed.
|
|
|
|
When the computed property is completely recomputed, the `accumulatedValue`
|
|
is discarded, it starts with `initialValue` again, and each item is passed
|
|
to `addedItem` in turn.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
Ember.Object.extend({
|
|
// When `string` is changed, `computed` is completely recomputed.
|
|
string: 'a string',
|
|
|
|
// When an item is added to `array`, `addedItem` is called.
|
|
array: [],
|
|
|
|
// When an item is added to `anotherArray`, `computed` is completely
|
|
// recomputed.
|
|
anotherArray: [],
|
|
|
|
computed: Ember.reduceComputed('string', 'array', 'anotherArray.[]', {
|
|
addedItem: addedItemCallback,
|
|
removedItem: removedItemCallback
|
|
})
|
|
});
|
|
```
|
|
|
|
@method reduceComputed
|
|
@for Ember
|
|
@param {String} [dependentKeys*]
|
|
@param {Object} options
|
|
@return {Ember.ComputedProperty}
|
|
*/
|
|
function reduceComputed(options) {
|
|
var args;
|
|
|
|
if (arguments.length > 1) {
|
|
args = a_slice.call(arguments, 0, -1);
|
|
options = a_slice.call(arguments, -1)[0];
|
|
}
|
|
|
|
if (typeof options !== 'object') {
|
|
throw new EmberError('Reduce Computed Property declared without an options hash');
|
|
}
|
|
|
|
if (!('initialValue' in options)) {
|
|
throw new EmberError('Reduce Computed Property declared without an initial value');
|
|
}
|
|
|
|
var cp = new ReduceComputedProperty(options);
|
|
|
|
if (args) {
|
|
cp.property.apply(cp, args);
|
|
}
|
|
|
|
return cp;
|
|
}
|
|
|
|
__exports__.reduceComputed = reduceComputed;
|
|
});
|
|
enifed("ember-runtime/computed/reduce_computed_macros",
|
|
["ember-metal/core","ember-metal/property_get","ember-metal/utils","ember-metal/error","ember-metal/enumerable_utils","ember-metal/run_loop","ember-metal/observer","ember-runtime/computed/array_computed","ember-runtime/computed/reduce_computed","ember-runtime/system/subarray","ember-metal/keys","ember-runtime/compare","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.assert
|
|
var get = __dependency2__.get;
|
|
var isArray = __dependency3__.isArray;
|
|
var guidFor = __dependency3__.guidFor;
|
|
var EmberError = __dependency4__["default"];
|
|
var forEach = __dependency5__.forEach;
|
|
var run = __dependency6__["default"];
|
|
var addObserver = __dependency7__.addObserver;
|
|
var arrayComputed = __dependency8__.arrayComputed;
|
|
var reduceComputed = __dependency9__.reduceComputed;
|
|
var SubArray = __dependency10__["default"];
|
|
var keys = __dependency11__["default"];
|
|
var compare = __dependency12__["default"];
|
|
|
|
var a_slice = [].slice;
|
|
|
|
/**
|
|
A computed property that returns the sum of the value
|
|
in the dependent array.
|
|
|
|
@method computed.sum
|
|
@for Ember
|
|
@param {String} dependentKey
|
|
@return {Ember.ComputedProperty} computes the sum of all values in the dependentKey's array
|
|
@since 1.4.0
|
|
*/
|
|
|
|
function sum(dependentKey){
|
|
return reduceComputed(dependentKey, {
|
|
initialValue: 0,
|
|
|
|
addedItem: function(accumulatedValue, item, changeMeta, instanceMeta){
|
|
return accumulatedValue + item;
|
|
},
|
|
|
|
removedItem: function(accumulatedValue, item, changeMeta, instanceMeta){
|
|
return accumulatedValue - item;
|
|
}
|
|
});
|
|
}
|
|
|
|
__exports__.sum = sum;/**
|
|
A computed property that calculates the maximum value in the
|
|
dependent array. This will return `-Infinity` when the dependent
|
|
array is empty.
|
|
|
|
```javascript
|
|
var Person = Ember.Object.extend({
|
|
childAges: Ember.computed.mapBy('children', 'age'),
|
|
maxChildAge: Ember.computed.max('childAges')
|
|
});
|
|
|
|
var lordByron = Person.create({ children: [] });
|
|
|
|
lordByron.get('maxChildAge'); // -Infinity
|
|
lordByron.get('children').pushObject({
|
|
name: 'Augusta Ada Byron', age: 7
|
|
});
|
|
lordByron.get('maxChildAge'); // 7
|
|
lordByron.get('children').pushObjects([{
|
|
name: 'Allegra Byron',
|
|
age: 5
|
|
}, {
|
|
name: 'Elizabeth Medora Leigh',
|
|
age: 8
|
|
}]);
|
|
lordByron.get('maxChildAge'); // 8
|
|
```
|
|
|
|
@method computed.max
|
|
@for Ember
|
|
@param {String} dependentKey
|
|
@return {Ember.ComputedProperty} computes the largest value in the dependentKey's array
|
|
*/
|
|
function max(dependentKey) {
|
|
return reduceComputed(dependentKey, {
|
|
initialValue: -Infinity,
|
|
|
|
addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
|
|
return Math.max(accumulatedValue, item);
|
|
},
|
|
|
|
removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
|
|
if (item < accumulatedValue) {
|
|
return accumulatedValue;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
__exports__.max = max;/**
|
|
A computed property that calculates the minimum value in the
|
|
dependent array. This will return `Infinity` when the dependent
|
|
array is empty.
|
|
|
|
```javascript
|
|
var Person = Ember.Object.extend({
|
|
childAges: Ember.computed.mapBy('children', 'age'),
|
|
minChildAge: Ember.computed.min('childAges')
|
|
});
|
|
|
|
var lordByron = Person.create({ children: [] });
|
|
|
|
lordByron.get('minChildAge'); // Infinity
|
|
lordByron.get('children').pushObject({
|
|
name: 'Augusta Ada Byron', age: 7
|
|
});
|
|
lordByron.get('minChildAge'); // 7
|
|
lordByron.get('children').pushObjects([{
|
|
name: 'Allegra Byron',
|
|
age: 5
|
|
}, {
|
|
name: 'Elizabeth Medora Leigh',
|
|
age: 8
|
|
}]);
|
|
lordByron.get('minChildAge'); // 5
|
|
```
|
|
|
|
@method computed.min
|
|
@for Ember
|
|
@param {String} dependentKey
|
|
@return {Ember.ComputedProperty} computes the smallest value in the dependentKey's array
|
|
*/
|
|
function min(dependentKey) {
|
|
return reduceComputed(dependentKey, {
|
|
initialValue: Infinity,
|
|
|
|
addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
|
|
return Math.min(accumulatedValue, item);
|
|
},
|
|
|
|
removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
|
|
if (item > accumulatedValue) {
|
|
return accumulatedValue;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
__exports__.min = min;/**
|
|
Returns an array mapped via the callback
|
|
|
|
The callback method you provide should have the following signature.
|
|
`item` is the current item in the iteration.
|
|
`index` is the integer index of the current item in the iteration.
|
|
|
|
```javascript
|
|
function(item, index);
|
|
```
|
|
|
|
Example
|
|
|
|
```javascript
|
|
var Hamster = Ember.Object.extend({
|
|
excitingChores: Ember.computed.map('chores', function(chore, index) {
|
|
return chore.toUpperCase() + '!';
|
|
})
|
|
});
|
|
|
|
var hamster = Hamster.create({
|
|
chores: ['clean', 'write more unit tests']
|
|
});
|
|
|
|
hamster.get('excitingChores'); // ['CLEAN!', 'WRITE MORE UNIT TESTS!']
|
|
```
|
|
|
|
@method computed.map
|
|
@for Ember
|
|
@param {String} dependentKey
|
|
@param {Function} callback
|
|
@return {Ember.ComputedProperty} an array mapped via the callback
|
|
*/
|
|
function map(dependentKey, callback) {
|
|
var options = {
|
|
addedItem: function(array, item, changeMeta, instanceMeta) {
|
|
var mapped = callback.call(this, item, changeMeta.index);
|
|
array.insertAt(changeMeta.index, mapped);
|
|
return array;
|
|
},
|
|
removedItem: function(array, item, changeMeta, instanceMeta) {
|
|
array.removeAt(changeMeta.index, 1);
|
|
return array;
|
|
}
|
|
};
|
|
|
|
return arrayComputed(dependentKey, options);
|
|
}
|
|
|
|
__exports__.map = map;/**
|
|
Returns an array mapped to the specified key.
|
|
|
|
```javascript
|
|
var Person = Ember.Object.extend({
|
|
childAges: Ember.computed.mapBy('children', 'age')
|
|
});
|
|
|
|
var lordByron = Person.create({ children: [] });
|
|
|
|
lordByron.get('childAges'); // []
|
|
lordByron.get('children').pushObject({ name: 'Augusta Ada Byron', age: 7 });
|
|
lordByron.get('childAges'); // [7]
|
|
lordByron.get('children').pushObjects([{
|
|
name: 'Allegra Byron',
|
|
age: 5
|
|
}, {
|
|
name: 'Elizabeth Medora Leigh',
|
|
age: 8
|
|
}]);
|
|
lordByron.get('childAges'); // [7, 5, 8]
|
|
```
|
|
|
|
@method computed.mapBy
|
|
@for Ember
|
|
@param {String} dependentKey
|
|
@param {String} propertyKey
|
|
@return {Ember.ComputedProperty} an array mapped to the specified key
|
|
*/
|
|
function mapBy (dependentKey, propertyKey) {
|
|
var callback = function(item) { return get(item, propertyKey); };
|
|
return map(dependentKey + '.@each.' + propertyKey, callback);
|
|
}
|
|
|
|
__exports__.mapBy = mapBy;/**
|
|
@method computed.mapProperty
|
|
@for Ember
|
|
@deprecated Use `Ember.computed.mapBy` instead
|
|
@param dependentKey
|
|
@param propertyKey
|
|
*/
|
|
var mapProperty = mapBy;
|
|
__exports__.mapProperty = mapProperty;
|
|
/**
|
|
Filters the array by the callback.
|
|
|
|
The callback method you provide should have the following signature.
|
|
`item` is the current item in the iteration.
|
|
`index` is the integer index of the current item in the iteration.
|
|
`array` is the dependant array itself.
|
|
|
|
```javascript
|
|
function(item, index, array);
|
|
```
|
|
|
|
```javascript
|
|
var Hamster = Ember.Object.extend({
|
|
remainingChores: Ember.computed.filter('chores', function(chore, index, array) {
|
|
return !chore.done;
|
|
})
|
|
});
|
|
|
|
var hamster = Hamster.create({
|
|
chores: [
|
|
{ name: 'cook', done: true },
|
|
{ name: 'clean', done: true },
|
|
{ name: 'write more unit tests', done: false }
|
|
]
|
|
});
|
|
|
|
hamster.get('remainingChores'); // [{name: 'write more unit tests', done: false}]
|
|
```
|
|
|
|
@method computed.filter
|
|
@for Ember
|
|
@param {String} dependentKey
|
|
@param {Function} callback
|
|
@return {Ember.ComputedProperty} the filtered array
|
|
*/
|
|
function filter(dependentKey, callback) {
|
|
var options = {
|
|
initialize: function (array, changeMeta, instanceMeta) {
|
|
instanceMeta.filteredArrayIndexes = new SubArray();
|
|
},
|
|
|
|
addedItem: function (array, item, changeMeta, instanceMeta) {
|
|
var match = !!callback.call(this, item, changeMeta.index, changeMeta.arrayChanged);
|
|
var filterIndex = instanceMeta.filteredArrayIndexes.addItem(changeMeta.index, match);
|
|
|
|
if (match) {
|
|
array.insertAt(filterIndex, item);
|
|
}
|
|
|
|
return array;
|
|
},
|
|
|
|
removedItem: function(array, item, changeMeta, instanceMeta) {
|
|
var filterIndex = instanceMeta.filteredArrayIndexes.removeItem(changeMeta.index);
|
|
|
|
if (filterIndex > -1) {
|
|
array.removeAt(filterIndex);
|
|
}
|
|
|
|
return array;
|
|
}
|
|
};
|
|
|
|
return arrayComputed(dependentKey, options);
|
|
}
|
|
|
|
__exports__.filter = filter;/**
|
|
Filters the array by the property and value
|
|
|
|
```javascript
|
|
var Hamster = Ember.Object.extend({
|
|
remainingChores: Ember.computed.filterBy('chores', 'done', false)
|
|
});
|
|
|
|
var hamster = Hamster.create({
|
|
chores: [
|
|
{ name: 'cook', done: true },
|
|
{ name: 'clean', done: true },
|
|
{ name: 'write more unit tests', done: false }
|
|
]
|
|
});
|
|
|
|
hamster.get('remainingChores'); // [{ name: 'write more unit tests', done: false }]
|
|
```
|
|
|
|
@method computed.filterBy
|
|
@for Ember
|
|
@param {String} dependentKey
|
|
@param {String} propertyKey
|
|
@param {*} value
|
|
@return {Ember.ComputedProperty} the filtered array
|
|
*/
|
|
function filterBy (dependentKey, propertyKey, value) {
|
|
var callback;
|
|
|
|
if (arguments.length === 2) {
|
|
callback = function(item) {
|
|
return get(item, propertyKey);
|
|
};
|
|
} else {
|
|
callback = function(item) {
|
|
return get(item, propertyKey) === value;
|
|
};
|
|
}
|
|
|
|
return filter(dependentKey + '.@each.' + propertyKey, callback);
|
|
}
|
|
|
|
__exports__.filterBy = filterBy;/**
|
|
@method computed.filterProperty
|
|
@for Ember
|
|
@param dependentKey
|
|
@param propertyKey
|
|
@param value
|
|
@deprecated Use `Ember.computed.filterBy` instead
|
|
*/
|
|
var filterProperty = filterBy;
|
|
__exports__.filterProperty = filterProperty;
|
|
/**
|
|
A computed property which returns a new array with all the unique
|
|
elements from one or more dependent arrays.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
var Hamster = Ember.Object.extend({
|
|
uniqueFruits: Ember.computed.uniq('fruits')
|
|
});
|
|
|
|
var hamster = Hamster.create({
|
|
fruits: [
|
|
'banana',
|
|
'grape',
|
|
'kale',
|
|
'banana'
|
|
]
|
|
});
|
|
|
|
hamster.get('uniqueFruits'); // ['banana', 'grape', 'kale']
|
|
```
|
|
|
|
@method computed.uniq
|
|
@for Ember
|
|
@param {String} propertyKey*
|
|
@return {Ember.ComputedProperty} computes a new array with all the
|
|
unique elements from the dependent array
|
|
*/
|
|
function uniq() {
|
|
var args = a_slice.call(arguments);
|
|
|
|
args.push({
|
|
initialize: function(array, changeMeta, instanceMeta) {
|
|
instanceMeta.itemCounts = {};
|
|
},
|
|
|
|
addedItem: function(array, item, changeMeta, instanceMeta) {
|
|
var guid = guidFor(item);
|
|
|
|
if (!instanceMeta.itemCounts[guid]) {
|
|
instanceMeta.itemCounts[guid] = 1;
|
|
array.pushObject(item);
|
|
} else {
|
|
++instanceMeta.itemCounts[guid];
|
|
}
|
|
return array;
|
|
},
|
|
|
|
removedItem: function(array, item, _, instanceMeta) {
|
|
var guid = guidFor(item);
|
|
var itemCounts = instanceMeta.itemCounts;
|
|
|
|
if (--itemCounts[guid] === 0) {
|
|
array.removeObject(item);
|
|
}
|
|
|
|
return array;
|
|
}
|
|
});
|
|
|
|
return arrayComputed.apply(null, args);
|
|
}
|
|
|
|
__exports__.uniq = uniq;/**
|
|
Alias for [Ember.computed.uniq](/api/#method_computed_uniq).
|
|
|
|
@method computed.union
|
|
@for Ember
|
|
@param {String} propertyKey*
|
|
@return {Ember.ComputedProperty} computes a new array with all the
|
|
unique elements from the dependent array
|
|
*/
|
|
var union = uniq;
|
|
__exports__.union = union;
|
|
/**
|
|
A computed property which returns a new array with all the duplicated
|
|
elements from two or more dependent arrays.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
var obj = Ember.Object.createWithMixins({
|
|
adaFriends: ['Charles Babbage', 'John Hobhouse', 'William King', 'Mary Somerville'],
|
|
charlesFriends: ['William King', 'Mary Somerville', 'Ada Lovelace', 'George Peacock'],
|
|
friendsInCommon: Ember.computed.intersect('adaFriends', 'charlesFriends')
|
|
});
|
|
|
|
obj.get('friendsInCommon'); // ['William King', 'Mary Somerville']
|
|
```
|
|
|
|
@method computed.intersect
|
|
@for Ember
|
|
@param {String} propertyKey*
|
|
@return {Ember.ComputedProperty} computes a new array with all the
|
|
duplicated elements from the dependent arrays
|
|
*/
|
|
function intersect() {
|
|
var args = a_slice.call(arguments);
|
|
|
|
args.push({
|
|
initialize: function (array, changeMeta, instanceMeta) {
|
|
instanceMeta.itemCounts = {};
|
|
},
|
|
|
|
addedItem: function(array, item, changeMeta, instanceMeta) {
|
|
var itemGuid = guidFor(item);
|
|
var dependentGuid = guidFor(changeMeta.arrayChanged);
|
|
var numberOfDependentArrays = changeMeta.property._dependentKeys.length;
|
|
var itemCounts = instanceMeta.itemCounts;
|
|
|
|
if (!itemCounts[itemGuid]) {
|
|
itemCounts[itemGuid] = {};
|
|
}
|
|
|
|
if (itemCounts[itemGuid][dependentGuid] === undefined) {
|
|
itemCounts[itemGuid][dependentGuid] = 0;
|
|
}
|
|
|
|
if (++itemCounts[itemGuid][dependentGuid] === 1 &&
|
|
numberOfDependentArrays === keys(itemCounts[itemGuid]).length) {
|
|
array.addObject(item);
|
|
}
|
|
|
|
return array;
|
|
},
|
|
|
|
removedItem: function(array, item, changeMeta, instanceMeta) {
|
|
var itemGuid = guidFor(item);
|
|
var dependentGuid = guidFor(changeMeta.arrayChanged);
|
|
var numberOfArraysItemAppearsIn;
|
|
var itemCounts = instanceMeta.itemCounts;
|
|
|
|
if (itemCounts[itemGuid][dependentGuid] === undefined) {
|
|
itemCounts[itemGuid][dependentGuid] = 0;
|
|
}
|
|
|
|
if (--itemCounts[itemGuid][dependentGuid] === 0) {
|
|
delete itemCounts[itemGuid][dependentGuid];
|
|
numberOfArraysItemAppearsIn = keys(itemCounts[itemGuid]).length;
|
|
|
|
if (numberOfArraysItemAppearsIn === 0) {
|
|
delete itemCounts[itemGuid];
|
|
}
|
|
|
|
array.removeObject(item);
|
|
}
|
|
|
|
return array;
|
|
}
|
|
});
|
|
|
|
return arrayComputed.apply(null, args);
|
|
}
|
|
|
|
__exports__.intersect = intersect;/**
|
|
A computed property which returns a new array with all the
|
|
properties from the first dependent array that are not in the second
|
|
dependent array.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
var Hamster = Ember.Object.extend({
|
|
likes: ['banana', 'grape', 'kale'],
|
|
wants: Ember.computed.setDiff('likes', 'fruits')
|
|
});
|
|
|
|
var hamster = Hamster.create({
|
|
fruits: [
|
|
'grape',
|
|
'kale',
|
|
]
|
|
});
|
|
|
|
hamster.get('wants'); // ['banana']
|
|
```
|
|
|
|
@method computed.setDiff
|
|
@for Ember
|
|
@param {String} setAProperty
|
|
@param {String} setBProperty
|
|
@return {Ember.ComputedProperty} computes a new array with all the
|
|
items from the first dependent array that are not in the second
|
|
dependent array
|
|
*/
|
|
function setDiff(setAProperty, setBProperty) {
|
|
if (arguments.length !== 2) {
|
|
throw new EmberError('setDiff requires exactly two dependent arrays.');
|
|
}
|
|
|
|
return arrayComputed(setAProperty, setBProperty, {
|
|
addedItem: function (array, item, changeMeta, instanceMeta) {
|
|
var setA = get(this, setAProperty);
|
|
var setB = get(this, setBProperty);
|
|
|
|
if (changeMeta.arrayChanged === setA) {
|
|
if (!setB.contains(item)) {
|
|
array.addObject(item);
|
|
}
|
|
} else {
|
|
array.removeObject(item);
|
|
}
|
|
|
|
return array;
|
|
},
|
|
|
|
removedItem: function (array, item, changeMeta, instanceMeta) {
|
|
var setA = get(this, setAProperty);
|
|
var setB = get(this, setBProperty);
|
|
|
|
if (changeMeta.arrayChanged === setB) {
|
|
if (setA.contains(item)) {
|
|
array.addObject(item);
|
|
}
|
|
} else {
|
|
array.removeObject(item);
|
|
}
|
|
|
|
return array;
|
|
}
|
|
});
|
|
}
|
|
|
|
__exports__.setDiff = setDiff;function binarySearch(array, item, low, high) {
|
|
var mid, midItem, res, guidMid, guidItem;
|
|
|
|
if (arguments.length < 4) {
|
|
high = get(array, 'length');
|
|
}
|
|
|
|
if (arguments.length < 3) {
|
|
low = 0;
|
|
}
|
|
|
|
if (low === high) {
|
|
return low;
|
|
}
|
|
|
|
mid = low + Math.floor((high - low) / 2);
|
|
midItem = array.objectAt(mid);
|
|
|
|
guidMid = guidFor(midItem);
|
|
guidItem = guidFor(item);
|
|
|
|
if (guidMid === guidItem) {
|
|
return mid;
|
|
}
|
|
|
|
res = this.order(midItem, item);
|
|
|
|
if (res === 0) {
|
|
res = guidMid < guidItem ? -1 : 1;
|
|
}
|
|
|
|
|
|
if (res < 0) {
|
|
return this.binarySearch(array, item, mid+1, high);
|
|
} else if (res > 0) {
|
|
return this.binarySearch(array, item, low, mid);
|
|
}
|
|
|
|
return mid;
|
|
}
|
|
|
|
|
|
/**
|
|
A computed property which returns a new array with all the
|
|
properties from the first dependent array sorted based on a property
|
|
or sort function.
|
|
|
|
The callback method you provide should have the following signature:
|
|
|
|
```javascript
|
|
function(itemA, itemB);
|
|
```
|
|
|
|
- `itemA` the first item to compare.
|
|
- `itemB` the second item to compare.
|
|
|
|
This function should return negative number (e.g. `-1`) when `itemA` should come before
|
|
`itemB`. It should return positive number (e.g. `1`) when `itemA` should come after
|
|
`itemB`. If the `itemA` and `itemB` are equal this function should return `0`.
|
|
|
|
Therefore, if this function is comparing some numeric values, simple `itemA - itemB` or
|
|
`itemA.get( 'foo' ) - itemB.get( 'foo' )` can be used instead of series of `if`.
|
|
|
|
Example
|
|
|
|
```javascript
|
|
var ToDoList = Ember.Object.extend({
|
|
// using standard ascending sort
|
|
todosSorting: ['name'],
|
|
sortedTodos: Ember.computed.sort('todos', 'todosSorting'),
|
|
|
|
// using descending sort
|
|
todosSortingDesc: ['name:desc'],
|
|
sortedTodosDesc: Ember.computed.sort('todos', 'todosSortingDesc'),
|
|
|
|
// using a custom sort function
|
|
priorityTodos: Ember.computed.sort('todos', function(a, b){
|
|
if (a.priority > b.priority) {
|
|
return 1;
|
|
} else if (a.priority < b.priority) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
})
|
|
});
|
|
|
|
var todoList = ToDoList.create({todos: [
|
|
{ name: 'Unit Test', priority: 2 },
|
|
{ name: 'Documentation', priority: 3 },
|
|
{ name: 'Release', priority: 1 }
|
|
]});
|
|
|
|
todoList.get('sortedTodos'); // [{ name:'Documentation', priority:3 }, { name:'Release', priority:1 }, { name:'Unit Test', priority:2 }]
|
|
todoList.get('sortedTodosDesc'); // [{ name:'Unit Test', priority:2 }, { name:'Release', priority:1 }, { name:'Documentation', priority:3 }]
|
|
todoList.get('priorityTodos'); // [{ name:'Release', priority:1 }, { name:'Unit Test', priority:2 }, { name:'Documentation', priority:3 }]
|
|
```
|
|
|
|
@method computed.sort
|
|
@for Ember
|
|
@param {String} dependentKey
|
|
@param {String or Function} sortDefinition a dependent key to an
|
|
array of sort properties (add `:desc` to the arrays sort properties to sort descending) or a function to use when sorting
|
|
@return {Ember.ComputedProperty} computes a new sorted array based
|
|
on the sort property array or callback function
|
|
*/
|
|
function sort(itemsKey, sortDefinition) {
|
|
Ember.assert('Ember.computed.sort requires two arguments: an array key to sort and ' +
|
|
'either a sort properties key or sort function', arguments.length === 2);
|
|
|
|
if (typeof sortDefinition === 'function') {
|
|
return customSort(itemsKey, sortDefinition);
|
|
} else {
|
|
return propertySort(itemsKey, sortDefinition);
|
|
}
|
|
}
|
|
|
|
__exports__.sort = sort;function customSort(itemsKey, comparator) {
|
|
return arrayComputed(itemsKey, {
|
|
initialize: function (array, changeMeta, instanceMeta) {
|
|
instanceMeta.order = comparator;
|
|
instanceMeta.binarySearch = binarySearch;
|
|
instanceMeta.waitingInsertions = [];
|
|
instanceMeta.insertWaiting = function() {
|
|
var index, item;
|
|
var waiting = instanceMeta.waitingInsertions;
|
|
instanceMeta.waitingInsertions = [];
|
|
for (var i=0; i<waiting.length; i++) {
|
|
item = waiting[i];
|
|
index = instanceMeta.binarySearch(array, item);
|
|
array.insertAt(index, item);
|
|
}
|
|
};
|
|
instanceMeta.insertLater = function(item) {
|
|
this.waitingInsertions.push(item);
|
|
};
|
|
},
|
|
|
|
addedItem: function (array, item, changeMeta, instanceMeta) {
|
|
instanceMeta.insertLater(item);
|
|
return array;
|
|
},
|
|
|
|
removedItem: function (array, item, changeMeta, instanceMeta) {
|
|
array.removeObject(item);
|
|
return array;
|
|
},
|
|
|
|
flushedChanges: function(array, instanceMeta) {
|
|
instanceMeta.insertWaiting();
|
|
}
|
|
});
|
|
}
|
|
|
|
function propertySort(itemsKey, sortPropertiesKey) {
|
|
return arrayComputed(itemsKey, {
|
|
initialize: function (array, changeMeta, instanceMeta) {
|
|
function setupSortProperties() {
|
|
var sortPropertyDefinitions = get(this, sortPropertiesKey);
|
|
var sortProperties = instanceMeta.sortProperties = [];
|
|
var sortPropertyAscending = instanceMeta.sortPropertyAscending = {};
|
|
var sortProperty, idx, asc;
|
|
|
|
Ember.assert('Cannot sort: \'' + sortPropertiesKey + '\' is not an array.',
|
|
isArray(sortPropertyDefinitions));
|
|
|
|
changeMeta.property.clearItemPropertyKeys(itemsKey);
|
|
|
|
forEach(sortPropertyDefinitions, function (sortPropertyDefinition) {
|
|
if ((idx = sortPropertyDefinition.indexOf(':')) !== -1) {
|
|
sortProperty = sortPropertyDefinition.substring(0, idx);
|
|
asc = sortPropertyDefinition.substring(idx+1).toLowerCase() !== 'desc';
|
|
} else {
|
|
sortProperty = sortPropertyDefinition;
|
|
asc = true;
|
|
}
|
|
|
|
sortProperties.push(sortProperty);
|
|
sortPropertyAscending[sortProperty] = asc;
|
|
changeMeta.property.itemPropertyKey(itemsKey, sortProperty);
|
|
});
|
|
|
|
sortPropertyDefinitions.addObserver('@each', this, updateSortPropertiesOnce);
|
|
}
|
|
|
|
function updateSortPropertiesOnce() {
|
|
run.once(this, updateSortProperties, changeMeta.propertyName);
|
|
}
|
|
|
|
function updateSortProperties(propertyName) {
|
|
setupSortProperties.call(this);
|
|
changeMeta.property.recomputeOnce.call(this, propertyName);
|
|
}
|
|
|
|
addObserver(this, sortPropertiesKey, updateSortPropertiesOnce);
|
|
setupSortProperties.call(this);
|
|
|
|
instanceMeta.order = function (itemA, itemB) {
|
|
var sortProperty, result, asc;
|
|
var keyA = this.keyFor(itemA);
|
|
var keyB = this.keyFor(itemB);
|
|
|
|
for (var i = 0; i < this.sortProperties.length; ++i) {
|
|
sortProperty = this.sortProperties[i];
|
|
|
|
result = compare(keyA[sortProperty], keyB[sortProperty]);
|
|
|
|
if (result !== 0) {
|
|
asc = this.sortPropertyAscending[sortProperty];
|
|
return asc ? result : (-1 * result);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
};
|
|
|
|
instanceMeta.binarySearch = binarySearch;
|
|
setupKeyCache(instanceMeta);
|
|
},
|
|
|
|
addedItem: function (array, item, changeMeta, instanceMeta) {
|
|
var index = instanceMeta.binarySearch(array, item);
|
|
array.insertAt(index, item);
|
|
return array;
|
|
},
|
|
|
|
removedItem: function (array, item, changeMeta, instanceMeta) {
|
|
var index = instanceMeta.binarySearch(array, item);
|
|
array.removeAt(index);
|
|
instanceMeta.dropKeyFor(item);
|
|
return array;
|
|
}
|
|
});
|
|
}
|
|
|
|
function setupKeyCache(instanceMeta) {
|
|
instanceMeta.keyFor = function(item) {
|
|
var guid = guidFor(item);
|
|
if (this.keyCache[guid]) {
|
|
return this.keyCache[guid];
|
|
}
|
|
var sortProperty;
|
|
var key = {};
|
|
for (var i = 0; i < this.sortProperties.length; ++i) {
|
|
sortProperty = this.sortProperties[i];
|
|
key[sortProperty] = get(item, sortProperty);
|
|
}
|
|
return this.keyCache[guid] = key;
|
|
};
|
|
|
|
instanceMeta.dropKeyFor = function(item) {
|
|
var guid = guidFor(item);
|
|
this.keyCache[guid] = null;
|
|
};
|
|
|
|
instanceMeta.keyCache = {};
|
|
}
|
|
});
|
|
enifed("ember-runtime/controllers/array_controller",
|
|
["ember-metal/core","ember-metal/property_get","ember-metal/enumerable_utils","ember-runtime/system/array_proxy","ember-runtime/mixins/sortable","ember-runtime/mixins/controller","ember-metal/computed","ember-metal/error","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
var get = __dependency2__.get;
|
|
var forEach = __dependency3__.forEach;
|
|
var replace = __dependency3__.replace;
|
|
var ArrayProxy = __dependency4__["default"];
|
|
var SortableMixin = __dependency5__["default"];
|
|
var ControllerMixin = __dependency6__["default"];
|
|
var computed = __dependency7__.computed;
|
|
var EmberError = __dependency8__["default"];
|
|
|
|
|
|
/**
|
|
`Ember.ArrayController` provides a way for you to publish a collection of
|
|
objects so that you can easily bind to the collection from a Handlebars
|
|
`#each` helper, an `Ember.CollectionView`, or other controllers.
|
|
|
|
The advantage of using an `ArrayController` is that you only have to set up
|
|
your view bindings once; to change what's displayed, simply swap out the
|
|
`model` property on the controller.
|
|
|
|
For example, imagine you wanted to display a list of items fetched via an XHR
|
|
request. Create an `Ember.ArrayController` and set its `model` property:
|
|
|
|
```javascript
|
|
MyApp.listController = Ember.ArrayController.create();
|
|
|
|
$.get('people.json', function(data) {
|
|
MyApp.listController.set('model', data);
|
|
});
|
|
```
|
|
|
|
Then, create a view that binds to your new controller:
|
|
|
|
```handlebars
|
|
{{#each person in MyApp.listController}}
|
|
{{person.firstName}} {{person.lastName}}
|
|
{{/each}}
|
|
```
|
|
|
|
Although you are binding to the controller, the behavior of this controller
|
|
is to pass through any methods or properties to the underlying array. This
|
|
capability comes from `Ember.ArrayProxy`, which this class inherits from.
|
|
|
|
Sometimes you want to display computed properties within the body of an
|
|
`#each` helper that depend on the underlying items in `model`, but are not
|
|
present on those items. To do this, set `itemController` to the name of a
|
|
controller (probably an `ObjectController`) that will wrap each individual item.
|
|
|
|
For example:
|
|
|
|
```handlebars
|
|
{{#each post in controller}}
|
|
<li>{{post.title}} ({{post.titleLength}} characters)</li>
|
|
{{/each}}
|
|
```
|
|
|
|
```javascript
|
|
App.PostsController = Ember.ArrayController.extend({
|
|
itemController: 'post'
|
|
});
|
|
|
|
App.PostController = Ember.ObjectController.extend({
|
|
// the `title` property will be proxied to the underlying post.
|
|
titleLength: function() {
|
|
return this.get('title').length;
|
|
}.property('title')
|
|
});
|
|
```
|
|
|
|
In some cases it is helpful to return a different `itemController` depending
|
|
on the particular item. Subclasses can do this by overriding
|
|
`lookupItemController`.
|
|
|
|
For example:
|
|
|
|
```javascript
|
|
App.MyArrayController = Ember.ArrayController.extend({
|
|
lookupItemController: function( object ) {
|
|
if (object.get('isSpecial')) {
|
|
return "special"; // use App.SpecialController
|
|
} else {
|
|
return "regular"; // use App.RegularController
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
The itemController instances will have a `parentController` property set to
|
|
the `ArrayController` instance.
|
|
|
|
@class ArrayController
|
|
@namespace Ember
|
|
@extends Ember.ArrayProxy
|
|
@uses Ember.SortableMixin
|
|
@uses Ember.ControllerMixin
|
|
*/
|
|
|
|
__exports__["default"] = ArrayProxy.extend(ControllerMixin, SortableMixin, {
|
|
|
|
/**
|
|
A string containing the controller name used to wrap items.
|
|
|
|
For example:
|
|
|
|
```javascript
|
|
App.MyArrayController = Ember.ArrayController.extend({
|
|
itemController: 'myItem' // use App.MyItemController
|
|
});
|
|
```
|
|
|
|
@property itemController
|
|
@type String
|
|
@default null
|
|
*/
|
|
itemController: null,
|
|
|
|
/**
|
|
Return the name of the controller to wrap items, or `null` if items should
|
|
be returned directly. The default implementation simply returns the
|
|
`itemController` property, but subclasses can override this method to return
|
|
different controllers for different objects.
|
|
|
|
For example:
|
|
|
|
```javascript
|
|
App.MyArrayController = Ember.ArrayController.extend({
|
|
lookupItemController: function( object ) {
|
|
if (object.get('isSpecial')) {
|
|
return "special"; // use App.SpecialController
|
|
} else {
|
|
return "regular"; // use App.RegularController
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
@method lookupItemController
|
|
@param {Object} object
|
|
@return {String}
|
|
*/
|
|
lookupItemController: function(object) {
|
|
return get(this, 'itemController');
|
|
},
|
|
|
|
objectAtContent: function(idx) {
|
|
var length = get(this, 'length');
|
|
var arrangedContent = get(this, 'arrangedContent');
|
|
var object = arrangedContent && arrangedContent.objectAt(idx);
|
|
var controllerClass;
|
|
|
|
if (idx >= 0 && idx < length) {
|
|
controllerClass = this.lookupItemController(object);
|
|
|
|
if (controllerClass) {
|
|
return this.controllerAt(idx, object, controllerClass);
|
|
}
|
|
}
|
|
|
|
// When `controllerClass` is falsy, we have not opted in to using item
|
|
// controllers, so return the object directly.
|
|
|
|
// When the index is out of range, we want to return the "out of range"
|
|
// value, whatever that might be. Rather than make assumptions
|
|
// (e.g. guessing `null` or `undefined`) we defer this to `arrangedContent`.
|
|
return object;
|
|
},
|
|
|
|
arrangedContentDidChange: function() {
|
|
this._super();
|
|
this._resetSubControllers();
|
|
},
|
|
|
|
arrayContentDidChange: function(idx, removedCnt, addedCnt) {
|
|
var subControllers = this._subControllers;
|
|
|
|
if (subControllers.length) {
|
|
var subControllersToRemove = subControllers.slice(idx, idx + removedCnt);
|
|
|
|
forEach(subControllersToRemove, function(subController) {
|
|
if (subController) {
|
|
subController.destroy();
|
|
}
|
|
});
|
|
|
|
replace(subControllers, idx, removedCnt, new Array(addedCnt));
|
|
}
|
|
|
|
// The shadow array of subcontrollers must be updated before we trigger
|
|
// observers, otherwise observers will get the wrong subcontainer when
|
|
// calling `objectAt`
|
|
this._super(idx, removedCnt, addedCnt);
|
|
},
|
|
|
|
init: function() {
|
|
this._super();
|
|
this._subControllers = [];
|
|
},
|
|
|
|
model: computed(function () {
|
|
return Ember.A();
|
|
}),
|
|
|
|
/**
|
|
* Flag to mark as being "virtual". Used to keep this instance
|
|
* from participating in the parentController hierarchy.
|
|
*
|
|
* @private
|
|
* @property _isVirtual
|
|
* @type Boolean
|
|
*/
|
|
_isVirtual: false,
|
|
|
|
controllerAt: function(idx, object, controllerClass) {
|
|
var container = get(this, 'container');
|
|
var subControllers = this._subControllers;
|
|
var fullName, subController, subControllerFactory, parentController, options;
|
|
|
|
if (subControllers.length > idx) {
|
|
subController = subControllers[idx];
|
|
|
|
if (subController) {
|
|
return subController;
|
|
}
|
|
}
|
|
|
|
if (this._isVirtual) {
|
|
parentController = get(this, 'parentController');
|
|
} else {
|
|
parentController = this;
|
|
}
|
|
|
|
|
|
fullName = 'controller:' + controllerClass;
|
|
|
|
if (!container.has(fullName)) {
|
|
throw new EmberError('Could not resolve itemController: "' + controllerClass + '"');
|
|
}
|
|
|
|
subController = container.lookupFactory(fullName).create({
|
|
target: parentController,
|
|
parentController: parentController,
|
|
model: object
|
|
});
|
|
|
|
|
|
subControllers[idx] = subController;
|
|
|
|
return subController;
|
|
},
|
|
|
|
_subControllers: null,
|
|
|
|
_resetSubControllers: function() {
|
|
var controller;
|
|
var subControllers = this._subControllers;
|
|
|
|
if (subControllers.length) {
|
|
for (var i = 0, length = subControllers.length; length > i; i++) {
|
|
controller = subControllers[i];
|
|
|
|
if (controller) {
|
|
controller.destroy();
|
|
}
|
|
}
|
|
|
|
subControllers.length = 0;
|
|
}
|
|
},
|
|
|
|
willDestroy: function() {
|
|
this._resetSubControllers();
|
|
this._super();
|
|
}
|
|
});
|
|
});
|
|
enifed("ember-runtime/controllers/controller",
|
|
["ember-metal/core","ember-runtime/system/object","ember-runtime/mixins/controller","ember-runtime/inject","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.assert
|
|
var EmberObject = __dependency2__["default"];
|
|
var Mixin = __dependency3__["default"];
|
|
var createInjectionHelper = __dependency4__.createInjectionHelper;
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
|
|
/**
|
|
@class Controller
|
|
@namespace Ember
|
|
@extends Ember.Object
|
|
@uses Ember.ControllerMixin
|
|
*/
|
|
var Controller = EmberObject.extend(Mixin);
|
|
|
|
function controllerInjectionHelper(factory) {
|
|
Ember.assert("Defining an injected controller property on a " +
|
|
"non-controller is not allowed.", Controller.detect(factory));
|
|
}
|
|
|
|
|
|
/**
|
|
Creates a property that lazily looks up another controller in the container.
|
|
Can only be used when defining another controller.
|
|
|
|
Example:
|
|
|
|
```javascript
|
|
App.PostController = Ember.Controller.extend({
|
|
posts: Ember.inject.controller()
|
|
});
|
|
```
|
|
|
|
This example will create a `posts` property on the `post` controller that
|
|
looks up the `posts` controller in the container, making it easy to
|
|
reference other controllers. This is functionally equivalent to:
|
|
|
|
```javascript
|
|
App.PostController = Ember.Controller.extend({
|
|
needs: 'posts',
|
|
posts: Ember.computed.alias('controllers.posts')
|
|
});
|
|
```
|
|
|
|
@method inject.controller
|
|
@for Ember
|
|
@param {String} name (optional) name of the controller to inject, defaults
|
|
to the property's name
|
|
@return {Ember.InjectedProperty} injection descriptor instance
|
|
*/
|
|
createInjectionHelper('controller', controllerInjectionHelper);
|
|
|
|
|
|
__exports__["default"] = Controller;
|
|
});
|
|
enifed("ember-runtime/controllers/object_controller",
|
|
["ember-runtime/mixins/controller","ember-runtime/system/object_proxy","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var ControllerMixin = __dependency1__["default"];
|
|
var ObjectProxy = __dependency2__["default"];
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
|
|
/**
|
|
`Ember.ObjectController` is part of Ember's Controller layer. It is intended
|
|
to wrap a single object, proxying unhandled attempts to `get` and `set` to the underlying
|
|
model object, and to forward unhandled action attempts to its `target`.
|
|
|
|
`Ember.ObjectController` derives this functionality from its superclass
|
|
`Ember.ObjectProxy` and the `Ember.ControllerMixin` mixin.
|
|
|
|
@class ObjectController
|
|
@namespace Ember
|
|
@extends Ember.ObjectProxy
|
|
@uses Ember.ControllerMixin
|
|
**/
|
|
__exports__["default"] = ObjectProxy.extend(ControllerMixin);
|
|
});
|
|
enifed("ember-runtime/copy",
|
|
["ember-metal/enumerable_utils","ember-metal/utils","ember-runtime/system/object","ember-runtime/mixins/copyable","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
|
|
"use strict";
|
|
var indexOf = __dependency1__.indexOf;
|
|
var typeOf = __dependency2__.typeOf;
|
|
var EmberObject = __dependency3__["default"];
|
|
var Copyable = __dependency4__["default"];
|
|
|
|
function _copy(obj, deep, seen, copies) {
|
|
var ret, loc, key;
|
|
|
|
// primitive data types are immutable, just return them.
|
|
if (typeof obj !== 'object' || obj === null) {
|
|
return obj;
|
|
}
|
|
|
|
// avoid cyclical loops
|
|
if (deep && (loc = indexOf(seen, obj)) >= 0) {
|
|
return copies[loc];
|
|
}
|
|
|
|
Ember.assert('Cannot clone an Ember.Object that does not implement Ember.Copyable',
|
|
!(obj instanceof EmberObject) || (Copyable && Copyable.detect(obj)));
|
|
|
|
// IMPORTANT: this specific test will detect a native array only. Any other
|
|
// object will need to implement Copyable.
|
|
if (typeOf(obj) === 'array') {
|
|
ret = obj.slice();
|
|
|
|
if (deep) {
|
|
loc = ret.length;
|
|
|
|
while (--loc >= 0) {
|
|
ret[loc] = _copy(ret[loc], deep, seen, copies);
|
|
}
|
|
}
|
|
} else if (Copyable && Copyable.detect(obj)) {
|
|
ret = obj.copy(deep, seen, copies);
|
|
} else if (obj instanceof Date) {
|
|
ret = new Date(obj.getTime());
|
|
} else {
|
|
ret = {};
|
|
|
|
for (key in obj) {
|
|
// support Null prototype
|
|
if (!Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
continue;
|
|
}
|
|
|
|
// Prevents browsers that don't respect non-enumerability from
|
|
// copying internal Ember properties
|
|
if (key.substring(0, 2) === '__') {
|
|
continue;
|
|
}
|
|
|
|
ret[key] = deep ? _copy(obj[key], deep, seen, copies) : obj[key];
|
|
}
|
|
}
|
|
|
|
if (deep) {
|
|
seen.push(obj);
|
|
copies.push(ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
Creates a clone of the passed object. This function can take just about
|
|
any type of object and create a clone of it, including primitive values
|
|
(which are not actually cloned because they are immutable).
|
|
|
|
If the passed object implements the `copy()` method, then this function
|
|
will simply call that method and return the result. Please see
|
|
`Ember.Copyable` for further details.
|
|
|
|
@method copy
|
|
@for Ember
|
|
@param {Object} obj The object to clone
|
|
@param {Boolean} deep If true, a deep copy of the object is made
|
|
@return {Object} The cloned object
|
|
*/
|
|
__exports__["default"] = function copy(obj, deep) {
|
|
// fast paths
|
|
if ('object' !== typeof obj || obj === null) {
|
|
return obj; // can't copy primitives
|
|
}
|
|
|
|
if (Copyable && Copyable.detect(obj)) {
|
|
return obj.copy(deep);
|
|
}
|
|
|
|
return _copy(obj, deep, deep ? [] : null, deep ? [] : null);
|
|
}
|
|
});
|
|
enifed("ember-runtime/core",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
|
|
/**
|
|
Compares two objects, returning true if they are logically equal. This is
|
|
a deeper comparison than a simple triple equal. For sets it will compare the
|
|
internal objects. For any other object that implements `isEqual()` it will
|
|
respect that method.
|
|
|
|
```javascript
|
|
Ember.isEqual('hello', 'hello'); // true
|
|
Ember.isEqual(1, 2); // false
|
|
Ember.isEqual([4, 2], [4, 2]); // false
|
|
```
|
|
|
|
@method isEqual
|
|
@for Ember
|
|
@param {Object} a first object to compare
|
|
@param {Object} b second object to compare
|
|
@return {Boolean}
|
|
*/
|
|
var isEqual = function isEqual(a, b) {
|
|
if (a && typeof a.isEqual === 'function') {
|
|
return a.isEqual(b);
|
|
}
|
|
|
|
if (a instanceof Date && b instanceof Date) {
|
|
return a.getTime() === b.getTime();
|
|
}
|
|
|
|
return a === b;
|
|
};
|
|
__exports__.isEqual = isEqual;
|
|
});
|
|
enifed("ember-runtime/ext/function",
|
|
["ember-metal/core","ember-metal/expand_properties","ember-metal/computed","ember-metal/mixin"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.EXTEND_PROTOTYPES, Ember.assert
|
|
var expandProperties = __dependency2__["default"];
|
|
var computed = __dependency3__.computed;
|
|
var observer = __dependency4__.observer;
|
|
|
|
var a_slice = Array.prototype.slice;
|
|
var FunctionPrototype = Function.prototype;
|
|
|
|
if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Function) {
|
|
|
|
/**
|
|
The `property` extension of Javascript's Function prototype is available
|
|
when `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Function` is
|
|
`true`, which is the default.
|
|
|
|
Computed properties allow you to treat a function like a property:
|
|
|
|
```javascript
|
|
MyApp.President = Ember.Object.extend({
|
|
firstName: '',
|
|
lastName: '',
|
|
|
|
fullName: function() {
|
|
return this.get('firstName') + ' ' + this.get('lastName');
|
|
}.property() // Call this flag to mark the function as a property
|
|
});
|
|
|
|
var president = MyApp.President.create({
|
|
firstName: 'Barack',
|
|
lastName: 'Obama'
|
|
});
|
|
|
|
president.get('fullName'); // 'Barack Obama'
|
|
```
|
|
|
|
Treating a function like a property is useful because they can work with
|
|
bindings, just like any other property.
|
|
|
|
Many computed properties have dependencies on other properties. For
|
|
example, in the above example, the `fullName` property depends on
|
|
`firstName` and `lastName` to determine its value. You can tell Ember
|
|
about these dependencies like this:
|
|
|
|
```javascript
|
|
MyApp.President = Ember.Object.extend({
|
|
firstName: '',
|
|
lastName: '',
|
|
|
|
fullName: function() {
|
|
return this.get('firstName') + ' ' + this.get('lastName');
|
|
|
|
// Tell Ember.js that this computed property depends on firstName
|
|
// and lastName
|
|
}.property('firstName', 'lastName')
|
|
});
|
|
```
|
|
|
|
Make sure you list these dependencies so Ember knows when to update
|
|
bindings that connect to a computed property. Changing a dependency
|
|
will not immediately trigger an update of the computed property, but
|
|
will instead clear the cache so that it is updated when the next `get`
|
|
is called on the property.
|
|
|
|
See [Ember.ComputedProperty](/api/classes/Ember.ComputedProperty.html), [Ember.computed](/api/#method_computed).
|
|
|
|
@method property
|
|
@for Function
|
|
*/
|
|
FunctionPrototype.property = function () {
|
|
var ret = computed(this);
|
|
// ComputedProperty.prototype.property expands properties; no need for us to
|
|
// do so here.
|
|
return ret.property.apply(ret, arguments);
|
|
};
|
|
|
|
/**
|
|
The `observes` extension of Javascript's Function prototype is available
|
|
when `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Function` is
|
|
true, which is the default.
|
|
|
|
You can observe property changes simply by adding the `observes`
|
|
call to the end of your method declarations in classes that you write.
|
|
For example:
|
|
|
|
```javascript
|
|
Ember.Object.extend({
|
|
valueObserver: function() {
|
|
// Executes whenever the "value" property changes
|
|
}.observes('value')
|
|
});
|
|
```
|
|
|
|
In the future this method may become asynchronous. If you want to ensure
|
|
synchronous behavior, use `observesImmediately`.
|
|
|
|
See `Ember.observer`.
|
|
|
|
@method observes
|
|
@for Function
|
|
*/
|
|
FunctionPrototype.observes = function() {
|
|
var length = arguments.length;
|
|
var args = new Array(length);
|
|
for (var x = 0; x < length; x++) {
|
|
args[x] = arguments[x];
|
|
}
|
|
return observer.apply(this, args.concat(this));
|
|
};
|
|
|
|
/**
|
|
The `observesImmediately` extension of Javascript's Function prototype is
|
|
available when `Ember.EXTEND_PROTOTYPES` or
|
|
`Ember.EXTEND_PROTOTYPES.Function` is true, which is the default.
|
|
|
|
You can observe property changes simply by adding the `observesImmediately`
|
|
call to the end of your method declarations in classes that you write.
|
|
For example:
|
|
|
|
```javascript
|
|
Ember.Object.extend({
|
|
valueObserver: function() {
|
|
// Executes immediately after the "value" property changes
|
|
}.observesImmediately('value')
|
|
});
|
|
```
|
|
|
|
In the future, `observes` may become asynchronous. In this event,
|
|
`observesImmediately` will maintain the synchronous behavior.
|
|
|
|
See `Ember.immediateObserver`.
|
|
|
|
@method observesImmediately
|
|
@for Function
|
|
*/
|
|
FunctionPrototype.observesImmediately = function () {
|
|
Ember.assert('Immediate observers must observe internal properties only, ' +
|
|
'not properties on other objects.', function checkIsInternalProperty() {
|
|
for(var i = 0, l = arguments.length; i < l; i++) {
|
|
if(arguments[i].indexOf('.') !== -1) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
|
|
// observes handles property expansion
|
|
return this.observes.apply(this, arguments);
|
|
};
|
|
|
|
/**
|
|
The `observesBefore` extension of Javascript's Function prototype is
|
|
available when `Ember.EXTEND_PROTOTYPES` or
|
|
`Ember.EXTEND_PROTOTYPES.Function` is true, which is the default.
|
|
|
|
You can get notified when a property change is about to happen by
|
|
by adding the `observesBefore` call to the end of your method
|
|
declarations in classes that you write. For example:
|
|
|
|
```javascript
|
|
Ember.Object.extend({
|
|
valueObserver: function() {
|
|
// Executes whenever the "value" property is about to change
|
|
}.observesBefore('value')
|
|
});
|
|
```
|
|
|
|
See `Ember.beforeObserver`.
|
|
|
|
@method observesBefore
|
|
@for Function
|
|
*/
|
|
FunctionPrototype.observesBefore = function () {
|
|
var watched = [];
|
|
var addWatchedProperty = function (obs) {
|
|
watched.push(obs);
|
|
};
|
|
|
|
for (var i = 0, l = arguments.length; i < l; ++i) {
|
|
expandProperties(arguments[i], addWatchedProperty);
|
|
}
|
|
|
|
this.__ember_observesBefore__ = watched;
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
The `on` extension of Javascript's Function prototype is available
|
|
when `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Function` is
|
|
true, which is the default.
|
|
|
|
You can listen for events simply by adding the `on` call to the end of
|
|
your method declarations in classes or mixins that you write. For example:
|
|
|
|
```javascript
|
|
Ember.Mixin.create({
|
|
doSomethingWithElement: function() {
|
|
// Executes whenever the "didInsertElement" event fires
|
|
}.on('didInsertElement')
|
|
});
|
|
```
|
|
|
|
See `Ember.on`.
|
|
|
|
@method on
|
|
@for Function
|
|
*/
|
|
FunctionPrototype.on = function () {
|
|
var events = a_slice.call(arguments);
|
|
this.__ember_listens__ = events;
|
|
|
|
return this;
|
|
};
|
|
}
|
|
});
|
|
enifed("ember-runtime/ext/rsvp",
|
|
["ember-metal/core","ember-metal/logger","ember-metal/run_loop","rsvp","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
|
|
"use strict";
|
|
/* globals RSVP:true */
|
|
|
|
var Ember = __dependency1__["default"];
|
|
var Logger = __dependency2__["default"];
|
|
var run = __dependency3__["default"];
|
|
|
|
// this is technically incorrect (per @wycats)
|
|
// it should be `import * as RSVP from 'rsvp';` but
|
|
// Esprima does not support this syntax yet (and neither does
|
|
// es6-module-transpiler 0.4.0 - 0.6.2).
|
|
var RSVP = __dependency4__;
|
|
|
|
var testModuleName = 'ember-testing/test';
|
|
var Test;
|
|
|
|
var asyncStart = function() {
|
|
if (Ember.Test && Ember.Test.adapter) {
|
|
Ember.Test.adapter.asyncStart();
|
|
}
|
|
};
|
|
|
|
var asyncEnd = function() {
|
|
if (Ember.Test && Ember.Test.adapter) {
|
|
Ember.Test.adapter.asyncEnd();
|
|
}
|
|
};
|
|
|
|
RSVP.configure('async', function(callback, promise) {
|
|
var async = !run.currentRunLoop;
|
|
|
|
if (Ember.testing && async) { asyncStart(); }
|
|
|
|
run.backburner.schedule('actions', function(){
|
|
if (Ember.testing && async) { asyncEnd(); }
|
|
callback(promise);
|
|
});
|
|
});
|
|
|
|
RSVP.Promise.prototype.fail = function(callback, label){
|
|
Ember.deprecate('RSVP.Promise.fail has been renamed as RSVP.Promise.catch');
|
|
return this['catch'](callback, label);
|
|
};
|
|
|
|
RSVP.onerrorDefault = function (e) {
|
|
var error;
|
|
|
|
if (e && e.errorThrown) {
|
|
// jqXHR provides this
|
|
error = e.errorThrown;
|
|
if (typeof error === 'string') {
|
|
error = new Error(error);
|
|
}
|
|
error.__reason_with_error_thrown__ = e;
|
|
} else {
|
|
error = e;
|
|
}
|
|
|
|
if (error && error.name !== 'TransitionAborted') {
|
|
if (Ember.testing) {
|
|
// ES6TODO: remove when possible
|
|
if (!Test && Ember.__loader.registry[testModuleName]) {
|
|
Test = requireModule(testModuleName)['default'];
|
|
}
|
|
|
|
if (Test && Test.adapter) {
|
|
Test.adapter.exception(error);
|
|
Logger.error(error.stack);
|
|
} else {
|
|
throw error;
|
|
}
|
|
} else if (Ember.onerror) {
|
|
Ember.onerror(error);
|
|
} else {
|
|
Logger.error(error.stack);
|
|
}
|
|
}
|
|
};
|
|
|
|
RSVP.on('error', RSVP.onerrorDefault);
|
|
|
|
__exports__["default"] = RSVP;
|
|
});
|
|
enifed("ember-runtime/ext/string",
|
|
["ember-metal/core","ember-runtime/system/string"],
|
|
function(__dependency1__, __dependency2__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.EXTEND_PROTOTYPES, Ember.assert, Ember.FEATURES
|
|
var fmt = __dependency2__.fmt;
|
|
var w = __dependency2__.w;
|
|
var loc = __dependency2__.loc;
|
|
var camelize = __dependency2__.camelize;
|
|
var decamelize = __dependency2__.decamelize;
|
|
var dasherize = __dependency2__.dasherize;
|
|
var underscore = __dependency2__.underscore;
|
|
var capitalize = __dependency2__.capitalize;
|
|
var classify = __dependency2__.classify;
|
|
|
|
var StringPrototype = String.prototype;
|
|
|
|
if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) {
|
|
|
|
/**
|
|
See [Ember.String.fmt](/api/classes/Ember.String.html#method_fmt).
|
|
|
|
@method fmt
|
|
@for String
|
|
*/
|
|
StringPrototype.fmt = function () {
|
|
return fmt(this, arguments);
|
|
};
|
|
|
|
/**
|
|
See [Ember.String.w](/api/classes/Ember.String.html#method_w).
|
|
|
|
@method w
|
|
@for String
|
|
*/
|
|
StringPrototype.w = function () {
|
|
return w(this);
|
|
};
|
|
|
|
/**
|
|
See [Ember.String.loc](/api/classes/Ember.String.html#method_loc).
|
|
|
|
@method loc
|
|
@for String
|
|
*/
|
|
StringPrototype.loc = function () {
|
|
return loc(this, arguments);
|
|
};
|
|
|
|
/**
|
|
See [Ember.String.camelize](/api/classes/Ember.String.html#method_camelize).
|
|
|
|
@method camelize
|
|
@for String
|
|
*/
|
|
StringPrototype.camelize = function () {
|
|
return camelize(this);
|
|
};
|
|
|
|
/**
|
|
See [Ember.String.decamelize](/api/classes/Ember.String.html#method_decamelize).
|
|
|
|
@method decamelize
|
|
@for String
|
|
*/
|
|
StringPrototype.decamelize = function () {
|
|
return decamelize(this);
|
|
};
|
|
|
|
/**
|
|
See [Ember.String.dasherize](/api/classes/Ember.String.html#method_dasherize).
|
|
|
|
@method dasherize
|
|
@for String
|
|
*/
|
|
StringPrototype.dasherize = function () {
|
|
return dasherize(this);
|
|
};
|
|
|
|
/**
|
|
See [Ember.String.underscore](/api/classes/Ember.String.html#method_underscore).
|
|
|
|
@method underscore
|
|
@for String
|
|
*/
|
|
StringPrototype.underscore = function () {
|
|
return underscore(this);
|
|
};
|
|
|
|
/**
|
|
See [Ember.String.classify](/api/classes/Ember.String.html#method_classify).
|
|
|
|
@method classify
|
|
@for String
|
|
*/
|
|
StringPrototype.classify = function () {
|
|
return classify(this);
|
|
};
|
|
|
|
/**
|
|
See [Ember.String.capitalize](/api/classes/Ember.String.html#method_capitalize).
|
|
|
|
@method capitalize
|
|
@for String
|
|
*/
|
|
StringPrototype.capitalize = function () {
|
|
return capitalize(this);
|
|
};
|
|
}
|
|
});
|
|
enifed("ember-runtime/inject",
|
|
["ember-metal/core","ember-metal/enumerable_utils","ember-metal/utils","ember-metal/injected_property","ember-metal/keys","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.assert
|
|
var indexOf = __dependency2__.indexOf;
|
|
var meta = __dependency3__.meta;
|
|
var InjectedProperty = __dependency4__["default"];
|
|
var keys = __dependency5__["default"];
|
|
|
|
/**
|
|
Namespace for injection helper methods.
|
|
|
|
@class inject
|
|
@namespace Ember
|
|
*/
|
|
function inject() {
|
|
Ember.assert("Injected properties must be created through helpers, see `" +
|
|
keys(inject).join("`, `") + "`");
|
|
}
|
|
|
|
// Dictionary of injection validations by type, added to by `createInjectionHelper`
|
|
var typeValidators = {};
|
|
|
|
/**
|
|
This method allows other Ember modules to register injection helpers for a
|
|
given container type. Helpers are exported to the `inject` namespace as the
|
|
container type itself.
|
|
|
|
@private
|
|
@method createInjectionHelper
|
|
@namespace Ember
|
|
@param {String} type The container type the helper will inject
|
|
@param {Function} validator A validation callback that is executed at mixin-time
|
|
*/
|
|
function createInjectionHelper(type, validator) {
|
|
typeValidators[type] = validator;
|
|
|
|
inject[type] = function(name) {
|
|
return new InjectedProperty(type, name);
|
|
};
|
|
}
|
|
|
|
__exports__.createInjectionHelper = createInjectionHelper;/**
|
|
Validation function that runs per-type validation functions once for each
|
|
injected type encountered.
|
|
|
|
@private
|
|
@method validatePropertyInjections
|
|
@namespace Ember
|
|
@param {Object} factory The factory object
|
|
*/
|
|
function validatePropertyInjections(factory) {
|
|
var proto = factory.proto();
|
|
var descs = meta(proto).descs;
|
|
var types = [];
|
|
var key, desc, validator, i, l;
|
|
|
|
for (key in descs) {
|
|
desc = descs[key];
|
|
if (desc instanceof InjectedProperty && indexOf(types, desc.type) === -1) {
|
|
types.push(desc.type);
|
|
}
|
|
}
|
|
|
|
if (types.length) {
|
|
for (i = 0, l = types.length; i < l; i++) {
|
|
validator = typeValidators[types[i]];
|
|
|
|
if (typeof validator === 'function') {
|
|
validator(factory);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
__exports__.validatePropertyInjections = validatePropertyInjections;__exports__["default"] = inject;
|
|
});
|
|
enifed("ember-runtime/mixins/-proxy",
|
|
["ember-metal/core","ember-metal/property_get","ember-metal/property_set","ember-metal/utils","ember-metal/observer","ember-metal/property_events","ember-metal/computed","ember-metal/properties","ember-metal/mixin","ember-runtime/system/string","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.assert
|
|
var get = __dependency2__.get;
|
|
var set = __dependency3__.set;
|
|
var meta = __dependency4__.meta;
|
|
var addObserver = __dependency5__.addObserver;
|
|
var removeObserver = __dependency5__.removeObserver;
|
|
var addBeforeObserver = __dependency5__.addBeforeObserver;
|
|
var removeBeforeObserver = __dependency5__.removeBeforeObserver;
|
|
var propertyWillChange = __dependency6__.propertyWillChange;
|
|
var propertyDidChange = __dependency6__.propertyDidChange;
|
|
var computed = __dependency7__.computed;
|
|
var defineProperty = __dependency8__.defineProperty;
|
|
var Mixin = __dependency9__.Mixin;
|
|
var observer = __dependency9__.observer;
|
|
var fmt = __dependency10__.fmt;
|
|
|
|
function contentPropertyWillChange(content, contentKey) {
|
|
var key = contentKey.slice(8); // remove "content."
|
|
if (key in this) { return; } // if shadowed in proxy
|
|
propertyWillChange(this, key);
|
|
}
|
|
|
|
function contentPropertyDidChange(content, contentKey) {
|
|
var key = contentKey.slice(8); // remove "content."
|
|
if (key in this) { return; } // if shadowed in proxy
|
|
propertyDidChange(this, key);
|
|
}
|
|
|
|
/**
|
|
`Ember.ProxyMixin` forwards all properties not defined by the proxy itself
|
|
to a proxied `content` object. See Ember.ObjectProxy for more details.
|
|
|
|
@class ProxyMixin
|
|
@namespace Ember
|
|
*/
|
|
__exports__["default"] = Mixin.create({
|
|
/**
|
|
The object whose properties will be forwarded.
|
|
|
|
@property content
|
|
@type Ember.Object
|
|
@default null
|
|
*/
|
|
content: null,
|
|
_contentDidChange: observer('content', function() {
|
|
Ember.assert("Can't set Proxy's content to itself", get(this, 'content') !== this);
|
|
}),
|
|
|
|
isTruthy: computed.bool('content'),
|
|
|
|
_debugContainerKey: null,
|
|
|
|
willWatchProperty: function (key) {
|
|
var contentKey = 'content.' + key;
|
|
addBeforeObserver(this, contentKey, null, contentPropertyWillChange);
|
|
addObserver(this, contentKey, null, contentPropertyDidChange);
|
|
},
|
|
|
|
didUnwatchProperty: function (key) {
|
|
var contentKey = 'content.' + key;
|
|
removeBeforeObserver(this, contentKey, null, contentPropertyWillChange);
|
|
removeObserver(this, contentKey, null, contentPropertyDidChange);
|
|
},
|
|
|
|
unknownProperty: function (key) {
|
|
var content = get(this, 'content');
|
|
if (content) {
|
|
return get(content, key);
|
|
}
|
|
},
|
|
|
|
setUnknownProperty: function (key, value) {
|
|
var m = meta(this);
|
|
if (m.proto === this) {
|
|
// if marked as prototype then just defineProperty
|
|
// rather than delegate
|
|
defineProperty(this, key, null, value);
|
|
return value;
|
|
}
|
|
|
|
var content = get(this, 'content');
|
|
Ember.assert(fmt("Cannot delegate set('%@', %@) to the 'content' property of" +
|
|
" object proxy %@: its 'content' is undefined.", [key, value, this]), content);
|
|
return set(content, key, value);
|
|
}
|
|
|
|
});
|
|
});
|
|
enifed("ember-runtime/mixins/action_handler",
|
|
["ember-metal/merge","ember-metal/mixin","ember-metal/property_get","ember-metal/utils","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
var merge = __dependency1__["default"];
|
|
var Mixin = __dependency2__.Mixin;
|
|
var get = __dependency3__.get;
|
|
var typeOf = __dependency4__.typeOf;
|
|
|
|
/**
|
|
The `Ember.ActionHandler` mixin implements support for moving an `actions`
|
|
property to an `_actions` property at extend time, and adding `_actions`
|
|
to the object's mergedProperties list.
|
|
|
|
`Ember.ActionHandler` is available on some familiar classes including
|
|
`Ember.Route`, `Ember.View`, `Ember.Component`, and controllers such as
|
|
`Ember.Controller` and `Ember.ObjectController`.
|
|
(Internally the mixin is used by `Ember.CoreView`, `Ember.ControllerMixin`,
|
|
and `Ember.Route` and available to the above classes through
|
|
inheritance.)
|
|
|
|
@class ActionHandler
|
|
@namespace Ember
|
|
*/
|
|
var ActionHandler = Mixin.create({
|
|
mergedProperties: ['_actions'],
|
|
|
|
/**
|
|
The collection of functions, keyed by name, available on this
|
|
`ActionHandler` as action targets.
|
|
|
|
These functions will be invoked when a matching `{{action}}` is triggered
|
|
from within a template and the application's current route is this route.
|
|
|
|
Actions can also be invoked from other parts of your application
|
|
via `ActionHandler#send`.
|
|
|
|
The `actions` hash will inherit action handlers from
|
|
the `actions` hash defined on extended parent classes
|
|
or mixins rather than just replace the entire hash, e.g.:
|
|
|
|
```js
|
|
App.CanDisplayBanner = Ember.Mixin.create({
|
|
actions: {
|
|
displayBanner: function(msg) {
|
|
// ...
|
|
}
|
|
}
|
|
});
|
|
|
|
App.WelcomeRoute = Ember.Route.extend(App.CanDisplayBanner, {
|
|
actions: {
|
|
playMusic: function() {
|
|
// ...
|
|
}
|
|
}
|
|
});
|
|
|
|
// `WelcomeRoute`, when active, will be able to respond
|
|
// to both actions, since the actions hash is merged rather
|
|
// then replaced when extending mixins / parent classes.
|
|
this.send('displayBanner');
|
|
this.send('playMusic');
|
|
```
|
|
|
|
Within a Controller, Route, View or Component's action handler,
|
|
the value of the `this` context is the Controller, Route, View or
|
|
Component object:
|
|
|
|
```js
|
|
App.SongRoute = Ember.Route.extend({
|
|
actions: {
|
|
myAction: function() {
|
|
this.controllerFor("song");
|
|
this.transitionTo("other.route");
|
|
...
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
It is also possible to call `this._super()` from within an
|
|
action handler if it overrides a handler defined on a parent
|
|
class or mixin:
|
|
|
|
Take for example the following routes:
|
|
|
|
```js
|
|
App.DebugRoute = Ember.Mixin.create({
|
|
actions: {
|
|
debugRouteInformation: function() {
|
|
console.debug("trololo");
|
|
}
|
|
}
|
|
});
|
|
|
|
App.AnnoyingDebugRoute = Ember.Route.extend(App.DebugRoute, {
|
|
actions: {
|
|
debugRouteInformation: function() {
|
|
// also call the debugRouteInformation of mixed in App.DebugRoute
|
|
this._super();
|
|
|
|
// show additional annoyance
|
|
window.alert(...);
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
## Bubbling
|
|
|
|
By default, an action will stop bubbling once a handler defined
|
|
on the `actions` hash handles it. To continue bubbling the action,
|
|
you must return `true` from the handler:
|
|
|
|
```js
|
|
App.Router.map(function() {
|
|
this.resource("album", function() {
|
|
this.route("song");
|
|
});
|
|
});
|
|
|
|
App.AlbumRoute = Ember.Route.extend({
|
|
actions: {
|
|
startPlaying: function() {
|
|
}
|
|
}
|
|
});
|
|
|
|
App.AlbumSongRoute = Ember.Route.extend({
|
|
actions: {
|
|
startPlaying: function() {
|
|
// ...
|
|
|
|
if (actionShouldAlsoBeTriggeredOnParentRoute) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
@property actions
|
|
@type Hash
|
|
@default null
|
|
*/
|
|
|
|
/**
|
|
Moves `actions` to `_actions` at extend time. Note that this currently
|
|
modifies the mixin themselves, which is technically dubious but
|
|
is practically of little consequence. This may change in the future.
|
|
|
|
@private
|
|
@method willMergeMixin
|
|
*/
|
|
willMergeMixin: function(props) {
|
|
var hashName;
|
|
|
|
if (!props._actions) {
|
|
Ember.assert("'actions' should not be a function", typeof(props.actions) !== 'function');
|
|
|
|
if (typeOf(props.actions) === 'object') {
|
|
hashName = 'actions';
|
|
} else if (typeOf(props.events) === 'object') {
|
|
Ember.deprecate('Action handlers contained in an `events` object are deprecated in favor' +
|
|
' of putting them in an `actions` object', false);
|
|
hashName = 'events';
|
|
}
|
|
|
|
if (hashName) {
|
|
props._actions = merge(props._actions || {}, props[hashName]);
|
|
}
|
|
|
|
delete props[hashName];
|
|
}
|
|
},
|
|
|
|
/**
|
|
Triggers a named action on the `ActionHandler`. Any parameters
|
|
supplied after the `actionName` string will be passed as arguments
|
|
to the action target function.
|
|
|
|
If the `ActionHandler` has its `target` property set, actions may
|
|
bubble to the `target`. Bubbling happens when an `actionName` can
|
|
not be found in the `ActionHandler`'s `actions` hash or if the
|
|
action target function returns `true`.
|
|
|
|
Example
|
|
|
|
```js
|
|
App.WelcomeRoute = Ember.Route.extend({
|
|
actions: {
|
|
playTheme: function() {
|
|
this.send('playMusic', 'theme.mp3');
|
|
},
|
|
playMusic: function(track) {
|
|
// ...
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
@method send
|
|
@param {String} actionName The action to trigger
|
|
@param {*} context a context to send with the action
|
|
*/
|
|
send: function(actionName) {
|
|
var args = [].slice.call(arguments, 1);
|
|
var target;
|
|
|
|
if (this._actions && this._actions[actionName]) {
|
|
if (this._actions[actionName].apply(this, args) === true) {
|
|
// handler returned true, so this action will bubble
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (target = get(this, 'target')) {
|
|
Ember.assert("The `target` for " + this + " (" + target +
|
|
") does not have a `send` method", typeof target.send === 'function');
|
|
target.send.apply(target, arguments);
|
|
}
|
|
}
|
|
});
|
|
|
|
__exports__["default"] = ActionHandler;
|
|
});
|
|
enifed("ember-runtime/mixins/array",
|
|
["ember-metal/core","ember-metal/property_get","ember-metal/computed","ember-metal/is_none","ember-runtime/mixins/enumerable","ember-metal/enumerable_utils","ember-metal/mixin","ember-metal/property_events","ember-metal/events","ember-metal/watching","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
|
|
// ..........................................................
|
|
// HELPERS
|
|
//
|
|
var Ember = __dependency1__["default"];
|
|
// ES6TODO: Ember.A
|
|
|
|
var get = __dependency2__.get;
|
|
var computed = __dependency3__.computed;
|
|
var cacheFor = __dependency3__.cacheFor;
|
|
var isNone = __dependency4__["default"];
|
|
var Enumerable = __dependency5__["default"];
|
|
var map = __dependency6__.map;
|
|
var Mixin = __dependency7__.Mixin;
|
|
var required = __dependency7__.required;
|
|
var propertyWillChange = __dependency8__.propertyWillChange;
|
|
var propertyDidChange = __dependency8__.propertyDidChange;
|
|
var addListener = __dependency9__.addListener;
|
|
var removeListener = __dependency9__.removeListener;
|
|
var sendEvent = __dependency9__.sendEvent;
|
|
var hasListeners = __dependency9__.hasListeners;
|
|
var isWatching = __dependency10__.isWatching;
|
|
|
|
function arrayObserversHelper(obj, target, opts, operation, notify) {
|
|
var willChange = (opts && opts.willChange) || 'arrayWillChange';
|
|
var didChange = (opts && opts.didChange) || 'arrayDidChange';
|
|
var hasObservers = get(obj, 'hasArrayObservers');
|
|
|
|
if (hasObservers === notify) {
|
|
propertyWillChange(obj, 'hasArrayObservers');
|
|
}
|
|
|
|
operation(obj, '@array:before', target, willChange);
|
|
operation(obj, '@array:change', target, didChange);
|
|
|
|
if (hasObservers === notify) {
|
|
propertyDidChange(obj, 'hasArrayObservers');
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
// ..........................................................
|
|
// ARRAY
|
|
//
|
|
/**
|
|
This mixin implements Observer-friendly Array-like behavior. It is not a
|
|
concrete implementation, but it can be used up by other classes that want
|
|
to appear like arrays.
|
|
|
|
For example, ArrayProxy and ArrayController are both concrete classes that can
|
|
be instantiated to implement array-like behavior. Both of these classes use
|
|
the Array Mixin by way of the MutableArray mixin, which allows observable
|
|
changes to be made to the underlying array.
|
|
|
|
Unlike `Ember.Enumerable,` this mixin defines methods specifically for
|
|
collections that provide index-ordered access to their contents. When you
|
|
are designing code that needs to accept any kind of Array-like object, you
|
|
should use these methods instead of Array primitives because these will
|
|
properly notify observers of changes to the array.
|
|
|
|
Although these methods are efficient, they do add a layer of indirection to
|
|
your application so it is a good idea to use them only when you need the
|
|
flexibility of using both true JavaScript arrays and "virtual" arrays such
|
|
as controllers and collections.
|
|
|
|
You can use the methods defined in this module to access and modify array
|
|
contents in a KVO-friendly way. You can also be notified whenever the
|
|
membership of an array changes by using `.observes('myArray.[]')`.
|
|
|
|
To support `Ember.Array` in your own class, you must override two
|
|
primitives to use it: `replace()` and `objectAt()`.
|
|
|
|
Note that the Ember.Array mixin also incorporates the `Ember.Enumerable`
|
|
mixin. All `Ember.Array`-like objects are also enumerable.
|
|
|
|
@class Array
|
|
@namespace Ember
|
|
@uses Ember.Enumerable
|
|
@since Ember 0.9.0
|
|
*/
|
|
__exports__["default"] = Mixin.create(Enumerable, {
|
|
|
|
/**
|
|
Your array must support the `length` property. Your replace methods should
|
|
set this property whenever it changes.
|
|
|
|
@property {Number} length
|
|
*/
|
|
length: required(),
|
|
|
|
/**
|
|
Returns the object at the given `index`. If the given `index` is negative
|
|
or is greater or equal than the array length, returns `undefined`.
|
|
|
|
This is one of the primitives you must implement to support `Ember.Array`.
|
|
If your object supports retrieving the value of an array item using `get()`
|
|
(i.e. `myArray.get(0)`), then you do not need to implement this method
|
|
yourself.
|
|
|
|
```javascript
|
|
var arr = ['a', 'b', 'c', 'd'];
|
|
|
|
arr.objectAt(0); // 'a'
|
|
arr.objectAt(3); // 'd'
|
|
arr.objectAt(-1); // undefined
|
|
arr.objectAt(4); // undefined
|
|
arr.objectAt(5); // undefined
|
|
```
|
|
|
|
@method objectAt
|
|
@param {Number} idx The index of the item to return.
|
|
@return {*} item at index or undefined
|
|
*/
|
|
objectAt: function(idx) {
|
|
if (idx < 0 || idx >= get(this, 'length')) {
|
|
return undefined;
|
|
}
|
|
|
|
return get(this, idx);
|
|
},
|
|
|
|
/**
|
|
This returns the objects at the specified indexes, using `objectAt`.
|
|
|
|
```javascript
|
|
var arr =Â ['a', 'b', 'c', 'd'];
|
|
|
|
arr.objectsAt([0, 1, 2]); // ['a', 'b', 'c']
|
|
arr.objectsAt([2, 3, 4]); // ['c', 'd', undefined]
|
|
```
|
|
|
|
@method objectsAt
|
|
@param {Array} indexes An array of indexes of items to return.
|
|
@return {Array}
|
|
*/
|
|
objectsAt: function(indexes) {
|
|
var self = this;
|
|
|
|
return map(indexes, function(idx) {
|
|
return self.objectAt(idx);
|
|
});
|
|
},
|
|
|
|
// overrides Ember.Enumerable version
|
|
nextObject: function(idx) {
|
|
return this.objectAt(idx);
|
|
},
|
|
|
|
/**
|
|
This is the handler for the special array content property. If you get
|
|
this property, it will return this. If you set this property to a new
|
|
array, it will replace the current content.
|
|
|
|
This property overrides the default property defined in `Ember.Enumerable`.
|
|
|
|
@property []
|
|
@return this
|
|
*/
|
|
'[]': computed(function(key, value) {
|
|
if (value !== undefined) {
|
|
this.replace(0, get(this, 'length'), value);
|
|
}
|
|
|
|
return this;
|
|
}),
|
|
|
|
firstObject: computed(function() {
|
|
return this.objectAt(0);
|
|
}),
|
|
|
|
lastObject: computed(function() {
|
|
return this.objectAt(get(this, 'length') - 1);
|
|
}),
|
|
|
|
// optimized version from Enumerable
|
|
contains: function(obj) {
|
|
return this.indexOf(obj) >= 0;
|
|
},
|
|
|
|
// Add any extra methods to Ember.Array that are native to the built-in Array.
|
|
/**
|
|
Returns a new array that is a slice of the receiver. This implementation
|
|
uses the observable array methods to retrieve the objects for the new
|
|
slice.
|
|
|
|
```javascript
|
|
var arr = ['red', 'green', 'blue'];
|
|
|
|
arr.slice(0); // ['red', 'green', 'blue']
|
|
arr.slice(0, 2); // ['red', 'green']
|
|
arr.slice(1, 100); // ['green', 'blue']
|
|
```
|
|
|
|
@method slice
|
|
@param {Integer} beginIndex (Optional) index to begin slicing from.
|
|
@param {Integer} endIndex (Optional) index to end the slice at (but not included).
|
|
@return {Array} New array with specified slice
|
|
*/
|
|
slice: function(beginIndex, endIndex) {
|
|
var ret = Ember.A();
|
|
var length = get(this, 'length');
|
|
|
|
if (isNone(beginIndex)) {
|
|
beginIndex = 0;
|
|
}
|
|
|
|
if (isNone(endIndex) || (endIndex > length)) {
|
|
endIndex = length;
|
|
}
|
|
|
|
if (beginIndex < 0) {
|
|
beginIndex = length + beginIndex;
|
|
}
|
|
|
|
if (endIndex < 0) {
|
|
endIndex = length + endIndex;
|
|
}
|
|
|
|
while (beginIndex < endIndex) {
|
|
ret[ret.length] = this.objectAt(beginIndex++);
|
|
}
|
|
|
|
return ret;
|
|
},
|
|
|
|
/**
|
|
Returns the index of the given object's first occurrence.
|
|
If no `startAt` argument is given, the starting location to
|
|
search is 0. If it's negative, will count backward from
|
|
the end of the array. Returns -1 if no match is found.
|
|
|
|
```javascript
|
|
var arr = ['a', 'b', 'c', 'd', 'a'];
|
|
|
|
arr.indexOf('a'); // 0
|
|
arr.indexOf('z'); // -1
|
|
arr.indexOf('a', 2); // 4
|
|
arr.indexOf('a', -1); // 4
|
|
arr.indexOf('b', 3); // -1
|
|
arr.indexOf('a', 100); // -1
|
|
```
|
|
|
|
@method indexOf
|
|
@param {Object} object the item to search for
|
|
@param {Number} startAt optional starting location to search, default 0
|
|
@return {Number} index or -1 if not found
|
|
*/
|
|
indexOf: function(object, startAt) {
|
|
var len = get(this, 'length');
|
|
var idx;
|
|
|
|
if (startAt === undefined) {
|
|
startAt = 0;
|
|
}
|
|
|
|
if (startAt < 0) {
|
|
startAt += len;
|
|
}
|
|
|
|
for (idx = startAt; idx < len; idx++) {
|
|
if (this.objectAt(idx) === object) {
|
|
return idx;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
},
|
|
|
|
/**
|
|
Returns the index of the given object's last occurrence.
|
|
If no `startAt` argument is given, the search starts from
|
|
the last position. If it's negative, will count backward
|
|
from the end of the array. Returns -1 if no match is found.
|
|
|
|
```javascript
|
|
var arr = ['a', 'b', 'c', 'd', 'a'];
|
|
|
|
arr.lastIndexOf('a'); // 4
|
|
arr.lastIndexOf('z'); // -1
|
|
arr.lastIndexOf('a', 2); // 0
|
|
arr.lastIndexOf('a', -1); // 4
|
|
arr.lastIndexOf('b', 3); // 1
|
|
arr.lastIndexOf('a', 100); // 4
|
|
```
|
|
|
|
@method lastIndexOf
|
|
@param {Object} object the item to search for
|
|
@param {Number} startAt optional starting location to search, default 0
|
|
@return {Number} index or -1 if not found
|
|
*/
|
|
lastIndexOf: function(object, startAt) {
|
|
var len = get(this, 'length');
|
|
var idx;
|
|
|
|
if (startAt === undefined || startAt >= len) {
|
|
startAt = len-1;
|
|
}
|
|
|
|
if (startAt < 0) {
|
|
startAt += len;
|
|
}
|
|
|
|
for (idx = startAt; idx >= 0; idx--) {
|
|
if (this.objectAt(idx) === object) {
|
|
return idx;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
},
|
|
|
|
// ..........................................................
|
|
// ARRAY OBSERVERS
|
|
//
|
|
|
|
/**
|
|
Adds an array observer to the receiving array. The array observer object
|
|
normally must implement two methods:
|
|
|
|
* `arrayWillChange(observedObj, start, removeCount, addCount)` - This method will be
|
|
called just before the array is modified.
|
|
* `arrayDidChange(observedObj, start, removeCount, addCount)` - This method will be
|
|
called just after the array is modified.
|
|
|
|
Both callbacks will be passed the observed object, starting index of the
|
|
change as well a a count of the items to be removed and added. You can use
|
|
these callbacks to optionally inspect the array during the change, clear
|
|
caches, or do any other bookkeeping necessary.
|
|
|
|
In addition to passing a target, you can also include an options hash
|
|
which you can use to override the method names that will be invoked on the
|
|
target.
|
|
|
|
@method addArrayObserver
|
|
@param {Object} target The observer object.
|
|
@param {Hash} opts Optional hash of configuration options including
|
|
`willChange` and `didChange` option.
|
|
@return {Ember.Array} receiver
|
|
*/
|
|
|
|
addArrayObserver: function(target, opts) {
|
|
return arrayObserversHelper(this, target, opts, addListener, false);
|
|
},
|
|
|
|
/**
|
|
Removes an array observer from the object if the observer is current
|
|
registered. Calling this method multiple times with the same object will
|
|
have no effect.
|
|
|
|
@method removeArrayObserver
|
|
@param {Object} target The object observing the array.
|
|
@param {Hash} opts Optional hash of configuration options including
|
|
`willChange` and `didChange` option.
|
|
@return {Ember.Array} receiver
|
|
*/
|
|
removeArrayObserver: function(target, opts) {
|
|
return arrayObserversHelper(this, target, opts, removeListener, true);
|
|
},
|
|
|
|
/**
|
|
Becomes true whenever the array currently has observers watching changes
|
|
on the array.
|
|
|
|
@property {Boolean} hasArrayObservers
|
|
*/
|
|
hasArrayObservers: computed(function() {
|
|
return hasListeners(this, '@array:change') || hasListeners(this, '@array:before');
|
|
}),
|
|
|
|
/**
|
|
If you are implementing an object that supports `Ember.Array`, call this
|
|
method just before the array content changes to notify any observers and
|
|
invalidate any related properties. Pass the starting index of the change
|
|
as well as a delta of the amounts to change.
|
|
|
|
@method arrayContentWillChange
|
|
@param {Number} startIdx The starting index in the array that will change.
|
|
@param {Number} removeAmt The number of items that will be removed. If you
|
|
pass `null` assumes 0
|
|
@param {Number} addAmt The number of items that will be added. If you
|
|
pass `null` assumes 0.
|
|
@return {Ember.Array} receiver
|
|
*/
|
|
arrayContentWillChange: function(startIdx, removeAmt, addAmt) {
|
|
var removing, lim;
|
|
|
|
// if no args are passed assume everything changes
|
|
if (startIdx === undefined) {
|
|
startIdx = 0;
|
|
removeAmt = addAmt = -1;
|
|
} else {
|
|
if (removeAmt === undefined) {
|
|
removeAmt = -1;
|
|
}
|
|
|
|
if (addAmt === undefined) {
|
|
addAmt = -1;
|
|
}
|
|
}
|
|
|
|
// Make sure the @each proxy is set up if anyone is observing @each
|
|
if (isWatching(this, '@each')) {
|
|
get(this, '@each');
|
|
}
|
|
|
|
sendEvent(this, '@array:before', [this, startIdx, removeAmt, addAmt]);
|
|
|
|
if (startIdx >= 0 && removeAmt >= 0 && get(this, 'hasEnumerableObservers')) {
|
|
removing = [];
|
|
lim = startIdx + removeAmt;
|
|
|
|
for (var idx = startIdx; idx < lim; idx++) {
|
|
removing.push(this.objectAt(idx));
|
|
}
|
|
} else {
|
|
removing = removeAmt;
|
|
}
|
|
|
|
this.enumerableContentWillChange(removing, addAmt);
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
If you are implementing an object that supports `Ember.Array`, call this
|
|
method just after the array content changes to notify any observers and
|
|
invalidate any related properties. Pass the starting index of the change
|
|
as well as a delta of the amounts to change.
|
|
|
|
@method arrayContentDidChange
|
|
@param {Number} startIdx The starting index in the array that did change.
|
|
@param {Number} removeAmt The number of items that were removed. If you
|
|
pass `null` assumes 0
|
|
@param {Number} addAmt The number of items that were added. If you
|
|
pass `null` assumes 0.
|
|
@return {Ember.Array} receiver
|
|
*/
|
|
arrayContentDidChange: function(startIdx, removeAmt, addAmt) {
|
|
var adding, lim;
|
|
|
|
// if no args are passed assume everything changes
|
|
if (startIdx === undefined) {
|
|
startIdx = 0;
|
|
removeAmt = addAmt = -1;
|
|
} else {
|
|
if (removeAmt === undefined) {
|
|
removeAmt = -1;
|
|
}
|
|
|
|
if (addAmt === undefined) {
|
|
addAmt = -1;
|
|
}
|
|
}
|
|
|
|
if (startIdx >= 0 && addAmt >= 0 && get(this, 'hasEnumerableObservers')) {
|
|
adding = [];
|
|
lim = startIdx + addAmt;
|
|
|
|
for (var idx = startIdx; idx < lim; idx++) {
|
|
adding.push(this.objectAt(idx));
|
|
}
|
|
} else {
|
|
adding = addAmt;
|
|
}
|
|
|
|
this.enumerableContentDidChange(removeAmt, adding);
|
|
sendEvent(this, '@array:change', [this, startIdx, removeAmt, addAmt]);
|
|
|
|
var length = get(this, 'length');
|
|
var cachedFirst = cacheFor(this, 'firstObject');
|
|
var cachedLast = cacheFor(this, 'lastObject');
|
|
|
|
if (this.objectAt(0) !== cachedFirst) {
|
|
propertyWillChange(this, 'firstObject');
|
|
propertyDidChange(this, 'firstObject');
|
|
}
|
|
|
|
if (this.objectAt(length-1) !== cachedLast) {
|
|
propertyWillChange(this, 'lastObject');
|
|
propertyDidChange(this, 'lastObject');
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
// ..........................................................
|
|
// ENUMERATED PROPERTIES
|
|
//
|
|
|
|
/**
|
|
Returns a special object that can be used to observe individual properties
|
|
on the array. Just get an equivalent property on this object and it will
|
|
return an enumerable that maps automatically to the named key on the
|
|
member objects.
|
|
|
|
If you merely want to watch for any items being added or removed to the array,
|
|
use the `[]` property instead of `@each`.
|
|
|
|
@property @each
|
|
*/
|
|
'@each': computed(function() {
|
|
if (!this.__each) {
|
|
// ES6TODO: GRRRRR
|
|
var EachProxy = requireModule('ember-runtime/system/each_proxy')['EachProxy'];
|
|
|
|
this.__each = new EachProxy(this);
|
|
}
|
|
|
|
return this.__each;
|
|
})
|
|
});
|
|
});
|
|
enifed("ember-runtime/mixins/comparable",
|
|
["ember-metal/mixin","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var Mixin = __dependency1__.Mixin;
|
|
var required = __dependency1__.required;
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
|
|
/**
|
|
Implements some standard methods for comparing objects. Add this mixin to
|
|
any class you create that can compare its instances.
|
|
|
|
You should implement the `compare()` method.
|
|
|
|
@class Comparable
|
|
@namespace Ember
|
|
@since Ember 0.9
|
|
*/
|
|
__exports__["default"] = Mixin.create({
|
|
|
|
/**
|
|
Override to return the result of the comparison of the two parameters. The
|
|
compare method should return:
|
|
|
|
- `-1` if `a < b`
|
|
- `0` if `a == b`
|
|
- `1` if `a > b`
|
|
|
|
Default implementation raises an exception.
|
|
|
|
@method compare
|
|
@param a {Object} the first object to compare
|
|
@param b {Object} the second object to compare
|
|
@return {Integer} the result of the comparison
|
|
*/
|
|
compare: required(Function)
|
|
});
|
|
});
|
|
enifed("ember-runtime/mixins/controller",
|
|
["ember-metal/mixin","ember-metal/computed","ember-runtime/mixins/action_handler","ember-runtime/mixins/controller_content_model_alias_deprecation","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
|
|
"use strict";
|
|
var Mixin = __dependency1__.Mixin;
|
|
var computed = __dependency2__.computed;
|
|
var ActionHandler = __dependency3__["default"];
|
|
var ControllerContentModelAliasDeprecation = __dependency4__["default"];
|
|
|
|
/**
|
|
`Ember.ControllerMixin` provides a standard interface for all classes that
|
|
compose Ember's controller layer: `Ember.Controller`,
|
|
`Ember.ArrayController`, and `Ember.ObjectController`.
|
|
|
|
@class ControllerMixin
|
|
@namespace Ember
|
|
@uses Ember.ActionHandler
|
|
*/
|
|
__exports__["default"] = Mixin.create(ActionHandler, ControllerContentModelAliasDeprecation, {
|
|
/* ducktype as a controller */
|
|
isController: true,
|
|
|
|
/**
|
|
The object to which actions from the view should be sent.
|
|
|
|
For example, when a Handlebars template uses the `{{action}}` helper,
|
|
it will attempt to send the action to the view's controller's `target`.
|
|
|
|
By default, the value of the target property is set to the router, and
|
|
is injected when a controller is instantiated. This injection is defined
|
|
in Ember.Application#buildContainer, and is applied as part of the
|
|
applications initialization process. It can also be set after a controller
|
|
has been instantiated, for instance when using the render helper in a
|
|
template, or when a controller is used as an `itemController`. In most
|
|
cases the `target` property will automatically be set to the logical
|
|
consumer of actions for the controller.
|
|
|
|
@property target
|
|
@default null
|
|
*/
|
|
target: null,
|
|
|
|
container: null,
|
|
|
|
parentController: null,
|
|
|
|
store: null,
|
|
|
|
/**
|
|
The controller's current model. When retrieving or modifying a controller's
|
|
model, this property should be used instead of the `content` property.
|
|
|
|
@property model
|
|
@public
|
|
*/
|
|
model: null,
|
|
|
|
/**
|
|
@private
|
|
*/
|
|
content: computed.alias('model')
|
|
|
|
});
|
|
});
|
|
enifed("ember-runtime/mixins/controller_content_model_alias_deprecation",
|
|
["ember-metal/core","ember-metal/mixin","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.deprecate
|
|
var Mixin = __dependency2__.Mixin;
|
|
|
|
/**
|
|
The ControllerContentModelAliasDeprecation mixin is used to provide a useful
|
|
deprecation warning when specifying `content` directly on a `Ember.Controller`
|
|
(without also specifying `model`).
|
|
|
|
Ember versions prior to 1.7 used `model` as an alias of `content`, but due to
|
|
much confusion this alias was reversed (so `content` is now an alias of `model).
|
|
|
|
This change reduces many caveats with model/content, and also sets a
|
|
simple ground rule: Never set a controllers content, rather always set
|
|
its model and ember will do the right thing.
|
|
|
|
|
|
`Ember.ControllerContentModelAliasDeprecation` is used internally by Ember in
|
|
`Ember.Controller`.
|
|
|
|
@class ControllerContentModelAliasDeprecation
|
|
@namespace Ember
|
|
@private
|
|
@since 1.7.0
|
|
*/
|
|
__exports__["default"] = Mixin.create({
|
|
/**
|
|
@private
|
|
|
|
Moves `content` to `model` at extend time if a `model` is not also specified.
|
|
|
|
Note that this currently modifies the mixin themselves, which is technically
|
|
dubious but is practically of little consequence. This may change in the
|
|
future.
|
|
|
|
@method willMergeMixin
|
|
@since 1.4.0
|
|
*/
|
|
willMergeMixin: function(props) {
|
|
// Calling super is only OK here since we KNOW that
|
|
// there is another Mixin loaded first.
|
|
this._super.apply(this, arguments);
|
|
|
|
var modelSpecified = !!props.model;
|
|
|
|
if (props.content && !modelSpecified) {
|
|
props.model = props.content;
|
|
delete props['content'];
|
|
|
|
Ember.deprecate('Do not specify `content` on a Controller, use `model` instead.', false);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
enifed("ember-runtime/mixins/copyable",
|
|
["ember-metal/property_get","ember-metal/mixin","ember-runtime/mixins/freezable","ember-runtime/system/string","ember-metal/error","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
|
|
|
|
var get = __dependency1__.get;
|
|
var required = __dependency2__.required;
|
|
var Freezable = __dependency3__.Freezable;
|
|
var Mixin = __dependency2__.Mixin;
|
|
var fmt = __dependency4__.fmt;
|
|
var EmberError = __dependency5__["default"];
|
|
|
|
|
|
/**
|
|
Implements some standard methods for copying an object. Add this mixin to
|
|
any object you create that can create a copy of itself. This mixin is
|
|
added automatically to the built-in array.
|
|
|
|
You should generally implement the `copy()` method to return a copy of the
|
|
receiver.
|
|
|
|
Note that `frozenCopy()` will only work if you also implement
|
|
`Ember.Freezable`.
|
|
|
|
@class Copyable
|
|
@namespace Ember
|
|
@since Ember 0.9
|
|
*/
|
|
__exports__["default"] = Mixin.create({
|
|
/**
|
|
Override to return a copy of the receiver. Default implementation raises
|
|
an exception.
|
|
|
|
@method copy
|
|
@param {Boolean} deep if `true`, a deep copy of the object should be made
|
|
@return {Object} copy of receiver
|
|
*/
|
|
copy: required(Function),
|
|
|
|
/**
|
|
If the object implements `Ember.Freezable`, then this will return a new
|
|
copy if the object is not frozen and the receiver if the object is frozen.
|
|
|
|
Raises an exception if you try to call this method on a object that does
|
|
not support freezing.
|
|
|
|
You should use this method whenever you want a copy of a freezable object
|
|
since a freezable object can simply return itself without actually
|
|
consuming more memory.
|
|
|
|
@method frozenCopy
|
|
@return {Object} copy of receiver or receiver
|
|
*/
|
|
frozenCopy: function() {
|
|
if (Freezable && Freezable.detect(this)) {
|
|
return get(this, 'isFrozen') ? this : this.copy().freeze();
|
|
} else {
|
|
throw new EmberError(fmt("%@ does not support freezing", [this]));
|
|
}
|
|
}
|
|
});
|
|
});
|
|
enifed("ember-runtime/mixins/deferred",
|
|
["ember-metal/core","ember-metal/property_get","ember-metal/mixin","ember-metal/computed","ember-runtime/ext/rsvp","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.FEATURES, Ember.Test
|
|
var get = __dependency2__.get;
|
|
var Mixin = __dependency3__.Mixin;
|
|
var computed = __dependency4__.computed;
|
|
var RSVP = __dependency5__["default"];
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
|
|
|
|
/**
|
|
@class Deferred
|
|
@namespace Ember
|
|
*/
|
|
__exports__["default"] = Mixin.create({
|
|
/**
|
|
Add handlers to be called when the Deferred object is resolved or rejected.
|
|
|
|
@method then
|
|
@param {Function} resolve a callback function to be called when done
|
|
@param {Function} reject a callback function to be called when failed
|
|
*/
|
|
then: function(resolve, reject, label) {
|
|
var deferred, promise, entity;
|
|
|
|
entity = this;
|
|
deferred = get(this, '_deferred');
|
|
promise = deferred.promise;
|
|
|
|
function fulfillmentHandler(fulfillment) {
|
|
if (fulfillment === promise) {
|
|
return resolve(entity);
|
|
} else {
|
|
return resolve(fulfillment);
|
|
}
|
|
}
|
|
|
|
return promise.then(resolve && fulfillmentHandler, reject, label);
|
|
},
|
|
|
|
/**
|
|
Resolve a Deferred object and call any `doneCallbacks` with the given args.
|
|
|
|
@method resolve
|
|
*/
|
|
resolve: function(value) {
|
|
var deferred, promise;
|
|
|
|
deferred = get(this, '_deferred');
|
|
promise = deferred.promise;
|
|
|
|
if (value === this) {
|
|
deferred.resolve(promise);
|
|
} else {
|
|
deferred.resolve(value);
|
|
}
|
|
},
|
|
|
|
/**
|
|
Reject a Deferred object and call any `failCallbacks` with the given args.
|
|
|
|
@method reject
|
|
*/
|
|
reject: function(value) {
|
|
get(this, '_deferred').reject(value);
|
|
},
|
|
|
|
_deferred: computed(function() {
|
|
Ember.deprecate('Usage of Ember.DeferredMixin or Ember.Deferred is deprecated.', this._suppressDeferredDeprecation, { url: 'http://emberjs.com/guides/deprecations/#toc_deprecate-ember-deferredmixin-and-ember-deferred' });
|
|
|
|
return RSVP.defer('Ember: DeferredMixin - ' + this);
|
|
})
|
|
});
|
|
});
|
|
enifed("ember-runtime/mixins/enumerable",
|
|
["ember-metal/core","ember-metal/property_get","ember-metal/property_set","ember-metal/utils","ember-metal/mixin","ember-metal/enumerable_utils","ember-metal/computed","ember-metal/property_events","ember-metal/events","ember-runtime/compare","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
|
|
// ..........................................................
|
|
// HELPERS
|
|
//
|
|
|
|
var Ember = __dependency1__["default"];
|
|
var get = __dependency2__.get;
|
|
var set = __dependency3__.set;
|
|
var apply = __dependency4__.apply;
|
|
var Mixin = __dependency5__.Mixin;
|
|
var required = __dependency5__.required;
|
|
var aliasMethod = __dependency5__.aliasMethod;
|
|
var indexOf = __dependency6__.indexOf;
|
|
var computed = __dependency7__.computed;
|
|
var propertyWillChange = __dependency8__.propertyWillChange;
|
|
var propertyDidChange = __dependency8__.propertyDidChange;
|
|
var addListener = __dependency9__.addListener;
|
|
var removeListener = __dependency9__.removeListener;
|
|
var sendEvent = __dependency9__.sendEvent;
|
|
var hasListeners = __dependency9__.hasListeners;
|
|
var compare = __dependency10__["default"];
|
|
|
|
var a_slice = Array.prototype.slice;
|
|
|
|
var contexts = [];
|
|
|
|
function popCtx() {
|
|
return contexts.length === 0 ? {} : contexts.pop();
|
|
}
|
|
|
|
function pushCtx(ctx) {
|
|
contexts.push(ctx);
|
|
return null;
|
|
}
|
|
|
|
function iter(key, value) {
|
|
var valueProvided = arguments.length === 2;
|
|
|
|
function i(item) {
|
|
var cur = get(item, key);
|
|
return valueProvided ? value === cur : !!cur;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
/**
|
|
This mixin defines the common interface implemented by enumerable objects
|
|
in Ember. Most of these methods follow the standard Array iteration
|
|
API defined up to JavaScript 1.8 (excluding language-specific features that
|
|
cannot be emulated in older versions of JavaScript).
|
|
|
|
This mixin is applied automatically to the Array class on page load, so you
|
|
can use any of these methods on simple arrays. If Array already implements
|
|
one of these methods, the mixin will not override them.
|
|
|
|
## Writing Your Own Enumerable
|
|
|
|
To make your own custom class enumerable, you need two items:
|
|
|
|
1. You must have a length property. This property should change whenever
|
|
the number of items in your enumerable object changes. If you use this
|
|
with an `Ember.Object` subclass, you should be sure to change the length
|
|
property using `set().`
|
|
|
|
2. You must implement `nextObject().` See documentation.
|
|
|
|
Once you have these two methods implemented, apply the `Ember.Enumerable` mixin
|
|
to your class and you will be able to enumerate the contents of your object
|
|
like any other collection.
|
|
|
|
## Using Ember Enumeration with Other Libraries
|
|
|
|
Many other libraries provide some kind of iterator or enumeration like
|
|
facility. This is often where the most common API conflicts occur.
|
|
Ember's API is designed to be as friendly as possible with other
|
|
libraries by implementing only methods that mostly correspond to the
|
|
JavaScript 1.8 API.
|
|
|
|
@class Enumerable
|
|
@namespace Ember
|
|
@since Ember 0.9
|
|
*/
|
|
__exports__["default"] = Mixin.create({
|
|
|
|
/**
|
|
Implement this method to make your class enumerable.
|
|
|
|
This method will be call repeatedly during enumeration. The index value
|
|
will always begin with 0 and increment monotonically. You don't have to
|
|
rely on the index value to determine what object to return, but you should
|
|
always check the value and start from the beginning when you see the
|
|
requested index is 0.
|
|
|
|
The `previousObject` is the object that was returned from the last call
|
|
to `nextObject` for the current iteration. This is a useful way to
|
|
manage iteration if you are tracing a linked list, for example.
|
|
|
|
Finally the context parameter will always contain a hash you can use as
|
|
a "scratchpad" to maintain any other state you need in order to iterate
|
|
properly. The context object is reused and is not reset between
|
|
iterations so make sure you setup the context with a fresh state whenever
|
|
the index parameter is 0.
|
|
|
|
Generally iterators will continue to call `nextObject` until the index
|
|
reaches the your current length-1. If you run out of data before this
|
|
time for some reason, you should simply return undefined.
|
|
|
|
The default implementation of this method simply looks up the index.
|
|
This works great on any Array-like objects.
|
|
|
|
@method nextObject
|
|
@param {Number} index the current index of the iteration
|
|
@param {Object} previousObject the value returned by the last call to
|
|
`nextObject`.
|
|
@param {Object} context a context object you can use to maintain state.
|
|
@return {Object} the next object in the iteration or undefined
|
|
*/
|
|
nextObject: required(Function),
|
|
|
|
/**
|
|
Helper method returns the first object from a collection. This is usually
|
|
used by bindings and other parts of the framework to extract a single
|
|
object if the enumerable contains only one item.
|
|
|
|
If you override this method, you should implement it so that it will
|
|
always return the same value each time it is called. If your enumerable
|
|
contains only one object, this method should always return that object.
|
|
If your enumerable is empty, this method should return `undefined`.
|
|
|
|
```javascript
|
|
var arr = ['a', 'b', 'c'];
|
|
arr.get('firstObject'); // 'a'
|
|
|
|
var arr = [];
|
|
arr.get('firstObject'); // undefined
|
|
```
|
|
|
|
@property firstObject
|
|
@return {Object} the object or undefined
|
|
*/
|
|
firstObject: computed('[]', function() {
|
|
if (get(this, 'length') === 0) {
|
|
return undefined;
|
|
}
|
|
|
|
// handle generic enumerables
|
|
var context = popCtx();
|
|
var ret = this.nextObject(0, null, context);
|
|
|
|
pushCtx(context);
|
|
|
|
return ret;
|
|
}),
|
|
|
|
/**
|
|
Helper method returns the last object from a collection. If your enumerable
|
|
contains only one object, this method should always return that object.
|
|
If your enumerable is empty, this method should return `undefined`.
|
|
|
|
```javascript
|
|
var arr = ['a', 'b', 'c'];
|
|
arr.get('lastObject'); // 'c'
|
|
|
|
var arr = [];
|
|
arr.get('lastObject'); // undefined
|
|
```
|
|
|
|
@property lastObject
|
|
@return {Object} the last object or undefined
|
|
*/
|
|
lastObject: computed('[]', function() {
|
|
var len = get(this, 'length');
|
|
|
|
if (len === 0) {
|
|
return undefined;
|
|
}
|
|
|
|
var context = popCtx();
|
|
var idx = 0;
|
|
var last = null;
|
|
var cur;
|
|
|
|
do {
|
|
last = cur;
|
|
cur = this.nextObject(idx++, last, context);
|
|
} while (cur !== undefined);
|
|
|
|
pushCtx(context);
|
|
|
|
return last;
|
|
}),
|
|
|
|
/**
|
|
Returns `true` if the passed object can be found in the receiver. The
|
|
default version will iterate through the enumerable until the object
|
|
is found. You may want to override this with a more efficient version.
|
|
|
|
```javascript
|
|
var arr = ['a', 'b', 'c'];
|
|
|
|
arr.contains('a'); // true
|
|
arr.contains('z'); // false
|
|
```
|
|
|
|
@method contains
|
|
@param {Object} obj The object to search for.
|
|
@return {Boolean} `true` if object is found in enumerable.
|
|
*/
|
|
contains: function(obj) {
|
|
var found = this.find(function(item) {
|
|
return item === obj;
|
|
});
|
|
|
|
return found !== undefined;
|
|
},
|
|
|
|
/**
|
|
Iterates through the enumerable, calling the passed function on each
|
|
item. This method corresponds to the `forEach()` method defined in
|
|
JavaScript 1.6.
|
|
|
|
The callback method you provide should have the following signature (all
|
|
parameters are optional):
|
|
|
|
```javascript
|
|
function(item, index, enumerable);
|
|
```
|
|
|
|
- `item` is the current item in the iteration.
|
|
- `index` is the current index in the iteration.
|
|
- `enumerable` is the enumerable object itself.
|
|
|
|
Note that in addition to a callback, you can also pass an optional target
|
|
object that will be set as `this` on the context. This is a good way
|
|
to give your iterator function access to the current object.
|
|
|
|
@method forEach
|
|
@param {Function} callback The callback to execute
|
|
@param {Object} [target] The target object to use
|
|
@return {Object} receiver
|
|
*/
|
|
forEach: function(callback, target) {
|
|
if (typeof callback !== 'function') {
|
|
throw new TypeError();
|
|
}
|
|
|
|
var context = popCtx();
|
|
var len = get(this, 'length');
|
|
var last = null;
|
|
|
|
if (target === undefined) {
|
|
target = null;
|
|
}
|
|
|
|
for(var idx = 0; idx < len; idx++) {
|
|
var next = this.nextObject(idx, last, context) ;
|
|
callback.call(target, next, idx, this);
|
|
last = next ;
|
|
}
|
|
|
|
last = null ;
|
|
context = pushCtx(context);
|
|
|
|
return this ;
|
|
},
|
|
|
|
/**
|
|
Alias for `mapBy`
|
|
|
|
@method getEach
|
|
@param {String} key name of the property
|
|
@return {Array} The mapped array.
|
|
*/
|
|
getEach: function(key) {
|
|
return this.mapBy(key);
|
|
},
|
|
|
|
/**
|
|
Sets the value on the named property for each member. This is more
|
|
efficient than using other methods defined on this helper. If the object
|
|
implements Ember.Observable, the value will be changed to `set(),` otherwise
|
|
it will be set directly. `null` objects are skipped.
|
|
|
|
@method setEach
|
|
@param {String} key The key to set
|
|
@param {Object} value The object to set
|
|
@return {Object} receiver
|
|
*/
|
|
setEach: function(key, value) {
|
|
return this.forEach(function(item) {
|
|
set(item, key, value);
|
|
});
|
|
},
|
|
|
|
/**
|
|
Maps all of the items in the enumeration to another value, returning
|
|
a new array. This method corresponds to `map()` defined in JavaScript 1.6.
|
|
|
|
The callback method you provide should have the following signature (all
|
|
parameters are optional):
|
|
|
|
```javascript
|
|
function(item, index, enumerable);
|
|
```
|
|
|
|
- `item` is the current item in the iteration.
|
|
- `index` is the current index in the iteration.
|
|
- `enumerable` is the enumerable object itself.
|
|
|
|
It should return the mapped value.
|
|
|
|
Note that in addition to a callback, you can also pass an optional target
|
|
object that will be set as `this` on the context. This is a good way
|
|
to give your iterator function access to the current object.
|
|
|
|
@method map
|
|
@param {Function} callback The callback to execute
|
|
@param {Object} [target] The target object to use
|
|
@return {Array} The mapped array.
|
|
*/
|
|
map: function(callback, target) {
|
|
var ret = Ember.A();
|
|
|
|
this.forEach(function(x, idx, i) {
|
|
ret[idx] = callback.call(target, x, idx,i);
|
|
});
|
|
|
|
return ret ;
|
|
},
|
|
|
|
/**
|
|
Similar to map, this specialized function returns the value of the named
|
|
property on all items in the enumeration.
|
|
|
|
@method mapBy
|
|
@param {String} key name of the property
|
|
@return {Array} The mapped array.
|
|
*/
|
|
mapBy: function(key) {
|
|
return this.map(function(next) {
|
|
return get(next, key);
|
|
});
|
|
},
|
|
|
|
/**
|
|
Similar to map, this specialized function returns the value of the named
|
|
property on all items in the enumeration.
|
|
|
|
@method mapProperty
|
|
@param {String} key name of the property
|
|
@return {Array} The mapped array.
|
|
@deprecated Use `mapBy` instead
|
|
*/
|
|
|
|
mapProperty: aliasMethod('mapBy'),
|
|
|
|
/**
|
|
Returns an array with all of the items in the enumeration that the passed
|
|
function returns true for. This method corresponds to `filter()` defined in
|
|
JavaScript 1.6.
|
|
|
|
The callback method you provide should have the following signature (all
|
|
parameters are optional):
|
|
|
|
```javascript
|
|
function(item, index, enumerable);
|
|
```
|
|
|
|
- `item` is the current item in the iteration.
|
|
- `index` is the current index in the iteration.
|
|
- `enumerable` is the enumerable object itself.
|
|
|
|
It should return `true` to include the item in the results, `false`
|
|
otherwise.
|
|
|
|
Note that in addition to a callback, you can also pass an optional target
|
|
object that will be set as `this` on the context. This is a good way
|
|
to give your iterator function access to the current object.
|
|
|
|
@method filter
|
|
@param {Function} callback The callback to execute
|
|
@param {Object} [target] The target object to use
|
|
@return {Array} A filtered array.
|
|
*/
|
|
filter: function(callback, target) {
|
|
var ret = Ember.A();
|
|
|
|
this.forEach(function(x, idx, i) {
|
|
if (callback.call(target, x, idx, i)) {
|
|
ret.push(x);
|
|
}
|
|
});
|
|
|
|
return ret ;
|
|
},
|
|
|
|
/**
|
|
Returns an array with all of the items in the enumeration where the passed
|
|
function returns false for. This method is the inverse of filter().
|
|
|
|
The callback method you provide should have the following signature (all
|
|
parameters are optional):
|
|
|
|
```javascript
|
|
function(item, index, enumerable);
|
|
```
|
|
|
|
- *item* is the current item in the iteration.
|
|
- *index* is the current index in the iteration
|
|
- *enumerable* is the enumerable object itself.
|
|
|
|
It should return the a falsey value to include the item in the results.
|
|
|
|
Note that in addition to a callback, you can also pass an optional target
|
|
object that will be set as "this" on the context. This is a good way
|
|
to give your iterator function access to the current object.
|
|
|
|
@method reject
|
|
@param {Function} callback The callback to execute
|
|
@param {Object} [target] The target object to use
|
|
@return {Array} A rejected array.
|
|
*/
|
|
reject: function(callback, target) {
|
|
return this.filter(function() {
|
|
return !(apply(target, callback, arguments));
|
|
});
|
|
},
|
|
|
|
/**
|
|
Returns an array with just the items with the matched property. You
|
|
can pass an optional second argument with the target value. Otherwise
|
|
this will match any property that evaluates to `true`.
|
|
|
|
@method filterBy
|
|
@param {String} key the property to test
|
|
@param {*} [value] optional value to test against.
|
|
@return {Array} filtered array
|
|
*/
|
|
filterBy: function(key, value) {
|
|
return this.filter(apply(this, iter, arguments));
|
|
},
|
|
|
|
/**
|
|
Returns an array with just the items with the matched property. You
|
|
can pass an optional second argument with the target value. Otherwise
|
|
this will match any property that evaluates to `true`.
|
|
|
|
@method filterProperty
|
|
@param {String} key the property to test
|
|
@param {String} [value] optional value to test against.
|
|
@return {Array} filtered array
|
|
@deprecated Use `filterBy` instead
|
|
*/
|
|
filterProperty: aliasMethod('filterBy'),
|
|
|
|
/**
|
|
Returns an array with the items that do not have truthy values for
|
|
key. You can pass an optional second argument with the target value. Otherwise
|
|
this will match any property that evaluates to false.
|
|
|
|
@method rejectBy
|
|
@param {String} key the property to test
|
|
@param {String} [value] optional value to test against.
|
|
@return {Array} rejected array
|
|
*/
|
|
rejectBy: function(key, value) {
|
|
var exactValue = function(item) {
|
|
return get(item, key) === value;
|
|
};
|
|
|
|
var hasValue = function(item) {
|
|
return !!get(item, key);
|
|
};
|
|
|
|
var use = (arguments.length === 2 ? exactValue : hasValue);
|
|
|
|
return this.reject(use);
|
|
},
|
|
|
|
/**
|
|
Returns an array with the items that do not have truthy values for
|
|
key. You can pass an optional second argument with the target value. Otherwise
|
|
this will match any property that evaluates to false.
|
|
|
|
@method rejectProperty
|
|
@param {String} key the property to test
|
|
@param {String} [value] optional value to test against.
|
|
@return {Array} rejected array
|
|
@deprecated Use `rejectBy` instead
|
|
*/
|
|
rejectProperty: aliasMethod('rejectBy'),
|
|
|
|
/**
|
|
Returns the first item in the array for which the callback returns true.
|
|
This method works similar to the `filter()` method defined in JavaScript 1.6
|
|
except that it will stop working on the array once a match is found.
|
|
|
|
The callback method you provide should have the following signature (all
|
|
parameters are optional):
|
|
|
|
```javascript
|
|
function(item, index, enumerable);
|
|
```
|
|
|
|
- `item` is the current item in the iteration.
|
|
- `index` is the current index in the iteration.
|
|
- `enumerable` is the enumerable object itself.
|
|
|
|
It should return the `true` to include the item in the results, `false`
|
|
otherwise.
|
|
|
|
Note that in addition to a callback, you can also pass an optional target
|
|
object that will be set as `this` on the context. This is a good way
|
|
to give your iterator function access to the current object.
|
|
|
|
@method find
|
|
@param {Function} callback The callback to execute
|
|
@param {Object} [target] The target object to use
|
|
@return {Object} Found item or `undefined`.
|
|
*/
|
|
find: function(callback, target) {
|
|
var len = get(this, 'length');
|
|
|
|
if (target === undefined) {
|
|
target = null;
|
|
}
|
|
|
|
var context = popCtx();
|
|
var found = false;
|
|
var last = null;
|
|
var next, ret;
|
|
|
|
for(var idx = 0; idx < len && !found; idx++) {
|
|
next = this.nextObject(idx, last, context);
|
|
|
|
if (found = callback.call(target, next, idx, this)) {
|
|
ret = next;
|
|
}
|
|
|
|
last = next;
|
|
}
|
|
|
|
next = last = null;
|
|
context = pushCtx(context);
|
|
|
|
return ret;
|
|
},
|
|
|
|
/**
|
|
Returns the first item with a property matching the passed value. You
|
|
can pass an optional second argument with the target value. Otherwise
|
|
this will match any property that evaluates to `true`.
|
|
|
|
This method works much like the more generic `find()` method.
|
|
|
|
@method findBy
|
|
@param {String} key the property to test
|
|
@param {String} [value] optional value to test against.
|
|
@return {Object} found item or `undefined`
|
|
*/
|
|
findBy: function(key, value) {
|
|
return this.find(apply(this, iter, arguments));
|
|
},
|
|
|
|
/**
|
|
Returns the first item with a property matching the passed value. You
|
|
can pass an optional second argument with the target value. Otherwise
|
|
this will match any property that evaluates to `true`.
|
|
|
|
This method works much like the more generic `find()` method.
|
|
|
|
@method findProperty
|
|
@param {String} key the property to test
|
|
@param {String} [value] optional value to test against.
|
|
@return {Object} found item or `undefined`
|
|
@deprecated Use `findBy` instead
|
|
*/
|
|
findProperty: aliasMethod('findBy'),
|
|
|
|
/**
|
|
Returns `true` if the passed function returns true for every item in the
|
|
enumeration. This corresponds with the `every()` method in JavaScript 1.6.
|
|
|
|
The callback method you provide should have the following signature (all
|
|
parameters are optional):
|
|
|
|
```javascript
|
|
function(item, index, enumerable);
|
|
```
|
|
|
|
- `item` is the current item in the iteration.
|
|
- `index` is the current index in the iteration.
|
|
- `enumerable` is the enumerable object itself.
|
|
|
|
It should return the `true` or `false`.
|
|
|
|
Note that in addition to a callback, you can also pass an optional target
|
|
object that will be set as `this` on the context. This is a good way
|
|
to give your iterator function access to the current object.
|
|
|
|
Example Usage:
|
|
|
|
```javascript
|
|
if (people.every(isEngineer)) {
|
|
Paychecks.addBigBonus();
|
|
}
|
|
```
|
|
|
|
@method every
|
|
@param {Function} callback The callback to execute
|
|
@param {Object} [target] The target object to use
|
|
@return {Boolean}
|
|
*/
|
|
every: function(callback, target) {
|
|
return !this.find(function(x, idx, i) {
|
|
return !callback.call(target, x, idx, i);
|
|
});
|
|
},
|
|
|
|
/**
|
|
@method everyBy
|
|
@param {String} key the property to test
|
|
@param {String} [value] optional value to test against.
|
|
@deprecated Use `isEvery` instead
|
|
@return {Boolean}
|
|
*/
|
|
everyBy: aliasMethod('isEvery'),
|
|
|
|
/**
|
|
@method everyProperty
|
|
@param {String} key the property to test
|
|
@param {String} [value] optional value to test against.
|
|
@deprecated Use `isEvery` instead
|
|
@return {Boolean}
|
|
*/
|
|
everyProperty: aliasMethod('isEvery'),
|
|
|
|
/**
|
|
Returns `true` if the passed property resolves to `true` for all items in
|
|
the enumerable. This method is often simpler/faster than using a callback.
|
|
|
|
@method isEvery
|
|
@param {String} key the property to test
|
|
@param {String} [value] optional value to test against.
|
|
@return {Boolean}
|
|
@since 1.3.0
|
|
*/
|
|
isEvery: function(key, value) {
|
|
return this.every(apply(this, iter, arguments));
|
|
},
|
|
|
|
/**
|
|
Returns `true` if the passed function returns true for any item in the
|
|
enumeration. This corresponds with the `some()` method in JavaScript 1.6.
|
|
|
|
The callback method you provide should have the following signature (all
|
|
parameters are optional):
|
|
|
|
```javascript
|
|
function(item, index, enumerable);
|
|
```
|
|
|
|
- `item` is the current item in the iteration.
|
|
- `index` is the current index in the iteration.
|
|
- `enumerable` is the enumerable object itself.
|
|
|
|
It should return the `true` to include the item in the results, `false`
|
|
otherwise.
|
|
|
|
Note that in addition to a callback, you can also pass an optional target
|
|
object that will be set as `this` on the context. This is a good way
|
|
to give your iterator function access to the current object.
|
|
|
|
Usage Example:
|
|
|
|
```javascript
|
|
if (people.any(isManager)) {
|
|
Paychecks.addBiggerBonus();
|
|
}
|
|
```
|
|
|
|
@method any
|
|
@param {Function} callback The callback to execute
|
|
@param {Object} [target] The target object to use
|
|
@return {Boolean} `true` if the passed function returns `true` for any item
|
|
*/
|
|
any: function(callback, target) {
|
|
var len = get(this, 'length');
|
|
var context = popCtx();
|
|
var found = false;
|
|
var last = null;
|
|
var next, idx;
|
|
|
|
if (target === undefined) {
|
|
target = null;
|
|
}
|
|
|
|
for (idx = 0; idx < len && !found; idx++) {
|
|
next = this.nextObject(idx, last, context);
|
|
found = callback.call(target, next, idx, this);
|
|
last = next;
|
|
}
|
|
|
|
next = last = null;
|
|
context = pushCtx(context);
|
|
return found;
|
|
},
|
|
|
|
/**
|
|
Returns `true` if the passed function returns true for any item in the
|
|
enumeration. This corresponds with the `some()` method in JavaScript 1.6.
|
|
|
|
The callback method you provide should have the following signature (all
|
|
parameters are optional):
|
|
|
|
```javascript
|
|
function(item, index, enumerable);
|
|
```
|
|
|
|
- `item` is the current item in the iteration.
|
|
- `index` is the current index in the iteration.
|
|
- `enumerable` is the enumerable object itself.
|
|
|
|
It should return the `true` to include the item in the results, `false`
|
|
otherwise.
|
|
|
|
Note that in addition to a callback, you can also pass an optional target
|
|
object that will be set as `this` on the context. This is a good way
|
|
to give your iterator function access to the current object.
|
|
|
|
Usage Example:
|
|
|
|
```javascript
|
|
if (people.some(isManager)) {
|
|
Paychecks.addBiggerBonus();
|
|
}
|
|
```
|
|
|
|
@method some
|
|
@param {Function} callback The callback to execute
|
|
@param {Object} [target] The target object to use
|
|
@return {Boolean} `true` if the passed function returns `true` for any item
|
|
@deprecated Use `any` instead
|
|
*/
|
|
some: aliasMethod('any'),
|
|
|
|
/**
|
|
Returns `true` if the passed property resolves to `true` for any item in
|
|
the enumerable. This method is often simpler/faster than using a callback.
|
|
|
|
@method isAny
|
|
@param {String} key the property to test
|
|
@param {String} [value] optional value to test against.
|
|
@return {Boolean}
|
|
@since 1.3.0
|
|
*/
|
|
isAny: function(key, value) {
|
|
return this.any(apply(this, iter, arguments));
|
|
},
|
|
|
|
/**
|
|
@method anyBy
|
|
@param {String} key the property to test
|
|
@param {String} [value] optional value to test against.
|
|
@return {Boolean}
|
|
@deprecated Use `isAny` instead
|
|
*/
|
|
anyBy: aliasMethod('isAny'),
|
|
|
|
/**
|
|
@method someProperty
|
|
@param {String} key the property to test
|
|
@param {String} [value] optional value to test against.
|
|
@return {Boolean}
|
|
@deprecated Use `isAny` instead
|
|
*/
|
|
someProperty: aliasMethod('isAny'),
|
|
|
|
/**
|
|
This will combine the values of the enumerator into a single value. It
|
|
is a useful way to collect a summary value from an enumeration. This
|
|
corresponds to the `reduce()` method defined in JavaScript 1.8.
|
|
|
|
The callback method you provide should have the following signature (all
|
|
parameters are optional):
|
|
|
|
```javascript
|
|
function(previousValue, item, index, enumerable);
|
|
```
|
|
|
|
- `previousValue` is the value returned by the last call to the iterator.
|
|
- `item` is the current item in the iteration.
|
|
- `index` is the current index in the iteration.
|
|
- `enumerable` is the enumerable object itself.
|
|
|
|
Return the new cumulative value.
|
|
|
|
In addition to the callback you can also pass an `initialValue`. An error
|
|
will be raised if you do not pass an initial value and the enumerator is
|
|
empty.
|
|
|
|
Note that unlike the other methods, this method does not allow you to
|
|
pass a target object to set as this for the callback. It's part of the
|
|
spec. Sorry.
|
|
|
|
@method reduce
|
|
@param {Function} callback The callback to execute
|
|
@param {Object} initialValue Initial value for the reduce
|
|
@param {String} reducerProperty internal use only.
|
|
@return {Object} The reduced value.
|
|
*/
|
|
reduce: function(callback, initialValue, reducerProperty) {
|
|
if (typeof callback !== 'function') {
|
|
throw new TypeError();
|
|
}
|
|
|
|
var ret = initialValue;
|
|
|
|
this.forEach(function(item, i) {
|
|
ret = callback(ret, item, i, this, reducerProperty);
|
|
}, this);
|
|
|
|
return ret;
|
|
},
|
|
|
|
/**
|
|
Invokes the named method on every object in the receiver that
|
|
implements it. This method corresponds to the implementation in
|
|
Prototype 1.6.
|
|
|
|
@method invoke
|
|
@param {String} methodName the name of the method
|
|
@param {Object...} args optional arguments to pass as well.
|
|
@return {Array} return values from calling invoke.
|
|
*/
|
|
invoke: function(methodName) {
|
|
var ret = Ember.A();
|
|
var args;
|
|
|
|
if (arguments.length > 1) {
|
|
args = a_slice.call(arguments, 1);
|
|
}
|
|
|
|
this.forEach(function(x, idx) {
|
|
var method = x && x[methodName];
|
|
|
|
if ('function' === typeof method) {
|
|
ret[idx] = args ? apply(x, method, args) : x[methodName]();
|
|
}
|
|
}, this);
|
|
|
|
return ret;
|
|
},
|
|
|
|
/**
|
|
Simply converts the enumerable into a genuine array. The order is not
|
|
guaranteed. Corresponds to the method implemented by Prototype.
|
|
|
|
@method toArray
|
|
@return {Array} the enumerable as an array.
|
|
*/
|
|
toArray: function() {
|
|
var ret = Ember.A();
|
|
|
|
this.forEach(function(o, idx) {
|
|
ret[idx] = o;
|
|
});
|
|
|
|
return ret;
|
|
},
|
|
|
|
/**
|
|
Returns a copy of the array with all `null` and `undefined` elements removed.
|
|
|
|
```javascript
|
|
var arr = ['a', null, 'c', undefined];
|
|
arr.compact(); // ['a', 'c']
|
|
```
|
|
|
|
@method compact
|
|
@return {Array} the array without null and undefined elements.
|
|
*/
|
|
compact: function() {
|
|
return this.filter(function(value) {
|
|
return value != null;
|
|
});
|
|
},
|
|
|
|
/**
|
|
Returns a new enumerable that excludes the passed value. The default
|
|
implementation returns an array regardless of the receiver type unless
|
|
the receiver does not contain the value.
|
|
|
|
```javascript
|
|
var arr = ['a', 'b', 'a', 'c'];
|
|
arr.without('a'); // ['b', 'c']
|
|
```
|
|
|
|
@method without
|
|
@param {Object} value
|
|
@return {Ember.Enumerable}
|
|
*/
|
|
without: function(value) {
|
|
if (!this.contains(value)) {
|
|
return this; // nothing to do
|
|
}
|
|
|
|
var ret = Ember.A();
|
|
|
|
this.forEach(function(k) {
|
|
if (k !== value) {
|
|
ret[ret.length] = k;
|
|
}
|
|
});
|
|
|
|
return ret;
|
|
},
|
|
|
|
/**
|
|
Returns a new enumerable that contains only unique values. The default
|
|
implementation returns an array regardless of the receiver type.
|
|
|
|
```javascript
|
|
var arr = ['a', 'a', 'b', 'b'];
|
|
arr.uniq(); // ['a', 'b']
|
|
```
|
|
|
|
This only works on primitive data types, e.g. Strings, Numbers, etc.
|
|
|
|
@method uniq
|
|
@return {Ember.Enumerable}
|
|
*/
|
|
uniq: function() {
|
|
var ret = Ember.A();
|
|
|
|
this.forEach(function(k) {
|
|
if (indexOf(ret, k) < 0) {
|
|
ret.push(k);
|
|
}
|
|
});
|
|
|
|
return ret;
|
|
},
|
|
|
|
/**
|
|
This property will trigger anytime the enumerable's content changes.
|
|
You can observe this property to be notified of changes to the enumerables
|
|
content.
|
|
|
|
For plain enumerables, this property is read only. `Array` overrides
|
|
this method.
|
|
|
|
@property []
|
|
@type Array
|
|
@return this
|
|
*/
|
|
'[]': computed(function(key, value) {
|
|
return this;
|
|
}),
|
|
|
|
// ..........................................................
|
|
// ENUMERABLE OBSERVERS
|
|
//
|
|
|
|
/**
|
|
Registers an enumerable observer. Must implement `Ember.EnumerableObserver`
|
|
mixin.
|
|
|
|
@method addEnumerableObserver
|
|
@param {Object} target
|
|
@param {Hash} [opts]
|
|
@return this
|
|
*/
|
|
addEnumerableObserver: function(target, opts) {
|
|
var willChange = (opts && opts.willChange) || 'enumerableWillChange';
|
|
var didChange = (opts && opts.didChange) || 'enumerableDidChange';
|
|
var hasObservers = get(this, 'hasEnumerableObservers');
|
|
|
|
if (!hasObservers) {
|
|
propertyWillChange(this, 'hasEnumerableObservers');
|
|
}
|
|
|
|
addListener(this, '@enumerable:before', target, willChange);
|
|
addListener(this, '@enumerable:change', target, didChange);
|
|
|
|
if (!hasObservers) {
|
|
propertyDidChange(this, 'hasEnumerableObservers');
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Removes a registered enumerable observer.
|
|
|
|
@method removeEnumerableObserver
|
|
@param {Object} target
|
|
@param {Hash} [opts]
|
|
@return this
|
|
*/
|
|
removeEnumerableObserver: function(target, opts) {
|
|
var willChange = (opts && opts.willChange) || 'enumerableWillChange';
|
|
var didChange = (opts && opts.didChange) || 'enumerableDidChange';
|
|
var hasObservers = get(this, 'hasEnumerableObservers');
|
|
|
|
if (hasObservers) {
|
|
propertyWillChange(this, 'hasEnumerableObservers');
|
|
}
|
|
|
|
removeListener(this, '@enumerable:before', target, willChange);
|
|
removeListener(this, '@enumerable:change', target, didChange);
|
|
|
|
if (hasObservers) {
|
|
propertyDidChange(this, 'hasEnumerableObservers');
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Becomes true whenever the array currently has observers watching changes
|
|
on the array.
|
|
|
|
@property hasEnumerableObservers
|
|
@type Boolean
|
|
*/
|
|
hasEnumerableObservers: computed(function() {
|
|
return hasListeners(this, '@enumerable:change') || hasListeners(this, '@enumerable:before');
|
|
}),
|
|
|
|
|
|
/**
|
|
Invoke this method just before the contents of your enumerable will
|
|
change. You can either omit the parameters completely or pass the objects
|
|
to be removed or added if available or just a count.
|
|
|
|
@method enumerableContentWillChange
|
|
@param {Ember.Enumerable|Number} removing An enumerable of the objects to
|
|
be removed or the number of items to be removed.
|
|
@param {Ember.Enumerable|Number} adding An enumerable of the objects to be
|
|
added or the number of items to be added.
|
|
@chainable
|
|
*/
|
|
enumerableContentWillChange: function(removing, adding) {
|
|
var removeCnt, addCnt, hasDelta;
|
|
|
|
if ('number' === typeof removing) {
|
|
removeCnt = removing;
|
|
} else if (removing) {
|
|
removeCnt = get(removing, 'length');
|
|
} else {
|
|
removeCnt = removing = -1;
|
|
}
|
|
|
|
if ('number' === typeof adding) {
|
|
addCnt = adding;
|
|
} else if (adding) {
|
|
addCnt = get(adding,'length');
|
|
} else {
|
|
addCnt = adding = -1;
|
|
}
|
|
|
|
hasDelta = addCnt < 0 || removeCnt < 0 || addCnt - removeCnt !== 0;
|
|
|
|
if (removing === -1) {
|
|
removing = null;
|
|
}
|
|
|
|
if (adding === -1) {
|
|
adding = null;
|
|
}
|
|
|
|
propertyWillChange(this, '[]');
|
|
|
|
if (hasDelta) {
|
|
propertyWillChange(this, 'length');
|
|
}
|
|
|
|
sendEvent(this, '@enumerable:before', [this, removing, adding]);
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Invoke this method when the contents of your enumerable has changed.
|
|
This will notify any observers watching for content changes. If you are
|
|
implementing an ordered enumerable (such as an array), also pass the
|
|
start and end values where the content changed so that it can be used to
|
|
notify range observers.
|
|
|
|
@method enumerableContentDidChange
|
|
@param {Ember.Enumerable|Number} removing An enumerable of the objects to
|
|
be removed or the number of items to be removed.
|
|
@param {Ember.Enumerable|Number} adding An enumerable of the objects to
|
|
be added or the number of items to be added.
|
|
@chainable
|
|
*/
|
|
enumerableContentDidChange: function(removing, adding) {
|
|
var removeCnt, addCnt, hasDelta;
|
|
|
|
if ('number' === typeof removing) {
|
|
removeCnt = removing;
|
|
} else if (removing) {
|
|
removeCnt = get(removing, 'length');
|
|
} else {
|
|
removeCnt = removing = -1;
|
|
}
|
|
|
|
if ('number' === typeof adding) {
|
|
addCnt = adding;
|
|
} else if (adding) {
|
|
addCnt = get(adding, 'length');
|
|
} else {
|
|
addCnt = adding = -1;
|
|
}
|
|
|
|
hasDelta = addCnt < 0 || removeCnt < 0 || addCnt - removeCnt !== 0;
|
|
|
|
if (removing === -1) {
|
|
removing = null;
|
|
}
|
|
|
|
if (adding === -1) {
|
|
adding = null;
|
|
}
|
|
|
|
sendEvent(this, '@enumerable:change', [this, removing, adding]);
|
|
|
|
if (hasDelta) {
|
|
propertyDidChange(this, 'length');
|
|
}
|
|
|
|
propertyDidChange(this, '[]');
|
|
|
|
return this ;
|
|
},
|
|
|
|
/**
|
|
Converts the enumerable into an array and sorts by the keys
|
|
specified in the argument.
|
|
|
|
You may provide multiple arguments to sort by multiple properties.
|
|
|
|
@method sortBy
|
|
@param {String} property name(s) to sort on
|
|
@return {Array} The sorted array.
|
|
@since 1.2.0
|
|
*/
|
|
sortBy: function() {
|
|
var sortKeys = arguments;
|
|
|
|
return this.toArray().sort(function(a, b) {
|
|
for(var i = 0; i < sortKeys.length; i++) {
|
|
var key = sortKeys[i];
|
|
var propA = get(a, key);
|
|
var propB = get(b, key);
|
|
// return 1 or -1 else continue to the next sortKey
|
|
var compareValue = compare(propA, propB);
|
|
|
|
if (compareValue) {
|
|
return compareValue;
|
|
}
|
|
}
|
|
return 0;
|
|
});
|
|
}
|
|
});
|
|
});
|
|
enifed("ember-runtime/mixins/evented",
|
|
["ember-metal/mixin","ember-metal/events","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var Mixin = __dependency1__.Mixin;
|
|
var addListener = __dependency2__.addListener;
|
|
var removeListener = __dependency2__.removeListener;
|
|
var hasListeners = __dependency2__.hasListeners;
|
|
var sendEvent = __dependency2__.sendEvent;
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
|
|
/**
|
|
This mixin allows for Ember objects to subscribe to and emit events.
|
|
|
|
```javascript
|
|
App.Person = Ember.Object.extend(Ember.Evented, {
|
|
greet: function() {
|
|
// ...
|
|
this.trigger('greet');
|
|
}
|
|
});
|
|
|
|
var person = App.Person.create();
|
|
|
|
person.on('greet', function() {
|
|
console.log('Our person has greeted');
|
|
});
|
|
|
|
person.greet();
|
|
|
|
// outputs: 'Our person has greeted'
|
|
```
|
|
|
|
You can also chain multiple event subscriptions:
|
|
|
|
```javascript
|
|
person.on('greet', function() {
|
|
console.log('Our person has greeted');
|
|
}).one('greet', function() {
|
|
console.log('Offer one-time special');
|
|
}).off('event', this, forgetThis);
|
|
```
|
|
|
|
@class Evented
|
|
@namespace Ember
|
|
*/
|
|
__exports__["default"] = Mixin.create({
|
|
|
|
/**
|
|
Subscribes to a named event with given function.
|
|
|
|
```javascript
|
|
person.on('didLoad', function() {
|
|
// fired once the person has loaded
|
|
});
|
|
```
|
|
|
|
An optional target can be passed in as the 2nd argument that will
|
|
be set as the "this" for the callback. This is a good way to give your
|
|
function access to the object triggering the event. When the target
|
|
parameter is used the callback becomes the third argument.
|
|
|
|
@method on
|
|
@param {String} name The name of the event
|
|
@param {Object} [target] The "this" binding for the callback
|
|
@param {Function} method The callback to execute
|
|
@return this
|
|
*/
|
|
on: function(name, target, method) {
|
|
addListener(this, name, target, method);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Subscribes a function to a named event and then cancels the subscription
|
|
after the first time the event is triggered. It is good to use ``one`` when
|
|
you only care about the first time an event has taken place.
|
|
|
|
This function takes an optional 2nd argument that will become the "this"
|
|
value for the callback. If this argument is passed then the 3rd argument
|
|
becomes the function.
|
|
|
|
@method one
|
|
@param {String} name The name of the event
|
|
@param {Object} [target] The "this" binding for the callback
|
|
@param {Function} method The callback to execute
|
|
@return this
|
|
*/
|
|
one: function(name, target, method) {
|
|
if (!method) {
|
|
method = target;
|
|
target = null;
|
|
}
|
|
|
|
addListener(this, name, target, method, true);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Triggers a named event for the object. Any additional arguments
|
|
will be passed as parameters to the functions that are subscribed to the
|
|
event.
|
|
|
|
```javascript
|
|
person.on('didEat', function(food) {
|
|
console.log('person ate some ' + food);
|
|
});
|
|
|
|
person.trigger('didEat', 'broccoli');
|
|
|
|
// outputs: person ate some broccoli
|
|
```
|
|
@method trigger
|
|
@param {String} name The name of the event
|
|
@param {Object...} args Optional arguments to pass on
|
|
*/
|
|
trigger: function(name) {
|
|
var length = arguments.length;
|
|
var args = new Array(length - 1);
|
|
|
|
for (var i = 1; i < length; i++) {
|
|
args[i - 1] = arguments[i];
|
|
}
|
|
|
|
sendEvent(this, name, args);
|
|
},
|
|
|
|
/**
|
|
Cancels subscription for given name, target, and method.
|
|
|
|
@method off
|
|
@param {String} name The name of the event
|
|
@param {Object} target The target of the subscription
|
|
@param {Function} method The function of the subscription
|
|
@return this
|
|
*/
|
|
off: function(name, target, method) {
|
|
removeListener(this, name, target, method);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Checks to see if object has any subscriptions for named event.
|
|
|
|
@method has
|
|
@param {String} name The name of the event
|
|
@return {Boolean} does the object have a subscription for event
|
|
*/
|
|
has: function(name) {
|
|
return hasListeners(this, name);
|
|
}
|
|
});
|
|
});
|
|
enifed("ember-runtime/mixins/freezable",
|
|
["ember-metal/mixin","ember-metal/property_get","ember-metal/property_set","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
|
|
var Mixin = __dependency1__.Mixin;
|
|
var get = __dependency2__.get;
|
|
var set = __dependency3__.set;
|
|
|
|
/**
|
|
The `Ember.Freezable` mixin implements some basic methods for marking an
|
|
object as frozen. Once an object is frozen it should be read only. No changes
|
|
may be made the internal state of the object.
|
|
|
|
## Enforcement
|
|
|
|
To fully support freezing in your subclass, you must include this mixin and
|
|
override any method that might alter any property on the object to instead
|
|
raise an exception. You can check the state of an object by checking the
|
|
`isFrozen` property.
|
|
|
|
Although future versions of JavaScript may support language-level freezing
|
|
object objects, that is not the case today. Even if an object is freezable,
|
|
it is still technically possible to modify the object, even though it could
|
|
break other parts of your application that do not expect a frozen object to
|
|
change. It is, therefore, very important that you always respect the
|
|
`isFrozen` property on all freezable objects.
|
|
|
|
## Example Usage
|
|
|
|
The example below shows a simple object that implement the `Ember.Freezable`
|
|
protocol.
|
|
|
|
```javascript
|
|
Contact = Ember.Object.extend(Ember.Freezable, {
|
|
firstName: null,
|
|
lastName: null,
|
|
|
|
// swaps the names
|
|
swapNames: function() {
|
|
if (this.get('isFrozen')) throw Ember.FROZEN_ERROR;
|
|
var tmp = this.get('firstName');
|
|
this.set('firstName', this.get('lastName'));
|
|
this.set('lastName', tmp);
|
|
return this;
|
|
}
|
|
|
|
});
|
|
|
|
c = Contact.create({ firstName: "John", lastName: "Doe" });
|
|
c.swapNames(); // returns c
|
|
c.freeze();
|
|
c.swapNames(); // EXCEPTION
|
|
```
|
|
|
|
## Copying
|
|
|
|
Usually the `Ember.Freezable` protocol is implemented in cooperation with the
|
|
`Ember.Copyable` protocol, which defines a `frozenCopy()` method that will
|
|
return a frozen object, if the object implements this method as well.
|
|
|
|
@class Freezable
|
|
@namespace Ember
|
|
@since Ember 0.9
|
|
*/
|
|
var Freezable = Mixin.create({
|
|
|
|
/**
|
|
Set to `true` when the object is frozen. Use this property to detect
|
|
whether your object is frozen or not.
|
|
|
|
@property isFrozen
|
|
@type Boolean
|
|
*/
|
|
isFrozen: false,
|
|
|
|
/**
|
|
Freezes the object. Once this method has been called the object should
|
|
no longer allow any properties to be edited.
|
|
|
|
@method freeze
|
|
@return {Object} receiver
|
|
*/
|
|
freeze: function() {
|
|
if (get(this, 'isFrozen')) return this;
|
|
set(this, 'isFrozen', true);
|
|
return this;
|
|
}
|
|
|
|
});
|
|
__exports__.Freezable = Freezable;
|
|
var FROZEN_ERROR = "Frozen object cannot be modified.";
|
|
__exports__.FROZEN_ERROR = FROZEN_ERROR;
|
|
});
|
|
enifed("ember-runtime/mixins/mutable_array",
|
|
["ember-metal/property_get","ember-metal/utils","ember-metal/error","ember-metal/mixin","ember-runtime/mixins/array","ember-runtime/mixins/mutable_enumerable","ember-runtime/mixins/enumerable","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
|
|
|
|
// require('ember-runtime/mixins/array');
|
|
// require('ember-runtime/mixins/mutable_enumerable');
|
|
|
|
// ..........................................................
|
|
// CONSTANTS
|
|
//
|
|
|
|
var OUT_OF_RANGE_EXCEPTION = "Index out of range";
|
|
var EMPTY = [];
|
|
|
|
// ..........................................................
|
|
// HELPERS
|
|
//
|
|
|
|
var get = __dependency1__.get;
|
|
var isArray = __dependency2__.isArray;
|
|
var EmberError = __dependency3__["default"];
|
|
var Mixin = __dependency4__.Mixin;
|
|
var required = __dependency4__.required;
|
|
var EmberArray = __dependency5__["default"];
|
|
var MutableEnumerable = __dependency6__["default"];
|
|
var Enumerable = __dependency7__["default"];
|
|
/**
|
|
This mixin defines the API for modifying array-like objects. These methods
|
|
can be applied only to a collection that keeps its items in an ordered set.
|
|
It builds upon the Array mixin and adds methods to modify the array.
|
|
Concrete implementations of this class include ArrayProxy and ArrayController.
|
|
|
|
It is important to use the methods in this class to modify arrays so that
|
|
changes are observable. This allows the binding system in Ember to function
|
|
correctly.
|
|
|
|
|
|
Note that an Array can change even if it does not implement this mixin.
|
|
For example, one might implement a SparseArray that cannot be directly
|
|
modified, but if its underlying enumerable changes, it will change also.
|
|
|
|
@class MutableArray
|
|
@namespace Ember
|
|
@uses Ember.Array
|
|
@uses Ember.MutableEnumerable
|
|
*/
|
|
__exports__["default"] = Mixin.create(EmberArray, MutableEnumerable, {
|
|
|
|
/**
|
|
__Required.__ You must implement this method to apply this mixin.
|
|
|
|
This is one of the primitives you must implement to support `Ember.Array`.
|
|
You should replace amt objects started at idx with the objects in the
|
|
passed array. You should also call `this.enumerableContentDidChange()`
|
|
|
|
@method replace
|
|
@param {Number} idx Starting index in the array to replace. If
|
|
idx >= length, then append to the end of the array.
|
|
@param {Number} amt Number of elements that should be removed from
|
|
the array, starting at *idx*.
|
|
@param {Array} objects An array of zero or more objects that should be
|
|
inserted into the array at *idx*
|
|
*/
|
|
replace: required(),
|
|
|
|
/**
|
|
Remove all elements from the array. This is useful if you
|
|
want to reuse an existing array without having to recreate it.
|
|
|
|
```javascript
|
|
var colors = ["red", "green", "blue"];
|
|
color.length(); // 3
|
|
colors.clear(); // []
|
|
colors.length(); // 0
|
|
```
|
|
|
|
@method clear
|
|
@return {Ember.Array} An empty Array.
|
|
*/
|
|
clear: function () {
|
|
var len = get(this, 'length');
|
|
if (len === 0) return this;
|
|
this.replace(0, len, EMPTY);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
This will use the primitive `replace()` method to insert an object at the
|
|
specified index.
|
|
|
|
```javascript
|
|
var colors = ["red", "green", "blue"];
|
|
colors.insertAt(2, "yellow"); // ["red", "green", "yellow", "blue"]
|
|
colors.insertAt(5, "orange"); // Error: Index out of range
|
|
```
|
|
|
|
@method insertAt
|
|
@param {Number} idx index of insert the object at.
|
|
@param {Object} object object to insert
|
|
@return {Ember.Array} receiver
|
|
*/
|
|
insertAt: function(idx, object) {
|
|
if (idx > get(this, 'length')) throw new EmberError(OUT_OF_RANGE_EXCEPTION);
|
|
this.replace(idx, 0, [object]);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Remove an object at the specified index using the `replace()` primitive
|
|
method. You can pass either a single index, or a start and a length.
|
|
|
|
If you pass a start and length that is beyond the
|
|
length this method will throw an `OUT_OF_RANGE_EXCEPTION`.
|
|
|
|
```javascript
|
|
var colors = ["red", "green", "blue", "yellow", "orange"];
|
|
colors.removeAt(0); // ["green", "blue", "yellow", "orange"]
|
|
colors.removeAt(2, 2); // ["green", "blue"]
|
|
colors.removeAt(4, 2); // Error: Index out of range
|
|
```
|
|
|
|
@method removeAt
|
|
@param {Number} start index, start of range
|
|
@param {Number} len length of passing range
|
|
@return {Ember.Array} receiver
|
|
*/
|
|
removeAt: function(start, len) {
|
|
if ('number' === typeof start) {
|
|
|
|
if ((start < 0) || (start >= get(this, 'length'))) {
|
|
throw new EmberError(OUT_OF_RANGE_EXCEPTION);
|
|
}
|
|
|
|
// fast case
|
|
if (len === undefined) len = 1;
|
|
this.replace(start, len, EMPTY);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Push the object onto the end of the array. Works just like `push()` but it
|
|
is KVO-compliant.
|
|
|
|
```javascript
|
|
var colors = ["red", "green"];
|
|
colors.pushObject("black"); // ["red", "green", "black"]
|
|
colors.pushObject(["yellow"]); // ["red", "green", ["yellow"]]
|
|
```
|
|
|
|
@method pushObject
|
|
@param {*} obj object to push
|
|
@return object same object passed as a param
|
|
*/
|
|
pushObject: function(obj) {
|
|
this.insertAt(get(this, 'length'), obj);
|
|
return obj;
|
|
},
|
|
|
|
/**
|
|
Add the objects in the passed numerable to the end of the array. Defers
|
|
notifying observers of the change until all objects are added.
|
|
|
|
```javascript
|
|
var colors = ["red"];
|
|
colors.pushObjects(["yellow", "orange"]); // ["red", "yellow", "orange"]
|
|
```
|
|
|
|
@method pushObjects
|
|
@param {Ember.Enumerable} objects the objects to add
|
|
@return {Ember.Array} receiver
|
|
*/
|
|
pushObjects: function(objects) {
|
|
if (!(Enumerable.detect(objects) || isArray(objects))) {
|
|
throw new TypeError("Must pass Ember.Enumerable to Ember.MutableArray#pushObjects");
|
|
}
|
|
this.replace(get(this, 'length'), 0, objects);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Pop object from array or nil if none are left. Works just like `pop()` but
|
|
it is KVO-compliant.
|
|
|
|
```javascript
|
|
var colors = ["red", "green", "blue"];
|
|
colors.popObject(); // "blue"
|
|
console.log(colors); // ["red", "green"]
|
|
```
|
|
|
|
@method popObject
|
|
@return object
|
|
*/
|
|
popObject: function() {
|
|
var len = get(this, 'length');
|
|
if (len === 0) return null;
|
|
|
|
var ret = this.objectAt(len-1);
|
|
this.removeAt(len-1, 1);
|
|
return ret;
|
|
},
|
|
|
|
/**
|
|
Shift an object from start of array or nil if none are left. Works just
|
|
like `shift()` but it is KVO-compliant.
|
|
|
|
```javascript
|
|
var colors = ["red", "green", "blue"];
|
|
colors.shiftObject(); // "red"
|
|
console.log(colors); // ["green", "blue"]
|
|
```
|
|
|
|
@method shiftObject
|
|
@return object
|
|
*/
|
|
shiftObject: function() {
|
|
if (get(this, 'length') === 0) return null;
|
|
var ret = this.objectAt(0);
|
|
this.removeAt(0);
|
|
return ret;
|
|
},
|
|
|
|
/**
|
|
Unshift an object to start of array. Works just like `unshift()` but it is
|
|
KVO-compliant.
|
|
|
|
```javascript
|
|
var colors = ["red"];
|
|
colors.unshiftObject("yellow"); // ["yellow", "red"]
|
|
colors.unshiftObject(["black"]); // [["black"], "yellow", "red"]
|
|
```
|
|
|
|
@method unshiftObject
|
|
@param {*} obj object to unshift
|
|
@return object same object passed as a param
|
|
*/
|
|
unshiftObject: function(obj) {
|
|
this.insertAt(0, obj);
|
|
return obj;
|
|
},
|
|
|
|
/**
|
|
Adds the named objects to the beginning of the array. Defers notifying
|
|
observers until all objects have been added.
|
|
|
|
```javascript
|
|
var colors = ["red"];
|
|
colors.unshiftObjects(["black", "white"]); // ["black", "white", "red"]
|
|
colors.unshiftObjects("yellow"); // Type Error: 'undefined' is not a function
|
|
```
|
|
|
|
@method unshiftObjects
|
|
@param {Ember.Enumerable} objects the objects to add
|
|
@return {Ember.Array} receiver
|
|
*/
|
|
unshiftObjects: function(objects) {
|
|
this.replace(0, 0, objects);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Reverse objects in the array. Works just like `reverse()` but it is
|
|
KVO-compliant.
|
|
|
|
@method reverseObjects
|
|
@return {Ember.Array} receiver
|
|
*/
|
|
reverseObjects: function() {
|
|
var len = get(this, 'length');
|
|
if (len === 0) return this;
|
|
var objects = this.toArray().reverse();
|
|
this.replace(0, len, objects);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Replace all the receiver's content with content of the argument.
|
|
If argument is an empty array receiver will be cleared.
|
|
|
|
```javascript
|
|
var colors = ["red", "green", "blue"];
|
|
colors.setObjects(["black", "white"]); // ["black", "white"]
|
|
colors.setObjects([]); // []
|
|
```
|
|
|
|
@method setObjects
|
|
@param {Ember.Array} objects array whose content will be used for replacing
|
|
the content of the receiver
|
|
@return {Ember.Array} receiver with the new content
|
|
*/
|
|
setObjects: function(objects) {
|
|
if (objects.length === 0) return this.clear();
|
|
|
|
var len = get(this, 'length');
|
|
this.replace(0, len, objects);
|
|
return this;
|
|
},
|
|
|
|
// ..........................................................
|
|
// IMPLEMENT Ember.MutableEnumerable
|
|
//
|
|
|
|
/**
|
|
Remove all occurrences of an object in the array.
|
|
|
|
```javascript
|
|
var cities = ["Chicago", "Berlin", "Lima", "Chicago"];
|
|
cities.removeObject("Chicago"); // ["Berlin", "Lima"]
|
|
cities.removeObject("Lima"); // ["Berlin"]
|
|
cities.removeObject("Tokyo") // ["Berlin"]
|
|
```
|
|
|
|
@method removeObject
|
|
@param {*} obj object to remove
|
|
@return {Ember.Array} receiver
|
|
*/
|
|
removeObject: function(obj) {
|
|
var loc = get(this, 'length') || 0;
|
|
while(--loc >= 0) {
|
|
var curObject = this.objectAt(loc);
|
|
if (curObject === obj) this.removeAt(loc);
|
|
}
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Push the object onto the end of the array if it is not already
|
|
present in the array.
|
|
|
|
```javascript
|
|
var cities = ["Chicago", "Berlin"];
|
|
cities.addObject("Lima"); // ["Chicago", "Berlin", "Lima"]
|
|
cities.addObject("Berlin"); // ["Chicago", "Berlin", "Lima"]
|
|
```
|
|
|
|
@method addObject
|
|
@param {*} obj object to add, if not already present
|
|
@return {Ember.Array} receiver
|
|
*/
|
|
addObject: function(obj) {
|
|
if (!this.contains(obj)) this.pushObject(obj);
|
|
return this;
|
|
}
|
|
|
|
});
|
|
});
|
|
enifed("ember-runtime/mixins/mutable_enumerable",
|
|
["ember-metal/enumerable_utils","ember-runtime/mixins/enumerable","ember-metal/mixin","ember-metal/property_events","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
|
|
"use strict";
|
|
var forEach = __dependency1__.forEach;
|
|
var Enumerable = __dependency2__["default"];
|
|
var Mixin = __dependency3__.Mixin;
|
|
var required = __dependency3__.required;
|
|
var beginPropertyChanges = __dependency4__.beginPropertyChanges;
|
|
var endPropertyChanges = __dependency4__.endPropertyChanges;
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
|
|
/**
|
|
This mixin defines the API for modifying generic enumerables. These methods
|
|
can be applied to an object regardless of whether it is ordered or
|
|
unordered.
|
|
|
|
Note that an Enumerable can change even if it does not implement this mixin.
|
|
For example, a MappedEnumerable cannot be directly modified but if its
|
|
underlying enumerable changes, it will change also.
|
|
|
|
## Adding Objects
|
|
|
|
To add an object to an enumerable, use the `addObject()` method. This
|
|
method will only add the object to the enumerable if the object is not
|
|
already present and is of a type supported by the enumerable.
|
|
|
|
```javascript
|
|
set.addObject(contact);
|
|
```
|
|
|
|
## Removing Objects
|
|
|
|
To remove an object from an enumerable, use the `removeObject()` method. This
|
|
will only remove the object if it is present in the enumerable, otherwise
|
|
this method has no effect.
|
|
|
|
```javascript
|
|
set.removeObject(contact);
|
|
```
|
|
|
|
## Implementing In Your Own Code
|
|
|
|
If you are implementing an object and want to support this API, just include
|
|
this mixin in your class and implement the required methods. In your unit
|
|
tests, be sure to apply the Ember.MutableEnumerableTests to your object.
|
|
|
|
@class MutableEnumerable
|
|
@namespace Ember
|
|
@uses Ember.Enumerable
|
|
*/
|
|
__exports__["default"] = Mixin.create(Enumerable, {
|
|
|
|
/**
|
|
__Required.__ You must implement this method to apply this mixin.
|
|
|
|
Attempts to add the passed object to the receiver if the object is not
|
|
already present in the collection. If the object is present, this method
|
|
has no effect.
|
|
|
|
If the passed object is of a type not supported by the receiver,
|
|
then this method should raise an exception.
|
|
|
|
@method addObject
|
|
@param {Object} object The object to add to the enumerable.
|
|
@return {Object} the passed object
|
|
*/
|
|
addObject: required(Function),
|
|
|
|
/**
|
|
Adds each object in the passed enumerable to the receiver.
|
|
|
|
@method addObjects
|
|
@param {Ember.Enumerable} objects the objects to add.
|
|
@return {Object} receiver
|
|
*/
|
|
addObjects: function(objects) {
|
|
beginPropertyChanges(this);
|
|
forEach(objects, function(obj) { this.addObject(obj); }, this);
|
|
endPropertyChanges(this);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
__Required.__ You must implement this method to apply this mixin.
|
|
|
|
Attempts to remove the passed object from the receiver collection if the
|
|
object is present in the collection. If the object is not present,
|
|
this method has no effect.
|
|
|
|
If the passed object is of a type not supported by the receiver,
|
|
then this method should raise an exception.
|
|
|
|
@method removeObject
|
|
@param {Object} object The object to remove from the enumerable.
|
|
@return {Object} the passed object
|
|
*/
|
|
removeObject: required(Function),
|
|
|
|
|
|
/**
|
|
Removes each object in the passed enumerable from the receiver.
|
|
|
|
@method removeObjects
|
|
@param {Ember.Enumerable} objects the objects to remove
|
|
@return {Object} receiver
|
|
*/
|
|
removeObjects: function(objects) {
|
|
beginPropertyChanges(this);
|
|
for (var i = objects.length - 1; i >= 0; i--) {
|
|
this.removeObject(objects[i]);
|
|
}
|
|
endPropertyChanges(this);
|
|
return this;
|
|
}
|
|
});
|
|
});
|
|
enifed("ember-runtime/mixins/observable",
|
|
["ember-metal/core","ember-metal/property_get","ember-metal/property_set","ember-metal/utils","ember-metal/get_properties","ember-metal/set_properties","ember-metal/mixin","ember-metal/events","ember-metal/property_events","ember-metal/observer","ember-metal/computed","ember-metal/is_none","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.assert
|
|
|
|
var get = __dependency2__.get;
|
|
var getWithDefault = __dependency2__.getWithDefault;
|
|
var set = __dependency3__.set;
|
|
var apply = __dependency4__.apply;
|
|
var getProperties = __dependency5__["default"];
|
|
var setProperties = __dependency6__["default"];
|
|
var Mixin = __dependency7__.Mixin;
|
|
var hasListeners = __dependency8__.hasListeners;
|
|
var beginPropertyChanges = __dependency9__.beginPropertyChanges;
|
|
var propertyWillChange = __dependency9__.propertyWillChange;
|
|
var propertyDidChange = __dependency9__.propertyDidChange;
|
|
var endPropertyChanges = __dependency9__.endPropertyChanges;
|
|
var addObserver = __dependency10__.addObserver;
|
|
var addBeforeObserver = __dependency10__.addBeforeObserver;
|
|
var removeObserver = __dependency10__.removeObserver;
|
|
var observersFor = __dependency10__.observersFor;
|
|
var cacheFor = __dependency11__.cacheFor;
|
|
var isNone = __dependency12__["default"];
|
|
|
|
|
|
var slice = Array.prototype.slice;
|
|
/**
|
|
## Overview
|
|
|
|
This mixin provides properties and property observing functionality, core
|
|
features of the Ember object model.
|
|
|
|
Properties and observers allow one object to observe changes to a
|
|
property on another object. This is one of the fundamental ways that
|
|
models, controllers and views communicate with each other in an Ember
|
|
application.
|
|
|
|
Any object that has this mixin applied can be used in observer
|
|
operations. That includes `Ember.Object` and most objects you will
|
|
interact with as you write your Ember application.
|
|
|
|
Note that you will not generally apply this mixin to classes yourself,
|
|
but you will use the features provided by this module frequently, so it
|
|
is important to understand how to use it.
|
|
|
|
## Using `get()` and `set()`
|
|
|
|
Because of Ember's support for bindings and observers, you will always
|
|
access properties using the get method, and set properties using the
|
|
set method. This allows the observing objects to be notified and
|
|
computed properties to be handled properly.
|
|
|
|
More documentation about `get` and `set` are below.
|
|
|
|
## Observing Property Changes
|
|
|
|
You typically observe property changes simply by adding the `observes`
|
|
call to the end of your method declarations in classes that you write.
|
|
For example:
|
|
|
|
```javascript
|
|
Ember.Object.extend({
|
|
valueObserver: function() {
|
|
// Executes whenever the "value" property changes
|
|
}.observes('value')
|
|
});
|
|
```
|
|
|
|
Although this is the most common way to add an observer, this capability
|
|
is actually built into the `Ember.Object` class on top of two methods
|
|
defined in this mixin: `addObserver` and `removeObserver`. You can use
|
|
these two methods to add and remove observers yourself if you need to
|
|
do so at runtime.
|
|
|
|
To add an observer for a property, call:
|
|
|
|
```javascript
|
|
object.addObserver('propertyKey', targetObject, targetAction)
|
|
```
|
|
|
|
This will call the `targetAction` method on the `targetObject` whenever
|
|
the value of the `propertyKey` changes.
|
|
|
|
Note that if `propertyKey` is a computed property, the observer will be
|
|
called when any of the property dependencies are changed, even if the
|
|
resulting value of the computed property is unchanged. This is necessary
|
|
because computed properties are not computed until `get` is called.
|
|
|
|
@class Observable
|
|
@namespace Ember
|
|
*/
|
|
__exports__["default"] = Mixin.create({
|
|
|
|
/**
|
|
Retrieves the value of a property from the object.
|
|
|
|
This method is usually similar to using `object[keyName]` or `object.keyName`,
|
|
however it supports both computed properties and the unknownProperty
|
|
handler.
|
|
|
|
Because `get` unifies the syntax for accessing all these kinds
|
|
of properties, it can make many refactorings easier, such as replacing a
|
|
simple property with a computed property, or vice versa.
|
|
|
|
### Computed Properties
|
|
|
|
Computed properties are methods defined with the `property` modifier
|
|
declared at the end, such as:
|
|
|
|
```javascript
|
|
fullName: function() {
|
|
return this.get('firstName') + ' ' + this.get('lastName');
|
|
}.property('firstName', 'lastName')
|
|
```
|
|
|
|
When you call `get` on a computed property, the function will be
|
|
called and the return value will be returned instead of the function
|
|
itself.
|
|
|
|
### Unknown Properties
|
|
|
|
Likewise, if you try to call `get` on a property whose value is
|
|
`undefined`, the `unknownProperty()` method will be called on the object.
|
|
If this method returns any value other than `undefined`, it will be returned
|
|
instead. This allows you to implement "virtual" properties that are
|
|
not defined upfront.
|
|
|
|
@method get
|
|
@param {String} keyName The property to retrieve
|
|
@return {Object} The property value or undefined.
|
|
*/
|
|
get: function(keyName) {
|
|
return get(this, keyName);
|
|
},
|
|
|
|
/**
|
|
To get the values of multiple properties at once, call `getProperties`
|
|
with a list of strings or an array:
|
|
|
|
```javascript
|
|
record.getProperties('firstName', 'lastName', 'zipCode');
|
|
// { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
|
|
```
|
|
|
|
is equivalent to:
|
|
|
|
```javascript
|
|
record.getProperties(['firstName', 'lastName', 'zipCode']);
|
|
// { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
|
|
```
|
|
|
|
@method getProperties
|
|
@param {String...|Array} list of keys to get
|
|
@return {Hash}
|
|
*/
|
|
getProperties: function() {
|
|
return apply(null, getProperties, [this].concat(slice.call(arguments)));
|
|
},
|
|
|
|
/**
|
|
Sets the provided key or path to the value.
|
|
|
|
This method is generally very similar to calling `object[key] = value` or
|
|
`object.key = value`, except that it provides support for computed
|
|
properties, the `setUnknownProperty()` method and property observers.
|
|
|
|
### Computed Properties
|
|
|
|
If you try to set a value on a key that has a computed property handler
|
|
defined (see the `get()` method for an example), then `set()` will call
|
|
that method, passing both the value and key instead of simply changing
|
|
the value itself. This is useful for those times when you need to
|
|
implement a property that is composed of one or more member
|
|
properties.
|
|
|
|
### Unknown Properties
|
|
|
|
If you try to set a value on a key that is undefined in the target
|
|
object, then the `setUnknownProperty()` handler will be called instead. This
|
|
gives you an opportunity to implement complex "virtual" properties that
|
|
are not predefined on the object. If `setUnknownProperty()` returns
|
|
undefined, then `set()` will simply set the value on the object.
|
|
|
|
### Property Observers
|
|
|
|
In addition to changing the property, `set()` will also register a property
|
|
change with the object. Unless you have placed this call inside of a
|
|
`beginPropertyChanges()` and `endPropertyChanges(),` any "local" observers
|
|
(i.e. observer methods declared on the same object), will be called
|
|
immediately. Any "remote" observers (i.e. observer methods declared on
|
|
another object) will be placed in a queue and called at a later time in a
|
|
coalesced manner.
|
|
|
|
### Chaining
|
|
|
|
In addition to property changes, `set()` returns the value of the object
|
|
itself so you can do chaining like this:
|
|
|
|
```javascript
|
|
record.set('firstName', 'Charles').set('lastName', 'Jolley');
|
|
```
|
|
|
|
@method set
|
|
@param {String} keyName The property to set
|
|
@param {Object} value The value to set or `null`.
|
|
@return {Ember.Observable}
|
|
*/
|
|
set: function(keyName, value) {
|
|
set(this, keyName, value);
|
|
return this;
|
|
},
|
|
|
|
|
|
/**
|
|
Sets a list of properties at once. These properties are set inside
|
|
a single `beginPropertyChanges` and `endPropertyChanges` batch, so
|
|
observers will be buffered.
|
|
|
|
```javascript
|
|
record.setProperties({ firstName: 'Charles', lastName: 'Jolley' });
|
|
```
|
|
|
|
@method setProperties
|
|
@param {Hash} hash the hash of keys and values to set
|
|
@return {Ember.Observable}
|
|
*/
|
|
setProperties: function(hash) {
|
|
return setProperties(this, hash);
|
|
},
|
|
|
|
/**
|
|
Begins a grouping of property changes.
|
|
|
|
You can use this method to group property changes so that notifications
|
|
will not be sent until the changes are finished. If you plan to make a
|
|
large number of changes to an object at one time, you should call this
|
|
method at the beginning of the changes to begin deferring change
|
|
notifications. When you are done making changes, call
|
|
`endPropertyChanges()` to deliver the deferred change notifications and end
|
|
deferring.
|
|
|
|
@method beginPropertyChanges
|
|
@return {Ember.Observable}
|
|
*/
|
|
beginPropertyChanges: function() {
|
|
beginPropertyChanges();
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Ends a grouping of property changes.
|
|
|
|
You can use this method to group property changes so that notifications
|
|
will not be sent until the changes are finished. If you plan to make a
|
|
large number of changes to an object at one time, you should call
|
|
`beginPropertyChanges()` at the beginning of the changes to defer change
|
|
notifications. When you are done making changes, call this method to
|
|
deliver the deferred change notifications and end deferring.
|
|
|
|
@method endPropertyChanges
|
|
@return {Ember.Observable}
|
|
*/
|
|
endPropertyChanges: function() {
|
|
endPropertyChanges();
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Notify the observer system that a property is about to change.
|
|
|
|
Sometimes you need to change a value directly or indirectly without
|
|
actually calling `get()` or `set()` on it. In this case, you can use this
|
|
method and `propertyDidChange()` instead. Calling these two methods
|
|
together will notify all observers that the property has potentially
|
|
changed value.
|
|
|
|
Note that you must always call `propertyWillChange` and `propertyDidChange`
|
|
as a pair. If you do not, it may get the property change groups out of
|
|
order and cause notifications to be delivered more often than you would
|
|
like.
|
|
|
|
@method propertyWillChange
|
|
@param {String} keyName The property key that is about to change.
|
|
@return {Ember.Observable}
|
|
*/
|
|
propertyWillChange: function(keyName) {
|
|
propertyWillChange(this, keyName);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Notify the observer system that a property has just changed.
|
|
|
|
Sometimes you need to change a value directly or indirectly without
|
|
actually calling `get()` or `set()` on it. In this case, you can use this
|
|
method and `propertyWillChange()` instead. Calling these two methods
|
|
together will notify all observers that the property has potentially
|
|
changed value.
|
|
|
|
Note that you must always call `propertyWillChange` and `propertyDidChange`
|
|
as a pair. If you do not, it may get the property change groups out of
|
|
order and cause notifications to be delivered more often than you would
|
|
like.
|
|
|
|
@method propertyDidChange
|
|
@param {String} keyName The property key that has just changed.
|
|
@return {Ember.Observable}
|
|
*/
|
|
propertyDidChange: function(keyName) {
|
|
propertyDidChange(this, keyName);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Convenience method to call `propertyWillChange` and `propertyDidChange` in
|
|
succession.
|
|
|
|
@method notifyPropertyChange
|
|
@param {String} keyName The property key to be notified about.
|
|
@return {Ember.Observable}
|
|
*/
|
|
notifyPropertyChange: function(keyName) {
|
|
this.propertyWillChange(keyName);
|
|
this.propertyDidChange(keyName);
|
|
return this;
|
|
},
|
|
|
|
addBeforeObserver: function(key, target, method) {
|
|
Ember.deprecate('Before observers are deprecated and will be removed in a future release. If you want to keep track of previous values you have to implement it yourself.', false, { url: 'http://emberjs.com/guides/deprecations/#toc_deprecate-beforeobservers' });
|
|
addBeforeObserver(this, key, target, method);
|
|
},
|
|
|
|
/**
|
|
Adds an observer on a property.
|
|
|
|
This is the core method used to register an observer for a property.
|
|
|
|
Once you call this method, any time the key's value is set, your observer
|
|
will be notified. Note that the observers are triggered any time the
|
|
value is set, regardless of whether it has actually changed. Your
|
|
observer should be prepared to handle that.
|
|
|
|
You can also pass an optional context parameter to this method. The
|
|
context will be passed to your observer method whenever it is triggered.
|
|
Note that if you add the same target/method pair on a key multiple times
|
|
with different context parameters, your observer will only be called once
|
|
with the last context you passed.
|
|
|
|
### Observer Methods
|
|
|
|
Observer methods you pass should generally have the following signature if
|
|
you do not pass a `context` parameter:
|
|
|
|
```javascript
|
|
fooDidChange: function(sender, key, value, rev) { };
|
|
```
|
|
|
|
The sender is the object that changed. The key is the property that
|
|
changes. The value property is currently reserved and unused. The rev
|
|
is the last property revision of the object when it changed, which you can
|
|
use to detect if the key value has really changed or not.
|
|
|
|
If you pass a `context` parameter, the context will be passed before the
|
|
revision like so:
|
|
|
|
```javascript
|
|
fooDidChange: function(sender, key, value, context, rev) { };
|
|
```
|
|
|
|
Usually you will not need the value, context or revision parameters at
|
|
the end. In this case, it is common to write observer methods that take
|
|
only a sender and key value as parameters or, if you aren't interested in
|
|
any of these values, to write an observer that has no parameters at all.
|
|
|
|
@method addObserver
|
|
@param {String} key The key to observer
|
|
@param {Object} target The target object to invoke
|
|
@param {String|Function} method The method to invoke.
|
|
*/
|
|
addObserver: function(key, target, method) {
|
|
addObserver(this, key, target, method);
|
|
},
|
|
|
|
/**
|
|
Remove an observer you have previously registered on this object. Pass
|
|
the same key, target, and method you passed to `addObserver()` and your
|
|
target will no longer receive notifications.
|
|
|
|
@method removeObserver
|
|
@param {String} key The key to observer
|
|
@param {Object} target The target object to invoke
|
|
@param {String|Function} method The method to invoke.
|
|
*/
|
|
removeObserver: function(key, target, method) {
|
|
removeObserver(this, key, target, method);
|
|
},
|
|
|
|
/**
|
|
Returns `true` if the object currently has observers registered for a
|
|
particular key. You can use this method to potentially defer performing
|
|
an expensive action until someone begins observing a particular property
|
|
on the object.
|
|
|
|
@method hasObserverFor
|
|
@param {String} key Key to check
|
|
@return {Boolean}
|
|
*/
|
|
hasObserverFor: function(key) {
|
|
return hasListeners(this, key+':change');
|
|
},
|
|
|
|
/**
|
|
Retrieves the value of a property, or a default value in the case that the
|
|
property returns `undefined`.
|
|
|
|
```javascript
|
|
person.getWithDefault('lastName', 'Doe');
|
|
```
|
|
|
|
@method getWithDefault
|
|
@param {String} keyName The name of the property to retrieve
|
|
@param {Object} defaultValue The value to return if the property value is undefined
|
|
@return {Object} The property value or the defaultValue.
|
|
*/
|
|
getWithDefault: function(keyName, defaultValue) {
|
|
return getWithDefault(this, keyName, defaultValue);
|
|
},
|
|
|
|
/**
|
|
Set the value of a property to the current value plus some amount.
|
|
|
|
```javascript
|
|
person.incrementProperty('age');
|
|
team.incrementProperty('score', 2);
|
|
```
|
|
|
|
@method incrementProperty
|
|
@param {String} keyName The name of the property to increment
|
|
@param {Number} increment The amount to increment by. Defaults to 1
|
|
@return {Number} The new property value
|
|
*/
|
|
incrementProperty: function(keyName, increment) {
|
|
if (isNone(increment)) { increment = 1; }
|
|
Ember.assert("Must pass a numeric value to incrementProperty", (!isNaN(parseFloat(increment)) && isFinite(increment)));
|
|
set(this, keyName, (parseFloat(get(this, keyName)) || 0) + increment);
|
|
return get(this, keyName);
|
|
},
|
|
|
|
/**
|
|
Set the value of a property to the current value minus some amount.
|
|
|
|
```javascript
|
|
player.decrementProperty('lives');
|
|
orc.decrementProperty('health', 5);
|
|
```
|
|
|
|
@method decrementProperty
|
|
@param {String} keyName The name of the property to decrement
|
|
@param {Number} decrement The amount to decrement by. Defaults to 1
|
|
@return {Number} The new property value
|
|
*/
|
|
decrementProperty: function(keyName, decrement) {
|
|
if (isNone(decrement)) { decrement = 1; }
|
|
Ember.assert("Must pass a numeric value to decrementProperty", (!isNaN(parseFloat(decrement)) && isFinite(decrement)));
|
|
set(this, keyName, (get(this, keyName) || 0) - decrement);
|
|
return get(this, keyName);
|
|
},
|
|
|
|
/**
|
|
Set the value of a boolean property to the opposite of its
|
|
current value.
|
|
|
|
```javascript
|
|
starship.toggleProperty('warpDriveEngaged');
|
|
```
|
|
|
|
@method toggleProperty
|
|
@param {String} keyName The name of the property to toggle
|
|
@return {Object} The new property value
|
|
*/
|
|
toggleProperty: function(keyName) {
|
|
set(this, keyName, !get(this, keyName));
|
|
return get(this, keyName);
|
|
},
|
|
|
|
/**
|
|
Returns the cached value of a computed property, if it exists.
|
|
This allows you to inspect the value of a computed property
|
|
without accidentally invoking it if it is intended to be
|
|
generated lazily.
|
|
|
|
@method cacheFor
|
|
@param {String} keyName
|
|
@return {Object} The cached value of the computed property, if any
|
|
*/
|
|
cacheFor: function(keyName) {
|
|
return cacheFor(this, keyName);
|
|
},
|
|
|
|
// intended for debugging purposes
|
|
observersForKey: function(keyName) {
|
|
return observersFor(this, keyName);
|
|
}
|
|
});
|
|
});
|
|
enifed("ember-runtime/mixins/promise_proxy",
|
|
["ember-metal/property_get","ember-metal/set_properties","ember-metal/computed","ember-metal/mixin","ember-metal/error","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) {
|
|
"use strict";
|
|
var get = __dependency1__.get;
|
|
var setProperties = __dependency2__["default"];
|
|
var computed = __dependency3__.computed;
|
|
var Mixin = __dependency4__.Mixin;
|
|
var EmberError = __dependency5__["default"];
|
|
|
|
var not = computed.not;
|
|
var or = computed.or;
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
|
|
function tap(proxy, promise) {
|
|
setProperties(proxy, {
|
|
isFulfilled: false,
|
|
isRejected: false
|
|
});
|
|
|
|
return promise.then(function(value) {
|
|
setProperties(proxy, {
|
|
content: value,
|
|
isFulfilled: true
|
|
});
|
|
return value;
|
|
}, function(reason) {
|
|
setProperties(proxy, {
|
|
reason: reason,
|
|
isRejected: true
|
|
});
|
|
throw reason;
|
|
}, "Ember: PromiseProxy");
|
|
}
|
|
|
|
/**
|
|
A low level mixin making ObjectProxy, ObjectController or ArrayController's promise aware.
|
|
|
|
```javascript
|
|
var ObjectPromiseController = Ember.ObjectController.extend(Ember.PromiseProxyMixin);
|
|
|
|
var controller = ObjectPromiseController.create({
|
|
promise: $.getJSON('/some/remote/data.json')
|
|
});
|
|
|
|
controller.then(function(json){
|
|
// the json
|
|
}, function(reason) {
|
|
// the reason why you have no json
|
|
});
|
|
```
|
|
|
|
the controller has bindable attributes which
|
|
track the promises life cycle
|
|
|
|
```javascript
|
|
controller.get('isPending') //=> true
|
|
controller.get('isSettled') //=> false
|
|
controller.get('isRejected') //=> false
|
|
controller.get('isFulfilled') //=> false
|
|
```
|
|
|
|
When the the $.getJSON completes, and the promise is fulfilled
|
|
with json, the life cycle attributes will update accordingly.
|
|
|
|
```javascript
|
|
controller.get('isPending') //=> false
|
|
controller.get('isSettled') //=> true
|
|
controller.get('isRejected') //=> false
|
|
controller.get('isFulfilled') //=> true
|
|
```
|
|
|
|
As the controller is an ObjectController, and the json now its content,
|
|
all the json properties will be available directly from the controller.
|
|
|
|
```javascript
|
|
// Assuming the following json:
|
|
{
|
|
firstName: 'Stefan',
|
|
lastName: 'Penner'
|
|
}
|
|
|
|
// both properties will accessible on the controller
|
|
controller.get('firstName') //=> 'Stefan'
|
|
controller.get('lastName') //=> 'Penner'
|
|
```
|
|
|
|
If the controller is backing a template, the attributes are
|
|
bindable from within that template
|
|
|
|
```handlebars
|
|
{{#if isPending}}
|
|
loading...
|
|
{{else}}
|
|
firstName: {{firstName}}
|
|
lastName: {{lastName}}
|
|
{{/if}}
|
|
```
|
|
@class Ember.PromiseProxyMixin
|
|
*/
|
|
__exports__["default"] = Mixin.create({
|
|
/**
|
|
If the proxied promise is rejected this will contain the reason
|
|
provided.
|
|
|
|
@property reason
|
|
@default null
|
|
*/
|
|
reason: null,
|
|
|
|
/**
|
|
Once the proxied promise has settled this will become `false`.
|
|
|
|
@property isPending
|
|
@default true
|
|
*/
|
|
isPending: not('isSettled').readOnly(),
|
|
|
|
/**
|
|
Once the proxied promise has settled this will become `true`.
|
|
|
|
@property isSettled
|
|
@default false
|
|
*/
|
|
isSettled: or('isRejected', 'isFulfilled').readOnly(),
|
|
|
|
/**
|
|
Will become `true` if the proxied promise is rejected.
|
|
|
|
@property isRejected
|
|
@default false
|
|
*/
|
|
isRejected: false,
|
|
|
|
/**
|
|
Will become `true` if the proxied promise is fulfilled.
|
|
|
|
@property isFulfilled
|
|
@default false
|
|
*/
|
|
isFulfilled: false,
|
|
|
|
/**
|
|
The promise whose fulfillment value is being proxied by this object.
|
|
|
|
This property must be specified upon creation, and should not be
|
|
changed once created.
|
|
|
|
Example:
|
|
|
|
```javascript
|
|
Ember.ObjectController.extend(Ember.PromiseProxyMixin).create({
|
|
promise: <thenable>
|
|
});
|
|
```
|
|
|
|
@property promise
|
|
*/
|
|
promise: computed(function(key, promise) {
|
|
if (arguments.length === 2) {
|
|
return tap(this, promise);
|
|
} else {
|
|
throw new EmberError("PromiseProxy's promise must be set");
|
|
}
|
|
}),
|
|
|
|
/**
|
|
An alias to the proxied promise's `then`.
|
|
|
|
See RSVP.Promise.then.
|
|
|
|
@method then
|
|
@param {Function} callback
|
|
@return {RSVP.Promise}
|
|
*/
|
|
then: promiseAlias('then'),
|
|
|
|
/**
|
|
An alias to the proxied promise's `catch`.
|
|
|
|
See RSVP.Promise.catch.
|
|
|
|
@method catch
|
|
@param {Function} callback
|
|
@return {RSVP.Promise}
|
|
@since 1.3.0
|
|
*/
|
|
'catch': promiseAlias('catch'),
|
|
|
|
/**
|
|
An alias to the proxied promise's `finally`.
|
|
|
|
See RSVP.Promise.finally.
|
|
|
|
@method finally
|
|
@param {Function} callback
|
|
@return {RSVP.Promise}
|
|
@since 1.3.0
|
|
*/
|
|
'finally': promiseAlias('finally')
|
|
|
|
});
|
|
|
|
function promiseAlias(name) {
|
|
return function () {
|
|
var promise = get(this, 'promise');
|
|
return promise[name].apply(promise, arguments);
|
|
};
|
|
}
|
|
});
|
|
enifed("ember-runtime/mixins/sortable",
|
|
["ember-metal/core","ember-metal/property_get","ember-metal/enumerable_utils","ember-metal/mixin","ember-runtime/mixins/mutable_enumerable","ember-runtime/compare","ember-metal/observer","ember-metal/computed","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.assert, Ember.A
|
|
|
|
var get = __dependency2__.get;
|
|
var forEach = __dependency3__.forEach;
|
|
var Mixin = __dependency4__.Mixin;
|
|
var MutableEnumerable = __dependency5__["default"];
|
|
var compare = __dependency6__["default"];
|
|
var addObserver = __dependency7__.addObserver;
|
|
var removeObserver = __dependency7__.removeObserver;
|
|
var computed = __dependency8__.computed;
|
|
var beforeObserver = __dependency4__.beforeObserver;
|
|
var observer = __dependency4__.observer;
|
|
//ES6TODO: should we access these directly from their package or from how their exposed in ember-metal?
|
|
|
|
/**
|
|
`Ember.SortableMixin` provides a standard interface for array proxies
|
|
to specify a sort order and maintain this sorting when objects are added,
|
|
removed, or updated without changing the implicit order of their underlying
|
|
model array:
|
|
|
|
```javascript
|
|
songs = [
|
|
{trackNumber: 4, title: 'Ob-La-Di, Ob-La-Da'},
|
|
{trackNumber: 2, title: 'Back in the U.S.S.R.'},
|
|
{trackNumber: 3, title: 'Glass Onion'},
|
|
];
|
|
|
|
songsController = Ember.ArrayController.create({
|
|
model: songs,
|
|
sortProperties: ['trackNumber'],
|
|
sortAscending: true
|
|
});
|
|
|
|
songsController.get('firstObject'); // {trackNumber: 2, title: 'Back in the U.S.S.R.'}
|
|
|
|
songsController.addObject({trackNumber: 1, title: 'Dear Prudence'});
|
|
songsController.get('firstObject'); // {trackNumber: 1, title: 'Dear Prudence'}
|
|
```
|
|
|
|
If you add or remove the properties to sort by or change the sort direction the model
|
|
sort order will be automatically updated.
|
|
|
|
```javascript
|
|
songsController.set('sortProperties', ['title']);
|
|
songsController.get('firstObject'); // {trackNumber: 2, title: 'Back in the U.S.S.R.'}
|
|
|
|
songsController.toggleProperty('sortAscending');
|
|
songsController.get('firstObject'); // {trackNumber: 4, title: 'Ob-La-Di, Ob-La-Da'}
|
|
```
|
|
|
|
`SortableMixin` works by sorting the `arrangedContent` array, which is the array that
|
|
`ArrayProxy` displays. Due to the fact that the underlying 'content' array is not changed, that
|
|
array will not display the sorted list:
|
|
|
|
```javascript
|
|
songsController.get('content').get('firstObject'); // Returns the unsorted original content
|
|
songsController.get('firstObject'); // Returns the sorted content.
|
|
```
|
|
|
|
Although the sorted content can also be accessed through the `arrangedContent` property,
|
|
it is preferable to use the proxied class and not the `arrangedContent` array directly.
|
|
|
|
@class SortableMixin
|
|
@namespace Ember
|
|
@uses Ember.MutableEnumerable
|
|
*/
|
|
__exports__["default"] = Mixin.create(MutableEnumerable, {
|
|
|
|
/**
|
|
Specifies which properties dictate the `arrangedContent`'s sort order.
|
|
|
|
When specifying multiple properties the sorting will use properties
|
|
from the `sortProperties` array prioritized from first to last.
|
|
|
|
@property {Array} sortProperties
|
|
*/
|
|
sortProperties: null,
|
|
|
|
/**
|
|
Specifies the `arrangedContent`'s sort direction.
|
|
Sorts the content in ascending order by default. Set to `false` to
|
|
use descending order.
|
|
|
|
@property {Boolean} sortAscending
|
|
@default true
|
|
*/
|
|
sortAscending: true,
|
|
|
|
/**
|
|
The function used to compare two values. You can override this if you
|
|
want to do custom comparisons. Functions must be of the type expected by
|
|
Array#sort, i.e.,
|
|
|
|
* return 0 if the two parameters are equal,
|
|
* return a negative value if the first parameter is smaller than the second or
|
|
* return a positive value otherwise:
|
|
|
|
```javascript
|
|
function(x, y) { // These are assumed to be integers
|
|
if (x === y)
|
|
return 0;
|
|
return x < y ? -1 : 1;
|
|
}
|
|
```
|
|
|
|
@property sortFunction
|
|
@type {Function}
|
|
@default Ember.compare
|
|
*/
|
|
sortFunction: compare,
|
|
|
|
orderBy: function(item1, item2) {
|
|
var result = 0;
|
|
var sortProperties = get(this, 'sortProperties');
|
|
var sortAscending = get(this, 'sortAscending');
|
|
var sortFunction = get(this, 'sortFunction');
|
|
|
|
Ember.assert("you need to define `sortProperties`", !!sortProperties);
|
|
|
|
forEach(sortProperties, function(propertyName) {
|
|
if (result === 0) {
|
|
result = sortFunction.call(this, get(item1, propertyName), get(item2, propertyName));
|
|
if ((result !== 0) && !sortAscending) {
|
|
result = (-1) * result;
|
|
}
|
|
}
|
|
}, this);
|
|
|
|
return result;
|
|
},
|
|
|
|
destroy: function() {
|
|
var content = get(this, 'content');
|
|
var sortProperties = get(this, 'sortProperties');
|
|
|
|
if (content && sortProperties) {
|
|
forEach(content, function(item) {
|
|
forEach(sortProperties, function(sortProperty) {
|
|
removeObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
|
|
}, this);
|
|
}, this);
|
|
}
|
|
|
|
return this._super();
|
|
},
|
|
|
|
isSorted: computed.notEmpty('sortProperties'),
|
|
|
|
/**
|
|
Overrides the default `arrangedContent` from `ArrayProxy` in order to sort by `sortFunction`.
|
|
Also sets up observers for each `sortProperty` on each item in the content Array.
|
|
|
|
@property arrangedContent
|
|
*/
|
|
arrangedContent: computed('content', 'sortProperties.@each', function(key, value) {
|
|
var content = get(this, 'content');
|
|
var isSorted = get(this, 'isSorted');
|
|
var sortProperties = get(this, 'sortProperties');
|
|
var self = this;
|
|
|
|
if (content && isSorted) {
|
|
content = content.slice();
|
|
content.sort(function(item1, item2) {
|
|
return self.orderBy(item1, item2);
|
|
});
|
|
forEach(content, function(item) {
|
|
forEach(sortProperties, function(sortProperty) {
|
|
addObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
|
|
}, this);
|
|
}, this);
|
|
return Ember.A(content);
|
|
}
|
|
|
|
return content;
|
|
}),
|
|
|
|
_contentWillChange: beforeObserver('content', function() {
|
|
var content = get(this, 'content');
|
|
var sortProperties = get(this, 'sortProperties');
|
|
|
|
if (content && sortProperties) {
|
|
forEach(content, function(item) {
|
|
forEach(sortProperties, function(sortProperty) {
|
|
removeObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
|
|
}, this);
|
|
}, this);
|
|
}
|
|
|
|
this._super();
|
|
}),
|
|
|
|
sortPropertiesWillChange: beforeObserver('sortProperties', function() {
|
|
this._lastSortAscending = undefined;
|
|
}),
|
|
|
|
sortPropertiesDidChange: observer('sortProperties', function() {
|
|
this._lastSortAscending = undefined;
|
|
}),
|
|
|
|
sortAscendingWillChange: beforeObserver('sortAscending', function() {
|
|
this._lastSortAscending = get(this, 'sortAscending');
|
|
}),
|
|
|
|
sortAscendingDidChange: observer('sortAscending', function() {
|
|
if (this._lastSortAscending !== undefined && get(this, 'sortAscending') !== this._lastSortAscending) {
|
|
var arrangedContent = get(this, 'arrangedContent');
|
|
arrangedContent.reverseObjects();
|
|
}
|
|
}),
|
|
|
|
contentArrayWillChange: function(array, idx, removedCount, addedCount) {
|
|
var isSorted = get(this, 'isSorted');
|
|
|
|
if (isSorted) {
|
|
var arrangedContent = get(this, 'arrangedContent');
|
|
var removedObjects = array.slice(idx, idx+removedCount);
|
|
var sortProperties = get(this, 'sortProperties');
|
|
|
|
forEach(removedObjects, function(item) {
|
|
arrangedContent.removeObject(item);
|
|
|
|
forEach(sortProperties, function(sortProperty) {
|
|
removeObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
|
|
}, this);
|
|
}, this);
|
|
}
|
|
|
|
return this._super(array, idx, removedCount, addedCount);
|
|
},
|
|
|
|
contentArrayDidChange: function(array, idx, removedCount, addedCount) {
|
|
var isSorted = get(this, 'isSorted');
|
|
var sortProperties = get(this, 'sortProperties');
|
|
|
|
if (isSorted) {
|
|
var addedObjects = array.slice(idx, idx+addedCount);
|
|
|
|
forEach(addedObjects, function(item) {
|
|
this.insertItemSorted(item);
|
|
|
|
forEach(sortProperties, function(sortProperty) {
|
|
addObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
|
|
}, this);
|
|
}, this);
|
|
}
|
|
|
|
return this._super(array, idx, removedCount, addedCount);
|
|
},
|
|
|
|
insertItemSorted: function(item) {
|
|
var arrangedContent = get(this, 'arrangedContent');
|
|
var length = get(arrangedContent, 'length');
|
|
|
|
var idx = this._binarySearch(item, 0, length);
|
|
arrangedContent.insertAt(idx, item);
|
|
},
|
|
|
|
contentItemSortPropertyDidChange: function(item) {
|
|
var arrangedContent = get(this, 'arrangedContent');
|
|
var oldIndex = arrangedContent.indexOf(item);
|
|
var leftItem = arrangedContent.objectAt(oldIndex - 1);
|
|
var rightItem = arrangedContent.objectAt(oldIndex + 1);
|
|
var leftResult = leftItem && this.orderBy(item, leftItem);
|
|
var rightResult = rightItem && this.orderBy(item, rightItem);
|
|
|
|
if (leftResult < 0 || rightResult > 0) {
|
|
arrangedContent.removeObject(item);
|
|
this.insertItemSorted(item);
|
|
}
|
|
},
|
|
|
|
_binarySearch: function(item, low, high) {
|
|
var mid, midItem, res, arrangedContent;
|
|
|
|
if (low === high) {
|
|
return low;
|
|
}
|
|
|
|
arrangedContent = get(this, 'arrangedContent');
|
|
|
|
mid = low + Math.floor((high - low) / 2);
|
|
midItem = arrangedContent.objectAt(mid);
|
|
|
|
res = this.orderBy(midItem, item);
|
|
|
|
if (res < 0) {
|
|
return this._binarySearch(item, mid+1, high);
|
|
} else if (res > 0) {
|
|
return this._binarySearch(item, low, mid);
|
|
}
|
|
|
|
return mid;
|
|
}
|
|
});
|
|
});
|
|
enifed("ember-runtime/mixins/target_action_support",
|
|
["ember-metal/core","ember-metal/property_get","ember-metal/utils","ember-metal/mixin","ember-metal/computed","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.lookup, Ember.assert
|
|
|
|
var get = __dependency2__.get;
|
|
var typeOf = __dependency3__.typeOf;
|
|
var Mixin = __dependency4__.Mixin;
|
|
var computed = __dependency5__.computed;
|
|
|
|
/**
|
|
`Ember.TargetActionSupport` is a mixin that can be included in a class
|
|
to add a `triggerAction` method with semantics similar to the Handlebars
|
|
`{{action}}` helper. In normal Ember usage, the `{{action}}` helper is
|
|
usually the best choice. This mixin is most often useful when you are
|
|
doing more complex event handling in View objects.
|
|
|
|
See also `Ember.ViewTargetActionSupport`, which has
|
|
view-aware defaults for target and actionContext.
|
|
|
|
@class TargetActionSupport
|
|
@namespace Ember
|
|
@extends Ember.Mixin
|
|
*/
|
|
var TargetActionSupport = Mixin.create({
|
|
target: null,
|
|
action: null,
|
|
actionContext: null,
|
|
|
|
targetObject: computed(function() {
|
|
var target = get(this, 'target');
|
|
|
|
if (typeOf(target) === "string") {
|
|
var value = get(this, target);
|
|
if (value === undefined) { value = get(Ember.lookup, target); }
|
|
return value;
|
|
} else {
|
|
return target;
|
|
}
|
|
}).property('target'),
|
|
|
|
actionContextObject: computed(function() {
|
|
var actionContext = get(this, 'actionContext');
|
|
|
|
if (typeOf(actionContext) === "string") {
|
|
var value = get(this, actionContext);
|
|
if (value === undefined) { value = get(Ember.lookup, actionContext); }
|
|
return value;
|
|
} else {
|
|
return actionContext;
|
|
}
|
|
}).property('actionContext'),
|
|
|
|
/**
|
|
Send an `action` with an `actionContext` to a `target`. The action, actionContext
|
|
and target will be retrieved from properties of the object. For example:
|
|
|
|
```javascript
|
|
App.SaveButtonView = Ember.View.extend(Ember.TargetActionSupport, {
|
|
target: Ember.computed.alias('controller'),
|
|
action: 'save',
|
|
actionContext: Ember.computed.alias('context'),
|
|
click: function() {
|
|
this.triggerAction(); // Sends the `save` action, along with the current context
|
|
// to the current controller
|
|
}
|
|
});
|
|
```
|
|
|
|
The `target`, `action`, and `actionContext` can be provided as properties of
|
|
an optional object argument to `triggerAction` as well.
|
|
|
|
```javascript
|
|
App.SaveButtonView = Ember.View.extend(Ember.TargetActionSupport, {
|
|
click: function() {
|
|
this.triggerAction({
|
|
action: 'save',
|
|
target: this.get('controller'),
|
|
actionContext: this.get('context')
|
|
}); // Sends the `save` action, along with the current context
|
|
// to the current controller
|
|
}
|
|
});
|
|
```
|
|
|
|
The `actionContext` defaults to the object you are mixing `TargetActionSupport` into.
|
|
But `target` and `action` must be specified either as properties or with the argument
|
|
to `triggerAction`, or a combination:
|
|
|
|
```javascript
|
|
App.SaveButtonView = Ember.View.extend(Ember.TargetActionSupport, {
|
|
target: Ember.computed.alias('controller'),
|
|
click: function() {
|
|
this.triggerAction({
|
|
action: 'save'
|
|
}); // Sends the `save` action, along with a reference to `this`,
|
|
// to the current controller
|
|
}
|
|
});
|
|
```
|
|
|
|
@method triggerAction
|
|
@param opts {Hash} (optional, with the optional keys action, target and/or actionContext)
|
|
@return {Boolean} true if the action was sent successfully and did not return false
|
|
*/
|
|
triggerAction: function(opts) {
|
|
opts = opts || {};
|
|
var action = opts.action || get(this, 'action');
|
|
var target = opts.target || get(this, 'targetObject');
|
|
var actionContext = opts.actionContext;
|
|
|
|
function args(options, actionName) {
|
|
var ret = [];
|
|
if (actionName) { ret.push(actionName); }
|
|
|
|
return ret.concat(options);
|
|
}
|
|
|
|
if (typeof actionContext === 'undefined') {
|
|
actionContext = get(this, 'actionContextObject') || this;
|
|
}
|
|
|
|
if (target && action) {
|
|
var ret;
|
|
|
|
if (target.send) {
|
|
ret = target.send.apply(target, args(actionContext, action));
|
|
} else {
|
|
Ember.assert("The action '" + action + "' did not exist on " + target, typeof target[action] === 'function');
|
|
ret = target[action].apply(target, args(actionContext));
|
|
}
|
|
|
|
if (ret !== false) ret = true;
|
|
|
|
return ret;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
});
|
|
|
|
__exports__["default"] = TargetActionSupport;
|
|
});
|
|
enifed("ember-runtime/system/application",
|
|
["ember-runtime/system/namespace","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var Namespace = __dependency1__["default"];
|
|
|
|
__exports__["default"] = Namespace.extend();
|
|
});
|
|
enifed("ember-runtime/system/array_proxy",
|
|
["ember-metal/core","ember-metal/property_get","ember-metal/utils","ember-metal/computed","ember-metal/mixin","ember-metal/property_events","ember-metal/error","ember-runtime/system/object","ember-runtime/mixins/mutable_array","ember-runtime/mixins/enumerable","ember-runtime/system/string","ember-metal/alias","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.assert
|
|
var get = __dependency2__.get;
|
|
var isArray = __dependency3__.isArray;
|
|
var apply = __dependency3__.apply;
|
|
var computed = __dependency4__.computed;
|
|
var beforeObserver = __dependency5__.beforeObserver;
|
|
var observer = __dependency5__.observer;
|
|
var beginPropertyChanges = __dependency6__.beginPropertyChanges;
|
|
var endPropertyChanges = __dependency6__.endPropertyChanges;
|
|
var EmberError = __dependency7__["default"];
|
|
var EmberObject = __dependency8__["default"];
|
|
var MutableArray = __dependency9__["default"];
|
|
var Enumerable = __dependency10__["default"];
|
|
var fmt = __dependency11__.fmt;
|
|
var alias = __dependency12__["default"];
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
|
|
var OUT_OF_RANGE_EXCEPTION = "Index out of range";
|
|
var EMPTY = [];
|
|
|
|
function K() { return this; }
|
|
|
|
/**
|
|
An ArrayProxy wraps any other object that implements `Ember.Array` and/or
|
|
`Ember.MutableArray,` forwarding all requests. This makes it very useful for
|
|
a number of binding use cases or other cases where being able to swap
|
|
out the underlying array is useful.
|
|
|
|
A simple example of usage:
|
|
|
|
```javascript
|
|
var pets = ['dog', 'cat', 'fish'];
|
|
var ap = Ember.ArrayProxy.create({ content: Ember.A(pets) });
|
|
|
|
ap.get('firstObject'); // 'dog'
|
|
ap.set('content', ['amoeba', 'paramecium']);
|
|
ap.get('firstObject'); // 'amoeba'
|
|
```
|
|
|
|
This class can also be useful as a layer to transform the contents of
|
|
an array, as they are accessed. This can be done by overriding
|
|
`objectAtContent`:
|
|
|
|
```javascript
|
|
var pets = ['dog', 'cat', 'fish'];
|
|
var ap = Ember.ArrayProxy.create({
|
|
content: Ember.A(pets),
|
|
objectAtContent: function(idx) {
|
|
return this.get('content').objectAt(idx).toUpperCase();
|
|
}
|
|
});
|
|
|
|
ap.get('firstObject'); // . 'DOG'
|
|
```
|
|
|
|
@class ArrayProxy
|
|
@namespace Ember
|
|
@extends Ember.Object
|
|
@uses Ember.MutableArray
|
|
*/
|
|
var ArrayProxy = EmberObject.extend(MutableArray, {
|
|
|
|
/**
|
|
The content array. Must be an object that implements `Ember.Array` and/or
|
|
`Ember.MutableArray.`
|
|
|
|
@property content
|
|
@type Ember.Array
|
|
*/
|
|
content: null,
|
|
|
|
/**
|
|
The array that the proxy pretends to be. In the default `ArrayProxy`
|
|
implementation, this and `content` are the same. Subclasses of `ArrayProxy`
|
|
can override this property to provide things like sorting and filtering.
|
|
|
|
@property arrangedContent
|
|
*/
|
|
arrangedContent: alias('content'),
|
|
|
|
/**
|
|
Should actually retrieve the object at the specified index from the
|
|
content. You can override this method in subclasses to transform the
|
|
content item to something new.
|
|
|
|
This method will only be called if content is non-`null`.
|
|
|
|
@method objectAtContent
|
|
@param {Number} idx The index to retrieve.
|
|
@return {Object} the value or undefined if none found
|
|
*/
|
|
objectAtContent: function(idx) {
|
|
return get(this, 'arrangedContent').objectAt(idx);
|
|
},
|
|
|
|
/**
|
|
Should actually replace the specified objects on the content array.
|
|
You can override this method in subclasses to transform the content item
|
|
into something new.
|
|
|
|
This method will only be called if content is non-`null`.
|
|
|
|
@method replaceContent
|
|
@param {Number} idx The starting index
|
|
@param {Number} amt The number of items to remove from the content.
|
|
@param {Array} objects Optional array of objects to insert or null if no
|
|
objects.
|
|
@return {void}
|
|
*/
|
|
replaceContent: function(idx, amt, objects) {
|
|
get(this, 'content').replace(idx, amt, objects);
|
|
},
|
|
|
|
/**
|
|
Invoked when the content property is about to change. Notifies observers that the
|
|
entire array content will change.
|
|
|
|
@private
|
|
@method _contentWillChange
|
|
*/
|
|
_contentWillChange: beforeObserver('content', function() {
|
|
this._teardownContent();
|
|
}),
|
|
|
|
_teardownContent: function() {
|
|
var content = get(this, 'content');
|
|
|
|
if (content) {
|
|
content.removeArrayObserver(this, {
|
|
willChange: 'contentArrayWillChange',
|
|
didChange: 'contentArrayDidChange'
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
Override to implement content array `willChange` observer.
|
|
|
|
@method contentArrayWillChange
|
|
|
|
@param {Ember.Array} contentArray the content array
|
|
@param {Number} start starting index of the change
|
|
@param {Number} removeCount count of items removed
|
|
@param {Number} addCount count of items added
|
|
|
|
*/
|
|
contentArrayWillChange: K,
|
|
/**
|
|
Override to implement content array `didChange` observer.
|
|
|
|
@method contentArrayDidChange
|
|
|
|
@param {Ember.Array} contentArray the content array
|
|
@param {Number} start starting index of the change
|
|
@param {Number} removeCount count of items removed
|
|
@param {Number} addCount count of items added
|
|
*/
|
|
contentArrayDidChange: K,
|
|
|
|
/**
|
|
Invoked when the content property changes. Notifies observers that the
|
|
entire array content has changed.
|
|
|
|
@private
|
|
@method _contentDidChange
|
|
*/
|
|
_contentDidChange: observer('content', function() {
|
|
var content = get(this, 'content');
|
|
|
|
Ember.assert("Can't set ArrayProxy's content to itself", content !== this);
|
|
|
|
this._setupContent();
|
|
}),
|
|
|
|
_setupContent: function() {
|
|
var content = get(this, 'content');
|
|
|
|
if (content) {
|
|
Ember.assert(fmt('ArrayProxy expects an Array or ' +
|
|
'Ember.ArrayProxy, but you passed %@', [typeof content]),
|
|
isArray(content) || content.isDestroyed);
|
|
|
|
content.addArrayObserver(this, {
|
|
willChange: 'contentArrayWillChange',
|
|
didChange: 'contentArrayDidChange'
|
|
});
|
|
}
|
|
},
|
|
|
|
_arrangedContentWillChange: beforeObserver('arrangedContent', function() {
|
|
var arrangedContent = get(this, 'arrangedContent');
|
|
var len = arrangedContent ? get(arrangedContent, 'length') : 0;
|
|
|
|
this.arrangedContentArrayWillChange(this, 0, len, undefined);
|
|
this.arrangedContentWillChange(this);
|
|
|
|
this._teardownArrangedContent(arrangedContent);
|
|
}),
|
|
|
|
_arrangedContentDidChange: observer('arrangedContent', function() {
|
|
var arrangedContent = get(this, 'arrangedContent');
|
|
var len = arrangedContent ? get(arrangedContent, 'length') : 0;
|
|
|
|
Ember.assert("Can't set ArrayProxy's content to itself", arrangedContent !== this);
|
|
|
|
this._setupArrangedContent();
|
|
|
|
this.arrangedContentDidChange(this);
|
|
this.arrangedContentArrayDidChange(this, 0, undefined, len);
|
|
}),
|
|
|
|
_setupArrangedContent: function() {
|
|
var arrangedContent = get(this, 'arrangedContent');
|
|
|
|
if (arrangedContent) {
|
|
Ember.assert(fmt('ArrayProxy expects an Array or ' +
|
|
'Ember.ArrayProxy, but you passed %@', [typeof arrangedContent]),
|
|
isArray(arrangedContent) || arrangedContent.isDestroyed);
|
|
|
|
arrangedContent.addArrayObserver(this, {
|
|
willChange: 'arrangedContentArrayWillChange',
|
|
didChange: 'arrangedContentArrayDidChange'
|
|
});
|
|
}
|
|
},
|
|
|
|
_teardownArrangedContent: function() {
|
|
var arrangedContent = get(this, 'arrangedContent');
|
|
|
|
if (arrangedContent) {
|
|
arrangedContent.removeArrayObserver(this, {
|
|
willChange: 'arrangedContentArrayWillChange',
|
|
didChange: 'arrangedContentArrayDidChange'
|
|
});
|
|
}
|
|
},
|
|
|
|
arrangedContentWillChange: K,
|
|
arrangedContentDidChange: K,
|
|
|
|
objectAt: function(idx) {
|
|
return get(this, 'content') && this.objectAtContent(idx);
|
|
},
|
|
|
|
length: computed(function() {
|
|
var arrangedContent = get(this, 'arrangedContent');
|
|
return arrangedContent ? get(arrangedContent, 'length') : 0;
|
|
// No dependencies since Enumerable notifies length of change
|
|
}),
|
|
|
|
_replace: function(idx, amt, objects) {
|
|
var content = get(this, 'content');
|
|
Ember.assert('The content property of '+ this.constructor + ' should be set before modifying it', content);
|
|
if (content) this.replaceContent(idx, amt, objects);
|
|
return this;
|
|
},
|
|
|
|
replace: function() {
|
|
if (get(this, 'arrangedContent') === get(this, 'content')) {
|
|
apply(this, this._replace, arguments);
|
|
} else {
|
|
throw new EmberError("Using replace on an arranged ArrayProxy is not allowed.");
|
|
}
|
|
},
|
|
|
|
_insertAt: function(idx, object) {
|
|
if (idx > get(this, 'content.length')) throw new EmberError(OUT_OF_RANGE_EXCEPTION);
|
|
this._replace(idx, 0, [object]);
|
|
return this;
|
|
},
|
|
|
|
insertAt: function(idx, object) {
|
|
if (get(this, 'arrangedContent') === get(this, 'content')) {
|
|
return this._insertAt(idx, object);
|
|
} else {
|
|
throw new EmberError("Using insertAt on an arranged ArrayProxy is not allowed.");
|
|
}
|
|
},
|
|
|
|
removeAt: function(start, len) {
|
|
if ('number' === typeof start) {
|
|
var content = get(this, 'content');
|
|
var arrangedContent = get(this, 'arrangedContent');
|
|
var indices = [];
|
|
var i;
|
|
|
|
if ((start < 0) || (start >= get(this, 'length'))) {
|
|
throw new EmberError(OUT_OF_RANGE_EXCEPTION);
|
|
}
|
|
|
|
if (len === undefined) len = 1;
|
|
|
|
// Get a list of indices in original content to remove
|
|
for (i=start; i<start+len; i++) {
|
|
// Use arrangedContent here so we avoid confusion with objects transformed by objectAtContent
|
|
indices.push(content.indexOf(arrangedContent.objectAt(i)));
|
|
}
|
|
|
|
// Replace in reverse order since indices will change
|
|
indices.sort(function(a,b) { return b - a; });
|
|
|
|
beginPropertyChanges();
|
|
for (i=0; i<indices.length; i++) {
|
|
this._replace(indices[i], 1, EMPTY);
|
|
}
|
|
endPropertyChanges();
|
|
}
|
|
|
|
return this ;
|
|
},
|
|
|
|
pushObject: function(obj) {
|
|
this._insertAt(get(this, 'content.length'), obj) ;
|
|
return obj ;
|
|
},
|
|
|
|
pushObjects: function(objects) {
|
|
if (!(Enumerable.detect(objects) || isArray(objects))) {
|
|
throw new TypeError("Must pass Ember.Enumerable to Ember.MutableArray#pushObjects");
|
|
}
|
|
this._replace(get(this, 'length'), 0, objects);
|
|
return this;
|
|
},
|
|
|
|
setObjects: function(objects) {
|
|
if (objects.length === 0) return this.clear();
|
|
|
|
var len = get(this, 'length');
|
|
this._replace(0, len, objects);
|
|
return this;
|
|
},
|
|
|
|
unshiftObject: function(obj) {
|
|
this._insertAt(0, obj) ;
|
|
return obj ;
|
|
},
|
|
|
|
unshiftObjects: function(objects) {
|
|
this._replace(0, 0, objects);
|
|
return this;
|
|
},
|
|
|
|
slice: function() {
|
|
var arr = this.toArray();
|
|
return arr.slice.apply(arr, arguments);
|
|
},
|
|
|
|
arrangedContentArrayWillChange: function(item, idx, removedCnt, addedCnt) {
|
|
this.arrayContentWillChange(idx, removedCnt, addedCnt);
|
|
},
|
|
|
|
arrangedContentArrayDidChange: function(item, idx, removedCnt, addedCnt) {
|
|
this.arrayContentDidChange(idx, removedCnt, addedCnt);
|
|
},
|
|
|
|
init: function() {
|
|
this._super();
|
|
this._setupContent();
|
|
this._setupArrangedContent();
|
|
},
|
|
|
|
willDestroy: function() {
|
|
this._teardownArrangedContent();
|
|
this._teardownContent();
|
|
}
|
|
});
|
|
|
|
__exports__["default"] = ArrayProxy;
|
|
});
|
|
enifed("ember-runtime/system/container",
|
|
["ember-metal/property_set","container","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var set = __dependency1__.set;
|
|
var Container = __dependency2__["default"];
|
|
|
|
Container.set = set;
|
|
|
|
__exports__["default"] = Container;
|
|
});
|
|
enifed("ember-runtime/system/core_object",
|
|
["ember-metal/core","ember-metal/merge","ember-metal/property_get","ember-metal/utils","ember-metal/platform","ember-metal/chains","ember-metal/events","ember-metal/mixin","ember-metal/enumerable_utils","ember-metal/error","ember-metal/keys","ember-runtime/mixins/action_handler","ember-metal/properties","ember-metal/binding","ember-metal/computed","ember-metal/injected_property","ember-metal/run_loop","ember-metal/watching","ember-runtime/inject","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __dependency16__, __dependency17__, __dependency18__, __dependency19__, __exports__) {
|
|
// Remove "use strict"; from transpiled module until
|
|
// https://bugs.webkit.org/show_bug.cgi?id=138038 is fixed
|
|
//
|
|
// REMOVE_USE_STRICT: true
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
var merge = __dependency2__["default"];
|
|
// Ember.assert, Ember.config
|
|
|
|
// NOTE: this object should never be included directly. Instead use `Ember.Object`.
|
|
// We only define this separately so that `Ember.Set` can depend on it.
|
|
var get = __dependency3__.get;
|
|
var guidFor = __dependency4__.guidFor;
|
|
var apply = __dependency4__.apply;
|
|
var o_create = __dependency5__.create;
|
|
var generateGuid = __dependency4__.generateGuid;
|
|
var GUID_KEY = __dependency4__.GUID_KEY;
|
|
var meta = __dependency4__.meta;
|
|
var makeArray = __dependency4__.makeArray;
|
|
var finishChains = __dependency6__.finishChains;
|
|
var sendEvent = __dependency7__.sendEvent;
|
|
var IS_BINDING = __dependency8__.IS_BINDING;
|
|
var Mixin = __dependency8__.Mixin;
|
|
var required = __dependency8__.required;
|
|
var indexOf = __dependency9__.indexOf;
|
|
var EmberError = __dependency10__["default"];
|
|
var o_defineProperty = __dependency5__.defineProperty;
|
|
var keys = __dependency11__["default"];
|
|
var ActionHandler = __dependency12__["default"];
|
|
var defineProperty = __dependency13__.defineProperty;
|
|
var Binding = __dependency14__.Binding;
|
|
var ComputedProperty = __dependency15__.ComputedProperty;
|
|
var computed = __dependency15__.computed;
|
|
var InjectedProperty = __dependency16__["default"];
|
|
var run = __dependency17__["default"];
|
|
var destroy = __dependency18__.destroy;
|
|
var K = __dependency1__.K;
|
|
var hasPropertyAccessors = __dependency5__.hasPropertyAccessors;
|
|
var validatePropertyInjections = __dependency19__.validatePropertyInjections;
|
|
|
|
var schedule = run.schedule;
|
|
var applyMixin = Mixin._apply;
|
|
var finishPartial = Mixin.finishPartial;
|
|
var reopen = Mixin.prototype.reopen;
|
|
var hasCachedComputedProperties = false;
|
|
|
|
var undefinedDescriptor = {
|
|
configurable: true,
|
|
writable: true,
|
|
enumerable: false,
|
|
value: undefined
|
|
};
|
|
|
|
var nullDescriptor = {
|
|
configurable: true,
|
|
writable: true,
|
|
enumerable: false,
|
|
value: null
|
|
};
|
|
|
|
function makeCtor() {
|
|
|
|
// Note: avoid accessing any properties on the object since it makes the
|
|
// method a lot faster. This is glue code so we want it to be as fast as
|
|
// possible.
|
|
|
|
var wasApplied = false;
|
|
var initMixins, initProperties;
|
|
|
|
var Class = function() {
|
|
if (!wasApplied) {
|
|
Class.proto(); // prepare prototype...
|
|
}
|
|
o_defineProperty(this, GUID_KEY, nullDescriptor);
|
|
o_defineProperty(this, '__nextSuper', undefinedDescriptor);
|
|
var m = meta(this);
|
|
var proto = m.proto;
|
|
m.proto = this;
|
|
if (initMixins) {
|
|
// capture locally so we can clear the closed over variable
|
|
var mixins = initMixins;
|
|
initMixins = null;
|
|
apply(this, this.reopen, mixins);
|
|
}
|
|
if (initProperties) {
|
|
// capture locally so we can clear the closed over variable
|
|
var props = initProperties;
|
|
initProperties = null;
|
|
|
|
var concatenatedProperties = this.concatenatedProperties;
|
|
var mergedProperties = this.mergedProperties;
|
|
|
|
for (var i = 0, l = props.length; i < l; i++) {
|
|
var properties = props[i];
|
|
|
|
Ember.assert("Ember.Object.create no longer supports mixing in other definitions, use createWithMixins instead.", !(properties instanceof Mixin));
|
|
|
|
if (typeof properties !== 'object' && properties !== undefined) {
|
|
throw new EmberError("Ember.Object.create only accepts objects.");
|
|
}
|
|
|
|
if (!properties) { continue; }
|
|
|
|
var keyNames = keys(properties);
|
|
|
|
for (var j = 0, ll = keyNames.length; j < ll; j++) {
|
|
var keyName = keyNames[j];
|
|
var value = properties[keyName];
|
|
|
|
if (IS_BINDING.test(keyName)) {
|
|
var bindings = m.bindings;
|
|
if (!bindings) {
|
|
bindings = m.bindings = {};
|
|
} else if (!m.hasOwnProperty('bindings')) {
|
|
bindings = m.bindings = o_create(m.bindings);
|
|
}
|
|
bindings[keyName] = value;
|
|
}
|
|
|
|
var desc = m.descs[keyName];
|
|
|
|
Ember.assert("Ember.Object.create no longer supports defining computed properties. Define computed properties using extend() or reopen() before calling create().", !(value instanceof ComputedProperty));
|
|
Ember.assert("Ember.Object.create no longer supports defining methods that call _super.", !(typeof value === 'function' && value.toString().indexOf('._super') !== -1));
|
|
Ember.assert("`actions` must be provided at extend time, not at create " +
|
|
"time, when Ember.ActionHandler is used (i.e. views, " +
|
|
"controllers & routes).", !((keyName === 'actions') && ActionHandler.detect(this)));
|
|
|
|
if (concatenatedProperties &&
|
|
concatenatedProperties.length > 0 &&
|
|
indexOf(concatenatedProperties, keyName) >= 0) {
|
|
var baseValue = this[keyName];
|
|
|
|
if (baseValue) {
|
|
if ('function' === typeof baseValue.concat) {
|
|
value = baseValue.concat(value);
|
|
} else {
|
|
value = makeArray(baseValue).concat(value);
|
|
}
|
|
} else {
|
|
value = makeArray(value);
|
|
}
|
|
}
|
|
|
|
if (mergedProperties &&
|
|
mergedProperties.length &&
|
|
indexOf(mergedProperties, keyName) >= 0) {
|
|
var originalValue = this[keyName];
|
|
|
|
value = merge(originalValue, value);
|
|
}
|
|
|
|
if (desc) {
|
|
desc.set(this, keyName, value);
|
|
} else {
|
|
if (typeof this.setUnknownProperty === 'function' && !(keyName in this)) {
|
|
this.setUnknownProperty(keyName, value);
|
|
} else {
|
|
|
|
if (hasPropertyAccessors) {
|
|
defineProperty(this, keyName, null, value); // setup mandatory setter
|
|
} else {
|
|
this[keyName] = value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
finishPartial(this, m);
|
|
|
|
var length = arguments.length;
|
|
|
|
if (length === 0) {
|
|
this.init();
|
|
} else if (length === 1) {
|
|
this.init(arguments[0]);
|
|
} else {
|
|
// v8 bug potentially incorrectly deopts this function: https://code.google.com/p/v8/issues/detail?id=3709
|
|
// we may want to keep this around till this ages out on mobile
|
|
var args = new Array(length);
|
|
for (var x = 0; x < length; x++) {
|
|
args[x] = arguments[x];
|
|
}
|
|
this.init.apply(this, args);
|
|
}
|
|
|
|
m.proto = proto;
|
|
finishChains(this);
|
|
sendEvent(this, 'init');
|
|
};
|
|
|
|
Class.toString = Mixin.prototype.toString;
|
|
Class.willReopen = function() {
|
|
if (wasApplied) {
|
|
Class.PrototypeMixin = Mixin.create(Class.PrototypeMixin);
|
|
}
|
|
|
|
wasApplied = false;
|
|
};
|
|
Class._initMixins = function(args) { initMixins = args; };
|
|
Class._initProperties = function(args) { initProperties = args; };
|
|
|
|
Class.proto = function() {
|
|
var superclass = Class.superclass;
|
|
if (superclass) { superclass.proto(); }
|
|
|
|
if (!wasApplied) {
|
|
wasApplied = true;
|
|
Class.PrototypeMixin.applyPartial(Class.prototype);
|
|
}
|
|
|
|
return this.prototype;
|
|
};
|
|
|
|
return Class;
|
|
|
|
}
|
|
|
|
/**
|
|
@class CoreObject
|
|
@namespace Ember
|
|
*/
|
|
var CoreObject = makeCtor();
|
|
CoreObject.toString = function() { return "Ember.CoreObject"; };
|
|
CoreObject.PrototypeMixin = Mixin.create({
|
|
reopen: function() {
|
|
var length = arguments.length;
|
|
var args = new Array(length);
|
|
for (var i = 0; i < length; i++) {
|
|
args[i] = arguments[i];
|
|
}
|
|
applyMixin(this, args, true);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
An overridable method called when objects are instantiated. By default,
|
|
does nothing unless it is overridden during class definition.
|
|
|
|
Example:
|
|
|
|
```javascript
|
|
App.Person = Ember.Object.extend({
|
|
init: function() {
|
|
alert('Name is ' + this.get('name'));
|
|
}
|
|
});
|
|
|
|
var steve = App.Person.create({
|
|
name: "Steve"
|
|
});
|
|
|
|
// alerts 'Name is Steve'.
|
|
```
|
|
|
|
NOTE: If you do override `init` for a framework class like `Ember.View` or
|
|
`Ember.ArrayController`, be sure to call `this._super()` in your
|
|
`init` declaration! If you don't, Ember may not have an opportunity to
|
|
do important setup work, and you'll see strange behavior in your
|
|
application.
|
|
|
|
@method init
|
|
*/
|
|
init: function() {},
|
|
|
|
/**
|
|
Defines the properties that will be concatenated from the superclass
|
|
(instead of overridden).
|
|
|
|
By default, when you extend an Ember class a property defined in
|
|
the subclass overrides a property with the same name that is defined
|
|
in the superclass. However, there are some cases where it is preferable
|
|
to build up a property's value by combining the superclass' property
|
|
value with the subclass' value. An example of this in use within Ember
|
|
is the `classNames` property of `Ember.View`.
|
|
|
|
Here is some sample code showing the difference between a concatenated
|
|
property and a normal one:
|
|
|
|
```javascript
|
|
App.BarView = Ember.View.extend({
|
|
someNonConcatenatedProperty: ['bar'],
|
|
classNames: ['bar']
|
|
});
|
|
|
|
App.FooBarView = App.BarView.extend({
|
|
someNonConcatenatedProperty: ['foo'],
|
|
classNames: ['foo']
|
|
});
|
|
|
|
var fooBarView = App.FooBarView.create();
|
|
fooBarView.get('someNonConcatenatedProperty'); // ['foo']
|
|
fooBarView.get('classNames'); // ['ember-view', 'bar', 'foo']
|
|
```
|
|
|
|
This behavior extends to object creation as well. Continuing the
|
|
above example:
|
|
|
|
```javascript
|
|
var view = App.FooBarView.create({
|
|
someNonConcatenatedProperty: ['baz'],
|
|
classNames: ['baz']
|
|
})
|
|
view.get('someNonConcatenatedProperty'); // ['baz']
|
|
view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz']
|
|
```
|
|
Adding a single property that is not an array will just add it in the array:
|
|
|
|
```javascript
|
|
var view = App.FooBarView.create({
|
|
classNames: 'baz'
|
|
})
|
|
view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz']
|
|
```
|
|
|
|
Using the `concatenatedProperties` property, we can tell Ember to mix the
|
|
content of the properties.
|
|
|
|
In `Ember.View` the `classNameBindings` and `attributeBindings` properties
|
|
are also concatenated, in addition to `classNames`.
|
|
|
|
This feature is available for you to use throughout the Ember object model,
|
|
although typical app developers are likely to use it infrequently. Since
|
|
it changes expectations about behavior of properties, you should properly
|
|
document its usage in each individual concatenated property (to not
|
|
mislead your users to think they can override the property in a subclass).
|
|
|
|
@property concatenatedProperties
|
|
@type Array
|
|
@default null
|
|
*/
|
|
concatenatedProperties: null,
|
|
|
|
/**
|
|
Destroyed object property flag.
|
|
|
|
if this property is `true` the observers and bindings were already
|
|
removed by the effect of calling the `destroy()` method.
|
|
|
|
@property isDestroyed
|
|
@default false
|
|
*/
|
|
isDestroyed: false,
|
|
|
|
/**
|
|
Destruction scheduled flag. The `destroy()` method has been called.
|
|
|
|
The object stays intact until the end of the run loop at which point
|
|
the `isDestroyed` flag is set.
|
|
|
|
@property isDestroying
|
|
@default false
|
|
*/
|
|
isDestroying: false,
|
|
|
|
/**
|
|
Destroys an object by setting the `isDestroyed` flag and removing its
|
|
metadata, which effectively destroys observers and bindings.
|
|
|
|
If you try to set a property on a destroyed object, an exception will be
|
|
raised.
|
|
|
|
Note that destruction is scheduled for the end of the run loop and does not
|
|
happen immediately. It will set an isDestroying flag immediately.
|
|
|
|
@method destroy
|
|
@return {Ember.Object} receiver
|
|
*/
|
|
destroy: function() {
|
|
if (this.isDestroying) { return; }
|
|
this.isDestroying = true;
|
|
|
|
schedule('actions', this, this.willDestroy);
|
|
schedule('destroy', this, this._scheduledDestroy);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Override to implement teardown.
|
|
|
|
@method willDestroy
|
|
*/
|
|
willDestroy: K,
|
|
|
|
/**
|
|
Invoked by the run loop to actually destroy the object. This is
|
|
scheduled for execution by the `destroy` method.
|
|
|
|
@private
|
|
@method _scheduledDestroy
|
|
*/
|
|
_scheduledDestroy: function() {
|
|
if (this.isDestroyed) { return; }
|
|
destroy(this);
|
|
this.isDestroyed = true;
|
|
},
|
|
|
|
bind: function(to, from) {
|
|
if (!(from instanceof Binding)) { from = Binding.from(from); }
|
|
from.to(to).connect(this);
|
|
return from;
|
|
},
|
|
|
|
/**
|
|
Returns a string representation which attempts to provide more information
|
|
than Javascript's `toString` typically does, in a generic way for all Ember
|
|
objects.
|
|
|
|
```javascript
|
|
App.Person = Em.Object.extend()
|
|
person = App.Person.create()
|
|
person.toString() //=> "<App.Person:ember1024>"
|
|
```
|
|
|
|
If the object's class is not defined on an Ember namespace, it will
|
|
indicate it is a subclass of the registered superclass:
|
|
|
|
```javascript
|
|
Student = App.Person.extend()
|
|
student = Student.create()
|
|
student.toString() //=> "<(subclass of App.Person):ember1025>"
|
|
```
|
|
|
|
If the method `toStringExtension` is defined, its return value will be
|
|
included in the output.
|
|
|
|
```javascript
|
|
App.Teacher = App.Person.extend({
|
|
toStringExtension: function() {
|
|
return this.get('fullName');
|
|
}
|
|
});
|
|
teacher = App.Teacher.create()
|
|
teacher.toString(); //=> "<App.Teacher:ember1026:Tom Dale>"
|
|
```
|
|
|
|
@method toString
|
|
@return {String} string representation
|
|
*/
|
|
toString: function toString() {
|
|
var hasToStringExtension = typeof this.toStringExtension === 'function';
|
|
var extension = hasToStringExtension ? ":" + this.toStringExtension() : '';
|
|
var ret = '<'+this.constructor.toString()+':'+guidFor(this)+extension+'>';
|
|
|
|
this.toString = makeToString(ret);
|
|
return ret;
|
|
}
|
|
});
|
|
|
|
CoreObject.PrototypeMixin.ownerConstructor = CoreObject;
|
|
|
|
function makeToString(ret) {
|
|
return function() { return ret; };
|
|
}
|
|
|
|
CoreObject.__super__ = null;
|
|
|
|
var ClassMixinProps = {
|
|
|
|
ClassMixin: required(),
|
|
|
|
PrototypeMixin: required(),
|
|
|
|
isClass: true,
|
|
|
|
isMethod: false,
|
|
|
|
/**
|
|
Creates a new subclass.
|
|
|
|
```javascript
|
|
App.Person = Ember.Object.extend({
|
|
say: function(thing) {
|
|
alert(thing);
|
|
}
|
|
});
|
|
```
|
|
|
|
This defines a new subclass of Ember.Object: `App.Person`. It contains one method: `say()`.
|
|
|
|
You can also create a subclass from any existing class by calling its `extend()` method. For example, you might want to create a subclass of Ember's built-in `Ember.View` class:
|
|
|
|
```javascript
|
|
App.PersonView = Ember.View.extend({
|
|
tagName: 'li',
|
|
classNameBindings: ['isAdministrator']
|
|
});
|
|
```
|
|
|
|
When defining a subclass, you can override methods but still access the implementation of your parent class by calling the special `_super()` method:
|
|
|
|
```javascript
|
|
App.Person = Ember.Object.extend({
|
|
say: function(thing) {
|
|
var name = this.get('name');
|
|
alert(name + ' says: ' + thing);
|
|
}
|
|
});
|
|
|
|
App.Soldier = App.Person.extend({
|
|
say: function(thing) {
|
|
this._super(thing + ", sir!");
|
|
},
|
|
march: function(numberOfHours) {
|
|
alert(this.get('name') + ' marches for ' + numberOfHours + ' hours.')
|
|
}
|
|
});
|
|
|
|
var yehuda = App.Soldier.create({
|
|
name: "Yehuda Katz"
|
|
});
|
|
|
|
yehuda.say("Yes"); // alerts "Yehuda Katz says: Yes, sir!"
|
|
```
|
|
|
|
The `create()` on line #17 creates an *instance* of the `App.Soldier` class. The `extend()` on line #8 creates a *subclass* of `App.Person`. Any instance of the `App.Person` class will *not* have the `march()` method.
|
|
|
|
You can also pass `Mixin` classes to add additional properties to the subclass.
|
|
|
|
```javascript
|
|
App.Person = Ember.Object.extend({
|
|
say: function(thing) {
|
|
alert(this.get('name') + ' says: ' + thing);
|
|
}
|
|
});
|
|
|
|
App.SingingMixin = Mixin.create({
|
|
sing: function(thing){
|
|
alert(this.get('name') + ' sings: la la la ' + thing);
|
|
}
|
|
});
|
|
|
|
App.BroadwayStar = App.Person.extend(App.SingingMixin, {
|
|
dance: function() {
|
|
alert(this.get('name') + ' dances: tap tap tap tap ');
|
|
}
|
|
});
|
|
```
|
|
|
|
The `App.BroadwayStar` class contains three methods: `say()`, `sing()`, and `dance()`.
|
|
|
|
@method extend
|
|
@static
|
|
|
|
@param {Mixin} [mixins]* One or more Mixin classes
|
|
@param {Object} [arguments]* Object containing values to use within the new class
|
|
*/
|
|
extend: function extend() {
|
|
var Class = makeCtor();
|
|
var proto;
|
|
Class.ClassMixin = Mixin.create(this.ClassMixin);
|
|
Class.PrototypeMixin = Mixin.create(this.PrototypeMixin);
|
|
|
|
Class.ClassMixin.ownerConstructor = Class;
|
|
Class.PrototypeMixin.ownerConstructor = Class;
|
|
|
|
reopen.apply(Class.PrototypeMixin, arguments);
|
|
|
|
Class.superclass = this;
|
|
Class.__super__ = this.prototype;
|
|
|
|
proto = Class.prototype = o_create(this.prototype);
|
|
proto.constructor = Class;
|
|
generateGuid(proto);
|
|
meta(proto).proto = proto; // this will disable observers on prototype
|
|
|
|
Class.ClassMixin.apply(Class);
|
|
return Class;
|
|
},
|
|
|
|
/**
|
|
Equivalent to doing `extend(arguments).create()`.
|
|
If possible use the normal `create` method instead.
|
|
|
|
@method createWithMixins
|
|
@static
|
|
@param [arguments]*
|
|
*/
|
|
createWithMixins: function() {
|
|
var C = this;
|
|
var l= arguments.length;
|
|
if (l > 0) {
|
|
var args = new Array(l);
|
|
for (var i = 0; i < l; i++) {
|
|
args[i] = arguments[i];
|
|
}
|
|
this._initMixins(args);
|
|
}
|
|
return new C();
|
|
},
|
|
|
|
/**
|
|
Creates an instance of a class. Accepts either no arguments, or an object
|
|
containing values to initialize the newly instantiated object with.
|
|
|
|
```javascript
|
|
App.Person = Ember.Object.extend({
|
|
helloWorld: function() {
|
|
alert("Hi, my name is " + this.get('name'));
|
|
}
|
|
});
|
|
|
|
var tom = App.Person.create({
|
|
name: 'Tom Dale'
|
|
});
|
|
|
|
tom.helloWorld(); // alerts "Hi, my name is Tom Dale".
|
|
```
|
|
|
|
`create` will call the `init` function if defined during
|
|
`Ember.AnyObject.extend`
|
|
|
|
If no arguments are passed to `create`, it will not set values to the new
|
|
instance during initialization:
|
|
|
|
```javascript
|
|
var noName = App.Person.create();
|
|
noName.helloWorld(); // alerts undefined
|
|
```
|
|
|
|
NOTE: For performance reasons, you cannot declare methods or computed
|
|
properties during `create`. You should instead declare methods and computed
|
|
properties when using `extend` or use the `createWithMixins` shorthand.
|
|
|
|
@method create
|
|
@static
|
|
@param [arguments]*
|
|
*/
|
|
create: function() {
|
|
var C = this;
|
|
var l = arguments.length;
|
|
if (l > 0) {
|
|
var args = new Array(l);
|
|
for (var i = 0; i < l; i++) {
|
|
args[i] = arguments[i];
|
|
}
|
|
this._initProperties(args);
|
|
}
|
|
return new C();
|
|
},
|
|
|
|
/**
|
|
Augments a constructor's prototype with additional
|
|
properties and functions:
|
|
|
|
```javascript
|
|
MyObject = Ember.Object.extend({
|
|
name: 'an object'
|
|
});
|
|
|
|
o = MyObject.create();
|
|
o.get('name'); // 'an object'
|
|
|
|
MyObject.reopen({
|
|
say: function(msg){
|
|
console.log(msg);
|
|
}
|
|
})
|
|
|
|
o2 = MyObject.create();
|
|
o2.say("hello"); // logs "hello"
|
|
|
|
o.say("goodbye"); // logs "goodbye"
|
|
```
|
|
|
|
To add functions and properties to the constructor itself,
|
|
see `reopenClass`
|
|
|
|
@method reopen
|
|
*/
|
|
reopen: function() {
|
|
this.willReopen();
|
|
|
|
var l = arguments.length;
|
|
var args = new Array(l);
|
|
if (l > 0) {
|
|
for (var i = 0; i < l; i++) {
|
|
args[i] = arguments[i];
|
|
}
|
|
}
|
|
|
|
apply(this.PrototypeMixin, reopen, args);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Augments a constructor's own properties and functions:
|
|
|
|
```javascript
|
|
MyObject = Ember.Object.extend({
|
|
name: 'an object'
|
|
});
|
|
|
|
MyObject.reopenClass({
|
|
canBuild: false
|
|
});
|
|
|
|
MyObject.canBuild; // false
|
|
o = MyObject.create();
|
|
```
|
|
|
|
In other words, this creates static properties and functions for the class. These are only available on the class
|
|
and not on any instance of that class.
|
|
|
|
```javascript
|
|
App.Person = Ember.Object.extend({
|
|
name : "",
|
|
sayHello : function(){
|
|
alert("Hello. My name is " + this.get('name'));
|
|
}
|
|
});
|
|
|
|
App.Person.reopenClass({
|
|
species : "Homo sapiens",
|
|
createPerson: function(newPersonsName){
|
|
return App.Person.create({
|
|
name:newPersonsName
|
|
});
|
|
}
|
|
});
|
|
|
|
var tom = App.Person.create({
|
|
name : "Tom Dale"
|
|
});
|
|
var yehuda = App.Person.createPerson("Yehuda Katz");
|
|
|
|
tom.sayHello(); // "Hello. My name is Tom Dale"
|
|
yehuda.sayHello(); // "Hello. My name is Yehuda Katz"
|
|
alert(App.Person.species); // "Homo sapiens"
|
|
```
|
|
|
|
Note that `species` and `createPerson` are *not* valid on the `tom` and `yehuda`
|
|
variables. They are only valid on `App.Person`.
|
|
|
|
To add functions and properties to instances of
|
|
a constructor by extending the constructor's prototype
|
|
see `reopen`
|
|
|
|
@method reopenClass
|
|
*/
|
|
reopenClass: function() {
|
|
var l = arguments.length;
|
|
var args = new Array(l);
|
|
if (l > 0) {
|
|
for (var i = 0; i < l; i++) {
|
|
args[i] = arguments[i];
|
|
}
|
|
}
|
|
|
|
apply(this.ClassMixin, reopen, args);
|
|
applyMixin(this, arguments, false);
|
|
return this;
|
|
},
|
|
|
|
detect: function(obj) {
|
|
if ('function' !== typeof obj) { return false; }
|
|
while(obj) {
|
|
if (obj===this) { return true; }
|
|
obj = obj.superclass;
|
|
}
|
|
return false;
|
|
},
|
|
|
|
detectInstance: function(obj) {
|
|
return obj instanceof this;
|
|
},
|
|
|
|
/**
|
|
In some cases, you may want to annotate computed properties with additional
|
|
metadata about how they function or what values they operate on. For
|
|
example, computed property functions may close over variables that are then
|
|
no longer available for introspection.
|
|
|
|
You can pass a hash of these values to a computed property like this:
|
|
|
|
```javascript
|
|
person: function() {
|
|
var personId = this.get('personId');
|
|
return App.Person.create({ id: personId });
|
|
}.property().meta({ type: App.Person })
|
|
```
|
|
|
|
Once you've done this, you can retrieve the values saved to the computed
|
|
property from your class like this:
|
|
|
|
```javascript
|
|
MyClass.metaForProperty('person');
|
|
```
|
|
|
|
This will return the original hash that was passed to `meta()`.
|
|
|
|
@static
|
|
@method metaForProperty
|
|
@param key {String} property name
|
|
*/
|
|
metaForProperty: function(key) {
|
|
var meta = this.proto()['__ember_meta__'];
|
|
var desc = meta && meta.descs[key];
|
|
|
|
Ember.assert("metaForProperty() could not find a computed property with key '"+key+"'.", !!desc && desc instanceof ComputedProperty);
|
|
return desc._meta || {};
|
|
},
|
|
|
|
_computedProperties: computed(function() {
|
|
hasCachedComputedProperties = true;
|
|
var proto = this.proto();
|
|
var descs = meta(proto).descs;
|
|
var property;
|
|
var properties = [];
|
|
|
|
for (var name in descs) {
|
|
property = descs[name];
|
|
|
|
if (property instanceof ComputedProperty) {
|
|
properties.push({
|
|
name: name,
|
|
meta: property._meta
|
|
});
|
|
}
|
|
}
|
|
return properties;
|
|
}).readOnly(),
|
|
|
|
/**
|
|
Iterate over each computed property for the class, passing its name
|
|
and any associated metadata (see `metaForProperty`) to the callback.
|
|
|
|
@static
|
|
@method eachComputedProperty
|
|
@param {Function} callback
|
|
@param {Object} binding
|
|
*/
|
|
eachComputedProperty: function(callback, binding) {
|
|
var property, name;
|
|
var empty = {};
|
|
|
|
var properties = get(this, '_computedProperties');
|
|
|
|
for (var i = 0, length = properties.length; i < length; i++) {
|
|
property = properties[i];
|
|
name = property.name;
|
|
callback.call(binding || this, property.name, property.meta || empty);
|
|
}
|
|
}
|
|
};
|
|
|
|
function injectedPropertyAssertion() {
|
|
Ember.assert("Injected properties are invalid", validatePropertyInjections(this));
|
|
}
|
|
|
|
function addOnLookupHandler() {
|
|
Ember.runInDebug(function() {
|
|
/**
|
|
Provides lookup-time type validation for injected properties.
|
|
|
|
@private
|
|
@method _onLookup
|
|
*/
|
|
ClassMixinProps._onLookup = injectedPropertyAssertion;
|
|
});
|
|
}
|
|
|
|
|
|
addOnLookupHandler();
|
|
|
|
/**
|
|
Returns a hash of property names and container names that injected
|
|
properties will lookup on the container lazily.
|
|
|
|
@method _lazyInjections
|
|
@return {Object} Hash of all lazy injected property keys to container names
|
|
*/
|
|
ClassMixinProps._lazyInjections = function() {
|
|
var injections = {};
|
|
var proto = this.proto();
|
|
var descs = meta(proto).descs;
|
|
var key, desc;
|
|
|
|
for (key in descs) {
|
|
desc = descs[key];
|
|
if (desc instanceof InjectedProperty) {
|
|
injections[key] = desc.type + ':' + (desc.name || key);
|
|
}
|
|
}
|
|
|
|
return injections;
|
|
};
|
|
|
|
|
|
var ClassMixin = Mixin.create(ClassMixinProps);
|
|
|
|
ClassMixin.ownerConstructor = CoreObject;
|
|
|
|
CoreObject.ClassMixin = ClassMixin;
|
|
|
|
ClassMixin.apply(CoreObject);
|
|
|
|
CoreObject.reopen({
|
|
didDefineProperty: function(proto, key, value) {
|
|
if (hasCachedComputedProperties === false) { return; }
|
|
if (value instanceof Ember.ComputedProperty) {
|
|
var cache = Ember.meta(this.constructor).cache;
|
|
|
|
if (cache._computedProperties !== undefined) {
|
|
cache._computedProperties = undefined;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
__exports__["default"] = CoreObject;
|
|
});
|
|
enifed("ember-runtime/system/deferred",
|
|
["ember-metal/core","ember-runtime/mixins/deferred","ember-runtime/system/object","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
var DeferredMixin = __dependency2__["default"];
|
|
var EmberObject = __dependency3__["default"];
|
|
|
|
var Deferred = EmberObject.extend(DeferredMixin, {
|
|
init: function() {
|
|
Ember.deprecate('Usage of Ember.Deferred is deprecated.', false, { url: 'http://emberjs.com/guides/deprecations/#toc_deprecate-ember-deferredmixin-and-ember-deferred' });
|
|
this._super();
|
|
}
|
|
});
|
|
|
|
Deferred.reopenClass({
|
|
promise: function(callback, binding) {
|
|
var deferred = Deferred.create();
|
|
callback.call(binding, deferred);
|
|
return deferred;
|
|
}
|
|
});
|
|
|
|
__exports__["default"] = Deferred;
|
|
});
|
|
enifed("ember-runtime/system/each_proxy",
|
|
["ember-metal/core","ember-metal/property_get","ember-metal/utils","ember-metal/enumerable_utils","ember-metal/array","ember-runtime/mixins/array","ember-runtime/system/object","ember-metal/computed","ember-metal/observer","ember-metal/events","ember-metal/properties","ember-metal/property_events","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.assert
|
|
|
|
var get = __dependency2__.get;
|
|
var guidFor = __dependency3__.guidFor;
|
|
var forEach = __dependency4__.forEach;
|
|
var indexOf = __dependency5__.indexOf;
|
|
var EmberArray = __dependency6__["default"];
|
|
// ES6TODO: WAT? Circular dep?
|
|
var EmberObject = __dependency7__["default"];
|
|
var computed = __dependency8__.computed;
|
|
var addObserver = __dependency9__.addObserver;
|
|
var addBeforeObserver = __dependency9__.addBeforeObserver;
|
|
var removeBeforeObserver = __dependency9__.removeBeforeObserver;
|
|
var removeObserver = __dependency9__.removeObserver;
|
|
var typeOf = __dependency3__.typeOf;
|
|
var watchedEvents = __dependency10__.watchedEvents;
|
|
var defineProperty = __dependency11__.defineProperty;
|
|
var beginPropertyChanges = __dependency12__.beginPropertyChanges;
|
|
var propertyDidChange = __dependency12__.propertyDidChange;
|
|
var propertyWillChange = __dependency12__.propertyWillChange;
|
|
var endPropertyChanges = __dependency12__.endPropertyChanges;
|
|
var changeProperties = __dependency12__.changeProperties;
|
|
|
|
var EachArray = EmberObject.extend(EmberArray, {
|
|
|
|
init: function(content, keyName, owner) {
|
|
this._super();
|
|
this._keyName = keyName;
|
|
this._owner = owner;
|
|
this._content = content;
|
|
},
|
|
|
|
objectAt: function(idx) {
|
|
var item = this._content.objectAt(idx);
|
|
return item && get(item, this._keyName);
|
|
},
|
|
|
|
length: computed(function() {
|
|
var content = this._content;
|
|
return content ? get(content, 'length') : 0;
|
|
})
|
|
|
|
});
|
|
|
|
var IS_OBSERVER = /^.+:(before|change)$/;
|
|
|
|
function addObserverForContentKey(content, keyName, proxy, idx, loc) {
|
|
var objects = proxy._objects;
|
|
var guid;
|
|
if (!objects) objects = proxy._objects = {};
|
|
|
|
while(--loc>=idx) {
|
|
var item = content.objectAt(loc);
|
|
if (item) {
|
|
Ember.assert('When using @each to observe the array ' + content + ', the array must return an object', typeOf(item) === 'instance' || typeOf(item) === 'object');
|
|
addBeforeObserver(item, keyName, proxy, 'contentKeyWillChange');
|
|
addObserver(item, keyName, proxy, 'contentKeyDidChange');
|
|
|
|
// keep track of the index each item was found at so we can map
|
|
// it back when the obj changes.
|
|
guid = guidFor(item);
|
|
if (!objects[guid]) objects[guid] = [];
|
|
objects[guid].push(loc);
|
|
}
|
|
}
|
|
}
|
|
|
|
function removeObserverForContentKey(content, keyName, proxy, idx, loc) {
|
|
var objects = proxy._objects;
|
|
if (!objects) objects = proxy._objects = {};
|
|
var indicies, guid;
|
|
|
|
while(--loc>=idx) {
|
|
var item = content.objectAt(loc);
|
|
if (item) {
|
|
removeBeforeObserver(item, keyName, proxy, 'contentKeyWillChange');
|
|
removeObserver(item, keyName, proxy, 'contentKeyDidChange');
|
|
|
|
guid = guidFor(item);
|
|
indicies = objects[guid];
|
|
indicies[indexOf.call(indicies, loc)] = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
This is the object instance returned when you get the `@each` property on an
|
|
array. It uses the unknownProperty handler to automatically create
|
|
EachArray instances for property names.
|
|
|
|
@private
|
|
@class EachProxy
|
|
@namespace Ember
|
|
@extends Ember.Object
|
|
*/
|
|
var EachProxy = EmberObject.extend({
|
|
|
|
init: function(content) {
|
|
this._super();
|
|
this._content = content;
|
|
content.addArrayObserver(this);
|
|
|
|
// in case someone is already observing some keys make sure they are
|
|
// added
|
|
forEach(watchedEvents(this), function(eventName) {
|
|
this.didAddListener(eventName);
|
|
}, this);
|
|
},
|
|
|
|
/**
|
|
You can directly access mapped properties by simply requesting them.
|
|
The `unknownProperty` handler will generate an EachArray of each item.
|
|
|
|
@method unknownProperty
|
|
@param keyName {String}
|
|
@param value {*}
|
|
*/
|
|
unknownProperty: function(keyName, value) {
|
|
var ret;
|
|
ret = new EachArray(this._content, keyName, this);
|
|
defineProperty(this, keyName, null, ret);
|
|
this.beginObservingContentKey(keyName);
|
|
return ret;
|
|
},
|
|
|
|
// ..........................................................
|
|
// ARRAY CHANGES
|
|
// Invokes whenever the content array itself changes.
|
|
|
|
arrayWillChange: function(content, idx, removedCnt, addedCnt) {
|
|
var keys = this._keys;
|
|
var key, lim;
|
|
|
|
lim = removedCnt>0 ? idx+removedCnt : -1;
|
|
beginPropertyChanges(this);
|
|
|
|
for(key in keys) {
|
|
if (!keys.hasOwnProperty(key)) { continue; }
|
|
|
|
if (lim>0) { removeObserverForContentKey(content, key, this, idx, lim); }
|
|
|
|
propertyWillChange(this, key);
|
|
}
|
|
|
|
propertyWillChange(this._content, '@each');
|
|
endPropertyChanges(this);
|
|
},
|
|
|
|
arrayDidChange: function(content, idx, removedCnt, addedCnt) {
|
|
var keys = this._keys;
|
|
var lim;
|
|
|
|
lim = addedCnt>0 ? idx+addedCnt : -1;
|
|
changeProperties(function() {
|
|
for(var key in keys) {
|
|
if (!keys.hasOwnProperty(key)) { continue; }
|
|
|
|
if (lim>0) { addObserverForContentKey(content, key, this, idx, lim); }
|
|
|
|
propertyDidChange(this, key);
|
|
}
|
|
|
|
propertyDidChange(this._content, '@each');
|
|
}, this);
|
|
},
|
|
|
|
// ..........................................................
|
|
// LISTEN FOR NEW OBSERVERS AND OTHER EVENT LISTENERS
|
|
// Start monitoring keys based on who is listening...
|
|
|
|
didAddListener: function(eventName) {
|
|
if (IS_OBSERVER.test(eventName)) {
|
|
this.beginObservingContentKey(eventName.slice(0, -7));
|
|
}
|
|
},
|
|
|
|
didRemoveListener: function(eventName) {
|
|
if (IS_OBSERVER.test(eventName)) {
|
|
this.stopObservingContentKey(eventName.slice(0, -7));
|
|
}
|
|
},
|
|
|
|
// ..........................................................
|
|
// CONTENT KEY OBSERVING
|
|
// Actual watch keys on the source content.
|
|
|
|
beginObservingContentKey: function(keyName) {
|
|
var keys = this._keys;
|
|
if (!keys) keys = this._keys = {};
|
|
if (!keys[keyName]) {
|
|
keys[keyName] = 1;
|
|
var content = this._content;
|
|
var len = get(content, 'length');
|
|
|
|
addObserverForContentKey(content, keyName, this, 0, len);
|
|
} else {
|
|
keys[keyName]++;
|
|
}
|
|
},
|
|
|
|
stopObservingContentKey: function(keyName) {
|
|
var keys = this._keys;
|
|
if (keys && (keys[keyName]>0) && (--keys[keyName]<=0)) {
|
|
var content = this._content;
|
|
var len = get(content, 'length');
|
|
|
|
removeObserverForContentKey(content, keyName, this, 0, len);
|
|
}
|
|
},
|
|
|
|
contentKeyWillChange: function(obj, keyName) {
|
|
propertyWillChange(this, keyName);
|
|
},
|
|
|
|
contentKeyDidChange: function(obj, keyName) {
|
|
propertyDidChange(this, keyName);
|
|
}
|
|
});
|
|
|
|
__exports__.EachArray = EachArray;
|
|
__exports__.EachProxy = EachProxy;
|
|
});
|
|
enifed("ember-runtime/system/lazy_load",
|
|
["ember-metal/core","ember-metal/array","ember-runtime/system/native_array","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
/*globals CustomEvent */
|
|
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.ENV.EMBER_LOAD_HOOKS
|
|
var forEach = __dependency2__.forEach;
|
|
// make sure Ember.A is setup.
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
|
|
var loadHooks = Ember.ENV.EMBER_LOAD_HOOKS || {};
|
|
var loaded = {};
|
|
|
|
/**
|
|
Detects when a specific package of Ember (e.g. 'Ember.Handlebars')
|
|
has fully loaded and is available for extension.
|
|
|
|
The provided `callback` will be called with the `name` passed
|
|
resolved from a string into the object:
|
|
|
|
``` javascript
|
|
Ember.onLoad('Ember.Handlebars' function(hbars) {
|
|
hbars.registerHelper(...);
|
|
});
|
|
```
|
|
|
|
@method onLoad
|
|
@for Ember
|
|
@param name {String} name of hook
|
|
@param callback {Function} callback to be called
|
|
*/
|
|
function onLoad(name, callback) {
|
|
var object;
|
|
|
|
loadHooks[name] = loadHooks[name] || Ember.A();
|
|
loadHooks[name].pushObject(callback);
|
|
|
|
if (object = loaded[name]) {
|
|
callback(object);
|
|
}
|
|
}
|
|
|
|
__exports__.onLoad = onLoad;/**
|
|
Called when an Ember.js package (e.g Ember.Handlebars) has finished
|
|
loading. Triggers any callbacks registered for this event.
|
|
|
|
@method runLoadHooks
|
|
@for Ember
|
|
@param name {String} name of hook
|
|
@param object {Object} object to pass to callbacks
|
|
*/
|
|
function runLoadHooks(name, object) {
|
|
loaded[name] = object;
|
|
|
|
if (typeof window === 'object' && typeof window.dispatchEvent === 'function' && typeof CustomEvent === "function") {
|
|
var event = new CustomEvent(name, {detail: object, name: name});
|
|
window.dispatchEvent(event);
|
|
}
|
|
|
|
if (loadHooks[name]) {
|
|
forEach.call(loadHooks[name], function(callback) {
|
|
callback(object);
|
|
});
|
|
}
|
|
}
|
|
|
|
__exports__.runLoadHooks = runLoadHooks;
|
|
});
|
|
enifed("ember-runtime/system/namespace",
|
|
["ember-metal/core","ember-metal/property_get","ember-metal/array","ember-metal/utils","ember-metal/mixin","ember-runtime/system/object","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
|
|
// Ember.lookup, Ember.BOOTED, Ember.deprecate, Ember.NAME_KEY, Ember.anyUnprocessedMixins
|
|
var Ember = __dependency1__["default"];
|
|
var get = __dependency2__.get;
|
|
var indexOf = __dependency3__.indexOf;
|
|
var GUID_KEY = __dependency4__.GUID_KEY;
|
|
var guidFor = __dependency4__.guidFor;
|
|
var Mixin = __dependency5__.Mixin;
|
|
|
|
var EmberObject = __dependency6__["default"];
|
|
|
|
/**
|
|
A Namespace is an object usually used to contain other objects or methods
|
|
such as an application or framework. Create a namespace anytime you want
|
|
to define one of these new containers.
|
|
|
|
# Example Usage
|
|
|
|
```javascript
|
|
MyFramework = Ember.Namespace.create({
|
|
VERSION: '1.0.0'
|
|
});
|
|
```
|
|
|
|
@class Namespace
|
|
@namespace Ember
|
|
@extends Ember.Object
|
|
*/
|
|
var Namespace = EmberObject.extend({
|
|
isNamespace: true,
|
|
|
|
init: function() {
|
|
Namespace.NAMESPACES.push(this);
|
|
Namespace.PROCESSED = false;
|
|
},
|
|
|
|
toString: function() {
|
|
var name = get(this, 'name') || get(this, 'modulePrefix');
|
|
if (name) { return name; }
|
|
|
|
findNamespaces();
|
|
return this[NAME_KEY];
|
|
},
|
|
|
|
nameClasses: function() {
|
|
processNamespace([this.toString()], this, {});
|
|
},
|
|
|
|
destroy: function() {
|
|
var namespaces = Namespace.NAMESPACES;
|
|
var toString = this.toString();
|
|
|
|
if (toString) {
|
|
Ember.lookup[toString] = undefined;
|
|
delete Namespace.NAMESPACES_BY_ID[toString];
|
|
}
|
|
namespaces.splice(indexOf.call(namespaces, this), 1);
|
|
this._super();
|
|
}
|
|
});
|
|
|
|
Namespace.reopenClass({
|
|
NAMESPACES: [Ember],
|
|
NAMESPACES_BY_ID: {},
|
|
PROCESSED: false,
|
|
processAll: processAllNamespaces,
|
|
byName: function(name) {
|
|
if (!Ember.BOOTED) {
|
|
processAllNamespaces();
|
|
}
|
|
|
|
return NAMESPACES_BY_ID[name];
|
|
}
|
|
});
|
|
|
|
var NAMESPACES_BY_ID = Namespace.NAMESPACES_BY_ID;
|
|
|
|
var hasOwnProp = ({}).hasOwnProperty;
|
|
|
|
function processNamespace(paths, root, seen) {
|
|
var idx = paths.length;
|
|
|
|
NAMESPACES_BY_ID[paths.join('.')] = root;
|
|
|
|
// Loop over all of the keys in the namespace, looking for classes
|
|
for(var key in root) {
|
|
if (!hasOwnProp.call(root, key)) { continue; }
|
|
var obj = root[key];
|
|
|
|
// If we are processing the `Ember` namespace, for example, the
|
|
// `paths` will start with `["Ember"]`. Every iteration through
|
|
// the loop will update the **second** element of this list with
|
|
// the key, so processing `Ember.View` will make the Array
|
|
// `['Ember', 'View']`.
|
|
paths[idx] = key;
|
|
|
|
// If we have found an unprocessed class
|
|
if (obj && obj.toString === classToString) {
|
|
// Replace the class' `toString` with the dot-separated path
|
|
// and set its `NAME_KEY`
|
|
obj.toString = makeToString(paths.join('.'));
|
|
obj[NAME_KEY] = paths.join('.');
|
|
|
|
// Support nested namespaces
|
|
} else if (obj && obj.isNamespace) {
|
|
// Skip aliased namespaces
|
|
if (seen[guidFor(obj)]) { continue; }
|
|
seen[guidFor(obj)] = true;
|
|
|
|
// Process the child namespace
|
|
processNamespace(paths, obj, seen);
|
|
}
|
|
}
|
|
|
|
paths.length = idx; // cut out last item
|
|
}
|
|
|
|
var STARTS_WITH_UPPERCASE = /^[A-Z]/;
|
|
|
|
function tryIsNamespace(lookup, prop) {
|
|
try {
|
|
var obj = lookup[prop];
|
|
return obj && obj.isNamespace && obj;
|
|
} catch (e) {
|
|
// continue
|
|
}
|
|
}
|
|
|
|
function findNamespaces() {
|
|
var lookup = Ember.lookup;
|
|
var obj;
|
|
|
|
if (Namespace.PROCESSED) { return; }
|
|
|
|
for (var prop in lookup) {
|
|
// Only process entities that start with uppercase A-Z
|
|
if (!STARTS_WITH_UPPERCASE.test(prop)) { continue; }
|
|
|
|
// Unfortunately, some versions of IE don't support window.hasOwnProperty
|
|
if (lookup.hasOwnProperty && !lookup.hasOwnProperty(prop)) { continue; }
|
|
|
|
// At times we are not allowed to access certain properties for security reasons.
|
|
// There are also times where even if we can access them, we are not allowed to access their properties.
|
|
obj = tryIsNamespace(lookup, prop);
|
|
if (obj) {
|
|
obj[NAME_KEY] = prop;
|
|
}
|
|
}
|
|
}
|
|
|
|
var NAME_KEY = Ember.NAME_KEY = GUID_KEY + '_name';
|
|
|
|
function superClassString(mixin) {
|
|
var superclass = mixin.superclass;
|
|
if (superclass) {
|
|
if (superclass[NAME_KEY]) { return superclass[NAME_KEY]; }
|
|
else { return superClassString(superclass); }
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
function classToString() {
|
|
if (!Ember.BOOTED && !this[NAME_KEY]) {
|
|
processAllNamespaces();
|
|
}
|
|
|
|
var ret;
|
|
|
|
if (this[NAME_KEY]) {
|
|
ret = this[NAME_KEY];
|
|
} else if (this._toString) {
|
|
ret = this._toString;
|
|
} else {
|
|
var str = superClassString(this);
|
|
if (str) {
|
|
ret = "(subclass of " + str + ")";
|
|
} else {
|
|
ret = "(unknown mixin)";
|
|
}
|
|
this.toString = makeToString(ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
function processAllNamespaces() {
|
|
var unprocessedNamespaces = !Namespace.PROCESSED;
|
|
var unprocessedMixins = Ember.anyUnprocessedMixins;
|
|
|
|
if (unprocessedNamespaces) {
|
|
findNamespaces();
|
|
Namespace.PROCESSED = true;
|
|
}
|
|
|
|
if (unprocessedNamespaces || unprocessedMixins) {
|
|
var namespaces = Namespace.NAMESPACES;
|
|
var namespace;
|
|
|
|
for (var i=0, l=namespaces.length; i<l; i++) {
|
|
namespace = namespaces[i];
|
|
processNamespace([namespace.toString()], namespace, {});
|
|
}
|
|
|
|
Ember.anyUnprocessedMixins = false;
|
|
}
|
|
}
|
|
|
|
function makeToString(ret) {
|
|
return function() { return ret; };
|
|
}
|
|
|
|
Mixin.prototype.toString = classToString; // ES6TODO: altering imported objects. SBB.
|
|
|
|
__exports__["default"] = Namespace;
|
|
});
|
|
enifed("ember-runtime/system/native_array",
|
|
["ember-metal/core","ember-metal/property_get","ember-metal/enumerable_utils","ember-metal/mixin","ember-metal/array","ember-runtime/mixins/array","ember-runtime/mixins/mutable_array","ember-runtime/mixins/observable","ember-runtime/mixins/copyable","ember-runtime/mixins/freezable","ember-runtime/copy","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.EXTEND_PROTOTYPES
|
|
|
|
var get = __dependency2__.get;
|
|
var replace = __dependency3__._replace;
|
|
var forEach = __dependency3__.forEach;
|
|
var Mixin = __dependency4__.Mixin;
|
|
var indexOf = __dependency5__.indexOf;
|
|
var lastIndexOf = __dependency5__.lastIndexOf;
|
|
var EmberArray = __dependency6__["default"];
|
|
var MutableArray = __dependency7__["default"];
|
|
var Observable = __dependency8__["default"];
|
|
var Copyable = __dependency9__["default"];
|
|
var FROZEN_ERROR = __dependency10__.FROZEN_ERROR;
|
|
var copy = __dependency11__["default"];
|
|
|
|
// Add Ember.Array to Array.prototype. Remove methods with native
|
|
// implementations and supply some more optimized versions of generic methods
|
|
// because they are so common.
|
|
|
|
/**
|
|
The NativeArray mixin contains the properties needed to make the native
|
|
Array support Ember.MutableArray and all of its dependent APIs. Unless you
|
|
have `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Array` set to
|
|
false, this will be applied automatically. Otherwise you can apply the mixin
|
|
at anytime by calling `Ember.NativeArray.activate`.
|
|
|
|
@class NativeArray
|
|
@namespace Ember
|
|
@uses Ember.MutableArray
|
|
@uses Ember.Observable
|
|
@uses Ember.Copyable
|
|
*/
|
|
var NativeArray = Mixin.create(MutableArray, Observable, Copyable, {
|
|
|
|
// because length is a built-in property we need to know to just get the
|
|
// original property.
|
|
get: function(key) {
|
|
if (key==='length') return this.length;
|
|
else if ('number' === typeof key) return this[key];
|
|
else return this._super(key);
|
|
},
|
|
|
|
objectAt: function(idx) {
|
|
return this[idx];
|
|
},
|
|
|
|
// primitive for array support.
|
|
replace: function(idx, amt, objects) {
|
|
|
|
if (this.isFrozen) throw FROZEN_ERROR;
|
|
|
|
// if we replaced exactly the same number of items, then pass only the
|
|
// replaced range. Otherwise, pass the full remaining array length
|
|
// since everything has shifted
|
|
var len = objects ? get(objects, 'length') : 0;
|
|
this.arrayContentWillChange(idx, amt, len);
|
|
|
|
if (len === 0) {
|
|
this.splice(idx, amt);
|
|
} else {
|
|
replace(this, idx, amt, objects);
|
|
}
|
|
|
|
this.arrayContentDidChange(idx, amt, len);
|
|
return this;
|
|
},
|
|
|
|
// If you ask for an unknown property, then try to collect the value
|
|
// from member items.
|
|
unknownProperty: function(key, value) {
|
|
var ret;// = this.reducedProperty(key, value) ;
|
|
if (value !== undefined && ret === undefined) {
|
|
ret = this[key] = value;
|
|
}
|
|
return ret;
|
|
},
|
|
|
|
indexOf: indexOf,
|
|
|
|
lastIndexOf: lastIndexOf,
|
|
|
|
copy: function(deep) {
|
|
if (deep) {
|
|
return this.map(function(item) { return copy(item, true); });
|
|
}
|
|
|
|
return this.slice();
|
|
}
|
|
});
|
|
|
|
// Remove any methods implemented natively so we don't override them
|
|
var ignore = ['length'];
|
|
forEach(NativeArray.keys(), function(methodName) {
|
|
if (Array.prototype[methodName]) ignore.push(methodName);
|
|
});
|
|
|
|
if (ignore.length > 0) {
|
|
NativeArray = NativeArray.without.apply(NativeArray, ignore);
|
|
}
|
|
|
|
/**
|
|
Creates an `Ember.NativeArray` from an Array like object.
|
|
Does not modify the original object. Ember.A is not needed if
|
|
`Ember.EXTEND_PROTOTYPES` is `true` (the default value). However,
|
|
it is recommended that you use Ember.A when creating addons for
|
|
ember or when you can not guarantee that `Ember.EXTEND_PROTOTYPES`
|
|
will be `true`.
|
|
|
|
Example
|
|
|
|
```js
|
|
var Pagination = Ember.CollectionView.extend({
|
|
tagName: 'ul',
|
|
classNames: ['pagination'],
|
|
|
|
init: function() {
|
|
this._super();
|
|
if (!this.get('content')) {
|
|
this.set('content', Ember.A());
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
@method A
|
|
@for Ember
|
|
@return {Ember.NativeArray}
|
|
*/
|
|
var A = function(arr) {
|
|
if (arr === undefined) { arr = []; }
|
|
return EmberArray.detect(arr) ? arr : NativeArray.apply(arr);
|
|
};
|
|
|
|
/**
|
|
Activates the mixin on the Array.prototype if not already applied. Calling
|
|
this method more than once is safe. This will be called when ember is loaded
|
|
unless you have `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Array`
|
|
set to `false`.
|
|
|
|
Example
|
|
|
|
```js
|
|
if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Array) {
|
|
Ember.NativeArray.activate();
|
|
}
|
|
```
|
|
|
|
@method activate
|
|
@for Ember.NativeArray
|
|
@static
|
|
@return {void}
|
|
*/
|
|
NativeArray.activate = function() {
|
|
NativeArray.apply(Array.prototype);
|
|
|
|
A = function(arr) { return arr || []; };
|
|
};
|
|
|
|
if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Array) {
|
|
NativeArray.activate();
|
|
}
|
|
|
|
Ember.A = A; // ES6TODO: Setting A onto the object returned by ember-metal/core to avoid circles
|
|
__exports__.A = A;
|
|
__exports__.NativeArray = NativeArray;
|
|
__exports__["default"] = NativeArray;
|
|
});
|
|
enifed("ember-runtime/system/object",
|
|
["ember-runtime/system/core_object","ember-runtime/mixins/observable","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
|
|
var CoreObject = __dependency1__["default"];
|
|
var Observable = __dependency2__["default"];
|
|
|
|
/**
|
|
`Ember.Object` is the main base class for all Ember objects. It is a subclass
|
|
of `Ember.CoreObject` with the `Ember.Observable` mixin applied. For details,
|
|
see the documentation for each of these.
|
|
|
|
@class Object
|
|
@namespace Ember
|
|
@extends Ember.CoreObject
|
|
@uses Ember.Observable
|
|
*/
|
|
var EmberObject = CoreObject.extend(Observable);
|
|
EmberObject.toString = function() {
|
|
return "Ember.Object";
|
|
};
|
|
|
|
__exports__["default"] = EmberObject;
|
|
});
|
|
enifed("ember-runtime/system/object_proxy",
|
|
["ember-runtime/system/object","ember-runtime/mixins/-proxy","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var EmberObject = __dependency1__["default"];
|
|
var _ProxyMixin = __dependency2__["default"];
|
|
|
|
/**
|
|
`Ember.ObjectProxy` forwards all properties not defined by the proxy itself
|
|
to a proxied `content` object.
|
|
|
|
```javascript
|
|
object = Ember.Object.create({
|
|
name: 'Foo'
|
|
});
|
|
|
|
proxy = Ember.ObjectProxy.create({
|
|
content: object
|
|
});
|
|
|
|
// Access and change existing properties
|
|
proxy.get('name') // 'Foo'
|
|
proxy.set('name', 'Bar');
|
|
object.get('name') // 'Bar'
|
|
|
|
// Create new 'description' property on `object`
|
|
proxy.set('description', 'Foo is a whizboo baz');
|
|
object.get('description') // 'Foo is a whizboo baz'
|
|
```
|
|
|
|
While `content` is unset, setting a property to be delegated will throw an
|
|
Error.
|
|
|
|
```javascript
|
|
proxy = Ember.ObjectProxy.create({
|
|
content: null,
|
|
flag: null
|
|
});
|
|
proxy.set('flag', true);
|
|
proxy.get('flag'); // true
|
|
proxy.get('foo'); // undefined
|
|
proxy.set('foo', 'data'); // throws Error
|
|
```
|
|
|
|
Delegated properties can be bound to and will change when content is updated.
|
|
|
|
Computed properties on the proxy itself can depend on delegated properties.
|
|
|
|
```javascript
|
|
ProxyWithComputedProperty = Ember.ObjectProxy.extend({
|
|
fullName: function () {
|
|
var firstName = this.get('firstName'),
|
|
lastName = this.get('lastName');
|
|
if (firstName && lastName) {
|
|
return firstName + ' ' + lastName;
|
|
}
|
|
return firstName || lastName;
|
|
}.property('firstName', 'lastName')
|
|
});
|
|
|
|
proxy = ProxyWithComputedProperty.create();
|
|
|
|
proxy.get('fullName'); // undefined
|
|
proxy.set('content', {
|
|
firstName: 'Tom', lastName: 'Dale'
|
|
}); // triggers property change for fullName on proxy
|
|
|
|
proxy.get('fullName'); // 'Tom Dale'
|
|
```
|
|
|
|
@class ObjectProxy
|
|
@namespace Ember
|
|
@extends Ember.Object
|
|
@extends Ember._ProxyMixin
|
|
*/
|
|
|
|
__exports__["default"] = EmberObject.extend(_ProxyMixin);
|
|
});
|
|
enifed("ember-runtime/system/service",
|
|
["ember-runtime/system/object","ember-runtime/inject","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var Object = __dependency1__["default"];
|
|
var createInjectionHelper = __dependency2__.createInjectionHelper;
|
|
|
|
var Service;
|
|
|
|
|
|
/**
|
|
@class Service
|
|
@namespace Ember
|
|
@extends Ember.Object
|
|
*/
|
|
Service = Object.extend();
|
|
|
|
/**
|
|
Creates a property that lazily looks up a service in the container. There
|
|
are no restrictions as to what objects a service can be injected into.
|
|
|
|
Example:
|
|
|
|
```javascript
|
|
App.ApplicationRoute = Ember.Route.extend({
|
|
authManager: Ember.inject.service('auth'),
|
|
|
|
model: function() {
|
|
return this.get('authManager').findCurrentUser();
|
|
}
|
|
});
|
|
```
|
|
|
|
This example will create an `authManager` property on the application route
|
|
that looks up the `auth` service in the container, making it easily
|
|
accessible in the `model` hook.
|
|
|
|
@method inject.service
|
|
@for Ember
|
|
@param {String} name (optional) name of the service to inject, defaults to
|
|
the property's name
|
|
@return {Ember.InjectedProperty} injection descriptor instance
|
|
*/
|
|
createInjectionHelper('service');
|
|
|
|
|
|
__exports__["default"] = Service;
|
|
});
|
|
enifed("ember-runtime/system/set",
|
|
["ember-metal/core","ember-metal/property_get","ember-metal/property_set","ember-metal/utils","ember-metal/is_none","ember-runtime/system/string","ember-runtime/system/core_object","ember-runtime/mixins/mutable_enumerable","ember-runtime/mixins/enumerable","ember-runtime/mixins/copyable","ember-runtime/mixins/freezable","ember-metal/error","ember-metal/property_events","ember-metal/mixin","ember-metal/computed","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.isNone, Ember.A
|
|
|
|
var get = __dependency2__.get;
|
|
var set = __dependency3__.set;
|
|
var guidFor = __dependency4__.guidFor;
|
|
var isNone = __dependency5__["default"];
|
|
var fmt = __dependency6__.fmt;
|
|
var CoreObject = __dependency7__["default"];
|
|
var MutableEnumerable = __dependency8__["default"];
|
|
var Enumerable = __dependency9__["default"];
|
|
var Copyable = __dependency10__["default"];
|
|
var Freezable = __dependency11__.Freezable;
|
|
var FROZEN_ERROR = __dependency11__.FROZEN_ERROR;
|
|
var EmberError = __dependency12__["default"];
|
|
var propertyWillChange = __dependency13__.propertyWillChange;
|
|
var propertyDidChange = __dependency13__.propertyDidChange;
|
|
var aliasMethod = __dependency14__.aliasMethod;
|
|
var computed = __dependency15__.computed;
|
|
|
|
/**
|
|
An unordered collection of objects.
|
|
|
|
A Set works a bit like an array except that its items are not ordered. You
|
|
can create a set to efficiently test for membership for an object. You can
|
|
also iterate through a set just like an array, even accessing objects by
|
|
index, however there is no guarantee as to their order.
|
|
|
|
All Sets are observable via the Enumerable Observer API - which works
|
|
on any enumerable object including both Sets and Arrays.
|
|
|
|
## Creating a Set
|
|
|
|
You can create a set like you would most objects using
|
|
`new Ember.Set()`. Most new sets you create will be empty, but you can
|
|
also initialize the set with some content by passing an array or other
|
|
enumerable of objects to the constructor.
|
|
|
|
Finally, you can pass in an existing set and the set will be copied. You
|
|
can also create a copy of a set by calling `Ember.Set#copy()`.
|
|
|
|
```javascript
|
|
// creates a new empty set
|
|
var foundNames = new Ember.Set();
|
|
|
|
// creates a set with four names in it.
|
|
var names = new Ember.Set(["Charles", "Tom", "Juan", "Alex"]); // :P
|
|
|
|
// creates a copy of the names set.
|
|
var namesCopy = new Ember.Set(names);
|
|
|
|
// same as above.
|
|
var anotherNamesCopy = names.copy();
|
|
```
|
|
|
|
## Adding/Removing Objects
|
|
|
|
You generally add or remove objects from a set using `add()` or
|
|
`remove()`. You can add any type of object including primitives such as
|
|
numbers, strings, and booleans.
|
|
|
|
Unlike arrays, objects can only exist one time in a set. If you call `add()`
|
|
on a set with the same object multiple times, the object will only be added
|
|
once. Likewise, calling `remove()` with the same object multiple times will
|
|
remove the object the first time and have no effect on future calls until
|
|
you add the object to the set again.
|
|
|
|
NOTE: You cannot add/remove `null` or `undefined` to a set. Any attempt to do
|
|
so will be ignored.
|
|
|
|
In addition to add/remove you can also call `push()`/`pop()`. Push behaves
|
|
just like `add()` but `pop()`, unlike `remove()` will pick an arbitrary
|
|
object, remove it and return it. This is a good way to use a set as a job
|
|
queue when you don't care which order the jobs are executed in.
|
|
|
|
## Testing for an Object
|
|
|
|
To test for an object's presence in a set you simply call
|
|
`Ember.Set#contains()`.
|
|
|
|
## Observing changes
|
|
|
|
When using `Ember.Set`, you can observe the `"[]"` property to be
|
|
alerted whenever the content changes. You can also add an enumerable
|
|
observer to the set to be notified of specific objects that are added and
|
|
removed from the set. See [Ember.Enumerable](/api/classes/Ember.Enumerable.html)
|
|
for more information on enumerables.
|
|
|
|
This is often unhelpful. If you are filtering sets of objects, for instance,
|
|
it is very inefficient to re-filter all of the items each time the set
|
|
changes. It would be better if you could just adjust the filtered set based
|
|
on what was changed on the original set. The same issue applies to merging
|
|
sets, as well.
|
|
|
|
## Other Methods
|
|
|
|
`Ember.Set` primary implements other mixin APIs. For a complete reference
|
|
on the methods you will use with `Ember.Set`, please consult these mixins.
|
|
The most useful ones will be `Ember.Enumerable` and
|
|
`Ember.MutableEnumerable` which implement most of the common iterator
|
|
methods you are used to on Array.
|
|
|
|
Note that you can also use the `Ember.Copyable` and `Ember.Freezable`
|
|
APIs on `Ember.Set` as well. Once a set is frozen it can no longer be
|
|
modified. The benefit of this is that when you call `frozenCopy()` on it,
|
|
Ember will avoid making copies of the set. This allows you to write
|
|
code that can know with certainty when the underlying set data will or
|
|
will not be modified.
|
|
|
|
@class Set
|
|
@namespace Ember
|
|
@extends Ember.CoreObject
|
|
@uses Ember.MutableEnumerable
|
|
@uses Ember.Copyable
|
|
@uses Ember.Freezable
|
|
@since Ember 0.9
|
|
@deprecated
|
|
*/
|
|
__exports__["default"] = CoreObject.extend(MutableEnumerable, Copyable, Freezable, {
|
|
|
|
// ..........................................................
|
|
// IMPLEMENT ENUMERABLE APIS
|
|
//
|
|
|
|
/**
|
|
This property will change as the number of objects in the set changes.
|
|
|
|
@property length
|
|
@type number
|
|
@default 0
|
|
*/
|
|
length: 0,
|
|
|
|
/**
|
|
Clears the set. This is useful if you want to reuse an existing set
|
|
without having to recreate it.
|
|
|
|
```javascript
|
|
var colors = new Ember.Set(["red", "green", "blue"]);
|
|
colors.length; // 3
|
|
colors.clear();
|
|
colors.length; // 0
|
|
```
|
|
|
|
@method clear
|
|
@return {Ember.Set} An empty Set
|
|
*/
|
|
clear: function() {
|
|
if (this.isFrozen) { throw new EmberError(FROZEN_ERROR); }
|
|
|
|
var len = get(this, 'length');
|
|
if (len === 0) { return this; }
|
|
|
|
var guid;
|
|
|
|
this.enumerableContentWillChange(len, 0);
|
|
propertyWillChange(this, 'firstObject');
|
|
propertyWillChange(this, 'lastObject');
|
|
|
|
for (var i=0; i < len; i++) {
|
|
guid = guidFor(this[i]);
|
|
delete this[guid];
|
|
delete this[i];
|
|
}
|
|
|
|
set(this, 'length', 0);
|
|
|
|
propertyDidChange(this, 'firstObject');
|
|
propertyDidChange(this, 'lastObject');
|
|
this.enumerableContentDidChange(len, 0);
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Returns true if the passed object is also an enumerable that contains the
|
|
same objects as the receiver.
|
|
|
|
```javascript
|
|
var colors = ["red", "green", "blue"],
|
|
same_colors = new Ember.Set(colors);
|
|
|
|
same_colors.isEqual(colors); // true
|
|
same_colors.isEqual(["purple", "brown"]); // false
|
|
```
|
|
|
|
@method isEqual
|
|
@param {Ember.Set} obj the other object.
|
|
@return {Boolean}
|
|
*/
|
|
isEqual: function(obj) {
|
|
// fail fast
|
|
if (!Enumerable.detect(obj)) return false;
|
|
|
|
var loc = get(this, 'length');
|
|
if (get(obj, 'length') !== loc) return false;
|
|
|
|
while(--loc >= 0) {
|
|
if (!obj.contains(this[loc])) return false;
|
|
}
|
|
|
|
return true;
|
|
},
|
|
|
|
/**
|
|
Adds an object to the set. Only non-`null` objects can be added to a set
|
|
and those can only be added once. If the object is already in the set or
|
|
the passed value is null this method will have no effect.
|
|
|
|
This is an alias for `Ember.MutableEnumerable.addObject()`.
|
|
|
|
```javascript
|
|
var colors = new Ember.Set();
|
|
colors.add("blue"); // ["blue"]
|
|
colors.add("blue"); // ["blue"]
|
|
colors.add("red"); // ["blue", "red"]
|
|
colors.add(null); // ["blue", "red"]
|
|
colors.add(undefined); // ["blue", "red"]
|
|
```
|
|
|
|
@method add
|
|
@param {Object} obj The object to add.
|
|
@return {Ember.Set} The set itself.
|
|
*/
|
|
add: aliasMethod('addObject'),
|
|
|
|
/**
|
|
Removes the object from the set if it is found. If you pass a `null` value
|
|
or an object that is already not in the set, this method will have no
|
|
effect. This is an alias for `Ember.MutableEnumerable.removeObject()`.
|
|
|
|
```javascript
|
|
var colors = new Ember.Set(["red", "green", "blue"]);
|
|
colors.remove("red"); // ["blue", "green"]
|
|
colors.remove("purple"); // ["blue", "green"]
|
|
colors.remove(null); // ["blue", "green"]
|
|
```
|
|
|
|
@method remove
|
|
@param {Object} obj The object to remove
|
|
@return {Ember.Set} The set itself.
|
|
*/
|
|
remove: aliasMethod('removeObject'),
|
|
|
|
/**
|
|
Removes the last element from the set and returns it, or `null` if it's empty.
|
|
|
|
```javascript
|
|
var colors = new Ember.Set(["green", "blue"]);
|
|
colors.pop(); // "blue"
|
|
colors.pop(); // "green"
|
|
colors.pop(); // null
|
|
```
|
|
|
|
@method pop
|
|
@return {Object} The removed object from the set or null.
|
|
*/
|
|
pop: function() {
|
|
if (get(this, 'isFrozen')) throw new EmberError(FROZEN_ERROR);
|
|
var obj = this.length > 0 ? this[this.length-1] : null;
|
|
this.remove(obj);
|
|
return obj;
|
|
},
|
|
|
|
/**
|
|
Inserts the given object on to the end of the set. It returns
|
|
the set itself.
|
|
|
|
This is an alias for `Ember.MutableEnumerable.addObject()`.
|
|
|
|
```javascript
|
|
var colors = new Ember.Set();
|
|
colors.push("red"); // ["red"]
|
|
colors.push("green"); // ["red", "green"]
|
|
colors.push("blue"); // ["red", "green", "blue"]
|
|
```
|
|
|
|
@method push
|
|
@return {Ember.Set} The set itself.
|
|
*/
|
|
push: aliasMethod('addObject'),
|
|
|
|
/**
|
|
Removes the last element from the set and returns it, or `null` if it's empty.
|
|
|
|
This is an alias for `Ember.Set.pop()`.
|
|
|
|
```javascript
|
|
var colors = new Ember.Set(["green", "blue"]);
|
|
colors.shift(); // "blue"
|
|
colors.shift(); // "green"
|
|
colors.shift(); // null
|
|
```
|
|
|
|
@method shift
|
|
@return {Object} The removed object from the set or null.
|
|
*/
|
|
shift: aliasMethod('pop'),
|
|
|
|
/**
|
|
Inserts the given object on to the end of the set. It returns
|
|
the set itself.
|
|
|
|
This is an alias of `Ember.Set.push()`
|
|
|
|
```javascript
|
|
var colors = new Ember.Set();
|
|
colors.unshift("red"); // ["red"]
|
|
colors.unshift("green"); // ["red", "green"]
|
|
colors.unshift("blue"); // ["red", "green", "blue"]
|
|
```
|
|
|
|
@method unshift
|
|
@return {Ember.Set} The set itself.
|
|
*/
|
|
unshift: aliasMethod('push'),
|
|
|
|
/**
|
|
Adds each object in the passed enumerable to the set.
|
|
|
|
This is an alias of `Ember.MutableEnumerable.addObjects()`
|
|
|
|
```javascript
|
|
var colors = new Ember.Set();
|
|
colors.addEach(["red", "green", "blue"]); // ["red", "green", "blue"]
|
|
```
|
|
|
|
@method addEach
|
|
@param {Ember.Enumerable} objects the objects to add.
|
|
@return {Ember.Set} The set itself.
|
|
*/
|
|
addEach: aliasMethod('addObjects'),
|
|
|
|
/**
|
|
Removes each object in the passed enumerable to the set.
|
|
|
|
This is an alias of `Ember.MutableEnumerable.removeObjects()`
|
|
|
|
```javascript
|
|
var colors = new Ember.Set(["red", "green", "blue"]);
|
|
colors.removeEach(["red", "blue"]); // ["green"]
|
|
```
|
|
|
|
@method removeEach
|
|
@param {Ember.Enumerable} objects the objects to remove.
|
|
@return {Ember.Set} The set itself.
|
|
*/
|
|
removeEach: aliasMethod('removeObjects'),
|
|
|
|
// ..........................................................
|
|
// PRIVATE ENUMERABLE SUPPORT
|
|
//
|
|
|
|
init: function(items) {
|
|
Ember.deprecate('Ember.Set is deprecated and will be removed in a future release.');
|
|
this._super();
|
|
if (items) this.addObjects(items);
|
|
},
|
|
|
|
// implement Ember.Enumerable
|
|
nextObject: function(idx) {
|
|
return this[idx];
|
|
},
|
|
|
|
// more optimized version
|
|
firstObject: computed(function() {
|
|
return this.length > 0 ? this[0] : undefined;
|
|
}),
|
|
|
|
// more optimized version
|
|
lastObject: computed(function() {
|
|
return this.length > 0 ? this[this.length-1] : undefined;
|
|
}),
|
|
|
|
// implements Ember.MutableEnumerable
|
|
addObject: function(obj) {
|
|
if (get(this, 'isFrozen')) throw new EmberError(FROZEN_ERROR);
|
|
if (isNone(obj)) return this; // nothing to do
|
|
|
|
var guid = guidFor(obj);
|
|
var idx = this[guid];
|
|
var len = get(this, 'length');
|
|
var added;
|
|
|
|
if (idx>=0 && idx<len && (this[idx] === obj)) return this; // added
|
|
|
|
added = [obj];
|
|
|
|
this.enumerableContentWillChange(null, added);
|
|
propertyWillChange(this, 'lastObject');
|
|
|
|
len = get(this, 'length');
|
|
this[guid] = len;
|
|
this[len] = obj;
|
|
set(this, 'length', len+1);
|
|
|
|
propertyDidChange(this, 'lastObject');
|
|
this.enumerableContentDidChange(null, added);
|
|
|
|
return this;
|
|
},
|
|
|
|
// implements Ember.MutableEnumerable
|
|
removeObject: function(obj) {
|
|
if (get(this, 'isFrozen')) throw new EmberError(FROZEN_ERROR);
|
|
if (isNone(obj)) return this; // nothing to do
|
|
|
|
var guid = guidFor(obj);
|
|
var idx = this[guid];
|
|
var len = get(this, 'length');
|
|
var isFirst = idx === 0;
|
|
var isLast = idx === len-1;
|
|
var last, removed;
|
|
|
|
|
|
if (idx>=0 && idx<len && (this[idx] === obj)) {
|
|
removed = [obj];
|
|
|
|
this.enumerableContentWillChange(removed, null);
|
|
if (isFirst) { propertyWillChange(this, 'firstObject'); }
|
|
if (isLast) { propertyWillChange(this, 'lastObject'); }
|
|
|
|
// swap items - basically move the item to the end so it can be removed
|
|
if (idx < len-1) {
|
|
last = this[len-1];
|
|
this[idx] = last;
|
|
this[guidFor(last)] = idx;
|
|
}
|
|
|
|
delete this[guid];
|
|
delete this[len-1];
|
|
set(this, 'length', len-1);
|
|
|
|
if (isFirst) { propertyDidChange(this, 'firstObject'); }
|
|
if (isLast) { propertyDidChange(this, 'lastObject'); }
|
|
this.enumerableContentDidChange(removed, null);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
// optimized version
|
|
contains: function(obj) {
|
|
return this[guidFor(obj)]>=0;
|
|
},
|
|
|
|
copy: function() {
|
|
var C = this.constructor, ret = new C(), loc = get(this, 'length');
|
|
set(ret, 'length', loc);
|
|
while(--loc>=0) {
|
|
ret[loc] = this[loc];
|
|
ret[guidFor(this[loc])] = loc;
|
|
}
|
|
return ret;
|
|
},
|
|
|
|
toString: function() {
|
|
var len = this.length, idx, array = [];
|
|
for(idx = 0; idx < len; idx++) {
|
|
array[idx] = this[idx];
|
|
}
|
|
return fmt("Ember.Set<%@>", [array.join(',')]);
|
|
}
|
|
});
|
|
});
|
|
enifed("ember-runtime/system/string",
|
|
["ember-metal/core","ember-metal/utils","ember-metal/cache","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-runtime
|
|
*/
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.STRINGS, Ember.FEATURES
|
|
var isArray = __dependency2__.isArray;
|
|
var emberInspect = __dependency2__.inspect;
|
|
|
|
var Cache = __dependency3__["default"];
|
|
|
|
var STRING_DASHERIZE_REGEXP = (/[ _]/g);
|
|
|
|
var STRING_DASHERIZE_CACHE = new Cache(1000, function(key) {
|
|
return decamelize(key).replace(STRING_DASHERIZE_REGEXP, '-');
|
|
});
|
|
|
|
var CAMELIZE_CACHE = new Cache(1000, function(key) {
|
|
return key.replace(STRING_CAMELIZE_REGEXP, function(match, separator, chr) {
|
|
return chr ? chr.toUpperCase() : '';
|
|
}).replace(/^([A-Z])/, function(match, separator, chr) {
|
|
return match.toLowerCase();
|
|
});
|
|
});
|
|
|
|
var CLASSIFY_CACHE = new Cache(1000, function(str) {
|
|
var parts = str.split(".");
|
|
var out = [];
|
|
|
|
for (var i=0, l=parts.length; i<l; i++) {
|
|
var camelized = camelize(parts[i]);
|
|
out.push(camelized.charAt(0).toUpperCase() + camelized.substr(1));
|
|
}
|
|
|
|
return out.join(".");
|
|
});
|
|
|
|
var UNDERSCORE_CACHE = new Cache(1000, function(str) {
|
|
return str.replace(STRING_UNDERSCORE_REGEXP_1, '$1_$2').
|
|
replace(STRING_UNDERSCORE_REGEXP_2, '_').toLowerCase();
|
|
});
|
|
|
|
var CAPITALIZE_CACHE = new Cache(1000, function(str) {
|
|
return str.charAt(0).toUpperCase() + str.substr(1);
|
|
});
|
|
|
|
var DECAMELIZE_CACHE = new Cache(1000, function(str) {
|
|
return str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase();
|
|
});
|
|
|
|
var STRING_DECAMELIZE_REGEXP = (/([a-z\d])([A-Z])/g);
|
|
var STRING_CAMELIZE_REGEXP = (/(\-|_|\.|\s)+(.)?/g);
|
|
var STRING_UNDERSCORE_REGEXP_1 = (/([a-z\d])([A-Z]+)/g);
|
|
var STRING_UNDERSCORE_REGEXP_2 = (/\-|\s+/g);
|
|
|
|
function fmt(str, formats) {
|
|
var cachedFormats = formats;
|
|
|
|
if (!isArray(cachedFormats) || arguments.length > 2) {
|
|
cachedFormats = new Array(arguments.length - 1);
|
|
|
|
for (var i = 1, l = arguments.length; i < l; i++) {
|
|
cachedFormats[i - 1] = arguments[i];
|
|
}
|
|
}
|
|
|
|
// first, replace any ORDERED replacements.
|
|
var idx = 0; // the current index for non-numerical replacements
|
|
return str.replace(/%@([0-9]+)?/g, function(s, argIndex) {
|
|
argIndex = (argIndex) ? parseInt(argIndex, 10) - 1 : idx++;
|
|
s = cachedFormats[argIndex];
|
|
return (s === null) ? '(null)' : (s === undefined) ? '' : emberInspect(s);
|
|
});
|
|
}
|
|
|
|
function loc(str, formats) {
|
|
if (!isArray(formats) || arguments.length > 2) {
|
|
formats = Array.prototype.slice.call(arguments, 1);
|
|
}
|
|
|
|
str = Ember.STRINGS[str] || str;
|
|
return fmt(str, formats);
|
|
}
|
|
|
|
function w(str) {
|
|
return str.split(/\s+/);
|
|
}
|
|
|
|
function decamelize(str) {
|
|
return DECAMELIZE_CACHE.get(str);
|
|
}
|
|
|
|
function dasherize(str) {
|
|
return STRING_DASHERIZE_CACHE.get(str);
|
|
}
|
|
|
|
function camelize(str) {
|
|
return CAMELIZE_CACHE.get(str);
|
|
}
|
|
|
|
function classify(str) {
|
|
return CLASSIFY_CACHE.get(str);
|
|
}
|
|
|
|
function underscore(str) {
|
|
return UNDERSCORE_CACHE.get(str);
|
|
}
|
|
|
|
function capitalize(str) {
|
|
return CAPITALIZE_CACHE.get(str);
|
|
}
|
|
|
|
/**
|
|
Defines the hash of localized strings for the current language. Used by
|
|
the `Ember.String.loc()` helper. To localize, add string values to this
|
|
hash.
|
|
|
|
@property STRINGS
|
|
@for Ember
|
|
@type Hash
|
|
*/
|
|
Ember.STRINGS = {};
|
|
|
|
/**
|
|
Defines string helper methods including string formatting and localization.
|
|
Unless `Ember.EXTEND_PROTOTYPES.String` is `false` these methods will also be
|
|
added to the `String.prototype` as well.
|
|
|
|
@class String
|
|
@namespace Ember
|
|
@static
|
|
*/
|
|
__exports__["default"] = {
|
|
/**
|
|
Apply formatting options to the string. This will look for occurrences
|
|
of "%@" in your string and substitute them with the arguments you pass into
|
|
this method. If you want to control the specific order of replacement,
|
|
you can add a number after the key as well to indicate which argument
|
|
you want to insert.
|
|
|
|
Ordered insertions are most useful when building loc strings where values
|
|
you need to insert may appear in different orders.
|
|
|
|
```javascript
|
|
"Hello %@ %@".fmt('John', 'Doe'); // "Hello John Doe"
|
|
"Hello %@2, %@1".fmt('John', 'Doe'); // "Hello Doe, John"
|
|
```
|
|
|
|
@method fmt
|
|
@param {String} str The string to format
|
|
@param {Array} formats An array of parameters to interpolate into string.
|
|
@return {String} formatted string
|
|
*/
|
|
fmt: fmt,
|
|
|
|
/**
|
|
Formats the passed string, but first looks up the string in the localized
|
|
strings hash. This is a convenient way to localize text. See
|
|
`Ember.String.fmt()` for more information on formatting.
|
|
|
|
Note that it is traditional but not required to prefix localized string
|
|
keys with an underscore or other character so you can easily identify
|
|
localized strings.
|
|
|
|
```javascript
|
|
Ember.STRINGS = {
|
|
'_Hello World': 'Bonjour le monde',
|
|
'_Hello %@ %@': 'Bonjour %@ %@'
|
|
};
|
|
|
|
Ember.String.loc("_Hello World"); // 'Bonjour le monde';
|
|
Ember.String.loc("_Hello %@ %@", ["John", "Smith"]); // "Bonjour John Smith";
|
|
```
|
|
|
|
@method loc
|
|
@param {String} str The string to format
|
|
@param {Array} formats Optional array of parameters to interpolate into string.
|
|
@return {String} formatted string
|
|
*/
|
|
loc: loc,
|
|
|
|
/**
|
|
Splits a string into separate units separated by spaces, eliminating any
|
|
empty strings in the process. This is a convenience method for split that
|
|
is mostly useful when applied to the `String.prototype`.
|
|
|
|
```javascript
|
|
Ember.String.w("alpha beta gamma").forEach(function(key) {
|
|
console.log(key);
|
|
});
|
|
|
|
// > alpha
|
|
// > beta
|
|
// > gamma
|
|
```
|
|
|
|
@method w
|
|
@param {String} str The string to split
|
|
@return {Array} array containing the split strings
|
|
*/
|
|
w: w,
|
|
|
|
/**
|
|
Converts a camelized string into all lower case separated by underscores.
|
|
|
|
```javascript
|
|
'innerHTML'.decamelize(); // 'inner_html'
|
|
'action_name'.decamelize(); // 'action_name'
|
|
'css-class-name'.decamelize(); // 'css-class-name'
|
|
'my favorite items'.decamelize(); // 'my favorite items'
|
|
```
|
|
|
|
@method decamelize
|
|
@param {String} str The string to decamelize.
|
|
@return {String} the decamelized string.
|
|
*/
|
|
decamelize: decamelize,
|
|
|
|
/**
|
|
Replaces underscores, spaces, or camelCase with dashes.
|
|
|
|
```javascript
|
|
'innerHTML'.dasherize(); // 'inner-html'
|
|
'action_name'.dasherize(); // 'action-name'
|
|
'css-class-name'.dasherize(); // 'css-class-name'
|
|
'my favorite items'.dasherize(); // 'my-favorite-items'
|
|
```
|
|
|
|
@method dasherize
|
|
@param {String} str The string to dasherize.
|
|
@return {String} the dasherized string.
|
|
*/
|
|
dasherize: dasherize,
|
|
|
|
/**
|
|
Returns the lowerCamelCase form of a string.
|
|
|
|
```javascript
|
|
'innerHTML'.camelize(); // 'innerHTML'
|
|
'action_name'.camelize(); // 'actionName'
|
|
'css-class-name'.camelize(); // 'cssClassName'
|
|
'my favorite items'.camelize(); // 'myFavoriteItems'
|
|
'My Favorite Items'.camelize(); // 'myFavoriteItems'
|
|
```
|
|
|
|
@method camelize
|
|
@param {String} str The string to camelize.
|
|
@return {String} the camelized string.
|
|
*/
|
|
camelize: camelize,
|
|
|
|
/**
|
|
Returns the UpperCamelCase form of a string.
|
|
|
|
```javascript
|
|
'innerHTML'.classify(); // 'InnerHTML'
|
|
'action_name'.classify(); // 'ActionName'
|
|
'css-class-name'.classify(); // 'CssClassName'
|
|
'my favorite items'.classify(); // 'MyFavoriteItems'
|
|
```
|
|
|
|
@method classify
|
|
@param {String} str the string to classify
|
|
@return {String} the classified string
|
|
*/
|
|
classify: classify,
|
|
|
|
/**
|
|
More general than decamelize. Returns the lower\_case\_and\_underscored
|
|
form of a string.
|
|
|
|
```javascript
|
|
'innerHTML'.underscore(); // 'inner_html'
|
|
'action_name'.underscore(); // 'action_name'
|
|
'css-class-name'.underscore(); // 'css_class_name'
|
|
'my favorite items'.underscore(); // 'my_favorite_items'
|
|
```
|
|
|
|
@method underscore
|
|
@param {String} str The string to underscore.
|
|
@return {String} the underscored string.
|
|
*/
|
|
underscore: underscore,
|
|
|
|
/**
|
|
Returns the Capitalized form of a string
|
|
|
|
```javascript
|
|
'innerHTML'.capitalize() // 'InnerHTML'
|
|
'action_name'.capitalize() // 'Action_name'
|
|
'css-class-name'.capitalize() // 'Css-class-name'
|
|
'my favorite items'.capitalize() // 'My favorite items'
|
|
```
|
|
|
|
@method capitalize
|
|
@param {String} str The string to capitalize.
|
|
@return {String} The capitalized string.
|
|
*/
|
|
capitalize: capitalize
|
|
};
|
|
|
|
__exports__.fmt = fmt;
|
|
__exports__.loc = loc;
|
|
__exports__.w = w;
|
|
__exports__.decamelize = decamelize;
|
|
__exports__.dasherize = dasherize;
|
|
__exports__.camelize = camelize;
|
|
__exports__.classify = classify;
|
|
__exports__.underscore = underscore;
|
|
__exports__.capitalize = capitalize;
|
|
});
|
|
enifed("ember-runtime/system/subarray",
|
|
["ember-metal/error","ember-metal/enumerable_utils","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var EmberError = __dependency1__["default"];
|
|
var EnumerableUtils = __dependency2__["default"];
|
|
|
|
var RETAIN = 'r';
|
|
var FILTER = 'f';
|
|
|
|
function Operation(type, count) {
|
|
this.type = type;
|
|
this.count = count;
|
|
}
|
|
|
|
__exports__["default"] = SubArray;
|
|
|
|
/**
|
|
An `Ember.SubArray` tracks an array in a way similar to, but more specialized
|
|
than, `Ember.TrackedArray`. It is useful for keeping track of the indexes of
|
|
items within a filtered array.
|
|
|
|
@class SubArray
|
|
@namespace Ember
|
|
*/
|
|
function SubArray (length) {
|
|
if (arguments.length < 1) { length = 0; }
|
|
|
|
if (length > 0) {
|
|
this._operations = [new Operation(RETAIN, length)];
|
|
} else {
|
|
this._operations = [];
|
|
}
|
|
}
|
|
|
|
|
|
SubArray.prototype = {
|
|
/**
|
|
Track that an item was added to the tracked array.
|
|
|
|
@method addItem
|
|
|
|
@param {Number} index The index of the item in the tracked array.
|
|
@param {Boolean} match `true` iff the item is included in the subarray.
|
|
|
|
@return {number} The index of the item in the subarray.
|
|
*/
|
|
addItem: function(index, match) {
|
|
var returnValue = -1;
|
|
var itemType = match ? RETAIN : FILTER;
|
|
var self = this;
|
|
|
|
this._findOperation(index, function(operation, operationIndex, rangeStart, rangeEnd, seenInSubArray) {
|
|
var newOperation, splitOperation;
|
|
|
|
if (itemType === operation.type) {
|
|
++operation.count;
|
|
} else if (index === rangeStart) {
|
|
// insert to the left of `operation`
|
|
self._operations.splice(operationIndex, 0, new Operation(itemType, 1));
|
|
} else {
|
|
newOperation = new Operation(itemType, 1);
|
|
splitOperation = new Operation(operation.type, rangeEnd - index + 1);
|
|
operation.count = index - rangeStart;
|
|
|
|
self._operations.splice(operationIndex + 1, 0, newOperation, splitOperation);
|
|
}
|
|
|
|
if (match) {
|
|
if (operation.type === RETAIN) {
|
|
returnValue = seenInSubArray + (index - rangeStart);
|
|
} else {
|
|
returnValue = seenInSubArray;
|
|
}
|
|
}
|
|
|
|
self._composeAt(operationIndex);
|
|
}, function(seenInSubArray) {
|
|
self._operations.push(new Operation(itemType, 1));
|
|
|
|
if (match) {
|
|
returnValue = seenInSubArray;
|
|
}
|
|
|
|
self._composeAt(self._operations.length-1);
|
|
});
|
|
|
|
return returnValue;
|
|
},
|
|
|
|
/**
|
|
Track that an item was removed from the tracked array.
|
|
|
|
@method removeItem
|
|
|
|
@param {Number} index The index of the item in the tracked array.
|
|
|
|
@return {number} The index of the item in the subarray, or `-1` if the item
|
|
was not in the subarray.
|
|
*/
|
|
removeItem: function(index) {
|
|
var returnValue = -1;
|
|
var self = this;
|
|
|
|
this._findOperation(index, function (operation, operationIndex, rangeStart, rangeEnd, seenInSubArray) {
|
|
if (operation.type === RETAIN) {
|
|
returnValue = seenInSubArray + (index - rangeStart);
|
|
}
|
|
|
|
if (operation.count > 1) {
|
|
--operation.count;
|
|
} else {
|
|
self._operations.splice(operationIndex, 1);
|
|
self._composeAt(operationIndex);
|
|
}
|
|
}, function() {
|
|
throw new EmberError("Can't remove an item that has never been added.");
|
|
});
|
|
|
|
return returnValue;
|
|
},
|
|
|
|
|
|
_findOperation: function (index, foundCallback, notFoundCallback) {
|
|
var seenInSubArray = 0;
|
|
var operationIndex, len, operation, rangeStart, rangeEnd;
|
|
|
|
// OPTIMIZE: change to balanced tree
|
|
// find leftmost operation to the right of `index`
|
|
for (operationIndex = rangeStart = 0, len = this._operations.length; operationIndex < len; rangeStart = rangeEnd + 1, ++operationIndex) {
|
|
operation = this._operations[operationIndex];
|
|
rangeEnd = rangeStart + operation.count - 1;
|
|
|
|
if (index >= rangeStart && index <= rangeEnd) {
|
|
foundCallback(operation, operationIndex, rangeStart, rangeEnd, seenInSubArray);
|
|
return;
|
|
} else if (operation.type === RETAIN) {
|
|
seenInSubArray += operation.count;
|
|
}
|
|
}
|
|
|
|
notFoundCallback(seenInSubArray);
|
|
},
|
|
|
|
_composeAt: function(index) {
|
|
var op = this._operations[index];
|
|
var otherOp;
|
|
|
|
if (!op) {
|
|
// Composing out of bounds is a no-op, as when removing the last operation
|
|
// in the list.
|
|
return;
|
|
}
|
|
|
|
if (index > 0) {
|
|
otherOp = this._operations[index-1];
|
|
if (otherOp.type === op.type) {
|
|
op.count += otherOp.count;
|
|
this._operations.splice(index-1, 1);
|
|
--index;
|
|
}
|
|
}
|
|
|
|
if (index < this._operations.length-1) {
|
|
otherOp = this._operations[index+1];
|
|
if (otherOp.type === op.type) {
|
|
op.count += otherOp.count;
|
|
this._operations.splice(index+1, 1);
|
|
}
|
|
}
|
|
},
|
|
|
|
toString: function () {
|
|
var str = "";
|
|
EnumerableUtils.forEach(this._operations, function (operation) {
|
|
str += " " + operation.type + ":" + operation.count;
|
|
});
|
|
return str.substring(1);
|
|
}
|
|
};
|
|
});
|
|
enifed("ember-runtime/system/tracked_array",
|
|
["ember-metal/property_get","ember-metal/enumerable_utils","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var get = __dependency1__.get;
|
|
var forEach = __dependency2__.forEach;
|
|
|
|
var RETAIN = 'r';
|
|
var INSERT = 'i';
|
|
var DELETE = 'd';
|
|
|
|
__exports__["default"] = TrackedArray;
|
|
|
|
/**
|
|
An `Ember.TrackedArray` tracks array operations. It's useful when you want to
|
|
lazily compute the indexes of items in an array after they've been shifted by
|
|
subsequent operations.
|
|
|
|
@class TrackedArray
|
|
@namespace Ember
|
|
@param {Array} [items=[]] The array to be tracked. This is used just to get
|
|
the initial items for the starting state of retain:n.
|
|
*/
|
|
function TrackedArray(items) {
|
|
if (arguments.length < 1) { items = []; }
|
|
|
|
var length = get(items, 'length');
|
|
|
|
if (length) {
|
|
this._operations = [new ArrayOperation(RETAIN, length, items)];
|
|
} else {
|
|
this._operations = [];
|
|
}
|
|
}
|
|
|
|
TrackedArray.RETAIN = RETAIN;
|
|
TrackedArray.INSERT = INSERT;
|
|
TrackedArray.DELETE = DELETE;
|
|
|
|
TrackedArray.prototype = {
|
|
|
|
/**
|
|
Track that `newItems` were added to the tracked array at `index`.
|
|
|
|
@method addItems
|
|
@param index
|
|
@param newItems
|
|
*/
|
|
addItems: function (index, newItems) {
|
|
var count = get(newItems, 'length');
|
|
if (count < 1) { return; }
|
|
|
|
var match = this._findArrayOperation(index);
|
|
var arrayOperation = match.operation;
|
|
var arrayOperationIndex = match.index;
|
|
var arrayOperationRangeStart = match.rangeStart;
|
|
var composeIndex, newArrayOperation;
|
|
|
|
newArrayOperation = new ArrayOperation(INSERT, count, newItems);
|
|
|
|
if (arrayOperation) {
|
|
if (!match.split) {
|
|
// insert left of arrayOperation
|
|
this._operations.splice(arrayOperationIndex, 0, newArrayOperation);
|
|
composeIndex = arrayOperationIndex;
|
|
} else {
|
|
this._split(arrayOperationIndex, index - arrayOperationRangeStart, newArrayOperation);
|
|
composeIndex = arrayOperationIndex + 1;
|
|
}
|
|
} else {
|
|
// insert at end
|
|
this._operations.push(newArrayOperation);
|
|
composeIndex = arrayOperationIndex;
|
|
}
|
|
|
|
this._composeInsert(composeIndex);
|
|
},
|
|
|
|
/**
|
|
Track that `count` items were removed at `index`.
|
|
|
|
@method removeItems
|
|
@param index
|
|
@param count
|
|
*/
|
|
removeItems: function (index, count) {
|
|
if (count < 1) { return; }
|
|
|
|
var match = this._findArrayOperation(index);
|
|
var arrayOperationIndex = match.index;
|
|
var arrayOperationRangeStart = match.rangeStart;
|
|
var newArrayOperation, composeIndex;
|
|
|
|
newArrayOperation = new ArrayOperation(DELETE, count);
|
|
if (!match.split) {
|
|
// insert left of arrayOperation
|
|
this._operations.splice(arrayOperationIndex, 0, newArrayOperation);
|
|
composeIndex = arrayOperationIndex;
|
|
} else {
|
|
this._split(arrayOperationIndex, index - arrayOperationRangeStart, newArrayOperation);
|
|
composeIndex = arrayOperationIndex + 1;
|
|
}
|
|
|
|
return this._composeDelete(composeIndex);
|
|
},
|
|
|
|
/**
|
|
Apply all operations, reducing them to retain:n, for `n`, the number of
|
|
items in the array.
|
|
|
|
`callback` will be called for each operation and will be passed the following arguments:
|
|
|
|
* {array} items The items for the given operation
|
|
* {number} offset The computed offset of the items, ie the index in the
|
|
array of the first item for this operation.
|
|
* {string} operation The type of the operation. One of
|
|
`Ember.TrackedArray.{RETAIN, DELETE, INSERT}`
|
|
|
|
@method apply
|
|
@param {Function} callback
|
|
*/
|
|
apply: function (callback) {
|
|
var items = [];
|
|
var offset = 0;
|
|
|
|
forEach(this._operations, function (arrayOperation, operationIndex) {
|
|
callback(arrayOperation.items, offset, arrayOperation.type, operationIndex);
|
|
|
|
if (arrayOperation.type !== DELETE) {
|
|
offset += arrayOperation.count;
|
|
items = items.concat(arrayOperation.items);
|
|
}
|
|
});
|
|
|
|
this._operations = [new ArrayOperation(RETAIN, items.length, items)];
|
|
},
|
|
|
|
/**
|
|
Return an `ArrayOperationMatch` for the operation that contains the item at `index`.
|
|
|
|
@method _findArrayOperation
|
|
|
|
@param {Number} index the index of the item whose operation information
|
|
should be returned.
|
|
@private
|
|
*/
|
|
_findArrayOperation: function (index) {
|
|
var split = false;
|
|
var arrayOperationIndex, arrayOperation,
|
|
arrayOperationRangeStart, arrayOperationRangeEnd,
|
|
len;
|
|
|
|
// OPTIMIZE: we could search these faster if we kept a balanced tree.
|
|
// find leftmost arrayOperation to the right of `index`
|
|
for (arrayOperationIndex = arrayOperationRangeStart = 0, len = this._operations.length; arrayOperationIndex < len; ++arrayOperationIndex) {
|
|
arrayOperation = this._operations[arrayOperationIndex];
|
|
|
|
if (arrayOperation.type === DELETE) { continue; }
|
|
|
|
arrayOperationRangeEnd = arrayOperationRangeStart + arrayOperation.count - 1;
|
|
|
|
if (index === arrayOperationRangeStart) {
|
|
break;
|
|
} else if (index > arrayOperationRangeStart && index <= arrayOperationRangeEnd) {
|
|
split = true;
|
|
break;
|
|
} else {
|
|
arrayOperationRangeStart = arrayOperationRangeEnd + 1;
|
|
}
|
|
}
|
|
|
|
return new ArrayOperationMatch(arrayOperation, arrayOperationIndex, split, arrayOperationRangeStart);
|
|
},
|
|
|
|
_split: function (arrayOperationIndex, splitIndex, newArrayOperation) {
|
|
var arrayOperation = this._operations[arrayOperationIndex];
|
|
var splitItems = arrayOperation.items.slice(splitIndex);
|
|
var splitArrayOperation = new ArrayOperation(arrayOperation.type, splitItems.length, splitItems);
|
|
|
|
// truncate LHS
|
|
arrayOperation.count = splitIndex;
|
|
arrayOperation.items = arrayOperation.items.slice(0, splitIndex);
|
|
|
|
this._operations.splice(arrayOperationIndex + 1, 0, newArrayOperation, splitArrayOperation);
|
|
},
|
|
|
|
// see SubArray for a better implementation.
|
|
_composeInsert: function (index) {
|
|
var newArrayOperation = this._operations[index];
|
|
var leftArrayOperation = this._operations[index-1]; // may be undefined
|
|
var rightArrayOperation = this._operations[index+1]; // may be undefined
|
|
var leftOp = leftArrayOperation && leftArrayOperation.type;
|
|
var rightOp = rightArrayOperation && rightArrayOperation.type;
|
|
|
|
if (leftOp === INSERT) {
|
|
// merge left
|
|
leftArrayOperation.count += newArrayOperation.count;
|
|
leftArrayOperation.items = leftArrayOperation.items.concat(newArrayOperation.items);
|
|
|
|
if (rightOp === INSERT) {
|
|
// also merge right (we have split an insert with an insert)
|
|
leftArrayOperation.count += rightArrayOperation.count;
|
|
leftArrayOperation.items = leftArrayOperation.items.concat(rightArrayOperation.items);
|
|
this._operations.splice(index, 2);
|
|
} else {
|
|
// only merge left
|
|
this._operations.splice(index, 1);
|
|
}
|
|
} else if (rightOp === INSERT) {
|
|
// merge right
|
|
newArrayOperation.count += rightArrayOperation.count;
|
|
newArrayOperation.items = newArrayOperation.items.concat(rightArrayOperation.items);
|
|
this._operations.splice(index + 1, 1);
|
|
}
|
|
},
|
|
|
|
_composeDelete: function (index) {
|
|
var arrayOperation = this._operations[index];
|
|
var deletesToGo = arrayOperation.count;
|
|
var leftArrayOperation = this._operations[index-1]; // may be undefined
|
|
var leftOp = leftArrayOperation && leftArrayOperation.type;
|
|
var nextArrayOperation;
|
|
var nextOp;
|
|
var nextCount;
|
|
var removeNewAndNextOp = false;
|
|
var removedItems = [];
|
|
|
|
if (leftOp === DELETE) {
|
|
arrayOperation = leftArrayOperation;
|
|
index -= 1;
|
|
}
|
|
|
|
for (var i = index + 1; deletesToGo > 0; ++i) {
|
|
nextArrayOperation = this._operations[i];
|
|
nextOp = nextArrayOperation.type;
|
|
nextCount = nextArrayOperation.count;
|
|
|
|
if (nextOp === DELETE) {
|
|
arrayOperation.count += nextCount;
|
|
continue;
|
|
}
|
|
|
|
if (nextCount > deletesToGo) {
|
|
// d:2 {r,i}:5 we reduce the retain or insert, but it stays
|
|
removedItems = removedItems.concat(nextArrayOperation.items.splice(0, deletesToGo));
|
|
nextArrayOperation.count -= deletesToGo;
|
|
|
|
// In the case where we truncate the last arrayOperation, we don't need to
|
|
// remove it; also the deletesToGo reduction is not the entirety of
|
|
// nextCount
|
|
i -= 1;
|
|
nextCount = deletesToGo;
|
|
|
|
deletesToGo = 0;
|
|
} else {
|
|
if (nextCount === deletesToGo) {
|
|
// Handle edge case of d:2 i:2 in which case both operations go away
|
|
// during composition.
|
|
removeNewAndNextOp = true;
|
|
}
|
|
removedItems = removedItems.concat(nextArrayOperation.items);
|
|
deletesToGo -= nextCount;
|
|
}
|
|
|
|
if (nextOp === INSERT) {
|
|
// d:2 i:3 will result in delete going away
|
|
arrayOperation.count -= nextCount;
|
|
}
|
|
}
|
|
|
|
if (arrayOperation.count > 0) {
|
|
// compose our new delete with possibly several operations to the right of
|
|
// disparate types
|
|
this._operations.splice(index+1, i-1-index);
|
|
} else {
|
|
// The delete operation can go away; it has merely reduced some other
|
|
// operation, as in d:3 i:4; it may also have eliminated that operation,
|
|
// as in d:3 i:3.
|
|
this._operations.splice(index, removeNewAndNextOp ? 2 : 1);
|
|
}
|
|
|
|
return removedItems;
|
|
},
|
|
|
|
toString: function () {
|
|
var str = "";
|
|
forEach(this._operations, function (operation) {
|
|
str += " " + operation.type + ":" + operation.count;
|
|
});
|
|
return str.substring(1);
|
|
}
|
|
};
|
|
|
|
/**
|
|
Internal data structure to represent an array operation.
|
|
|
|
@method ArrayOperation
|
|
@private
|
|
@param {String} type The type of the operation. One of
|
|
`Ember.TrackedArray.{RETAIN, INSERT, DELETE}`
|
|
@param {Number} count The number of items in this operation.
|
|
@param {Array} items The items of the operation, if included. RETAIN and
|
|
INSERT include their items, DELETE does not.
|
|
*/
|
|
function ArrayOperation (operation, count, items) {
|
|
this.type = operation; // RETAIN | INSERT | DELETE
|
|
this.count = count;
|
|
this.items = items;
|
|
}
|
|
|
|
/**
|
|
Internal data structure used to include information when looking up operations
|
|
by item index.
|
|
|
|
@method ArrayOperationMatch
|
|
@private
|
|
@param {ArrayOperation} operation
|
|
@param {Number} index The index of `operation` in the array of operations.
|
|
@param {Boolean} split Whether or not the item index searched for would
|
|
require a split for a new operation type.
|
|
@param {Number} rangeStart The index of the first item in the operation,
|
|
with respect to the tracked array. The index of the last item can be computed
|
|
from `rangeStart` and `operation.count`.
|
|
*/
|
|
function ArrayOperationMatch(operation, index, split, rangeStart) {
|
|
this.operation = operation;
|
|
this.index = index;
|
|
this.split = split;
|
|
this.rangeStart = rangeStart;
|
|
}
|
|
});
|
|
enifed("ember-template-compiler",
|
|
["ember-metal/core","ember-template-compiler/system/precompile","ember-template-compiler/system/compile","ember-template-compiler/system/template","ember-template-compiler/plugins","ember-template-compiler/plugins/transform-each-in-to-hash","ember-template-compiler/plugins/transform-with-as-to-hash","ember-template-compiler/compat","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) {
|
|
"use strict";
|
|
var _Ember = __dependency1__["default"];
|
|
var precompile = __dependency2__["default"];
|
|
var compile = __dependency3__["default"];
|
|
var template = __dependency4__["default"];
|
|
var registerPlugin = __dependency5__.registerPlugin;
|
|
|
|
var TransformEachInToHash = __dependency6__["default"];
|
|
var TransformWithAsToHash = __dependency7__["default"];
|
|
|
|
// used for adding Ember.Handlebars.compile for backwards compat
|
|
|
|
registerPlugin('ast', TransformWithAsToHash);
|
|
registerPlugin('ast', TransformEachInToHash);
|
|
|
|
__exports__._Ember = _Ember;
|
|
__exports__.precompile = precompile;
|
|
__exports__.compile = compile;
|
|
__exports__.template = template;
|
|
__exports__.registerPlugin = registerPlugin;
|
|
});
|
|
enifed("ember-template-compiler/compat",
|
|
["ember-metal/core","ember-template-compiler/compat/precompile","ember-template-compiler/system/compile","ember-template-compiler/system/template"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
var precompile = __dependency2__["default"];
|
|
var compile = __dependency3__["default"];
|
|
var template = __dependency4__["default"];
|
|
|
|
var EmberHandlebars = Ember.Handlebars = Ember.Handlebars || {};
|
|
|
|
EmberHandlebars.precompile = precompile;
|
|
EmberHandlebars.compile = compile;
|
|
EmberHandlebars.template = template;
|
|
});
|
|
enifed("ember-template-compiler/compat/precompile",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-template-compiler
|
|
*/
|
|
|
|
var compile, compileSpec;
|
|
|
|
__exports__["default"] = function(string) {
|
|
if ((!compile || !compileSpec) && Ember.__loader.registry['htmlbars-compiler/compiler']) {
|
|
var Compiler = requireModule('htmlbars-compiler/compiler');
|
|
|
|
compile = Compiler.compile;
|
|
compileSpec = Compiler.compileSpec;
|
|
}
|
|
|
|
if (!compile || !compileSpec) {
|
|
throw new Error('Cannot call `precompile` without the template compiler loaded. Please load `ember-template-compiler.js` prior to calling `precompile`.');
|
|
}
|
|
|
|
var asObject = arguments[1] === undefined ? true : arguments[1];
|
|
var compileFunc = asObject ? compile : compileSpec;
|
|
|
|
return compileFunc(string);
|
|
}
|
|
});
|
|
enifed("ember-template-compiler/plugins",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-template-compiler
|
|
*/
|
|
|
|
/**
|
|
@private
|
|
@property helpers
|
|
*/
|
|
var plugins = {
|
|
ast: [ ]
|
|
};
|
|
|
|
/**
|
|
Adds an AST plugin to be used by Ember.HTMLBars.compile.
|
|
|
|
@private
|
|
@method registerASTPlugin
|
|
*/
|
|
function registerPlugin(type, Plugin) {
|
|
if (!plugins[type]) {
|
|
throw new Error('Attempting to register "' + Plugin + '" as "' + type + '" which is not a valid HTMLBars plugin type.');
|
|
}
|
|
|
|
plugins[type].push(Plugin);
|
|
}
|
|
|
|
__exports__.registerPlugin = registerPlugin;__exports__["default"] = plugins;
|
|
});
|
|
enifed("ember-template-compiler/plugins/transform-each-in-to-hash",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
|
|
/**
|
|
An HTMLBars AST transformation that replaces all instances of
|
|
|
|
```handlebars
|
|
{{#each item in items}}
|
|
{{/each}}
|
|
```
|
|
|
|
with
|
|
|
|
```handlebars
|
|
{{#each items keyword="item"}}
|
|
{{/each}}
|
|
```
|
|
|
|
@class TransformEachInToHash
|
|
@private
|
|
*/
|
|
function TransformEachInToHash() {
|
|
// set later within HTMLBars to the syntax package
|
|
this.syntax = null;
|
|
}
|
|
|
|
/**
|
|
@private
|
|
@method transform
|
|
@param {AST} The AST to be transformed.
|
|
*/
|
|
TransformEachInToHash.prototype.transform = function TransformEachInToHash_transform(ast) {
|
|
var pluginContext = this;
|
|
var walker = new pluginContext.syntax.Walker();
|
|
var b = pluginContext.syntax.builders;
|
|
|
|
walker.visit(ast, function(node) {
|
|
if (pluginContext.validate(node)) {
|
|
|
|
if (node.program && node.program.blockParams.length) {
|
|
throw new Error('You cannot use keyword (`{{each foo in bar}}`) and block params (`{{each bar as |foo|}}`) at the same time.');
|
|
}
|
|
|
|
var removedParams = node.sexpr.params.splice(0, 2);
|
|
var keyword = removedParams[0].original;
|
|
|
|
// TODO: This may not be necessary.
|
|
if (!node.sexpr.hash) {
|
|
node.sexpr.hash = b.hash();
|
|
}
|
|
|
|
node.sexpr.hash.pairs.push(b.pair(
|
|
'keyword',
|
|
b.string(keyword)
|
|
));
|
|
}
|
|
});
|
|
|
|
return ast;
|
|
};
|
|
|
|
TransformEachInToHash.prototype.validate = function TransformEachInToHash_validate(node) {
|
|
return (node.type === 'BlockStatement' || node.type === 'MustacheStatement') &&
|
|
node.sexpr.path.original === 'each' &&
|
|
node.sexpr.params.length === 3 &&
|
|
node.sexpr.params[1].type === 'PathExpression' &&
|
|
node.sexpr.params[1].original === 'in';
|
|
};
|
|
|
|
__exports__["default"] = TransformEachInToHash;
|
|
});
|
|
enifed("ember-template-compiler/plugins/transform-with-as-to-hash",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
/**
|
|
An HTMLBars AST transformation that replaces all instances of
|
|
|
|
```handlebars
|
|
{{#with foo.bar as bar}}
|
|
{{/with}}
|
|
```
|
|
|
|
with
|
|
|
|
```handlebars
|
|
{{#with foo.bar as |bar|}}
|
|
{{/with}}
|
|
```
|
|
|
|
@private
|
|
@class TransformWithAsToHash
|
|
*/
|
|
function TransformWithAsToHash() {
|
|
// set later within HTMLBars to the syntax package
|
|
this.syntax = null;
|
|
}
|
|
|
|
/**
|
|
@private
|
|
@method transform
|
|
@param {AST} The AST to be transformed.
|
|
*/
|
|
TransformWithAsToHash.prototype.transform = function TransformWithAsToHash_transform(ast) {
|
|
var pluginContext = this;
|
|
var walker = new pluginContext.syntax.Walker();
|
|
|
|
walker.visit(ast, function(node) {
|
|
if (pluginContext.validate(node)) {
|
|
|
|
if (node.program && node.program.blockParams.length) {
|
|
throw new Error('You cannot use keyword (`{{with foo as bar}}`) and block params (`{{with foo as |bar|}}`) at the same time.');
|
|
}
|
|
|
|
var removedParams = node.sexpr.params.splice(1, 2);
|
|
var keyword = removedParams[1].original;
|
|
node.program.blockParams = [ keyword ];
|
|
}
|
|
});
|
|
|
|
return ast;
|
|
};
|
|
|
|
TransformWithAsToHash.prototype.validate = function TransformWithAsToHash_validate(node) {
|
|
return node.type === 'BlockStatement' &&
|
|
node.sexpr.path.original === 'with' &&
|
|
node.sexpr.params.length === 3 &&
|
|
node.sexpr.params[1].type === 'PathExpression' &&
|
|
node.sexpr.params[1].original === 'as';
|
|
};
|
|
|
|
__exports__["default"] = TransformWithAsToHash;
|
|
});
|
|
enifed("ember-template-compiler/system/compile",
|
|
["ember-template-compiler/system/compile_options","ember-template-compiler/system/template","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-template-compiler
|
|
*/
|
|
|
|
var compile;
|
|
var compileOptions = __dependency1__["default"];
|
|
var template = __dependency2__["default"];
|
|
|
|
/**
|
|
Uses HTMLBars `compile` function to process a string into a compiled template.
|
|
|
|
This is not present in production builds.
|
|
|
|
@private
|
|
@method compile
|
|
@param {String} templateString This is the string to be compiled by HTMLBars.
|
|
*/
|
|
__exports__["default"] = function(templateString) {
|
|
if (!compile && Ember.__loader.registry['htmlbars-compiler/compiler']) {
|
|
compile = requireModule('htmlbars-compiler/compiler').compile;
|
|
}
|
|
|
|
if (!compile) {
|
|
throw new Error('Cannot call `compile` without the template compiler loaded. Please load `ember-template-compiler.js` prior to calling `compile`.');
|
|
}
|
|
|
|
var templateSpec = compile(templateString, compileOptions());
|
|
|
|
return template(templateSpec);
|
|
}
|
|
});
|
|
enifed("ember-template-compiler/system/compile_options",
|
|
["ember-metal/core","ember-template-compiler/plugins","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-template-compiler
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
var plugins = __dependency2__["default"];
|
|
|
|
/**
|
|
@private
|
|
@property compileOptions
|
|
*/
|
|
__exports__["default"] = function() {
|
|
var disableComponentGeneration = true;
|
|
|
|
return {
|
|
disableComponentGeneration: disableComponentGeneration,
|
|
|
|
plugins: plugins
|
|
};
|
|
}
|
|
});
|
|
enifed("ember-template-compiler/system/precompile",
|
|
["ember-template-compiler/system/compile_options","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-template-compiler
|
|
*/
|
|
|
|
var compileOptions = __dependency1__["default"];
|
|
var compileSpec;
|
|
|
|
/**
|
|
Uses HTMLBars `compile` function to process a string into a compiled template string.
|
|
The returned string must be passed through `Ember.HTMLBars.template`.
|
|
|
|
This is not present in production builds.
|
|
|
|
@private
|
|
@method precompile
|
|
@param {String} templateString This is the string to be compiled by HTMLBars.
|
|
*/
|
|
__exports__["default"] = function(templateString) {
|
|
if (!compileSpec && Ember.__loader.registry['htmlbars-compiler/compiler']) {
|
|
compileSpec = requireModule('htmlbars-compiler/compiler').compileSpec;
|
|
}
|
|
|
|
if (!compileSpec) {
|
|
throw new Error('Cannot call `compileSpec` without the template compiler loaded. Please load `ember-template-compiler.js` prior to calling `compileSpec`.');
|
|
}
|
|
|
|
return compileSpec(templateString, compileOptions());
|
|
}
|
|
});
|
|
enifed("ember-template-compiler/system/template",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-template-compiler
|
|
*/
|
|
|
|
/**
|
|
Augments the detault precompiled output of an HTMLBars template with
|
|
additional information needed by Ember.
|
|
|
|
@private
|
|
@method template
|
|
@param {Function} templateSpec This is the compiled HTMLBars template spec.
|
|
*/
|
|
|
|
__exports__["default"] = function(templateSpec) {
|
|
templateSpec.isTop = true;
|
|
templateSpec.isMethod = false;
|
|
|
|
return templateSpec;
|
|
}
|
|
});
|
|
enifed("ember-testing",
|
|
["ember-metal/core","ember-testing/initializers","ember-testing/support","ember-testing/setup_for_testing","ember-testing/test","ember-testing/adapters/adapter","ember-testing/adapters/qunit","ember-testing/helpers"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
|
|
// to setup initializer
|
|
// to handle various edge cases
|
|
|
|
var setupForTesting = __dependency4__["default"];
|
|
var Test = __dependency5__["default"];
|
|
var Adapter = __dependency6__["default"];
|
|
var QUnitAdapter = __dependency7__["default"];
|
|
// adds helpers to helpers object in Test
|
|
|
|
/**
|
|
Ember Testing
|
|
|
|
@module ember
|
|
@submodule ember-testing
|
|
@requires ember-application
|
|
*/
|
|
|
|
Ember.Test = Test;
|
|
Ember.Test.Adapter = Adapter;
|
|
Ember.Test.QUnitAdapter = QUnitAdapter;
|
|
Ember.setupForTesting = setupForTesting;
|
|
});
|
|
enifed("ember-testing/adapters/adapter",
|
|
["ember-runtime/system/object","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var EmberObject = __dependency1__["default"];
|
|
|
|
function K() { return this; }
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-testing
|
|
*/
|
|
|
|
/**
|
|
The primary purpose of this class is to create hooks that can be implemented
|
|
by an adapter for various test frameworks.
|
|
|
|
@class Adapter
|
|
@namespace Ember.Test
|
|
*/
|
|
var Adapter = EmberObject.extend({
|
|
/**
|
|
This callback will be called whenever an async operation is about to start.
|
|
|
|
Override this to call your framework's methods that handle async
|
|
operations.
|
|
|
|
@public
|
|
@method asyncStart
|
|
*/
|
|
asyncStart: K,
|
|
|
|
/**
|
|
This callback will be called whenever an async operation has completed.
|
|
|
|
@public
|
|
@method asyncEnd
|
|
*/
|
|
asyncEnd: K,
|
|
|
|
/**
|
|
Override this method with your testing framework's false assertion.
|
|
This function is called whenever an exception occurs causing the testing
|
|
promise to fail.
|
|
|
|
QUnit example:
|
|
|
|
```javascript
|
|
exception: function(error) {
|
|
ok(false, error);
|
|
};
|
|
```
|
|
|
|
@public
|
|
@method exception
|
|
@param {String} error The exception to be raised.
|
|
*/
|
|
exception: function(error) {
|
|
throw error;
|
|
}
|
|
});
|
|
|
|
__exports__["default"] = Adapter;
|
|
});
|
|
enifed("ember-testing/adapters/qunit",
|
|
["ember-testing/adapters/adapter","ember-metal/utils","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var Adapter = __dependency1__["default"];
|
|
var inspect = __dependency2__.inspect;
|
|
|
|
/**
|
|
This class implements the methods defined by Ember.Test.Adapter for the
|
|
QUnit testing framework.
|
|
|
|
@class QUnitAdapter
|
|
@namespace Ember.Test
|
|
@extends Ember.Test.Adapter
|
|
*/
|
|
__exports__["default"] = Adapter.extend({
|
|
asyncStart: function() {
|
|
QUnit.stop();
|
|
},
|
|
asyncEnd: function() {
|
|
QUnit.start();
|
|
},
|
|
exception: function(error) {
|
|
ok(false, inspect(error));
|
|
}
|
|
});
|
|
});
|
|
enifed("ember-testing/helpers",
|
|
["ember-metal/property_get","ember-metal/error","ember-metal/run_loop","ember-views/system/jquery","ember-testing/test"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__) {
|
|
"use strict";
|
|
var get = __dependency1__.get;
|
|
var EmberError = __dependency2__["default"];
|
|
var run = __dependency3__["default"];
|
|
var jQuery = __dependency4__["default"];
|
|
var Test = __dependency5__["default"];
|
|
|
|
/**
|
|
* @module ember
|
|
* @submodule ember-testing
|
|
*/
|
|
|
|
var helper = Test.registerHelper;
|
|
var asyncHelper = Test.registerAsyncHelper;
|
|
var countAsync = 0;
|
|
|
|
function currentRouteName(app){
|
|
var appController = app.__container__.lookup('controller:application');
|
|
|
|
return get(appController, 'currentRouteName');
|
|
}
|
|
|
|
function currentPath(app){
|
|
var appController = app.__container__.lookup('controller:application');
|
|
|
|
return get(appController, 'currentPath');
|
|
}
|
|
|
|
function currentURL(app){
|
|
var router = app.__container__.lookup('router:main');
|
|
|
|
return get(router, 'location').getURL();
|
|
}
|
|
|
|
function pauseTest(){
|
|
Test.adapter.asyncStart();
|
|
return new Ember.RSVP.Promise(function(){ }, 'TestAdapter paused promise');
|
|
}
|
|
|
|
function visit(app, url) {
|
|
var router = app.__container__.lookup('router:main');
|
|
router.location.setURL(url);
|
|
|
|
if (app._readinessDeferrals > 0) {
|
|
router['initialURL'] = url;
|
|
run(app, 'advanceReadiness');
|
|
delete router['initialURL'];
|
|
} else {
|
|
run(app, app.handleURL, url);
|
|
}
|
|
|
|
return app.testHelpers.wait();
|
|
}
|
|
|
|
function click(app, selector, context) {
|
|
var $el = app.testHelpers.findWithAssert(selector, context);
|
|
run($el, 'mousedown');
|
|
|
|
if ($el.is(':input, [contenteditable=true]')) {
|
|
var type = $el.prop('type');
|
|
if (type !== 'checkbox' && type !== 'radio' && type !== 'hidden') {
|
|
run($el, function(){
|
|
// Firefox does not trigger the `focusin` event if the window
|
|
// does not have focus. If the document doesn't have focus just
|
|
// use trigger('focusin') instead.
|
|
if (!document.hasFocus || document.hasFocus()) {
|
|
this.focus();
|
|
} else {
|
|
this.trigger('focusin');
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
run($el, 'mouseup');
|
|
run($el, 'click');
|
|
|
|
return app.testHelpers.wait();
|
|
}
|
|
|
|
function triggerEvent(app, selector, contextOrType, typeOrOptions, possibleOptions){
|
|
var arity = arguments.length;
|
|
var context, type, options;
|
|
|
|
if (arity === 3) {
|
|
// context and options are optional, so this is
|
|
// app, selector, type
|
|
context = null;
|
|
type = contextOrType;
|
|
options = {};
|
|
} else if (arity === 4) {
|
|
// context and options are optional, so this is
|
|
if (typeof typeOrOptions === "object") { // either
|
|
// app, selector, type, options
|
|
context = null;
|
|
type = contextOrType;
|
|
options = typeOrOptions;
|
|
} else { // or
|
|
// app, selector, context, type
|
|
context = contextOrType;
|
|
type = typeOrOptions;
|
|
options = {};
|
|
}
|
|
} else {
|
|
context = contextOrType;
|
|
type = typeOrOptions;
|
|
options = possibleOptions;
|
|
}
|
|
|
|
var $el = app.testHelpers.findWithAssert(selector, context);
|
|
|
|
var event = jQuery.Event(type, options);
|
|
|
|
run($el, 'trigger', event);
|
|
|
|
return app.testHelpers.wait();
|
|
}
|
|
|
|
function keyEvent(app, selector, contextOrType, typeOrKeyCode, keyCode) {
|
|
var context, type;
|
|
|
|
if (typeof keyCode === 'undefined') {
|
|
context = null;
|
|
keyCode = typeOrKeyCode;
|
|
type = contextOrType;
|
|
} else {
|
|
context = contextOrType;
|
|
type = typeOrKeyCode;
|
|
}
|
|
|
|
return app.testHelpers.triggerEvent(selector, context, type, { keyCode: keyCode, which: keyCode });
|
|
}
|
|
|
|
function fillIn(app, selector, contextOrText, text) {
|
|
var $el, context;
|
|
if (typeof text === 'undefined') {
|
|
text = contextOrText;
|
|
} else {
|
|
context = contextOrText;
|
|
}
|
|
$el = app.testHelpers.findWithAssert(selector, context);
|
|
run(function() {
|
|
$el.val(text).change();
|
|
});
|
|
return app.testHelpers.wait();
|
|
}
|
|
|
|
function findWithAssert(app, selector, context) {
|
|
var $el = app.testHelpers.find(selector, context);
|
|
if ($el.length === 0) {
|
|
throw new EmberError("Element " + selector + " not found.");
|
|
}
|
|
return $el;
|
|
}
|
|
|
|
function find(app, selector, context) {
|
|
var $el;
|
|
context = context || get(app, 'rootElement');
|
|
$el = app.$(selector, context);
|
|
|
|
return $el;
|
|
}
|
|
|
|
function andThen(app, callback) {
|
|
return app.testHelpers.wait(callback(app));
|
|
}
|
|
|
|
function wait(app, value) {
|
|
return Test.promise(function(resolve) {
|
|
// If this is the first async promise, kick off the async test
|
|
if (++countAsync === 1) {
|
|
Test.adapter.asyncStart();
|
|
}
|
|
|
|
// Every 10ms, poll for the async thing to have finished
|
|
var watcher = setInterval(function() {
|
|
var router = app.__container__.lookup('router:main');
|
|
|
|
// 1. If the router is loading, keep polling
|
|
var routerIsLoading = router.router && !!router.router.activeTransition;
|
|
if (routerIsLoading) { return; }
|
|
|
|
// 2. If there are pending Ajax requests, keep polling
|
|
if (Test.pendingAjaxRequests) { return; }
|
|
|
|
// 3. If there are scheduled timers or we are inside of a run loop, keep polling
|
|
if (run.hasScheduledTimers() || run.currentRunLoop) { return; }
|
|
if (Test.waiters && Test.waiters.any(function(waiter) {
|
|
var context = waiter[0];
|
|
var callback = waiter[1];
|
|
return !callback.call(context);
|
|
})) { return; }
|
|
// Stop polling
|
|
clearInterval(watcher);
|
|
|
|
// If this is the last async promise, end the async test
|
|
if (--countAsync === 0) {
|
|
Test.adapter.asyncEnd();
|
|
}
|
|
|
|
// Synchronously resolve the promise
|
|
run(null, resolve, value);
|
|
}, 10);
|
|
});
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* Loads a route, sets up any controllers, and renders any templates associated
|
|
* with the route as though a real user had triggered the route change while
|
|
* using your app.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```javascript
|
|
* visit('posts/index').then(function() {
|
|
* // assert something
|
|
* });
|
|
* ```
|
|
*
|
|
* @method visit
|
|
* @param {String} url the name of the route
|
|
* @return {RSVP.Promise}
|
|
*/
|
|
asyncHelper('visit', visit);
|
|
|
|
/**
|
|
* Clicks an element and triggers any actions triggered by the element's `click`
|
|
* event.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```javascript
|
|
* click('.some-jQuery-selector').then(function() {
|
|
* // assert something
|
|
* });
|
|
* ```
|
|
*
|
|
* @method click
|
|
* @param {String} selector jQuery selector for finding element on the DOM
|
|
* @return {RSVP.Promise}
|
|
*/
|
|
asyncHelper('click', click);
|
|
|
|
/**
|
|
* Simulates a key event, e.g. `keypress`, `keydown`, `keyup` with the desired keyCode
|
|
*
|
|
* Example:
|
|
*
|
|
* ```javascript
|
|
* keyEvent('.some-jQuery-selector', 'keypress', 13).then(function() {
|
|
* // assert something
|
|
* });
|
|
* ```
|
|
*
|
|
* @method keyEvent
|
|
* @param {String} selector jQuery selector for finding element on the DOM
|
|
* @param {String} type the type of key event, e.g. `keypress`, `keydown`, `keyup`
|
|
* @param {Number} keyCode the keyCode of the simulated key event
|
|
* @return {RSVP.Promise}
|
|
* @since 1.5.0
|
|
*/
|
|
asyncHelper('keyEvent', keyEvent);
|
|
|
|
/**
|
|
* Fills in an input element with some text.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```javascript
|
|
* fillIn('#email', 'you@example.com').then(function() {
|
|
* // assert something
|
|
* });
|
|
* ```
|
|
*
|
|
* @method fillIn
|
|
* @param {String} selector jQuery selector finding an input element on the DOM
|
|
* to fill text with
|
|
* @param {String} text text to place inside the input element
|
|
* @return {RSVP.Promise}
|
|
*/
|
|
asyncHelper('fillIn', fillIn);
|
|
|
|
/**
|
|
* Finds an element in the context of the app's container element. A simple alias
|
|
* for `app.$(selector)`.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```javascript
|
|
* var $el = find('.my-selector');
|
|
* ```
|
|
*
|
|
* @method find
|
|
* @param {String} selector jQuery string selector for element lookup
|
|
* @return {Object} jQuery object representing the results of the query
|
|
*/
|
|
helper('find', find);
|
|
|
|
/**
|
|
* Like `find`, but throws an error if the element selector returns no results.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```javascript
|
|
* var $el = findWithAssert('.doesnt-exist'); // throws error
|
|
* ```
|
|
*
|
|
* @method findWithAssert
|
|
* @param {String} selector jQuery selector string for finding an element within
|
|
* the DOM
|
|
* @return {Object} jQuery object representing the results of the query
|
|
* @throws {Error} throws error if jQuery object returned has a length of 0
|
|
*/
|
|
helper('findWithAssert', findWithAssert);
|
|
|
|
/**
|
|
Causes the run loop to process any pending events. This is used to ensure that
|
|
any async operations from other helpers (or your assertions) have been processed.
|
|
|
|
This is most often used as the return value for the helper functions (see 'click',
|
|
'fillIn','visit',etc).
|
|
|
|
Example:
|
|
|
|
```javascript
|
|
Ember.Test.registerAsyncHelper('loginUser', function(app, username, password) {
|
|
visit('secured/path/here')
|
|
.fillIn('#username', username)
|
|
.fillIn('#password', password)
|
|
.click('.submit')
|
|
|
|
return app.testHelpers.wait();
|
|
});
|
|
|
|
@method wait
|
|
@param {Object} value The value to be returned.
|
|
@return {RSVP.Promise}
|
|
*/
|
|
asyncHelper('wait', wait);
|
|
asyncHelper('andThen', andThen);
|
|
|
|
|
|
/**
|
|
Returns the currently active route name.
|
|
|
|
Example:
|
|
|
|
```javascript
|
|
function validateRouteName(){
|
|
equal(currentRouteName(), 'some.path', "correct route was transitioned into.");
|
|
}
|
|
|
|
visit('/some/path').then(validateRouteName)
|
|
```
|
|
|
|
@method currentRouteName
|
|
@return {Object} The name of the currently active route.
|
|
@since 1.5.0
|
|
*/
|
|
helper('currentRouteName', currentRouteName);
|
|
|
|
/**
|
|
Returns the current path.
|
|
|
|
Example:
|
|
|
|
```javascript
|
|
function validateURL(){
|
|
equal(currentPath(), 'some.path.index', "correct path was transitioned into.");
|
|
}
|
|
|
|
click('#some-link-id').then(validateURL);
|
|
```
|
|
|
|
@method currentPath
|
|
@return {Object} The currently active path.
|
|
@since 1.5.0
|
|
*/
|
|
helper('currentPath', currentPath);
|
|
|
|
/**
|
|
Returns the current URL.
|
|
|
|
Example:
|
|
|
|
```javascript
|
|
function validateURL(){
|
|
equal(currentURL(), '/some/path', "correct URL was transitioned into.");
|
|
}
|
|
|
|
click('#some-link-id').then(validateURL);
|
|
```
|
|
|
|
@method currentURL
|
|
@return {Object} The currently active URL.
|
|
@since 1.5.0
|
|
*/
|
|
helper('currentURL', currentURL);
|
|
|
|
|
|
/**
|
|
Pauses the current test - this is useful for debugging while testing or for test-driving.
|
|
It allows you to inspect the state of your application at any point.
|
|
|
|
Example (The test will pause before clicking the button):
|
|
|
|
```javascript
|
|
visit('/')
|
|
return pauseTest();
|
|
|
|
click('.btn');
|
|
```
|
|
|
|
@since 1.9.0
|
|
@method pauseTest
|
|
@return {Object} A promise that will never resolve
|
|
*/
|
|
helper('pauseTest', pauseTest);
|
|
|
|
|
|
/**
|
|
Triggers the given DOM event on the element identified by the provided selector.
|
|
|
|
Example:
|
|
|
|
```javascript
|
|
triggerEvent('#some-elem-id', 'blur');
|
|
```
|
|
|
|
This is actually used internally by the `keyEvent` helper like so:
|
|
|
|
```javascript
|
|
triggerEvent('#some-elem-id', 'keypress', { keyCode: 13 });
|
|
```
|
|
|
|
@method triggerEvent
|
|
@param {String} selector jQuery selector for finding element on the DOM
|
|
@param {String} [context] jQuery selector that will limit the selector
|
|
argument to find only within the context's children
|
|
@param {String} type The event type to be triggered.
|
|
@param {Object} [options] The options to be passed to jQuery.Event.
|
|
@return {RSVP.Promise}
|
|
@since 1.5.0
|
|
*/
|
|
asyncHelper('triggerEvent', triggerEvent);
|
|
});
|
|
enifed("ember-testing/initializers",
|
|
["ember-runtime/system/lazy_load"],
|
|
function(__dependency1__) {
|
|
"use strict";
|
|
var onLoad = __dependency1__.onLoad;
|
|
|
|
var name = 'deferReadiness in `testing` mode';
|
|
|
|
onLoad('Ember.Application', function(Application) {
|
|
if (!Application.initializers[name]) {
|
|
Application.initializer({
|
|
name: name,
|
|
|
|
initialize: function(container, application){
|
|
if (application.testing) {
|
|
application.deferReadiness();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
enifed("ember-testing/setup_for_testing",
|
|
["ember-metal/core","ember-testing/adapters/qunit","ember-views/system/jquery","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
// import Test from "ember-testing/test"; // ES6TODO: fix when cycles are supported
|
|
var QUnitAdapter = __dependency2__["default"];
|
|
var jQuery = __dependency3__["default"];
|
|
|
|
var Test, requests;
|
|
|
|
function incrementAjaxPendingRequests(_, xhr){
|
|
requests.push(xhr);
|
|
Test.pendingAjaxRequests = requests.length;
|
|
}
|
|
|
|
function decrementAjaxPendingRequests(_, xhr){
|
|
for (var i=0;i<requests.length;i++) {
|
|
if (xhr === requests[i]) {
|
|
requests.splice(i, 1);
|
|
}
|
|
}
|
|
Test.pendingAjaxRequests = requests.length;
|
|
}
|
|
|
|
/**
|
|
Sets Ember up for testing. This is useful to perform
|
|
basic setup steps in order to unit test.
|
|
|
|
Use `App.setupForTesting` to perform integration tests (full
|
|
application testing).
|
|
|
|
@method setupForTesting
|
|
@namespace Ember
|
|
@since 1.5.0
|
|
*/
|
|
__exports__["default"] = function setupForTesting() {
|
|
if (!Test) { Test = requireModule('ember-testing/test')['default']; }
|
|
|
|
Ember.testing = true;
|
|
|
|
// if adapter is not manually set default to QUnit
|
|
if (!Test.adapter) {
|
|
Test.adapter = QUnitAdapter.create();
|
|
}
|
|
|
|
requests = [];
|
|
Test.pendingAjaxRequests = requests.length;
|
|
|
|
jQuery(document).off('ajaxSend', incrementAjaxPendingRequests);
|
|
jQuery(document).off('ajaxComplete', decrementAjaxPendingRequests);
|
|
jQuery(document).on('ajaxSend', incrementAjaxPendingRequests);
|
|
jQuery(document).on('ajaxComplete', decrementAjaxPendingRequests);
|
|
}
|
|
});
|
|
enifed("ember-testing/support",
|
|
["ember-metal/core","ember-views/system/jquery"],
|
|
function(__dependency1__, __dependency2__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
var jQuery = __dependency2__["default"];
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-testing
|
|
*/
|
|
|
|
var $ = jQuery;
|
|
|
|
/**
|
|
This method creates a checkbox and triggers the click event to fire the
|
|
passed in handler. It is used to correct for a bug in older versions
|
|
of jQuery (e.g 1.8.3).
|
|
|
|
@private
|
|
@method testCheckboxClick
|
|
*/
|
|
function testCheckboxClick(handler) {
|
|
$('<input type="checkbox">')
|
|
.css({ position: 'absolute', left: '-1000px', top: '-1000px' })
|
|
.appendTo('body')
|
|
.on('click', handler)
|
|
.trigger('click')
|
|
.remove();
|
|
}
|
|
|
|
$(function() {
|
|
/*
|
|
Determine whether a checkbox checked using jQuery's "click" method will have
|
|
the correct value for its checked property.
|
|
|
|
If we determine that the current jQuery version exhibits this behavior,
|
|
patch it to work correctly as in the commit for the actual fix:
|
|
https://github.com/jquery/jquery/commit/1fb2f92.
|
|
*/
|
|
testCheckboxClick(function() {
|
|
if (!this.checked && !$.event.special.click) {
|
|
$.event.special.click = {
|
|
// For checkbox, fire native event so checked state will be right
|
|
trigger: function() {
|
|
if ($.nodeName( this, "input" ) && this.type === "checkbox" && this.click) {
|
|
this.click();
|
|
return false;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
});
|
|
|
|
// Try again to verify that the patch took effect or blow up.
|
|
testCheckboxClick(function() {
|
|
Ember.warn("clicked checkboxes should be checked! the jQuery patch didn't work", this.checked);
|
|
});
|
|
});
|
|
});
|
|
enifed("ember-testing/test",
|
|
["ember-metal/core","ember-metal/run_loop","ember-metal/platform","ember-runtime/ext/rsvp","ember-testing/setup_for_testing","ember-application/system/application","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
var emberRun = __dependency2__["default"];
|
|
var create = __dependency3__.create;
|
|
var RSVP = __dependency4__["default"];
|
|
var setupForTesting = __dependency5__["default"];
|
|
var EmberApplication = __dependency6__["default"];
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-testing
|
|
*/
|
|
var slice = [].slice;
|
|
var helpers = {};
|
|
var injectHelpersCallbacks = [];
|
|
|
|
/**
|
|
This is a container for an assortment of testing related functionality:
|
|
|
|
* Choose your default test adapter (for your framework of choice).
|
|
* Register/Unregister additional test helpers.
|
|
* Setup callbacks to be fired when the test helpers are injected into
|
|
your application.
|
|
|
|
@class Test
|
|
@namespace Ember
|
|
*/
|
|
var Test = {
|
|
/**
|
|
Hash containing all known test helpers.
|
|
|
|
@property _helpers
|
|
@private
|
|
@since 1.7.0
|
|
*/
|
|
_helpers: helpers,
|
|
|
|
/**
|
|
`registerHelper` is used to register a test helper that will be injected
|
|
when `App.injectTestHelpers` is called.
|
|
|
|
The helper method will always be called with the current Application as
|
|
the first parameter.
|
|
|
|
For example:
|
|
|
|
```javascript
|
|
Ember.Test.registerHelper('boot', function(app) {
|
|
Ember.run(app, app.advanceReadiness);
|
|
});
|
|
```
|
|
|
|
This helper can later be called without arguments because it will be
|
|
called with `app` as the first parameter.
|
|
|
|
```javascript
|
|
App = Ember.Application.create();
|
|
App.injectTestHelpers();
|
|
boot();
|
|
```
|
|
|
|
@public
|
|
@method registerHelper
|
|
@param {String} name The name of the helper method to add.
|
|
@param {Function} helperMethod
|
|
@param options {Object}
|
|
*/
|
|
registerHelper: function(name, helperMethod) {
|
|
helpers[name] = {
|
|
method: helperMethod,
|
|
meta: { wait: false }
|
|
};
|
|
},
|
|
|
|
/**
|
|
`registerAsyncHelper` is used to register an async test helper that will be injected
|
|
when `App.injectTestHelpers` is called.
|
|
|
|
The helper method will always be called with the current Application as
|
|
the first parameter.
|
|
|
|
For example:
|
|
|
|
```javascript
|
|
Ember.Test.registerAsyncHelper('boot', function(app) {
|
|
Ember.run(app, app.advanceReadiness);
|
|
});
|
|
```
|
|
|
|
The advantage of an async helper is that it will not run
|
|
until the last async helper has completed. All async helpers
|
|
after it will wait for it complete before running.
|
|
|
|
|
|
For example:
|
|
|
|
```javascript
|
|
Ember.Test.registerAsyncHelper('deletePost', function(app, postId) {
|
|
click('.delete-' + postId);
|
|
});
|
|
|
|
// ... in your test
|
|
visit('/post/2');
|
|
deletePost(2);
|
|
visit('/post/3');
|
|
deletePost(3);
|
|
```
|
|
|
|
@public
|
|
@method registerAsyncHelper
|
|
@param {String} name The name of the helper method to add.
|
|
@param {Function} helperMethod
|
|
@since 1.2.0
|
|
*/
|
|
registerAsyncHelper: function(name, helperMethod) {
|
|
helpers[name] = {
|
|
method: helperMethod,
|
|
meta: { wait: true }
|
|
};
|
|
},
|
|
|
|
/**
|
|
Remove a previously added helper method.
|
|
|
|
Example:
|
|
|
|
```javascript
|
|
Ember.Test.unregisterHelper('wait');
|
|
```
|
|
|
|
@public
|
|
@method unregisterHelper
|
|
@param {String} name The helper to remove.
|
|
*/
|
|
unregisterHelper: function(name) {
|
|
delete helpers[name];
|
|
delete Test.Promise.prototype[name];
|
|
},
|
|
|
|
/**
|
|
Used to register callbacks to be fired whenever `App.injectTestHelpers`
|
|
is called.
|
|
|
|
The callback will receive the current application as an argument.
|
|
|
|
Example:
|
|
|
|
```javascript
|
|
Ember.Test.onInjectHelpers(function() {
|
|
Ember.$(document).ajaxSend(function() {
|
|
Test.pendingAjaxRequests++;
|
|
});
|
|
|
|
Ember.$(document).ajaxComplete(function() {
|
|
Test.pendingAjaxRequests--;
|
|
});
|
|
});
|
|
```
|
|
|
|
@public
|
|
@method onInjectHelpers
|
|
@param {Function} callback The function to be called.
|
|
*/
|
|
onInjectHelpers: function(callback) {
|
|
injectHelpersCallbacks.push(callback);
|
|
},
|
|
|
|
/**
|
|
This returns a thenable tailored for testing. It catches failed
|
|
`onSuccess` callbacks and invokes the `Ember.Test.adapter.exception`
|
|
callback in the last chained then.
|
|
|
|
This method should be returned by async helpers such as `wait`.
|
|
|
|
@public
|
|
@method promise
|
|
@param {Function} resolver The function used to resolve the promise.
|
|
*/
|
|
promise: function(resolver) {
|
|
return new Test.Promise(resolver);
|
|
},
|
|
|
|
/**
|
|
Used to allow ember-testing to communicate with a specific testing
|
|
framework.
|
|
|
|
You can manually set it before calling `App.setupForTesting()`.
|
|
|
|
Example:
|
|
|
|
```javascript
|
|
Ember.Test.adapter = MyCustomAdapter.create()
|
|
```
|
|
|
|
If you do not set it, ember-testing will default to `Ember.Test.QUnitAdapter`.
|
|
|
|
@public
|
|
@property adapter
|
|
@type {Class} The adapter to be used.
|
|
@default Ember.Test.QUnitAdapter
|
|
*/
|
|
adapter: null,
|
|
|
|
/**
|
|
Replacement for `Ember.RSVP.resolve`
|
|
The only difference is this uses
|
|
an instance of `Ember.Test.Promise`
|
|
|
|
@public
|
|
@method resolve
|
|
@param {Mixed} The value to resolve
|
|
@since 1.2.0
|
|
*/
|
|
resolve: function(val) {
|
|
return Test.promise(function(resolve) {
|
|
return resolve(val);
|
|
});
|
|
},
|
|
|
|
/**
|
|
This allows ember-testing to play nicely with other asynchronous
|
|
events, such as an application that is waiting for a CSS3
|
|
transition or an IndexDB transaction.
|
|
|
|
For example:
|
|
|
|
```javascript
|
|
Ember.Test.registerWaiter(function() {
|
|
return myPendingTransactions() == 0;
|
|
});
|
|
```
|
|
The `context` argument allows you to optionally specify the `this`
|
|
with which your callback will be invoked.
|
|
|
|
For example:
|
|
|
|
```javascript
|
|
Ember.Test.registerWaiter(MyDB, MyDB.hasPendingTransactions);
|
|
```
|
|
|
|
@public
|
|
@method registerWaiter
|
|
@param {Object} context (optional)
|
|
@param {Function} callback
|
|
@since 1.2.0
|
|
*/
|
|
registerWaiter: function(context, callback) {
|
|
if (arguments.length === 1) {
|
|
callback = context;
|
|
context = null;
|
|
}
|
|
if (!this.waiters) {
|
|
this.waiters = Ember.A();
|
|
}
|
|
this.waiters.push([context, callback]);
|
|
},
|
|
/**
|
|
`unregisterWaiter` is used to unregister a callback that was
|
|
registered with `registerWaiter`.
|
|
|
|
@public
|
|
@method unregisterWaiter
|
|
@param {Object} context (optional)
|
|
@param {Function} callback
|
|
@since 1.2.0
|
|
*/
|
|
unregisterWaiter: function(context, callback) {
|
|
if (!this.waiters) { return; }
|
|
if (arguments.length === 1) {
|
|
callback = context;
|
|
context = null;
|
|
}
|
|
this.waiters = Ember.A(this.waiters.filter(function(elt) {
|
|
return !(elt[0] === context && elt[1] === callback);
|
|
}));
|
|
}
|
|
};
|
|
|
|
function helper(app, name) {
|
|
var fn = helpers[name].method;
|
|
var meta = helpers[name].meta;
|
|
|
|
return function() {
|
|
var args = slice.call(arguments);
|
|
var lastPromise = Test.lastPromise;
|
|
|
|
args.unshift(app);
|
|
|
|
// some helpers are not async and
|
|
// need to return a value immediately.
|
|
// example: `find`
|
|
if (!meta.wait) {
|
|
return fn.apply(app, args);
|
|
}
|
|
|
|
if (!lastPromise) {
|
|
// It's the first async helper in current context
|
|
lastPromise = fn.apply(app, args);
|
|
} else {
|
|
// wait for last helper's promise to resolve
|
|
// and then execute
|
|
run(function() {
|
|
lastPromise = Test.resolve(lastPromise).then(function() {
|
|
return fn.apply(app, args);
|
|
});
|
|
});
|
|
}
|
|
|
|
return lastPromise;
|
|
};
|
|
}
|
|
|
|
function run(fn) {
|
|
if (!emberRun.currentRunLoop) {
|
|
emberRun(fn);
|
|
} else {
|
|
fn();
|
|
}
|
|
}
|
|
|
|
EmberApplication.reopen({
|
|
/**
|
|
This property contains the testing helpers for the current application. These
|
|
are created once you call `injectTestHelpers` on your `Ember.Application`
|
|
instance. The included helpers are also available on the `window` object by
|
|
default, but can be used from this object on the individual application also.
|
|
|
|
@property testHelpers
|
|
@type {Object}
|
|
@default {}
|
|
*/
|
|
testHelpers: {},
|
|
|
|
/**
|
|
This property will contain the original methods that were registered
|
|
on the `helperContainer` before `injectTestHelpers` is called.
|
|
|
|
When `removeTestHelpers` is called, these methods are restored to the
|
|
`helperContainer`.
|
|
|
|
@property originalMethods
|
|
@type {Object}
|
|
@default {}
|
|
@private
|
|
@since 1.3.0
|
|
*/
|
|
originalMethods: {},
|
|
|
|
|
|
/**
|
|
This property indicates whether or not this application is currently in
|
|
testing mode. This is set when `setupForTesting` is called on the current
|
|
application.
|
|
|
|
@property testing
|
|
@type {Boolean}
|
|
@default false
|
|
@since 1.3.0
|
|
*/
|
|
testing: false,
|
|
|
|
/**
|
|
This hook defers the readiness of the application, so that you can start
|
|
the app when your tests are ready to run. It also sets the router's
|
|
location to 'none', so that the window's location will not be modified
|
|
(preventing both accidental leaking of state between tests and interference
|
|
with your testing framework).
|
|
|
|
Example:
|
|
|
|
```
|
|
App.setupForTesting();
|
|
```
|
|
|
|
@method setupForTesting
|
|
*/
|
|
setupForTesting: function() {
|
|
setupForTesting();
|
|
|
|
this.testing = true;
|
|
|
|
this.Router.reopen({
|
|
location: 'none'
|
|
});
|
|
},
|
|
|
|
/**
|
|
This will be used as the container to inject the test helpers into. By
|
|
default the helpers are injected into `window`.
|
|
|
|
@property helperContainer
|
|
@type {Object} The object to be used for test helpers.
|
|
@default window
|
|
@since 1.2.0
|
|
*/
|
|
helperContainer: window,
|
|
|
|
/**
|
|
This injects the test helpers into the `helperContainer` object. If an object is provided
|
|
it will be used as the helperContainer. If `helperContainer` is not set it will default
|
|
to `window`. If a function of the same name has already been defined it will be cached
|
|
(so that it can be reset if the helper is removed with `unregisterHelper` or
|
|
`removeTestHelpers`).
|
|
|
|
Any callbacks registered with `onInjectHelpers` will be called once the
|
|
helpers have been injected.
|
|
|
|
Example:
|
|
```
|
|
App.injectTestHelpers();
|
|
```
|
|
|
|
@method injectTestHelpers
|
|
*/
|
|
injectTestHelpers: function(helperContainer) {
|
|
if (helperContainer) { this.helperContainer = helperContainer; }
|
|
|
|
this.testHelpers = {};
|
|
for (var name in helpers) {
|
|
this.originalMethods[name] = this.helperContainer[name];
|
|
this.testHelpers[name] = this.helperContainer[name] = helper(this, name);
|
|
protoWrap(Test.Promise.prototype, name, helper(this, name), helpers[name].meta.wait);
|
|
}
|
|
|
|
for(var i = 0, l = injectHelpersCallbacks.length; i < l; i++) {
|
|
injectHelpersCallbacks[i](this);
|
|
}
|
|
},
|
|
|
|
/**
|
|
This removes all helpers that have been registered, and resets and functions
|
|
that were overridden by the helpers.
|
|
|
|
Example:
|
|
|
|
```javascript
|
|
App.removeTestHelpers();
|
|
```
|
|
|
|
@public
|
|
@method removeTestHelpers
|
|
*/
|
|
removeTestHelpers: function() {
|
|
for (var name in helpers) {
|
|
this.helperContainer[name] = this.originalMethods[name];
|
|
delete this.testHelpers[name];
|
|
delete this.originalMethods[name];
|
|
}
|
|
}
|
|
});
|
|
|
|
// This method is no longer needed
|
|
// But still here for backwards compatibility
|
|
// of helper chaining
|
|
function protoWrap(proto, name, callback, isAsync) {
|
|
proto[name] = function() {
|
|
var args = arguments;
|
|
if (isAsync) {
|
|
return callback.apply(this, args);
|
|
} else {
|
|
return this.then(function() {
|
|
return callback.apply(this, args);
|
|
});
|
|
}
|
|
};
|
|
}
|
|
|
|
Test.Promise = function() {
|
|
RSVP.Promise.apply(this, arguments);
|
|
Test.lastPromise = this;
|
|
};
|
|
|
|
Test.Promise.prototype = create(RSVP.Promise.prototype);
|
|
Test.Promise.prototype.constructor = Test.Promise;
|
|
|
|
// Patch `then` to isolate async methods
|
|
// specifically `Ember.Test.lastPromise`
|
|
var originalThen = RSVP.Promise.prototype.then;
|
|
Test.Promise.prototype.then = function(onSuccess, onFailure) {
|
|
return originalThen.call(this, function(val) {
|
|
return isolate(onSuccess, val);
|
|
}, onFailure);
|
|
};
|
|
|
|
// This method isolates nested async methods
|
|
// so that they don't conflict with other last promises.
|
|
//
|
|
// 1. Set `Ember.Test.lastPromise` to null
|
|
// 2. Invoke method
|
|
// 3. Return the last promise created during method
|
|
// 4. Restore `Ember.Test.lastPromise` to original value
|
|
function isolate(fn, val) {
|
|
var value, lastPromise;
|
|
|
|
// Reset lastPromise for nested helpers
|
|
Test.lastPromise = null;
|
|
|
|
value = fn(val);
|
|
|
|
lastPromise = Test.lastPromise;
|
|
|
|
// If the method returned a promise
|
|
// return that promise. If not,
|
|
// return the last async helper's promise
|
|
if ((value && (value instanceof Test.Promise)) || !lastPromise) {
|
|
return value;
|
|
} else {
|
|
run(function() {
|
|
lastPromise = Test.resolve(lastPromise).then(function() {
|
|
return value;
|
|
});
|
|
});
|
|
return lastPromise;
|
|
}
|
|
}
|
|
|
|
__exports__["default"] = Test;
|
|
});
|
|
enifed("ember-views",
|
|
["ember-runtime","ember-views/system/jquery","ember-views/system/utils","ember-views/system/render_buffer","ember-views/system/ext","ember-views/views/states","ember-views/views/core_view","ember-views/views/view","ember-views/views/container_view","ember-views/views/collection_view","ember-views/views/component","ember-views/system/event_dispatcher","ember-views/mixins/view_target_action_support","ember-views/component_lookup","ember-views/views/checkbox","ember-views/mixins/text_support","ember-views/views/text_field","ember-views/views/text_area","ember-views/views/bound_view","ember-views/views/simple_bound_view","ember-views/views/metamorph_view","ember-views/views/select","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __dependency16__, __dependency17__, __dependency18__, __dependency19__, __dependency20__, __dependency21__, __dependency22__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
Ember Views
|
|
|
|
@module ember
|
|
@submodule ember-views
|
|
@requires ember-runtime
|
|
@main ember-views
|
|
*/
|
|
|
|
// BEGIN IMPORTS
|
|
var Ember = __dependency1__["default"];
|
|
var jQuery = __dependency2__["default"];
|
|
var isSimpleClick = __dependency3__.isSimpleClick;
|
|
var getViewClientRects = __dependency3__.getViewClientRects;
|
|
var getViewBoundingClientRect = __dependency3__.getViewBoundingClientRect;
|
|
var RenderBuffer = __dependency4__["default"];
|
|
// for the side effect of extending Ember.run.queues
|
|
var cloneStates = __dependency6__.cloneStates;
|
|
var states = __dependency6__.states;
|
|
|
|
var CoreView = __dependency7__["default"];
|
|
var View = __dependency8__["default"];
|
|
var ContainerView = __dependency9__["default"];
|
|
var CollectionView = __dependency10__["default"];
|
|
var Component = __dependency11__["default"];
|
|
|
|
var EventDispatcher = __dependency12__["default"];
|
|
var ViewTargetActionSupport = __dependency13__["default"];
|
|
var ComponentLookup = __dependency14__["default"];
|
|
var Checkbox = __dependency15__["default"];
|
|
var TextSupport = __dependency16__["default"];
|
|
var TextField = __dependency17__["default"];
|
|
var TextArea = __dependency18__["default"];
|
|
|
|
var BoundView = __dependency19__["default"];
|
|
var SimpleBoundView = __dependency20__["default"];
|
|
var _MetamorphView = __dependency21__["default"];
|
|
var _SimpleMetamorphView = __dependency21__._SimpleMetamorphView;
|
|
var _Metamorph = __dependency21__._Metamorph;
|
|
var Select = __dependency22__.Select;
|
|
var SelectOption = __dependency22__.SelectOption;
|
|
var SelectOptgroup = __dependency22__.SelectOptgroup;
|
|
// END IMPORTS
|
|
|
|
/**
|
|
Alias for jQuery
|
|
|
|
@method $
|
|
@for Ember
|
|
*/
|
|
|
|
// BEGIN EXPORTS
|
|
Ember.$ = jQuery;
|
|
|
|
Ember.ViewTargetActionSupport = ViewTargetActionSupport;
|
|
Ember.RenderBuffer = RenderBuffer;
|
|
|
|
var ViewUtils = Ember.ViewUtils = {};
|
|
ViewUtils.isSimpleClick = isSimpleClick;
|
|
ViewUtils.getViewClientRects = getViewClientRects;
|
|
ViewUtils.getViewBoundingClientRect = getViewBoundingClientRect;
|
|
|
|
Ember.CoreView = CoreView;
|
|
Ember.View = View;
|
|
Ember.View.states = states;
|
|
Ember.View.cloneStates = cloneStates;
|
|
Ember.Checkbox = Checkbox;
|
|
Ember.TextField = TextField;
|
|
Ember.TextArea = TextArea;
|
|
|
|
Ember._SimpleBoundView = SimpleBoundView;
|
|
Ember._BoundView = BoundView;
|
|
Ember._SimpleMetamorphView = _SimpleMetamorphView;
|
|
Ember._MetamorphView = _MetamorphView;
|
|
Ember._Metamorph = _Metamorph;
|
|
Ember.Select = Select;
|
|
Ember.SelectOption = SelectOption;
|
|
Ember.SelectOptgroup = SelectOptgroup;
|
|
|
|
Ember.TextSupport = TextSupport;
|
|
Ember.ComponentLookup = ComponentLookup;
|
|
Ember.ContainerView = ContainerView;
|
|
Ember.CollectionView = CollectionView;
|
|
Ember.Component = Component;
|
|
Ember.EventDispatcher = EventDispatcher;
|
|
// END EXPORTS
|
|
|
|
__exports__["default"] = Ember;
|
|
});
|
|
enifed("ember-views/attr_nodes/attr_node",
|
|
["ember-metal/streams/utils","ember-metal/run_loop","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
var read = __dependency1__.read;
|
|
var subscribe = __dependency1__.subscribe;
|
|
var unsubscribe = __dependency1__.unsubscribe;
|
|
var run = __dependency2__["default"];
|
|
|
|
function AttrNode(attrName, attrValue) {
|
|
this.init(attrName, attrValue);
|
|
}
|
|
|
|
AttrNode.prototype.init = function init(attrName, simpleAttrValue){
|
|
this.isView = true;
|
|
|
|
// That these semantics are used is very unfortunate.
|
|
this.tagName = '';
|
|
this.classNameBindings = [];
|
|
|
|
this.attrName = attrName;
|
|
this.attrValue = simpleAttrValue;
|
|
this.isDirty = true;
|
|
this.lastValue = null;
|
|
|
|
subscribe(this.attrValue, this.rerender, this);
|
|
};
|
|
|
|
AttrNode.prototype.renderIfDirty = function renderIfDirty(){
|
|
if (this.isDirty) {
|
|
var value = read(this.attrValue);
|
|
if (value !== this.lastValue) {
|
|
this._renderer.renderTree(this, this._parentView);
|
|
} else {
|
|
this.isDirty = false;
|
|
}
|
|
}
|
|
};
|
|
|
|
AttrNode.prototype.render = function render(buffer) {
|
|
this.isDirty = false;
|
|
var value = read(this.attrValue);
|
|
|
|
this._morph.setContent(value);
|
|
|
|
this.lastValue = value;
|
|
};
|
|
|
|
AttrNode.prototype.rerender = function render() {
|
|
this.isDirty = true;
|
|
run.schedule('render', this, this.renderIfDirty);
|
|
};
|
|
|
|
AttrNode.prototype.destroy = function render() {
|
|
this.isDirty = false;
|
|
unsubscribe(this.attrValue, this.rerender, this);
|
|
|
|
var parent = this._parentView;
|
|
if (parent) { parent.removeChild(this); }
|
|
};
|
|
|
|
__exports__["default"] = AttrNode;
|
|
});
|
|
enifed("ember-views/attr_nodes/legacy_bind",
|
|
["./attr_node","ember-runtime/system/string","ember-metal/utils","ember-metal/streams/utils","ember-metal/platform/create","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-htmlbars
|
|
*/
|
|
|
|
var AttrNode = __dependency1__["default"];
|
|
var fmt = __dependency2__.fmt;
|
|
var typeOf = __dependency3__.typeOf;
|
|
var read = __dependency4__.read;
|
|
var create = __dependency5__["default"];
|
|
|
|
function LegacyBindAttrNode(attrName, attrValue) {
|
|
this.init(attrName, attrValue);
|
|
}
|
|
|
|
LegacyBindAttrNode.prototype = create(AttrNode.prototype);
|
|
|
|
LegacyBindAttrNode.prototype.render = function render(buffer) {
|
|
this.isDirty = false;
|
|
var value = read(this.attrValue);
|
|
var type = typeOf(value);
|
|
|
|
Ember.assert(fmt("Attributes must be numbers, strings or booleans, not %@", [value]),
|
|
value === null || value === undefined || type === 'number' || type === 'string' || type === 'boolean');
|
|
|
|
this._morph.setContent(value);
|
|
|
|
this.lastValue = value;
|
|
};
|
|
|
|
__exports__["default"] = LegacyBindAttrNode;
|
|
});
|
|
enifed("ember-views/component_lookup",
|
|
["ember-runtime/system/object","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var EmberObject = __dependency1__["default"];
|
|
|
|
__exports__["default"] = EmberObject.extend({
|
|
lookupFactory: function(name, container) {
|
|
|
|
container = container || this.container;
|
|
|
|
var fullName = 'component:' + name;
|
|
var templateFullName = 'template:components/' + name;
|
|
var templateRegistered = container && container.has(templateFullName);
|
|
|
|
if (templateRegistered) {
|
|
container.injection(fullName, 'layout', templateFullName);
|
|
}
|
|
|
|
var Component = container.lookupFactory(fullName);
|
|
|
|
// Only treat as a component if either the component
|
|
// or a template has been registered.
|
|
if (templateRegistered || Component) {
|
|
if (!Component) {
|
|
container.register(fullName, Ember.Component);
|
|
Component = container.lookupFactory(fullName);
|
|
}
|
|
return Component;
|
|
}
|
|
}
|
|
});
|
|
});
|
|
enifed("ember-views/mixins/component_template_deprecation",
|
|
["ember-metal/core","ember-metal/property_get","ember-metal/mixin","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.deprecate
|
|
var get = __dependency2__.get;
|
|
var Mixin = __dependency3__.Mixin;
|
|
|
|
/**
|
|
The ComponentTemplateDeprecation mixin is used to provide a useful
|
|
deprecation warning when using either `template` or `templateName` with
|
|
a component. The `template` and `templateName` properties specified at
|
|
extend time are moved to `layout` and `layoutName` respectively.
|
|
|
|
`Ember.ComponentTemplateDeprecation` is used internally by Ember in
|
|
`Ember.Component`.
|
|
|
|
@class ComponentTemplateDeprecation
|
|
@namespace Ember
|
|
*/
|
|
__exports__["default"] = Mixin.create({
|
|
/**
|
|
@private
|
|
|
|
Moves `templateName` to `layoutName` and `template` to `layout` at extend
|
|
time if a layout is not also specified.
|
|
|
|
Note that this currently modifies the mixin themselves, which is technically
|
|
dubious but is practically of little consequence. This may change in the
|
|
future.
|
|
|
|
@method willMergeMixin
|
|
@since 1.4.0
|
|
*/
|
|
willMergeMixin: function(props) {
|
|
// must call _super here to ensure that the ActionHandler
|
|
// mixin is setup properly (moves actions -> _actions)
|
|
//
|
|
// Calling super is only OK here since we KNOW that
|
|
// there is another Mixin loaded first.
|
|
this._super.apply(this, arguments);
|
|
|
|
var deprecatedProperty, replacementProperty;
|
|
var layoutSpecified = (props.layoutName || props.layout || get(this, 'layoutName'));
|
|
|
|
if (props.templateName && !layoutSpecified) {
|
|
deprecatedProperty = 'templateName';
|
|
replacementProperty = 'layoutName';
|
|
|
|
props.layoutName = props.templateName;
|
|
delete props['templateName'];
|
|
}
|
|
|
|
if (props.template && !layoutSpecified) {
|
|
deprecatedProperty = 'template';
|
|
replacementProperty = 'layout';
|
|
|
|
props.layout = props.template;
|
|
delete props['template'];
|
|
}
|
|
|
|
Ember.deprecate('Do not specify ' + deprecatedProperty + ' on a Component, use ' + replacementProperty + ' instead.', !deprecatedProperty);
|
|
}
|
|
});
|
|
});
|
|
enifed("ember-views/mixins/text_support",
|
|
["ember-metal/property_get","ember-metal/property_set","ember-metal/mixin","ember-runtime/mixins/target_action_support","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-views
|
|
*/
|
|
|
|
var get = __dependency1__.get;
|
|
var set = __dependency2__.set;
|
|
var Mixin = __dependency3__.Mixin;
|
|
var TargetActionSupport = __dependency4__["default"];
|
|
|
|
/**
|
|
`TextSupport` is a shared mixin used by both `Ember.TextField` and
|
|
`Ember.TextArea`. `TextSupport` adds a number of methods that allow you to
|
|
specify a controller action to invoke when a certain event is fired on your
|
|
text field or textarea. The specifed controller action would get the current
|
|
value of the field passed in as the only argument unless the value of
|
|
the field is empty. In that case, the instance of the field itself is passed
|
|
in as the only argument.
|
|
|
|
Let's use the pressing of the escape key as an example. If you wanted to
|
|
invoke a controller action when a user presses the escape key while on your
|
|
field, you would use the `escape-press` attribute on your field like so:
|
|
|
|
```handlebars
|
|
{{! application.hbs}}
|
|
|
|
{{input escape-press='alertUser'}}
|
|
```
|
|
|
|
```javascript
|
|
App = Ember.Application.create();
|
|
|
|
App.ApplicationController = Ember.Controller.extend({
|
|
actions: {
|
|
alertUser: function ( currentValue ) {
|
|
alert( 'escape pressed, current value: ' + currentValue );
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
The following chart is a visual representation of what takes place when the
|
|
escape key is pressed in this scenario:
|
|
|
|
The Template
|
|
+---------------------------+
|
|
| |
|
|
| escape-press='alertUser' |
|
|
| | TextSupport Mixin
|
|
+----+----------------------+ +-------------------------------+
|
|
| | cancel method |
|
|
| escape button pressed | |
|
|
+-------------------------------> | checks for the `escape-press` |
|
|
| attribute and pulls out the |
|
|
+-------------------------------+ | `alertUser` value |
|
|
| action name 'alertUser' +-------------------------------+
|
|
| sent to controller
|
|
v
|
|
Controller
|
|
+------------------------------------------ +
|
|
| |
|
|
| actions: { |
|
|
| alertUser: function( currentValue ){ |
|
|
| alert( 'the esc key was pressed!' ) |
|
|
| } |
|
|
| } |
|
|
| |
|
|
+-------------------------------------------+
|
|
|
|
Here are the events that we currently support along with the name of the
|
|
attribute you would need to use on your field. To reiterate, you would use the
|
|
attribute name like so:
|
|
|
|
```handlebars
|
|
{{input attribute-name='controllerAction'}}
|
|
```
|
|
|
|
+--------------------+----------------+
|
|
| | |
|
|
| event | attribute name |
|
|
+--------------------+----------------+
|
|
| new line inserted | insert-newline |
|
|
| | |
|
|
| enter key pressed | insert-newline |
|
|
| | |
|
|
| cancel key pressed | escape-press |
|
|
| | |
|
|
| focusin | focus-in |
|
|
| | |
|
|
| focusout | focus-out |
|
|
| | |
|
|
| keypress | key-press |
|
|
| | |
|
|
| keyup | key-up |
|
|
| | |
|
|
| keydown | key-down |
|
|
+--------------------+----------------+
|
|
|
|
@class TextSupport
|
|
@namespace Ember
|
|
@uses Ember.TargetActionSupport
|
|
@extends Ember.Mixin
|
|
@private
|
|
*/
|
|
var TextSupport = Mixin.create(TargetActionSupport, {
|
|
value: "",
|
|
|
|
attributeBindings: [
|
|
'autocapitalize',
|
|
'autocorrect',
|
|
'autofocus',
|
|
'disabled',
|
|
'form',
|
|
'maxlength',
|
|
'placeholder',
|
|
'readonly',
|
|
'required',
|
|
'selectionDirection',
|
|
'spellcheck',
|
|
'tabindex',
|
|
'title'
|
|
],
|
|
placeholder: null,
|
|
disabled: false,
|
|
maxlength: null,
|
|
|
|
init: function() {
|
|
this._super();
|
|
this.on("paste", this, this._elementValueDidChange);
|
|
this.on("cut", this, this._elementValueDidChange);
|
|
this.on("input", this, this._elementValueDidChange);
|
|
},
|
|
|
|
/**
|
|
The action to be sent when the user presses the return key.
|
|
|
|
This is similar to the `{{action}}` helper, but is fired when
|
|
the user presses the return key when editing a text field, and sends
|
|
the value of the field as the context.
|
|
|
|
@property action
|
|
@type String
|
|
@default null
|
|
*/
|
|
action: null,
|
|
|
|
/**
|
|
The event that should send the action.
|
|
|
|
Options are:
|
|
|
|
* `enter`: the user pressed enter
|
|
* `keyPress`: the user pressed a key
|
|
|
|
@property onEvent
|
|
@type String
|
|
@default enter
|
|
*/
|
|
onEvent: 'enter',
|
|
|
|
/**
|
|
Whether the `keyUp` event that triggers an `action` to be sent continues
|
|
propagating to other views.
|
|
|
|
By default, when the user presses the return key on their keyboard and
|
|
the text field has an `action` set, the action will be sent to the view's
|
|
controller and the key event will stop propagating.
|
|
|
|
If you would like parent views to receive the `keyUp` event even after an
|
|
action has been dispatched, set `bubbles` to true.
|
|
|
|
@property bubbles
|
|
@type Boolean
|
|
@default false
|
|
*/
|
|
bubbles: false,
|
|
|
|
interpretKeyEvents: function(event) {
|
|
var map = TextSupport.KEY_EVENTS;
|
|
var method = map[event.keyCode];
|
|
|
|
this._elementValueDidChange();
|
|
if (method) { return this[method](event); }
|
|
},
|
|
|
|
_elementValueDidChange: function() {
|
|
set(this, 'value', this.$().val());
|
|
},
|
|
|
|
change: function(event) {
|
|
this._elementValueDidChange(event);
|
|
},
|
|
|
|
/**
|
|
Allows you to specify a controller action to invoke when either the `enter`
|
|
key is pressed or, in the case of the field being a textarea, when a newline
|
|
is inserted. To use this method, give your field an `insert-newline`
|
|
attribute. The value of that attribute should be the name of the action
|
|
in your controller that you wish to invoke.
|
|
|
|
For an example on how to use the `insert-newline` attribute, please
|
|
reference the example near the top of this file.
|
|
|
|
@method insertNewline
|
|
@param {Event} event
|
|
*/
|
|
insertNewline: function(event) {
|
|
sendAction('enter', this, event);
|
|
sendAction('insert-newline', this, event);
|
|
},
|
|
|
|
/**
|
|
Allows you to specify a controller action to invoke when the escape button
|
|
is pressed. To use this method, give your field an `escape-press`
|
|
attribute. The value of that attribute should be the name of the action
|
|
in your controller that you wish to invoke.
|
|
|
|
For an example on how to use the `escape-press` attribute, please reference
|
|
the example near the top of this file.
|
|
|
|
@method cancel
|
|
@param {Event} event
|
|
*/
|
|
cancel: function(event) {
|
|
sendAction('escape-press', this, event);
|
|
},
|
|
|
|
/**
|
|
Allows you to specify a controller action to invoke when a field receives
|
|
focus. To use this method, give your field a `focus-in` attribute. The value
|
|
of that attribute should be the name of the action in your controller
|
|
that you wish to invoke.
|
|
|
|
For an example on how to use the `focus-in` attribute, please reference the
|
|
example near the top of this file.
|
|
|
|
@method focusIn
|
|
@param {Event} event
|
|
*/
|
|
focusIn: function(event) {
|
|
sendAction('focus-in', this, event);
|
|
},
|
|
|
|
/**
|
|
Allows you to specify a controller action to invoke when a field loses
|
|
focus. To use this method, give your field a `focus-out` attribute. The value
|
|
of that attribute should be the name of the action in your controller
|
|
that you wish to invoke.
|
|
|
|
For an example on how to use the `focus-out` attribute, please reference the
|
|
example near the top of this file.
|
|
|
|
@method focusOut
|
|
@param {Event} event
|
|
*/
|
|
focusOut: function(event) {
|
|
this._elementValueDidChange(event);
|
|
sendAction('focus-out', this, event);
|
|
},
|
|
|
|
/**
|
|
Allows you to specify a controller action to invoke when a key is pressed.
|
|
To use this method, give your field a `key-press` attribute. The value of
|
|
that attribute should be the name of the action in your controller you
|
|
that wish to invoke.
|
|
|
|
For an example on how to use the `key-press` attribute, please reference the
|
|
example near the top of this file.
|
|
|
|
@method keyPress
|
|
@param {Event} event
|
|
*/
|
|
keyPress: function(event) {
|
|
sendAction('key-press', this, event);
|
|
},
|
|
|
|
/**
|
|
Allows you to specify a controller action to invoke when a key-up event is
|
|
fired. To use this method, give your field a `key-up` attribute. The value
|
|
of that attribute should be the name of the action in your controller
|
|
that you wish to invoke.
|
|
|
|
For an example on how to use the `key-up` attribute, please reference the
|
|
example near the top of this file.
|
|
|
|
@method keyUp
|
|
@param {Event} event
|
|
*/
|
|
keyUp: function(event) {
|
|
this.interpretKeyEvents(event);
|
|
|
|
this.sendAction('key-up', get(this, 'value'), event);
|
|
},
|
|
|
|
/**
|
|
Allows you to specify a controller action to invoke when a key-down event is
|
|
fired. To use this method, give your field a `key-down` attribute. The value
|
|
of that attribute should be the name of the action in your controller that
|
|
you wish to invoke.
|
|
|
|
For an example on how to use the `key-down` attribute, please reference the
|
|
example near the top of this file.
|
|
|
|
@method keyDown
|
|
@param {Event} event
|
|
*/
|
|
keyDown: function(event) {
|
|
this.sendAction('key-down', get(this, 'value'), event);
|
|
}
|
|
});
|
|
|
|
TextSupport.KEY_EVENTS = {
|
|
13: 'insertNewline',
|
|
27: 'cancel'
|
|
};
|
|
|
|
// In principle, this shouldn't be necessary, but the legacy
|
|
// sendAction semantics for TextField are different from
|
|
// the component semantics so this method normalizes them.
|
|
function sendAction(eventName, view, event) {
|
|
var action = get(view, eventName);
|
|
var on = get(view, 'onEvent');
|
|
var value = get(view, 'value');
|
|
|
|
// back-compat support for keyPress as an event name even though
|
|
// it's also a method name that consumes the event (and therefore
|
|
// incompatible with sendAction semantics).
|
|
if (on === eventName || (on === 'keyPress' && eventName === 'key-press')) {
|
|
view.sendAction('action', value);
|
|
}
|
|
|
|
view.sendAction(eventName, value);
|
|
|
|
if (action || on === eventName) {
|
|
if(!get(view, 'bubbles')) {
|
|
event.stopPropagation();
|
|
}
|
|
}
|
|
}
|
|
|
|
__exports__["default"] = TextSupport;
|
|
});
|
|
enifed("ember-views/mixins/view_target_action_support",
|
|
["ember-metal/mixin","ember-runtime/mixins/target_action_support","ember-metal/alias","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
var Mixin = __dependency1__.Mixin;
|
|
var TargetActionSupport = __dependency2__["default"];
|
|
var alias = __dependency3__["default"];
|
|
|
|
/**
|
|
`Ember.ViewTargetActionSupport` is a mixin that can be included in a
|
|
view class to add a `triggerAction` method with semantics similar to
|
|
the Handlebars `{{action}}` helper. It provides intelligent defaults
|
|
for the action's target: the view's controller; and the context that is
|
|
sent with the action: the view's context.
|
|
|
|
Note: In normal Ember usage, the `{{action}}` helper is usually the best
|
|
choice. This mixin is most often useful when you are doing more complex
|
|
event handling in custom View subclasses.
|
|
|
|
For example:
|
|
|
|
```javascript
|
|
App.SaveButtonView = Ember.View.extend(Ember.ViewTargetActionSupport, {
|
|
action: 'save',
|
|
click: function() {
|
|
this.triggerAction(); // Sends the `save` action, along with the current context
|
|
// to the current controller
|
|
}
|
|
});
|
|
```
|
|
|
|
The `action` can be provided as properties of an optional object argument
|
|
to `triggerAction` as well.
|
|
|
|
```javascript
|
|
App.SaveButtonView = Ember.View.extend(Ember.ViewTargetActionSupport, {
|
|
click: function() {
|
|
this.triggerAction({
|
|
action: 'save'
|
|
}); // Sends the `save` action, along with the current context
|
|
// to the current controller
|
|
}
|
|
});
|
|
```
|
|
|
|
@class ViewTargetActionSupport
|
|
@namespace Ember
|
|
@extends Ember.TargetActionSupport
|
|
*/
|
|
__exports__["default"] = Mixin.create(TargetActionSupport, {
|
|
/**
|
|
@property target
|
|
*/
|
|
target: alias('controller'),
|
|
/**
|
|
@property actionContext
|
|
*/
|
|
actionContext: alias('context')
|
|
});
|
|
});
|
|
enifed("ember-views/streams/class_name_binding",
|
|
["ember-metal/streams/utils","ember-metal/property_get","ember-runtime/system/string","ember-metal/utils","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
|
|
"use strict";
|
|
var chain = __dependency1__.chain;
|
|
var read = __dependency1__.read;
|
|
var get = __dependency2__.get;
|
|
var dasherize = __dependency3__.dasherize;
|
|
var isArray = __dependency4__.isArray;
|
|
|
|
/**
|
|
Parse a path and return an object which holds the parsed properties.
|
|
|
|
For example a path like "content.isEnabled:enabled:disabled" will return the
|
|
following object:
|
|
|
|
```javascript
|
|
{
|
|
path: "content.isEnabled",
|
|
className: "enabled",
|
|
falsyClassName: "disabled",
|
|
classNames: ":enabled:disabled"
|
|
}
|
|
```
|
|
|
|
@method parsePropertyPath
|
|
@static
|
|
@private
|
|
*/
|
|
function parsePropertyPath(path) {
|
|
var split = path.split(':');
|
|
var propertyPath = split[0];
|
|
var classNames = "";
|
|
var className, falsyClassName;
|
|
|
|
// check if the property is defined as prop:class or prop:trueClass:falseClass
|
|
if (split.length > 1) {
|
|
className = split[1];
|
|
if (split.length === 3) {
|
|
falsyClassName = split[2];
|
|
}
|
|
|
|
classNames = ':' + className;
|
|
if (falsyClassName) {
|
|
classNames += ":" + falsyClassName;
|
|
}
|
|
}
|
|
|
|
return {
|
|
path: propertyPath,
|
|
classNames: classNames,
|
|
className: (className === '') ? undefined : className,
|
|
falsyClassName: falsyClassName
|
|
};
|
|
}
|
|
|
|
__exports__.parsePropertyPath = parsePropertyPath;/**
|
|
Get the class name for a given value, based on the path, optional
|
|
`className` and optional `falsyClassName`.
|
|
|
|
- if a `className` or `falsyClassName` has been specified:
|
|
- if the value is truthy and `className` has been specified,
|
|
`className` is returned
|
|
- if the value is falsy and `falsyClassName` has been specified,
|
|
`falsyClassName` is returned
|
|
- otherwise `null` is returned
|
|
- if the value is `true`, the dasherized last part of the supplied path
|
|
is returned
|
|
- if the value is not `false`, `undefined` or `null`, the `value`
|
|
is returned
|
|
- if none of the above rules apply, `null` is returned
|
|
|
|
@method classStringForValue
|
|
@param path
|
|
@param val
|
|
@param className
|
|
@param falsyClassName
|
|
@static
|
|
@private
|
|
*/
|
|
function classStringForValue(path, val, className, falsyClassName) {
|
|
if(isArray(val)) {
|
|
val = get(val, 'length') !== 0;
|
|
}
|
|
|
|
// When using the colon syntax, evaluate the truthiness or falsiness
|
|
// of the value to determine which className to return
|
|
if (className || falsyClassName) {
|
|
if (className && !!val) {
|
|
return className;
|
|
|
|
} else if (falsyClassName && !val) {
|
|
return falsyClassName;
|
|
|
|
} else {
|
|
return null;
|
|
}
|
|
|
|
// If value is a Boolean and true, return the dasherized property
|
|
// name.
|
|
} else if (val === true) {
|
|
// Normalize property path to be suitable for use
|
|
// as a class name. For exaple, content.foo.barBaz
|
|
// becomes bar-baz.
|
|
var parts = path.split('.');
|
|
return dasherize(parts[parts.length-1]);
|
|
|
|
// If the value is not false, undefined, or null, return the current
|
|
// value of the property.
|
|
} else if (val !== false && val != null) {
|
|
return val;
|
|
|
|
// Nothing to display. Return null so that the old class is removed
|
|
// but no new class is added.
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
__exports__.classStringForValue = classStringForValue;function streamifyClassNameBinding(view, classNameBinding, prefix){
|
|
prefix = prefix || '';
|
|
Ember.assert("classNameBindings must not have spaces in them. Multiple class name bindings can be provided as elements of an array, e.g. ['foo', ':bar']", classNameBinding.indexOf(' ') === -1);
|
|
var parsedPath = parsePropertyPath(classNameBinding);
|
|
if (parsedPath.path === '') {
|
|
return classStringForValue(
|
|
parsedPath.path,
|
|
true,
|
|
parsedPath.className,
|
|
parsedPath.falsyClassName
|
|
);
|
|
} else {
|
|
var pathValue = view.getStream(prefix+parsedPath.path);
|
|
return chain(pathValue, function() {
|
|
return classStringForValue(
|
|
parsedPath.path,
|
|
read(pathValue),
|
|
parsedPath.className,
|
|
parsedPath.falsyClassName
|
|
);
|
|
});
|
|
}
|
|
}
|
|
|
|
__exports__.streamifyClassNameBinding = streamifyClassNameBinding;
|
|
});
|
|
enifed("ember-views/streams/conditional_stream",
|
|
["ember-metal/streams/stream","ember-metal/streams/utils","ember-metal/platform","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
var Stream = __dependency1__["default"];
|
|
var read = __dependency2__.read;
|
|
var subscribe = __dependency2__.subscribe;
|
|
var unsubscribe = __dependency2__.unsubscribe;
|
|
var create = __dependency3__.create;
|
|
|
|
function ConditionalStream(test, consequent, alternate) {
|
|
this.init();
|
|
|
|
this.oldTestResult = undefined;
|
|
this.test = test;
|
|
this.consequent = consequent;
|
|
this.alternate = alternate;
|
|
}
|
|
|
|
ConditionalStream.prototype = create(Stream.prototype);
|
|
|
|
ConditionalStream.prototype.valueFn = function() {
|
|
var oldTestResult = this.oldTestResult;
|
|
var newTestResult = !!read(this.test);
|
|
|
|
if (newTestResult !== oldTestResult) {
|
|
switch (oldTestResult) {
|
|
case true: unsubscribe(this.consequent, this.notify, this); break;
|
|
case false: unsubscribe(this.alternate, this.notify, this); break;
|
|
case undefined: subscribe(this.test, this.notify, this);
|
|
}
|
|
|
|
switch (newTestResult) {
|
|
case true: subscribe(this.consequent, this.notify, this); break;
|
|
case false: subscribe(this.alternate, this.notify, this);
|
|
}
|
|
|
|
this.oldTestResult = newTestResult;
|
|
}
|
|
|
|
return newTestResult ? read(this.consequent) : read(this.alternate);
|
|
};
|
|
|
|
__exports__["default"] = ConditionalStream;
|
|
});
|
|
enifed("ember-views/streams/context_stream",
|
|
["ember-metal/core","ember-metal/merge","ember-metal/platform","ember-metal/path_cache","ember-metal/streams/stream","ember-metal/streams/simple","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
|
|
var merge = __dependency2__["default"];
|
|
var create = __dependency3__.create;
|
|
var isGlobal = __dependency4__.isGlobal;
|
|
var Stream = __dependency5__["default"];
|
|
var SimpleStream = __dependency6__["default"];
|
|
|
|
function ContextStream(view) {
|
|
Ember.assert("ContextStream error: the argument is not a view", view && view.isView);
|
|
|
|
this.init();
|
|
this.view = view;
|
|
}
|
|
|
|
ContextStream.prototype = create(Stream.prototype);
|
|
|
|
merge(ContextStream.prototype, {
|
|
value: function() {},
|
|
|
|
_makeChildStream: function(key, _fullPath) {
|
|
var stream;
|
|
|
|
if (key === '' || key === 'this') {
|
|
stream = this.view._baseContext;
|
|
} else if (isGlobal(key) && Ember.lookup[key]) {
|
|
Ember.deprecate("Global lookup of " + _fullPath + " from a Handlebars template is deprecated.");
|
|
stream = new SimpleStream(Ember.lookup[key]);
|
|
stream._isGlobal = true;
|
|
} else if (key in this.view._keywords) {
|
|
stream = new SimpleStream(this.view._keywords[key]);
|
|
} else {
|
|
stream = new SimpleStream(this.view._baseContext.get(key));
|
|
}
|
|
|
|
stream._isRoot = true;
|
|
|
|
if (key === 'controller') {
|
|
stream._isController = true;
|
|
}
|
|
|
|
return stream;
|
|
}
|
|
});
|
|
|
|
__exports__["default"] = ContextStream;
|
|
});
|
|
enifed("ember-views/streams/key_stream",
|
|
["ember-metal/core","ember-metal/merge","ember-metal/platform","ember-metal/property_get","ember-metal/property_set","ember-metal/observer","ember-metal/streams/stream","ember-metal/streams/utils","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
|
|
var merge = __dependency2__["default"];
|
|
var create = __dependency3__.create;
|
|
var get = __dependency4__.get;
|
|
var set = __dependency5__.set;
|
|
var addObserver = __dependency6__.addObserver;
|
|
var removeObserver = __dependency6__.removeObserver;
|
|
var Stream = __dependency7__["default"];
|
|
var read = __dependency8__.read;
|
|
var isStream = __dependency8__.isStream;
|
|
|
|
function KeyStream(source, key) {
|
|
Ember.assert("KeyStream error: key must be a non-empty string", typeof key === 'string' && key.length > 0);
|
|
Ember.assert("KeyStream error: key must not have a '.'", key.indexOf('.') === -1);
|
|
|
|
this.init();
|
|
this.source = source;
|
|
this.obj = undefined;
|
|
this.key = key;
|
|
|
|
if (isStream(source)) {
|
|
source.subscribe(this._didChange, this);
|
|
}
|
|
}
|
|
|
|
KeyStream.prototype = create(Stream.prototype);
|
|
|
|
merge(KeyStream.prototype, {
|
|
valueFn: function() {
|
|
var prevObj = this.obj;
|
|
var nextObj = read(this.source);
|
|
|
|
if (nextObj !== prevObj) {
|
|
if (prevObj && typeof prevObj === 'object') {
|
|
removeObserver(prevObj, this.key, this, this._didChange);
|
|
}
|
|
|
|
if (nextObj && typeof nextObj === 'object') {
|
|
addObserver(nextObj, this.key, this, this._didChange);
|
|
}
|
|
|
|
this.obj = nextObj;
|
|
}
|
|
|
|
if (nextObj) {
|
|
return get(nextObj, this.key);
|
|
}
|
|
},
|
|
|
|
setValue: function(value) {
|
|
if (this.obj) {
|
|
set(this.obj, this.key, value);
|
|
}
|
|
},
|
|
|
|
setSource: function(nextSource) {
|
|
Ember.assert("KeyStream error: source must be an object", typeof nextSource === 'object');
|
|
|
|
var prevSource = this.source;
|
|
|
|
if (nextSource !== prevSource) {
|
|
if (isStream(prevSource)) {
|
|
prevSource.unsubscribe(this._didChange, this);
|
|
}
|
|
|
|
if (isStream(nextSource)) {
|
|
nextSource.subscribe(this._didChange, this);
|
|
}
|
|
|
|
this.source = nextSource;
|
|
this.notify();
|
|
}
|
|
},
|
|
|
|
_didChange: function() {
|
|
this.notify();
|
|
},
|
|
|
|
_super$destroy: Stream.prototype.destroy,
|
|
|
|
destroy: function() {
|
|
if (this._super$destroy()) {
|
|
if (isStream(this.source)) {
|
|
this.source.unsubscribe(this._didChange, this);
|
|
}
|
|
|
|
if (this.obj && typeof this.obj === 'object') {
|
|
removeObserver(this.obj, this.key, this, this._didChange);
|
|
}
|
|
|
|
this.source = undefined;
|
|
this.obj = undefined;
|
|
return true;
|
|
}
|
|
}
|
|
});
|
|
|
|
__exports__["default"] = KeyStream;
|
|
|
|
// The transpiler does not resolve cycles, so we export
|
|
// the `_makeChildStream` method onto `Stream` here.
|
|
|
|
Stream.prototype._makeChildStream = function(key) {
|
|
return new KeyStream(this, key);
|
|
};
|
|
});
|
|
enifed("ember-views/streams/utils",
|
|
["ember-metal/core","ember-metal/property_get","ember-metal/path_cache","ember-runtime/system/string","ember-metal/streams/utils","ember-views/views/view","ember-runtime/mixins/controller","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
var get = __dependency2__.get;
|
|
var isGlobal = __dependency3__.isGlobal;
|
|
var fmt = __dependency4__.fmt;
|
|
var read = __dependency5__.read;
|
|
var isStream = __dependency5__.isStream;
|
|
var View = __dependency6__["default"];
|
|
var ControllerMixin = __dependency7__["default"];
|
|
|
|
function readViewFactory(object, container) {
|
|
var value = read(object);
|
|
var viewClass;
|
|
|
|
if (typeof value === 'string') {
|
|
if (isGlobal(value)) {
|
|
viewClass = get(null, value);
|
|
Ember.deprecate('Resolved the view "'+value+'" on the global context. Pass a view name to be looked up on the container instead, such as {{view "select"}}.', !viewClass, { url: 'http://emberjs.com/guides/deprecations/#toc_global-lookup-of-views' });
|
|
} else {
|
|
Ember.assert("View requires a container to resolve views not passed in through the context", !!container);
|
|
viewClass = container.lookupFactory('view:'+value);
|
|
}
|
|
} else {
|
|
viewClass = value;
|
|
}
|
|
|
|
Ember.assert(fmt(value+" must be a subclass or an instance of Ember.View, not %@", [viewClass]), View.detect(viewClass) || View.detectInstance(viewClass));
|
|
|
|
return viewClass;
|
|
}
|
|
|
|
__exports__.readViewFactory = readViewFactory;function readUnwrappedModel(object) {
|
|
if (isStream(object)) {
|
|
var result = object.value();
|
|
|
|
// If the path is exactly `controller` then we don't unwrap it.
|
|
if (!object._isController) {
|
|
while (ControllerMixin.detect(result)) {
|
|
result = get(result, 'model');
|
|
}
|
|
}
|
|
|
|
return result;
|
|
} else {
|
|
return object;
|
|
}
|
|
}
|
|
|
|
__exports__.readUnwrappedModel = readUnwrappedModel;
|
|
});
|
|
enifed("ember-views/system/action_manager",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-views
|
|
*/
|
|
|
|
function ActionManager() {}
|
|
|
|
/**
|
|
Global action id hash.
|
|
|
|
@private
|
|
@property registeredActions
|
|
@type Object
|
|
*/
|
|
ActionManager.registeredActions = {};
|
|
|
|
__exports__["default"] = ActionManager;
|
|
});
|
|
enifed("ember-views/system/event_dispatcher",
|
|
["ember-metal/core","ember-metal/property_get","ember-metal/property_set","ember-metal/is_none","ember-metal/run_loop","ember-metal/utils","ember-runtime/system/string","ember-runtime/system/object","ember-views/system/jquery","ember-views/system/action_manager","ember-views/views/view","ember-metal/merge","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-views
|
|
*/
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.assert
|
|
|
|
var get = __dependency2__.get;
|
|
var set = __dependency3__.set;
|
|
var isNone = __dependency4__["default"];
|
|
var run = __dependency5__["default"];
|
|
var typeOf = __dependency6__.typeOf;
|
|
var fmt = __dependency7__.fmt;
|
|
var EmberObject = __dependency8__["default"];
|
|
var jQuery = __dependency9__["default"];
|
|
var ActionManager = __dependency10__["default"];
|
|
var View = __dependency11__["default"];
|
|
var merge = __dependency12__["default"];
|
|
|
|
//ES6TODO:
|
|
// find a better way to do Ember.View.views without global state
|
|
|
|
/**
|
|
`Ember.EventDispatcher` handles delegating browser events to their
|
|
corresponding `Ember.Views.` For example, when you click on a view,
|
|
`Ember.EventDispatcher` ensures that that view's `mouseDown` method gets
|
|
called.
|
|
|
|
@class EventDispatcher
|
|
@namespace Ember
|
|
@private
|
|
@extends Ember.Object
|
|
*/
|
|
__exports__["default"] = EmberObject.extend({
|
|
|
|
/**
|
|
The set of events names (and associated handler function names) to be setup
|
|
and dispatched by the `EventDispatcher`. Custom events can added to this list at setup
|
|
time, generally via the `Ember.Application.customEvents` hash. Only override this
|
|
default set to prevent the EventDispatcher from listening on some events all together.
|
|
|
|
This set will be modified by `setup` to also include any events added at that time.
|
|
|
|
@property events
|
|
@type Object
|
|
*/
|
|
events: {
|
|
touchstart : 'touchStart',
|
|
touchmove : 'touchMove',
|
|
touchend : 'touchEnd',
|
|
touchcancel : 'touchCancel',
|
|
keydown : 'keyDown',
|
|
keyup : 'keyUp',
|
|
keypress : 'keyPress',
|
|
mousedown : 'mouseDown',
|
|
mouseup : 'mouseUp',
|
|
contextmenu : 'contextMenu',
|
|
click : 'click',
|
|
dblclick : 'doubleClick',
|
|
mousemove : 'mouseMove',
|
|
focusin : 'focusIn',
|
|
focusout : 'focusOut',
|
|
mouseenter : 'mouseEnter',
|
|
mouseleave : 'mouseLeave',
|
|
submit : 'submit',
|
|
input : 'input',
|
|
change : 'change',
|
|
dragstart : 'dragStart',
|
|
drag : 'drag',
|
|
dragenter : 'dragEnter',
|
|
dragleave : 'dragLeave',
|
|
dragover : 'dragOver',
|
|
drop : 'drop',
|
|
dragend : 'dragEnd'
|
|
},
|
|
|
|
/**
|
|
The root DOM element to which event listeners should be attached. Event
|
|
listeners will be attached to the document unless this is overridden.
|
|
|
|
Can be specified as a DOMElement or a selector string.
|
|
|
|
The default body is a string since this may be evaluated before document.body
|
|
exists in the DOM.
|
|
|
|
@private
|
|
@property rootElement
|
|
@type DOMElement
|
|
@default 'body'
|
|
*/
|
|
rootElement: 'body',
|
|
|
|
/**
|
|
It enables events to be dispatched to the view's `eventManager.` When present,
|
|
this object takes precedence over handling of events on the view itself.
|
|
|
|
Note that most Ember applications do not use this feature. If your app also
|
|
does not use it, consider setting this property to false to gain some performance
|
|
improvement by allowing the EventDispatcher to skip the search for the
|
|
`eventManager` on the view tree.
|
|
|
|
```javascript
|
|
var EventDispatcher = Em.EventDispatcher.extend({
|
|
events: {
|
|
click : 'click',
|
|
focusin : 'focusIn',
|
|
focusout : 'focusOut',
|
|
change : 'change'
|
|
},
|
|
canDispatchToEventManager: false
|
|
});
|
|
container.register('event_dispatcher:main', EventDispatcher);
|
|
```
|
|
|
|
@property canDispatchToEventManager
|
|
@type boolean
|
|
@default 'true'
|
|
@since 1.7.0
|
|
*/
|
|
canDispatchToEventManager: true,
|
|
|
|
/**
|
|
Sets up event listeners for standard browser events.
|
|
|
|
This will be called after the browser sends a `DOMContentReady` event. By
|
|
default, it will set up all of the listeners on the document body. If you
|
|
would like to register the listeners on a different element, set the event
|
|
dispatcher's `root` property.
|
|
|
|
@private
|
|
@method setup
|
|
@param addedEvents {Hash}
|
|
*/
|
|
setup: function(addedEvents, rootElement) {
|
|
var event, events = get(this, 'events');
|
|
|
|
merge(events, addedEvents || {});
|
|
|
|
if (!isNone(rootElement)) {
|
|
set(this, 'rootElement', rootElement);
|
|
}
|
|
|
|
rootElement = jQuery(get(this, 'rootElement'));
|
|
|
|
Ember.assert(fmt('You cannot use the same root element (%@) multiple times in an Ember.Application', [rootElement.selector || rootElement[0].tagName]), !rootElement.is('.ember-application'));
|
|
Ember.assert('You cannot make a new Ember.Application using a root element that is a descendent of an existing Ember.Application', !rootElement.closest('.ember-application').length);
|
|
Ember.assert('You cannot make a new Ember.Application using a root element that is an ancestor of an existing Ember.Application', !rootElement.find('.ember-application').length);
|
|
|
|
rootElement.addClass('ember-application');
|
|
|
|
Ember.assert('Unable to add "ember-application" class to rootElement. Make sure you set rootElement to the body or an element in the body.', rootElement.is('.ember-application'));
|
|
|
|
for (event in events) {
|
|
if (events.hasOwnProperty(event)) {
|
|
this.setupHandler(rootElement, event, events[event]);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
Registers an event listener on the rootElement. If the given event is
|
|
triggered, the provided event handler will be triggered on the target view.
|
|
|
|
If the target view does not implement the event handler, or if the handler
|
|
returns `false`, the parent view will be called. The event will continue to
|
|
bubble to each successive parent view until it reaches the top.
|
|
|
|
@private
|
|
@method setupHandler
|
|
@param {Element} rootElement
|
|
@param {String} event the browser-originated event to listen to
|
|
@param {String} eventName the name of the method to call on the view
|
|
*/
|
|
setupHandler: function(rootElement, event, eventName) {
|
|
var self = this;
|
|
|
|
rootElement.on(event + '.ember', '.ember-view', function(evt, triggeringManager) {
|
|
var view = View.views[this.id];
|
|
var result = true;
|
|
|
|
var manager = self.canDispatchToEventManager ? self._findNearestEventManager(view, eventName) : null;
|
|
|
|
if (manager && manager !== triggeringManager) {
|
|
result = self._dispatchEvent(manager, evt, eventName, view);
|
|
} else if (view) {
|
|
result = self._bubbleEvent(view, evt, eventName);
|
|
}
|
|
|
|
return result;
|
|
});
|
|
|
|
rootElement.on(event + '.ember', '[data-ember-action]', function(evt) {
|
|
var actionId = jQuery(evt.currentTarget).attr('data-ember-action');
|
|
var action = ActionManager.registeredActions[actionId];
|
|
|
|
// We have to check for action here since in some cases, jQuery will trigger
|
|
// an event on `removeChild` (i.e. focusout) after we've already torn down the
|
|
// action handlers for the view.
|
|
if (action && action.eventName === eventName) {
|
|
return action.handler(evt);
|
|
}
|
|
});
|
|
},
|
|
|
|
_findNearestEventManager: function(view, eventName) {
|
|
var manager = null;
|
|
|
|
while (view) {
|
|
manager = get(view, 'eventManager');
|
|
if (manager && manager[eventName]) { break; }
|
|
|
|
view = get(view, 'parentView');
|
|
}
|
|
|
|
return manager;
|
|
},
|
|
|
|
_dispatchEvent: function(object, evt, eventName, view) {
|
|
var result = true;
|
|
|
|
var handler = object[eventName];
|
|
if (typeOf(handler) === 'function') {
|
|
result = run(object, handler, evt, view);
|
|
// Do not preventDefault in eventManagers.
|
|
evt.stopPropagation();
|
|
}
|
|
else {
|
|
result = this._bubbleEvent(view, evt, eventName);
|
|
}
|
|
|
|
return result;
|
|
},
|
|
|
|
_bubbleEvent: function(view, evt, eventName) {
|
|
return run.join(view, view.handleEvent, eventName, evt);
|
|
},
|
|
|
|
destroy: function() {
|
|
var rootElement = get(this, 'rootElement');
|
|
jQuery(rootElement).off('.ember', '**').removeClass('ember-application');
|
|
return this._super();
|
|
},
|
|
|
|
toString: function() {
|
|
return '(EventDispatcher)';
|
|
}
|
|
});
|
|
});
|
|
enifed("ember-views/system/ext",
|
|
["ember-metal/run_loop"],
|
|
function(__dependency1__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-views
|
|
*/
|
|
|
|
var run = __dependency1__["default"];
|
|
|
|
// Add a new named queue for rendering views that happens
|
|
// after bindings have synced, and a queue for scheduling actions
|
|
// that that should occur after view rendering.
|
|
run._addQueue('render', 'actions');
|
|
run._addQueue('afterRender', 'render');
|
|
});
|
|
enifed("ember-views/system/jquery",
|
|
["ember-metal/core","ember-metal/enumerable_utils","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.assert
|
|
|
|
// ES6TODO: the functions on EnumerableUtils need their own exports
|
|
var forEach = __dependency2__.forEach;
|
|
|
|
/**
|
|
Ember Views
|
|
|
|
@module ember
|
|
@submodule ember-views
|
|
@requires ember-runtime
|
|
@main ember-views
|
|
*/
|
|
|
|
var jQuery = (Ember.imports && Ember.imports.jQuery) || (this && this.jQuery);
|
|
if (!jQuery && typeof eriuqer === 'function') {
|
|
jQuery = eriuqer('jquery');
|
|
}
|
|
|
|
Ember.assert("Ember Views require jQuery between 1.7 and 2.1", jQuery &&
|
|
(jQuery().jquery.match(/^((1\.(7|8|9|10|11))|(2\.(0|1)))(\.\d+)?(pre|rc\d?)?/) ||
|
|
Ember.ENV.FORCE_JQUERY));
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-views
|
|
*/
|
|
if (jQuery) {
|
|
// http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#dndevents
|
|
var dragEvents = [
|
|
'dragstart',
|
|
'drag',
|
|
'dragenter',
|
|
'dragleave',
|
|
'dragover',
|
|
'drop',
|
|
'dragend'
|
|
];
|
|
|
|
// Copies the `dataTransfer` property from a browser event object onto the
|
|
// jQuery event object for the specified events
|
|
forEach(dragEvents, function(eventName) {
|
|
jQuery.event.fixHooks[eventName] = {
|
|
props: ['dataTransfer']
|
|
};
|
|
});
|
|
}
|
|
|
|
__exports__["default"] = jQuery;
|
|
});
|
|
enifed("ember-views/system/render_buffer",
|
|
["ember-views/system/jquery","morph","ember-metal/core","ember-metal/platform","morph/dom-helper/prop","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-views
|
|
*/
|
|
|
|
var jQuery = __dependency1__["default"];
|
|
var DOMHelper = __dependency2__.DOMHelper;
|
|
var Ember = __dependency3__["default"];
|
|
var create = __dependency4__.create;
|
|
var normalizeProperty = __dependency5__.normalizeProperty;
|
|
|
|
// The HTML spec allows for "omitted start tags". These tags are optional
|
|
// when their intended child is the first thing in the parent tag. For
|
|
// example, this is a tbody start tag:
|
|
//
|
|
// <table>
|
|
// <tbody>
|
|
// <tr>
|
|
//
|
|
// The tbody may be omitted, and the browser will accept and render:
|
|
//
|
|
// <table>
|
|
// <tr>
|
|
//
|
|
// However, the omitted start tag will still be added to the DOM. Here
|
|
// we test the string and context to see if the browser is about to
|
|
// perform this cleanup, but with a special allowance for disregarding
|
|
// <script tags. This disregarding of <script being the first child item
|
|
// may bend the official spec a bit, and is only needed for Handlebars
|
|
// templates.
|
|
//
|
|
// http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#optional-tags
|
|
// describes which tags are omittable. The spec for tbody and colgroup
|
|
// explains this behavior:
|
|
//
|
|
// http://www.whatwg.org/specs/web-apps/current-work/multipage/tables.html#the-tbody-element
|
|
// http://www.whatwg.org/specs/web-apps/current-work/multipage/tables.html#the-colgroup-element
|
|
//
|
|
var omittedStartTagChildren = {
|
|
tr: document.createElement('tbody'),
|
|
col: document.createElement('colgroup')
|
|
};
|
|
|
|
var omittedStartTagChildTest = /(?:<script)*.*?<([\w:]+)/i;
|
|
|
|
function detectOmittedStartTag(string, contextualElement){
|
|
// Omitted start tags are only inside table tags.
|
|
if (contextualElement.tagName === 'TABLE') {
|
|
var omittedStartTagChildMatch = omittedStartTagChildTest.exec(string);
|
|
if (omittedStartTagChildMatch) {
|
|
// It is already asserted that the contextual element is a table
|
|
// and not the proper start tag. Just look up the start tag.
|
|
return omittedStartTagChildren[omittedStartTagChildMatch[1].toLowerCase()];
|
|
}
|
|
}
|
|
}
|
|
|
|
function ClassSet() {
|
|
this.seen = create(null);
|
|
this.list = [];
|
|
}
|
|
|
|
ClassSet.prototype = {
|
|
add: function(string) {
|
|
if (this.seen[string] === true) { return; }
|
|
this.seen[string] = true;
|
|
|
|
this.list.push(string);
|
|
}
|
|
};
|
|
|
|
var BAD_TAG_NAME_TEST_REGEXP = /[^a-zA-Z0-9\-]/;
|
|
var BAD_TAG_NAME_REPLACE_REGEXP = /[^a-zA-Z0-9\-]/g;
|
|
|
|
function stripTagName(tagName) {
|
|
if (!tagName) {
|
|
return tagName;
|
|
}
|
|
|
|
if (!BAD_TAG_NAME_TEST_REGEXP.test(tagName)) {
|
|
return tagName;
|
|
}
|
|
|
|
return tagName.replace(BAD_TAG_NAME_REPLACE_REGEXP, '');
|
|
}
|
|
|
|
var BAD_CHARS_REGEXP = /&(?!\w+;)|[<>"'`]/g;
|
|
var POSSIBLE_CHARS_REGEXP = /[&<>"'`]/;
|
|
|
|
function escapeAttribute(value) {
|
|
// Stolen shamelessly from Handlebars
|
|
|
|
var escape = {
|
|
"<": "<",
|
|
">": ">",
|
|
'"': """,
|
|
"'": "'",
|
|
"`": "`"
|
|
};
|
|
|
|
var escapeChar = function(chr) {
|
|
return escape[chr] || "&";
|
|
};
|
|
|
|
var string = value.toString();
|
|
|
|
if(!POSSIBLE_CHARS_REGEXP.test(string)) { return string; }
|
|
return string.replace(BAD_CHARS_REGEXP, escapeChar);
|
|
}
|
|
|
|
// IE 6/7 have bugs around setting names on inputs during creation.
|
|
// From http://msdn.microsoft.com/en-us/library/ie/ms536389(v=vs.85).aspx:
|
|
// "To include the NAME attribute at run time on objects created with the createElement method, use the eTag."
|
|
var canSetNameOnInputs = (function() {
|
|
var div = document.createElement('div');
|
|
var el = document.createElement('input');
|
|
|
|
el.setAttribute('name', 'foo');
|
|
div.appendChild(el);
|
|
|
|
return !!div.innerHTML.match('foo');
|
|
})();
|
|
|
|
/**
|
|
`Ember.renderBuffer` gathers information regarding the view and generates the
|
|
final representation. `Ember.renderBuffer` will generate HTML which can be pushed
|
|
to the DOM.
|
|
|
|
```javascript
|
|
var buffer = Ember.renderBuffer('div', contextualElement);
|
|
```
|
|
|
|
@method renderBuffer
|
|
@namespace Ember
|
|
@param {String} tagName tag name (such as 'div' or 'p') used for the buffer
|
|
*/
|
|
__exports__["default"] = function renderBuffer(tagName, contextualElement) {
|
|
return new _RenderBuffer(tagName, contextualElement); // jshint ignore:line
|
|
}
|
|
|
|
function _RenderBuffer(tagName, contextualElement) {
|
|
this.tagName = tagName;
|
|
this._outerContextualElement = contextualElement;
|
|
this.buffer = null;
|
|
this.childViews = [];
|
|
this.dom = new DOMHelper();
|
|
}
|
|
|
|
_RenderBuffer.prototype = {
|
|
|
|
reset: function(tagName, contextualElement) {
|
|
this.tagName = tagName;
|
|
this.buffer = null;
|
|
this._element = null;
|
|
this._outerContextualElement = contextualElement;
|
|
this.elementClasses = null;
|
|
this.elementId = null;
|
|
this.elementAttributes = null;
|
|
this.elementProperties = null;
|
|
this.elementTag = null;
|
|
this.elementStyle = null;
|
|
this.childViews.length = 0;
|
|
},
|
|
|
|
// The root view's element
|
|
_element: null,
|
|
|
|
// The root view's contextualElement
|
|
_outerContextualElement: null,
|
|
|
|
/**
|
|
An internal set used to de-dupe class names when `addClass()` is
|
|
used. After each call to `addClass()`, the `classes` property
|
|
will be updated.
|
|
|
|
@private
|
|
@property elementClasses
|
|
@type Array
|
|
@default null
|
|
*/
|
|
elementClasses: null,
|
|
|
|
/**
|
|
Array of class names which will be applied in the class attribute.
|
|
|
|
You can use `setClasses()` to set this property directly. If you
|
|
use `addClass()`, it will be maintained for you.
|
|
|
|
@property classes
|
|
@type Array
|
|
@default null
|
|
*/
|
|
classes: null,
|
|
|
|
/**
|
|
The id in of the element, to be applied in the id attribute.
|
|
|
|
You should not set this property yourself, rather, you should use
|
|
the `id()` method of `Ember.RenderBuffer`.
|
|
|
|
@property elementId
|
|
@type String
|
|
@default null
|
|
*/
|
|
elementId: null,
|
|
|
|
/**
|
|
A hash keyed on the name of the attribute and whose value will be
|
|
applied to that attribute. For example, if you wanted to apply a
|
|
`data-view="Foo.bar"` property to an element, you would set the
|
|
elementAttributes hash to `{'data-view':'Foo.bar'}`.
|
|
|
|
You should not maintain this hash yourself, rather, you should use
|
|
the `attr()` method of `Ember.RenderBuffer`.
|
|
|
|
@property elementAttributes
|
|
@type Hash
|
|
@default {}
|
|
*/
|
|
elementAttributes: null,
|
|
|
|
/**
|
|
A hash keyed on the name of the properties and whose value will be
|
|
applied to that property. For example, if you wanted to apply a
|
|
`checked=true` property to an element, you would set the
|
|
elementProperties hash to `{'checked':true}`.
|
|
|
|
You should not maintain this hash yourself, rather, you should use
|
|
the `prop()` method of `Ember.RenderBuffer`.
|
|
|
|
@property elementProperties
|
|
@type Hash
|
|
@default {}
|
|
*/
|
|
elementProperties: null,
|
|
|
|
/**
|
|
The tagname of the element an instance of `Ember.RenderBuffer` represents.
|
|
|
|
Usually, this gets set as the first parameter to `Ember.RenderBuffer`. For
|
|
example, if you wanted to create a `p` tag, then you would call
|
|
|
|
```javascript
|
|
Ember.RenderBuffer('p', contextualElement)
|
|
```
|
|
|
|
@property elementTag
|
|
@type String
|
|
@default null
|
|
*/
|
|
elementTag: null,
|
|
|
|
/**
|
|
A hash keyed on the name of the style attribute and whose value will
|
|
be applied to that attribute. For example, if you wanted to apply a
|
|
`background-color:black;` style to an element, you would set the
|
|
elementStyle hash to `{'background-color':'black'}`.
|
|
|
|
You should not maintain this hash yourself, rather, you should use
|
|
the `style()` method of `Ember.RenderBuffer`.
|
|
|
|
@property elementStyle
|
|
@type Hash
|
|
@default {}
|
|
*/
|
|
elementStyle: null,
|
|
|
|
pushChildView: function (view) {
|
|
var index = this.childViews.length;
|
|
this.childViews[index] = view;
|
|
this.push("<script id='morph-"+index+"' type='text/x-placeholder'>\x3C/script>");
|
|
},
|
|
|
|
hydrateMorphs: function (contextualElement) {
|
|
var childViews = this.childViews;
|
|
var el = this._element;
|
|
for (var i=0,l=childViews.length; i<l; i++) {
|
|
var childView = childViews[i];
|
|
var ref = el.querySelector('#morph-'+i);
|
|
|
|
Ember.assert('An error occurred while setting up template bindings. Please check ' +
|
|
(childView && childView._parentView && childView._parentView._debugTemplateName ?
|
|
'"' + childView._parentView._debugTemplateName + '" template ' :
|
|
''
|
|
) + 'for invalid markup or bindings within HTML comments.',
|
|
ref);
|
|
|
|
var parent = ref.parentNode;
|
|
|
|
childView._morph = this.dom.insertMorphBefore(
|
|
parent,
|
|
ref,
|
|
parent.nodeType === 1 ? parent : contextualElement
|
|
);
|
|
parent.removeChild(ref);
|
|
}
|
|
},
|
|
|
|
/**
|
|
Adds a string of HTML to the `RenderBuffer`.
|
|
|
|
@method push
|
|
@param {String} string HTML to push into the buffer
|
|
@chainable
|
|
*/
|
|
push: function(content) {
|
|
if (typeof content === 'string') {
|
|
if (this.buffer === null) {
|
|
this.buffer = '';
|
|
}
|
|
Ember.assert("A string cannot be pushed into the buffer after a fragment", !this.buffer.nodeType);
|
|
this.buffer += content;
|
|
} else {
|
|
Ember.assert("A fragment cannot be pushed into a buffer that contains content", !this.buffer);
|
|
this.buffer = content;
|
|
}
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Adds a class to the buffer, which will be rendered to the class attribute.
|
|
|
|
@method addClass
|
|
@param {String} className Class name to add to the buffer
|
|
@chainable
|
|
*/
|
|
addClass: function(className) {
|
|
// lazily create elementClasses
|
|
this.elementClasses = (this.elementClasses || new ClassSet());
|
|
this.elementClasses.add(className);
|
|
this.classes = this.elementClasses.list;
|
|
|
|
return this;
|
|
},
|
|
|
|
setClasses: function(classNames) {
|
|
this.elementClasses = null;
|
|
var len = classNames.length;
|
|
var i;
|
|
for (i = 0; i < len; i++) {
|
|
this.addClass(classNames[i]);
|
|
}
|
|
},
|
|
|
|
/**
|
|
Sets the elementID to be used for the element.
|
|
|
|
@method id
|
|
@param {String} id
|
|
@chainable
|
|
*/
|
|
id: function(id) {
|
|
this.elementId = id;
|
|
return this;
|
|
},
|
|
|
|
// duck type attribute functionality like jQuery so a render buffer
|
|
// can be used like a jQuery object in attribute binding scenarios.
|
|
|
|
/**
|
|
Adds an attribute which will be rendered to the element.
|
|
|
|
@method attr
|
|
@param {String} name The name of the attribute
|
|
@param {String} value The value to add to the attribute
|
|
@chainable
|
|
@return {Ember.RenderBuffer|String} this or the current attribute value
|
|
*/
|
|
attr: function(name, value) {
|
|
var attributes = this.elementAttributes = (this.elementAttributes || {});
|
|
|
|
if (arguments.length === 1) {
|
|
return attributes[name];
|
|
} else {
|
|
attributes[name] = value;
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Remove an attribute from the list of attributes to render.
|
|
|
|
@method removeAttr
|
|
@param {String} name The name of the attribute
|
|
@chainable
|
|
*/
|
|
removeAttr: function(name) {
|
|
var attributes = this.elementAttributes;
|
|
if (attributes) { delete attributes[name]; }
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Adds a property which will be rendered to the element.
|
|
|
|
@method prop
|
|
@param {String} name The name of the property
|
|
@param {String} value The value to add to the property
|
|
@chainable
|
|
@return {Ember.RenderBuffer|String} this or the current property value
|
|
*/
|
|
prop: function(name, value) {
|
|
var properties = this.elementProperties = (this.elementProperties || {});
|
|
|
|
if (arguments.length === 1) {
|
|
return properties[name];
|
|
} else {
|
|
properties[name] = value;
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Remove an property from the list of properties to render.
|
|
|
|
@method removeProp
|
|
@param {String} name The name of the property
|
|
@chainable
|
|
*/
|
|
removeProp: function(name) {
|
|
var properties = this.elementProperties;
|
|
if (properties) { delete properties[name]; }
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Adds a style to the style attribute which will be rendered to the element.
|
|
|
|
@method style
|
|
@param {String} name Name of the style
|
|
@param {String} value
|
|
@chainable
|
|
*/
|
|
style: function(name, value) {
|
|
this.elementStyle = (this.elementStyle || {});
|
|
|
|
this.elementStyle[name] = value;
|
|
return this;
|
|
},
|
|
|
|
generateElement: function() {
|
|
var tagName = this.tagName;
|
|
var id = this.elementId;
|
|
var classes = this.classes;
|
|
var attrs = this.elementAttributes;
|
|
var props = this.elementProperties;
|
|
var style = this.elementStyle;
|
|
var styleBuffer = '';
|
|
var attr, prop, tagString;
|
|
|
|
if (attrs && attrs.name && !canSetNameOnInputs) {
|
|
// IE allows passing a tag to createElement. See note on `canSetNameOnInputs` above as well.
|
|
tagString = '<'+stripTagName(tagName)+' name="'+escapeAttribute(attrs.name)+'">';
|
|
} else {
|
|
tagString = tagName;
|
|
}
|
|
|
|
var element = this.dom.createElement(tagString, this.outerContextualElement());
|
|
|
|
if (id) {
|
|
this.dom.setAttribute(element, 'id', id);
|
|
this.elementId = null;
|
|
}
|
|
if (classes) {
|
|
this.dom.setAttribute(element, 'class', classes.join(' '));
|
|
this.classes = null;
|
|
this.elementClasses = null;
|
|
}
|
|
|
|
if (style) {
|
|
for (prop in style) {
|
|
styleBuffer += (prop + ':' + style[prop] + ';');
|
|
}
|
|
|
|
this.dom.setAttribute(element, 'style', styleBuffer);
|
|
|
|
this.elementStyle = null;
|
|
}
|
|
|
|
if (attrs) {
|
|
for (attr in attrs) {
|
|
this.dom.setAttribute(element, attr, attrs[attr]);
|
|
}
|
|
|
|
this.elementAttributes = null;
|
|
}
|
|
|
|
if (props) {
|
|
for (prop in props) {
|
|
var normalizedCase = normalizeProperty(element, prop.toLowerCase()) || prop;
|
|
|
|
this.dom.setPropertyStrict(element, normalizedCase, props[prop]);
|
|
}
|
|
|
|
this.elementProperties = null;
|
|
}
|
|
|
|
this._element = element;
|
|
},
|
|
|
|
/**
|
|
@method element
|
|
@return {DOMElement} The element corresponding to the generated HTML
|
|
of this buffer
|
|
*/
|
|
element: function() {
|
|
var content = this.innerContent();
|
|
// No content means a text node buffer, with the content
|
|
// in _element. Ember._BoundView is an example.
|
|
if (content === null) {
|
|
return this._element;
|
|
}
|
|
|
|
var contextualElement = this.innerContextualElement(content);
|
|
this.dom.detectNamespace(contextualElement);
|
|
|
|
if (!this._element) {
|
|
this._element = document.createDocumentFragment();
|
|
}
|
|
|
|
if (content.nodeType) {
|
|
this._element.appendChild(content);
|
|
} else {
|
|
var nodes;
|
|
nodes = this.dom.parseHTML(content, contextualElement);
|
|
while (nodes[0]) {
|
|
this._element.appendChild(nodes[0]);
|
|
}
|
|
}
|
|
// This should only happen with legacy string buffers
|
|
if (this.childViews.length > 0) {
|
|
this.hydrateMorphs(contextualElement);
|
|
}
|
|
|
|
return this._element;
|
|
},
|
|
|
|
/**
|
|
Generates the HTML content for this buffer.
|
|
|
|
@method string
|
|
@return {String} The generated HTML
|
|
*/
|
|
string: function() {
|
|
if (this._element) {
|
|
// Firefox versions < 11 do not have support for element.outerHTML.
|
|
var thisElement = this.element();
|
|
var outerHTML = thisElement.outerHTML;
|
|
if (typeof outerHTML === 'undefined') {
|
|
return jQuery('<div/>').append(thisElement).html();
|
|
}
|
|
return outerHTML;
|
|
} else {
|
|
return this.innerString();
|
|
}
|
|
},
|
|
|
|
outerContextualElement: function() {
|
|
if (!this._outerContextualElement) {
|
|
Ember.deprecate("The render buffer expects an outer contextualElement to exist." +
|
|
" This ensures DOM that requires context is correctly generated (tr, SVG tags)." +
|
|
" Defaulting to document.body, but this will be removed in the future");
|
|
this.outerContextualElement = document.body;
|
|
}
|
|
return this._outerContextualElement;
|
|
},
|
|
|
|
innerContextualElement: function(html) {
|
|
var innerContextualElement;
|
|
if (this._element && this._element.nodeType === 1) {
|
|
innerContextualElement = this._element;
|
|
} else {
|
|
innerContextualElement = this.outerContextualElement();
|
|
}
|
|
|
|
var omittedStartTag;
|
|
if (html) {
|
|
omittedStartTag = detectOmittedStartTag(html, innerContextualElement);
|
|
}
|
|
return omittedStartTag || innerContextualElement;
|
|
},
|
|
|
|
innerString: function() {
|
|
var content = this.innerContent();
|
|
if (content && !content.nodeType) {
|
|
return content;
|
|
}
|
|
},
|
|
|
|
innerContent: function() {
|
|
return this.buffer;
|
|
}
|
|
};
|
|
});
|
|
enifed("ember-views/system/renderer",
|
|
["ember-metal/core","ember-metal-views/renderer","ember-metal/platform","ember-views/system/render_buffer","ember-metal/run_loop","ember-metal/property_set","ember-metal/property_get","ember-metal/instrumentation","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
var Renderer = __dependency2__["default"];
|
|
var create = __dependency3__.create;
|
|
var renderBuffer = __dependency4__["default"];
|
|
var run = __dependency5__["default"];
|
|
var set = __dependency6__.set;
|
|
var get = __dependency7__.get;
|
|
var _instrumentStart = __dependency8__._instrumentStart;
|
|
var subscribers = __dependency8__.subscribers;
|
|
|
|
function EmberRenderer() {
|
|
this.buffer = renderBuffer();
|
|
this._super$constructor();
|
|
}
|
|
|
|
EmberRenderer.prototype = create(Renderer.prototype);
|
|
EmberRenderer.prototype.constructor = EmberRenderer;
|
|
EmberRenderer.prototype._super$constructor = Renderer;
|
|
|
|
EmberRenderer.prototype.scheduleRender =
|
|
function EmberRenderer_scheduleRender(ctx, fn) {
|
|
return run.scheduleOnce('render', ctx, fn);
|
|
};
|
|
|
|
EmberRenderer.prototype.cancelRender =
|
|
function EmberRenderer_cancelRender(id) {
|
|
run.cancel(id);
|
|
};
|
|
|
|
EmberRenderer.prototype.createElement =
|
|
function EmberRenderer_createElement(view, contextualElement) {
|
|
// If this is the top-most view, start a new buffer. Otherwise,
|
|
// create a new buffer relative to the original using the
|
|
// provided buffer operation (for example, `insertAfter` will
|
|
// insert a new buffer after the "parent buffer").
|
|
var tagName = view.tagName;
|
|
if (tagName === undefined) {
|
|
tagName = get(view, 'tagName');
|
|
Ember.deprecate('In the future using a computed property to define tagName will not be permitted. That value will be respected, but changing it will not update the element.', !tagName);
|
|
}
|
|
var classNameBindings = view.classNameBindings;
|
|
var taglessViewWithClassBindings = tagName === '' && (classNameBindings && classNameBindings.length > 0);
|
|
|
|
if (tagName === null || tagName === undefined) {
|
|
tagName = 'div';
|
|
}
|
|
|
|
Ember.assert('You cannot use `classNameBindings` on a tag-less view: ' + view.toString(), !taglessViewWithClassBindings);
|
|
|
|
var buffer = view.buffer = this.buffer;
|
|
buffer.reset(tagName, contextualElement);
|
|
|
|
if (view.beforeRender) {
|
|
view.beforeRender(buffer);
|
|
}
|
|
|
|
if (tagName !== '') {
|
|
if (view.applyAttributesToBuffer) {
|
|
view.applyAttributesToBuffer(buffer);
|
|
}
|
|
buffer.generateElement();
|
|
}
|
|
|
|
if (view.render) {
|
|
view.render(buffer);
|
|
}
|
|
|
|
if (view.afterRender) {
|
|
view.afterRender(buffer);
|
|
}
|
|
|
|
var element = buffer.element();
|
|
|
|
view.buffer = null;
|
|
if (element && element.nodeType === 1) {
|
|
view.element = element;
|
|
}
|
|
return element;
|
|
};
|
|
|
|
EmberRenderer.prototype.destroyView = function destroyView(view) {
|
|
view.removedFromDOM = true;
|
|
view.destroy();
|
|
};
|
|
|
|
EmberRenderer.prototype.childViews = function childViews(view) {
|
|
return view._childViews;
|
|
};
|
|
|
|
Renderer.prototype.willCreateElement = function (view) {
|
|
if (subscribers.length && view.instrumentDetails) {
|
|
view._instrumentEnd = _instrumentStart('render.'+view.instrumentName, function viewInstrumentDetails() {
|
|
var details = {};
|
|
view.instrumentDetails(details);
|
|
return details;
|
|
});
|
|
}
|
|
if (view._transitionTo) {
|
|
view._transitionTo('inBuffer');
|
|
}
|
|
}; // inBuffer
|
|
Renderer.prototype.didCreateElement = function (view) {
|
|
if (view._transitionTo) {
|
|
view._transitionTo('hasElement');
|
|
}
|
|
if (view._instrumentEnd) {
|
|
view._instrumentEnd();
|
|
}
|
|
}; // hasElement
|
|
Renderer.prototype.willInsertElement = function (view) {
|
|
if (view.trigger) { view.trigger('willInsertElement'); }
|
|
}; // will place into DOM
|
|
Renderer.prototype.didInsertElement = function (view) {
|
|
if (view._transitionTo) {
|
|
view._transitionTo('inDOM');
|
|
}
|
|
if (view.trigger) { view.trigger('didInsertElement'); }
|
|
}; // inDOM // placed into DOM
|
|
|
|
Renderer.prototype.willRemoveElement = function (view) {};
|
|
|
|
Renderer.prototype.willDestroyElement = function (view) {
|
|
if (view.trigger) { view.trigger('willDestroyElement'); }
|
|
if (view.trigger) { view.trigger('willClearRender'); }
|
|
};
|
|
|
|
Renderer.prototype.didDestroyElement = function (view) {
|
|
set(view, 'element', null);
|
|
if (view._transitionTo) {
|
|
view._transitionTo('preRender');
|
|
}
|
|
}; // element destroyed so view.destroy shouldn't try to remove it removedFromDOM
|
|
|
|
__exports__["default"] = EmberRenderer;
|
|
});
|
|
enifed("ember-views/system/sanitize_attribute_value",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
/* jshint scripturl:true */
|
|
|
|
var parsingNode;
|
|
var badProtocols = {
|
|
'javascript:': true,
|
|
'vbscript:': true
|
|
};
|
|
|
|
var badTags = {
|
|
'A': true,
|
|
'BODY': true,
|
|
'LINK': true,
|
|
'IMG': true,
|
|
'IFRAME': true
|
|
};
|
|
|
|
var badAttributes = {
|
|
'href': true,
|
|
'src': true,
|
|
'background': true
|
|
};
|
|
__exports__.badAttributes = badAttributes;
|
|
__exports__["default"] = function sanitizeAttributeValue(element, attribute, value) {
|
|
var tagName;
|
|
|
|
if (!parsingNode) {
|
|
parsingNode = document.createElement('a');
|
|
}
|
|
|
|
if (!element) {
|
|
tagName = null;
|
|
} else {
|
|
tagName = element.tagName;
|
|
}
|
|
|
|
if (value && value.toHTML) {
|
|
return value.toHTML();
|
|
}
|
|
|
|
if ((tagName === null || badTags[tagName]) && badAttributes[attribute]) {
|
|
parsingNode.href = value;
|
|
|
|
if (badProtocols[parsingNode.protocol] === true) {
|
|
return 'unsafe:' + value;
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
});
|
|
enifed("ember-views/system/utils",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-views
|
|
*/
|
|
|
|
function isSimpleClick(event) {
|
|
var modifier = event.shiftKey || event.metaKey || event.altKey || event.ctrlKey;
|
|
var secondaryClick = event.which > 1; // IE9 may return undefined
|
|
|
|
return !modifier && !secondaryClick;
|
|
}
|
|
|
|
__exports__.isSimpleClick = isSimpleClick;/**
|
|
@private
|
|
@method getViewRange
|
|
@param {Ember.View} view
|
|
*/
|
|
function getViewRange(view) {
|
|
var range = document.createRange();
|
|
range.setStartAfter(view._morph.start);
|
|
range.setEndBefore(view._morph.end);
|
|
return range;
|
|
}
|
|
|
|
/**
|
|
`getViewClientRects` provides information about the position of the border
|
|
box edges of a view relative to the viewport.
|
|
|
|
It is only intended to be used by development tools like the Ember Inpsector
|
|
and may not work on older browsers.
|
|
|
|
@private
|
|
@method getViewClientRects
|
|
@param {Ember.View} view
|
|
*/
|
|
function getViewClientRects(view) {
|
|
var range = getViewRange(view);
|
|
return range.getClientRects();
|
|
}
|
|
|
|
__exports__.getViewClientRects = getViewClientRects;/**
|
|
`getViewBoundingClientRect` provides information about the position of the
|
|
bounding border box edges of a view relative to the viewport.
|
|
|
|
It is only intended to be used by development tools like the Ember Inpsector
|
|
and may not work on older browsers.
|
|
|
|
@private
|
|
@method getViewBoundingClientRect
|
|
@param {Ember.View} view
|
|
*/
|
|
function getViewBoundingClientRect(view) {
|
|
var range = getViewRange(view);
|
|
return range.getBoundingClientRect();
|
|
}
|
|
|
|
__exports__.getViewBoundingClientRect = getViewBoundingClientRect;
|
|
});
|
|
enifed("ember-views/views/bound_view",
|
|
["ember-metal/property_get","ember-metal/property_set","ember-metal/merge","ember-htmlbars/utils/string","ember-views/views/states","ember-views/views/metamorph_view","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-views
|
|
*/
|
|
|
|
var get = __dependency1__.get;
|
|
var set = __dependency2__.set;
|
|
var merge = __dependency3__["default"];
|
|
var escapeExpression = __dependency4__.escapeExpression;
|
|
var SafeString = __dependency4__.SafeString;
|
|
var cloneStates = __dependency5__.cloneStates;
|
|
var viewStates = __dependency5__.states;
|
|
var _MetamorphView = __dependency6__["default"];
|
|
|
|
function K() { return this; }
|
|
|
|
var states = cloneStates(viewStates);
|
|
|
|
merge(states._default, {
|
|
rerenderIfNeeded: K
|
|
});
|
|
|
|
merge(states.inDOM, {
|
|
rerenderIfNeeded: function(view) {
|
|
if (view.normalizedValue() !== view._lastNormalizedValue) {
|
|
view.rerender();
|
|
}
|
|
}
|
|
});
|
|
|
|
/**
|
|
`Ember._BoundView` is a private view created by the Handlebars
|
|
`{{bind}}` helpers that is used to keep track of bound properties.
|
|
|
|
Every time a property is bound using a `{{mustache}}`, an anonymous subclass
|
|
of `Ember._BoundView` is created with the appropriate sub-template
|
|
and context set up. When the associated property changes, just the template
|
|
for this view will re-render.
|
|
|
|
@class _BoundView
|
|
@namespace Ember
|
|
@extends Ember._MetamorphView
|
|
@private
|
|
*/
|
|
var BoundView = _MetamorphView.extend({
|
|
instrumentName: 'bound',
|
|
|
|
_states: states,
|
|
|
|
/**
|
|
The function used to determine if the `displayTemplate` or
|
|
`inverseTemplate` should be rendered. This should be a function that takes
|
|
a value and returns a Boolean.
|
|
|
|
@property shouldDisplayFunc
|
|
@type Function
|
|
@default null
|
|
*/
|
|
shouldDisplayFunc: null,
|
|
|
|
/**
|
|
Whether the template rendered by this view gets passed the context object
|
|
of its parent template, or gets passed the value of retrieving `path`
|
|
from the `pathRoot`.
|
|
|
|
For example, this is true when using the `{{#if}}` helper, because the
|
|
template inside the helper should look up properties relative to the same
|
|
object as outside the block. This would be `false` when used with `{{#with
|
|
foo}}` because the template should receive the object found by evaluating
|
|
`foo`.
|
|
|
|
@property preserveContext
|
|
@type Boolean
|
|
@default false
|
|
*/
|
|
preserveContext: false,
|
|
|
|
/**
|
|
If `preserveContext` is true, this is the object that will be used
|
|
to render the template.
|
|
|
|
@property previousContext
|
|
@type Object
|
|
*/
|
|
previousContext: null,
|
|
|
|
/**
|
|
The template to render when `shouldDisplayFunc` evaluates to `true`.
|
|
|
|
@property displayTemplate
|
|
@type Function
|
|
@default null
|
|
*/
|
|
displayTemplate: null,
|
|
|
|
/**
|
|
The template to render when `shouldDisplayFunc` evaluates to `false`.
|
|
|
|
@property inverseTemplate
|
|
@type Function
|
|
@default null
|
|
*/
|
|
inverseTemplate: null,
|
|
|
|
lazyValue: null,
|
|
|
|
normalizedValue: function() {
|
|
var value = this.lazyValue.value();
|
|
var valueNormalizer = get(this, 'valueNormalizerFunc');
|
|
return valueNormalizer ? valueNormalizer(value) : value;
|
|
},
|
|
|
|
rerenderIfNeeded: function() {
|
|
this.currentState.rerenderIfNeeded(this);
|
|
},
|
|
|
|
/**
|
|
Determines which template to invoke, sets up the correct state based on
|
|
that logic, then invokes the default `Ember.View` `render` implementation.
|
|
|
|
This method will first look up the `path` key on `pathRoot`,
|
|
then pass that value to the `shouldDisplayFunc` function. If that returns
|
|
`true,` the `displayTemplate` function will be rendered to DOM. Otherwise,
|
|
`inverseTemplate`, if specified, will be rendered.
|
|
|
|
For example, if this `Ember._BoundView` represented the `{{#with
|
|
foo}}` helper, it would look up the `foo` property of its context, and
|
|
`shouldDisplayFunc` would always return true. The object found by looking
|
|
up `foo` would be passed to `displayTemplate`.
|
|
|
|
@method render
|
|
@param {Ember.RenderBuffer} buffer
|
|
*/
|
|
render: function(buffer) {
|
|
// If not invoked via a triple-mustache ({{{foo}}}), escape
|
|
// the content of the template.
|
|
var escape = get(this, 'isEscaped');
|
|
|
|
var shouldDisplay = get(this, 'shouldDisplayFunc');
|
|
var preserveContext = get(this, 'preserveContext');
|
|
var context = get(this, 'previousContext');
|
|
|
|
var inverseTemplate = get(this, 'inverseTemplate');
|
|
var displayTemplate = get(this, 'displayTemplate');
|
|
|
|
var result = this.normalizedValue();
|
|
|
|
this._lastNormalizedValue = result;
|
|
|
|
// First, test the conditional to see if we should
|
|
// render the template or not.
|
|
if (shouldDisplay(result)) {
|
|
set(this, 'template', displayTemplate);
|
|
|
|
// If we are preserving the context (for example, if this
|
|
// is an #if block, call the template with the same object.
|
|
if (preserveContext) {
|
|
set(this, '_context', context);
|
|
} else {
|
|
// Otherwise, determine if this is a block bind or not.
|
|
// If so, pass the specified object to the template
|
|
if (displayTemplate) {
|
|
set(this, '_context', result);
|
|
} else {
|
|
// This is not a bind block, just push the result of the
|
|
// expression to the render context and return.
|
|
if (result === null || result === undefined) {
|
|
result = "";
|
|
} else if (!(result instanceof SafeString)) {
|
|
result = String(result);
|
|
}
|
|
|
|
if (escape) { result = escapeExpression(result); }
|
|
buffer.push(result);
|
|
return;
|
|
}
|
|
}
|
|
} else if (inverseTemplate) {
|
|
set(this, 'template', inverseTemplate);
|
|
|
|
if (preserveContext) {
|
|
set(this, '_context', context);
|
|
} else {
|
|
set(this, '_context', result);
|
|
}
|
|
} else {
|
|
set(this, 'template', function() { return ''; });
|
|
}
|
|
|
|
return this._super(buffer);
|
|
}
|
|
});
|
|
|
|
__exports__["default"] = BoundView;
|
|
});
|
|
enifed("ember-views/views/checkbox",
|
|
["ember-metal/property_get","ember-metal/property_set","ember-views/views/view","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
var get = __dependency1__.get;
|
|
var set = __dependency2__.set;
|
|
var View = __dependency3__["default"];
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-views
|
|
*/
|
|
|
|
/**
|
|
The internal class used to create text inputs when the `{{input}}`
|
|
helper is used with `type` of `checkbox`.
|
|
|
|
See [handlebars.helpers.input](/api/classes/Ember.Handlebars.helpers.html#method_input) for usage details.
|
|
|
|
## Direct manipulation of `checked`
|
|
|
|
The `checked` attribute of an `Ember.Checkbox` object should always be set
|
|
through the Ember object or by interacting with its rendered element
|
|
representation via the mouse, keyboard, or touch. Updating the value of the
|
|
checkbox via jQuery will result in the checked value of the object and its
|
|
element losing synchronization.
|
|
|
|
## Layout and LayoutName properties
|
|
|
|
Because HTML `input` elements are self closing `layout` and `layoutName`
|
|
properties will not be applied. See [Ember.View](/api/classes/Ember.View.html)'s
|
|
layout section for more information.
|
|
|
|
@class Checkbox
|
|
@namespace Ember
|
|
@extends Ember.View
|
|
*/
|
|
__exports__["default"] = View.extend({
|
|
instrumentDisplay: '{{input type="checkbox"}}',
|
|
|
|
classNames: ['ember-checkbox'],
|
|
|
|
tagName: 'input',
|
|
|
|
attributeBindings: [
|
|
'type',
|
|
'checked',
|
|
'indeterminate',
|
|
'disabled',
|
|
'tabindex',
|
|
'name',
|
|
'autofocus',
|
|
'required',
|
|
'form'
|
|
],
|
|
|
|
type: 'checkbox',
|
|
checked: false,
|
|
disabled: false,
|
|
indeterminate: false,
|
|
|
|
init: function() {
|
|
this._super();
|
|
this.on('change', this, this._updateElementValue);
|
|
},
|
|
|
|
didInsertElement: function() {
|
|
this._super();
|
|
get(this, 'element').indeterminate = !!get(this, 'indeterminate');
|
|
},
|
|
|
|
_updateElementValue: function() {
|
|
set(this, 'checked', this.$().prop('checked'));
|
|
}
|
|
});
|
|
});
|
|
enifed("ember-views/views/collection_view",
|
|
["ember-metal/core","ember-metal/binding","ember-metal/property_get","ember-metal/property_set","ember-runtime/system/string","ember-views/views/container_view","ember-views/views/core_view","ember-views/views/view","ember-metal/mixin","ember-views/streams/utils","ember-runtime/mixins/array","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __exports__) {
|
|
"use strict";
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-views
|
|
*/
|
|
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.assert
|
|
var isGlobalPath = __dependency2__.isGlobalPath;
|
|
var get = __dependency3__.get;
|
|
var set = __dependency4__.set;
|
|
var fmt = __dependency5__.fmt;
|
|
var ContainerView = __dependency6__["default"];
|
|
var CoreView = __dependency7__["default"];
|
|
var View = __dependency8__["default"];
|
|
var observer = __dependency9__.observer;
|
|
var beforeObserver = __dependency9__.beforeObserver;
|
|
var readViewFactory = __dependency10__.readViewFactory;
|
|
var EmberArray = __dependency11__["default"];
|
|
|
|
/**
|
|
`Ember.CollectionView` is an `Ember.View` descendent responsible for managing
|
|
a collection (an array or array-like object) by maintaining a child view object
|
|
and associated DOM representation for each item in the array and ensuring
|
|
that child views and their associated rendered HTML are updated when items in
|
|
the array are added, removed, or replaced.
|
|
|
|
## Setting content
|
|
|
|
The managed collection of objects is referenced as the `Ember.CollectionView`
|
|
instance's `content` property.
|
|
|
|
```javascript
|
|
someItemsView = Ember.CollectionView.create({
|
|
content: ['A', 'B','C']
|
|
})
|
|
```
|
|
|
|
The view for each item in the collection will have its `content` property set
|
|
to the item.
|
|
|
|
## Specifying `itemViewClass`
|
|
|
|
By default the view class for each item in the managed collection will be an
|
|
instance of `Ember.View`. You can supply a different class by setting the
|
|
`CollectionView`'s `itemViewClass` property.
|
|
|
|
Given the following application code:
|
|
|
|
```javascript
|
|
var App = Ember.Application.create();
|
|
App.ItemListView = Ember.CollectionView.extend({
|
|
classNames: ['a-collection'],
|
|
content: ['A','B','C'],
|
|
itemViewClass: Ember.View.extend({
|
|
template: Ember.Handlebars.compile("the letter: {{view.content}}")
|
|
})
|
|
});
|
|
```
|
|
|
|
And a simple application template:
|
|
|
|
```handlebars
|
|
{{view 'item-list'}}
|
|
```
|
|
|
|
The following HTML will result:
|
|
|
|
```html
|
|
<div class="ember-view a-collection">
|
|
<div class="ember-view">the letter: A</div>
|
|
<div class="ember-view">the letter: B</div>
|
|
<div class="ember-view">the letter: C</div>
|
|
</div>
|
|
```
|
|
|
|
## Automatic matching of parent/child tagNames
|
|
|
|
Setting the `tagName` property of a `CollectionView` to any of
|
|
"ul", "ol", "table", "thead", "tbody", "tfoot", "tr", or "select" will result
|
|
in the item views receiving an appropriately matched `tagName` property.
|
|
|
|
Given the following application code:
|
|
|
|
```javascript
|
|
var App = Ember.Application.create();
|
|
App.UnorderedListView = Ember.CollectionView.create({
|
|
tagName: 'ul',
|
|
content: ['A','B','C'],
|
|
itemViewClass: Ember.View.extend({
|
|
template: Ember.Handlebars.compile("the letter: {{view.content}}")
|
|
})
|
|
});
|
|
```
|
|
|
|
And a simple application template:
|
|
|
|
```handlebars
|
|
{{view 'unordered-list-view'}}
|
|
```
|
|
|
|
The following HTML will result:
|
|
|
|
```html
|
|
<ul class="ember-view a-collection">
|
|
<li class="ember-view">the letter: A</li>
|
|
<li class="ember-view">the letter: B</li>
|
|
<li class="ember-view">the letter: C</li>
|
|
</ul>
|
|
```
|
|
|
|
Additional `tagName` pairs can be provided by adding to
|
|
`Ember.CollectionView.CONTAINER_MAP`. For example:
|
|
|
|
```javascript
|
|
Ember.CollectionView.CONTAINER_MAP['article'] = 'section'
|
|
```
|
|
|
|
## Programmatic creation of child views
|
|
|
|
For cases where additional customization beyond the use of a single
|
|
`itemViewClass` or `tagName` matching is required CollectionView's
|
|
`createChildView` method can be overidden:
|
|
|
|
```javascript
|
|
App.CustomCollectionView = Ember.CollectionView.extend({
|
|
createChildView: function(viewClass, attrs) {
|
|
if (attrs.content.kind == 'album') {
|
|
viewClass = App.AlbumView;
|
|
} else {
|
|
viewClass = App.SongView;
|
|
}
|
|
return this._super(viewClass, attrs);
|
|
}
|
|
});
|
|
```
|
|
|
|
## Empty View
|
|
|
|
You can provide an `Ember.View` subclass to the `Ember.CollectionView`
|
|
instance as its `emptyView` property. If the `content` property of a
|
|
`CollectionView` is set to `null` or an empty array, an instance of this view
|
|
will be the `CollectionView`s only child.
|
|
|
|
```javascript
|
|
var App = Ember.Application.create();
|
|
App.ListWithNothing = Ember.CollectionView.create({
|
|
classNames: ['nothing'],
|
|
content: null,
|
|
emptyView: Ember.View.extend({
|
|
template: Ember.Handlebars.compile("The collection is empty")
|
|
})
|
|
});
|
|
```
|
|
|
|
And a simple application template:
|
|
|
|
```handlebars
|
|
{{view 'list-with-nothing'}}
|
|
```
|
|
|
|
The following HTML will result:
|
|
|
|
```html
|
|
<div class="ember-view nothing">
|
|
<div class="ember-view">
|
|
The collection is empty
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
## Adding and Removing items
|
|
|
|
The `childViews` property of a `CollectionView` should not be directly
|
|
manipulated. Instead, add, remove, replace items from its `content` property.
|
|
This will trigger appropriate changes to its rendered HTML.
|
|
|
|
|
|
@class CollectionView
|
|
@namespace Ember
|
|
@extends Ember.ContainerView
|
|
@since Ember 0.9
|
|
*/
|
|
var CollectionView = ContainerView.extend({
|
|
|
|
/**
|
|
A list of items to be displayed by the `Ember.CollectionView`.
|
|
|
|
@property content
|
|
@type Ember.Array
|
|
@default null
|
|
*/
|
|
content: null,
|
|
|
|
/**
|
|
This provides metadata about what kind of empty view class this
|
|
collection would like if it is being instantiated from another
|
|
system (like Handlebars)
|
|
|
|
@private
|
|
@property emptyViewClass
|
|
*/
|
|
emptyViewClass: View,
|
|
|
|
/**
|
|
An optional view to display if content is set to an empty array.
|
|
|
|
@property emptyView
|
|
@type Ember.View
|
|
@default null
|
|
*/
|
|
emptyView: null,
|
|
|
|
/**
|
|
@property itemViewClass
|
|
@type Ember.View
|
|
@default Ember.View
|
|
*/
|
|
itemViewClass: View,
|
|
|
|
/**
|
|
Setup a CollectionView
|
|
|
|
@method init
|
|
*/
|
|
init: function() {
|
|
var ret = this._super();
|
|
this._contentDidChange();
|
|
return ret;
|
|
},
|
|
|
|
/**
|
|
Invoked when the content property is about to change. Notifies observers that the
|
|
entire array content will change.
|
|
|
|
@private
|
|
@method _contentWillChange
|
|
*/
|
|
_contentWillChange: beforeObserver('content', function() {
|
|
var content = this.get('content');
|
|
|
|
if (content) { content.removeArrayObserver(this); }
|
|
var len = content ? get(content, 'length') : 0;
|
|
this.arrayWillChange(content, 0, len);
|
|
}),
|
|
|
|
/**
|
|
Check to make sure that the content has changed, and if so,
|
|
update the children directly. This is always scheduled
|
|
asynchronously, to allow the element to be created before
|
|
bindings have synchronized and vice versa.
|
|
|
|
@private
|
|
@method _contentDidChange
|
|
*/
|
|
_contentDidChange: observer('content', function() {
|
|
var content = get(this, 'content');
|
|
|
|
if (content) {
|
|
this._assertArrayLike(content);
|
|
content.addArrayObserver(this);
|
|
}
|
|
|
|
var len = content ? get(content, 'length') : 0;
|
|
this.arrayDidChange(content, 0, null, len);
|
|
}),
|
|
|
|
/**
|
|
Ensure that the content implements Ember.Array
|
|
|
|
@private
|
|
@method _assertArrayLike
|
|
*/
|
|
_assertArrayLike: function(content) {
|
|
Ember.assert(fmt("an Ember.CollectionView's content must implement Ember.Array. You passed %@", [content]), EmberArray.detect(content));
|
|
},
|
|
|
|
/**
|
|
Removes the content and content observers.
|
|
|
|
@method destroy
|
|
*/
|
|
destroy: function() {
|
|
if (!this._super()) { return; }
|
|
|
|
var content = get(this, 'content');
|
|
if (content) { content.removeArrayObserver(this); }
|
|
|
|
if (this._createdEmptyView) {
|
|
this._createdEmptyView.destroy();
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Called when a mutation to the underlying content array will occur.
|
|
|
|
This method will remove any views that are no longer in the underlying
|
|
content array.
|
|
|
|
Invokes whenever the content array itself will change.
|
|
|
|
@method arrayWillChange
|
|
@param {Array} content the managed collection of objects
|
|
@param {Number} start the index at which the changes will occur
|
|
@param {Number} removed number of object to be removed from content
|
|
*/
|
|
arrayWillChange: function(content, start, removedCount) {
|
|
// If the contents were empty before and this template collection has an
|
|
// empty view remove it now.
|
|
var emptyView = get(this, 'emptyView');
|
|
if (emptyView && emptyView instanceof View) {
|
|
emptyView.removeFromParent();
|
|
}
|
|
|
|
// Loop through child views that correspond with the removed items.
|
|
// Note that we loop from the end of the array to the beginning because
|
|
// we are mutating it as we go.
|
|
var childViews = this._childViews;
|
|
var childView, idx;
|
|
|
|
for (idx = start + removedCount - 1; idx >= start; idx--) {
|
|
childView = childViews[idx];
|
|
childView.destroy();
|
|
}
|
|
},
|
|
|
|
/**
|
|
Called when a mutation to the underlying content array occurs.
|
|
|
|
This method will replay that mutation against the views that compose the
|
|
`Ember.CollectionView`, ensuring that the view reflects the model.
|
|
|
|
This array observer is added in `contentDidChange`.
|
|
|
|
@method arrayDidChange
|
|
@param {Array} content the managed collection of objects
|
|
@param {Number} start the index at which the changes occurred
|
|
@param {Number} removed number of object removed from content
|
|
@param {Number} added number of object added to content
|
|
*/
|
|
arrayDidChange: function(content, start, removed, added) {
|
|
var addedViews = [];
|
|
var view, item, idx, len, itemViewClass, emptyView, itemViewProps;
|
|
|
|
len = content ? get(content, 'length') : 0;
|
|
|
|
if (len) {
|
|
itemViewProps = this._itemViewProps || {};
|
|
itemViewClass = get(this, 'itemViewClass');
|
|
|
|
itemViewClass = readViewFactory(itemViewClass, this.container);
|
|
|
|
for (idx = start; idx < start+added; idx++) {
|
|
item = content.objectAt(idx);
|
|
|
|
itemViewProps.content = item;
|
|
itemViewProps._blockArguments = [item];
|
|
itemViewProps.contentIndex = idx;
|
|
|
|
view = this.createChildView(itemViewClass, itemViewProps);
|
|
|
|
addedViews.push(view);
|
|
}
|
|
} else {
|
|
emptyView = get(this, 'emptyView');
|
|
|
|
if (!emptyView) { return; }
|
|
|
|
if ('string' === typeof emptyView && isGlobalPath(emptyView)) {
|
|
emptyView = get(emptyView) || emptyView;
|
|
}
|
|
|
|
emptyView = this.createChildView(emptyView);
|
|
|
|
addedViews.push(emptyView);
|
|
set(this, 'emptyView', emptyView);
|
|
|
|
if (CoreView.detect(emptyView)) {
|
|
this._createdEmptyView = emptyView;
|
|
}
|
|
}
|
|
|
|
this.replace(start, 0, addedViews);
|
|
},
|
|
|
|
/**
|
|
Instantiates a view to be added to the childViews array during view
|
|
initialization. You generally will not call this method directly unless
|
|
you are overriding `createChildViews()`. Note that this method will
|
|
automatically configure the correct settings on the new view instance to
|
|
act as a child of the parent.
|
|
|
|
The tag name for the view will be set to the tagName of the viewClass
|
|
passed in.
|
|
|
|
@method createChildView
|
|
@param {Class} viewClass
|
|
@param {Hash} [attrs] Attributes to add
|
|
@return {Ember.View} new instance
|
|
*/
|
|
createChildView: function(view, attrs) {
|
|
view = this._super(view, attrs);
|
|
|
|
var itemTagName = get(view, 'tagName');
|
|
|
|
if (itemTagName === null || itemTagName === undefined) {
|
|
itemTagName = CollectionView.CONTAINER_MAP[get(this, 'tagName')];
|
|
set(view, 'tagName', itemTagName);
|
|
}
|
|
|
|
return view;
|
|
}
|
|
});
|
|
|
|
/**
|
|
A map of parent tags to their default child tags. You can add
|
|
additional parent tags if you want collection views that use
|
|
a particular parent tag to default to a child tag.
|
|
|
|
@property CONTAINER_MAP
|
|
@type Hash
|
|
@static
|
|
@final
|
|
*/
|
|
CollectionView.CONTAINER_MAP = {
|
|
ul: 'li',
|
|
ol: 'li',
|
|
table: 'tr',
|
|
thead: 'tr',
|
|
tbody: 'tr',
|
|
tfoot: 'tr',
|
|
tr: 'td',
|
|
select: 'option'
|
|
};
|
|
|
|
__exports__["default"] = CollectionView;
|
|
});
|
|
enifed("ember-views/views/component",
|
|
["ember-metal/core","ember-views/mixins/component_template_deprecation","ember-runtime/mixins/target_action_support","ember-views/views/view","ember-metal/property_get","ember-metal/property_set","ember-metal/is_none","ember-metal/computed","ember-htmlbars/templates/component","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.assert, Ember.Handlebars
|
|
|
|
var ComponentTemplateDeprecation = __dependency2__["default"];
|
|
var TargetActionSupport = __dependency3__["default"];
|
|
var View = __dependency4__["default"];
|
|
|
|
var get = __dependency5__.get;
|
|
var set = __dependency6__.set;
|
|
var isNone = __dependency7__["default"];
|
|
|
|
var computed = __dependency8__.computed;
|
|
var defaultComponentLayout = __dependency9__["default"];
|
|
|
|
var a_slice = Array.prototype.slice;
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-views
|
|
*/
|
|
|
|
/**
|
|
An `Ember.Component` is a view that is completely
|
|
isolated. Properties accessed in its templates go
|
|
to the view object and actions are targeted at
|
|
the view object. There is no access to the
|
|
surrounding context or outer controller; all
|
|
contextual information must be passed in.
|
|
|
|
The easiest way to create an `Ember.Component` is via
|
|
a template. If you name a template
|
|
`components/my-foo`, you will be able to use
|
|
`{{my-foo}}` in other templates, which will make
|
|
an instance of the isolated component.
|
|
|
|
```handlebars
|
|
{{app-profile person=currentUser}}
|
|
```
|
|
|
|
```handlebars
|
|
<!-- app-profile template -->
|
|
<h1>{{person.title}}</h1>
|
|
<img {{bind-attr src=person.avatar}}>
|
|
<p class='signature'>{{person.signature}}</p>
|
|
```
|
|
|
|
You can use `yield` inside a template to
|
|
include the **contents** of any block attached to
|
|
the component. The block will be executed in the
|
|
context of the surrounding context or outer controller:
|
|
|
|
```handlebars
|
|
{{#app-profile person=currentUser}}
|
|
<p>Admin mode</p>
|
|
{{! Executed in the controller's context. }}
|
|
{{/app-profile}}
|
|
```
|
|
|
|
```handlebars
|
|
<!-- app-profile template -->
|
|
<h1>{{person.title}}</h1>
|
|
{{! Executed in the components context. }}
|
|
{{yield}} {{! block contents }}
|
|
```
|
|
|
|
If you want to customize the component, in order to
|
|
handle events or actions, you implement a subclass
|
|
of `Ember.Component` named after the name of the
|
|
component. Note that `Component` needs to be appended to the name of
|
|
your subclass like `AppProfileComponent`.
|
|
|
|
For example, you could implement the action
|
|
`hello` for the `app-profile` component:
|
|
|
|
```javascript
|
|
App.AppProfileComponent = Ember.Component.extend({
|
|
actions: {
|
|
hello: function(name) {
|
|
console.log("Hello", name);
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
And then use it in the component's template:
|
|
|
|
```handlebars
|
|
<!-- app-profile template -->
|
|
|
|
<h1>{{person.title}}</h1>
|
|
{{yield}} <!-- block contents -->
|
|
|
|
<button {{action 'hello' person.name}}>
|
|
Say Hello to {{person.name}}
|
|
</button>
|
|
```
|
|
|
|
Components must have a `-` in their name to avoid
|
|
conflicts with built-in controls that wrap HTML
|
|
elements. This is consistent with the same
|
|
requirement in web components.
|
|
|
|
@class Component
|
|
@namespace Ember
|
|
@extends Ember.View
|
|
*/
|
|
var Component = View.extend(TargetActionSupport, ComponentTemplateDeprecation, {
|
|
instrumentName: 'component',
|
|
instrumentDisplay: computed(function() {
|
|
if (this._debugContainerKey) {
|
|
return '{{' + this._debugContainerKey.split(':')[1] + '}}';
|
|
}
|
|
}),
|
|
|
|
init: function() {
|
|
this._super();
|
|
this._keywords.view = this;
|
|
set(this, 'context', this);
|
|
set(this, 'controller', this);
|
|
},
|
|
|
|
defaultLayout: defaultComponentLayout,
|
|
|
|
/**
|
|
A components template property is set by passing a block
|
|
during its invocation. It is executed within the parent context.
|
|
|
|
Example:
|
|
|
|
```handlebars
|
|
{{#my-component}}
|
|
// something that is run in the context
|
|
// of the parent context
|
|
{{/my-component}}
|
|
```
|
|
|
|
Specifying a template directly to a component is deprecated without
|
|
also specifying the layout property.
|
|
|
|
@deprecated
|
|
@property template
|
|
*/
|
|
template: computed(function(key, value) {
|
|
if (value !== undefined) { return value; }
|
|
|
|
var templateName = get(this, 'templateName');
|
|
var template = this.templateForName(templateName, 'template');
|
|
|
|
Ember.assert("You specified the templateName " + templateName + " for " + this + ", but it did not exist.", !templateName || !!template);
|
|
|
|
return template || get(this, 'defaultTemplate');
|
|
}).property('templateName'),
|
|
|
|
/**
|
|
Specifying a components `templateName` is deprecated without also
|
|
providing the `layout` or `layoutName` properties.
|
|
|
|
@deprecated
|
|
@property templateName
|
|
*/
|
|
templateName: null,
|
|
|
|
_setupKeywords: function() {},
|
|
|
|
_yield: function(context, options, morph, blockArguments) {
|
|
var view = options.data.view;
|
|
var parentView = this._parentView;
|
|
var template = get(this, 'template');
|
|
|
|
if (template) {
|
|
Ember.assert("A Component must have a parent view in order to yield.", parentView);
|
|
|
|
view.appendChild(View, {
|
|
isVirtual: true,
|
|
tagName: '',
|
|
template: template,
|
|
_blockArguments: blockArguments,
|
|
_contextView: parentView,
|
|
_morph: morph,
|
|
context: get(parentView, 'context'),
|
|
controller: get(parentView, 'controller')
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
If the component is currently inserted into the DOM of a parent view, this
|
|
property will point to the controller of the parent view.
|
|
|
|
@property targetObject
|
|
@type Ember.Controller
|
|
@default null
|
|
*/
|
|
targetObject: computed(function(key) {
|
|
var parentView = get(this, '_parentView');
|
|
return parentView ? get(parentView, 'controller') : null;
|
|
}).property('_parentView'),
|
|
|
|
/**
|
|
Triggers a named action on the controller context where the component is used if
|
|
this controller has registered for notifications of the action.
|
|
|
|
For example a component for playing or pausing music may translate click events
|
|
into action notifications of "play" or "stop" depending on some internal state
|
|
of the component:
|
|
|
|
|
|
```javascript
|
|
App.PlayButtonComponent = Ember.Component.extend({
|
|
click: function(){
|
|
if (this.get('isPlaying')) {
|
|
this.sendAction('play');
|
|
} else {
|
|
this.sendAction('stop');
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
When used inside a template these component actions are configured to
|
|
trigger actions in the outer application context:
|
|
|
|
```handlebars
|
|
{{! application.hbs }}
|
|
{{play-button play="musicStarted" stop="musicStopped"}}
|
|
```
|
|
|
|
When the component receives a browser `click` event it translate this
|
|
interaction into application-specific semantics ("play" or "stop") and
|
|
triggers the specified action name on the controller for the template
|
|
where the component is used:
|
|
|
|
|
|
```javascript
|
|
App.ApplicationController = Ember.Controller.extend({
|
|
actions: {
|
|
musicStarted: function(){
|
|
// called when the play button is clicked
|
|
// and the music started playing
|
|
},
|
|
musicStopped: function(){
|
|
// called when the play button is clicked
|
|
// and the music stopped playing
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
If no action name is passed to `sendAction` a default name of "action"
|
|
is assumed.
|
|
|
|
```javascript
|
|
App.NextButtonComponent = Ember.Component.extend({
|
|
click: function(){
|
|
this.sendAction();
|
|
}
|
|
});
|
|
```
|
|
|
|
```handlebars
|
|
{{! application.hbs }}
|
|
{{next-button action="playNextSongInAlbum"}}
|
|
```
|
|
|
|
```javascript
|
|
App.ApplicationController = Ember.Controller.extend({
|
|
actions: {
|
|
playNextSongInAlbum: function(){
|
|
...
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
@method sendAction
|
|
@param [action] {String} the action to trigger
|
|
@param [context] {*} a context to send with the action
|
|
*/
|
|
sendAction: function(action) {
|
|
var actionName;
|
|
var contexts = a_slice.call(arguments, 1);
|
|
|
|
// Send the default action
|
|
if (action === undefined) {
|
|
actionName = get(this, 'action');
|
|
Ember.assert("The default action was triggered on the component " + this.toString() +
|
|
", but the action name (" + actionName + ") was not a string.",
|
|
isNone(actionName) || typeof actionName === 'string');
|
|
} else {
|
|
actionName = get(this, action);
|
|
Ember.assert("The " + action + " action was triggered on the component " +
|
|
this.toString() + ", but the action name (" + actionName +
|
|
") was not a string.",
|
|
isNone(actionName) || typeof actionName === 'string');
|
|
}
|
|
|
|
// If no action name for that action could be found, just abort.
|
|
if (actionName === undefined) { return; }
|
|
|
|
this.triggerAction({
|
|
action: actionName,
|
|
actionContext: contexts
|
|
});
|
|
},
|
|
|
|
send: function(actionName) {
|
|
var args = [].slice.call(arguments, 1);
|
|
var target;
|
|
var hasAction = this._actions && this._actions[actionName];
|
|
|
|
if (hasAction) {
|
|
if (this._actions[actionName].apply(this, args) === true) {
|
|
// handler returned true, so this action will bubble
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (target = get(this, 'target')) {
|
|
Ember.assert("The `target` for " + this + " (" + target +
|
|
") does not have a `send` method", typeof target.send === 'function');
|
|
target.send.apply(target, arguments);
|
|
} else {
|
|
if (!hasAction) {
|
|
throw new Error(Ember.inspect(this) + ' had no action handler for: ' + actionName);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
__exports__["default"] = Component;
|
|
});
|
|
enifed("ember-views/views/container_view",
|
|
["ember-metal/core","ember-metal/merge","ember-runtime/mixins/mutable_array","ember-metal/property_get","ember-metal/property_set","ember-views/views/view","ember-views/views/states","ember-metal/error","ember-metal/enumerable_utils","ember-metal/computed","ember-metal/run_loop","ember-metal/properties","ember-metal/mixin","ember-runtime/system/native_array","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.assert, Ember.deprecate
|
|
|
|
var merge = __dependency2__["default"];
|
|
var MutableArray = __dependency3__["default"];
|
|
var get = __dependency4__.get;
|
|
var set = __dependency5__.set;
|
|
|
|
var View = __dependency6__["default"];
|
|
|
|
var cloneStates = __dependency7__.cloneStates;
|
|
var EmberViewStates = __dependency7__.states;
|
|
|
|
var EmberError = __dependency8__["default"];
|
|
|
|
var forEach = __dependency9__.forEach;
|
|
|
|
var computed = __dependency10__.computed;
|
|
var run = __dependency11__["default"];
|
|
var defineProperty = __dependency12__.defineProperty;
|
|
var observer = __dependency13__.observer;
|
|
var beforeObserver = __dependency13__.beforeObserver;
|
|
var emberA = __dependency14__.A;
|
|
|
|
function K() { return this; }
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-views
|
|
*/
|
|
|
|
var states = cloneStates(EmberViewStates);
|
|
|
|
/**
|
|
A `ContainerView` is an `Ember.View` subclass that implements `Ember.MutableArray`
|
|
allowing programmatic management of its child views.
|
|
|
|
## Setting Initial Child Views
|
|
|
|
The initial array of child views can be set in one of two ways. You can
|
|
provide a `childViews` property at creation time that contains instance of
|
|
`Ember.View`:
|
|
|
|
```javascript
|
|
aContainer = Ember.ContainerView.create({
|
|
childViews: [Ember.View.create(), Ember.View.create()]
|
|
});
|
|
```
|
|
|
|
You can also provide a list of property names whose values are instances of
|
|
`Ember.View`:
|
|
|
|
```javascript
|
|
aContainer = Ember.ContainerView.create({
|
|
childViews: ['aView', 'bView', 'cView'],
|
|
aView: Ember.View.create(),
|
|
bView: Ember.View.create(),
|
|
cView: Ember.View.create()
|
|
});
|
|
```
|
|
|
|
The two strategies can be combined:
|
|
|
|
```javascript
|
|
aContainer = Ember.ContainerView.create({
|
|
childViews: ['aView', Ember.View.create()],
|
|
aView: Ember.View.create()
|
|
});
|
|
```
|
|
|
|
Each child view's rendering will be inserted into the container's rendered
|
|
HTML in the same order as its position in the `childViews` property.
|
|
|
|
## Adding and Removing Child Views
|
|
|
|
The container view implements `Ember.MutableArray` allowing programmatic management of its child views.
|
|
|
|
To remove a view, pass that view into a `removeObject` call on the container view.
|
|
|
|
Given an empty `<body>` the following code
|
|
|
|
```javascript
|
|
aContainer = Ember.ContainerView.create({
|
|
classNames: ['the-container'],
|
|
childViews: ['aView', 'bView'],
|
|
aView: Ember.View.create({
|
|
template: Ember.Handlebars.compile("A")
|
|
}),
|
|
bView: Ember.View.create({
|
|
template: Ember.Handlebars.compile("B")
|
|
})
|
|
});
|
|
|
|
aContainer.appendTo('body');
|
|
```
|
|
|
|
Results in the HTML
|
|
|
|
```html
|
|
<div class="ember-view the-container">
|
|
<div class="ember-view">A</div>
|
|
<div class="ember-view">B</div>
|
|
</div>
|
|
```
|
|
|
|
Removing a view
|
|
|
|
```javascript
|
|
aContainer.toArray(); // [aContainer.aView, aContainer.bView]
|
|
aContainer.removeObject(aContainer.get('bView'));
|
|
aContainer.toArray(); // [aContainer.aView]
|
|
```
|
|
|
|
Will result in the following HTML
|
|
|
|
```html
|
|
<div class="ember-view the-container">
|
|
<div class="ember-view">A</div>
|
|
</div>
|
|
```
|
|
|
|
Similarly, adding a child view is accomplished by adding `Ember.View` instances to the
|
|
container view.
|
|
|
|
Given an empty `<body>` the following code
|
|
|
|
```javascript
|
|
aContainer = Ember.ContainerView.create({
|
|
classNames: ['the-container'],
|
|
childViews: ['aView', 'bView'],
|
|
aView: Ember.View.create({
|
|
template: Ember.Handlebars.compile("A")
|
|
}),
|
|
bView: Ember.View.create({
|
|
template: Ember.Handlebars.compile("B")
|
|
})
|
|
});
|
|
|
|
aContainer.appendTo('body');
|
|
```
|
|
|
|
Results in the HTML
|
|
|
|
```html
|
|
<div class="ember-view the-container">
|
|
<div class="ember-view">A</div>
|
|
<div class="ember-view">B</div>
|
|
</div>
|
|
```
|
|
|
|
Adding a view
|
|
|
|
```javascript
|
|
AnotherViewClass = Ember.View.extend({
|
|
template: Ember.Handlebars.compile("Another view")
|
|
});
|
|
|
|
aContainer.toArray(); // [aContainer.aView, aContainer.bView]
|
|
aContainer.pushObject(AnotherViewClass.create());
|
|
aContainer.toArray(); // [aContainer.aView, aContainer.bView, <AnotherViewClass instance>]
|
|
```
|
|
|
|
Will result in the following HTML
|
|
|
|
```html
|
|
<div class="ember-view the-container">
|
|
<div class="ember-view">A</div>
|
|
<div class="ember-view">B</div>
|
|
<div class="ember-view">Another view</div>
|
|
</div>
|
|
```
|
|
|
|
## Templates and Layout
|
|
|
|
A `template`, `templateName`, `defaultTemplate`, `layout`, `layoutName` or
|
|
`defaultLayout` property on a container view will not result in the template
|
|
or layout being rendered. The HTML contents of a `Ember.ContainerView`'s DOM
|
|
representation will only be the rendered HTML of its child views.
|
|
|
|
@class ContainerView
|
|
@namespace Ember
|
|
@extends Ember.View
|
|
*/
|
|
var ContainerView = View.extend(MutableArray, {
|
|
_states: states,
|
|
|
|
willWatchProperty: function(prop){
|
|
Ember.deprecate(
|
|
"ContainerViews should not be observed as arrays. This behavior will change in future implementations of ContainerView.",
|
|
!prop.match(/\[]/) && prop.indexOf('@') !== 0
|
|
);
|
|
},
|
|
|
|
init: function() {
|
|
this._super();
|
|
|
|
var childViews = get(this, 'childViews');
|
|
Ember.deprecate('Setting `childViews` on a Container is deprecated.', Ember.isEmpty(childViews));
|
|
|
|
// redefine view's childViews property that was obliterated
|
|
defineProperty(this, 'childViews', View.childViewsProperty);
|
|
|
|
var _childViews = this._childViews;
|
|
|
|
forEach(childViews, function(viewName, idx) {
|
|
var view;
|
|
|
|
if ('string' === typeof viewName) {
|
|
view = get(this, viewName);
|
|
view = this.createChildView(view);
|
|
set(this, viewName, view);
|
|
} else {
|
|
view = this.createChildView(viewName);
|
|
}
|
|
|
|
_childViews[idx] = view;
|
|
}, this);
|
|
|
|
var currentView = get(this, 'currentView');
|
|
if (currentView) {
|
|
if (!_childViews.length) { _childViews = this._childViews = this._childViews.slice(); }
|
|
_childViews.push(this.createChildView(currentView));
|
|
}
|
|
},
|
|
|
|
replace: function(idx, removedCount, addedViews) {
|
|
var addedCount = addedViews ? get(addedViews, 'length') : 0;
|
|
var self = this;
|
|
Ember.assert("You can't add a child to a container - the child is already a child of another view", emberA(addedViews).every(function(item) { return !get(item, '_parentView') || get(item, '_parentView') === self; }));
|
|
|
|
this.arrayContentWillChange(idx, removedCount, addedCount);
|
|
this.childViewsWillChange(this._childViews, idx, removedCount);
|
|
|
|
if (addedCount === 0) {
|
|
this._childViews.splice(idx, removedCount) ;
|
|
} else {
|
|
var args = [idx, removedCount].concat(addedViews);
|
|
if (addedViews.length && !this._childViews.length) { this._childViews = this._childViews.slice(); }
|
|
this._childViews.splice.apply(this._childViews, args);
|
|
}
|
|
|
|
this.arrayContentDidChange(idx, removedCount, addedCount);
|
|
this.childViewsDidChange(this._childViews, idx, removedCount, addedCount);
|
|
|
|
return this;
|
|
},
|
|
|
|
objectAt: function(idx) {
|
|
return this._childViews[idx];
|
|
},
|
|
|
|
length: computed(function () {
|
|
return this._childViews.length;
|
|
})["volatile"](),
|
|
|
|
/**
|
|
Instructs each child view to render to the passed render buffer.
|
|
|
|
@private
|
|
@method render
|
|
@param {Ember.RenderBuffer} buffer the buffer to render to
|
|
*/
|
|
render: function(buffer) {
|
|
var element = buffer.element();
|
|
var dom = buffer.dom;
|
|
|
|
if (this.tagName === '') {
|
|
element = dom.createDocumentFragment();
|
|
buffer._element = element;
|
|
this._childViewsMorph = dom.appendMorph(element, this._morph.contextualElement);
|
|
} else {
|
|
this._childViewsMorph = dom.createMorph(element, element.lastChild, null);
|
|
}
|
|
|
|
return element;
|
|
},
|
|
|
|
instrumentName: 'container',
|
|
|
|
/**
|
|
When a child view is removed, destroy its element so that
|
|
it is removed from the DOM.
|
|
|
|
The array observer that triggers this action is set up in the
|
|
`renderToBuffer` method.
|
|
|
|
@private
|
|
@method childViewsWillChange
|
|
@param {Ember.Array} views the child views array before mutation
|
|
@param {Number} start the start position of the mutation
|
|
@param {Number} removed the number of child views removed
|
|
**/
|
|
childViewsWillChange: function(views, start, removed) {
|
|
this.propertyWillChange('childViews');
|
|
|
|
if (removed > 0) {
|
|
var changedViews = views.slice(start, start+removed);
|
|
// transition to preRender before clearing parentView
|
|
this.currentState.childViewsWillChange(this, views, start, removed);
|
|
this.initializeViews(changedViews, null, null);
|
|
}
|
|
},
|
|
|
|
removeChild: function(child) {
|
|
this.removeObject(child);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
When a child view is added, make sure the DOM gets updated appropriately.
|
|
|
|
If the view has already rendered an element, we tell the child view to
|
|
create an element and insert it into the DOM. If the enclosing container
|
|
view has already written to a buffer, but not yet converted that buffer
|
|
into an element, we insert the string representation of the child into the
|
|
appropriate place in the buffer.
|
|
|
|
@private
|
|
@method childViewsDidChange
|
|
@param {Ember.Array} views the array of child views after the mutation has occurred
|
|
@param {Number} start the start position of the mutation
|
|
@param {Number} removed the number of child views removed
|
|
@param {Number} added the number of child views added
|
|
*/
|
|
childViewsDidChange: function(views, start, removed, added) {
|
|
if (added > 0) {
|
|
var changedViews = views.slice(start, start+added);
|
|
this.initializeViews(changedViews, this);
|
|
this.currentState.childViewsDidChange(this, views, start, added);
|
|
}
|
|
this.propertyDidChange('childViews');
|
|
},
|
|
|
|
initializeViews: function(views, parentView) {
|
|
forEach(views, function(view) {
|
|
set(view, '_parentView', parentView);
|
|
|
|
if (!view.container && parentView) {
|
|
set(view, 'container', parentView.container);
|
|
}
|
|
});
|
|
},
|
|
|
|
currentView: null,
|
|
|
|
_currentViewWillChange: beforeObserver('currentView', function() {
|
|
var currentView = get(this, 'currentView');
|
|
if (currentView) {
|
|
currentView.destroy();
|
|
}
|
|
}),
|
|
|
|
_currentViewDidChange: observer('currentView', function() {
|
|
var currentView = get(this, 'currentView');
|
|
if (currentView) {
|
|
Ember.assert("You tried to set a current view that already has a parent. Make sure you don't have multiple outlets in the same view.", !get(currentView, '_parentView'));
|
|
this.pushObject(currentView);
|
|
}
|
|
}),
|
|
|
|
_ensureChildrenAreInDOM: function () {
|
|
this.currentState.ensureChildrenAreInDOM(this);
|
|
}
|
|
});
|
|
|
|
merge(states._default, {
|
|
childViewsWillChange: K,
|
|
childViewsDidChange: K,
|
|
ensureChildrenAreInDOM: K
|
|
});
|
|
|
|
merge(states.inBuffer, {
|
|
childViewsDidChange: function(parentView, views, start, added) {
|
|
throw new EmberError('You cannot modify child views while in the inBuffer state');
|
|
}
|
|
});
|
|
|
|
merge(states.hasElement, {
|
|
childViewsWillChange: function(view, views, start, removed) {
|
|
for (var i=start; i<start+removed; i++) {
|
|
var _view = views[i];
|
|
_view._unsubscribeFromStreamBindings();
|
|
_view.remove();
|
|
}
|
|
},
|
|
|
|
childViewsDidChange: function(view, views, start, added) {
|
|
run.scheduleOnce('render', view, '_ensureChildrenAreInDOM');
|
|
},
|
|
|
|
ensureChildrenAreInDOM: function(view) {
|
|
var childViews = view._childViews;
|
|
var renderer = view._renderer;
|
|
|
|
var i, len, childView;
|
|
for (i = 0, len = childViews.length; i < len; i++) {
|
|
childView = childViews[i];
|
|
if (!childView._elementCreated) {
|
|
renderer.renderTree(childView, view, i);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
__exports__["default"] = ContainerView;
|
|
});
|
|
enifed("ember-views/views/core_view",
|
|
["ember-views/system/renderer","ember-views/views/states","ember-runtime/system/object","ember-runtime/mixins/evented","ember-runtime/mixins/action_handler","ember-metal/property_get","ember-metal/computed","ember-metal/utils","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) {
|
|
"use strict";
|
|
var Rerender = __dependency1__["default"];
|
|
|
|
var cloneStates = __dependency2__.cloneStates;
|
|
var states = __dependency2__.states;
|
|
var EmberObject = __dependency3__["default"];
|
|
var Evented = __dependency4__["default"];
|
|
var ActionHandler = __dependency5__["default"];
|
|
|
|
var get = __dependency6__.get;
|
|
var computed = __dependency7__.computed;
|
|
|
|
var typeOf = __dependency8__.typeOf;
|
|
|
|
function K() { return this; }
|
|
|
|
/**
|
|
`Ember.CoreView` is an abstract class that exists to give view-like behavior
|
|
to both Ember's main view class `Ember.View` and other classes like
|
|
`Ember._SimpleMetamorphView` that don't need the fully functionaltiy of
|
|
`Ember.View`.
|
|
|
|
Unless you have specific needs for `CoreView`, you will use `Ember.View`
|
|
in your applications.
|
|
|
|
@class CoreView
|
|
@namespace Ember
|
|
@extends Ember.Object
|
|
@uses Ember.Evented
|
|
@uses Ember.ActionHandler
|
|
*/
|
|
var CoreView = EmberObject.extend(Evented, ActionHandler, {
|
|
isView: true,
|
|
isVirtual: false,
|
|
|
|
_states: cloneStates(states),
|
|
|
|
init: function() {
|
|
this._super();
|
|
this._state = 'preRender';
|
|
this.currentState = this._states.preRender;
|
|
this._isVisible = get(this, 'isVisible');
|
|
},
|
|
|
|
/**
|
|
If the view is currently inserted into the DOM of a parent view, this
|
|
property will point to the parent of the view.
|
|
|
|
@property parentView
|
|
@type Ember.View
|
|
@default null
|
|
*/
|
|
parentView: computed('_parentView', function() {
|
|
var parent = this._parentView;
|
|
|
|
if (parent && parent.isVirtual) {
|
|
return get(parent, 'parentView');
|
|
} else {
|
|
return parent;
|
|
}
|
|
}),
|
|
|
|
_state: null,
|
|
|
|
_parentView: null,
|
|
|
|
// return the current view, not including virtual views
|
|
concreteView: computed('parentView', function() {
|
|
if (!this.isVirtual) { return this; }
|
|
else { return get(this, 'parentView.concreteView'); }
|
|
}),
|
|
|
|
instrumentName: 'core_view',
|
|
|
|
instrumentDetails: function(hash) {
|
|
hash.object = this.toString();
|
|
hash.containerKey = this._debugContainerKey;
|
|
hash.view = this;
|
|
},
|
|
|
|
/**
|
|
Override the default event firing from `Ember.Evented` to
|
|
also call methods with the given name.
|
|
|
|
@method trigger
|
|
@param name {String}
|
|
@private
|
|
*/
|
|
trigger: function() {
|
|
this._super.apply(this, arguments);
|
|
var name = arguments[0];
|
|
var method = this[name];
|
|
if (method) {
|
|
var length = arguments.length;
|
|
var args = new Array(length - 1);
|
|
for (var i = 1; i < length; i++) {
|
|
args[i - 1] = arguments[i];
|
|
}
|
|
return method.apply(this, args);
|
|
}
|
|
},
|
|
|
|
has: function(name) {
|
|
return typeOf(this[name]) === 'function' || this._super(name);
|
|
},
|
|
|
|
destroy: function() {
|
|
var parent = this._parentView;
|
|
|
|
if (!this._super()) { return; }
|
|
|
|
|
|
// destroy the element -- this will avoid each child view destroying
|
|
// the element over and over again...
|
|
if (!this.removedFromDOM && this._renderer) {
|
|
this._renderer.remove(this, true);
|
|
}
|
|
|
|
// remove from parent if found. Don't call removeFromParent,
|
|
// as removeFromParent will try to remove the element from
|
|
// the DOM again.
|
|
if (parent) { parent.removeChild(this); }
|
|
|
|
this._transitionTo('destroying', false);
|
|
|
|
return this;
|
|
},
|
|
|
|
clearRenderedChildren: K,
|
|
_transitionTo: K,
|
|
destroyElement: K
|
|
});
|
|
|
|
CoreView.reopenClass({
|
|
renderer: new Rerender()
|
|
});
|
|
|
|
__exports__["default"] = CoreView;
|
|
});
|
|
enifed("ember-views/views/each",
|
|
["ember-metal/core","ember-runtime/system/string","ember-metal/property_get","ember-metal/property_set","ember-views/views/collection_view","ember-metal/binding","ember-runtime/mixins/controller","ember-runtime/controllers/array_controller","ember-runtime/mixins/array","ember-metal/observer","ember-views/views/metamorph_view","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
var fmt = __dependency2__.fmt;
|
|
var get = __dependency3__.get;
|
|
var set = __dependency4__.set;
|
|
var CollectionView = __dependency5__["default"];
|
|
var Binding = __dependency6__.Binding;
|
|
var ControllerMixin = __dependency7__["default"];
|
|
var ArrayController = __dependency8__["default"];
|
|
var EmberArray = __dependency9__["default"];
|
|
|
|
var addObserver = __dependency10__.addObserver;
|
|
var removeObserver = __dependency10__.removeObserver;
|
|
var addBeforeObserver = __dependency10__.addBeforeObserver;
|
|
var removeBeforeObserver = __dependency10__.removeBeforeObserver;
|
|
|
|
var _MetamorphView = __dependency11__["default"];
|
|
var _Metamorph = __dependency11__._Metamorph;
|
|
|
|
__exports__["default"] = CollectionView.extend(_Metamorph, {
|
|
|
|
init: function() {
|
|
var itemController = get(this, 'itemController');
|
|
var binding;
|
|
|
|
if (itemController) {
|
|
var controller = get(this, 'controller.container').lookupFactory('controller:array').create({
|
|
_isVirtual: true,
|
|
parentController: get(this, 'controller'),
|
|
itemController: itemController,
|
|
target: get(this, 'controller'),
|
|
_eachView: this
|
|
});
|
|
|
|
this.disableContentObservers(function() {
|
|
set(this, 'content', controller);
|
|
binding = new Binding('content', '_eachView.dataSource').oneWay();
|
|
binding.connect(controller);
|
|
});
|
|
|
|
set(this, '_arrayController', controller);
|
|
} else {
|
|
this.disableContentObservers(function() {
|
|
binding = new Binding('content', 'dataSource').oneWay();
|
|
binding.connect(this);
|
|
});
|
|
}
|
|
|
|
return this._super();
|
|
},
|
|
|
|
_assertArrayLike: function(content) {
|
|
Ember.assert(fmt("The value that #each loops over must be an Array. You " +
|
|
"passed %@, but it should have been an ArrayController",
|
|
[content.constructor]),
|
|
!ControllerMixin.detect(content) ||
|
|
(content && content.isGenerated) ||
|
|
content instanceof ArrayController);
|
|
Ember.assert(fmt("The value that #each loops over must be an Array. You passed %@",
|
|
[(ControllerMixin.detect(content) &&
|
|
content.get('model') !== undefined) ?
|
|
fmt("'%@' (wrapped in %@)", [content.get('model'), content]) : content]),
|
|
EmberArray.detect(content));
|
|
},
|
|
|
|
disableContentObservers: function(callback) {
|
|
removeBeforeObserver(this, 'content', null, '_contentWillChange');
|
|
removeObserver(this, 'content', null, '_contentDidChange');
|
|
|
|
callback.call(this);
|
|
|
|
addBeforeObserver(this, 'content', null, '_contentWillChange');
|
|
addObserver(this, 'content', null, '_contentDidChange');
|
|
},
|
|
|
|
itemViewClass: _MetamorphView,
|
|
emptyViewClass: _MetamorphView,
|
|
|
|
createChildView: function(view, attrs) {
|
|
view = this._super(view, attrs);
|
|
|
|
var content = get(view, 'content');
|
|
var keyword = get(this, 'keyword');
|
|
|
|
if (keyword) {
|
|
view._keywords[keyword] = content;
|
|
}
|
|
|
|
// If {{#each}} is looping over an array of controllers,
|
|
// point each child view at their respective controller.
|
|
if (content && content.isController) {
|
|
set(view, 'controller', content);
|
|
}
|
|
|
|
return view;
|
|
},
|
|
|
|
destroy: function() {
|
|
if (!this._super()) { return; }
|
|
|
|
var arrayController = get(this, '_arrayController');
|
|
|
|
if (arrayController) {
|
|
arrayController.destroy();
|
|
}
|
|
|
|
return this;
|
|
}
|
|
});
|
|
});
|
|
enifed("ember-views/views/metamorph_view",
|
|
["ember-metal/core","ember-views/views/core_view","ember-views/views/view","ember-metal/mixin","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
|
|
"use strict";
|
|
/*jshint newcap:false*/
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.deprecate
|
|
|
|
var CoreView = __dependency2__["default"];
|
|
var View = __dependency3__["default"];
|
|
var Mixin = __dependency4__.Mixin;
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-views
|
|
*/
|
|
|
|
// The `morph` and `outerHTML` properties are internal only
|
|
// and not observable.
|
|
|
|
/**
|
|
@class _Metamorph
|
|
@namespace Ember
|
|
@private
|
|
*/
|
|
var _Metamorph = Mixin.create({
|
|
isVirtual: true,
|
|
tagName: '',
|
|
|
|
instrumentName: 'metamorph',
|
|
|
|
init: function() {
|
|
this._super();
|
|
Ember.deprecate('Supplying a tagName to Metamorph views is unreliable and is deprecated.' +
|
|
' You may be setting the tagName on a Handlebars helper that creates a Metamorph.', !this.tagName);
|
|
}
|
|
});
|
|
__exports__._Metamorph = _Metamorph;
|
|
/**
|
|
@class _MetamorphView
|
|
@namespace Ember
|
|
@extends Ember.View
|
|
@uses Ember._Metamorph
|
|
@private
|
|
*/
|
|
__exports__["default"] = View.extend(_Metamorph);
|
|
|
|
/**
|
|
@class _SimpleMetamorphView
|
|
@namespace Ember
|
|
@extends Ember.CoreView
|
|
@uses Ember._Metamorph
|
|
@private
|
|
*/
|
|
var _SimpleMetamorphView = CoreView.extend(_Metamorph);
|
|
__exports__._SimpleMetamorphView = _SimpleMetamorphView;
|
|
});
|
|
enifed("ember-views/views/select",
|
|
["ember-metal/enumerable_utils","ember-metal/property_get","ember-metal/property_set","ember-views/views/view","ember-views/views/collection_view","ember-metal/utils","ember-metal/is_none","ember-metal/computed","ember-runtime/system/native_array","ember-metal/mixin","ember-metal/properties","ember-htmlbars/templates/select","ember-htmlbars/templates/select-option","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-views
|
|
*/
|
|
|
|
var forEach = __dependency1__.forEach;
|
|
var indexOf = __dependency1__.indexOf;
|
|
var indexesOf = __dependency1__.indexesOf;
|
|
var replace = __dependency1__.replace;
|
|
|
|
var get = __dependency2__.get;
|
|
var set = __dependency3__.set;
|
|
var View = __dependency4__["default"];
|
|
var CollectionView = __dependency5__["default"];
|
|
var isArray = __dependency6__.isArray;
|
|
var isNone = __dependency7__["default"];
|
|
var computed = __dependency8__.computed;
|
|
var emberA = __dependency9__.A;
|
|
var observer = __dependency10__.observer;
|
|
var defineProperty = __dependency11__.defineProperty;
|
|
|
|
var htmlbarsTemplate = __dependency12__["default"];
|
|
var selectOptionDefaultTemplate = __dependency13__["default"];
|
|
|
|
var defaultTemplate = htmlbarsTemplate;
|
|
|
|
var SelectOption = View.extend({
|
|
instrumentDisplay: 'Ember.SelectOption',
|
|
|
|
tagName: 'option',
|
|
attributeBindings: ['value', 'selected'],
|
|
|
|
defaultTemplate: selectOptionDefaultTemplate,
|
|
|
|
init: function() {
|
|
this.labelPathDidChange();
|
|
this.valuePathDidChange();
|
|
|
|
this._super();
|
|
},
|
|
|
|
selected: computed(function() {
|
|
var content = get(this, 'content');
|
|
var selection = get(this, 'parentView.selection');
|
|
if (get(this, 'parentView.multiple')) {
|
|
return selection && indexOf(selection, content.valueOf()) > -1;
|
|
} else {
|
|
// Primitives get passed through bindings as objects... since
|
|
// `new Number(4) !== 4`, we use `==` below
|
|
return content == selection; // jshint ignore:line
|
|
}
|
|
}).property('content', 'parentView.selection'),
|
|
|
|
labelPathDidChange: observer('parentView.optionLabelPath', function() {
|
|
var labelPath = get(this, 'parentView.optionLabelPath');
|
|
|
|
if (!labelPath) { return; }
|
|
|
|
defineProperty(this, 'label', computed(function() {
|
|
return get(this, labelPath);
|
|
}).property(labelPath));
|
|
}),
|
|
|
|
valuePathDidChange: observer('parentView.optionValuePath', function() {
|
|
var valuePath = get(this, 'parentView.optionValuePath');
|
|
|
|
if (!valuePath) { return; }
|
|
|
|
defineProperty(this, 'value', computed(function() {
|
|
return get(this, valuePath);
|
|
}).property(valuePath));
|
|
})
|
|
});
|
|
|
|
var SelectOptgroup = CollectionView.extend({
|
|
instrumentDisplay: 'Ember.SelectOptgroup',
|
|
|
|
tagName: 'optgroup',
|
|
attributeBindings: ['label'],
|
|
|
|
selectionBinding: 'parentView.selection',
|
|
multipleBinding: 'parentView.multiple',
|
|
optionLabelPathBinding: 'parentView.optionLabelPath',
|
|
optionValuePathBinding: 'parentView.optionValuePath',
|
|
|
|
itemViewClassBinding: 'parentView.optionView'
|
|
});
|
|
|
|
/**
|
|
The `Ember.Select` view class renders a
|
|
[select](https://developer.mozilla.org/en/HTML/Element/select) HTML element,
|
|
allowing the user to choose from a list of options.
|
|
|
|
The text and `value` property of each `<option>` element within the
|
|
`<select>` element are populated from the objects in the `Element.Select`'s
|
|
`content` property. The underlying data object of the selected `<option>` is
|
|
stored in the `Element.Select`'s `value` property.
|
|
|
|
## The Content Property (array of strings)
|
|
|
|
The simplest version of an `Ember.Select` takes an array of strings as its
|
|
`content` property. The string will be used as both the `value` property and
|
|
the inner text of each `<option>` element inside the rendered `<select>`.
|
|
|
|
Example:
|
|
|
|
```javascript
|
|
App.ApplicationController = Ember.ObjectController.extend({
|
|
names: ["Yehuda", "Tom"]
|
|
});
|
|
```
|
|
|
|
```handlebars
|
|
{{view "select" content=names}}
|
|
```
|
|
|
|
Would result in the following HTML:
|
|
|
|
```html
|
|
<select class="ember-select">
|
|
<option value="Yehuda">Yehuda</option>
|
|
<option value="Tom">Tom</option>
|
|
</select>
|
|
```
|
|
|
|
You can control which `<option>` is selected through the `Ember.Select`'s
|
|
`value` property:
|
|
|
|
```javascript
|
|
App.ApplicationController = Ember.ObjectController.extend({
|
|
selectedName: 'Tom',
|
|
names: ["Yehuda", "Tom"]
|
|
});
|
|
```
|
|
|
|
```handlebars
|
|
{{view "select" content=names value=selectedName}}
|
|
```
|
|
|
|
Would result in the following HTML with the `<option>` for 'Tom' selected:
|
|
|
|
```html
|
|
<select class="ember-select">
|
|
<option value="Yehuda">Yehuda</option>
|
|
<option value="Tom" selected="selected">Tom</option>
|
|
</select>
|
|
```
|
|
|
|
A user interacting with the rendered `<select>` to choose "Yehuda" would
|
|
update the value of `selectedName` to "Yehuda".
|
|
|
|
## The Content Property (array of Objects)
|
|
|
|
An `Ember.Select` can also take an array of JavaScript or Ember objects as
|
|
its `content` property.
|
|
|
|
When using objects you need to tell the `Ember.Select` which property should
|
|
be accessed on each object to supply the `value` attribute of the `<option>`
|
|
and which property should be used to supply the element text.
|
|
|
|
The `optionValuePath` option is used to specify the path on each object to
|
|
the desired property for the `value` attribute. The `optionLabelPath`
|
|
specifies the path on each object to the desired property for the
|
|
element's text. Both paths must reference each object itself as `content`:
|
|
|
|
```javascript
|
|
App.ApplicationController = Ember.ObjectController.extend({
|
|
programmers: [
|
|
{firstName: "Yehuda", id: 1},
|
|
{firstName: "Tom", id: 2}
|
|
]
|
|
});
|
|
```
|
|
|
|
```handlebars
|
|
{{view "select"
|
|
content=programmers
|
|
optionValuePath="content.id"
|
|
optionLabelPath="content.firstName"}}
|
|
```
|
|
|
|
Would result in the following HTML:
|
|
|
|
```html
|
|
<select class="ember-select">
|
|
<option value="1">Yehuda</option>
|
|
<option value="2">Tom</option>
|
|
</select>
|
|
```
|
|
|
|
The `value` attribute of the selected `<option>` within an `Ember.Select`
|
|
can be bound to a property on another object:
|
|
|
|
```javascript
|
|
App.ApplicationController = Ember.ObjectController.extend({
|
|
programmers: [
|
|
{firstName: "Yehuda", id: 1},
|
|
{firstName: "Tom", id: 2}
|
|
],
|
|
currentProgrammer: {
|
|
id: 2
|
|
}
|
|
});
|
|
```
|
|
|
|
```handlebars
|
|
{{view "select"
|
|
content=programmers
|
|
optionValuePath="content.id"
|
|
optionLabelPath="content.firstName"
|
|
value=currentProgrammer.id}}
|
|
```
|
|
|
|
Would result in the following HTML with a selected option:
|
|
|
|
```html
|
|
<select class="ember-select">
|
|
<option value="1">Yehuda</option>
|
|
<option value="2" selected="selected">Tom</option>
|
|
</select>
|
|
```
|
|
|
|
Interacting with the rendered element by selecting the first option
|
|
('Yehuda') will update the `id` of `currentProgrammer`
|
|
to match the `value` property of the newly selected `<option>`.
|
|
|
|
Alternatively, you can control selection through the underlying objects
|
|
used to render each object by binding the `selection` option. When the selected
|
|
`<option>` is changed, the property path provided to `selection`
|
|
will be updated to match the content object of the rendered `<option>`
|
|
element:
|
|
|
|
```javascript
|
|
|
|
var yehuda = {firstName: "Yehuda", id: 1, bff4eva: 'tom'}
|
|
var tom = {firstName: "Tom", id: 2, bff4eva: 'yehuda'};
|
|
|
|
App.ApplicationController = Ember.ObjectController.extend({
|
|
selectedPerson: tom,
|
|
programmers: [ yehuda, tom ]
|
|
});
|
|
```
|
|
|
|
```handlebars
|
|
{{view "select"
|
|
content=programmers
|
|
optionValuePath="content.id"
|
|
optionLabelPath="content.firstName"
|
|
selection=selectedPerson}}
|
|
```
|
|
|
|
Would result in the following HTML with a selected option:
|
|
|
|
```html
|
|
<select class="ember-select">
|
|
<option value="1">Yehuda</option>
|
|
<option value="2" selected="selected">Tom</option>
|
|
</select>
|
|
```
|
|
|
|
Interacting with the rendered element by selecting the first option
|
|
('Yehuda') will update the `selectedPerson` to match the object of
|
|
the newly selected `<option>`. In this case it is the first object
|
|
in the `programmers`
|
|
|
|
## Supplying a Prompt
|
|
|
|
A `null` value for the `Ember.Select`'s `value` or `selection` property
|
|
results in there being no `<option>` with a `selected` attribute:
|
|
|
|
```javascript
|
|
App.ApplicationController = Ember.ObjectController.extend({
|
|
selectedProgrammer: null,
|
|
programmers: ["Yehuda", "Tom"]
|
|
});
|
|
```
|
|
|
|
``` handlebars
|
|
{{view "select"
|
|
content=programmers
|
|
value=selectedProgrammer
|
|
}}
|
|
```
|
|
|
|
Would result in the following HTML:
|
|
|
|
```html
|
|
<select class="ember-select">
|
|
<option value="Yehuda">Yehuda</option>
|
|
<option value="Tom">Tom</option>
|
|
</select>
|
|
```
|
|
|
|
Although `selectedProgrammer` is `null` and no `<option>`
|
|
has a `selected` attribute the rendered HTML will display the
|
|
first item as though it were selected. You can supply a string
|
|
value for the `Ember.Select` to display when there is no selection
|
|
with the `prompt` option:
|
|
|
|
```javascript
|
|
App.ApplicationController = Ember.ObjectController.extend({
|
|
selectedProgrammer: null,
|
|
programmers: [ "Yehuda", "Tom" ]
|
|
});
|
|
```
|
|
|
|
```handlebars
|
|
{{view "select"
|
|
content=programmers
|
|
value=selectedProgrammer
|
|
prompt="Please select a name"
|
|
}}
|
|
```
|
|
|
|
Would result in the following HTML:
|
|
|
|
```html
|
|
<select class="ember-select">
|
|
<option>Please select a name</option>
|
|
<option value="Yehuda">Yehuda</option>
|
|
<option value="Tom">Tom</option>
|
|
</select>
|
|
```
|
|
|
|
@class Select
|
|
@namespace Ember
|
|
@extends Ember.View
|
|
*/
|
|
var Select = View.extend({
|
|
instrumentDisplay: 'Ember.Select',
|
|
|
|
tagName: 'select',
|
|
classNames: ['ember-select'],
|
|
defaultTemplate: defaultTemplate,
|
|
attributeBindings: ['multiple', 'disabled', 'tabindex', 'name', 'required', 'autofocus',
|
|
'form', 'size'],
|
|
|
|
/**
|
|
The `multiple` attribute of the select element. Indicates whether multiple
|
|
options can be selected.
|
|
|
|
@property multiple
|
|
@type Boolean
|
|
@default false
|
|
*/
|
|
multiple: false,
|
|
|
|
/**
|
|
The `disabled` attribute of the select element. Indicates whether
|
|
the element is disabled from interactions.
|
|
|
|
@property disabled
|
|
@type Boolean
|
|
@default false
|
|
*/
|
|
disabled: false,
|
|
|
|
/**
|
|
The `required` attribute of the select element. Indicates whether
|
|
a selected option is required for form validation.
|
|
|
|
@property required
|
|
@type Boolean
|
|
@default false
|
|
@since 1.5.0
|
|
*/
|
|
required: false,
|
|
|
|
/**
|
|
The list of options.
|
|
|
|
If `optionLabelPath` and `optionValuePath` are not overridden, this should
|
|
be a list of strings, which will serve simultaneously as labels and values.
|
|
|
|
Otherwise, this should be a list of objects. For instance:
|
|
|
|
```javascript
|
|
var App = Ember.Application.create();
|
|
var App.MySelect = Ember.Select.extend({
|
|
content: Ember.A([
|
|
{ id: 1, firstName: 'Yehuda' },
|
|
{ id: 2, firstName: 'Tom' }
|
|
]),
|
|
optionLabelPath: 'content.firstName',
|
|
optionValuePath: 'content.id'
|
|
});
|
|
```
|
|
|
|
@property content
|
|
@type Array
|
|
@default null
|
|
*/
|
|
content: null,
|
|
|
|
/**
|
|
When `multiple` is `false`, the element of `content` that is currently
|
|
selected, if any.
|
|
|
|
When `multiple` is `true`, an array of such elements.
|
|
|
|
@property selection
|
|
@type Object or Array
|
|
@default null
|
|
*/
|
|
selection: null,
|
|
|
|
/**
|
|
In single selection mode (when `multiple` is `false`), value can be used to
|
|
get the current selection's value or set the selection by its value.
|
|
|
|
It is not currently supported in multiple selection mode.
|
|
|
|
@property value
|
|
@type String
|
|
@default null
|
|
*/
|
|
value: computed(function(key, value) {
|
|
if (arguments.length === 2) { return value; }
|
|
var valuePath = get(this, 'optionValuePath').replace(/^content\.?/, '');
|
|
return valuePath ? get(this, 'selection.' + valuePath) : get(this, 'selection');
|
|
}).property('selection'),
|
|
|
|
/**
|
|
If given, a top-most dummy option will be rendered to serve as a user
|
|
prompt.
|
|
|
|
@property prompt
|
|
@type String
|
|
@default null
|
|
*/
|
|
prompt: null,
|
|
|
|
/**
|
|
The path of the option labels. See [content](/api/classes/Ember.Select.html#property_content).
|
|
|
|
@property optionLabelPath
|
|
@type String
|
|
@default 'content'
|
|
*/
|
|
optionLabelPath: 'content',
|
|
|
|
/**
|
|
The path of the option values. See [content](/api/classes/Ember.Select.html#property_content).
|
|
|
|
@property optionValuePath
|
|
@type String
|
|
@default 'content'
|
|
*/
|
|
optionValuePath: 'content',
|
|
|
|
/**
|
|
The path of the option group.
|
|
When this property is used, `content` should be sorted by `optionGroupPath`.
|
|
|
|
@property optionGroupPath
|
|
@type String
|
|
@default null
|
|
*/
|
|
optionGroupPath: null,
|
|
|
|
/**
|
|
The view class for optgroup.
|
|
|
|
@property groupView
|
|
@type Ember.View
|
|
@default Ember.SelectOptgroup
|
|
*/
|
|
groupView: SelectOptgroup,
|
|
|
|
groupedContent: computed(function() {
|
|
var groupPath = get(this, 'optionGroupPath');
|
|
var groupedContent = emberA();
|
|
var content = get(this, 'content') || [];
|
|
|
|
forEach(content, function(item) {
|
|
var label = get(item, groupPath);
|
|
|
|
if (get(groupedContent, 'lastObject.label') !== label) {
|
|
groupedContent.pushObject({
|
|
label: label,
|
|
content: emberA()
|
|
});
|
|
}
|
|
|
|
get(groupedContent, 'lastObject.content').push(item);
|
|
});
|
|
|
|
return groupedContent;
|
|
}).property('optionGroupPath', 'content.@each'),
|
|
|
|
/**
|
|
The view class for option.
|
|
|
|
@property optionView
|
|
@type Ember.View
|
|
@default Ember.SelectOption
|
|
*/
|
|
optionView: SelectOption,
|
|
|
|
_change: function() {
|
|
if (get(this, 'multiple')) {
|
|
this._changeMultiple();
|
|
} else {
|
|
this._changeSingle();
|
|
}
|
|
},
|
|
|
|
selectionDidChange: observer('selection.@each', function() {
|
|
var selection = get(this, 'selection');
|
|
if (get(this, 'multiple')) {
|
|
if (!isArray(selection)) {
|
|
set(this, 'selection', emberA([selection]));
|
|
return;
|
|
}
|
|
this._selectionDidChangeMultiple();
|
|
} else {
|
|
this._selectionDidChangeSingle();
|
|
}
|
|
}),
|
|
|
|
valueDidChange: observer('value', function() {
|
|
var content = get(this, 'content');
|
|
var value = get(this, 'value');
|
|
var valuePath = get(this, 'optionValuePath').replace(/^content\.?/, '');
|
|
var selectedValue = (valuePath ? get(this, 'selection.' + valuePath) : get(this, 'selection'));
|
|
var selection;
|
|
|
|
if (value !== selectedValue) {
|
|
selection = content ? content.find(function(obj) {
|
|
return value === (valuePath ? get(obj, valuePath) : obj);
|
|
}) : null;
|
|
|
|
this.set('selection', selection);
|
|
}
|
|
}),
|
|
|
|
_setDefaults: function() {
|
|
var selection = get(this, 'selection');
|
|
var value = get(this, 'value');
|
|
|
|
if (!isNone(selection)) { this.selectionDidChange(); }
|
|
if (!isNone(value)) { this.valueDidChange(); }
|
|
if (isNone(selection)) {
|
|
this._change();
|
|
}
|
|
},
|
|
|
|
_changeSingle: function() {
|
|
var selectedIndex = this.$()[0].selectedIndex;
|
|
var content = get(this, 'content');
|
|
var prompt = get(this, 'prompt');
|
|
|
|
if (!content || !get(content, 'length')) { return; }
|
|
if (prompt && selectedIndex === 0) { set(this, 'selection', null); return; }
|
|
|
|
if (prompt) { selectedIndex -= 1; }
|
|
set(this, 'selection', content.objectAt(selectedIndex));
|
|
},
|
|
|
|
_changeMultiple: function() {
|
|
var options = this.$('option:selected');
|
|
var prompt = get(this, 'prompt');
|
|
var offset = prompt ? 1 : 0;
|
|
var content = get(this, 'content');
|
|
var selection = get(this, 'selection');
|
|
|
|
if (!content) { return; }
|
|
if (options) {
|
|
var selectedIndexes = options.map(function() {
|
|
return this.index - offset;
|
|
}).toArray();
|
|
var newSelection = content.objectsAt(selectedIndexes);
|
|
|
|
if (isArray(selection)) {
|
|
replace(selection, 0, get(selection, 'length'), newSelection);
|
|
} else {
|
|
set(this, 'selection', newSelection);
|
|
}
|
|
}
|
|
},
|
|
|
|
_selectionDidChangeSingle: function() {
|
|
var content = get(this, 'content');
|
|
var selection = get(this, 'selection');
|
|
var self = this;
|
|
if (selection && selection.then) {
|
|
selection.then(function(resolved) {
|
|
// Ensure that we don't overwrite a new selection
|
|
if (self.get('selection') === selection) {
|
|
self._setSelectionIndex(content, resolved);
|
|
}
|
|
});
|
|
} else {
|
|
this._setSelectionIndex(content, selection);
|
|
}
|
|
},
|
|
|
|
_setSelectionIndex: function(content, selection) {
|
|
var el = get(this, 'element');
|
|
if (!el) { return; }
|
|
|
|
var selectionIndex = content ? indexOf(content, selection) : -1;
|
|
var prompt = get(this, 'prompt');
|
|
|
|
if (prompt) { selectionIndex += 1; }
|
|
if (el) { el.selectedIndex = selectionIndex; }
|
|
},
|
|
|
|
_selectionDidChangeMultiple: function() {
|
|
var content = get(this, 'content');
|
|
var selection = get(this, 'selection');
|
|
var selectedIndexes = content ? indexesOf(content, selection) : [-1];
|
|
var prompt = get(this, 'prompt');
|
|
var offset = prompt ? 1 : 0;
|
|
var options = this.$('option');
|
|
var adjusted;
|
|
|
|
if (options) {
|
|
options.each(function() {
|
|
adjusted = this.index > -1 ? this.index - offset : -1;
|
|
this.selected = indexOf(selectedIndexes, adjusted) > -1;
|
|
});
|
|
}
|
|
},
|
|
|
|
init: function() {
|
|
this._super();
|
|
this.on("didInsertElement", this, this._setDefaults);
|
|
this.on("change", this, this._change);
|
|
}
|
|
});
|
|
|
|
__exports__["default"] = Select;
|
|
__exports__.Select = Select;
|
|
__exports__.SelectOption = SelectOption;
|
|
__exports__.SelectOptgroup = SelectOptgroup;
|
|
});
|
|
enifed("ember-views/views/simple_bound_view",
|
|
["ember-metal/error","ember-metal/run_loop","ember-htmlbars/utils/string","ember-metal/utils","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-views
|
|
*/
|
|
|
|
var EmberError = __dependency1__["default"];
|
|
var run = __dependency2__["default"];
|
|
var htmlbarsSafeString = __dependency3__.SafeString;
|
|
var htmlbarsHtmlSafe = __dependency3__.htmlSafe;
|
|
var GUID_KEY = __dependency4__.GUID_KEY;
|
|
var uuid = __dependency4__.uuid;
|
|
|
|
function K() { return this; }
|
|
|
|
var SafeString = htmlbarsSafeString;
|
|
var htmlSafe = htmlbarsHtmlSafe;
|
|
|
|
function SimpleBoundView(lazyValue, isEscaped) {
|
|
this.lazyValue = lazyValue;
|
|
this.isEscaped = isEscaped;
|
|
this[GUID_KEY] = uuid();
|
|
this._lastNormalizedValue = undefined;
|
|
this.state = 'preRender';
|
|
this.updateId = null;
|
|
this._parentView = null;
|
|
this.buffer = null;
|
|
this._morph = null;
|
|
}
|
|
|
|
SimpleBoundView.prototype = {
|
|
isVirtual: true,
|
|
isView: true,
|
|
tagName: '',
|
|
|
|
destroy: function () {
|
|
if (this.updateId) {
|
|
run.cancel(this.updateId);
|
|
this.updateId = null;
|
|
}
|
|
if (this._parentView) {
|
|
this._parentView.removeChild(this);
|
|
}
|
|
this.morph = null;
|
|
this.state = 'destroyed';
|
|
},
|
|
|
|
propertyWillChange: K,
|
|
|
|
propertyDidChange: K,
|
|
|
|
normalizedValue: function() {
|
|
var result = this.lazyValue.value();
|
|
|
|
if (result === null || result === undefined) {
|
|
result = "";
|
|
} else if (!this.isEscaped && !(result instanceof SafeString)) {
|
|
result = htmlSafe(result);
|
|
}
|
|
|
|
return result;
|
|
},
|
|
|
|
render: function(buffer) {
|
|
var value = this.normalizedValue();
|
|
this._lastNormalizedValue = value;
|
|
buffer._element = value;
|
|
},
|
|
|
|
rerender: function() {
|
|
switch(this.state) {
|
|
case 'preRender':
|
|
case 'destroyed':
|
|
break;
|
|
case 'inBuffer':
|
|
throw new EmberError("Something you did tried to replace an {{expression}} before it was inserted into the DOM.");
|
|
case 'hasElement':
|
|
case 'inDOM':
|
|
this.updateId = run.scheduleOnce('render', this, 'update');
|
|
break;
|
|
}
|
|
return this;
|
|
},
|
|
|
|
update: function () {
|
|
this.updateId = null;
|
|
var value = this.normalizedValue();
|
|
// doesn't diff SafeString instances
|
|
if (value !== this._lastNormalizedValue) {
|
|
this._lastNormalizedValue = value;
|
|
this._morph.setContent(value);
|
|
}
|
|
},
|
|
|
|
_transitionTo: function(state) {
|
|
this.state = state;
|
|
}
|
|
};
|
|
|
|
function appendSimpleBoundView(parentView, morph, stream) {
|
|
var view = new SimpleBoundView(stream, morph.escaped);
|
|
view._morph = morph;
|
|
|
|
stream.subscribe(parentView._wrapAsScheduled(function() {
|
|
run.scheduleOnce('render', view, 'rerender');
|
|
}));
|
|
|
|
parentView.appendChild(view);
|
|
}
|
|
|
|
__exports__.appendSimpleBoundView = appendSimpleBoundView;__exports__["default"] = SimpleBoundView;
|
|
});
|
|
enifed("ember-views/views/states",
|
|
["ember-metal/platform","ember-metal/merge","ember-views/views/states/default","ember-views/views/states/pre_render","ember-views/views/states/in_buffer","ember-views/views/states/has_element","ember-views/views/states/in_dom","ember-views/views/states/destroying","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) {
|
|
"use strict";
|
|
var create = __dependency1__.create;
|
|
var merge = __dependency2__["default"];
|
|
var _default = __dependency3__["default"];
|
|
var preRender = __dependency4__["default"];
|
|
var inBuffer = __dependency5__["default"];
|
|
var hasElement = __dependency6__["default"];
|
|
var inDOM = __dependency7__["default"];
|
|
var destroying = __dependency8__["default"];
|
|
|
|
function cloneStates(from) {
|
|
var into = {};
|
|
|
|
into._default = {};
|
|
into.preRender = create(into._default);
|
|
into.destroying = create(into._default);
|
|
into.inBuffer = create(into._default);
|
|
into.hasElement = create(into._default);
|
|
into.inDOM = create(into.hasElement);
|
|
|
|
for (var stateName in from) {
|
|
if (!from.hasOwnProperty(stateName)) { continue; }
|
|
merge(into[stateName], from[stateName]);
|
|
}
|
|
|
|
return into;
|
|
}
|
|
|
|
__exports__.cloneStates = cloneStates;var states = {
|
|
_default: _default,
|
|
preRender: preRender,
|
|
inDOM: inDOM,
|
|
inBuffer: inBuffer,
|
|
hasElement: hasElement,
|
|
destroying: destroying
|
|
};
|
|
__exports__.states = states;
|
|
});
|
|
enifed("ember-views/views/states/default",
|
|
["ember-metal/error","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var EmberError = __dependency1__["default"];
|
|
|
|
function K() { return this; }
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-views
|
|
*/
|
|
__exports__["default"] = {
|
|
// appendChild is only legal while rendering the buffer.
|
|
appendChild: function() {
|
|
throw new EmberError("You can't use appendChild outside of the rendering process");
|
|
},
|
|
|
|
$: function() {
|
|
return undefined;
|
|
},
|
|
|
|
getElement: function() {
|
|
return null;
|
|
},
|
|
|
|
// Handle events from `Ember.EventDispatcher`
|
|
handleEvent: function() {
|
|
return true; // continue event propagation
|
|
},
|
|
|
|
destroyElement: function(view) {
|
|
if (view._renderer)
|
|
view._renderer.remove(view, false);
|
|
return view;
|
|
},
|
|
|
|
rerender: K,
|
|
invokeObserver: K
|
|
};
|
|
});
|
|
enifed("ember-views/views/states/destroying",
|
|
["ember-metal/merge","ember-metal/platform","ember-runtime/system/string","ember-views/views/states/default","ember-metal/error","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) {
|
|
"use strict";
|
|
var merge = __dependency1__["default"];
|
|
var create = __dependency2__.create;
|
|
var fmt = __dependency3__.fmt;
|
|
var _default = __dependency4__["default"];
|
|
var EmberError = __dependency5__["default"];
|
|
/**
|
|
@module ember
|
|
@submodule ember-views
|
|
*/
|
|
|
|
var destroyingError = "You can't call %@ on a view being destroyed";
|
|
|
|
var destroying = create(_default);
|
|
|
|
merge(destroying, {
|
|
appendChild: function() {
|
|
throw new EmberError(fmt(destroyingError, ['appendChild']));
|
|
},
|
|
rerender: function() {
|
|
throw new EmberError(fmt(destroyingError, ['rerender']));
|
|
},
|
|
destroyElement: function() {
|
|
throw new EmberError(fmt(destroyingError, ['destroyElement']));
|
|
}
|
|
});
|
|
|
|
__exports__["default"] = destroying;
|
|
});
|
|
enifed("ember-views/views/states/has_element",
|
|
["ember-views/views/states/default","ember-metal/run_loop","ember-metal/merge","ember-metal/platform","ember-views/system/jquery","ember-metal/error","ember-metal/property_get","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) {
|
|
"use strict";
|
|
var _default = __dependency1__["default"];
|
|
var run = __dependency2__["default"];
|
|
var merge = __dependency3__["default"];
|
|
var create = __dependency4__.create;
|
|
var jQuery = __dependency5__["default"];
|
|
var EmberError = __dependency6__["default"];
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-views
|
|
*/
|
|
|
|
var get = __dependency7__.get;
|
|
|
|
var hasElement = create(_default);
|
|
|
|
merge(hasElement, {
|
|
$: function(view, sel) {
|
|
var elem = view.get('concreteView').element;
|
|
return sel ? jQuery(sel, elem) : jQuery(elem);
|
|
},
|
|
|
|
getElement: function(view) {
|
|
var parent = get(view, 'parentView');
|
|
if (parent) { parent = get(parent, 'element'); }
|
|
if (parent) { return view.findElementInParentElement(parent); }
|
|
return jQuery("#" + get(view, 'elementId'))[0];
|
|
},
|
|
|
|
// once the view has been inserted into the DOM, rerendering is
|
|
// deferred to allow bindings to synchronize.
|
|
rerender: function(view) {
|
|
if (view._root._morph && !view._elementInserted) {
|
|
throw new EmberError("Something you did caused a view to re-render after it rendered but before it was inserted into the DOM.");
|
|
}
|
|
// TODO: should be scheduled with renderer
|
|
run.scheduleOnce('render', function () {
|
|
if (view.isDestroying) return;
|
|
view._renderer.renderTree(view, view._parentView);
|
|
});
|
|
},
|
|
|
|
// once the view is already in the DOM, destroying it removes it
|
|
// from the DOM, nukes its element, and puts it back into the
|
|
// preRender state if inDOM.
|
|
|
|
destroyElement: function(view) {
|
|
view._renderer.remove(view, false);
|
|
return view;
|
|
},
|
|
|
|
// Handle events from `Ember.EventDispatcher`
|
|
handleEvent: function(view, eventName, evt) {
|
|
if (view.has(eventName)) {
|
|
// Handler should be able to re-dispatch events, so we don't
|
|
// preventDefault or stopPropagation.
|
|
return view.trigger(eventName, evt);
|
|
} else {
|
|
return true; // continue event propagation
|
|
}
|
|
},
|
|
|
|
invokeObserver: function(target, observer) {
|
|
observer.call(target);
|
|
}
|
|
});
|
|
|
|
__exports__["default"] = hasElement;
|
|
});
|
|
enifed("ember-views/views/states/in_buffer",
|
|
["ember-views/views/states/default","ember-metal/error","ember-views/system/jquery","ember-metal/platform","ember-metal/merge","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) {
|
|
"use strict";
|
|
var _default = __dependency1__["default"];
|
|
var EmberError = __dependency2__["default"];
|
|
|
|
var jQuery = __dependency3__["default"];
|
|
var create = __dependency4__.create;
|
|
var merge = __dependency5__["default"];
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-views
|
|
*/
|
|
|
|
var inBuffer = create(_default);
|
|
|
|
merge(inBuffer, {
|
|
$: function(view, sel) {
|
|
// if we don't have an element yet, someone calling this.$() is
|
|
// trying to update an element that isn't in the DOM. Instead,
|
|
// rerender the view to allow the render method to reflect the
|
|
// changes.
|
|
view.rerender();
|
|
return jQuery();
|
|
},
|
|
|
|
// when a view is rendered in a buffer, rerendering it simply
|
|
// replaces the existing buffer with a new one
|
|
rerender: function(view) {
|
|
throw new EmberError("Something you did caused a view to re-render after it rendered but before it was inserted into the DOM.");
|
|
},
|
|
|
|
// when a view is rendered in a buffer, appending a child
|
|
// view will render that view and append the resulting
|
|
// buffer into its buffer.
|
|
appendChild: function(view, childView, options) {
|
|
var buffer = view.buffer;
|
|
var _childViews = view._childViews;
|
|
|
|
childView = view.createChildView(childView, options);
|
|
if (!_childViews.length) { _childViews = view._childViews = _childViews.slice(); }
|
|
_childViews.push(childView);
|
|
|
|
if (!childView._morph) {
|
|
buffer.pushChildView(childView);
|
|
}
|
|
|
|
view.propertyDidChange('childViews');
|
|
|
|
return childView;
|
|
},
|
|
|
|
invokeObserver: function(target, observer) {
|
|
observer.call(target);
|
|
}
|
|
});
|
|
|
|
__exports__["default"] = inBuffer;
|
|
});
|
|
enifed("ember-views/views/states/in_dom",
|
|
["ember-metal/core","ember-metal/platform","ember-metal/merge","ember-metal/error","ember-metal/observer","ember-views/views/states/has_element","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) {
|
|
"use strict";
|
|
var Ember = __dependency1__["default"];
|
|
// Ember.assert
|
|
var create = __dependency2__.create;
|
|
var merge = __dependency3__["default"];
|
|
var EmberError = __dependency4__["default"];
|
|
var addBeforeObserver = __dependency5__.addBeforeObserver;
|
|
|
|
var hasElement = __dependency6__["default"];
|
|
/**
|
|
@module ember
|
|
@submodule ember-views
|
|
*/
|
|
|
|
var inDOM = create(hasElement);
|
|
|
|
var View;
|
|
|
|
merge(inDOM, {
|
|
enter: function(view) {
|
|
if (!View) { View = requireModule('ember-views/views/view')["default"]; } // ES6TODO: this sucks. Have to avoid cycles...
|
|
|
|
// Register the view for event handling. This hash is used by
|
|
// Ember.EventDispatcher to dispatch incoming events.
|
|
if (!view.isVirtual) {
|
|
Ember.assert("Attempted to register a view with an id already in use: "+view.elementId, !View.views[view.elementId]);
|
|
View.views[view.elementId] = view;
|
|
}
|
|
|
|
Ember.runInDebug(function() {
|
|
addBeforeObserver(view, 'elementId', function() {
|
|
throw new EmberError("Changing a view's elementId after creation is not allowed");
|
|
});
|
|
});
|
|
},
|
|
|
|
exit: function(view) {
|
|
if (!View) { View = requireModule('ember-views/views/view')["default"]; } // ES6TODO: this sucks. Have to avoid cycles...
|
|
|
|
if (!this.isVirtual) delete View.views[view.elementId];
|
|
}
|
|
});
|
|
|
|
__exports__["default"] = inDOM;
|
|
});
|
|
enifed("ember-views/views/states/pre_render",
|
|
["ember-views/views/states/default","ember-metal/platform","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var _default = __dependency1__["default"];
|
|
var create = __dependency2__.create;
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-views
|
|
*/
|
|
var preRender = create(_default);
|
|
|
|
__exports__["default"] = preRender;
|
|
});
|
|
enifed("ember-views/views/text_area",
|
|
["ember-metal/property_get","ember-views/views/component","ember-views/mixins/text_support","ember-metal/mixin","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
|
|
"use strict";
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-views
|
|
*/
|
|
var get = __dependency1__.get;
|
|
var Component = __dependency2__["default"];
|
|
var TextSupport = __dependency3__["default"];
|
|
var observer = __dependency4__.observer;
|
|
|
|
/**
|
|
The internal class used to create textarea element when the `{{textarea}}`
|
|
helper is used.
|
|
|
|
See [handlebars.helpers.textarea](/api/classes/Ember.Handlebars.helpers.html#method_textarea) for usage details.
|
|
|
|
## Layout and LayoutName properties
|
|
|
|
Because HTML `textarea` elements do not contain inner HTML the `layout` and
|
|
`layoutName` properties will not be applied. See [Ember.View](/api/classes/Ember.View.html)'s
|
|
layout section for more information.
|
|
|
|
@class TextArea
|
|
@namespace Ember
|
|
@extends Ember.Component
|
|
@uses Ember.TextSupport
|
|
*/
|
|
__exports__["default"] = Component.extend(TextSupport, {
|
|
instrumentDisplay: '{{textarea}}',
|
|
|
|
classNames: ['ember-text-area'],
|
|
|
|
tagName: "textarea",
|
|
attributeBindings: [
|
|
'rows',
|
|
'cols',
|
|
'name',
|
|
'selectionEnd',
|
|
'selectionStart',
|
|
'wrap',
|
|
'lang',
|
|
'dir'
|
|
],
|
|
rows: null,
|
|
cols: null,
|
|
|
|
_updateElementValue: observer('value', function() {
|
|
// We do this check so cursor position doesn't get affected in IE
|
|
var value = get(this, 'value');
|
|
var $el = this.$();
|
|
if ($el && value !== $el.val()) {
|
|
$el.val(value);
|
|
}
|
|
}),
|
|
|
|
init: function() {
|
|
this._super();
|
|
this.on("didInsertElement", this, this._updateElementValue);
|
|
}
|
|
});
|
|
});
|
|
enifed("ember-views/views/text_field",
|
|
["ember-views/views/component","ember-views/mixins/text_support","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
/**
|
|
@module ember
|
|
@submodule ember-views
|
|
*/
|
|
var Component = __dependency1__["default"];
|
|
var TextSupport = __dependency2__["default"];
|
|
|
|
/**
|
|
|
|
The internal class used to create text inputs when the `{{input}}`
|
|
helper is used with `type` of `text`.
|
|
|
|
See [Handlebars.helpers.input](/api/classes/Ember.Handlebars.helpers.html#method_input) for usage details.
|
|
|
|
## Layout and LayoutName properties
|
|
|
|
Because HTML `input` elements are self closing `layout` and `layoutName`
|
|
properties will not be applied. See [Ember.View](/api/classes/Ember.View.html)'s
|
|
layout section for more information.
|
|
|
|
@class TextField
|
|
@namespace Ember
|
|
@extends Ember.Component
|
|
@uses Ember.TextSupport
|
|
*/
|
|
__exports__["default"] = Component.extend(TextSupport, {
|
|
instrumentDisplay: '{{input type="text"}}',
|
|
|
|
classNames: ['ember-text-field'],
|
|
tagName: "input",
|
|
attributeBindings: [
|
|
'accept',
|
|
'autocomplete',
|
|
'autosave',
|
|
'dir',
|
|
'formaction',
|
|
'formenctype',
|
|
'formmethod',
|
|
'formnovalidate',
|
|
'formtarget',
|
|
'height',
|
|
'inputmode',
|
|
'lang',
|
|
'list',
|
|
'max',
|
|
'min',
|
|
'multiple',
|
|
'name',
|
|
'pattern',
|
|
'size',
|
|
'step',
|
|
'type',
|
|
'value',
|
|
'width'
|
|
],
|
|
|
|
defaultLayout: null,
|
|
|
|
/**
|
|
The `value` attribute of the input element. As the user inputs text, this
|
|
property is updated live.
|
|
|
|
@property value
|
|
@type String
|
|
@default ""
|
|
*/
|
|
value: "",
|
|
|
|
/**
|
|
The `type` attribute of the input element.
|
|
|
|
@property type
|
|
@type String
|
|
@default "text"
|
|
*/
|
|
type: "text",
|
|
|
|
/**
|
|
The `size` of the text field in characters.
|
|
|
|
@property size
|
|
@type String
|
|
@default null
|
|
*/
|
|
size: null,
|
|
|
|
/**
|
|
The `pattern` attribute of input element.
|
|
|
|
@property pattern
|
|
@type String
|
|
@default null
|
|
*/
|
|
pattern: null,
|
|
|
|
/**
|
|
The `min` attribute of input element used with `type="number"` or `type="range"`.
|
|
|
|
@property min
|
|
@type String
|
|
@default null
|
|
@since 1.4.0
|
|
*/
|
|
min: null,
|
|
|
|
/**
|
|
The `max` attribute of input element used with `type="number"` or `type="range"`.
|
|
|
|
@property max
|
|
@type String
|
|
@default null
|
|
@since 1.4.0
|
|
*/
|
|
max: null
|
|
});
|
|
});
|
|
enifed("ember-views/views/view",
|
|
["ember-metal/core","ember-metal/platform","ember-runtime/mixins/evented","ember-runtime/system/object","ember-metal/error","ember-metal/property_get","ember-metal/property_set","ember-metal/set_properties","ember-metal/run_loop","ember-metal/observer","ember-metal/properties","ember-metal/utils","ember-metal/computed","ember-metal/mixin","ember-views/streams/key_stream","ember-metal/streams/stream_binding","ember-views/streams/context_stream","ember-metal/is_none","ember-metal/deprecate_property","ember-runtime/system/native_array","ember-views/streams/class_name_binding","ember-metal/enumerable_utils","ember-metal/property_events","ember-views/system/jquery","ember-views/system/ext","ember-views/views/core_view","ember-metal/streams/utils","ember-views/system/sanitize_attribute_value","morph/dom-helper/prop","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __dependency16__, __dependency17__, __dependency18__, __dependency19__, __dependency20__, __dependency21__, __dependency22__, __dependency23__, __dependency24__, __dependency25__, __dependency26__, __dependency27__, __dependency28__, __dependency29__, __exports__) {
|
|
"use strict";
|
|
// Ember.assert, Ember.deprecate, Ember.warn, Ember.TEMPLATES,
|
|
// jQuery, Ember.lookup,
|
|
// Ember.ContainerView circular dependency
|
|
// Ember.ENV
|
|
var Ember = __dependency1__["default"];
|
|
var create = __dependency2__.create;
|
|
|
|
var Evented = __dependency3__["default"];
|
|
var EmberObject = __dependency4__["default"];
|
|
var EmberError = __dependency5__["default"];
|
|
var get = __dependency6__.get;
|
|
var set = __dependency7__.set;
|
|
var setProperties = __dependency8__["default"];
|
|
var run = __dependency9__["default"];
|
|
var addObserver = __dependency10__.addObserver;
|
|
var removeObserver = __dependency10__.removeObserver;
|
|
var defineProperty = __dependency11__.defineProperty;
|
|
var guidFor = __dependency12__.guidFor;
|
|
var computed = __dependency13__.computed;
|
|
var observer = __dependency14__.observer;
|
|
var KeyStream = __dependency15__["default"];
|
|
var StreamBinding = __dependency16__["default"];
|
|
var ContextStream = __dependency17__["default"];
|
|
|
|
var typeOf = __dependency12__.typeOf;
|
|
var isNone = __dependency18__["default"];
|
|
var Mixin = __dependency14__.Mixin;
|
|
var deprecateProperty = __dependency19__.deprecateProperty;
|
|
var emberA = __dependency20__.A;
|
|
|
|
var streamifyClassNameBinding = __dependency21__.streamifyClassNameBinding;
|
|
|
|
// ES6TODO: functions on EnumerableUtils should get their own export
|
|
var forEach = __dependency22__.forEach;
|
|
var addObject = __dependency22__.addObject;
|
|
var removeObject = __dependency22__.removeObject;
|
|
|
|
var beforeObserver = __dependency14__.beforeObserver;
|
|
|
|
var propertyWillChange = __dependency23__.propertyWillChange;
|
|
var propertyDidChange = __dependency23__.propertyDidChange;
|
|
|
|
var jQuery = __dependency24__["default"];
|
|
// for the side effect of extending Ember.run.queues
|
|
|
|
var CoreView = __dependency26__["default"];
|
|
var subscribe = __dependency27__.subscribe;
|
|
var read = __dependency27__.read;
|
|
var isStream = __dependency27__.isStream;
|
|
var sanitizeAttributeValue = __dependency28__["default"];
|
|
var normalizeProperty = __dependency29__.normalizeProperty;
|
|
|
|
function K() { return this; }
|
|
|
|
// Circular dep
|
|
var _htmlbarsDefaultEnv;
|
|
function buildHTMLBarsDefaultEnv(){
|
|
if (!_htmlbarsDefaultEnv) {
|
|
_htmlbarsDefaultEnv = eriuqer('ember-htmlbars').defaultEnv;
|
|
}
|
|
return create(_htmlbarsDefaultEnv);
|
|
}
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-views
|
|
*/
|
|
var childViewsProperty = computed(function() {
|
|
var childViews = this._childViews;
|
|
var ret = emberA();
|
|
|
|
forEach(childViews, function(view) {
|
|
var currentChildViews;
|
|
if (view.isVirtual) {
|
|
if (currentChildViews = get(view, 'childViews')) {
|
|
ret.pushObjects(currentChildViews);
|
|
}
|
|
} else {
|
|
ret.push(view);
|
|
}
|
|
});
|
|
|
|
ret.replace = function (idx, removedCount, addedViews) {
|
|
throw new EmberError("childViews is immutable");
|
|
};
|
|
|
|
return ret;
|
|
});
|
|
|
|
Ember.warn("The VIEW_PRESERVES_CONTEXT flag has been removed and the functionality can no longer be disabled.", Ember.ENV.VIEW_PRESERVES_CONTEXT !== false);
|
|
|
|
/**
|
|
Global hash of shared templates. This will automatically be populated
|
|
by the build tools so that you can store your Handlebars templates in
|
|
separate files that get loaded into JavaScript at buildtime.
|
|
|
|
@property TEMPLATES
|
|
@for Ember
|
|
@type Hash
|
|
*/
|
|
Ember.TEMPLATES = {};
|
|
|
|
var EMPTY_ARRAY = [];
|
|
|
|
/**
|
|
`Ember.View` is the class in Ember responsible for encapsulating templates of
|
|
HTML content, combining templates with data to render as sections of a page's
|
|
DOM, and registering and responding to user-initiated events.
|
|
|
|
## HTML Tag
|
|
|
|
The default HTML tag name used for a view's DOM representation is `div`. This
|
|
can be customized by setting the `tagName` property. The following view
|
|
class:
|
|
|
|
```javascript
|
|
ParagraphView = Ember.View.extend({
|
|
tagName: 'em'
|
|
});
|
|
```
|
|
|
|
Would result in instances with the following HTML:
|
|
|
|
```html
|
|
<em id="ember1" class="ember-view"></em>
|
|
```
|
|
|
|
## HTML `class` Attribute
|
|
|
|
The HTML `class` attribute of a view's tag can be set by providing a
|
|
`classNames` property that is set to an array of strings:
|
|
|
|
```javascript
|
|
MyView = Ember.View.extend({
|
|
classNames: ['my-class', 'my-other-class']
|
|
});
|
|
```
|
|
|
|
Will result in view instances with an HTML representation of:
|
|
|
|
```html
|
|
<div id="ember1" class="ember-view my-class my-other-class"></div>
|
|
```
|
|
|
|
`class` attribute values can also be set by providing a `classNameBindings`
|
|
property set to an array of properties names for the view. The return value
|
|
of these properties will be added as part of the value for the view's `class`
|
|
attribute. These properties can be computed properties:
|
|
|
|
```javascript
|
|
MyView = Ember.View.extend({
|
|
classNameBindings: ['propertyA', 'propertyB'],
|
|
propertyA: 'from-a',
|
|
propertyB: function() {
|
|
if (someLogic) { return 'from-b'; }
|
|
}.property()
|
|
});
|
|
```
|
|
|
|
Will result in view instances with an HTML representation of:
|
|
|
|
```html
|
|
<div id="ember1" class="ember-view from-a from-b"></div>
|
|
```
|
|
|
|
If the value of a class name binding returns a boolean the property name
|
|
itself will be used as the class name if the property is true. The class name
|
|
will not be added if the value is `false` or `undefined`.
|
|
|
|
```javascript
|
|
MyView = Ember.View.extend({
|
|
classNameBindings: ['hovered'],
|
|
hovered: true
|
|
});
|
|
```
|
|
|
|
Will result in view instances with an HTML representation of:
|
|
|
|
```html
|
|
<div id="ember1" class="ember-view hovered"></div>
|
|
```
|
|
|
|
When using boolean class name bindings you can supply a string value other
|
|
than the property name for use as the `class` HTML attribute by appending the
|
|
preferred value after a ":" character when defining the binding:
|
|
|
|
```javascript
|
|
MyView = Ember.View.extend({
|
|
classNameBindings: ['awesome:so-very-cool'],
|
|
awesome: true
|
|
});
|
|
```
|
|
|
|
Will result in view instances with an HTML representation of:
|
|
|
|
```html
|
|
<div id="ember1" class="ember-view so-very-cool"></div>
|
|
```
|
|
|
|
Boolean value class name bindings whose property names are in a
|
|
camelCase-style format will be converted to a dasherized format:
|
|
|
|
```javascript
|
|
MyView = Ember.View.extend({
|
|
classNameBindings: ['isUrgent'],
|
|
isUrgent: true
|
|
});
|
|
```
|
|
|
|
Will result in view instances with an HTML representation of:
|
|
|
|
```html
|
|
<div id="ember1" class="ember-view is-urgent"></div>
|
|
```
|
|
|
|
Class name bindings can also refer to object values that are found by
|
|
traversing a path relative to the view itself:
|
|
|
|
```javascript
|
|
MyView = Ember.View.extend({
|
|
classNameBindings: ['messages.empty']
|
|
messages: Ember.Object.create({
|
|
empty: true
|
|
})
|
|
});
|
|
```
|
|
|
|
Will result in view instances with an HTML representation of:
|
|
|
|
```html
|
|
<div id="ember1" class="ember-view empty"></div>
|
|
```
|
|
|
|
If you want to add a class name for a property which evaluates to true and
|
|
and a different class name if it evaluates to false, you can pass a binding
|
|
like this:
|
|
|
|
```javascript
|
|
// Applies 'enabled' class when isEnabled is true and 'disabled' when isEnabled is false
|
|
Ember.View.extend({
|
|
classNameBindings: ['isEnabled:enabled:disabled']
|
|
isEnabled: true
|
|
});
|
|
```
|
|
|
|
Will result in view instances with an HTML representation of:
|
|
|
|
```html
|
|
<div id="ember1" class="ember-view enabled"></div>
|
|
```
|
|
|
|
When isEnabled is `false`, the resulting HTML reprensentation looks like
|
|
this:
|
|
|
|
```html
|
|
<div id="ember1" class="ember-view disabled"></div>
|
|
```
|
|
|
|
This syntax offers the convenience to add a class if a property is `false`:
|
|
|
|
```javascript
|
|
// Applies no class when isEnabled is true and class 'disabled' when isEnabled is false
|
|
Ember.View.extend({
|
|
classNameBindings: ['isEnabled::disabled']
|
|
isEnabled: true
|
|
});
|
|
```
|
|
|
|
Will result in view instances with an HTML representation of:
|
|
|
|
```html
|
|
<div id="ember1" class="ember-view"></div>
|
|
```
|
|
|
|
When the `isEnabled` property on the view is set to `false`, it will result
|
|
in view instances with an HTML representation of:
|
|
|
|
```html
|
|
<div id="ember1" class="ember-view disabled"></div>
|
|
```
|
|
|
|
Updates to the the value of a class name binding will result in automatic
|
|
update of the HTML `class` attribute in the view's rendered HTML
|
|
representation. If the value becomes `false` or `undefined` the class name
|
|
will be removed.
|
|
|
|
Both `classNames` and `classNameBindings` are concatenated properties. See
|
|
[Ember.Object](/api/classes/Ember.Object.html) documentation for more
|
|
information about concatenated properties.
|
|
|
|
## HTML Attributes
|
|
|
|
The HTML attribute section of a view's tag can be set by providing an
|
|
`attributeBindings` property set to an array of property names on the view.
|
|
The return value of these properties will be used as the value of the view's
|
|
HTML associated attribute:
|
|
|
|
```javascript
|
|
AnchorView = Ember.View.extend({
|
|
tagName: 'a',
|
|
attributeBindings: ['href'],
|
|
href: 'http://google.com'
|
|
});
|
|
```
|
|
|
|
Will result in view instances with an HTML representation of:
|
|
|
|
```html
|
|
<a id="ember1" class="ember-view" href="http://google.com"></a>
|
|
```
|
|
|
|
One property can be mapped on to another by placing a ":" between
|
|
the source property and the destination property:
|
|
|
|
```javascript
|
|
AnchorView = Ember.View.extend({
|
|
tagName: 'a',
|
|
attributeBindings: ['url:href'],
|
|
url: 'http://google.com'
|
|
});
|
|
```
|
|
|
|
Will result in view instances with an HTML representation of:
|
|
|
|
```html
|
|
<a id="ember1" class="ember-view" href="http://google.com"></a>
|
|
```
|
|
|
|
If the return value of an `attributeBindings` monitored property is a boolean
|
|
the property will follow HTML's pattern of repeating the attribute's name as
|
|
its value:
|
|
|
|
```javascript
|
|
MyTextInput = Ember.View.extend({
|
|
tagName: 'input',
|
|
attributeBindings: ['disabled'],
|
|
disabled: true
|
|
});
|
|
```
|
|
|
|
Will result in view instances with an HTML representation of:
|
|
|
|
```html
|
|
<input id="ember1" class="ember-view" disabled="disabled" />
|
|
```
|
|
|
|
`attributeBindings` can refer to computed properties:
|
|
|
|
```javascript
|
|
MyTextInput = Ember.View.extend({
|
|
tagName: 'input',
|
|
attributeBindings: ['disabled'],
|
|
disabled: function() {
|
|
if (someLogic) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}.property()
|
|
});
|
|
```
|
|
|
|
Updates to the the property of an attribute binding will result in automatic
|
|
update of the HTML attribute in the view's rendered HTML representation.
|
|
|
|
`attributeBindings` is a concatenated property. See [Ember.Object](/api/classes/Ember.Object.html)
|
|
documentation for more information about concatenated properties.
|
|
|
|
## Templates
|
|
|
|
The HTML contents of a view's rendered representation are determined by its
|
|
template. Templates can be any function that accepts an optional context
|
|
parameter and returns a string of HTML that will be inserted within the
|
|
view's tag. Most typically in Ember this function will be a compiled
|
|
`Ember.Handlebars` template.
|
|
|
|
```javascript
|
|
AView = Ember.View.extend({
|
|
template: Ember.Handlebars.compile('I am the template')
|
|
});
|
|
```
|
|
|
|
Will result in view instances with an HTML representation of:
|
|
|
|
```html
|
|
<div id="ember1" class="ember-view">I am the template</div>
|
|
```
|
|
|
|
Within an Ember application is more common to define a Handlebars templates as
|
|
part of a page:
|
|
|
|
```html
|
|
<script type='text/x-handlebars' data-template-name='some-template'>
|
|
Hello
|
|
</script>
|
|
```
|
|
|
|
And associate it by name using a view's `templateName` property:
|
|
|
|
```javascript
|
|
AView = Ember.View.extend({
|
|
templateName: 'some-template'
|
|
});
|
|
```
|
|
|
|
If you have nested resources, your Handlebars template will look like this:
|
|
|
|
```html
|
|
<script type='text/x-handlebars' data-template-name='posts/new'>
|
|
<h1>New Post</h1>
|
|
</script>
|
|
```
|
|
|
|
And `templateName` property:
|
|
|
|
```javascript
|
|
AView = Ember.View.extend({
|
|
templateName: 'posts/new'
|
|
});
|
|
```
|
|
|
|
Using a value for `templateName` that does not have a Handlebars 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}}` Handlebars 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.Handlebars.compile('I was the default'),
|
|
template: null,
|
|
templateName: null
|
|
});
|
|
```
|
|
|
|
Will result in instances with an HTML representation of:
|
|
|
|
```html
|
|
<div id="ember1" class="ember-view">I was the default</div>
|
|
```
|
|
|
|
If a `template` or `templateName` is provided it will take precedence over
|
|
`defaultTemplate`:
|
|
|
|
```javascript
|
|
AView = Ember.View.extend({
|
|
defaultTemplate: Ember.Handlebars.compile('I was the default')
|
|
});
|
|
|
|
aView = AView.create({
|
|
template: Ember.Handlebars.compile('I was the template, not default')
|
|
});
|
|
```
|
|
|
|
Will result in the following HTML representation when rendered:
|
|
|
|
```html
|
|
<div id="ember1" class="ember-view">I was the template, not default</div>
|
|
```
|
|
|
|
## View Context
|
|
|
|
The default context of the compiled template is the view's controller:
|
|
|
|
```javascript
|
|
AView = Ember.View.extend({
|
|
template: Ember.Handlebars.compile('Hello {{excitedGreeting}}')
|
|
});
|
|
|
|
aController = Ember.Object.create({
|
|
firstName: 'Barry',
|
|
excitedGreeting: function() {
|
|
return this.get("content.firstName") + "!!!"
|
|
}.property()
|
|
});
|
|
|
|
aView = AView.create({
|
|
controller: aController
|
|
});
|
|
```
|
|
|
|
Will result in an HTML representation of:
|
|
|
|
```html
|
|
<div id="ember1" class="ember-view">Hello Barry!!!</div>
|
|
```
|
|
|
|
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. `<input />`)
|
|
cannot have a layout and this property will be ignored.
|
|
|
|
Most typically in Ember a layout will be a compiled `Ember.Handlebars`
|
|
template.
|
|
|
|
A view's layout can be set directly with the `layout` property or reference
|
|
an existing Handlebars template by name with the `layoutName` property.
|
|
|
|
A template used as a layout must contain a single use of the Handlebars
|
|
`{{yield}}` helper. The HTML contents of a view's rendered `template` will be
|
|
inserted at this location:
|
|
|
|
```javascript
|
|
AViewWithLayout = Ember.View.extend({
|
|
layout: Ember.Handlebars.compile("<div class='my-decorative-class'>{{yield}}</div>"),
|
|
template: Ember.Handlebars.compile("I got wrapped")
|
|
});
|
|
```
|
|
|
|
Will result in view instances with an HTML representation of:
|
|
|
|
```html
|
|
<div id="ember1" class="ember-view">
|
|
<div class="my-decorative-class">
|
|
I got wrapped
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
See [Ember.Handlebars.helpers.yield](/api/classes/Ember.Handlebars.helpers.html#method_yield)
|
|
for more information.
|
|
|
|
## Responding to Browser Events
|
|
|
|
Views can respond to user-initiated events in one of three ways: method
|
|
implementation, through an event manager, and through `{{action}}` helper use
|
|
in their template or layout.
|
|
|
|
### Method Implementation
|
|
|
|
Views can respond to user-initiated events by implementing a method that
|
|
matches the event name. A `jQuery.Event` object will be passed as the
|
|
argument to this method.
|
|
|
|
```javascript
|
|
AView = Ember.View.extend({
|
|
click: function(event) {
|
|
// will be called when when an instance's
|
|
// rendered element is clicked
|
|
}
|
|
});
|
|
```
|
|
|
|
### Event Managers
|
|
|
|
Views can define an object as their `eventManager` property. This object can
|
|
then implement methods that match the desired event names. Matching events
|
|
that occur on the view's rendered HTML or the rendered HTML of any of its DOM
|
|
descendants will trigger this method. A `jQuery.Event` object will be passed
|
|
as the first argument to the method and an `Ember.View` object as the
|
|
second. The `Ember.View` will be the view whose rendered HTML was interacted
|
|
with. This may be the view with the `eventManager` property or one of its
|
|
descendent views.
|
|
|
|
```javascript
|
|
AView = Ember.View.extend({
|
|
eventManager: Ember.Object.create({
|
|
doubleClick: function(event, view) {
|
|
// will be called when when an instance's
|
|
// rendered element or any rendering
|
|
// of this views's descendent
|
|
// 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 descendent. 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 descendent.
|
|
|
|
```javascript
|
|
var App = Ember.Application.create();
|
|
App.OuterView = Ember.View.extend({
|
|
template: Ember.Handlebars.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.
|
|
}
|
|
});
|
|
```
|
|
|
|
### Handlebars `{{action}}` Helper
|
|
|
|
See [Handlebars.helpers.action](/api/classes/Ember.Handlebars.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`
|
|
|
|
## Handlebars `{{view}}` Helper
|
|
|
|
Other `Ember.View` instances can be included as part of a view's template by
|
|
using the `{{view}}` Handlebars helper. See [Ember.Handlebars.helpers.view](/api/classes/Ember.Handlebars.helpers.html#method_view)
|
|
for additional information.
|
|
|
|
@class View
|
|
@namespace Ember
|
|
@extends Ember.CoreView
|
|
*/
|
|
var View = CoreView.extend({
|
|
|
|
concatenatedProperties: ['classNames', 'classNameBindings', 'attributeBindings'],
|
|
|
|
/**
|
|
@property isView
|
|
@type Boolean
|
|
@default true
|
|
@static
|
|
*/
|
|
isView: true,
|
|
|
|
// ..........................................................
|
|
// TEMPLATE SUPPORT
|
|
//
|
|
|
|
/**
|
|
The name of the template to lookup if no template is provided.
|
|
|
|
By default `Ember.View` will lookup a template with this name in
|
|
`Ember.TEMPLATES` (a shared global object).
|
|
|
|
@property templateName
|
|
@type String
|
|
@default null
|
|
*/
|
|
templateName: null,
|
|
|
|
/**
|
|
The name of the layout to lookup if no layout is provided.
|
|
|
|
By default `Ember.View` will lookup a template with this name in
|
|
`Ember.TEMPLATES` (a shared global object).
|
|
|
|
@property layoutName
|
|
@type String
|
|
@default null
|
|
*/
|
|
layoutName: null,
|
|
|
|
/**
|
|
Used to identify this view during debugging
|
|
|
|
@property instrumentDisplay
|
|
@type String
|
|
*/
|
|
instrumentDisplay: computed(function() {
|
|
if (this.helperName) {
|
|
return '{{' + this.helperName + '}}';
|
|
}
|
|
}),
|
|
|
|
/**
|
|
The template used to render the view. This should be a function that
|
|
accepts an optional context parameter and returns a string of HTML that
|
|
will be inserted into the DOM relative to its parent view.
|
|
|
|
In general, you should set the `templateName` property instead of setting
|
|
the template yourself.
|
|
|
|
@property template
|
|
@type Function
|
|
*/
|
|
template: computed('templateName', function(key, value) {
|
|
if (value !== undefined) { return value; }
|
|
|
|
var templateName = get(this, 'templateName');
|
|
var template = this.templateForName(templateName, 'template');
|
|
|
|
Ember.assert("You specified the templateName " + templateName + " for " + this + ", but it did not exist.", !templateName || !!template);
|
|
|
|
return template || get(this, 'defaultTemplate');
|
|
}),
|
|
|
|
_controller: null,
|
|
|
|
/**
|
|
The controller managing this view. If this property is set, it will be
|
|
made available for use by the template.
|
|
|
|
@property controller
|
|
@type Object
|
|
*/
|
|
controller: computed(function(key, value) {
|
|
if (arguments.length === 2) {
|
|
this._controller = value;
|
|
return value;
|
|
}
|
|
|
|
if (this._controller) {
|
|
return this._controller;
|
|
}
|
|
|
|
var parentView = get(this, '_parentView');
|
|
return parentView ? get(parentView, 'controller') : null;
|
|
}),
|
|
|
|
/**
|
|
A view may contain a layout. A layout is a regular template but
|
|
supersedes the `template` property during rendering. It is the
|
|
responsibility of the layout template to retrieve the `template`
|
|
property from the view (or alternatively, call `Handlebars.helpers.yield`,
|
|
`{{yield}}`) to render it in the correct location.
|
|
|
|
This is useful for a view that has a shared wrapper, but which delegates
|
|
the rendering of the contents of the wrapper to the `template` property
|
|
on a subclass.
|
|
|
|
@property layout
|
|
@type Function
|
|
*/
|
|
layout: computed(function(key) {
|
|
var layoutName = get(this, 'layoutName');
|
|
var layout = this.templateForName(layoutName, 'layout');
|
|
|
|
Ember.assert("You specified the layoutName " + layoutName + " for " + this + ", but it did not exist.", !layoutName || !!layout);
|
|
|
|
return layout || get(this, 'defaultLayout');
|
|
}).property('layoutName'),
|
|
|
|
_yield: function(context, options, morph) {
|
|
var template = get(this, 'template');
|
|
|
|
if (template) {
|
|
var useHTMLBars = false;
|
|
|
|
useHTMLBars = template.isHTMLBars;
|
|
|
|
|
|
if (useHTMLBars) {
|
|
return template.render(this, options, morph.contextualElement);
|
|
} else {
|
|
return template(context, options);
|
|
}
|
|
}
|
|
},
|
|
|
|
_blockArguments: EMPTY_ARRAY,
|
|
|
|
templateForName: function(name, type) {
|
|
if (!name) { return; }
|
|
Ember.assert("templateNames are not allowed to contain periods: "+name, name.indexOf('.') === -1);
|
|
|
|
if (!this.container) {
|
|
throw new EmberError('Container was not found when looking up a views template. ' +
|
|
'This is most likely due to manually instantiating an Ember.View. ' +
|
|
'See: http://git.io/EKPpnA');
|
|
}
|
|
|
|
return this.container.lookup('template:' + name);
|
|
},
|
|
|
|
/**
|
|
The object from which templates should access properties.
|
|
|
|
This object will be passed to the template function each time the render
|
|
method is called, but it is up to the individual function to decide what
|
|
to do with it.
|
|
|
|
By default, this will be the view's controller.
|
|
|
|
@property context
|
|
@type Object
|
|
*/
|
|
context: computed(function(key, value) {
|
|
if (arguments.length === 2) {
|
|
set(this, '_context', value);
|
|
return value;
|
|
} else {
|
|
return get(this, '_context');
|
|
}
|
|
})["volatile"](),
|
|
|
|
/**
|
|
Private copy of the view's template context. This can be set directly
|
|
by Handlebars without triggering the observer that causes the view
|
|
to be re-rendered.
|
|
|
|
The context of a view is looked up as follows:
|
|
|
|
1. Supplied context (usually by Handlebars)
|
|
2. Specified controller
|
|
3. `parentView`'s context (for a child of a ContainerView)
|
|
|
|
The code in Handlebars that overrides the `_context` property first
|
|
checks to see whether the view has a specified controller. This is
|
|
something of a hack and should be revisited.
|
|
|
|
@property _context
|
|
@private
|
|
*/
|
|
_context: computed(function(key, value) {
|
|
if (arguments.length === 2) {
|
|
return value;
|
|
}
|
|
|
|
var parentView, controller;
|
|
|
|
if (controller = get(this, 'controller')) {
|
|
return controller;
|
|
}
|
|
|
|
parentView = this._parentView;
|
|
if (parentView) {
|
|
return get(parentView, '_context');
|
|
}
|
|
|
|
return null;
|
|
}),
|
|
|
|
/**
|
|
If a value that affects template rendering changes, the view should be
|
|
re-rendered to reflect the new value.
|
|
|
|
@method _contextDidChange
|
|
@private
|
|
*/
|
|
_contextDidChange: observer('context', function() {
|
|
this.rerender();
|
|
}),
|
|
|
|
/**
|
|
If `false`, the view will appear hidden in DOM.
|
|
|
|
@property isVisible
|
|
@type Boolean
|
|
@default null
|
|
*/
|
|
isVisible: true,
|
|
|
|
/**
|
|
Array of child views. You should never edit this array directly.
|
|
Instead, use `appendChild` and `removeFromParent`.
|
|
|
|
@property childViews
|
|
@type Array
|
|
@default []
|
|
@private
|
|
*/
|
|
childViews: childViewsProperty,
|
|
|
|
_childViews: EMPTY_ARRAY,
|
|
|
|
// When it's a virtual view, we need to notify the parent that their
|
|
// childViews will change.
|
|
_childViewsWillChange: beforeObserver('childViews', function() {
|
|
if (this.isVirtual) {
|
|
var parentView = get(this, 'parentView');
|
|
if (parentView) { propertyWillChange(parentView, 'childViews'); }
|
|
}
|
|
}),
|
|
|
|
// When it's a virtual view, we need to notify the parent that their
|
|
// childViews did change.
|
|
_childViewsDidChange: observer('childViews', function() {
|
|
if (this.isVirtual) {
|
|
var parentView = get(this, 'parentView');
|
|
if (parentView) { propertyDidChange(parentView, 'childViews'); }
|
|
}
|
|
}),
|
|
|
|
/**
|
|
Return the nearest ancestor that is an instance of the provided
|
|
class.
|
|
|
|
@method nearestInstanceOf
|
|
@param {Class} klass Subclass of Ember.View (or Ember.View itself)
|
|
@return Ember.View
|
|
@deprecated
|
|
*/
|
|
nearestInstanceOf: function(klass) {
|
|
Ember.deprecate("nearestInstanceOf is deprecated and will be removed from future releases. Use nearestOfType.");
|
|
var view = get(this, 'parentView');
|
|
|
|
while (view) {
|
|
if (view instanceof klass) { return view; }
|
|
view = get(view, 'parentView');
|
|
}
|
|
},
|
|
|
|
/**
|
|
Return the nearest ancestor that is an instance of the provided
|
|
class or mixin.
|
|
|
|
@method nearestOfType
|
|
@param {Class,Mixin} klass Subclass of Ember.View (or Ember.View itself),
|
|
or an instance of Ember.Mixin.
|
|
@return Ember.View
|
|
*/
|
|
nearestOfType: function(klass) {
|
|
var view = get(this, 'parentView');
|
|
var isOfType = klass instanceof Mixin ?
|
|
function(view) { return klass.detect(view); } :
|
|
function(view) { return klass.detect(view.constructor); };
|
|
|
|
while (view) {
|
|
if (isOfType(view)) { return view; }
|
|
view = get(view, 'parentView');
|
|
}
|
|
},
|
|
|
|
/**
|
|
Return the nearest ancestor that has a given property.
|
|
|
|
@method nearestWithProperty
|
|
@param {String} property A property name
|
|
@return Ember.View
|
|
*/
|
|
nearestWithProperty: function(property) {
|
|
var view = get(this, 'parentView');
|
|
|
|
while (view) {
|
|
if (property in view) { return view; }
|
|
view = get(view, 'parentView');
|
|
}
|
|
},
|
|
|
|
/**
|
|
Return the nearest ancestor whose parent is an instance of
|
|
`klass`.
|
|
|
|
@method nearestChildOf
|
|
@param {Class} klass Subclass of Ember.View (or Ember.View itself)
|
|
@return Ember.View
|
|
*/
|
|
nearestChildOf: function(klass) {
|
|
var view = get(this, 'parentView');
|
|
|
|
while (view) {
|
|
if (get(view, 'parentView') instanceof klass) { return view; }
|
|
view = get(view, 'parentView');
|
|
}
|
|
},
|
|
|
|
/**
|
|
When the parent view changes, recursively invalidate `controller`
|
|
|
|
@method _parentViewDidChange
|
|
@private
|
|
*/
|
|
_parentViewDidChange: observer('_parentView', function() {
|
|
if (this.isDestroying) { return; }
|
|
|
|
this._setupKeywords();
|
|
this.trigger('parentViewDidChange');
|
|
|
|
if (get(this, 'parentView.controller') && !get(this, 'controller')) {
|
|
this.notifyPropertyChange('controller');
|
|
}
|
|
}),
|
|
|
|
_controllerDidChange: observer('controller', function() {
|
|
if (this.isDestroying) { return; }
|
|
|
|
this.rerender();
|
|
|
|
this.forEachChildView(function(view) {
|
|
view.propertyDidChange('controller');
|
|
});
|
|
}),
|
|
|
|
_setupKeywords: function() {
|
|
var keywords = this._keywords;
|
|
var contextView = this._contextView || this._parentView;
|
|
|
|
if (contextView) {
|
|
var parentKeywords = contextView._keywords;
|
|
|
|
keywords.view = this.isVirtual ? parentKeywords.view : this;
|
|
|
|
for (var name in parentKeywords) {
|
|
if (keywords[name]) continue;
|
|
keywords[name] = parentKeywords[name];
|
|
}
|
|
} else {
|
|
keywords.view = this.isVirtual ? null : this;
|
|
}
|
|
},
|
|
|
|
/**
|
|
Called on your view when it should push strings of HTML into a
|
|
`Ember.RenderBuffer`. Most users will want to override the `template`
|
|
or `templateName` properties instead of this method.
|
|
|
|
By default, `Ember.View` will look for a function in the `template`
|
|
property and invoke it with the value of `context`. The value of
|
|
`context` will be the view's controller unless you override it.
|
|
|
|
@method render
|
|
@param {Ember.RenderBuffer} buffer The render buffer
|
|
*/
|
|
render: function(buffer) {
|
|
// If this view has a layout, it is the responsibility of the
|
|
// the layout to render the view's template. Otherwise, render the template
|
|
// directly.
|
|
var template = get(this, 'layout') || get(this, 'template');
|
|
|
|
if (template) {
|
|
var context = get(this, 'context');
|
|
var output;
|
|
|
|
var data = {
|
|
view: this,
|
|
buffer: buffer,
|
|
isRenderData: true
|
|
};
|
|
|
|
// Invoke the template with the provided template context, which
|
|
// is the view's controller by default. A hash of data is also passed that provides
|
|
// the template with access to the view and render buffer.
|
|
|
|
// The template should write directly to the render buffer instead
|
|
// of returning a string.
|
|
var options = { data: data };
|
|
var useHTMLBars = false;
|
|
|
|
|
|
useHTMLBars = template.isHTMLBars;
|
|
|
|
|
|
if (useHTMLBars) {
|
|
Ember.assert('template must be an object. Did you mean to call Ember.Handlebars.compile("...") or specify templateName instead?', typeof template === 'object');
|
|
var env = Ember.merge(buildHTMLBarsDefaultEnv(), options);
|
|
output = template.render(this, env, buffer.innerContextualElement(), this._blockArguments);
|
|
} else {
|
|
Ember.assert('template must be a function. Did you mean to call Ember.Handlebars.compile("...") or specify templateName instead?', typeof template === 'function');
|
|
output = template(context, options);
|
|
}
|
|
|
|
// If the template returned a string instead of writing to the buffer,
|
|
// push the string onto the buffer.
|
|
if (output !== undefined) { buffer.push(output); }
|
|
}
|
|
},
|
|
|
|
/**
|
|
Renders the view again. This will work regardless of whether the
|
|
view is already in the DOM or not. If the view is in the DOM, the
|
|
rendering process will be deferred to give bindings a chance
|
|
to synchronize.
|
|
|
|
If children were added during the rendering process using `appendChild`,
|
|
`rerender` will remove them, because they will be added again
|
|
if needed by the next `render`.
|
|
|
|
In general, if the display of your view changes, you should modify
|
|
the DOM element directly instead of manually calling `rerender`, which can
|
|
be slow.
|
|
|
|
@method rerender
|
|
*/
|
|
rerender: function() {
|
|
return this.currentState.rerender(this);
|
|
},
|
|
|
|
/**
|
|
Iterates over the view's `classNameBindings` array, inserts the value
|
|
of the specified property into the `classNames` array, then creates an
|
|
observer to update the view's element if the bound property ever changes
|
|
in the future.
|
|
|
|
@method _applyClassNameBindings
|
|
@private
|
|
*/
|
|
_applyClassNameBindings: function(classBindings) {
|
|
var classNames = this.classNames;
|
|
var elem, newClass, dasherizedClass;
|
|
|
|
// Loop through all of the configured bindings. These will be either
|
|
// property names ('isUrgent') or property paths relative to the view
|
|
// ('content.isUrgent')
|
|
forEach(classBindings, function(binding) {
|
|
|
|
var boundBinding;
|
|
if (isStream(binding)) {
|
|
boundBinding = binding;
|
|
} else {
|
|
boundBinding = streamifyClassNameBinding(this, binding, '_view.');
|
|
}
|
|
|
|
// Variable in which the old class value is saved. The observer function
|
|
// closes over this variable, so it knows which string to remove when
|
|
// the property changes.
|
|
var oldClass;
|
|
|
|
// Set up an observer on the context. If the property changes, toggle the
|
|
// class name.
|
|
var observer = this._wrapAsScheduled(function() {
|
|
// Get the current value of the property
|
|
elem = this.$();
|
|
newClass = read(boundBinding);
|
|
|
|
// If we had previously added a class to the element, remove it.
|
|
if (oldClass) {
|
|
elem.removeClass(oldClass);
|
|
// Also remove from classNames so that if the view gets rerendered,
|
|
// the class doesn't get added back to the DOM.
|
|
classNames.removeObject(oldClass);
|
|
}
|
|
|
|
// If necessary, add a new class. Make sure we keep track of it so
|
|
// it can be removed in the future.
|
|
if (newClass) {
|
|
elem.addClass(newClass);
|
|
oldClass = newClass;
|
|
} else {
|
|
oldClass = null;
|
|
}
|
|
});
|
|
|
|
// Get the class name for the property at its current value
|
|
dasherizedClass = read(boundBinding);
|
|
|
|
if (dasherizedClass) {
|
|
// Ensure that it gets into the classNames array
|
|
// so it is displayed when we render.
|
|
addObject(classNames, dasherizedClass);
|
|
|
|
// Save a reference to the class name so we can remove it
|
|
// if the observer fires. Remember that this variable has
|
|
// been closed over by the observer.
|
|
oldClass = dasherizedClass;
|
|
}
|
|
|
|
subscribe(boundBinding, observer, this);
|
|
// Remove className so when the view is rerendered,
|
|
// the className is added based on binding reevaluation
|
|
this.one('willClearRender', function() {
|
|
if (oldClass) {
|
|
classNames.removeObject(oldClass);
|
|
oldClass = null;
|
|
}
|
|
});
|
|
|
|
}, this);
|
|
},
|
|
|
|
_unspecifiedAttributeBindings: null,
|
|
|
|
/**
|
|
Iterates through the view's attribute bindings, sets up observers for each,
|
|
then applies the current value of the attributes to the passed render buffer.
|
|
|
|
@method _applyAttributeBindings
|
|
@param {Ember.RenderBuffer} buffer
|
|
@private
|
|
*/
|
|
_applyAttributeBindings: function(buffer, attributeBindings) {
|
|
var attributeValue;
|
|
var unspecifiedAttributeBindings = this._unspecifiedAttributeBindings = this._unspecifiedAttributeBindings || {};
|
|
|
|
forEach(attributeBindings, function(binding) {
|
|
var split = binding.split(':');
|
|
var property = split[0];
|
|
var attributeName = split[1] || property;
|
|
|
|
Ember.assert('You cannot use class as an attributeBinding, use classNameBindings instead.', attributeName !== 'class');
|
|
|
|
if (property in this) {
|
|
this._setupAttributeBindingObservation(property, attributeName);
|
|
|
|
// Determine the current value and add it to the render buffer
|
|
// if necessary.
|
|
attributeValue = get(this, property);
|
|
View.applyAttributeBindings(buffer, attributeName, attributeValue);
|
|
} else {
|
|
unspecifiedAttributeBindings[property] = attributeName;
|
|
}
|
|
}, this);
|
|
|
|
// Lazily setup setUnknownProperty after attributeBindings are initially applied
|
|
this.setUnknownProperty = this._setUnknownProperty;
|
|
},
|
|
|
|
_setupAttributeBindingObservation: function(property, attributeName) {
|
|
var attributeValue, elem;
|
|
|
|
// Create an observer to add/remove/change the attribute if the
|
|
// JavaScript property changes.
|
|
var observer = function() {
|
|
elem = this.$();
|
|
|
|
attributeValue = get(this, property);
|
|
|
|
var normalizedName = normalizeProperty(elem, attributeName.toLowerCase()) || attributeName;
|
|
View.applyAttributeBindings(elem, normalizedName, attributeValue);
|
|
};
|
|
|
|
this.registerObserver(this, property, observer);
|
|
},
|
|
|
|
/**
|
|
We're using setUnknownProperty as a hook to setup attributeBinding observers for
|
|
properties that aren't defined on a view at initialization time.
|
|
|
|
Note: setUnknownProperty will only be called once for each property.
|
|
|
|
@method setUnknownProperty
|
|
@param key
|
|
@param value
|
|
@private
|
|
*/
|
|
setUnknownProperty: null, // Gets defined after initialization by _applyAttributeBindings
|
|
|
|
_setUnknownProperty: function(key, value) {
|
|
var attributeName = this._unspecifiedAttributeBindings && this._unspecifiedAttributeBindings[key];
|
|
if (attributeName) {
|
|
this._setupAttributeBindingObservation(key, attributeName);
|
|
}
|
|
|
|
defineProperty(this, key);
|
|
return set(this, key, value);
|
|
},
|
|
|
|
/**
|
|
Given a property name, returns a dasherized version of that
|
|
property name if the property evaluates to a non-falsy value.
|
|
|
|
For example, if the view has property `isUrgent` that evaluates to true,
|
|
passing `isUrgent` to this method will return `"is-urgent"`.
|
|
|
|
@method _classStringForProperty
|
|
@param property
|
|
@private
|
|
*/
|
|
_classStringForProperty: function(parsedPath) {
|
|
return View._classStringForValue(parsedPath.path, parsedPath.stream.value(), parsedPath.className, parsedPath.falsyClassName);
|
|
},
|
|
|
|
// ..........................................................
|
|
// ELEMENT SUPPORT
|
|
//
|
|
|
|
/**
|
|
Returns the current DOM element for the view.
|
|
|
|
@property element
|
|
@type DOMElement
|
|
*/
|
|
element: null,
|
|
|
|
/**
|
|
Returns a jQuery object for this view's element. If you pass in a selector
|
|
string, this method will return a jQuery object, using the current element
|
|
as its buffer.
|
|
|
|
For example, calling `view.$('li')` will return a jQuery object containing
|
|
all of the `li` elements inside the DOM element of this view.
|
|
|
|
@method $
|
|
@param {String} [selector] a jQuery-compatible selector string
|
|
@return {jQuery} the jQuery object for the DOM node
|
|
*/
|
|
$: function(sel) {
|
|
return this.currentState.$(this, sel);
|
|
},
|
|
|
|
mutateChildViews: function(callback) {
|
|
var childViews = this._childViews;
|
|
var idx = childViews.length;
|
|
var view;
|
|
|
|
while(--idx >= 0) {
|
|
view = childViews[idx];
|
|
callback(this, view, idx);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
forEachChildView: function(callback) {
|
|
var childViews = this._childViews;
|
|
|
|
if (!childViews) { return this; }
|
|
|
|
var len = childViews.length;
|
|
var view, idx;
|
|
|
|
for (idx = 0; idx < len; idx++) {
|
|
view = childViews[idx];
|
|
callback(view);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Appends the view's element to the specified parent element.
|
|
|
|
If the view does not have an HTML representation yet, `createElement()`
|
|
will be called automatically.
|
|
|
|
Note that this method just schedules the view to be appended; the DOM
|
|
element will not be appended to the given element until all bindings have
|
|
finished synchronizing.
|
|
|
|
This is not typically a function that you will need to call directly when
|
|
building your application. You might consider using `Ember.ContainerView`
|
|
instead. If you do need to use `appendTo`, be sure that the target element
|
|
you are providing is associated with an `Ember.Application` and does not
|
|
have an ancestor element that is associated with an Ember view.
|
|
|
|
@method appendTo
|
|
@param {String|DOMElement|jQuery} A selector, element, HTML string, or jQuery object
|
|
@return {Ember.View} receiver
|
|
*/
|
|
appendTo: function(selector) {
|
|
var target = jQuery(selector);
|
|
|
|
Ember.assert("You tried to append to (" + selector + ") but that isn't in the DOM", target.length > 0);
|
|
Ember.assert("You cannot append to an existing Ember.View. Consider using Ember.ContainerView instead.", !target.is('.ember-view') && !target.parents().is('.ember-view'));
|
|
|
|
this.constructor.renderer.appendTo(this, target[0]);
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Replaces the content of the specified parent element with this view's
|
|
element. If the view does not have an HTML representation yet,
|
|
the element will be generated automatically.
|
|
|
|
Note that this method just schedules the view to be appended; the DOM
|
|
element will not be appended to the given element until all bindings have
|
|
finished synchronizing
|
|
|
|
@method replaceIn
|
|
@param {String|DOMElement|jQuery} target A selector, element, HTML string, or jQuery object
|
|
@return {Ember.View} received
|
|
*/
|
|
replaceIn: function(selector) {
|
|
var target = jQuery(selector);
|
|
|
|
Ember.assert("You tried to replace in (" + selector + ") but that isn't in the DOM", target.length > 0);
|
|
Ember.assert("You cannot replace an existing Ember.View. Consider using Ember.ContainerView instead.", !target.is('.ember-view') && !target.parents().is('.ember-view'));
|
|
|
|
this.constructor.renderer.replaceIn(this, target[0]);
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Appends the view's element to the document body. If the view does
|
|
not have an HTML representation yet
|
|
the element will be generated automatically.
|
|
|
|
If your application uses the `rootElement` property, you must append
|
|
the view within that element. Rendering views outside of the `rootElement`
|
|
is not supported.
|
|
|
|
Note that this method just schedules the view to be appended; the DOM
|
|
element will not be appended to the document body until all bindings have
|
|
finished synchronizing.
|
|
|
|
@method append
|
|
@return {Ember.View} receiver
|
|
*/
|
|
append: function() {
|
|
return this.appendTo(document.body);
|
|
},
|
|
|
|
/**
|
|
Removes the view's element from the element to which it is attached.
|
|
|
|
@method remove
|
|
@return {Ember.View} receiver
|
|
*/
|
|
remove: function() {
|
|
// What we should really do here is wait until the end of the run loop
|
|
// to determine if the element has been re-appended to a different
|
|
// element.
|
|
// In the interim, we will just re-render if that happens. It is more
|
|
// important than elements get garbage collected.
|
|
if (!this.removedFromDOM) { this.destroyElement(); }
|
|
},
|
|
|
|
/**
|
|
The HTML `id` of the view's element in the DOM. You can provide this
|
|
value yourself but it must be unique (just as in HTML):
|
|
|
|
```handlebars
|
|
{{my-component elementId="a-really-cool-id"}}
|
|
```
|
|
|
|
If not manually set a default value will be provided by the framework.
|
|
|
|
Once rendered an element's `elementId` is considered immutable and you
|
|
should never change it.
|
|
|
|
@property elementId
|
|
@type String
|
|
*/
|
|
elementId: null,
|
|
|
|
/**
|
|
Attempts to discover the element in the parent element. The default
|
|
implementation looks for an element with an ID of `elementId` (or the
|
|
view's guid if `elementId` is null). You can override this method to
|
|
provide your own form of lookup. For example, if you want to discover your
|
|
element using a CSS class name instead of an ID.
|
|
|
|
@method findElementInParentElement
|
|
@param {DOMElement} parentElement The parent's DOM element
|
|
@return {DOMElement} The discovered element
|
|
*/
|
|
findElementInParentElement: function(parentElem) {
|
|
var id = "#" + this.elementId;
|
|
return jQuery(id)[0] || jQuery(id, parentElem)[0];
|
|
},
|
|
|
|
/**
|
|
Creates a DOM representation of the view and all of its child views by
|
|
recursively calling the `render()` method.
|
|
|
|
After the element has been inserted into the DOM, `didInsertElement` will
|
|
be called on this view and all of its child views.
|
|
|
|
@method createElement
|
|
@return {Ember.View} receiver
|
|
*/
|
|
createElement: function() {
|
|
if (this.element) { return this; }
|
|
|
|
this._didCreateElementWithoutMorph = true;
|
|
this.constructor.renderer.renderTree(this);
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Called when a view is going to insert an element into the DOM.
|
|
|
|
@event willInsertElement
|
|
*/
|
|
willInsertElement: K,
|
|
|
|
/**
|
|
Called when the element of the view has been inserted into the DOM
|
|
or after the view was re-rendered. Override this function to do any
|
|
set up that requires an element in the document body.
|
|
|
|
When a view has children, didInsertElement will be called on the
|
|
child view(s) first, bubbling upwards through the hierarchy.
|
|
|
|
@event didInsertElement
|
|
*/
|
|
didInsertElement: K,
|
|
|
|
/**
|
|
Called when the view is about to rerender, but before anything has
|
|
been torn down. This is a good opportunity to tear down any manual
|
|
observers you have installed based on the DOM state
|
|
|
|
@event willClearRender
|
|
*/
|
|
willClearRender: K,
|
|
|
|
/**
|
|
Destroys any existing element along with the element for any child views
|
|
as well. If the view does not currently have a element, then this method
|
|
will do nothing.
|
|
|
|
If you implement `willDestroyElement()` on your view, then this method will
|
|
be invoked on your view before your element is destroyed to give you a
|
|
chance to clean up any event handlers, etc.
|
|
|
|
If you write a `willDestroyElement()` handler, you can assume that your
|
|
`didInsertElement()` handler was called earlier for the same element.
|
|
|
|
You should not call or override this method yourself, but you may
|
|
want to implement the above callbacks.
|
|
|
|
@method destroyElement
|
|
@return {Ember.View} receiver
|
|
*/
|
|
destroyElement: function() {
|
|
return this.currentState.destroyElement(this);
|
|
},
|
|
|
|
/**
|
|
Called when the element of the view is going to be destroyed. Override
|
|
this function to do any teardown that requires an element, like removing
|
|
event listeners.
|
|
|
|
Please note: any property changes made during this event will have no
|
|
effect on object observers.
|
|
|
|
@event willDestroyElement
|
|
*/
|
|
willDestroyElement: K,
|
|
|
|
/**
|
|
Called when the parentView property has changed.
|
|
|
|
@event parentViewDidChange
|
|
*/
|
|
parentViewDidChange: K,
|
|
|
|
instrumentName: 'view',
|
|
|
|
instrumentDetails: function(hash) {
|
|
hash.template = get(this, 'templateName');
|
|
this._super(hash);
|
|
},
|
|
|
|
beforeRender: function(buffer) {},
|
|
|
|
afterRender: function(buffer) {},
|
|
|
|
applyAttributesToBuffer: function(buffer) {
|
|
// Creates observers for all registered class name and attribute bindings,
|
|
// then adds them to the element.
|
|
var classNameBindings = this.classNameBindings;
|
|
if (classNameBindings.length) {
|
|
this._applyClassNameBindings(classNameBindings);
|
|
}
|
|
|
|
// Pass the render buffer so the method can apply attributes directly.
|
|
// This isn't needed for class name bindings because they use the
|
|
// existing classNames infrastructure.
|
|
var attributeBindings = this.attributeBindings;
|
|
if (attributeBindings.length) {
|
|
this._applyAttributeBindings(buffer, attributeBindings);
|
|
}
|
|
|
|
buffer.setClasses(this.classNames);
|
|
buffer.id(this.elementId);
|
|
|
|
var role = get(this, 'ariaRole');
|
|
if (role) {
|
|
buffer.attr('role', role);
|
|
}
|
|
|
|
if (get(this, 'isVisible') === false) {
|
|
buffer.style('display', 'none');
|
|
}
|
|
},
|
|
|
|
// ..........................................................
|
|
// STANDARD RENDER PROPERTIES
|
|
//
|
|
|
|
/**
|
|
Tag name for the view's outer element. The tag name is only used when an
|
|
element is first created. If you change the `tagName` for an element, you
|
|
must destroy and recreate the view element.
|
|
|
|
By default, the render buffer will use a `<div>` tag for views.
|
|
|
|
@property tagName
|
|
@type String
|
|
@default null
|
|
*/
|
|
|
|
// We leave this null by default so we can tell the difference between
|
|
// the default case and a user-specified tag.
|
|
tagName: null,
|
|
|
|
/**
|
|
The WAI-ARIA role of the control represented by this view. For example, a
|
|
button may have a role of type 'button', or a pane may have a role of
|
|
type 'alertdialog'. This property is used by assistive software to help
|
|
visually challenged users navigate rich web applications.
|
|
|
|
The full list of valid WAI-ARIA roles is available at:
|
|
[http://www.w3.org/TR/wai-aria/roles#roles_categorization](http://www.w3.org/TR/wai-aria/roles#roles_categorization)
|
|
|
|
@property ariaRole
|
|
@type String
|
|
@default null
|
|
*/
|
|
ariaRole: null,
|
|
|
|
/**
|
|
Standard CSS class names to apply to the view's outer element. This
|
|
property automatically inherits any class names defined by the view's
|
|
superclasses as well.
|
|
|
|
@property classNames
|
|
@type Array
|
|
@default ['ember-view']
|
|
*/
|
|
classNames: ['ember-view'],
|
|
|
|
/**
|
|
A list of properties of the view to apply as class names. If the property
|
|
is a string value, the value of that string will be applied as a class
|
|
name.
|
|
|
|
```javascript
|
|
// Applies the 'high' class to the view element
|
|
Ember.View.extend({
|
|
classNameBindings: ['priority']
|
|
priority: 'high'
|
|
});
|
|
```
|
|
|
|
If the value of the property is a Boolean, the name of that property is
|
|
added as a dasherized class name.
|
|
|
|
```javascript
|
|
// Applies the 'is-urgent' class to the view element
|
|
Ember.View.extend({
|
|
classNameBindings: ['isUrgent']
|
|
isUrgent: true
|
|
});
|
|
```
|
|
|
|
If you would prefer to use a custom value instead of the dasherized
|
|
property name, you can pass a binding like this:
|
|
|
|
```javascript
|
|
// Applies the 'urgent' class to the view element
|
|
Ember.View.extend({
|
|
classNameBindings: ['isUrgent:urgent']
|
|
isUrgent: true
|
|
});
|
|
```
|
|
|
|
This list of properties is inherited from the view's superclasses as well.
|
|
|
|
@property classNameBindings
|
|
@type Array
|
|
@default []
|
|
*/
|
|
classNameBindings: EMPTY_ARRAY,
|
|
|
|
/**
|
|
A list of properties of the view to apply as attributes. If the property is
|
|
a string value, the value of that string will be applied as the attribute.
|
|
|
|
```javascript
|
|
// Applies the type attribute to the element
|
|
// with the value "button", like <div type="button">
|
|
Ember.View.extend({
|
|
attributeBindings: ['type'],
|
|
type: 'button'
|
|
});
|
|
```
|
|
|
|
If the value of the property is a Boolean, the name of that property is
|
|
added as an attribute.
|
|
|
|
```javascript
|
|
// Renders something like <div enabled="enabled">
|
|
Ember.View.extend({
|
|
attributeBindings: ['enabled'],
|
|
enabled: true
|
|
});
|
|
```
|
|
|
|
@property attributeBindings
|
|
*/
|
|
attributeBindings: EMPTY_ARRAY,
|
|
|
|
// .......................................................
|
|
// CORE DISPLAY METHODS
|
|
//
|
|
|
|
/**
|
|
Setup a view, but do not finish waking it up.
|
|
|
|
* configure `childViews`
|
|
* register the view with the global views hash, which is used for event
|
|
dispatch
|
|
|
|
@method init
|
|
@private
|
|
*/
|
|
init: function() {
|
|
if (!this.isVirtual && !this.elementId) {
|
|
this.elementId = guidFor(this);
|
|
}
|
|
|
|
this._super();
|
|
|
|
// setup child views. be sure to clone the child views array first
|
|
this._childViews = this._childViews.slice();
|
|
this._baseContext = undefined;
|
|
this._contextStream = undefined;
|
|
this._streamBindings = undefined;
|
|
|
|
if (!this._keywords) {
|
|
this._keywords = create(null);
|
|
}
|
|
this._keywords._view = this;
|
|
this._keywords.view = undefined;
|
|
this._keywords.controller = new KeyStream(this, 'controller');
|
|
this._setupKeywords();
|
|
|
|
Ember.assert("Only arrays are allowed for 'classNameBindings'", typeOf(this.classNameBindings) === 'array');
|
|
this.classNameBindings = emberA(this.classNameBindings.slice());
|
|
|
|
Ember.assert("Only arrays of static class strings are allowed for 'classNames'. For dynamic classes, use 'classNameBindings'.", typeOf(this.classNames) === 'array');
|
|
this.classNames = emberA(this.classNames.slice());
|
|
},
|
|
|
|
appendChild: function(view, options) {
|
|
return this.currentState.appendChild(this, view, options);
|
|
},
|
|
|
|
/**
|
|
Removes the child view from the parent view.
|
|
|
|
@method removeChild
|
|
@param {Ember.View} view
|
|
@return {Ember.View} receiver
|
|
*/
|
|
removeChild: function(view) {
|
|
// If we're destroying, the entire subtree will be
|
|
// freed, and the DOM will be handled separately,
|
|
// so no need to mess with childViews.
|
|
if (this.isDestroying) { return; }
|
|
|
|
// update parent node
|
|
set(view, '_parentView', null);
|
|
|
|
// remove view from childViews array.
|
|
var childViews = this._childViews;
|
|
|
|
removeObject(childViews, view);
|
|
|
|
this.propertyDidChange('childViews'); // HUH?! what happened to will change?
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Removes all children from the `parentView`.
|
|
|
|
@method removeAllChildren
|
|
@return {Ember.View} receiver
|
|
*/
|
|
removeAllChildren: function() {
|
|
return this.mutateChildViews(function(parentView, view) {
|
|
parentView.removeChild(view);
|
|
});
|
|
},
|
|
|
|
destroyAllChildren: function() {
|
|
return this.mutateChildViews(function(parentView, view) {
|
|
view.destroy();
|
|
});
|
|
},
|
|
|
|
/**
|
|
Removes the view from its `parentView`, if one is found. Otherwise
|
|
does nothing.
|
|
|
|
@method removeFromParent
|
|
@return {Ember.View} receiver
|
|
*/
|
|
removeFromParent: function() {
|
|
var parent = this._parentView;
|
|
|
|
// Remove DOM element from parent
|
|
this.remove();
|
|
|
|
if (parent) { parent.removeChild(this); }
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
You must call `destroy` on a view to destroy the view (and all of its
|
|
child views). This will remove the view from any parent node, then make
|
|
sure that the DOM element managed by the view can be released by the
|
|
memory manager.
|
|
|
|
@method destroy
|
|
*/
|
|
destroy: function() {
|
|
// get parentView before calling super because it'll be destroyed
|
|
var nonVirtualParentView = get(this, 'parentView');
|
|
var viewName = this.viewName;
|
|
|
|
if (!this._super()) { return; }
|
|
|
|
// remove from non-virtual parent view if viewName was specified
|
|
if (viewName && nonVirtualParentView) {
|
|
nonVirtualParentView.set(viewName, null);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
Instantiates a view to be added to the childViews array during view
|
|
initialization. You generally will not call this method directly unless
|
|
you are overriding `createChildViews()`. Note that this method will
|
|
automatically configure the correct settings on the new view instance to
|
|
act as a child of the parent.
|
|
|
|
@method createChildView
|
|
@param {Class|String} viewClass
|
|
@param {Hash} [attrs] Attributes to add
|
|
@return {Ember.View} new instance
|
|
*/
|
|
createChildView: function(view, attrs) {
|
|
if (!view) {
|
|
throw new TypeError("createChildViews first argument must exist");
|
|
}
|
|
|
|
if (view.isView && view._parentView === this && view.container === this.container) {
|
|
return view;
|
|
}
|
|
|
|
attrs = attrs || {};
|
|
attrs._parentView = this;
|
|
|
|
if (CoreView.detect(view)) {
|
|
attrs.container = this.container;
|
|
view = view.create(attrs);
|
|
|
|
// don't set the property on a virtual view, as they are invisible to
|
|
// consumers of the view API
|
|
if (view.viewName) {
|
|
set(get(this, 'concreteView'), view.viewName, view);
|
|
}
|
|
} else if ('string' === typeof view) {
|
|
var fullName = 'view:' + view;
|
|
var ViewKlass = this.container.lookupFactory(fullName);
|
|
|
|
Ember.assert("Could not find view: '" + fullName + "'", !!ViewKlass);
|
|
|
|
view = ViewKlass.create(attrs);
|
|
} else {
|
|
Ember.assert('You must pass instance or subclass of View', view.isView);
|
|
|
|
attrs.container = this.container;
|
|
setProperties(view, attrs);
|
|
}
|
|
|
|
return view;
|
|
},
|
|
|
|
becameVisible: K,
|
|
becameHidden: K,
|
|
|
|
/**
|
|
When the view's `isVisible` property changes, toggle the visibility
|
|
element of the actual DOM element.
|
|
|
|
@method _isVisibleDidChange
|
|
@private
|
|
*/
|
|
_isVisibleDidChange: observer('isVisible', function() {
|
|
if (this._isVisible === get(this, 'isVisible')) { return ; }
|
|
run.scheduleOnce('render', this, this._toggleVisibility);
|
|
}),
|
|
|
|
_toggleVisibility: function() {
|
|
var $el = this.$();
|
|
var isVisible = get(this, 'isVisible');
|
|
|
|
if (this._isVisible === isVisible) { return ; }
|
|
|
|
// It's important to keep these in sync, even if we don't yet have
|
|
// an element in the DOM to manipulate:
|
|
this._isVisible = isVisible;
|
|
|
|
if (!$el) { return; }
|
|
|
|
$el.toggle(isVisible);
|
|
|
|
if (this._isAncestorHidden()) { return; }
|
|
|
|
if (isVisible) {
|
|
this._notifyBecameVisible();
|
|
} else {
|
|
this._notifyBecameHidden();
|
|
}
|
|
},
|
|
|
|
_notifyBecameVisible: function() {
|
|
this.trigger('becameVisible');
|
|
|
|
this.forEachChildView(function(view) {
|
|
var isVisible = get(view, 'isVisible');
|
|
|
|
if (isVisible || isVisible === null) {
|
|
view._notifyBecameVisible();
|
|
}
|
|
});
|
|
},
|
|
|
|
_notifyBecameHidden: function() {
|
|
this.trigger('becameHidden');
|
|
this.forEachChildView(function(view) {
|
|
var isVisible = get(view, 'isVisible');
|
|
|
|
if (isVisible || isVisible === null) {
|
|
view._notifyBecameHidden();
|
|
}
|
|
});
|
|
},
|
|
|
|
_isAncestorHidden: function() {
|
|
var parent = get(this, 'parentView');
|
|
|
|
while (parent) {
|
|
if (get(parent, 'isVisible') === false) { return true; }
|
|
|
|
parent = get(parent, 'parentView');
|
|
}
|
|
|
|
return false;
|
|
},
|
|
transitionTo: function(state, children) {
|
|
Ember.deprecate("Ember.View#transitionTo has been deprecated, it is for internal use only");
|
|
this._transitionTo(state, children);
|
|
},
|
|
_transitionTo: function(state, children) {
|
|
var priorState = this.currentState;
|
|
var currentState = this.currentState = this._states[state];
|
|
this._state = state;
|
|
|
|
if (priorState && priorState.exit) { priorState.exit(this); }
|
|
if (currentState.enter) { currentState.enter(this); }
|
|
},
|
|
|
|
// .......................................................
|
|
// EVENT HANDLING
|
|
//
|
|
|
|
/**
|
|
Handle events from `Ember.EventDispatcher`
|
|
|
|
@method handleEvent
|
|
@param eventName {String}
|
|
@param evt {Event}
|
|
@private
|
|
*/
|
|
handleEvent: function(eventName, evt) {
|
|
return this.currentState.handleEvent(this, eventName, evt);
|
|
},
|
|
|
|
registerObserver: function(root, path, target, observer) {
|
|
if (!observer && 'function' === typeof target) {
|
|
observer = target;
|
|
target = null;
|
|
}
|
|
|
|
if (!root || typeof root !== 'object') {
|
|
return;
|
|
}
|
|
|
|
var scheduledObserver = this._wrapAsScheduled(observer);
|
|
|
|
addObserver(root, path, target, scheduledObserver);
|
|
|
|
this.one('willClearRender', function() {
|
|
removeObserver(root, path, target, scheduledObserver);
|
|
});
|
|
},
|
|
|
|
_wrapAsScheduled: function(fn) {
|
|
var view = this;
|
|
var stateCheckedFn = function() {
|
|
view.currentState.invokeObserver(this, fn);
|
|
};
|
|
var scheduledFn = function() {
|
|
run.scheduleOnce('render', this, stateCheckedFn);
|
|
};
|
|
return scheduledFn;
|
|
},
|
|
|
|
getStream: function(path) {
|
|
var stream = this._getContextStream().get(path);
|
|
|
|
stream._label = path;
|
|
|
|
return stream;
|
|
},
|
|
|
|
_getBindingForStream: function(pathOrStream) {
|
|
if (this._streamBindings === undefined) {
|
|
this._streamBindings = create(null);
|
|
this.one('willDestroyElement', this, this._destroyStreamBindings);
|
|
}
|
|
|
|
var path = pathOrStream;
|
|
if (isStream(pathOrStream)) {
|
|
path = pathOrStream._label;
|
|
|
|
if (!path) {
|
|
// if no _label is present on the provided stream
|
|
// it is likely a subexpr and cannot be set (so it
|
|
// does not need a StreamBinding)
|
|
return pathOrStream;
|
|
}
|
|
}
|
|
|
|
if (this._streamBindings[path] !== undefined) {
|
|
return this._streamBindings[path];
|
|
} else {
|
|
var stream = this._getContextStream().get(path);
|
|
var streamBinding = new StreamBinding(stream);
|
|
|
|
streamBinding._label = path;
|
|
|
|
return this._streamBindings[path] = streamBinding;
|
|
}
|
|
},
|
|
|
|
_destroyStreamBindings: function() {
|
|
var streamBindings = this._streamBindings;
|
|
for (var path in streamBindings) {
|
|
streamBindings[path].destroy();
|
|
}
|
|
this._streamBindings = undefined;
|
|
},
|
|
|
|
_getContextStream: function() {
|
|
if (this._contextStream === undefined) {
|
|
this._baseContext = new KeyStream(this, 'context');
|
|
this._contextStream = new ContextStream(this);
|
|
this.one('willDestroyElement', this, this._destroyContextStream);
|
|
}
|
|
|
|
return this._contextStream;
|
|
},
|
|
|
|
_destroyContextStream: function() {
|
|
this._baseContext.destroy();
|
|
this._baseContext = undefined;
|
|
this._contextStream.destroy();
|
|
this._contextStream = undefined;
|
|
},
|
|
|
|
_unsubscribeFromStreamBindings: function() {
|
|
for (var key in this._streamBindingSubscriptions) {
|
|
var streamBinding = this[key + 'Binding'];
|
|
var callback = this._streamBindingSubscriptions[key];
|
|
streamBinding.unsubscribe(callback);
|
|
}
|
|
}
|
|
});
|
|
|
|
deprecateProperty(View.prototype, 'state', '_state');
|
|
deprecateProperty(View.prototype, 'states', '_states');
|
|
|
|
/*
|
|
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.
|
|
|
|
var mutation = EmberObject.extend(Evented).create();
|
|
// TODO MOVE TO RENDERER HOOKS
|
|
View.addMutationListener = function(callback) {
|
|
mutation.on('change', callback);
|
|
};
|
|
|
|
View.removeMutationListener = function(callback) {
|
|
mutation.off('change', callback);
|
|
};
|
|
|
|
View.notifyMutationListeners = function() {
|
|
mutation.trigger('change');
|
|
};
|
|
|
|
/**
|
|
Global views hash
|
|
|
|
@property views
|
|
@static
|
|
@type Hash
|
|
*/
|
|
View.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.
|
|
View.childViewsProperty = childViewsProperty;
|
|
|
|
// Used by Handlebars helpers, view element attributes
|
|
View.applyAttributeBindings = function(elem, name, initialValue) {
|
|
var value = sanitizeAttributeValue(elem[0], name, initialValue);
|
|
var type = typeOf(value);
|
|
|
|
// if this changes, also change the logic in ember-handlebars/lib/helpers/binding.js
|
|
if (name !== 'value' && (type === 'string' || (type === 'number' && !isNaN(value)))) {
|
|
if (value !== elem.attr(name)) {
|
|
elem.attr(name, value);
|
|
}
|
|
} else if (name === 'value' || type === 'boolean') {
|
|
if (isNone(value) || value === false) {
|
|
// `null`, `undefined` or `false` should remove attribute
|
|
elem.removeAttr(name);
|
|
// In IE8 `prop` couldn't remove attribute when name is `required`.
|
|
if (name === 'required') {
|
|
elem.removeProp(name);
|
|
} else {
|
|
elem.prop(name, '');
|
|
}
|
|
} else if (value !== elem.prop(name)) {
|
|
// value should always be properties
|
|
elem.prop(name, value);
|
|
}
|
|
} else if (!value) {
|
|
elem.removeAttr(name);
|
|
}
|
|
};
|
|
|
|
__exports__["default"] = View;
|
|
});
|
|
enifed("ember-views/views/with_view",
|
|
["ember-metal/property_set","ember-metal/utils","ember-views/views/bound_view","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
|
|
/**
|
|
@module ember
|
|
@submodule ember-views
|
|
*/
|
|
|
|
var set = __dependency1__.set;
|
|
var apply = __dependency2__.apply;
|
|
var BoundView = __dependency3__["default"];
|
|
|
|
__exports__["default"] = BoundView.extend({
|
|
init: function() {
|
|
apply(this, this._super, arguments);
|
|
|
|
var controllerName = this.templateHash.controller;
|
|
|
|
if (controllerName) {
|
|
var previousContext = this.previousContext;
|
|
var controller = this.container.lookupFactory('controller:'+controllerName).create({
|
|
parentController: previousContext,
|
|
target: previousContext
|
|
});
|
|
|
|
this._generatedController = controller;
|
|
|
|
if (this.preserveContext) {
|
|
this._blockArguments = [ controller ];
|
|
this.lazyValue.subscribe(function(modelStream) {
|
|
set(controller, 'model', modelStream.value());
|
|
});
|
|
} else {
|
|
set(this, 'controller', controller);
|
|
this.valueNormalizerFunc = function(result) {
|
|
controller.set('model', result);
|
|
return controller;
|
|
};
|
|
}
|
|
|
|
set(controller, 'model', this.lazyValue.value());
|
|
} else {
|
|
if (this.preserveContext) {
|
|
this._blockArguments = [ this.lazyValue ];
|
|
}
|
|
}
|
|
},
|
|
|
|
willDestroy: function() {
|
|
this._super();
|
|
|
|
if (this._generatedController) {
|
|
this._generatedController.destroy();
|
|
}
|
|
}
|
|
});
|
|
});
|
|
enifed("ember",
|
|
["ember-metal","ember-runtime","ember-views","ember-routing","ember-application","ember-extension-support","ember-htmlbars","ember-routing-htmlbars","ember-runtime/system/lazy_load"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__) {
|
|
"use strict";
|
|
/* global navigator */
|
|
// require the main entry points for each of these packages
|
|
// this is so that the global exports occur properly
|
|
|
|
var runLoadHooks = __dependency9__.runLoadHooks;
|
|
|
|
if (Ember.__loader.registry['ember-template-compiler']) {
|
|
requireModule('ember-template-compiler');
|
|
}
|
|
|
|
// do this to ensure that Ember.Test is defined properly on the global
|
|
// if it is present.
|
|
if (Ember.__loader.registry['ember-testing']) {
|
|
requireModule('ember-testing');
|
|
}
|
|
|
|
runLoadHooks('Ember');
|
|
|
|
/**
|
|
Ember
|
|
|
|
@module ember
|
|
*/
|
|
|
|
Ember.deprecate('Usage of Ember is deprecated for Internet Explorer 6 and 7, support will be removed in the next major version.', !navigator.userAgent.match(/MSIE [67]/));
|
|
});
|
|
enifed("htmlbars-util",
|
|
["./htmlbars-util/safe-string","./htmlbars-util/handlebars/utils","./htmlbars-util/namespaces","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
var SafeString = __dependency1__["default"];
|
|
var escapeExpression = __dependency2__.escapeExpression;
|
|
var getAttrNamespace = __dependency3__.getAttrNamespace;
|
|
|
|
__exports__.SafeString = SafeString;
|
|
__exports__.escapeExpression = escapeExpression;
|
|
__exports__.getAttrNamespace = getAttrNamespace;
|
|
});
|
|
enifed("htmlbars-util/array-utils",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
__exports__.forEach = forEach;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;
|
|
}
|
|
|
|
__exports__.map = map;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 indexOfArray = getIdx;
|
|
__exports__.indexOfArray = indexOfArray;
|
|
});
|
|
enifed("htmlbars-util/handlebars/safe-string",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
// Build out our basic SafeString type
|
|
function SafeString(string) {
|
|
this.string = string;
|
|
}
|
|
|
|
SafeString.prototype.toString = SafeString.prototype.toHTML = function() {
|
|
return "" + this.string;
|
|
};
|
|
|
|
__exports__["default"] = SafeString;
|
|
});
|
|
enifed("htmlbars-util/handlebars/utils",
|
|
["./safe-string","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
/*jshint -W004 */
|
|
var SafeString = __dependency1__["default"];
|
|
|
|
var escape = {
|
|
"&": "&",
|
|
"<": "<",
|
|
">": ">",
|
|
'"': """,
|
|
"'": "'",
|
|
"`": "`"
|
|
};
|
|
|
|
var badChars = /[&<>"'`]/g;
|
|
var 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;
|
|
}
|
|
|
|
__exports__.extend = extend;var toString = Object.prototype.toString;
|
|
__exports__.toString = toString;
|
|
// Sourced from lodash
|
|
// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
|
|
var isFunction = function(value) {
|
|
return typeof value === 'function';
|
|
};
|
|
// fallback for older versions of Chrome and Safari
|
|
/* istanbul ignore next */
|
|
if (isFunction(/x/)) {
|
|
isFunction = function(value) {
|
|
return typeof value === 'function' && toString.call(value) === '[object Function]';
|
|
};
|
|
}
|
|
var isFunction;
|
|
__exports__.isFunction = isFunction;
|
|
/* istanbul ignore next */
|
|
var isArray = Array.isArray || function(value) {
|
|
return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false;
|
|
};
|
|
__exports__.isArray = isArray;
|
|
|
|
function escapeExpression(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);
|
|
}
|
|
|
|
__exports__.escapeExpression = escapeExpression;function isEmpty(value) {
|
|
if (!value && value !== 0) {
|
|
return true;
|
|
} else if (isArray(value) && value.length === 0) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
__exports__.isEmpty = isEmpty;function appendContextPath(contextPath, id) {
|
|
return (contextPath ? contextPath + '.' : '') + id;
|
|
}
|
|
|
|
__exports__.appendContextPath = appendContextPath;
|
|
});
|
|
enifed("htmlbars-util/namespaces",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
// ref http://dev.w3.org/html5/spec-LC/namespaces.html
|
|
var defaultNamespaces = {
|
|
html: 'http://www.w3.org/1999/xhtml',
|
|
mathml: 'http://www.w3.org/1998/Math/MathML',
|
|
svg: 'http://www.w3.org/2000/svg',
|
|
xlink: 'http://www.w3.org/1999/xlink',
|
|
xml: 'http://www.w3.org/XML/1998/namespace'
|
|
};
|
|
|
|
function getAttrNamespace(attrName) {
|
|
var namespace;
|
|
|
|
var colonIndex = attrName.indexOf(':');
|
|
if (colonIndex !== -1) {
|
|
var prefix = attrName.slice(0, colonIndex);
|
|
namespace = defaultNamespaces[prefix];
|
|
}
|
|
|
|
return namespace || null;
|
|
}
|
|
|
|
__exports__.getAttrNamespace = getAttrNamespace;
|
|
});
|
|
enifed("htmlbars-util/object-utils",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
function merge(options, defaults) {
|
|
for (var prop in defaults) {
|
|
if (options.hasOwnProperty(prop)) { continue; }
|
|
options[prop] = defaults[prop];
|
|
}
|
|
return options;
|
|
}
|
|
|
|
__exports__.merge = merge;
|
|
});
|
|
enifed("htmlbars-util/quoting",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
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(", ") + "}";
|
|
}
|
|
|
|
__exports__.hash = hash;function repeat(chars, times) {
|
|
var str = "";
|
|
while (times--) {
|
|
str += chars;
|
|
}
|
|
return str;
|
|
}
|
|
|
|
__exports__.repeat = repeat;
|
|
});
|
|
enifed("htmlbars-util/safe-string",
|
|
["./handlebars/safe-string","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var SafeString = __dependency1__["default"];
|
|
|
|
__exports__["default"] = SafeString;
|
|
});
|
|
enifed("morph",
|
|
["./morph/morph","./morph/attr-morph","./morph/dom-helper","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
var Morph = __dependency1__["default"];
|
|
var AttrMorph = __dependency2__["default"];
|
|
var DOMHelper = __dependency3__["default"];
|
|
|
|
__exports__.Morph = Morph;
|
|
__exports__.AttrMorph = AttrMorph;
|
|
__exports__.DOMHelper = DOMHelper;
|
|
});
|
|
enifed("morph/attr-morph",
|
|
["./attr-morph/sanitize-attribute-value","./dom-helper/prop","./dom-helper/build-html-dom","../htmlbars-util","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
|
|
"use strict";
|
|
var sanitizeAttributeValue = __dependency1__.sanitizeAttributeValue;
|
|
var isAttrRemovalValue = __dependency2__.isAttrRemovalValue;
|
|
var normalizeProperty = __dependency2__.normalizeProperty;
|
|
var svgNamespace = __dependency3__.svgNamespace;
|
|
var getAttrNamespace = __dependency4__.getAttrNamespace;
|
|
|
|
function updateProperty(value) {
|
|
this.domHelper.setPropertyStrict(this.element, this.attrName, value);
|
|
}
|
|
|
|
function updateAttribute(value) {
|
|
if (isAttrRemovalValue(value)) {
|
|
this.domHelper.removeAttribute(this.element, this.attrName);
|
|
} else {
|
|
this.domHelper.setAttribute(this.element, this.attrName, value);
|
|
}
|
|
}
|
|
|
|
function updateAttributeNS(value) {
|
|
if (isAttrRemovalValue(value)) {
|
|
this.domHelper.removeAttribute(this.element, this.attrName);
|
|
} else {
|
|
this.domHelper.setAttributeNS(this.element, this.namespace, this.attrName, value);
|
|
}
|
|
}
|
|
|
|
function AttrMorph(element, attrName, domHelper, namespace) {
|
|
this.element = element;
|
|
this.domHelper = domHelper;
|
|
this.namespace = namespace !== undefined ? namespace : getAttrNamespace(attrName);
|
|
this.escaped = true;
|
|
|
|
var normalizedAttrName = normalizeProperty(this.element, attrName);
|
|
if (this.namespace) {
|
|
this._update = updateAttributeNS;
|
|
this.attrName = attrName;
|
|
} else {
|
|
if (element.namespaceURI === svgNamespace || attrName === 'style' || !normalizedAttrName) {
|
|
this.attrName = attrName;
|
|
this._update = updateAttribute;
|
|
} else {
|
|
this.attrName = normalizedAttrName;
|
|
this._update = updateProperty;
|
|
}
|
|
}
|
|
}
|
|
|
|
AttrMorph.prototype.setContent = function (value) {
|
|
if (this.escaped) {
|
|
var sanitized = sanitizeAttributeValue(this.element, this.attrName, value);
|
|
this._update(sanitized, this.namespace);
|
|
} else {
|
|
this._update(value, this.namespace);
|
|
}
|
|
};
|
|
|
|
__exports__["default"] = AttrMorph;
|
|
});
|
|
enifed("morph/attr-morph/sanitize-attribute-value",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
/* jshint scripturl:true */
|
|
|
|
var parsingNode;
|
|
var badProtocols = {
|
|
'javascript:': true,
|
|
'vbscript:': true
|
|
};
|
|
|
|
var badTags = {
|
|
'A': true,
|
|
'BODY': true,
|
|
'LINK': true,
|
|
'IMG': true,
|
|
'IFRAME': true
|
|
};
|
|
|
|
var badAttributes = {
|
|
'href': true,
|
|
'src': true,
|
|
'background': true
|
|
};
|
|
__exports__.badAttributes = badAttributes;
|
|
function sanitizeAttributeValue(element, attribute, value) {
|
|
var tagName;
|
|
|
|
if (!parsingNode) {
|
|
parsingNode = document.createElement('a');
|
|
}
|
|
|
|
if (!element) {
|
|
tagName = null;
|
|
} else {
|
|
tagName = element.tagName;
|
|
}
|
|
|
|
if (value && value.toHTML) {
|
|
return value.toHTML();
|
|
}
|
|
|
|
if ((tagName === null || badTags[tagName]) && badAttributes[attribute]) {
|
|
parsingNode.href = value;
|
|
|
|
if (badProtocols[parsingNode.protocol] === true) {
|
|
return 'unsafe:' + value;
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
__exports__.sanitizeAttributeValue = sanitizeAttributeValue;
|
|
});
|
|
enifed("morph/dom-helper",
|
|
["../morph/morph","../morph/attr-morph","./dom-helper/build-html-dom","./dom-helper/classes","./dom-helper/prop","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) {
|
|
"use strict";
|
|
var Morph = __dependency1__["default"];
|
|
var AttrMorph = __dependency2__["default"];
|
|
var buildHTMLDOM = __dependency3__.buildHTMLDOM;
|
|
var svgNamespace = __dependency3__.svgNamespace;
|
|
var svgHTMLIntegrationPoints = __dependency3__.svgHTMLIntegrationPoints;
|
|
var addClasses = __dependency4__.addClasses;
|
|
var removeClasses = __dependency4__.removeClasses;
|
|
var normalizeProperty = __dependency5__.normalizeProperty;
|
|
var isAttrRemovalValue = __dependency5__.isAttrRemovalValue;
|
|
|
|
var doc = typeof document === 'undefined' ? false : document;
|
|
|
|
var deletesBlankTextNodes = doc && (function(document){
|
|
var element = document.createElement('div');
|
|
element.appendChild( document.createTextNode('') );
|
|
var clonedElement = element.cloneNode(true);
|
|
return clonedElement.childNodes.length === 0;
|
|
})(doc);
|
|
|
|
var ignoresCheckedAttribute = doc && (function(document){
|
|
var element = document.createElement('input');
|
|
element.setAttribute('checked', 'checked');
|
|
var clonedElement = element.cloneNode(false);
|
|
return !clonedElement.checked;
|
|
})(doc);
|
|
|
|
var canRemoveSvgViewBoxAttribute = doc && (doc.createElementNS ? (function(document){
|
|
var element = document.createElementNS(svgNamespace, 'svg');
|
|
element.setAttribute('viewBox', '0 0 100 100');
|
|
element.removeAttribute('viewBox');
|
|
return !element.getAttribute('viewBox');
|
|
})(doc) : true);
|
|
|
|
var canClone = doc && (function(document){
|
|
var element = document.createElement('div');
|
|
element.appendChild( document.createTextNode(' '));
|
|
element.appendChild( document.createTextNode(' '));
|
|
var clonedElement = element.cloneNode(true);
|
|
return clonedElement.childNodes[0].nodeValue === ' ';
|
|
})(doc);
|
|
|
|
// This is not the namespace of the element, but of
|
|
// the elements inside that elements.
|
|
function interiorNamespace(element){
|
|
if (
|
|
element &&
|
|
element.namespaceURI === svgNamespace &&
|
|
!svgHTMLIntegrationPoints[element.tagName]
|
|
) {
|
|
return svgNamespace;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// The HTML spec allows for "omitted start tags". These tags are optional
|
|
// when their intended child is the first thing in the parent tag. For
|
|
// example, this is a tbody start tag:
|
|
//
|
|
// <table>
|
|
// <tbody>
|
|
// <tr>
|
|
//
|
|
// The tbody may be omitted, and the browser will accept and render:
|
|
//
|
|
// <table>
|
|
// <tr>
|
|
//
|
|
// However, the omitted start tag will still be added to the DOM. Here
|
|
// we test the string and context to see if the browser is about to
|
|
// perform this cleanup.
|
|
//
|
|
// http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#optional-tags
|
|
// describes which tags are omittable. The spec for tbody and colgroup
|
|
// explains this behavior:
|
|
//
|
|
// http://www.whatwg.org/specs/web-apps/current-work/multipage/tables.html#the-tbody-element
|
|
// http://www.whatwg.org/specs/web-apps/current-work/multipage/tables.html#the-colgroup-element
|
|
//
|
|
|
|
var omittedStartTagChildTest = /<([\w:]+)/;
|
|
function detectOmittedStartTag(string, contextualElement){
|
|
// Omitted start tags are only inside table tags.
|
|
if (contextualElement.tagName === 'TABLE') {
|
|
var omittedStartTagChildMatch = omittedStartTagChildTest.exec(string);
|
|
if (omittedStartTagChildMatch) {
|
|
var omittedStartTagChild = omittedStartTagChildMatch[1];
|
|
// It is already asserted that the contextual element is a table
|
|
// and not the proper start tag. Just see if a tag was omitted.
|
|
return omittedStartTagChild === 'tr' ||
|
|
omittedStartTagChild === 'col';
|
|
}
|
|
}
|
|
}
|
|
|
|
function buildSVGDOM(html, dom){
|
|
var div = dom.document.createElement('div');
|
|
div.innerHTML = '<svg>'+html+'</svg>';
|
|
return div.firstChild.childNodes;
|
|
}
|
|
|
|
/*
|
|
* A class wrapping DOM functions to address environment compatibility,
|
|
* namespaces, contextual elements for morph un-escaped content
|
|
* insertion.
|
|
*
|
|
* When entering a template, a DOMHelper should be passed:
|
|
*
|
|
* template(context, { hooks: hooks, dom: new DOMHelper() });
|
|
*
|
|
* TODO: support foreignObject as a passed contextual element. It has
|
|
* a namespace (svg) that does not match its internal namespace
|
|
* (xhtml).
|
|
*
|
|
* @class DOMHelper
|
|
* @constructor
|
|
* @param {HTMLDocument} _document The document DOM methods are proxied to
|
|
*/
|
|
function DOMHelper(_document){
|
|
this.document = _document || document;
|
|
if (!this.document) {
|
|
throw new Error("A document object must be passed to the DOMHelper, or available on the global scope");
|
|
}
|
|
this.canClone = canClone;
|
|
this.namespace = null;
|
|
}
|
|
|
|
var prototype = DOMHelper.prototype;
|
|
prototype.constructor = DOMHelper;
|
|
|
|
prototype.getElementById = function(id, rootNode) {
|
|
rootNode = rootNode || this.document;
|
|
return rootNode.getElementById(id);
|
|
};
|
|
|
|
prototype.insertBefore = function(element, childElement, referenceChild) {
|
|
return element.insertBefore(childElement, referenceChild);
|
|
};
|
|
|
|
prototype.appendChild = function(element, childElement) {
|
|
return element.appendChild(childElement);
|
|
};
|
|
|
|
prototype.childAt = function(element, indices) {
|
|
var child = element;
|
|
|
|
for (var i = 0; i < indices.length; i++) {
|
|
child = child.childNodes.item(indices[i]);
|
|
}
|
|
|
|
return child;
|
|
};
|
|
|
|
// Note to a Fellow Implementor:
|
|
// Ahh, accessing a child node at an index. Seems like it should be so simple,
|
|
// doesn't it? Unfortunately, this particular method has caused us a surprising
|
|
// amount of pain. As you'll note below, this method has been modified to walk
|
|
// the linked list of child nodes rather than access the child by index
|
|
// directly, even though there are two (2) APIs in the DOM that do this for us.
|
|
// If you're thinking to yourself, "What an oversight! What an opportunity to
|
|
// optimize this code!" then to you I say: stop! For I have a tale to tell.
|
|
//
|
|
// First, this code must be compatible with simple-dom for rendering on the
|
|
// server where there is no real DOM. Previously, we accessed a child node
|
|
// directly via `element.childNodes[index]`. While we *could* in theory do a
|
|
// full-fidelity simulation of a live `childNodes` array, this is slow,
|
|
// complicated and error-prone.
|
|
//
|
|
// "No problem," we thought, "we'll just use the similar
|
|
// `childNodes.item(index)` API." Then, we could just implement our own `item`
|
|
// method in simple-dom and walk the child node linked list there, allowing
|
|
// us to retain the performance advantages of the (surely optimized) `item()`
|
|
// API in the browser.
|
|
//
|
|
// Unfortunately, an enterprising soul named Samy Alzahrani discovered that in
|
|
// IE8, accessing an item out-of-bounds via `item()` causes an exception where
|
|
// other browsers return null. This necessitated a... check of
|
|
// `childNodes.length`, bringing us back around to having to support a
|
|
// full-fidelity `childNodes` array!
|
|
//
|
|
// Worst of all, Kris Selden investigated how browsers are actualy implemented
|
|
// and discovered that they're all linked lists under the hood anyway. Accessing
|
|
// `childNodes` requires them to allocate a new live collection backed by that
|
|
// linked list, which is itself a rather expensive operation. Our assumed
|
|
// optimization had backfired! That is the danger of magical thinking about
|
|
// the performance of native implementations.
|
|
//
|
|
// And this, my friends, is why the following implementation just walks the
|
|
// linked list, as surprised as that may make you. Please ensure you understand
|
|
// the above before changing this and submitting a PR.
|
|
//
|
|
// Tom Dale, January 18th, 2015, Portland OR
|
|
prototype.childAtIndex = function(element, index) {
|
|
var node = element.firstChild;
|
|
|
|
for (var idx = 0; node && idx < index; idx++) {
|
|
node = node.nextSibling;
|
|
}
|
|
|
|
return node;
|
|
};
|
|
|
|
prototype.appendText = function(element, text) {
|
|
return element.appendChild(this.document.createTextNode(text));
|
|
};
|
|
|
|
prototype.setAttribute = function(element, name, value) {
|
|
element.setAttribute(name, String(value));
|
|
};
|
|
|
|
prototype.setAttributeNS = function(element, namespace, name, value) {
|
|
element.setAttributeNS(namespace, name, String(value));
|
|
};
|
|
|
|
if (canRemoveSvgViewBoxAttribute){
|
|
prototype.removeAttribute = function(element, name) {
|
|
element.removeAttribute(name);
|
|
};
|
|
} else {
|
|
prototype.removeAttribute = function(element, name) {
|
|
if (element.tagName === 'svg' && name === 'viewBox') {
|
|
element.setAttribute(name, null);
|
|
} else {
|
|
element.removeAttribute(name);
|
|
}
|
|
};
|
|
}
|
|
|
|
prototype.setPropertyStrict = function(element, name, value) {
|
|
element[name] = value;
|
|
};
|
|
|
|
prototype.setProperty = function(element, name, value, namespace) {
|
|
var lowercaseName = name.toLowerCase();
|
|
if (element.namespaceURI === svgNamespace || lowercaseName === 'style') {
|
|
if (isAttrRemovalValue(value)) {
|
|
element.removeAttribute(name);
|
|
} else {
|
|
if (namespace) {
|
|
element.setAttributeNS(namespace, name, value);
|
|
} else {
|
|
element.setAttribute(name, value);
|
|
}
|
|
}
|
|
} else {
|
|
var normalized = normalizeProperty(element, name);
|
|
if (normalized) {
|
|
element[normalized] = value;
|
|
} else {
|
|
if (isAttrRemovalValue(value)) {
|
|
element.removeAttribute(name);
|
|
} else {
|
|
if (namespace && element.setAttributeNS) {
|
|
element.setAttributeNS(namespace, name, value);
|
|
} else {
|
|
element.setAttribute(name, value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
if (doc && doc.createElementNS) {
|
|
// Only opt into namespace detection if a contextualElement
|
|
// is passed.
|
|
prototype.createElement = function(tagName, contextualElement) {
|
|
var namespace = this.namespace;
|
|
if (contextualElement) {
|
|
if (tagName === 'svg') {
|
|
namespace = svgNamespace;
|
|
} else {
|
|
namespace = interiorNamespace(contextualElement);
|
|
}
|
|
}
|
|
if (namespace) {
|
|
return this.document.createElementNS(namespace, tagName);
|
|
} else {
|
|
return this.document.createElement(tagName);
|
|
}
|
|
};
|
|
prototype.setAttributeNS = function(element, namespace, name, value) {
|
|
element.setAttributeNS(namespace, name, String(value));
|
|
};
|
|
} else {
|
|
prototype.createElement = function(tagName) {
|
|
return this.document.createElement(tagName);
|
|
};
|
|
prototype.setAttributeNS = function(element, namespace, name, value) {
|
|
element.setAttribute(name, String(value));
|
|
};
|
|
}
|
|
|
|
prototype.addClasses = addClasses;
|
|
prototype.removeClasses = removeClasses;
|
|
|
|
prototype.setNamespace = function(ns) {
|
|
this.namespace = ns;
|
|
};
|
|
|
|
prototype.detectNamespace = function(element) {
|
|
this.namespace = interiorNamespace(element);
|
|
};
|
|
|
|
prototype.createDocumentFragment = function(){
|
|
return this.document.createDocumentFragment();
|
|
};
|
|
|
|
prototype.createTextNode = function(text){
|
|
return this.document.createTextNode(text);
|
|
};
|
|
|
|
prototype.createComment = function(text){
|
|
return this.document.createComment(text);
|
|
};
|
|
|
|
prototype.repairClonedNode = function(element, blankChildTextNodes, isChecked){
|
|
if (deletesBlankTextNodes && blankChildTextNodes.length > 0) {
|
|
for (var i=0, len=blankChildTextNodes.length;i<len;i++){
|
|
var textNode = this.document.createTextNode(''),
|
|
offset = blankChildTextNodes[i],
|
|
before = this.childAtIndex(element, offset);
|
|
if (before) {
|
|
element.insertBefore(textNode, before);
|
|
} else {
|
|
element.appendChild(textNode);
|
|
}
|
|
}
|
|
}
|
|
if (ignoresCheckedAttribute && isChecked) {
|
|
element.setAttribute('checked', 'checked');
|
|
}
|
|
};
|
|
|
|
prototype.cloneNode = function(element, deep){
|
|
var clone = element.cloneNode(!!deep);
|
|
return clone;
|
|
};
|
|
|
|
prototype.createAttrMorph = function(element, attrName, namespace){
|
|
return new AttrMorph(element, attrName, this, namespace);
|
|
};
|
|
|
|
prototype.createUnsafeAttrMorph = function(element, attrName, namespace){
|
|
var morph = this.createAttrMorph(element, attrName, namespace);
|
|
morph.escaped = false;
|
|
return morph;
|
|
};
|
|
|
|
prototype.createMorph = function(parent, start, end, contextualElement){
|
|
if (!contextualElement && parent.nodeType === 1) {
|
|
contextualElement = parent;
|
|
}
|
|
return new Morph(parent, start, end, this, contextualElement);
|
|
};
|
|
|
|
prototype.createUnsafeMorph = function(parent, start, end, contextualElement){
|
|
var morph = this.createMorph(parent, start, end, contextualElement);
|
|
morph.escaped = false;
|
|
return morph;
|
|
};
|
|
|
|
// This helper is just to keep the templates good looking,
|
|
// passing integers instead of element references.
|
|
prototype.createMorphAt = function(parent, startIndex, endIndex, contextualElement){
|
|
var start = startIndex === -1 ? null : this.childAtIndex(parent, startIndex),
|
|
end = endIndex === -1 ? null : this.childAtIndex(parent, endIndex);
|
|
return this.createMorph(parent, start, end, contextualElement);
|
|
};
|
|
|
|
prototype.createUnsafeMorphAt = function(parent, startIndex, endIndex, contextualElement) {
|
|
var morph = this.createMorphAt(parent, startIndex, endIndex, contextualElement);
|
|
morph.escaped = false;
|
|
return morph;
|
|
};
|
|
|
|
prototype.insertMorphBefore = function(element, referenceChild, contextualElement) {
|
|
var start = this.document.createTextNode('');
|
|
var end = this.document.createTextNode('');
|
|
element.insertBefore(start, referenceChild);
|
|
element.insertBefore(end, referenceChild);
|
|
return this.createMorph(element, start, end, contextualElement);
|
|
};
|
|
|
|
prototype.appendMorph = function(element, contextualElement) {
|
|
var start = this.document.createTextNode('');
|
|
var end = this.document.createTextNode('');
|
|
element.appendChild(start);
|
|
element.appendChild(end);
|
|
return this.createMorph(element, start, end, contextualElement);
|
|
};
|
|
|
|
prototype.parseHTML = function(html, contextualElement) {
|
|
if (interiorNamespace(contextualElement) === svgNamespace) {
|
|
return buildSVGDOM(html, this);
|
|
} else {
|
|
var nodes = buildHTMLDOM(html, contextualElement, this);
|
|
if (detectOmittedStartTag(html, contextualElement)) {
|
|
var node = nodes[0];
|
|
while (node && node.nodeType !== 1) {
|
|
node = node.nextSibling;
|
|
}
|
|
return node.childNodes;
|
|
} else {
|
|
return nodes;
|
|
}
|
|
}
|
|
};
|
|
|
|
var parsingNode;
|
|
|
|
// Used to determine whether a URL needs to be sanitized.
|
|
prototype.protocolForURL = function(url) {
|
|
if (!parsingNode) {
|
|
parsingNode = this.document.createElement('a');
|
|
}
|
|
|
|
parsingNode.href = url;
|
|
return parsingNode.protocol;
|
|
};
|
|
|
|
__exports__["default"] = DOMHelper;
|
|
});
|
|
enifed("morph/dom-helper/build-html-dom",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
/* global XMLSerializer:false */
|
|
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 = "<div></div>";
|
|
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 = "<div></div>";
|
|
testEl.firstChild.innerHTML = "<script><\/script>";
|
|
return testEl.firstChild.innerHTML === '';
|
|
})(doc);
|
|
|
|
// IE 8 (and likely earlier) likes to move whitespace preceeding
|
|
// a script tag to appear after it. This means that we can
|
|
// accidentally remove whitespace when updating a morph.
|
|
var movesWhitespace = doc && (function(document) {
|
|
var testEl = document.createElement('div');
|
|
testEl.innerHTML = "Test: <script type='text/x-placeholder'><\/script>Value";
|
|
return testEl.childNodes[0].nodeValue === 'Test:' &&
|
|
testEl.childNodes[2].nodeValue === ' Value';
|
|
})(doc);
|
|
|
|
// IE8 create a selected attribute where they should only
|
|
// create a property
|
|
var createsSelectedAttribute = doc && (function(document) {
|
|
var testEl = document.createElement('div');
|
|
testEl.innerHTML = "<select><option></option></select>";
|
|
return testEl.childNodes[0].childNodes[0].getAttribute('selected') === 'selected';
|
|
})(doc);
|
|
|
|
var detectAutoSelectedOption;
|
|
if (createsSelectedAttribute) {
|
|
detectAutoSelectedOption = (function(){
|
|
var detectAutoSelectedOptionRegex = /<option[^>]*selected/;
|
|
return function detectAutoSelectedOption(select, option, html) { //jshint ignore:line
|
|
return select.selectedIndex === 0 &&
|
|
!detectAutoSelectedOptionRegex.test(html);
|
|
};
|
|
})();
|
|
} else {
|
|
detectAutoSelectedOption = function detectAutoSelectedOption(select, option, html) { //jshint ignore:line
|
|
var selectedAttribute = option.getAttribute('selected');
|
|
return select.selectedIndex === 0 && (
|
|
selectedAttribute === null ||
|
|
( selectedAttribute !== '' && selectedAttribute.toLowerCase() !== 'selected' )
|
|
);
|
|
};
|
|
}
|
|
|
|
var tagNamesRequiringInnerHTMLFix = doc && (function(document) {
|
|
var tagNamesRequiringInnerHTMLFix;
|
|
// IE 9 and earlier don't allow us to set innerHTML on col, colgroup, frameset,
|
|
// html, style, table, tbody, tfoot, thead, title, tr. Detect this and add
|
|
// them to an initial list of corrected tags.
|
|
//
|
|
// Here we are only dealing with the ones which can have child nodes.
|
|
//
|
|
var tableNeedsInnerHTMLFix;
|
|
var tableInnerHTMLTestElement = document.createElement('table');
|
|
try {
|
|
tableInnerHTMLTestElement.innerHTML = '<tbody></tbody>';
|
|
} catch (e) {
|
|
} finally {
|
|
tableNeedsInnerHTMLFix = (tableInnerHTMLTestElement.childNodes.length === 0);
|
|
}
|
|
if (tableNeedsInnerHTMLFix) {
|
|
tagNamesRequiringInnerHTMLFix = {
|
|
colgroup: ['table'],
|
|
table: [],
|
|
tbody: ['table'],
|
|
tfoot: ['table'],
|
|
thead: ['table'],
|
|
tr: ['table', 'tbody']
|
|
};
|
|
}
|
|
|
|
// IE 8 doesn't allow setting innerHTML on a select tag. Detect this and
|
|
// add it to the list of corrected tags.
|
|
//
|
|
var selectInnerHTMLTestElement = document.createElement('select');
|
|
selectInnerHTMLTestElement.innerHTML = '<option></option>';
|
|
if (!selectInnerHTMLTestElement.childNodes[0]) {
|
|
tagNamesRequiringInnerHTMLFix = tagNamesRequiringInnerHTMLFix || {};
|
|
tagNamesRequiringInnerHTMLFix.select = [];
|
|
}
|
|
return tagNamesRequiringInnerHTMLFix;
|
|
})(doc);
|
|
|
|
function scriptSafeInnerHTML(element, html) {
|
|
// without a leading text node, IE will drop a leading script tag.
|
|
html = '­'+html;
|
|
|
|
element.innerHTML = html;
|
|
|
|
var nodes = element.childNodes;
|
|
|
|
// Look for ­ to remove it.
|
|
var shyElement = nodes[0];
|
|
while (shyElement.nodeType === 1 && !shyElement.nodeName) {
|
|
shyElement = shyElement.firstChild;
|
|
}
|
|
// At this point it's the actual unicode character.
|
|
if (shyElement.nodeType === 3 && shyElement.nodeValue.charAt(0) === "\u00AD") {
|
|
var newValue = shyElement.nodeValue.slice(1);
|
|
if (newValue.length) {
|
|
shyElement.nodeValue = shyElement.nodeValue.slice(1);
|
|
} else {
|
|
shyElement.parentNode.removeChild(shyElement);
|
|
}
|
|
}
|
|
|
|
return nodes;
|
|
}
|
|
|
|
function buildDOMWithFix(html, contextualElement){
|
|
var tagName = contextualElement.tagName;
|
|
|
|
// Firefox versions < 11 do not have support for element.outerHTML.
|
|
var outerHTML = contextualElement.outerHTML || new XMLSerializer().serializeToString(contextualElement);
|
|
if (!outerHTML) {
|
|
throw "Can't set innerHTML on "+tagName+" in this browser";
|
|
}
|
|
|
|
var wrappingTags = tagNamesRequiringInnerHTMLFix[tagName.toLowerCase()];
|
|
var startTag = outerHTML.match(new RegExp("<"+tagName+"([^>]*)>", 'i'))[0];
|
|
var endTag = '</'+tagName+'>';
|
|
|
|
var wrappedHTML = [startTag, html, endTag];
|
|
|
|
var i = wrappingTags.length;
|
|
var wrappedDepth = 1 + i;
|
|
while(i--) {
|
|
wrappedHTML.unshift('<'+wrappingTags[i]+'>');
|
|
wrappedHTML.push('</'+wrappingTags[i]+'>');
|
|
}
|
|
|
|
var wrapper = document.createElement('div');
|
|
scriptSafeInnerHTML(wrapper, wrappedHTML.join(''));
|
|
var element = wrapper;
|
|
while (wrappedDepth--) {
|
|
element = element.firstChild;
|
|
while (element && element.nodeType !== 1) {
|
|
element = element.nextSibling;
|
|
}
|
|
}
|
|
while (element && element.tagName !== tagName) {
|
|
element = element.nextSibling;
|
|
}
|
|
return element ? element.childNodes : [];
|
|
}
|
|
|
|
var buildDOM;
|
|
if (needsShy) {
|
|
buildDOM = function buildDOM(html, contextualElement, dom){
|
|
contextualElement = dom.cloneNode(contextualElement, false);
|
|
scriptSafeInnerHTML(contextualElement, html);
|
|
return contextualElement.childNodes;
|
|
};
|
|
} else {
|
|
buildDOM = function buildDOM(html, contextualElement, dom){
|
|
contextualElement = dom.cloneNode(contextualElement, false);
|
|
contextualElement.innerHTML = html;
|
|
return contextualElement.childNodes;
|
|
};
|
|
}
|
|
|
|
var buildIESafeDOM;
|
|
if (tagNamesRequiringInnerHTMLFix || movesWhitespace) {
|
|
buildIESafeDOM = function buildIESafeDOM(html, contextualElement, dom) {
|
|
// Make a list of the leading text on script nodes. Include
|
|
// script tags without any whitespace for easier processing later.
|
|
var spacesBefore = [];
|
|
var spacesAfter = [];
|
|
if (typeof html === 'string') {
|
|
html = html.replace(/(\s*)(<script)/g, function(match, spaces, tag) {
|
|
spacesBefore.push(spaces);
|
|
return tag;
|
|
});
|
|
|
|
html = html.replace(/(<\/script>)(\s*)/g, function(match, tag, spaces) {
|
|
spacesAfter.push(spaces);
|
|
return tag;
|
|
});
|
|
}
|
|
|
|
// Fetch nodes
|
|
var nodes;
|
|
if (tagNamesRequiringInnerHTMLFix[contextualElement.tagName.toLowerCase()]) {
|
|
// buildDOMWithFix uses string wrappers for problematic innerHTML.
|
|
nodes = buildDOMWithFix(html, contextualElement);
|
|
} else {
|
|
nodes = buildDOM(html, contextualElement, dom);
|
|
}
|
|
|
|
// Build a list of script tags, the nodes themselves will be
|
|
// mutated as we add test nodes.
|
|
var i, j, node, nodeScriptNodes;
|
|
var scriptNodes = [];
|
|
for (i=0;i<nodes.length;i++) {
|
|
node=nodes[i];
|
|
if (node.nodeType !== 1) {
|
|
continue;
|
|
}
|
|
if (node.tagName === 'SCRIPT') {
|
|
scriptNodes.push(node);
|
|
} else {
|
|
nodeScriptNodes = node.getElementsByTagName('script');
|
|
for (j=0;j<nodeScriptNodes.length;j++) {
|
|
scriptNodes.push(nodeScriptNodes[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Walk the script tags and put back their leading text nodes.
|
|
var scriptNode, textNode, spaceBefore, spaceAfter;
|
|
for (i=0;i<scriptNodes.length;i++) {
|
|
scriptNode = scriptNodes[i];
|
|
spaceBefore = spacesBefore[i];
|
|
if (spaceBefore && spaceBefore.length > 0) {
|
|
textNode = dom.document.createTextNode(spaceBefore);
|
|
scriptNode.parentNode.insertBefore(textNode, scriptNode);
|
|
}
|
|
|
|
spaceAfter = spacesAfter[i];
|
|
if (spaceAfter && spaceAfter.length > 0) {
|
|
textNode = dom.document.createTextNode(spaceAfter);
|
|
scriptNode.parentNode.insertBefore(textNode, scriptNode.nextSibling);
|
|
}
|
|
}
|
|
|
|
return nodes;
|
|
};
|
|
} else {
|
|
buildIESafeDOM = buildDOM;
|
|
}
|
|
|
|
// When parsing innerHTML, the browser may set up DOM with some things
|
|
// not desired. For example, with a select element context and option
|
|
// innerHTML the first option will be marked selected.
|
|
//
|
|
// This method cleans up some of that, resetting those values back to
|
|
// their defaults.
|
|
//
|
|
function buildSafeDOM(html, contextualElement, dom) {
|
|
var childNodes = buildIESafeDOM(html, contextualElement, dom);
|
|
|
|
if (contextualElement.tagName === 'SELECT') {
|
|
// Walk child nodes
|
|
for (var i = 0; childNodes[i]; i++) {
|
|
// Find and process the first option child node
|
|
if (childNodes[i].tagName === 'OPTION') {
|
|
if (detectAutoSelectedOption(childNodes[i].parentNode, childNodes[i], html)) {
|
|
// If the first node is selected but does not have an attribute,
|
|
// presume it is not really selected.
|
|
childNodes[i].parentNode.selectedIndex = -1;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return childNodes;
|
|
}
|
|
|
|
var buildHTMLDOM;
|
|
if (needsIntegrationPointFix) {
|
|
buildHTMLDOM = function buildHTMLDOM(html, contextualElement, dom){
|
|
if (svgHTMLIntegrationPoints[contextualElement.tagName]) {
|
|
return buildSafeDOM(html, document.createElement('div'), dom);
|
|
} else {
|
|
return buildSafeDOM(html, contextualElement, dom);
|
|
}
|
|
};
|
|
} else {
|
|
buildHTMLDOM = buildSafeDOM;
|
|
}
|
|
|
|
__exports__.buildHTMLDOM = buildHTMLDOM;
|
|
});
|
|
enifed("morph/dom-helper/classes",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
var doc = typeof document === 'undefined' ? false : document;
|
|
|
|
// PhantomJS has a broken classList. See https://github.com/ariya/phantomjs/issues/12782
|
|
var canClassList = doc && (function(){
|
|
var d = document.createElement('div');
|
|
if (!d.classList) {
|
|
return false;
|
|
}
|
|
d.classList.add('boo');
|
|
d.classList.add('boo', 'baz');
|
|
return (d.className === 'boo baz');
|
|
})();
|
|
|
|
function buildClassList(element) {
|
|
var classString = (element.getAttribute('class') || '');
|
|
return classString !== '' && classString !== ' ' ? classString.split(' ') : [];
|
|
}
|
|
|
|
function intersect(containingArray, valuesArray) {
|
|
var containingIndex = 0;
|
|
var containingLength = containingArray.length;
|
|
var valuesIndex = 0;
|
|
var valuesLength = valuesArray.length;
|
|
|
|
var intersection = new Array(valuesLength);
|
|
|
|
// TODO: rewrite this loop in an optimal manner
|
|
for (;containingIndex<containingLength;containingIndex++) {
|
|
valuesIndex = 0;
|
|
for (;valuesIndex<valuesLength;valuesIndex++) {
|
|
if (valuesArray[valuesIndex] === containingArray[containingIndex]) {
|
|
intersection[valuesIndex] = containingIndex;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return intersection;
|
|
}
|
|
|
|
function addClassesViaAttribute(element, classNames) {
|
|
var existingClasses = buildClassList(element);
|
|
|
|
var indexes = intersect(existingClasses, classNames);
|
|
var didChange = false;
|
|
|
|
for (var i=0, l=classNames.length; i<l; i++) {
|
|
if (indexes[i] === undefined) {
|
|
didChange = true;
|
|
existingClasses.push(classNames[i]);
|
|
}
|
|
}
|
|
|
|
if (didChange) {
|
|
element.setAttribute('class', existingClasses.length > 0 ? existingClasses.join(' ') : '');
|
|
}
|
|
}
|
|
|
|
function removeClassesViaAttribute(element, classNames) {
|
|
var existingClasses = buildClassList(element);
|
|
|
|
var indexes = intersect(classNames, existingClasses);
|
|
var didChange = false;
|
|
var newClasses = [];
|
|
|
|
for (var i=0, l=existingClasses.length; i<l; i++) {
|
|
if (indexes[i] === undefined) {
|
|
newClasses.push(existingClasses[i]);
|
|
} else {
|
|
didChange = true;
|
|
}
|
|
}
|
|
|
|
if (didChange) {
|
|
element.setAttribute('class', newClasses.length > 0 ? newClasses.join(' ') : '');
|
|
}
|
|
}
|
|
|
|
var addClasses, removeClasses;
|
|
if (canClassList) {
|
|
addClasses = function addClasses(element, classNames) {
|
|
if (element.classList) {
|
|
if (classNames.length === 1) {
|
|
element.classList.add(classNames[0]);
|
|
} else if (classNames.length === 2) {
|
|
element.classList.add(classNames[0], classNames[1]);
|
|
} else {
|
|
element.classList.add.apply(element.classList, classNames);
|
|
}
|
|
} else {
|
|
addClassesViaAttribute(element, classNames);
|
|
}
|
|
};
|
|
removeClasses = function removeClasses(element, classNames) {
|
|
if (element.classList) {
|
|
if (classNames.length === 1) {
|
|
element.classList.remove(classNames[0]);
|
|
} else if (classNames.length === 2) {
|
|
element.classList.remove(classNames[0], classNames[1]);
|
|
} else {
|
|
element.classList.remove.apply(element.classList, classNames);
|
|
}
|
|
} else {
|
|
removeClassesViaAttribute(element, classNames);
|
|
}
|
|
};
|
|
} else {
|
|
addClasses = addClassesViaAttribute;
|
|
removeClasses = removeClassesViaAttribute;
|
|
}
|
|
|
|
__exports__.addClasses = addClasses;
|
|
__exports__.removeClasses = removeClasses;
|
|
});
|
|
enifed("morph/dom-helper/prop",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
function isAttrRemovalValue(value) {
|
|
return value === null || value === undefined;
|
|
}
|
|
|
|
__exports__.isAttrRemovalValue = isAttrRemovalValue;// TODO should this be an o_create kind of thing?
|
|
var propertyCaches = {};
|
|
__exports__.propertyCaches = propertyCaches;
|
|
function normalizeProperty(element, attrName) {
|
|
var tagName = element.tagName;
|
|
var key;
|
|
var cache = propertyCaches[tagName];
|
|
if (!cache) {
|
|
// TODO should this be an o_create kind of thing?
|
|
cache = {};
|
|
for (key in element) {
|
|
cache[key.toLowerCase()] = key;
|
|
}
|
|
propertyCaches[tagName] = cache;
|
|
}
|
|
|
|
// presumes that the attrName has been lowercased.
|
|
return cache[attrName];
|
|
}
|
|
|
|
__exports__.normalizeProperty = normalizeProperty;
|
|
});
|
|
enifed("morph/morph",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
var splice = Array.prototype.splice;
|
|
|
|
function ensureStartEnd(start, end) {
|
|
if (start === null || end === null) {
|
|
throw new Error('a fragment parent must have boundary nodes in order to detect insertion');
|
|
}
|
|
}
|
|
|
|
function ensureContext(contextualElement) {
|
|
if (!contextualElement || contextualElement.nodeType !== 1) {
|
|
throw new Error('An element node must be provided for a contextualElement, you provided ' +
|
|
(contextualElement ? 'nodeType ' + contextualElement.nodeType : 'nothing'));
|
|
}
|
|
}
|
|
|
|
// TODO: this is an internal API, this should be an assert
|
|
function Morph(parent, start, end, domHelper, contextualElement) {
|
|
if (parent.nodeType === 11) {
|
|
ensureStartEnd(start, end);
|
|
this.element = null;
|
|
} else {
|
|
this.element = parent;
|
|
}
|
|
this._parent = parent;
|
|
this.start = start;
|
|
this.end = end;
|
|
this.domHelper = domHelper;
|
|
ensureContext(contextualElement);
|
|
this.contextualElement = contextualElement;
|
|
this.escaped = true;
|
|
this.reset();
|
|
}
|
|
|
|
Morph.prototype.reset = function() {
|
|
this.text = null;
|
|
this.owner = null;
|
|
this.morphs = null;
|
|
this.before = null;
|
|
this.after = null;
|
|
};
|
|
|
|
Morph.prototype.parent = function () {
|
|
if (!this.element) {
|
|
var parent = this.start.parentNode;
|
|
if (this._parent !== parent) {
|
|
this._parent = parent;
|
|
}
|
|
if (parent.nodeType === 1) {
|
|
this.element = parent;
|
|
}
|
|
}
|
|
return this._parent;
|
|
};
|
|
|
|
Morph.prototype.destroy = function () {
|
|
if (this.owner) {
|
|
this.owner.removeMorph(this);
|
|
} else {
|
|
clear(this.element || this.parent(), this.start, this.end);
|
|
}
|
|
};
|
|
|
|
Morph.prototype.removeMorph = function (morph) {
|
|
var morphs = this.morphs;
|
|
for (var i=0, l=morphs.length; i<l; i++) {
|
|
if (morphs[i] === morph) {
|
|
this.replace(i, 1);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
Morph.prototype.setContent = function (nodeOrString) {
|
|
this._update(this.element || this.parent(), nodeOrString);
|
|
};
|
|
|
|
Morph.prototype.updateNode = function (node) {
|
|
var parent = this.element || this.parent();
|
|
if (!node) {
|
|
return this._updateText(parent, '');
|
|
}
|
|
this._updateNode(parent, node);
|
|
};
|
|
|
|
Morph.prototype.updateText = function (text) {
|
|
this._updateText(this.element || this.parent(), text);
|
|
};
|
|
|
|
Morph.prototype.updateHTML = function (html) {
|
|
var parent = this.element || this.parent();
|
|
if (!html) {
|
|
return this._updateText(parent, '');
|
|
}
|
|
this._updateHTML(parent, html);
|
|
};
|
|
|
|
Morph.prototype._update = function (parent, nodeOrString) {
|
|
if (nodeOrString === null || nodeOrString === undefined) {
|
|
this._updateText(parent, '');
|
|
} else if (typeof nodeOrString === 'string') {
|
|
if (this.escaped) {
|
|
this._updateText(parent, nodeOrString);
|
|
} else {
|
|
this._updateHTML(parent, nodeOrString);
|
|
}
|
|
} else if (nodeOrString.nodeType) {
|
|
this._updateNode(parent, nodeOrString);
|
|
} else if (nodeOrString.string) { // duck typed SafeString
|
|
this._updateHTML(parent, nodeOrString.string);
|
|
} else {
|
|
this._updateText(parent, nodeOrString.toString());
|
|
}
|
|
};
|
|
|
|
Morph.prototype._updateNode = function (parent, node) {
|
|
if (this.text) {
|
|
if (node.nodeType === 3) {
|
|
this.text.nodeValue = node.nodeValue;
|
|
return;
|
|
} else {
|
|
this.text = null;
|
|
}
|
|
}
|
|
var start = this.start, end = this.end;
|
|
clear(parent, start, end);
|
|
parent.insertBefore(node, end);
|
|
if (this.before !== null) {
|
|
this.before.end = start.nextSibling;
|
|
}
|
|
if (this.after !== null) {
|
|
this.after.start = end.previousSibling;
|
|
}
|
|
};
|
|
|
|
Morph.prototype._updateText = function (parent, text) {
|
|
if (this.text) {
|
|
this.text.nodeValue = text;
|
|
return;
|
|
}
|
|
var node = this.domHelper.createTextNode(text);
|
|
this.text = node;
|
|
clear(parent, this.start, this.end);
|
|
parent.insertBefore(node, this.end);
|
|
if (this.before !== null) {
|
|
this.before.end = node;
|
|
}
|
|
if (this.after !== null) {
|
|
this.after.start = node;
|
|
}
|
|
};
|
|
|
|
Morph.prototype._updateHTML = function (parent, html) {
|
|
var start = this.start, end = this.end;
|
|
clear(parent, start, end);
|
|
this.text = null;
|
|
var childNodes = this.domHelper.parseHTML(html, this.contextualElement);
|
|
appendChildren(parent, end, childNodes);
|
|
if (this.before !== null) {
|
|
this.before.end = start.nextSibling;
|
|
}
|
|
if (this.after !== null) {
|
|
this.after.start = end.previousSibling;
|
|
}
|
|
};
|
|
|
|
Morph.prototype.append = function (node) {
|
|
if (this.morphs === null) {
|
|
this.morphs = [];
|
|
}
|
|
var index = this.morphs.length;
|
|
return this.insert(index, node);
|
|
};
|
|
|
|
Morph.prototype.insert = function (index, node) {
|
|
if (this.morphs === null) {
|
|
this.morphs = [];
|
|
}
|
|
var parent = this.element || this.parent();
|
|
var morphs = this.morphs;
|
|
var before = index > 0 ? morphs[index-1] : null;
|
|
var after = index < morphs.length ? morphs[index] : null;
|
|
var start = before === null ? this.start : (before.end === null ? parent.lastChild : before.end.previousSibling);
|
|
var end = after === null ? this.end : (after.start === null ? parent.firstChild : after.start.nextSibling);
|
|
var morph = new Morph(parent, start, end, this.domHelper, this.contextualElement);
|
|
|
|
morph.owner = this;
|
|
morph._update(parent, node);
|
|
|
|
if (before !== null) {
|
|
morph.before = before;
|
|
before.end = start.nextSibling;
|
|
before.after = morph;
|
|
}
|
|
|
|
if (after !== null) {
|
|
morph.after = after;
|
|
after.before = morph;
|
|
after.start = end.previousSibling;
|
|
}
|
|
|
|
this.morphs.splice(index, 0, morph);
|
|
return morph;
|
|
};
|
|
|
|
Morph.prototype.replace = function (index, removedLength, addedNodes) {
|
|
if (this.morphs === null) {
|
|
this.morphs = [];
|
|
}
|
|
var parent = this.element || this.parent();
|
|
var morphs = this.morphs;
|
|
var before = index > 0 ? morphs[index-1] : null;
|
|
var after = index+removedLength < morphs.length ? morphs[index+removedLength] : null;
|
|
var start = before === null ? this.start : (before.end === null ? parent.lastChild : before.end.previousSibling);
|
|
var end = after === null ? this.end : (after.start === null ? parent.firstChild : after.start.nextSibling);
|
|
var addedLength = addedNodes === undefined ? 0 : addedNodes.length;
|
|
var args, i, current;
|
|
|
|
if (removedLength > 0) {
|
|
clear(parent, start, end);
|
|
}
|
|
|
|
if (addedLength === 0) {
|
|
if (before !== null) {
|
|
before.after = after;
|
|
before.end = end;
|
|
}
|
|
if (after !== null) {
|
|
after.before = before;
|
|
after.start = start;
|
|
}
|
|
morphs.splice(index, removedLength);
|
|
return;
|
|
}
|
|
|
|
args = new Array(addedLength+2);
|
|
if (addedLength > 0) {
|
|
for (i=0; i<addedLength; i++) {
|
|
args[i+2] = current = new Morph(parent, start, end, this.domHelper, this.contextualElement);
|
|
current._update(parent, addedNodes[i]);
|
|
current.owner = this;
|
|
if (before !== null) {
|
|
current.before = before;
|
|
before.end = start.nextSibling;
|
|
before.after = current;
|
|
}
|
|
before = current;
|
|
start = end === null ? parent.lastChild : end.previousSibling;
|
|
}
|
|
if (after !== null) {
|
|
current.after = after;
|
|
after.before = current;
|
|
after.start = end.previousSibling;
|
|
}
|
|
}
|
|
|
|
args[0] = index;
|
|
args[1] = removedLength;
|
|
|
|
splice.apply(morphs, args);
|
|
};
|
|
|
|
function appendChildren(parent, end, nodeList) {
|
|
var ref = end;
|
|
var i = nodeList.length;
|
|
var node;
|
|
|
|
while (i--) {
|
|
node = nodeList[i];
|
|
parent.insertBefore(node, ref);
|
|
ref = node;
|
|
}
|
|
}
|
|
|
|
function clear(parent, start, end) {
|
|
var current, previous;
|
|
if (end === null) {
|
|
current = parent.lastChild;
|
|
} else {
|
|
current = end.previousSibling;
|
|
}
|
|
|
|
while (current !== null && current !== start) {
|
|
previous = current.previousSibling;
|
|
parent.removeChild(current);
|
|
current = previous;
|
|
}
|
|
}
|
|
|
|
__exports__["default"] = Morph;
|
|
});
|
|
enifed("route-recognizer",
|
|
["./route-recognizer/dsl","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var map = __dependency1__["default"];
|
|
|
|
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() + " <then> " + 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 = map;
|
|
|
|
RouteRecognizer.VERSION = '1.10.1';
|
|
|
|
__exports__["default"] = RouteRecognizer;
|
|
});
|
|
enifed("route-recognizer.umd",
|
|
["./route-recognizer"],
|
|
function(__dependency1__) {
|
|
"use strict";
|
|
var RouteRecognizer = __dependency1__["default"];
|
|
|
|
/* global define:true module:true window: true */
|
|
if (typeof enifed === 'function' && enifed['amd']) {
|
|
enifed(function() { return RouteRecognizer; });
|
|
} else if (typeof module !== 'undefined' && module['exports']) {
|
|
module['exports'] = RouteRecognizer;
|
|
} else if (typeof this !== 'undefined') {
|
|
this['RouteRecognizer'] = RouteRecognizer;
|
|
}
|
|
});
|
|
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("router",
|
|
["./router/router","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var Router = __dependency1__["default"];
|
|
|
|
__exports__["default"] = Router;
|
|
});
|
|
enifed("router/handler-info",
|
|
["./utils","rsvp/promise","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var bind = __dependency1__.bind;
|
|
var merge = __dependency1__.merge;
|
|
var serialize = __dependency1__.serialize;
|
|
var promiseLabel = __dependency1__.promiseLabel;
|
|
var applyHook = __dependency1__.applyHook;
|
|
var Promise = __dependency2__["default"];
|
|
|
|
function HandlerInfo(_props) {
|
|
var props = _props || {};
|
|
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 promiseLabel("'" + this.name + "' " + label);
|
|
},
|
|
|
|
getUnresolved: function() {
|
|
return this;
|
|
},
|
|
|
|
serialize: function() {
|
|
return this.params || {};
|
|
},
|
|
|
|
resolve: function(shouldContinue, payload) {
|
|
var checkForAbort = bind(this, this.checkForAbort, shouldContinue),
|
|
beforeModel = bind(this, this.runBeforeModelHook, payload),
|
|
model = bind(this, this.getModel, payload),
|
|
afterModel = bind(this, this.runAfterModelHook, payload),
|
|
becomeResolved = bind(this, this.becomeResolved, payload);
|
|
|
|
return Promise.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 = applyHook(this.handler, hookName, args);
|
|
|
|
if (result && result.isTransition) {
|
|
result = null;
|
|
}
|
|
|
|
return Promise.resolve(result, this.promiseLabel("Resolve value returned from one of the model hooks"));
|
|
},
|
|
|
|
// overridden by subclasses
|
|
getModel: null,
|
|
|
|
checkForAbort: function(shouldContinue, promiseValue) {
|
|
return Promise.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/handler-info/factory",
|
|
["router/handler-info/resolved-handler-info","router/handler-info/unresolved-handler-info-by-object","router/handler-info/unresolved-handler-info-by-param","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
var ResolvedHandlerInfo = __dependency1__["default"];
|
|
var UnresolvedHandlerInfoByObject = __dependency2__["default"];
|
|
var UnresolvedHandlerInfoByParam = __dependency3__["default"];
|
|
|
|
handlerInfoFactory.klasses = {
|
|
resolved: ResolvedHandlerInfo,
|
|
param: UnresolvedHandlerInfoByParam,
|
|
object: UnresolvedHandlerInfoByObject
|
|
};
|
|
|
|
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",
|
|
["../handler-info","router/utils","rsvp/promise","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
var HandlerInfo = __dependency1__["default"];
|
|
var subclass = __dependency2__.subclass;
|
|
var promiseLabel = __dependency2__.promiseLabel;
|
|
var Promise = __dependency3__["default"];
|
|
|
|
var ResolvedHandlerInfo = subclass(HandlerInfo, {
|
|
resolve: function(shouldContinue, payload) {
|
|
// A ResolvedHandlerInfo just resolved with itself.
|
|
if (payload && payload.resolvedModels) {
|
|
payload.resolvedModels[this.name] = this.context;
|
|
}
|
|
return Promise.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",
|
|
["../handler-info","router/utils","rsvp/promise","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
var HandlerInfo = __dependency1__["default"];
|
|
var merge = __dependency2__.merge;
|
|
var subclass = __dependency2__.subclass;
|
|
var promiseLabel = __dependency2__.promiseLabel;
|
|
var isParam = __dependency2__.isParam;
|
|
var Promise = __dependency3__["default"];
|
|
|
|
var UnresolvedHandlerInfoByObject = subclass(HandlerInfo, {
|
|
getModel: function(payload) {
|
|
this.log(payload, this.name + ": resolving provided model");
|
|
return Promise.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 (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",
|
|
["../handler-info","router/utils","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var HandlerInfo = __dependency1__["default"];
|
|
var resolveHook = __dependency2__.resolveHook;
|
|
var merge = __dependency2__.merge;
|
|
var subclass = __dependency2__.subclass;
|
|
var promiseLabel = __dependency2__.promiseLabel;
|
|
|
|
// Generated by URL transitions and non-dynamic route segments in named Transitions.
|
|
var UnresolvedHandlerInfoByParam = subclass (HandlerInfo, {
|
|
initialize: function(props) {
|
|
this.params = props.params || {};
|
|
},
|
|
|
|
getModel: function(payload) {
|
|
var fullParams = this.params;
|
|
if (payload && payload.queryParams) {
|
|
fullParams = {};
|
|
merge(fullParams, this.params);
|
|
fullParams.queryParams = payload.queryParams;
|
|
}
|
|
|
|
var handler = this.handler;
|
|
var hookName = resolveHook(handler, 'deserialize') ||
|
|
resolveHook(handler, 'model');
|
|
|
|
return this.runSharedModelHook(payload, hookName, [fullParams]);
|
|
}
|
|
});
|
|
|
|
__exports__["default"] = UnresolvedHandlerInfoByParam;
|
|
});
|
|
enifed("router/router",
|
|
["route-recognizer","rsvp/promise","./utils","./transition-state","./transition","./transition-intent/named-transition-intent","./transition-intent/url-transition-intent","./handler-info","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) {
|
|
"use strict";
|
|
var RouteRecognizer = __dependency1__["default"];
|
|
var Promise = __dependency2__["default"];
|
|
var trigger = __dependency3__.trigger;
|
|
var log = __dependency3__.log;
|
|
var slice = __dependency3__.slice;
|
|
var forEach = __dependency3__.forEach;
|
|
var merge = __dependency3__.merge;
|
|
var serialize = __dependency3__.serialize;
|
|
var extractQueryParams = __dependency3__.extractQueryParams;
|
|
var getChangelist = __dependency3__.getChangelist;
|
|
var promiseLabel = __dependency3__.promiseLabel;
|
|
var callHook = __dependency3__.callHook;
|
|
var TransitionState = __dependency4__["default"];
|
|
var logAbort = __dependency5__.logAbort;
|
|
var Transition = __dependency5__.Transition;
|
|
var TransitionAborted = __dependency5__.TransitionAborted;
|
|
var NamedTransitionIntent = __dependency6__["default"];
|
|
var URLTransitionIntent = __dependency7__["default"];
|
|
var ResolvedHandlerInfo = __dependency8__.ResolvedHandlerInfo;
|
|
|
|
var pop = Array.prototype.pop;
|
|
|
|
function Router() {
|
|
this.recognizer = new RouteRecognizer();
|
|
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 = 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 new Transition(this);
|
|
}
|
|
|
|
if (isIntermediate) {
|
|
setupContexts(this, newState);
|
|
return;
|
|
}
|
|
|
|
// Create a new transition to the destination route.
|
|
newTransition = new 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, 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);
|
|
},
|
|
|
|
queryParamsTransition: function(changelist, wasTransitioning, oldState, newState) {
|
|
var router = this;
|
|
|
|
fireQueryParamDidChange(this, newState, changelist);
|
|
|
|
if (!wasTransitioning && this.activeTransition) {
|
|
// One of the handlers in queryParamsDidChange
|
|
// caused a transition. Just return that transition.
|
|
return this.activeTransition;
|
|
} else {
|
|
// Running queryParamsDidChange didn't change anything.
|
|
// Just update query params and be on our way.
|
|
|
|
// We have to return a noop transition that will
|
|
// perform a URL update at the end. This gives
|
|
// the user the ability to set the url update
|
|
// method (default is replaceState).
|
|
var newTransition = new Transition(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, promiseLabel("Transition complete"));
|
|
return newTransition;
|
|
}
|
|
},
|
|
|
|
// NOTE: this doesn't really belong here, but here
|
|
// it shall remain until our ES6 transpiler can
|
|
// handle cyclical deps.
|
|
transitionByIntent: function(intent, isIntermediate) {
|
|
try {
|
|
return getTransitionByIntent.apply(this, arguments);
|
|
} catch(e) {
|
|
return new Transition(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) {
|
|
forEach(this.state.handlerInfos.slice().reverse(), function(handlerInfo) {
|
|
var handler = handlerInfo.handler;
|
|
callHook(handler, 'exit');
|
|
});
|
|
}
|
|
|
|
this.state = new TransitionState();
|
|
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 = 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 || {};
|
|
}
|
|
|
|
log(this, "Starting a refresh transition");
|
|
var intent = new NamedTransitionIntent({
|
|
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 = extractQueryParams(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 NamedTransitionIntent({ 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();
|
|
merge(params, handlerParams);
|
|
}
|
|
params.queryParams = queryParams;
|
|
|
|
return this.recognizer.generate(handlerName, params);
|
|
},
|
|
|
|
applyIntent: function(handlerName, contexts) {
|
|
var intent = new NamedTransitionIntent({
|
|
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) {
|
|
var targetHandlerInfos = this.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 state = new TransitionState();
|
|
state.handlerInfos = targetHandlerInfos.slice(0, index + 1);
|
|
recogHandlers = recogHandlers.slice(0, index + 1);
|
|
|
|
var intent = new NamedTransitionIntent({
|
|
name: targetHandler,
|
|
contexts: contexts
|
|
});
|
|
|
|
var newState = intent.applyToHandlers(state, recogHandlers, this.getHandler, targetHandler, true, true);
|
|
|
|
var handlersEqual = handlerInfosEqual(newState.handlerInfos, state.handlerInfos);
|
|
if (!queryParams || !handlersEqual) {
|
|
return handlersEqual;
|
|
}
|
|
|
|
// Get a hash of QPs that will still be active on new route
|
|
var activeQPsOnNewHandler = {};
|
|
merge(activeQPsOnNewHandler, queryParams);
|
|
|
|
var activeQueryParams = this.state.queryParams;
|
|
for (var key in activeQueryParams) {
|
|
if (activeQueryParams.hasOwnProperty(key) &&
|
|
activeQPsOnNewHandler.hasOwnProperty(key)) {
|
|
activeQPsOnNewHandler[key] = activeQueryParams[key];
|
|
}
|
|
}
|
|
|
|
return handlersEqual && !getChangelist(activeQPsOnNewHandler, queryParams);
|
|
},
|
|
|
|
isActive: function(handlerName) {
|
|
var partitionedArgs = extractQueryParams(slice.call(arguments, 1));
|
|
return this.isActiveIntent(handlerName, partitionedArgs[0], partitionedArgs[1]);
|
|
},
|
|
|
|
trigger: function(name) {
|
|
var args = slice.call(arguments);
|
|
trigger(this, this.currentHandlerInfos, false, args);
|
|
},
|
|
|
|
/**
|
|
Hook point for logging transition status updates.
|
|
|
|
@param {String} message The message to log.
|
|
*/
|
|
log: null,
|
|
|
|
_willChangeContextEvent: 'willChangeContext',
|
|
_triggerWillChangeContext: function(handlerInfos, newTransition) {
|
|
trigger(this, handlerInfos, true, [this._willChangeContextEvent, newTransition]);
|
|
},
|
|
|
|
_triggerWillLeave: function(handlerInfos, newTransition, leavingChecker) {
|
|
trigger(this, handlerInfos, true, ['willLeave', newTransition, leavingChecker]);
|
|
}
|
|
};
|
|
|
|
/**
|
|
@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;
|
|
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);
|
|
|
|
forEach(partition.exited, function(handlerInfo) {
|
|
var handler = handlerInfo.handler;
|
|
delete handler.context;
|
|
|
|
callHook(handler, 'reset', true, transition);
|
|
callHook(handler, 'exit', transition);
|
|
});
|
|
|
|
var oldState = router.oldState = router.state;
|
|
router.state = newState;
|
|
var currentHandlerInfos = router.currentHandlerInfos = partition.unchanged.slice();
|
|
|
|
try {
|
|
forEach(partition.reset, function(handlerInfo) {
|
|
var handler = handlerInfo.handler;
|
|
callHook(handler, 'reset', false, transition);
|
|
});
|
|
|
|
forEach(partition.updatedContext, function(handlerInfo) {
|
|
return handlerEnteredOrUpdated(currentHandlerInfos, handlerInfo, false, transition);
|
|
});
|
|
|
|
forEach(partition.entered, function(handlerInfo) {
|
|
return handlerEnteredOrUpdated(currentHandlerInfos, handlerInfo, 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) {
|
|
callHook(handler, 'enter', transition);
|
|
}
|
|
if (transition && transition.isAborted) {
|
|
throw new TransitionAborted();
|
|
}
|
|
|
|
handler.context = context;
|
|
callHook(handler, 'contextDidChange');
|
|
|
|
callHook(handler, 'setup', context, transition);
|
|
if (transition && transition.isAborted) {
|
|
throw new 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];
|
|
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 {
|
|
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 Promise.reject(logAbort(transition));
|
|
}
|
|
|
|
updateURL(transition, newState, transition.intent.url);
|
|
|
|
transition.isActive = false;
|
|
router.activeTransition = null;
|
|
|
|
trigger(router, router.currentHandlerInfos, true, ['didTransition']);
|
|
|
|
if (router.didTransition) {
|
|
router.didTransition(router.currentHandlerInfos);
|
|
}
|
|
|
|
log(router, transition.sequence, "TRANSITION COMPLETE.");
|
|
|
|
// Resolve with the final handler.
|
|
return handlerInfos[handlerInfos.length - 1].handler;
|
|
} catch(e) {
|
|
if (!((e instanceof 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) {
|
|
|
|
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 NamedTransitionIntent({
|
|
name: handlerInfos[handlerInfos.length - 1].name,
|
|
contexts: [],
|
|
queryParams: queryParams
|
|
});
|
|
|
|
} else if (name.charAt(0) === '/') {
|
|
|
|
log(router, "Attempting URL transition to " + name);
|
|
intent = new URLTransitionIntent({ url: name });
|
|
|
|
} else {
|
|
|
|
log(router, "Attempting transition to " + name);
|
|
intent = new NamedTransitionIntent({
|
|
name: args[0],
|
|
contexts: 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 = [];
|
|
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;
|
|
};
|
|
|
|
router._triggerWillLeave(leaving, newTransition, leavingChecker);
|
|
}
|
|
|
|
if (changing.length > 0) {
|
|
router._triggerWillChangeContext(changing, newTransition);
|
|
}
|
|
|
|
trigger(router, oldHandlers, true, ['willTransition', newTransition]);
|
|
}
|
|
|
|
__exports__["default"] = Router;
|
|
});
|
|
enifed("router/transition-intent",
|
|
["./utils","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var merge = __dependency1__.merge;
|
|
|
|
function TransitionIntent(props) {
|
|
this.initialize(props);
|
|
|
|
// TODO: wat
|
|
this.data = this.data || {};
|
|
}
|
|
|
|
TransitionIntent.prototype = {
|
|
initialize: null,
|
|
applyToState: null
|
|
};
|
|
|
|
__exports__["default"] = TransitionIntent;
|
|
});
|
|
enifed("router/transition-intent/named-transition-intent",
|
|
["../transition-intent","../transition-state","../handler-info/factory","../utils","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
|
|
"use strict";
|
|
var TransitionIntent = __dependency1__["default"];
|
|
var TransitionState = __dependency2__["default"];
|
|
var handlerInfoFactory = __dependency3__["default"];
|
|
var isParam = __dependency4__.isParam;
|
|
var extractQueryParams = __dependency4__.extractQueryParams;
|
|
var merge = __dependency4__.merge;
|
|
var subclass = __dependency4__.subclass;
|
|
|
|
__exports__["default"] = subclass(TransitionIntent, {
|
|
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 = extractQueryParams([this.name].concat(this.contexts)),
|
|
pureArgs = partitionedArgs[0],
|
|
queryParams = partitionedArgs[1],
|
|
handlers = recognizer.handlersFor(pureArgs[0]);
|
|
|
|
var targetRouteName = handlers[handlers.length-1].handler;
|
|
|
|
return this.applyToHandlers(oldState, handlers, getHandler, targetRouteName, isIntermediate);
|
|
},
|
|
|
|
applyToHandlers: function(oldState, handlers, getHandler, targetRouteName, isIntermediate, checkingIfActive) {
|
|
|
|
var i, len;
|
|
var newState = new TransitionState();
|
|
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);
|
|
}
|
|
|
|
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 (isParam(objectToUse)) {
|
|
return this.createParamHandlerInfo(name, handler, names, objects, oldHandlerInfo);
|
|
} else {
|
|
objects.pop();
|
|
}
|
|
} else if (oldHandlerInfo && oldHandlerInfo.name === name) {
|
|
// Reuse the matching oldHandlerInfo
|
|
return oldHandlerInfo;
|
|
} else {
|
|
if (this.preTransitionState) {
|
|
var preTransitionHandlerInfo = this.preTransitionState.handlerInfos[i];
|
|
objectToUse = preTransitionHandlerInfo && preTransitionHandlerInfo.context;
|
|
} else {
|
|
// Ideally we should throw this error to provide maximal
|
|
// information to the user that not enough context objects
|
|
// were provided, but this proves too cumbersome in Ember
|
|
// in cases where inner template helpers are evaluated
|
|
// before parent helpers un-render, in which cases this
|
|
// error somewhat prematurely fires.
|
|
//throw new Error("Not enough context objects were provided to complete a transition to " + targetRouteName + ". Specifically, the " + name + " route needs an object that can be serialized into its dynamic URL segments [" + names.join(', ') + "]");
|
|
return oldHandlerInfo;
|
|
}
|
|
}
|
|
|
|
return handlerInfoFactory('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 (isParam(peek)) {
|
|
params[paramName] = "" + objects.pop();
|
|
} else {
|
|
// If we're here, this means only some of the params
|
|
// were string/number params, so try and use a param
|
|
// value from a previous handler.
|
|
if (oldParams.hasOwnProperty(paramName)) {
|
|
params[paramName] = oldParams[paramName];
|
|
} else {
|
|
throw new Error("You didn't provide enough string/numeric parameters to satisfy all of the dynamic segments for route " + name);
|
|
}
|
|
}
|
|
}
|
|
|
|
return handlerInfoFactory('param', {
|
|
name: name,
|
|
handler: handler,
|
|
params: params
|
|
});
|
|
}
|
|
});
|
|
});
|
|
enifed("router/transition-intent/url-transition-intent",
|
|
["../transition-intent","../transition-state","../handler-info/factory","../utils","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
|
|
"use strict";
|
|
var TransitionIntent = __dependency1__["default"];
|
|
var TransitionState = __dependency2__["default"];
|
|
var handlerInfoFactory = __dependency3__["default"];
|
|
var oCreate = __dependency4__.oCreate;
|
|
var merge = __dependency4__.merge;
|
|
var subclass = __dependency4__.subclass;
|
|
|
|
__exports__["default"] = subclass(TransitionIntent, {
|
|
url: null,
|
|
|
|
initialize: function(props) {
|
|
this.url = props.url;
|
|
},
|
|
|
|
applyToState: function(oldState, recognizer, getHandler) {
|
|
var newState = new TransitionState();
|
|
|
|
var results = recognizer.recognize(this.url),
|
|
queryParams = {},
|
|
i, len;
|
|
|
|
if (!results) {
|
|
throw new UnrecognizedURLError(this.url);
|
|
}
|
|
|
|
var statesDiffer = false;
|
|
|
|
for (i = 0, len = results.length; i < len; ++i) {
|
|
var result = results[i];
|
|
var name = result.handler;
|
|
var handler = getHandler(name);
|
|
|
|
if (handler.inaccessibleByURL) {
|
|
throw new UnrecognizedURLError(this.url);
|
|
}
|
|
|
|
var newHandlerInfo = handlerInfoFactory('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;
|
|
}
|
|
}
|
|
|
|
merge(newState.queryParams, results.queryParams);
|
|
|
|
return newState;
|
|
}
|
|
});
|
|
|
|
/**
|
|
Promise reject reasons passed to promise rejection
|
|
handlers for failed transitions.
|
|
*/
|
|
function UnrecognizedURLError(message) {
|
|
this.message = (message || "UnrecognizedURLError");
|
|
this.name = "UnrecognizedURLError";
|
|
}
|
|
});
|
|
enifed("router/transition-state",
|
|
["./handler-info","./utils","rsvp/promise","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
var ResolvedHandlerInfo = __dependency1__.ResolvedHandlerInfo;
|
|
var forEach = __dependency2__.forEach;
|
|
var promiseLabel = __dependency2__.promiseLabel;
|
|
var callHook = __dependency2__.callHook;
|
|
var Promise = __dependency3__["default"];
|
|
|
|
function TransitionState(other) {
|
|
this.handlerInfos = [];
|
|
this.queryParams = {};
|
|
this.params = {};
|
|
}
|
|
|
|
TransitionState.prototype = {
|
|
handlerInfos: null,
|
|
queryParams: null,
|
|
params: null,
|
|
|
|
promiseLabel: function(label) {
|
|
var targetName = '';
|
|
forEach(this.handlerInfos, function(handlerInfo) {
|
|
if (targetName !== '') {
|
|
targetName += '.';
|
|
}
|
|
targetName += handlerInfo.name;
|
|
});
|
|
return 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;
|
|
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 Promise.resolve(null, this.promiseLabel("Start transition"))
|
|
.then(resolveOneHandlerInfo, null, this.promiseLabel('Resolve handler'))['catch'](handleError, this.promiseLabel('Handle error'));
|
|
|
|
function innerShouldContinue() {
|
|
return Promise.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 Promise.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 Promise.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;
|
|
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",
|
|
["rsvp/promise","./handler-info","./utils","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
var Promise = __dependency1__["default"];
|
|
var ResolvedHandlerInfo = __dependency2__.ResolvedHandlerInfo;
|
|
var trigger = __dependency3__.trigger;
|
|
var slice = __dependency3__.slice;
|
|
var log = __dependency3__.log;
|
|
var promiseLabel = __dependency3__.promiseLabel;
|
|
|
|
/**
|
|
@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 = Promise.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 Promise.reject(logAbort(transition));
|
|
} else {
|
|
transition.trigger('error', result.error, transition, result.handlerWithError);
|
|
transition.abort();
|
|
return Promise.reject(result.error);
|
|
}
|
|
}, promiseLabel('Handle Abort'));
|
|
} else {
|
|
this.promise = Promise.resolve(this.state);
|
|
this.params = {};
|
|
}
|
|
|
|
function checkForAbort() {
|
|
if (transition.isAborted) {
|
|
return Promise.reject(undefined, 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; }
|
|
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 = slice.call(arguments);
|
|
if (typeof ignoreFailure === 'boolean') {
|
|
args.shift();
|
|
} else {
|
|
// Throw errors on unhandled trigger events by default
|
|
ignoreFailure = false;
|
|
}
|
|
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 Promise.reject(reason);
|
|
});
|
|
},
|
|
|
|
toString: function() {
|
|
return "Transition (sequence " + this.sequence + ")";
|
|
},
|
|
|
|
/**
|
|
@private
|
|
*/
|
|
log: function(message) {
|
|
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) {
|
|
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/utils",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
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];
|
|
}
|
|
}
|
|
|
|
__exports__.extractQueryParams = extractQueryParams;/**
|
|
@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);
|
|
}
|
|
}
|
|
|
|
__exports__.log = log;function bind(context, fn) {
|
|
var boundArgs = arguments;
|
|
return function(value) {
|
|
var args = slice.call(boundArgs, 2);
|
|
args.push(value);
|
|
return fn.apply(context, args);
|
|
};
|
|
}
|
|
|
|
__exports__.bind = bind;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++) { }
|
|
}
|
|
|
|
__exports__.forEach = forEach;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 + "'.");
|
|
}
|
|
}
|
|
|
|
__exports__.trigger = trigger;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;
|
|
}
|
|
|
|
__exports__.getChangelist = getChangelist;function promiseLabel(label) {
|
|
return 'Router: ' + label;
|
|
}
|
|
|
|
__exports__.promiseLabel = promiseLabel;function subclass(parentConstructor, proto) {
|
|
function C(props) {
|
|
parentConstructor.call(this, props || {});
|
|
}
|
|
C.prototype = oCreate(parentConstructor.prototype);
|
|
merge(C.prototype, proto);
|
|
return C;
|
|
}
|
|
|
|
__exports__.subclass = subclass;function resolveHook(obj, hookName) {
|
|
if (!obj) { return; }
|
|
var underscored = "_" + hookName;
|
|
return obj[underscored] && underscored ||
|
|
obj[hookName] && hookName;
|
|
}
|
|
|
|
function callHook(obj, hookName) {
|
|
var args = slice.call(arguments, 2);
|
|
return applyHook(obj, hookName, args);
|
|
}
|
|
|
|
function applyHook(obj, _hookName, args) {
|
|
var hookName = resolveHook(obj, _hookName);
|
|
if (hookName) {
|
|
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("rsvp",
|
|
["./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","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __dependency16__, __exports__) {
|
|
"use strict";
|
|
var Promise = __dependency1__["default"];
|
|
var EventTarget = __dependency2__["default"];
|
|
var denodeify = __dependency3__["default"];
|
|
var all = __dependency4__["default"];
|
|
var allSettled = __dependency5__["default"];
|
|
var race = __dependency6__["default"];
|
|
var hash = __dependency7__["default"];
|
|
var hashSettled = __dependency8__["default"];
|
|
var rethrow = __dependency9__["default"];
|
|
var defer = __dependency10__["default"];
|
|
var config = __dependency11__.config;
|
|
var configure = __dependency11__.configure;
|
|
var map = __dependency12__["default"];
|
|
var resolve = __dependency13__["default"];
|
|
var reject = __dependency14__["default"];
|
|
var filter = __dependency15__["default"];
|
|
var asap = __dependency16__["default"];
|
|
|
|
config.async = asap; // default async is asap;
|
|
var cast = resolve;
|
|
function async(callback, arg) {
|
|
config.async(callback, arg);
|
|
}
|
|
|
|
function on() {
|
|
config.on.apply(config, arguments);
|
|
}
|
|
|
|
function off() {
|
|
config.off.apply(config, arguments);
|
|
}
|
|
|
|
// Set up instrumentation through `window.__PROMISE_INTRUMENTATION__`
|
|
if (typeof window !== 'undefined' && typeof window['__PROMISE_INSTRUMENTATION__'] === 'object') {
|
|
var callbacks = window['__PROMISE_INSTRUMENTATION__'];
|
|
configure('instrument', true);
|
|
for (var eventName in callbacks) {
|
|
if (callbacks.hasOwnProperty(eventName)) {
|
|
on(eventName, callbacks[eventName]);
|
|
}
|
|
}
|
|
}
|
|
|
|
__exports__.cast = cast;
|
|
__exports__.Promise = Promise;
|
|
__exports__.EventTarget = EventTarget;
|
|
__exports__.all = all;
|
|
__exports__.allSettled = allSettled;
|
|
__exports__.race = race;
|
|
__exports__.hash = hash;
|
|
__exports__.hashSettled = hashSettled;
|
|
__exports__.rethrow = rethrow;
|
|
__exports__.defer = defer;
|
|
__exports__.denodeify = denodeify;
|
|
__exports__.configure = configure;
|
|
__exports__.on = on;
|
|
__exports__.off = off;
|
|
__exports__.resolve = resolve;
|
|
__exports__.reject = reject;
|
|
__exports__.async = async;
|
|
__exports__.map = map;
|
|
__exports__.filter = filter;
|
|
});
|
|
enifed("rsvp.umd",
|
|
["./rsvp"],
|
|
function(__dependency1__) {
|
|
"use strict";
|
|
var Promise = __dependency1__.Promise;
|
|
var allSettled = __dependency1__.allSettled;
|
|
var hash = __dependency1__.hash;
|
|
var hashSettled = __dependency1__.hashSettled;
|
|
var denodeify = __dependency1__.denodeify;
|
|
var on = __dependency1__.on;
|
|
var off = __dependency1__.off;
|
|
var map = __dependency1__.map;
|
|
var filter = __dependency1__.filter;
|
|
var resolve = __dependency1__.resolve;
|
|
var reject = __dependency1__.reject;
|
|
var rethrow = __dependency1__.rethrow;
|
|
var all = __dependency1__.all;
|
|
var defer = __dependency1__.defer;
|
|
var EventTarget = __dependency1__.EventTarget;
|
|
var configure = __dependency1__.configure;
|
|
var race = __dependency1__.race;
|
|
var async = __dependency1__.async;
|
|
|
|
var RSVP = {
|
|
'race': race,
|
|
'Promise': Promise,
|
|
'allSettled': allSettled,
|
|
'hash': hash,
|
|
'hashSettled': hashSettled,
|
|
'denodeify': denodeify,
|
|
'on': on,
|
|
'off': off,
|
|
'map': map,
|
|
'filter': filter,
|
|
'resolve': resolve,
|
|
'reject': reject,
|
|
'all': all,
|
|
'rethrow': rethrow,
|
|
'defer': defer,
|
|
'EventTarget': EventTarget,
|
|
'configure': configure,
|
|
'async': async
|
|
};
|
|
|
|
/* global define:true module:true window: true */
|
|
if (typeof enifed === 'function' && enifed['amd']) {
|
|
enifed(function() { return RSVP; });
|
|
} else if (typeof module !== 'undefined' && module['exports']) {
|
|
module['exports'] = RSVP;
|
|
} else if (typeof this !== 'undefined') {
|
|
this['RSVP'] = RSVP;
|
|
}
|
|
});
|
|
enifed("rsvp/-internal",
|
|
["./utils","./instrument","./config","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
var objectOrFunction = __dependency1__.objectOrFunction;
|
|
var isFunction = __dependency1__.isFunction;
|
|
|
|
var instrument = __dependency2__["default"];
|
|
|
|
var config = __dependency3__.config;
|
|
|
|
function withOwnPromise() {
|
|
return new TypeError('A promises callback cannot return that same promise.');
|
|
}
|
|
|
|
function noop() {}
|
|
|
|
var PENDING = void 0;
|
|
var FULFILLED = 1;
|
|
var REJECTED = 2;
|
|
|
|
var GET_THEN_ERROR = new ErrorObject();
|
|
|
|
function getThen(promise) {
|
|
try {
|
|
return promise.then;
|
|
} catch(error) {
|
|
GET_THEN_ERROR.error = error;
|
|
return GET_THEN_ERROR;
|
|
}
|
|
}
|
|
|
|
function tryThen(then, value, fulfillmentHandler, rejectionHandler) {
|
|
try {
|
|
then.call(value, fulfillmentHandler, rejectionHandler);
|
|
} catch(e) {
|
|
return e;
|
|
}
|
|
}
|
|
|
|
function handleForeignThenable(promise, thenable, then) {
|
|
config.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 (promise._state === REJECTED) {
|
|
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 (isFunction(then)) {
|
|
handleForeignThenable(promise, maybeThenable, then);
|
|
} else {
|
|
fulfill(promise, maybeThenable);
|
|
}
|
|
}
|
|
}
|
|
|
|
function resolve(promise, value) {
|
|
if (promise === value) {
|
|
fulfill(promise, value);
|
|
} else if (objectOrFunction(value)) {
|
|
handleMaybeThenable(promise, value);
|
|
} else {
|
|
fulfill(promise, value);
|
|
}
|
|
}
|
|
|
|
function publishRejection(promise) {
|
|
if (promise._onerror) {
|
|
promise._onerror(promise._result);
|
|
}
|
|
|
|
publish(promise);
|
|
}
|
|
|
|
function fulfill(promise, value) {
|
|
if (promise._state !== PENDING) { return; }
|
|
|
|
promise._result = value;
|
|
promise._state = FULFILLED;
|
|
|
|
if (promise._subscribers.length === 0) {
|
|
if (config.instrument) {
|
|
instrument('fulfilled', promise);
|
|
}
|
|
} else {
|
|
config.async(publish, promise);
|
|
}
|
|
}
|
|
|
|
function reject(promise, reason) {
|
|
if (promise._state !== PENDING) { return; }
|
|
promise._state = REJECTED;
|
|
promise._result = reason;
|
|
|
|
config.async(publishRejection, promise);
|
|
}
|
|
|
|
function subscribe(parent, child, onFulfillment, onRejection) {
|
|
var subscribers = parent._subscribers;
|
|
var length = subscribers.length;
|
|
|
|
parent._onerror = null;
|
|
|
|
subscribers[length] = child;
|
|
subscribers[length + FULFILLED] = onFulfillment;
|
|
subscribers[length + REJECTED] = onRejection;
|
|
|
|
if (length === 0 && parent._state) {
|
|
config.async(publish, parent);
|
|
}
|
|
}
|
|
|
|
function publish(promise) {
|
|
var subscribers = promise._subscribers;
|
|
var settled = promise._state;
|
|
|
|
if (config.instrument) {
|
|
instrument(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 = 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) {
|
|
try {
|
|
resolver(function resolvePromise(value){
|
|
resolve(promise, value);
|
|
}, function rejectPromise(reason) {
|
|
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",
|
|
["./enumerator","./promise","./utils","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
var Enumerator = __dependency1__["default"];
|
|
var makeSettledResult = __dependency1__.makeSettledResult;
|
|
var Promise = __dependency2__["default"];
|
|
var o_create = __dependency3__.o_create;
|
|
|
|
function AllSettled(Constructor, entries, label) {
|
|
this._superConstructor(Constructor, entries, false /* don't abort on reject */, label);
|
|
}
|
|
|
|
AllSettled.prototype = o_create(Enumerator.prototype);
|
|
AllSettled.prototype._superConstructor = Enumerator;
|
|
AllSettled.prototype._makeResult = 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} promises
|
|
@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.
|
|
*/
|
|
|
|
__exports__["default"] = function allSettled(entries, label) {
|
|
return new AllSettled(Promise, entries, label).promise;
|
|
}
|
|
});
|
|
enifed("rsvp/all",
|
|
["./promise","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var Promise = __dependency1__["default"];
|
|
|
|
/**
|
|
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.
|
|
*/
|
|
__exports__["default"] = function all(array, label) {
|
|
return Promise.all(array, label);
|
|
}
|
|
});
|
|
enifed("rsvp/asap",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
var len = 0;
|
|
|
|
__exports__["default"] = 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;
|
|
|
|
// test for web worker but not in IE10
|
|
var isWorker = typeof Uint8ClampedArray !== 'undefined' &&
|
|
typeof importScripts !== 'undefined' &&
|
|
typeof MessageChannel !== 'undefined';
|
|
|
|
// node
|
|
function useNextTick() {
|
|
return function() {
|
|
process.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 vertx = eriuqer('vertx');
|
|
var 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 (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') {
|
|
scheduleFlush = useNextTick();
|
|
} else if (BrowserMutationObserver) {
|
|
scheduleFlush = useMutationObserver();
|
|
} else if (isWorker) {
|
|
scheduleFlush = useMessageChannel();
|
|
} else if (browserWindow === undefined && typeof eriuqer === 'function') {
|
|
scheduleFlush = attemptVertex();
|
|
} else {
|
|
scheduleFlush = useSetTimeout();
|
|
}
|
|
});
|
|
enifed("rsvp/config",
|
|
["./events","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var EventTarget = __dependency1__["default"];
|
|
|
|
var config = {
|
|
instrument: false
|
|
};
|
|
|
|
EventTarget.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",
|
|
["./promise","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var Promise = __dependency1__["default"];
|
|
|
|
/**
|
|
`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!");
|
|
|
|
defered.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}
|
|
*/
|
|
|
|
__exports__["default"] = function defer(label) {
|
|
var deferred = { };
|
|
|
|
deferred['promise'] = new Promise(function(resolve, reject) {
|
|
deferred['resolve'] = resolve;
|
|
deferred['reject'] = reject;
|
|
}, label);
|
|
|
|
return deferred;
|
|
}
|
|
});
|
|
enifed("rsvp/enumerator",
|
|
["./utils","./-internal","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var isArray = __dependency1__.isArray;
|
|
var isMaybeThenable = __dependency1__.isMaybeThenable;
|
|
|
|
var noop = __dependency2__.noop;
|
|
var reject = __dependency2__.reject;
|
|
var fulfill = __dependency2__.fulfill;
|
|
var subscribe = __dependency2__.subscribe;
|
|
var FULFILLED = __dependency2__.FULFILLED;
|
|
var REJECTED = __dependency2__.REJECTED;
|
|
var PENDING = __dependency2__.PENDING;
|
|
|
|
function makeSettledResult(state, position, value) {
|
|
if (state === FULFILLED) {
|
|
return {
|
|
state: 'fulfilled',
|
|
value: value
|
|
};
|
|
} else {
|
|
return {
|
|
state: 'rejected',
|
|
reason: value
|
|
};
|
|
}
|
|
}
|
|
|
|
__exports__.makeSettledResult = makeSettledResult;function Enumerator(Constructor, input, abortOnReject, label) {
|
|
this._instanceConstructor = Constructor;
|
|
this.promise = new Constructor(noop, label);
|
|
this._abortOnReject = abortOnReject;
|
|
|
|
if (this._validateInput(input)) {
|
|
this._input = input;
|
|
this.length = input.length;
|
|
this._remaining = input.length;
|
|
|
|
this._init();
|
|
|
|
if (this.length === 0) {
|
|
fulfill(this.promise, this._result);
|
|
} else {
|
|
this.length = this.length || 0;
|
|
this._enumerate();
|
|
if (this._remaining === 0) {
|
|
fulfill(this.promise, this._result);
|
|
}
|
|
}
|
|
} else {
|
|
reject(this.promise, this._validationError());
|
|
}
|
|
}
|
|
|
|
Enumerator.prototype._validateInput = function(input) {
|
|
return 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);
|
|
};
|
|
|
|
__exports__["default"] = Enumerator;
|
|
|
|
Enumerator.prototype._enumerate = function() {
|
|
var length = this.length;
|
|
var promise = this.promise;
|
|
var input = this._input;
|
|
|
|
for (var i = 0; promise._state === PENDING && i < length; i++) {
|
|
this._eachEntry(input[i], i);
|
|
}
|
|
};
|
|
|
|
Enumerator.prototype._eachEntry = function(entry, i) {
|
|
var c = this._instanceConstructor;
|
|
if (isMaybeThenable(entry)) {
|
|
if (entry.constructor === c && entry._state !== PENDING) {
|
|
entry._onerror = null;
|
|
this._settledAt(entry._state, i, entry._result);
|
|
} else {
|
|
this._willSettleAt(c.resolve(entry), i);
|
|
}
|
|
} else {
|
|
this._remaining--;
|
|
this._result[i] = this._makeResult(FULFILLED, i, entry);
|
|
}
|
|
};
|
|
|
|
Enumerator.prototype._settledAt = function(state, i, value) {
|
|
var promise = this.promise;
|
|
|
|
if (promise._state === PENDING) {
|
|
this._remaining--;
|
|
|
|
if (this._abortOnReject && state === REJECTED) {
|
|
reject(promise, value);
|
|
} else {
|
|
this._result[i] = this._makeResult(state, i, value);
|
|
}
|
|
}
|
|
|
|
if (this._remaining === 0) {
|
|
fulfill(promise, this._result);
|
|
}
|
|
};
|
|
|
|
Enumerator.prototype._makeResult = function(state, i, value) {
|
|
return value;
|
|
};
|
|
|
|
Enumerator.prototype._willSettleAt = function(promise, i) {
|
|
var enumerator = this;
|
|
|
|
subscribe(promise, undefined, function(value) {
|
|
enumerator._settledAt(FULFILLED, i, value);
|
|
}, function(reason) {
|
|
enumerator._settledAt(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) {
|
|
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 {Any} 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",
|
|
["./promise","./utils","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var Promise = __dependency1__["default"];
|
|
var isFunction = __dependency2__.isFunction;
|
|
|
|
/**
|
|
`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}
|
|
*/
|
|
__exports__["default"] = function filter(promises, filterFn, label) {
|
|
return Promise.all(promises, label).then(function(values) {
|
|
if (!isFunction(filterFn)) {
|
|
throw new TypeError("You must pass a function as filter's second argument.");
|
|
}
|
|
|
|
var length = values.length;
|
|
var filtered = new Array(length);
|
|
|
|
for (var i = 0; i < length; i++) {
|
|
filtered[i] = filterFn(values[i]);
|
|
}
|
|
|
|
return Promise.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",
|
|
["./promise","./enumerator","./promise-hash","./utils","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
|
|
"use strict";
|
|
var Promise = __dependency1__["default"];
|
|
var makeSettledResult = __dependency2__.makeSettledResult;
|
|
var PromiseHash = __dependency3__["default"];
|
|
var Enumerator = __dependency2__["default"];
|
|
var o_create = __dependency4__.o_create;
|
|
|
|
function HashSettled(Constructor, object, label) {
|
|
this._superConstructor(Constructor, object, false, label);
|
|
}
|
|
|
|
HashSettled.prototype = o_create(PromiseHash.prototype);
|
|
HashSettled.prototype._superConstructor = Enumerator;
|
|
HashSettled.prototype._makeResult = 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} promises
|
|
@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
|
|
*/
|
|
__exports__["default"] = function hashSettled(object, label) {
|
|
return new HashSettled(Promise, object, label).promise;
|
|
}
|
|
});
|
|
enifed("rsvp/hash",
|
|
["./promise","./promise-hash","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var Promise = __dependency1__["default"];
|
|
var PromiseHash = __dependency2__["default"];
|
|
|
|
/**
|
|
`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} promises
|
|
@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.
|
|
*/
|
|
__exports__["default"] = function hash(object, label) {
|
|
return new PromiseHash(Promise, object, label).promise;
|
|
}
|
|
});
|
|
enifed("rsvp/instrument",
|
|
["./config","./utils","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var config = __dependency1__.config;
|
|
var now = __dependency2__.now;
|
|
|
|
var queue = [];
|
|
|
|
function scheduleFlush() {
|
|
setTimeout(function() {
|
|
var entry;
|
|
for (var i = 0; i < queue.length; i++) {
|
|
entry = queue[i];
|
|
|
|
var payload = entry.payload;
|
|
|
|
payload.guid = payload.key + payload.id;
|
|
payload.childGuid = payload.key + payload.childId;
|
|
if (payload.error) {
|
|
payload.stack = payload.error.stack;
|
|
}
|
|
|
|
config.trigger(entry.name, entry.payload);
|
|
}
|
|
queue.length = 0;
|
|
}, 50);
|
|
}
|
|
|
|
__exports__["default"] = 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: now(),
|
|
error: config["instrument-with-stack"] ? new Error(promise._label) : null
|
|
}})) {
|
|
scheduleFlush();
|
|
}
|
|
}
|
|
});
|
|
enifed("rsvp/map",
|
|
["./promise","./utils","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var Promise = __dependency1__["default"];
|
|
var isFunction = __dependency2__.isFunction;
|
|
|
|
/**
|
|
`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
|
|
*/
|
|
__exports__["default"] = function map(promises, mapFn, label) {
|
|
return Promise.all(promises, label).then(function(values) {
|
|
if (!isFunction(mapFn)) {
|
|
throw new TypeError("You must pass a function as map's second argument.");
|
|
}
|
|
|
|
var length = values.length;
|
|
var results = new Array(length);
|
|
|
|
for (var i = 0; i < length; i++) {
|
|
results[i] = mapFn(values[i]);
|
|
}
|
|
|
|
return Promise.all(results, label);
|
|
});
|
|
}
|
|
});
|
|
enifed("rsvp/node",
|
|
["./promise","./-internal","./utils","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
var Promise = __dependency1__["default"];
|
|
var noop = __dependency2__.noop;
|
|
var resolve = __dependency2__.resolve;
|
|
var reject = __dependency2__.reject;
|
|
var isArray = __dependency3__.isArray;
|
|
|
|
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} argumentNames 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
|
|
*/
|
|
__exports__["default"] = function denodeify(nodeFunc, options) {
|
|
var fn = function() {
|
|
var self = this;
|
|
var l = arguments.length;
|
|
var args = new Array(l + 1);
|
|
var arg;
|
|
var promiseInput = false;
|
|
|
|
for (var i = 0; i < l; ++i) {
|
|
arg = arguments[i];
|
|
|
|
if (!promiseInput) {
|
|
// TODO: clean this up
|
|
promiseInput = needsPromiseInput(arg);
|
|
if (promiseInput === GET_THEN_ERROR) {
|
|
var p = new Promise(noop);
|
|
reject(p, GET_THEN_ERROR.value);
|
|
return p;
|
|
} else if (promiseInput && promiseInput !== true) {
|
|
arg = wrapThenable(promiseInput, arg);
|
|
}
|
|
}
|
|
args[i] = arg;
|
|
}
|
|
|
|
var promise = new Promise(noop);
|
|
|
|
args[l] = function(err, val) {
|
|
if (err)
|
|
reject(promise, err);
|
|
else if (options === undefined)
|
|
resolve(promise, val);
|
|
else if (options === true)
|
|
resolve(promise, arrayResult(arguments));
|
|
else if (isArray(options))
|
|
resolve(promise, makeObject(arguments, options));
|
|
else
|
|
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) {
|
|
reject(promise, result.value);
|
|
}
|
|
return promise;
|
|
}
|
|
|
|
function handlePromiseInput(promise, args, nodeFunc, self){
|
|
return Promise.all(args).then(function(args){
|
|
var result = tryApply(nodeFunc, self, args);
|
|
if (result === ERROR) {
|
|
reject(promise, result.value);
|
|
}
|
|
return promise;
|
|
});
|
|
}
|
|
|
|
function needsPromiseInput(arg) {
|
|
if (arg && typeof arg === 'object') {
|
|
if (arg.constructor === Promise) {
|
|
return true;
|
|
} else {
|
|
return getThen(arg);
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
});
|
|
enifed("rsvp/promise-hash",
|
|
["./enumerator","./-internal","./utils","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
|
|
"use strict";
|
|
var Enumerator = __dependency1__["default"];
|
|
var PENDING = __dependency2__.PENDING;
|
|
var o_create = __dependency3__.o_create;
|
|
|
|
function PromiseHash(Constructor, object, label) {
|
|
this._superConstructor(Constructor, object, true, label);
|
|
}
|
|
|
|
__exports__["default"] = PromiseHash;
|
|
|
|
PromiseHash.prototype = o_create(Enumerator.prototype);
|
|
PromiseHash.prototype._superConstructor = Enumerator;
|
|
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 promise = this.promise;
|
|
var input = this._input;
|
|
var results = [];
|
|
|
|
for (var key in input) {
|
|
if (promise._state === PENDING && input.hasOwnProperty(key)) {
|
|
results.push({
|
|
position: key,
|
|
entry: input[key]
|
|
});
|
|
}
|
|
}
|
|
|
|
var length = results.length;
|
|
this._remaining = length;
|
|
var result;
|
|
|
|
for (var i = 0; promise._state === PENDING && i < length; i++) {
|
|
result = results[i];
|
|
this._eachEntry(result.entry, result.position);
|
|
}
|
|
};
|
|
});
|
|
enifed("rsvp/promise",
|
|
["./config","./instrument","./utils","./-internal","./promise/all","./promise/race","./promise/resolve","./promise/reject","exports"],
|
|
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) {
|
|
"use strict";
|
|
var config = __dependency1__.config;
|
|
var instrument = __dependency2__["default"];
|
|
|
|
var isFunction = __dependency3__.isFunction;
|
|
var now = __dependency3__.now;
|
|
|
|
var noop = __dependency4__.noop;
|
|
var subscribe = __dependency4__.subscribe;
|
|
var initializePromise = __dependency4__.initializePromise;
|
|
var invokeCallback = __dependency4__.invokeCallback;
|
|
var FULFILLED = __dependency4__.FULFILLED;
|
|
var REJECTED = __dependency4__.REJECTED;
|
|
|
|
var all = __dependency5__["default"];
|
|
var race = __dependency6__["default"];
|
|
var Resolve = __dependency7__["default"];
|
|
var Reject = __dependency8__["default"];
|
|
|
|
var guidKey = 'rsvp_' + 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.");
|
|
}
|
|
__exports__["default"] = Promise;
|
|
/**
|
|
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) {
|
|
this._id = counter++;
|
|
this._label = label;
|
|
this._state = undefined;
|
|
this._result = undefined;
|
|
this._subscribers = [];
|
|
|
|
if (config.instrument) {
|
|
instrument('created', this);
|
|
}
|
|
|
|
if (noop !== resolver) {
|
|
if (!isFunction(resolver)) {
|
|
needsResolver();
|
|
}
|
|
|
|
if (!(this instanceof Promise)) {
|
|
needsNew();
|
|
}
|
|
|
|
initializePromise(this, resolver);
|
|
}
|
|
}
|
|
|
|
Promise.cast = Resolve; // deprecated
|
|
Promise.all = all;
|
|
Promise.race = race;
|
|
Promise.resolve = Resolve;
|
|
Promise.reject = Reject;
|
|
|
|
Promise.prototype = {
|
|
constructor: Promise,
|
|
|
|
_guidKey: guidKey,
|
|
|
|
_onerror: function (reason) {
|
|
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} onFulfilled
|
|
@param {Function} onRejected
|
|
@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 === FULFILLED && !onFulfillment || state === REJECTED && !onRejection) {
|
|
if (config.instrument) {
|
|
instrument('chained', this, this);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
parent._onerror = null;
|
|
|
|
var child = new this.constructor(noop, label);
|
|
var result = parent._result;
|
|
|
|
if (config.instrument) {
|
|
instrument('chained', parent, child);
|
|
}
|
|
|
|
if (state) {
|
|
var callback = arguments[state - 1];
|
|
config.async(function(){
|
|
invokeCallback(state, child, callback, result);
|
|
});
|
|
} else {
|
|
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(null, 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 constructor = this.constructor;
|
|
|
|
return this.then(function(value) {
|
|
return constructor.resolve(callback()).then(function(){
|
|
return value;
|
|
});
|
|
}, function(reason) {
|
|
return constructor.resolve(callback()).then(function(){
|
|
throw reason;
|
|
});
|
|
}, label);
|
|
}
|
|
};
|
|
});
|
|
enifed("rsvp/promise/all",
|
|
["../enumerator","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var Enumerator = __dependency1__["default"];
|
|
|
|
/**
|
|
`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
|
|
*/
|
|
__exports__["default"] = function all(entries, label) {
|
|
return new Enumerator(this, entries, true /* abort on reject */, label).promise;
|
|
}
|
|
});
|
|
enifed("rsvp/promise/race",
|
|
["../utils","../-internal","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var isArray = __dependency1__.isArray;
|
|
|
|
var noop = __dependency2__.noop;
|
|
var resolve = __dependency2__.resolve;
|
|
var reject = __dependency2__.reject;
|
|
var subscribe = __dependency2__.subscribe;
|
|
var PENDING = __dependency2__.PENDING;
|
|
|
|
/**
|
|
`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} promises 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.
|
|
*/
|
|
__exports__["default"] = function race(entries, label) {
|
|
/*jshint validthis:true */
|
|
var Constructor = this;
|
|
|
|
var promise = new Constructor(noop, label);
|
|
|
|
if (!isArray(entries)) {
|
|
reject(promise, new TypeError('You must pass an array to race.'));
|
|
return promise;
|
|
}
|
|
|
|
var length = entries.length;
|
|
|
|
function onFulfillment(value) {
|
|
resolve(promise, value);
|
|
}
|
|
|
|
function onRejection(reason) {
|
|
reject(promise, reason);
|
|
}
|
|
|
|
for (var i = 0; promise._state === PENDING && i < length; i++) {
|
|
subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection);
|
|
}
|
|
|
|
return promise;
|
|
}
|
|
});
|
|
enifed("rsvp/promise/reject",
|
|
["../-internal","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var noop = __dependency1__.noop;
|
|
var _reject = __dependency1__.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 {Any} 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`.
|
|
*/
|
|
__exports__["default"] = function reject(reason, label) {
|
|
/*jshint validthis:true */
|
|
var Constructor = this;
|
|
var promise = new Constructor(noop, label);
|
|
_reject(promise, reason);
|
|
return promise;
|
|
}
|
|
});
|
|
enifed("rsvp/promise/resolve",
|
|
["../-internal","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var noop = __dependency1__.noop;
|
|
var _resolve = __dependency1__.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 {Any} 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`
|
|
*/
|
|
__exports__["default"] = function resolve(object, label) {
|
|
/*jshint validthis:true */
|
|
var Constructor = this;
|
|
|
|
if (object && typeof object === 'object' && object.constructor === Constructor) {
|
|
return object;
|
|
}
|
|
|
|
var promise = new Constructor(noop, label);
|
|
_resolve(promise, object);
|
|
return promise;
|
|
}
|
|
});
|
|
enifed("rsvp/race",
|
|
["./promise","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var Promise = __dependency1__["default"];
|
|
|
|
/**
|
|
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.
|
|
*/
|
|
__exports__["default"] = function race(array, label) {
|
|
return Promise.race(array, label);
|
|
}
|
|
});
|
|
enifed("rsvp/reject",
|
|
["./promise","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var Promise = __dependency1__["default"];
|
|
|
|
/**
|
|
This is a convenient alias for `RSVP.Promise.reject`.
|
|
|
|
@method reject
|
|
@static
|
|
@for RSVP
|
|
@param {Any} 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`.
|
|
*/
|
|
__exports__["default"] = function reject(reason, label) {
|
|
return Promise.reject(reason, label);
|
|
}
|
|
});
|
|
enifed("rsvp/resolve",
|
|
["./promise","exports"],
|
|
function(__dependency1__, __exports__) {
|
|
"use strict";
|
|
var Promise = __dependency1__["default"];
|
|
|
|
/**
|
|
This is a convenient alias for `RSVP.Promise.resolve`.
|
|
|
|
@method resolve
|
|
@static
|
|
@for RSVP
|
|
@param {Any} 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`
|
|
*/
|
|
__exports__["default"] = function resolve(value, label) {
|
|
return Promise.resolve(value, label);
|
|
}
|
|
});
|
|
enifed("rsvp/rethrow",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
/**
|
|
`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
|
|
*/
|
|
__exports__["default"] = function rethrow(reason) {
|
|
setTimeout(function() {
|
|
throw reason;
|
|
});
|
|
throw reason;
|
|
}
|
|
});
|
|
enifed("rsvp/utils",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
function objectOrFunction(x) {
|
|
return typeof x === 'function' || (typeof x === 'object' && x !== null);
|
|
}
|
|
|
|
__exports__.objectOrFunction = objectOrFunction;function isFunction(x) {
|
|
return typeof x === 'function';
|
|
}
|
|
|
|
__exports__.isFunction = isFunction;function isMaybeThenable(x) {
|
|
return typeof x === 'object' && x !== null;
|
|
}
|
|
|
|
__exports__.isMaybeThenable = isMaybeThenable;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;
|
|
});
|
|
requireModule("ember");
|
|
|
|
})();
|