| /*! |
| * @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.7.0-beta.4+pre.4b6ff143 |
| */ |
| |
| (function() { |
| var define, requireModule, require, requirejs, Ember; |
| |
| (function() { |
| Ember = this.Ember = this.Ember || {}; |
| if (typeof Ember === 'undefined') { |
| Ember = {} |
| }; |
| |
| if (typeof Ember.__loader === 'undefined') { |
| var registry = {}, seen = {}; |
| |
| define = function(name, deps, callback) { |
| registry[name] = { |
| deps: deps, |
| callback: callback |
| }; |
| }; |
| |
| requirejs = require = requireModule = function(name) { |
| if (seen.hasOwnProperty(name)) { |
| return seen[name]; |
| } |
| seen[name] = {}; |
| |
| if (!registry[name]) { |
| throw new Error("Could not find module " + name); |
| } |
| |
| var mod = registry[name], |
| deps = mod.deps, |
| callback = mod.callback, |
| reified = [], |
| exports; |
| |
| for (var i = 0, l = deps.length; i < l; i++) { |
| if (deps[i] === 'exports') { |
| reified.push(exports = {}); |
| } else { |
| reified.push(requireModule(resolve(deps[i]))); |
| } |
| } |
| |
| var value = callback.apply(this, reified); |
| return seen[name] = exports || value; |
| |
| function resolve(child) { |
| 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: define, |
| require: require, |
| registry: registry |
| }; |
| } else { |
| define = Ember.__loader.define; |
| requirejs = require = requireModule = Ember.__loader.require; |
| } |
| })(); |
| |
| define("backburner", |
| ["backburner/utils", "backburner/deferred_action_queues", "exports"], |
| function(__dependency1__, __dependency2__, __exports__) { |
| "use strict"; |
| var Utils = __dependency1__["default"]; |
| var DeferredActionQueues = __dependency2__.DeferredActionQueues; |
| |
| var slice = [].slice, |
| pop = [].pop, |
| each = Utils.each, |
| isString = Utils.isString, |
| isFunction = Utils.isFunction, |
| isNumber = Utils.isNumber, |
| timers = [], |
| global = this, |
| NUMBER = /\d+/; |
| |
| // 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; |
| })(); |
| |
| function isCoercableNumber(number) { |
| return isNumber(number) || NUMBER.test(number); |
| } |
| |
| 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 = []; |
| } |
| |
| Backburner.prototype = { |
| queueNames: null, |
| options: null, |
| currentInstance: null, |
| instanceStack: null, |
| |
| begin: function() { |
| var options = this.options, |
| onBegin = options && options.onBegin, |
| 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, |
| onEnd = options && options.onEnd, |
| currentInstance = this.currentInstance, |
| 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(); |
| } |
| } |
| } |
| }, |
| |
| 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, |
| args = arguments.length > 3 ? slice.call(arguments, 3) : 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, |
| args = arguments.length > 3 ? slice.call(arguments, 3) : undefined; |
| if (!this.currentInstance) { |
| createAutorun(this); |
| } |
| return this.currentInstance.schedule(queueName, target, method, args, true, stack); |
| }, |
| |
| setTimeout: function() { |
| var args = slice.call(arguments), |
| 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 = ( + new Date()) + 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, timers); |
| |
| timers.splice(i, 0, executeAt, fn); |
| |
| updateLaterTimer(this, executeAt, wait); |
| |
| return fn; |
| }, |
| |
| throttle: function(target, method /* , args, wait, [immediate] */ |
| ) { |
| var self = this, |
| args = arguments, |
| immediate = pop.call(args), |
| 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) { |
| self.run.apply(self, args); |
| } |
| var index = findThrottler(target, method, self._throttlers); |
| if (index > -1) { |
| self._throttlers.splice(index, 1); |
| } |
| }, wait); |
| |
| if (immediate) { |
| self.run.apply(self, args); |
| } |
| |
| throttler = [target, method, timer]; |
| |
| this._throttlers.push(throttler); |
| |
| return throttler; |
| }, |
| |
| debounce: function(target, method /* , args, wait, [immediate] */ |
| ) { |
| var self = this, |
| args = arguments, |
| immediate = pop.call(args), |
| 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) { |
| self.run.apply(self, args); |
| } |
| var index = findDebouncee(target, method, self._debouncees); |
| if (index > -1) { |
| self._debouncees.splice(index, 1); |
| } |
| }, wait); |
| |
| if (immediate && index === -1) { |
| self.run.apply(self, args); |
| } |
| |
| debouncee = [target, method, timer]; |
| |
| self._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; |
| } |
| timers = []; |
| |
| if (this._autorun) { |
| clearTimeout(this._autorun); |
| this._autorun = null; |
| } |
| }, |
| |
| hasTimers: function() { |
| return !!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 = timers.length; i < l; i += 2) { |
| if (timers[i + 1] === timer) { |
| timers.splice(i, 2); // remove the two elements |
| 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 wrapInTryCatch(func) { |
| return function () { |
| try { |
| return func.apply(this, arguments); |
| } catch (e) { |
| throw e; |
| } |
| }; |
| } |
| |
| 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(self, executeAt, wait) { |
| if (!self._laterTimer || executeAt < self._laterTimerExpiresAt) { |
| self._laterTimer = global.setTimeout(function() { |
| self._laterTimer = null; |
| self._laterTimerExpiresAt = null; |
| executeTimers(self); |
| }, wait); |
| self._laterTimerExpiresAt = executeAt; |
| } |
| } |
| |
| function executeTimers(self) { |
| var now = + new Date(), |
| time, fns, i, l; |
| |
| self.run(function() { |
| i = searchTimer(now, timers); |
| |
| fns = timers.splice(0, i); |
| |
| for (i = 1, l = fns.length; i < l; i += 2) { |
| self.schedule(self.options.defaultQueue, null, fns[i]); |
| } |
| }); |
| |
| if (timers.length) { |
| updateLaterTimer(self, timers[0], timers[0] - now); |
| } |
| } |
| |
| 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, |
| index = -1; |
| |
| for (var i = 0, l = collection.length; i < l; i++) { |
| item = collection[i]; |
| if (item[0] === target && item[1] === method) { |
| index = i; |
| break; |
| } |
| } |
| |
| return index; |
| } |
| |
| function searchTimer(time, timers) { |
| var start = 0, |
| end = timers.length - 2, |
| 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; |
| } |
| |
| __exports__.Backburner = Backburner; |
| }); |
| define("backburner/deferred_action_queues", |
| ["backburner/utils", "backburner/queue", "exports"], |
| function(__dependency1__, __dependency2__, __exports__) { |
| "use strict"; |
| var Utils = __dependency1__["default"]; |
| var Queue = __dependency2__.Queue; |
| |
| var each = Utils.each, |
| isString = Utils.isString; |
| |
| function DeferredActionQueues(queueNames, options) { |
| var queues = this.queues = {}; |
| this.queueNames = queueNames = queueNames || []; |
| |
| this.options = options; |
| |
| each(queueNames, function(queueName) { |
| queues[queueName] = new Queue(this, queueName, options); |
| }); |
| } |
| |
| DeferredActionQueues.prototype = { |
| queueNames: null, |
| queues: null, |
| options: null, |
| |
| schedule: function(queueName, target, method, args, onceFlag, stack) { |
| var queues = this.queues, |
| queue = queues[queueName]; |
| |
| if (!queue) { |
| throw new Error("You attempted to schedule an action in a queue (" + queueName + ") that doesn't exist"); |
| } |
| |
| if (onceFlag) { |
| return queue.pushUnique(target, method, args, stack); |
| } else { |
| return queue.push(target, method, args, stack); |
| } |
| }, |
| |
| invoke: function(target, method, args, _) { |
| if (args && args.length > 0) { |
| method.apply(target, args); |
| } else { |
| method.call(target); |
| } |
| }, |
| |
| invokeWithOnError: function(target, method, args, onError) { |
| try { |
| if (args && args.length > 0) { |
| method.apply(target, args); |
| } else { |
| method.call(target); |
| } |
| } catch (error) { |
| onError(error); |
| } |
| }, |
| |
| flush: function() { |
| var queues = this.queues, |
| queueNames = this.queueNames, |
| queueName, queue, queueItems, priorQueueNameIndex, |
| queueNameIndex = 0, numberOfQueues = queueNames.length, |
| options = this.options, |
| onError = options.onError || (options.onErrorTarget && options.onErrorTarget[options.onErrorMethod]), |
| invoke = onError ? this.invokeWithOnError : this.invoke; |
| |
| outerloop: |
| while (queueNameIndex < numberOfQueues) { |
| queueName = queueNames[queueNameIndex]; |
| queue = queues[queueName]; |
| queueItems = queue._queueBeingFlushed = queue._queue.slice(); |
| queue._queue = []; |
| |
| var queueOptions = queue.options, // TODO: write a test for this |
| before = queueOptions && queueOptions.before, |
| after = queueOptions && queueOptions.after, |
| target, method, args, stack, |
| queueIndex = 0, numberOfQueueItems = queueItems.length; |
| |
| if (numberOfQueueItems && before) { |
| before(); |
| } |
| |
| while (queueIndex < numberOfQueueItems) { |
| target = queueItems[queueIndex]; |
| method = queueItems[queueIndex + 1]; |
| args = queueItems[queueIndex + 2]; |
| stack = queueItems[queueIndex + 3]; // Debugging assistance |
| |
| if (isString(method)) { |
| method = target[method]; |
| } |
| |
| // method could have been nullified / canceled during flush |
| if (method) { |
| invoke(target, method, args, onError); |
| } |
| |
| queueIndex += 4; |
| } |
| |
| queue._queueBeingFlushed = null; |
| if (numberOfQueueItems && after) { |
| after(); |
| } |
| |
| if ((priorQueueNameIndex = indexOfPriorQueueWithActions(this, queueNameIndex)) !== -1) { |
| queueNameIndex = priorQueueNameIndex; |
| continue outerloop; |
| } |
| |
| queueNameIndex++; |
| } |
| } |
| }; |
| |
| function indexOfPriorQueueWithActions(daq, currentQueueIndex) { |
| var queueName, queue; |
| |
| for (var i = 0, l = currentQueueIndex; i <= l; i++) { |
| queueName = daq.queueNames[i]; |
| queue = daq.queues[queueName]; |
| if (queue._queue.length) { |
| return i; |
| } |
| } |
| |
| return -1; |
| } |
| |
| __exports__.DeferredActionQueues = DeferredActionQueues; |
| }); |
| define("backburner/queue", |
| ["exports"], |
| function(__exports__) { |
| "use strict"; |
| function Queue(daq, name, options) { |
| this.daq = daq; |
| this.name = name; |
| this.globalOptions = options; |
| this.options = options[name]; |
| this._queue = []; |
| } |
| |
| Queue.prototype = { |
| daq: null, |
| name: null, |
| options: null, |
| onError: null, |
| _queue: null, |
| |
| push: function(target, method, args, stack) { |
| var queue = this._queue; |
| queue.push(target, method, args, stack); |
| return { |
| queue: this, |
| target: target, |
| method: method |
| }; |
| }, |
| |
| pushUnique: function(target, method, args, stack) { |
| var queue = this._queue, currentTarget, currentMethod, i, l; |
| |
| for (i = 0, l = queue.length; i < l; i += 4) { |
| currentTarget = queue[i]; |
| currentMethod = queue[i + 1]; |
| |
| if (currentTarget === target && currentMethod === method) { |
| queue[i + 2] = args; // replace args |
| queue[i + 3] = stack; // replace stack |
| return { |
| queue: this, |
| target: target, |
| method: method |
| }; |
| } |
| } |
| |
| queue.push(target, method, args, stack); |
| return { |
| queue: this, |
| target: target, |
| method: method |
| }; |
| }, |
| |
| // TODO: remove me, only being used for Ember.run.sync |
| flush: function() { |
| var queue = this._queue, |
| globalOptions = this.globalOptions, |
| options = this.options, |
| before = options && options.before, |
| after = options && options.after, |
| onError = globalOptions.onError || (globalOptions.onErrorTarget && globalOptions.onErrorTarget[globalOptions.onErrorMethod]), |
| target, method, args, stack, i, l = queue.length; |
| |
| if (l && before) { |
| before(); |
| } |
| for (i = 0; i < l; i += 4) { |
| target = queue[i]; |
| method = queue[i + 1]; |
| args = queue[i + 2]; |
| stack = queue[i + 3]; // Debugging assistance |
| |
| // TODO: error handling |
| if (args && args.length > 0) { |
| if (onError) { |
| try { |
| method.apply(target, args); |
| } catch (e) { |
| onError(e); |
| } |
| } else { |
| method.apply(target, args); |
| } |
| } else { |
| if (onError) { |
| try { |
| method.call(target); |
| } catch (e) { |
| onError(e); |
| } |
| } else { |
| method.call(target); |
| } |
| } |
| } |
| if (l && after) { |
| after(); |
| } |
| |
| // check if new items have been added |
| if (queue.length > l) { |
| this._queue = queue.slice(l); |
| this.flush(); |
| } else { |
| this._queue.length = 0; |
| } |
| }, |
| |
| cancel: function(actionToCancel) { |
| var queue = this._queue, currentTarget, currentMethod, i, l; |
| |
| for (i = 0, l = queue.length; i < l; i += 4) { |
| currentTarget = queue[i]; |
| currentMethod = queue[i + 1]; |
| |
| if (currentTarget === actionToCancel.target && currentMethod === actionToCancel.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 === actionToCancel.target && currentMethod === actionToCancel.method) { |
| // don't mess with array during flush |
| // just nullify the method |
| queue[i + 1] = null; |
| return true; |
| } |
| } |
| } |
| }; |
| |
| __exports__.Queue = Queue; |
| }); |
| define("backburner/utils", |
| ["exports"], |
| function(__exports__) { |
| "use strict"; |
| __exports__["default"] = { |
| each: function(collection, callback) { |
| for (var i = 0; i < collection.length; i++) { |
| callback(collection[i]); |
| } |
| }, |
| |
| isString: function(suspect) { |
| return typeof suspect === 'string'; |
| }, |
| |
| isFunction: function(suspect) { |
| return typeof suspect === 'function'; |
| }, |
| |
| isNumber: function(suspect) { |
| return typeof suspect === 'number'; |
| } |
| }; |
| }); |
| |
| define("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; |
| }); |
| define("container/container", |
| ["container/inheriting_dict", "ember-metal/core", "exports"], |
| function(__dependency1__, __dependency2__, __exports__) { |
| "use strict"; |
| var InheritingDict = __dependency1__["default"]; |
| var Ember = __dependency2__["default"]; |
| // Ember.assert |
| |
| // 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 = new InheritingDict(parent && parent.registry); |
| this.cache = new InheritingDict(parent && parent.cache); |
| this.factoryCache = new InheritingDict(parent && parent.factoryCache); |
| this.resolveCache = new InheritingDict(parent && parent.resolveCache); |
| this.typeInjections = new InheritingDict(parent && parent.typeInjections); |
| this.injections = {}; |
| |
| this.factoryTypeInjections = new InheritingDict(parent && parent.factoryTypeInjections); |
| this.factoryInjections = {}; |
| |
| this._options = new InheritingDict(parent && parent._options); |
| this._typeOptions = new InheritingDict(parent && parent._typeOptions); |
| } |
| |
| 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; |
| }, |
| |
| /** |
| Sets a key-value pair on the current container. If a parent container, |
| has the same key, once set on a child, the parent and child will diverge |
| as expected. |
| |
| @method set |
| @param {Object} object |
| @param {String} key |
| @param {any} value |
| */ |
| set: function(object, key, value) { |
| object[key] = value; |
| }, |
| |
| /** |
| 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 (this.cache.has(normalizedName)) { |
| throw new Error('Cannot re-register: `' + fullName + '`, as it has already been looked up.'); |
| } |
| |
| this.registry.set(normalizedName, factory); |
| this._options.set(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); |
| |
| this.registry.remove(normalizedName); |
| this.cache.remove(normalizedName); |
| this.factoryCache.remove(normalizedName); |
| this.resolveCache.remove(normalizedName); |
| this._options.remove(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 normalize |
| @param {String} fullName |
| @return {string} normalized fullName |
| */ |
| normalize: function(fullName) { |
| return 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.set(type, options); |
| }, |
| |
| /** |
| @method options |
| @param {String} type |
| @param {Object} options |
| */ |
| options: function(type, options) { |
| this.optionsForType(type, 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.has(normalizedName)) { |
| throw new Error("Attempted to register an injection for a type that has already been looked up. ('" + normalizedName + "', '" + property + "', '" + injectionName + "')"); |
| } |
| addInjection(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.has(normalizedName)) { |
| throw new Error('Attempted to register a factoryInjection for a type that has already ' + |
| 'been looked up. (\'' + normalizedName + '\', \'' + property + '\', \'' + injectionName + '\')'); |
| } |
| |
| addInjection(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.get(normalizedName); |
| if (cached) { |
| return cached; |
| } |
| |
| var resolved = container.resolver(normalizedName) || container.registry.get(normalizedName); |
| container.resolveCache.set(normalizedName, resolved); |
| |
| return resolved; |
| } |
| |
| function has(container, fullName) { |
| if (container.cache.has(fullName)) { |
| return true; |
| } |
| |
| return !!container.resolve(fullName); |
| } |
| |
| function lookup(container, fullName, options) { |
| options = options || {}; |
| |
| if (container.cache.has(fullName) && options.singleton !== false) { |
| return container.cache.get(fullName); |
| } |
| |
| var value = instantiate(container, fullName); |
| |
| if (value === undefined) { |
| return; |
| } |
| |
| if (isSingleton(container, fullName) && options.singleton !== false) { |
| container.cache.set(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; |
| } |
| |
| var injection, injectable; |
| |
| for (var i = 0, length = injections.length; i < length; i++) { |
| injection = injections[i]; |
| injectable = lookup(container, injection.fullName); |
| |
| if (injectable !== undefined) { |
| hash[injection.property] = injectable; |
| } else { |
| throw new Error('Attempting to inject an unknown injection: `' + injection.fullName + '`'); |
| } |
| } |
| |
| return hash; |
| } |
| |
| function option(container, fullName, optionName) { |
| var options = container._options.get(fullName); |
| |
| if (options && options[optionName] !== undefined) { |
| return options[optionName]; |
| } |
| |
| var type = fullName.split(':')[0]; |
| options = container._typeOptions.get(type); |
| |
| if (options) { |
| return options[optionName]; |
| } |
| } |
| |
| function factoryFor(container, fullName) { |
| var cache = container.factoryCache; |
| if (cache.has(fullName)) { |
| return cache.get(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')) { |
| // TODO: think about a 'safe' merge style extension |
| // for now just fallback to create time injection |
| 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); |
| |
| cache.set(fullName, injectedFactory); |
| |
| return injectedFactory; |
| } |
| } |
| |
| function injectionsFor(container, fullName) { |
| var splitName = fullName.split(':'), |
| type = splitName[0], |
| injections = []; |
| |
| injections = injections.concat(container.typeInjections.get(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(':'), |
| type = splitName[0], |
| factoryInjections = []; |
| |
| factoryInjections = factoryInjections.concat(container.factoryTypeInjections.get(type) || []); |
| factoryInjections = factoryInjections.concat(container.factoryInjections[fullName] || []); |
| |
| factoryInjections = buildInjections(container, factoryInjections); |
| factoryInjections._debugContainerKey = fullName; |
| |
| return factoryInjections; |
| } |
| |
| function instantiate(container, fullName) { |
| var factory = factoryFor(container, fullName); |
| |
| 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.'); |
| } |
| |
| 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) { |
| container.cache.eachLocal(function(key, value) { |
| if (option(container, key, 'instantiate') === false) { |
| return; |
| } |
| callback(value); |
| }); |
| } |
| |
| function resetCache(container) { |
| container.cache.eachLocal(function(key, value) { |
| if (option(container, key, 'instantiate') === false) { |
| return; |
| } |
| value.destroy(); |
| }); |
| container.cache.dict = {}; |
| } |
| |
| function addTypeInjection(rules, type, property, fullName) { |
| var injections = rules.get(type); |
| |
| if (!injections) { |
| injections = []; |
| rules.set(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 addInjection(rules, factoryName, property, injectionName) { |
| var injections = rules[factoryName] = rules[factoryName] || []; |
| injections.push({ |
| property: property, |
| fullName: injectionName |
| }); |
| } |
| |
| __exports__["default"] = Container; |
| }); |
| define("container/inheriting_dict", |
| ["exports"], |
| function(__exports__) { |
| "use strict"; |
| // A safe and simple inheriting object. |
| function InheritingDict(parent) { |
| this.parent = parent; |
| this.dict = {}; |
| } |
| |
| InheritingDict.prototype = { |
| |
| /** |
| @property parent |
| @type InheritingDict |
| @default null |
| */ |
| |
| parent: null, |
| |
| /** |
| Object used to store the current nodes data. |
| |
| @property dict |
| @type Object |
| @default Object |
| */ |
| dict: null, |
| |
| /** |
| Retrieve the value given a key, if the value is present at the current |
| level use it, otherwise walk up the parent hierarchy and try again. If |
| no matching key is found, return undefined. |
| |
| @method get |
| @param {String} key |
| @return {any} |
| */ |
| get: function(key) { |
| var dict = this.dict; |
| |
| if (dict.hasOwnProperty(key)) { |
| return dict[key]; |
| } |
| |
| if (this.parent) { |
| return this.parent.get(key); |
| } |
| }, |
| |
| /** |
| Set the given value for the given key, at the current level. |
| |
| @method set |
| @param {String} key |
| @param {Any} value |
| */ |
| set: function(key, value) { |
| this.dict[key] = value; |
| }, |
| |
| /** |
| Delete the given key |
| |
| @method remove |
| @param {String} key |
| */ |
| remove: function(key) { |
| delete this.dict[key]; |
| }, |
| |
| /** |
| Check for the existence of given a key, if the key is present at the current |
| level return true, otherwise walk up the parent hierarchy and try again. If |
| no matching key is found, return false. |
| |
| @method has |
| @param {String} key |
| @return {Boolean} |
| */ |
| has: function(key) { |
| var dict = this.dict; |
| |
| if (dict.hasOwnProperty(key)) { |
| return true; |
| } |
| |
| if (this.parent) { |
| return this.parent.has(key); |
| } |
| |
| return false; |
| }, |
| |
| /** |
| Iterate and invoke a callback for each local key-value pair. |
| |
| @method eachLocal |
| @param {Function} callback |
| @param {Object} binding |
| */ |
| eachLocal: function(callback, binding) { |
| var dict = this.dict; |
| |
| for (var prop in dict) { |
| if (dict.hasOwnProperty(prop)) { |
| callback.call(binding, prop, dict[prop]); |
| } |
| } |
| } |
| }; |
| |
| __exports__["default"] = InheritingDict; |
| }); |
| define("ember-application", |
| ["ember-metal/core", "ember-runtime/system/lazy_load", "ember-application/system/dag", "ember-application/system/resolver", "ember-application/system/application", "ember-application/ext/controller"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| var runLoadHooks = __dependency2__.runLoadHooks; |
| |
| /** |
| Ember Application |
| |
| @module ember |
| @submodule ember-application |
| @requires ember-views, ember-routing |
| */ |
| |
| var DAG = __dependency3__["default"]; |
| var Resolver = __dependency4__.Resolver; |
| var DefaultResolver = __dependency4__["default"]; |
| var Application = __dependency5__["default"]; |
| // side effect of extending ControllerMixin |
| |
| Ember.Application = Application; |
| Ember.DAG = DAG; |
| Ember.Resolver = Resolver; |
| Ember.DefaultResolver = DefaultResolver; |
| |
| runLoadHooks('Ember.Application', Application); |
| }); |
| define("ember-application/ext/controller", |
| ["ember-metal/core", "ember-metal/property_get", "ember-metal/property_set", "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__, __dependency8__, __exports__) { |
| "use strict"; |
| /** |
| @module ember |
| @submodule ember-application |
| */ |
| |
| var Ember = __dependency1__["default"]; |
| // Ember.assert |
| var get = __dependency2__.get; |
| var set = __dependency3__.set; |
| var EmberError = __dependency4__["default"]; |
| var inspect = __dependency5__.inspect; |
| var computed = __dependency6__.computed; |
| var ControllerMixin = __dependency7__["default"]; |
| var meta = __dependency5__.meta; |
| var controllerFor = __dependency8__["default"]; |
| |
| function verifyNeedsDependencies(controller, container, needs) { |
| var dependency, i, l, 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, |
| 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; |
| }); |
| define("ember-application/system/application", |
| ["ember-metal", "ember-metal/property_get", "ember-metal/property_set", "ember-runtime/system/lazy_load", "ember-application/system/dag", "ember-runtime/system/namespace", "ember-runtime/mixins/deferred", "ember-application/system/resolver", "ember-metal/platform", "ember-metal/run_loop", "ember-metal/utils", "container/container", "ember-runtime/controllers/controller", "ember-metal/enumerable_utils", "ember-runtime/controllers/object_controller", "ember-runtime/controllers/array_controller", "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-metal/core", "ember-handlebars-compiler", "ember-application/system/deprecated-container", "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 Ember = __dependency1__["default"]; |
| // Ember.FEATURES, Ember.deprecate, Ember.assert, Ember.libraries, LOG_VERSION, Namespace, BOOTED |
| var get = __dependency2__.get; |
| var set = __dependency3__.set; |
| var runLoadHooks = __dependency4__.runLoadHooks; |
| var DAG = __dependency5__["default"]; |
| var Namespace = __dependency6__["default"]; |
| var DeferredMixin = __dependency7__["default"]; |
| var DefaultResolver = __dependency8__["default"]; |
| var create = __dependency9__.create; |
| var run = __dependency10__["default"]; |
| var canInvoke = __dependency11__.canInvoke; |
| var Container = __dependency12__["default"]; |
| var Controller = __dependency13__["default"]; |
| var EnumerableUtils = __dependency14__["default"]; |
| var ObjectController = __dependency15__["default"]; |
| var ArrayController = __dependency16__["default"]; |
| var EventDispatcher = __dependency17__["default"]; |
| //import ContainerDebugAdapter from "ember-extension-support/container_debug_adapter"; |
| var jQuery = __dependency18__["default"]; |
| var Route = __dependency19__["default"]; |
| var Router = __dependency20__["default"]; |
| var HashLocation = __dependency21__["default"]; |
| var HistoryLocation = __dependency22__["default"]; |
| var AutoLocation = __dependency23__["default"]; |
| var NoneLocation = __dependency24__["default"]; |
| var BucketCache = __dependency25__["default"]; |
| |
| var K = __dependency26__.K; |
| var EmberHandlebars = __dependency27__["default"]; |
| var DeprecatedContainer = __dependency28__["default"]; |
| |
| var ContainerDebugAdapter; |
| |
| /** |
| 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 |
| 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 |
| window.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 |
| window.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 |
| App = Ember.Application.create({ |
| customEvents: { |
| // add support for the paste event |
| paste: "paste" |
| } |
| }); |
| ``` |
| |
| @property customEvents |
| @type Object |
| @default null |
| */ |
| customEvents: null, |
| |
| // Start off the number of deferrals at 1. This will be |
| // decremented by the Application's own `initialize` method. |
| _readinessDeferrals: 1, |
| |
| init: function() { |
| if (!this.$) { |
| this.$ = jQuery; |
| } |
| this.__container__ = this.buildContainer(); |
| |
| this.Router = this.defaultRouter(); |
| |
| this._super(); |
| |
| this.scheduleInitialize(); |
| |
| Ember.libraries.registerCoreLibrary('Handlebars', EmberHandlebars.VERSION); |
| Ember.libraries.registerCoreLibrary('jQuery', jQuery().jquery); |
| |
| if ( Ember.LOG_VERSION ) { |
| Ember.LOG_VERSION = false; // we only need to see this once per Application#init |
| |
| var nameLengths = EnumerableUtils.map(Ember.libraries, function(item) { |
| return get(item, "name.length"); |
| }); |
| |
| var maxNameLength = Math.max.apply(this, nameLengths); |
| |
| Ember.debug('-------------------------------'); |
| Ember.libraries.each(function(name, version) { |
| var spaces = new Array(maxNameLength - name.length + 1).join(" "); |
| Ember.debug([name, spaces, ' : ', 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() { |
| var self = this; |
| |
| if (!this.$ || this.$.isReady) { |
| run.schedule('actions', self, '_initialize'); |
| } else { |
| this.$().ready(function runInitialize() { |
| run(self, '_initialize'); |
| }); |
| } |
| }, |
| |
| /** |
| Use this to defer readiness until some condition is true. |
| |
| Example: |
| |
| ```javascript |
| App = Ember.Application.create(); |
| App.deferReadiness(); |
| |
| jQuery.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(), |
| 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(), |
| 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. |
| |
| 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("first 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, function() { |
| this._initialize(); |
| }); |
| } |
| |
| run.join(this, handleReset); |
| }, |
| |
| /** |
| @private |
| @method runInitializers |
| */ |
| runInitializers: function() { |
| var initializers = get(this.constructor, 'initializers'); |
| var container = this.__container__; |
| var graph = new DAG(); |
| var namespace = this; |
| var name, initializer; |
| |
| for (name in initializers) { |
| initializer = initializers[name]; |
| 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.'); |
| |
| this._super.apply(this, arguments); |
| } |
| }); |
| |
| Application.reopenClass({ |
| initializers: {}, |
| |
| /** |
| 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')); |
| |
| 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.defaultContainer = new DeprecatedContainer(container); |
| |
| container.set = set; |
| container.resolver = resolverFor(namespace); |
| container.normalize = 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('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 |
| |
| // ES6TODO: resolve this via import once ember-application package is ES6'ed |
| if (!ContainerDebugAdapter) { |
| ContainerDebugAdapter = requireModule('ember-extension-support/container_debug_adapter')['default']; |
| } |
| 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) { |
| if (namespace.get('resolver')) { |
| Ember.deprecate('Application.resolver is deprecated in favor of Application.Resolver', false); |
| } |
| |
| 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; |
| }); |
| define("ember-application/system/dag", |
| ["ember-metal/error", "exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| var EmberError = __dependency1__["default"]; |
| |
| 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(); |
| } |
| |
| function DAG() { |
| this.names = []; |
| this.vertices = {}; |
| } |
| |
| DAG.prototype.add = function(name) { |
| if (!name) { |
| return; |
| } |
| if (this.vertices.hasOwnProperty(name)) { |
| return this.vertices[name]; |
| } |
| var vertex = { |
| name: name, |
| incoming: {}, |
| incomingNames: [], |
| hasOutgoing: false, |
| value: null |
| }; |
| this.vertices[name] = vertex; |
| this.names.push(name); |
| return vertex; |
| }; |
| |
| DAG.prototype.map = function(name, value) { |
| this.add(name).value = value; |
| }; |
| |
| DAG.prototype.addEdge = function(fromName, toName) { |
| if (!fromName || !toName || fromName === toName) { |
| return; |
| } |
| var from = this.add(fromName), to = this.add(toName); |
| if (to.incoming.hasOwnProperty(fromName)) { |
| return; |
| } |
| function checkCycle(vertex, path) { |
| if (vertex.name === toName) { |
| throw new EmberError("cycle detected: " + toName + " <- " + path.join(" <- ")); |
| } |
| } |
| visit(from, checkCycle); |
| from.hasOutgoing = true; |
| to.incoming[fromName] = from; |
| to.incomingNames.push(fromName); |
| }; |
| |
| 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); |
| } |
| } |
| }; |
| |
| 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; |
| }); |
| define("ember-application/system/deprecated-container", |
| ["exports"], |
| function(__exports__) { |
| "use strict"; |
| function DeprecatedContainer(container) { |
| this._container = container; |
| } |
| |
| DeprecatedContainer.deprecate = function(method) { |
| return function() { |
| var container = this._container; |
| |
| Ember.deprecate('Using the defaultContainer is no longer supported. [defaultContainer#' + method + '] see: http://git.io/EKPpnA', false); |
| return container[method].apply(container, arguments); |
| }; |
| }; |
| |
| DeprecatedContainer.prototype = { |
| _container: null, |
| lookup: DeprecatedContainer.deprecate('lookup'), |
| resolve: DeprecatedContainer.deprecate('resolve'), |
| register: DeprecatedContainer.deprecate('register') |
| }; |
| |
| __exports__["default"] = DeprecatedContainer; |
| }); |
| define("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-handlebars", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __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 EmberHandlebars = __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 |
| */ |
| |
| __exports__["default"] = EmberObject.extend({ |
| /** |
| This will be set to the Application instance when it is |
| created. |
| |
| @property namespace |
| */ |
| namespace: null, |
| |
| normalize: function(fullName) { |
| var split = fullName.split(':', 2), |
| type = split[0], |
| 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), |
| resolveMethodName = parsedName.resolveMethodName, |
| 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) { |
| var nameParts = fullName.split(':'), |
| type = nameParts[0], fullNameWithoutType = nameParts[1], |
| name = fullNameWithoutType, |
| namespace = get(this, 'namespace'), |
| 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); |
| |
| if (parsedName.type === 'template') { |
| return 'template at ' + parsedName.fullNameWithoutType.replace(/\./g, '/'); |
| } |
| |
| var description = parsedName.root + '.' + classify(parsedName.name); |
| 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) || EmberHandlebars.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)); |
| } |
| }); |
| }); |
| define("ember-debug", |
| ["ember-metal/core", "ember-metal/error", "ember-metal/logger"], |
| function(__dependency1__, __dependency2__, __dependency3__) { |
| "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) { |
| if (!test) { |
| 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. |
| */ |
| Ember.deprecate = function(message, test) { |
| if (test) { |
| 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 (Ember.LOG_STACKTRACE_ON_DEPRECATION && error.stack) { |
| var stack, 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(); |
| }; |
| |
| // Inform the developer about the Ember Inspector if not installed. |
| if (!Ember.testing) { |
| 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); |
| } |
| } |
| }); |
| define("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; |
| }); |
| define("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 EmberObject |
| @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(), self = this; |
| 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; |
| } |
| }); |
| }); |
| define("ember-extension-support/data_adapter", |
| ["ember-metal/core", "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__, __dependency8__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| var get = __dependency2__.get; |
| var run = __dependency3__["default"]; |
| var dasherize = __dependency4__.dasherize; |
| var Namespace = __dependency5__["default"]; |
| var EmberObject = __dependency6__["default"]; |
| var emberA = __dependency7__.A; |
| var Application = __dependency8__["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(), |
| self = this, typesToSend, releaseMethods = emberA(); |
| |
| 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: Ember.K |
| }; |
| 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, records = this.getRecords(type); |
| |
| var onChange = function() { |
| typesUpdated([self.wrapModelType(type)]); |
| }; |
| var observer = { |
| didChange: function() { |
| run.scheduleOnce('actions', this, onChange); |
| }, |
| willChange: Ember.K |
| }; |
| |
| 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 release, records = this.getRecords(type), |
| typeToSend, self = this; |
| |
| 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 types, self = this, |
| containerDebugAdapter = this.get('containerDebugAdapter'); |
| |
| 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), |
| types = emberA(), |
| 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 |
| }, columnValues = {}, self = this; |
| |
| 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() {}; |
| } |
| }); |
| }); |
| define("ember-extension-support/initializers", |
| [], |
| function() { |
| "use strict"; |
| |
| }); |
| define("ember-handlebars-compiler", |
| ["ember-metal/core", "exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| /* global Handlebars:true */ |
| |
| /** |
| @module ember |
| @submodule ember-handlebars-compiler |
| */ |
| |
| var Ember = __dependency1__["default"]; |
| |
| // ES6Todo: you'll need to import debugger once debugger is es6'd. |
| if (typeof Ember.assert === 'undefined') { |
| Ember.assert = function() {}; |
| } |
| if (typeof Ember.FEATURES === 'undefined') { |
| Ember.FEATURES = { |
| isEnabled: function() {} |
| }; |
| } |
| |
| var objectCreate = Object.create || function(parent) { |
| function F() {} |
| F.prototype = parent; |
| return new F(); |
| }; |
| |
| // set up for circular references later |
| var View, Component; |
| |
| // ES6Todo: when ember-debug is es6'ed import this. |
| // var emberAssert = Ember.assert; |
| var Handlebars = (Ember.imports && Ember.imports.Handlebars) || (this && this.Handlebars); |
| if (!Handlebars && typeof require === 'function') { |
| Handlebars = require('handlebars'); |
| } |
| |
| Ember.assert("Ember Handlebars requires Handlebars version 1.0 or 1.1. Include " + |
| "a SCRIPT tag in the HTML HEAD linking to the Handlebars file " + |
| "before you link to Ember.", Handlebars); |
| |
| Ember.assert("Ember Handlebars requires Handlebars version 1.0 or 1.1, " + |
| "COMPILER_REVISION expected: 4, got: " + Handlebars.COMPILER_REVISION + |
| " - Please note: Builds of master may have other COMPILER_REVISION values.", |
| Handlebars.COMPILER_REVISION === 4); |
| |
| /** |
| Prepares the Handlebars templating library for use inside Ember's view |
| system. |
| |
| The `Ember.Handlebars` object is the standard Handlebars library, extended to |
| use Ember's `get()` method instead of direct property access, which allows |
| computed properties to be used inside templates. |
| |
| To create an `Ember.Handlebars` template, call `Ember.Handlebars.compile()`. |
| This will return a function that can be used by `Ember.View` for rendering. |
| |
| @class Handlebars |
| @namespace Ember |
| */ |
| var EmberHandlebars = Ember.Handlebars = objectCreate(Handlebars); |
| |
| /** |
| Register a bound helper or custom view helper. |
| |
| ## Simple bound helper example |
| |
| ```javascript |
| Ember.Handlebars.helper('capitalize', function(value) { |
| return value.toUpperCase(); |
| }); |
| ``` |
| |
| 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. |
| |
| For more examples of bound helpers, see documentation for |
| `Ember.Handlebars.registerBoundHelper`. |
| |
| ## Custom view helper example |
| |
| Assuming a view subclass named `App.CalendarView` were defined, a helper |
| for rendering instances of this view could be registered as follows: |
| |
| ```javascript |
| Ember.Handlebars.helper('calendar', App.CalendarView): |
| ``` |
| |
| The above bound helper can be used inside of templates as follows: |
| |
| ```handlebars |
| {{calendar}} |
| ``` |
| |
| Which is functionally equivalent to: |
| |
| ```handlebars |
| {{view App.CalendarView}} |
| ``` |
| |
| Options in the helper will be passed to the view in exactly the same |
| manner as with the `view` helper. |
| |
| @method helper |
| @for Ember.Handlebars |
| @param {String} name |
| @param {Function|Ember.View} function or view class constructor |
| @param {String} dependentKeys* |
| */ |
| EmberHandlebars.helper = function(name, value) { |
| if (!View) { |
| View = requireModule('ember-views/views/view')['default']; |
| } |
| // ES6TODO: stupid circular dep |
| if (!Component) { |
| Component = requireModule('ember-views/views/component')['default']; |
| } |
| // ES6TODO: stupid circular dep |
| |
| 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)) { |
| EmberHandlebars.registerHelper(name, EmberHandlebars.makeViewHelper(value)); |
| } else { |
| EmberHandlebars.registerBoundHelper.apply(null, arguments); |
| } |
| }; |
| |
| /** |
| 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 |
| @for Ember.Handlebars |
| @param {Function} ViewClass view class constructor |
| @since 1.2.0 |
| */ |
| EmberHandlebars.makeViewHelper = function(ViewClass) { |
| return function(options) { |
| Ember.assert("You can only pass attributes (such as name=value) not bare values to a helper for a View found in '" + ViewClass.toString() + "'", arguments.length < 2); |
| return EmberHandlebars.helpers.view.call(this, ViewClass, options); |
| }; |
| }; |
| |
| /** |
| @class helpers |
| @namespace Ember.Handlebars |
| */ |
| EmberHandlebars.helpers = objectCreate(Handlebars.helpers); |
| |
| /** |
| Override the the opcode compiler and JavaScript compiler for Handlebars. |
| |
| @class Compiler |
| @namespace Ember.Handlebars |
| @private |
| @constructor |
| */ |
| EmberHandlebars.Compiler = function() {}; |
| |
| // Handlebars.Compiler doesn't exist in runtime-only |
| if (Handlebars.Compiler) { |
| EmberHandlebars.Compiler.prototype = objectCreate(Handlebars.Compiler.prototype); |
| } |
| |
| EmberHandlebars.Compiler.prototype.compiler = EmberHandlebars.Compiler; |
| |
| /** |
| @class JavaScriptCompiler |
| @namespace Ember.Handlebars |
| @private |
| @constructor |
| */ |
| EmberHandlebars.JavaScriptCompiler = function() {}; |
| |
| // Handlebars.JavaScriptCompiler doesn't exist in runtime-only |
| if (Handlebars.JavaScriptCompiler) { |
| EmberHandlebars.JavaScriptCompiler.prototype = objectCreate(Handlebars.JavaScriptCompiler.prototype); |
| EmberHandlebars.JavaScriptCompiler.prototype.compiler = EmberHandlebars.JavaScriptCompiler; |
| } |
| |
| |
| EmberHandlebars.JavaScriptCompiler.prototype.namespace = "Ember.Handlebars"; |
| |
| EmberHandlebars.JavaScriptCompiler.prototype.initializeBuffer = function() { |
| return "''"; |
| }; |
| |
| /** |
| Override the default buffer for Ember Handlebars. By default, Handlebars |
| creates an empty String at the beginning of each invocation and appends to |
| it. Ember's Handlebars overrides this to append to a single shared buffer. |
| |
| @private |
| @method appendToBuffer |
| @param string {String} |
| */ |
| EmberHandlebars.JavaScriptCompiler.prototype.appendToBuffer = function(string) { |
| return "data.buffer.push(" + string + ");"; |
| }; |
| |
| // Hacks ahead: |
| // Handlebars presently has a bug where the `blockHelperMissing` hook |
| // doesn't get passed the name of the missing helper name, but rather |
| // gets passed the value of that missing helper evaluated on the current |
| // context, which is most likely `undefined` and totally useless. |
| // |
| // So we alter the compiled template function to pass the name of the helper |
| // instead, as expected. |
| // |
| // This can go away once the following is closed: |
| // https://github.com/wycats/handlebars.js/issues/634 |
| |
| var DOT_LOOKUP_REGEX = /helpers\.(.*?)\)/, |
| BRACKET_STRING_LOOKUP_REGEX = /helpers\['(.*?)'/, |
| INVOCATION_SPLITTING_REGEX = /(.*blockHelperMissing\.call\(.*)(stack[0-9]+)(,.*)/; |
| |
| EmberHandlebars.JavaScriptCompiler.stringifyLastBlockHelperMissingInvocation = function(source) { |
| var helperInvocation = source[source.length - 1], |
| helperName = (DOT_LOOKUP_REGEX.exec(helperInvocation) || BRACKET_STRING_LOOKUP_REGEX.exec(helperInvocation))[1], |
| matches = INVOCATION_SPLITTING_REGEX.exec(helperInvocation); |
| |
| source[source.length - 1] = matches[1] + "'" + helperName + "'" + matches[3]; |
| }; |
| |
| var stringifyBlockHelperMissing = EmberHandlebars.JavaScriptCompiler.stringifyLastBlockHelperMissingInvocation; |
| |
| var originalBlockValue = EmberHandlebars.JavaScriptCompiler.prototype.blockValue; |
| EmberHandlebars.JavaScriptCompiler.prototype.blockValue = function() { |
| originalBlockValue.apply(this, arguments); |
| stringifyBlockHelperMissing(this.source); |
| }; |
| |
| var originalAmbiguousBlockValue = EmberHandlebars.JavaScriptCompiler.prototype.ambiguousBlockValue; |
| EmberHandlebars.JavaScriptCompiler.prototype.ambiguousBlockValue = function() { |
| originalAmbiguousBlockValue.apply(this, arguments); |
| stringifyBlockHelperMissing(this.source); |
| }; |
| |
| /** |
| Rewrite simple mustaches from `{{foo}}` to `{{bind "foo"}}`. This means that |
| all simple mustaches in Ember's Handlebars will also set up an observer to |
| keep the DOM up to date when the underlying property changes. |
| |
| @private |
| @method mustache |
| @for Ember.Handlebars.Compiler |
| @param mustache |
| */ |
| EmberHandlebars.Compiler.prototype.mustache = function(mustache) { |
| if (!(mustache.params.length || mustache.hash)) { |
| var id = new Handlebars.AST.IdNode([{ |
| part: '_triageMustache' |
| } |
| ]); |
| |
| // Update the mustache node to include a hash value indicating whether the original node |
| // was escaped. This will allow us to properly escape values when the underlying value |
| // changes and we need to re-render the value. |
| if (!mustache.escaped) { |
| mustache.hash = mustache.hash || new Handlebars.AST.HashNode([]); |
| mustache.hash.pairs.push(["unescaped", new Handlebars.AST.StringNode("true")]); |
| } |
| mustache = new Handlebars.AST.MustacheNode([id].concat([mustache.id]), mustache.hash, !mustache.escaped); |
| } |
| |
| return Handlebars.Compiler.prototype.mustache.call(this, mustache); |
| }; |
| |
| /** |
| Used for precompilation of Ember Handlebars templates. This will not be used |
| during normal app execution. |
| |
| @method precompile |
| @for Ember.Handlebars |
| @static |
| @param {String} string The template to precompile |
| @param {Boolean} asObject optional parameter, defaulting to true, of whether or not the |
| compiled template should be returned as an Object or a String |
| */ |
| EmberHandlebars.precompile = function(string, asObject) { |
| var ast = Handlebars.parse(string); |
| |
| var options = { |
| knownHelpers: { |
| action: true, |
| unbound: true, |
| 'bind-attr': true, |
| template: true, |
| view: true, |
| _triageMustache: true |
| }, |
| data: true, |
| stringParams: true |
| }; |
| |
| asObject = asObject === undefined ? true : asObject; |
| |
| var environment = new EmberHandlebars.Compiler().compile(ast, options); |
| return new EmberHandlebars.JavaScriptCompiler().compile(environment, options, undefined, asObject); |
| }; |
| |
| // We don't support this for Handlebars runtime-only |
| if (Handlebars.compile) { |
| /** |
| The entry point for Ember Handlebars. This replaces the default |
| `Handlebars.compile` and turns on template-local data and String |
| parameters. |
| |
| @method compile |
| @for Ember.Handlebars |
| @static |
| @param {String} string The template to compile |
| @return {Function} |
| */ |
| EmberHandlebars.compile = function(string) { |
| var ast = Handlebars.parse(string); |
| var options = { |
| data: true, |
| stringParams: true |
| }; |
| var environment = new EmberHandlebars.Compiler().compile(ast, options); |
| var templateSpec = new EmberHandlebars.JavaScriptCompiler().compile(environment, options, undefined, true); |
| |
| var template = EmberHandlebars.template(templateSpec); |
| template.isMethod = false; //Make sure we don't wrap templates with ._super |
| |
| return template; |
| }; |
| } |
| |
| __exports__["default"] = EmberHandlebars; |
| }); |
| define("ember-handlebars", |
| ["ember-handlebars-compiler", "ember-metal/core", "ember-runtime/system/lazy_load", "ember-handlebars/loader", "ember-handlebars/ext", "ember-handlebars/string", "ember-handlebars/helpers/shared", "ember-handlebars/helpers/binding", "ember-handlebars/helpers/collection", "ember-handlebars/helpers/view", "ember-handlebars/helpers/unbound", "ember-handlebars/helpers/debug", "ember-handlebars/helpers/each", "ember-handlebars/helpers/template", "ember-handlebars/helpers/partial", "ember-handlebars/helpers/yield", "ember-handlebars/helpers/loc", "ember-handlebars/controls/checkbox", "ember-handlebars/controls/select", "ember-handlebars/controls/text_area", "ember-handlebars/controls/text_field", "ember-handlebars/controls/text_support", "ember-handlebars/controls", "ember-handlebars/component_lookup", "ember-handlebars/views/handlebars_bound_view", "ember-handlebars/views/metamorph_view", "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__, __exports__) { |
| "use strict"; |
| var EmberHandlebars = __dependency1__["default"]; |
| var Ember = __dependency2__["default"]; |
| // to add to globals |
| |
| var runLoadHooks = __dependency3__.runLoadHooks; |
| var bootstrap = __dependency4__["default"]; |
| |
| var normalizePath = __dependency5__.normalizePath; |
| var template = __dependency5__.template; |
| var makeBoundHelper = __dependency5__.makeBoundHelper; |
| var registerBoundHelper = __dependency5__.registerBoundHelper; |
| var resolveHash = __dependency5__.resolveHash; |
| var resolveParams = __dependency5__.resolveParams; |
| var getEscaped = __dependency5__.getEscaped; |
| var handlebarsGet = __dependency5__.handlebarsGet; |
| var evaluateUnboundHelper = __dependency5__.evaluateUnboundHelper; |
| var helperMissingHelper = __dependency5__.helperMissingHelper; |
| var blockHelperMissingHelper = __dependency5__.blockHelperMissingHelper; |
| |
| |
| // side effect of extending StringUtils of htmlSafe |
| |
| var resolvePaths = __dependency7__["default"]; |
| var bind = __dependency8__.bind; |
| var _triageMustacheHelper = __dependency8__._triageMustacheHelper; |
| var resolveHelper = __dependency8__.resolveHelper; |
| var bindHelper = __dependency8__.bindHelper; |
| var boundIfHelper = __dependency8__.boundIfHelper; |
| var unboundIfHelper = __dependency8__.unboundIfHelper; |
| var withHelper = __dependency8__.withHelper; |
| var ifHelper = __dependency8__.ifHelper; |
| var unlessHelper = __dependency8__.unlessHelper; |
| var bindAttrHelper = __dependency8__.bindAttrHelper; |
| var bindAttrHelperDeprecated = __dependency8__.bindAttrHelperDeprecated; |
| var bindClasses = __dependency8__.bindClasses; |
| |
| var collectionHelper = __dependency9__["default"]; |
| var ViewHelper = __dependency10__.ViewHelper; |
| var viewHelper = __dependency10__.viewHelper; |
| var unboundHelper = __dependency11__["default"]; |
| var logHelper = __dependency12__.logHelper; |
| var debuggerHelper = __dependency12__.debuggerHelper; |
| var EachView = __dependency13__.EachView; |
| var GroupedEach = __dependency13__.GroupedEach; |
| var eachHelper = __dependency13__.eachHelper; |
| var templateHelper = __dependency14__["default"]; |
| var partialHelper = __dependency15__["default"]; |
| var yieldHelper = __dependency16__["default"]; |
| var locHelper = __dependency17__["default"]; |
| |
| |
| var Checkbox = __dependency18__["default"]; |
| var Select = __dependency19__.Select; |
| var SelectOption = __dependency19__.SelectOption; |
| var SelectOptgroup = __dependency19__.SelectOptgroup; |
| var TextArea = __dependency20__["default"]; |
| var TextField = __dependency21__["default"]; |
| var TextSupport = __dependency22__["default"]; |
| var inputHelper = __dependency23__.inputHelper; |
| var textareaHelper = __dependency23__.textareaHelper; |
| |
| |
| var ComponentLookup = __dependency24__["default"]; |
| var _HandlebarsBoundView = __dependency25__._HandlebarsBoundView; |
| var SimpleHandlebarsView = __dependency25__.SimpleHandlebarsView; |
| var _wrapMap = __dependency26__._wrapMap; |
| var _SimpleMetamorphView = __dependency26__._SimpleMetamorphView; |
| var _MetamorphView = __dependency26__._MetamorphView; |
| var _Metamorph = __dependency26__._Metamorph; |
| |
| |
| /** |
| Ember Handlebars |
| |
| @module ember |
| @submodule ember-handlebars |
| @requires ember-views |
| */ |
| |
| // Ember.Handlebars.Globals |
| EmberHandlebars.bootstrap = bootstrap; |
| EmberHandlebars.template = template; |
| EmberHandlebars.makeBoundHelper = makeBoundHelper; |
| EmberHandlebars.registerBoundHelper = registerBoundHelper; |
| EmberHandlebars.resolveHash = resolveHash; |
| EmberHandlebars.resolveParams = resolveParams; |
| EmberHandlebars.resolveHelper = resolveHelper; |
| EmberHandlebars.get = handlebarsGet; |
| EmberHandlebars.getEscaped = getEscaped; |
| EmberHandlebars.evaluateUnboundHelper = evaluateUnboundHelper; |
| EmberHandlebars.bind = bind; |
| EmberHandlebars.bindClasses = bindClasses; |
| EmberHandlebars.EachView = EachView; |
| EmberHandlebars.GroupedEach = GroupedEach; |
| EmberHandlebars.resolvePaths = resolvePaths; |
| EmberHandlebars.ViewHelper = ViewHelper; |
| EmberHandlebars.normalizePath = normalizePath; |
| |
| |
| // Ember Globals |
| Ember.Handlebars = EmberHandlebars; |
| Ember.ComponentLookup = ComponentLookup; |
| Ember._SimpleHandlebarsView = SimpleHandlebarsView; |
| Ember._HandlebarsBoundView = _HandlebarsBoundView; |
| Ember._SimpleMetamorphView = _SimpleMetamorphView; |
| Ember._MetamorphView = _MetamorphView; |
| Ember._Metamorph = _Metamorph; |
| Ember._metamorphWrapMap = _wrapMap; |
| Ember.TextSupport = TextSupport; |
| Ember.Checkbox = Checkbox; |
| Ember.Select = Select; |
| Ember.SelectOption = SelectOption; |
| Ember.SelectOptgroup = SelectOptgroup; |
| Ember.TextArea = TextArea; |
| Ember.TextField = TextField; |
| Ember.TextSupport = TextSupport; |
| |
| // register helpers |
| EmberHandlebars.registerHelper('helperMissing', helperMissingHelper); |
| EmberHandlebars.registerHelper('blockHelperMissing', blockHelperMissingHelper); |
| EmberHandlebars.registerHelper('bind', bindHelper); |
| EmberHandlebars.registerHelper('boundIf', boundIfHelper); |
| EmberHandlebars.registerHelper('_triageMustache', _triageMustacheHelper); |
| EmberHandlebars.registerHelper('unboundIf', unboundIfHelper); |
| EmberHandlebars.registerHelper('with', withHelper); |
| EmberHandlebars.registerHelper('if', ifHelper); |
| EmberHandlebars.registerHelper('unless', unlessHelper); |
| EmberHandlebars.registerHelper('bind-attr', bindAttrHelper); |
| EmberHandlebars.registerHelper('bindAttr', bindAttrHelperDeprecated); |
| EmberHandlebars.registerHelper('collection', collectionHelper); |
| EmberHandlebars.registerHelper("log", logHelper); |
| EmberHandlebars.registerHelper("debugger", debuggerHelper); |
| EmberHandlebars.registerHelper("each", eachHelper); |
| EmberHandlebars.registerHelper("loc", locHelper); |
| EmberHandlebars.registerHelper("partial", partialHelper); |
| EmberHandlebars.registerHelper("template", templateHelper); |
| EmberHandlebars.registerHelper("yield", yieldHelper); |
| EmberHandlebars.registerHelper("view", viewHelper); |
| EmberHandlebars.registerHelper("unbound", unboundHelper); |
| EmberHandlebars.registerHelper("input", inputHelper); |
| EmberHandlebars.registerHelper("textarea", textareaHelper); |
| |
| // run load hooks |
| runLoadHooks('Ember.Handlebars', EmberHandlebars); |
| |
| __exports__["default"] = EmberHandlebars; |
| }); |
| define("ember-handlebars/component_lookup", |
| ["ember-runtime/system/object", "exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| var EmberObject = __dependency1__["default"]; |
| |
| var ComponentLookup = EmberObject.extend({ |
| lookupFactory: function(name, container) { |
| |
| container = container || this.container; |
| |
| var fullName = 'component:' + name, |
| templateFullName = 'template:components/' + name, |
| 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; |
| } |
| } |
| }); |
| |
| __exports__["default"] = ComponentLookup; |
| }); |
| define("ember-handlebars/controls", |
| ["ember-handlebars/controls/checkbox", "ember-handlebars/controls/text_field", "ember-handlebars/controls/text_area", "ember-metal/core", "ember-handlebars-compiler", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) { |
| "use strict"; |
| var Checkbox = __dependency1__["default"]; |
| var TextField = __dependency2__["default"]; |
| var TextArea = __dependency3__["default"]; |
| |
| var Ember = __dependency4__["default"]; |
| // Ember.assert |
| // var emberAssert = Ember.assert; |
| |
| var EmberHandlebars = __dependency5__["default"]; |
| var helpers = EmberHandlebars.helpers; |
| /** |
| @module ember |
| @submodule ember-handlebars-compiler |
| */ |
| |
| /** |
| |
| 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 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 |
| {{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(options) { |
| Ember.assert('You can only pass attributes to the `input` helper, not arguments', arguments.length < 2); |
| |
| var hash = options.hash, |
| types = options.hashTypes, |
| inputType = hash.type, |
| onEvent = hash.on; |
| |
| delete hash.type; |
| delete hash.on; |
| |
| if (inputType === 'checkbox') { |
| Ember.assert("{{input type='checkbox'}} does not support setting `value=someBooleanValue`; you must use `checked=someBooleanValue` instead.", options.hashTypes.value !== 'ID'); |
| return helpers.view.call(this, Checkbox, options); |
| } else { |
| if (inputType) { |
| hash.type = inputType; |
| } |
| hash.onEvent = onEvent || 'enter'; |
| return helpers.view.call(this, TextField, options); |
| } |
| } |
| |
| __exports__.inputHelper = inputHelper; /** |
| `{{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(options) { |
| Ember.assert('You can only pass attributes to the `textarea` helper, not arguments', arguments.length < 2); |
| |
| var hash = options.hash, |
| types = options.hashTypes; |
| |
| return helpers.view.call(this, TextArea, options); |
| } |
| |
| __exports__.textareaHelper = textareaHelper; |
| }); |
| define("ember-handlebars/controls/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-handlebars |
| */ |
| |
| /** |
| 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')); |
| } |
| }); |
| }); |
| define("ember-handlebars/controls/select", |
| ["ember-handlebars-compiler", "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", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __exports__) { |
| "use strict"; |
| /** |
| @module ember |
| @submodule ember-handlebars |
| */ |
| |
| var EmberHandlebars = __dependency1__["default"]; |
| |
| var forEach = __dependency2__.forEach; |
| var indexOf = __dependency2__.indexOf; |
| var indexesOf = __dependency2__.indexesOf; |
| var replace = __dependency2__.replace; |
| |
| var get = __dependency3__.get; |
| var set = __dependency4__.set; |
| var View = __dependency5__["default"]; |
| var CollectionView = __dependency6__["default"]; |
| var isArray = __dependency7__.isArray; |
| var isNone = __dependency8__["default"]; |
| var computed = __dependency9__.computed; |
| var emberA = __dependency10__.A; |
| var observer = __dependency11__.observer; |
| var defineProperty = __dependency12__.defineProperty; |
| |
| var precompileTemplate = EmberHandlebars.compile; |
| |
| var SelectOption = View.extend({ |
| instrumentDisplay: 'Ember.SelectOption', |
| |
| tagName: 'option', |
| attributeBindings: ['value', 'selected'], |
| |
| defaultTemplate: function(context, options) { |
| options = { |
| data: options.data, |
| hash: {} |
| }; |
| EmberHandlebars.helpers.bind.call(context, "view.label", options); |
| }, |
| |
| init: function() { |
| this.labelPathDidChange(); |
| this.valuePathDidChange(); |
| |
| this._super(); |
| }, |
| |
| selected: computed(function() { |
| var content = get(this, 'content'), |
| 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 Ember.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 Ember.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 Ember.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 Ember.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 Ember.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 Ember.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 Ember.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: Ember.Handlebars.template(function anonymous(Handlebars, depth0, helpers, partials, data) { |
| this.compilerInfo = [4, '>= 1.0.0']; |
| helpers = this.merge(helpers, Ember.Handlebars.helpers); |
| data = data || {}; |
| var buffer = '', stack1, escapeExpression = this.escapeExpression, self = this; |
| |
| function program1(depth0, data) { |
| |
| var buffer = '', stack1; |
| data.buffer.push("<option value=\"\">"); |
| stack1 = helpers._triageMustache.call(depth0, "view.prompt", { |
| hash: {}, |
| hashTypes: {}, |
| hashContexts: {}, |
| contexts: [depth0], |
| types: ["ID"], |
| data: data |
| }); |
| if (stack1 || stack1 === 0) { |
| data.buffer.push(stack1); |
| } |
| data.buffer.push("</option>"); |
| return buffer; |
| } |
| |
| function program3(depth0, data) { |
| |
| var stack1; |
| stack1 = helpers.each.call(depth0, "view.groupedContent", { |
| hash: {}, |
| hashTypes: {}, |
| hashContexts: {}, |
| inverse: self.noop, |
| fn: self.program(4, program4, data), |
| contexts: [depth0], |
| types: ["ID"], |
| data: data |
| }); |
| if (stack1 || stack1 === 0) { |
| data.buffer.push(stack1); |
| } else { |
| data.buffer.push(''); |
| } |
| } |
| function program4(depth0, data) { |
| |
| |
| data.buffer.push(escapeExpression(helpers.view.call(depth0, "view.groupView", { |
| hash: { |
| 'content': ("content"), |
| 'label': ("label") |
| }, |
| hashTypes: { |
| 'content': "ID", |
| 'label': "ID" |
| }, |
| hashContexts: { |
| 'content': depth0, |
| 'label': depth0 |
| }, |
| contexts: [depth0], |
| types: ["ID"], |
| data: data |
| }))); |
| } |
| |
| function program6(depth0, data) { |
| |
| var stack1; |
| stack1 = helpers.each.call(depth0, "view.content", { |
| hash: {}, |
| hashTypes: {}, |
| hashContexts: {}, |
| inverse: self.noop, |
| fn: self.program(7, program7, data), |
| contexts: [depth0], |
| types: ["ID"], |
| data: data |
| }); |
| if (stack1 || stack1 === 0) { |
| data.buffer.push(stack1); |
| } else { |
| data.buffer.push(''); |
| } |
| } |
| function program7(depth0, data) { |
| |
| |
| data.buffer.push(escapeExpression(helpers.view.call(depth0, "view.optionView", { |
| hash: { |
| 'content': ("") |
| }, |
| hashTypes: { |
| 'content': "ID" |
| }, |
| hashContexts: { |
| 'content': depth0 |
| }, |
| contexts: [depth0], |
| types: ["ID"], |
| data: data |
| }))); |
| } |
| |
| stack1 = helpers['if'].call(depth0, "view.prompt", { |
| hash: {}, |
| hashTypes: {}, |
| hashContexts: {}, |
| inverse: self.noop, |
| fn: self.program(1, program1, data), |
| contexts: [depth0], |
| types: ["ID"], |
| data: data |
| }); |
| if (stack1 || stack1 === 0) { |
| data.buffer.push(stack1); |
| } |
| stack1 = helpers['if'].call(depth0, "view.optionGroupPath", { |
| hash: {}, |
| hashTypes: {}, |
| hashContexts: {}, |
| inverse: self.program(6, program6, data), |
| fn: self.program(3, program3, data), |
| contexts: [depth0], |
| types: ["ID"], |
| data: data |
| }); |
| if (stack1 || stack1 === 0) { |
| data.buffer.push(stack1); |
| } |
| return buffer; |
| |
| }), |
| 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 |
| Ember.Select.create({ |
| 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 it's 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'), |
| value = get(this, 'value'), |
| valuePath = get(this, 'optionValuePath').replace(/^content\.?/, ''), |
| selectedValue = (valuePath ? get(this, 'selection.' + valuePath) : get(this, 'selection')), |
| selection; |
| |
| if (value !== selectedValue) { |
| selection = content ? content.find(function(obj) { |
| return value === (valuePath ? get(obj, valuePath) : obj); |
| }) : null; |
| |
| this.set('selection', selection); |
| } |
| }), |
| |
| |
| _triggerChange: function() { |
| var selection = get(this, 'selection'); |
| var value = get(this, 'value'); |
| |
| if (!isNone(selection)) { |
| this.selectionDidChange(); |
| } |
| if (!isNone(value)) { |
| this.valueDidChange(); |
| } |
| |
| this._change(); |
| }, |
| |
| _changeSingle: function() { |
| var selectedIndex = this.$()[0].selectedIndex, |
| content = get(this, 'content'), |
| 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'), |
| prompt = get(this, 'prompt'), |
| offset = prompt ? 1 : 0, |
| content = get(this, 'content'), |
| 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 el = this.get('element'); |
| if (!el) { |
| return; |
| } |
| |
| var content = get(this, 'content'), |
| selection = get(this, 'selection'), |
| selectionIndex = content ? indexOf(content, selection) : -1, |
| prompt = get(this, 'prompt'); |
| |
| if (prompt) { |
| selectionIndex += 1; |
| } |
| if (el) { |
| el.selectedIndex = selectionIndex; |
| } |
| }, |
| |
| _selectionDidChangeMultiple: function() { |
| var content = get(this, 'content'), |
| selection = get(this, 'selection'), |
| selectedIndexes = content ? indexesOf(content, selection) : [-1], |
| prompt = get(this, 'prompt'), |
| offset = prompt ? 1 : 0, |
| options = this.$('option'), |
| 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._triggerChange); |
| this.on("change", this, this._change); |
| } |
| }); |
| |
| __exports__["default"] = Select; |
| __exports__.Select = Select; |
| __exports__.SelectOption = SelectOption; |
| __exports__.SelectOptgroup = SelectOptgroup; |
| }); |
| define("ember-handlebars/controls/text_area", |
| ["ember-metal/property_get", "ember-views/views/component", "ember-handlebars/controls/text_support", "ember-metal/mixin", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { |
| "use strict"; |
| |
| /** |
| @module ember |
| @submodule ember-handlebars |
| */ |
| 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'], |
| 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'), |
| $el = this.$(); |
| if ($el && value !== $el.val()) { |
| $el.val(value); |
| } |
| }), |
| |
| init: function() { |
| this._super(); |
| this.on("didInsertElement", this, this._updateElementValue); |
| } |
| }); |
| }); |
| define("ember-handlebars/controls/text_field", |
| ["ember-metal/property_get", "ember-metal/property_set", "ember-views/views/component", "ember-handlebars/controls/text_support", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { |
| "use strict"; |
| /** |
| @module ember |
| @submodule ember-handlebars |
| */ |
| |
| var get = __dependency1__.get; |
| var set = __dependency2__.set; |
| var Component = __dependency3__["default"]; |
| var TextSupport = __dependency4__["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: ['type', 'value', 'size', 'pattern', 'name', 'min', 'max', |
| 'accept', 'autocomplete', 'autosave', 'formaction', |
| 'formenctype', 'formmethod', 'formnovalidate', 'formtarget', |
| 'height', 'inputmode', 'list', 'multiple', 'step', |
| 'width'], |
| |
| /** |
| 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 |
| }); |
| }); |
| define("ember-handlebars/controls/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-handlebars |
| */ |
| |
| var get = __dependency1__.get; |
| var set = __dependency2__.set; |
| var Mixin = __dependency3__.Mixin; |
| var TargetActionSupport = __dependency4__["default"]; |
| |
| /** |
| Shared mixin used by `Ember.TextField` and `Ember.TextArea`. |
| |
| @class TextSupport |
| @namespace Ember |
| @uses Ember.TargetActionSupport |
| @extends Ember.Mixin |
| @private |
| */ |
| var TextSupport = Mixin.create(TargetActionSupport, { |
| value: "", |
| |
| attributeBindings: ['placeholder', 'disabled', 'maxlength', 'tabindex', 'readonly', |
| 'autofocus', 'form', 'selectionDirection', 'spellcheck', 'required', |
| 'title', 'autocapitalize', 'autocorrect'], |
| placeholder: null, |
| disabled: false, |
| maxlength: null, |
| |
| init: function() { |
| this._super(); |
| this.on("focusOut", this, this._elementValueDidChange); |
| this.on("change", this, this._elementValueDidChange); |
| this.on("paste", this, this._elementValueDidChange); |
| this.on("cut", this, this._elementValueDidChange); |
| this.on("input", this, this._elementValueDidChange); |
| this.on("keyUp", this, this.interpretKeyEvents); |
| }, |
| |
| /** |
| 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 they `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()); |
| }, |
| |
| /** |
| Called when the user inserts a new line. |
| |
| Called by the `Ember.TextSupport` mixin on keyUp if keycode matches 13. |
| Uses sendAction to send the `enter` action. |
| |
| @method insertNewline |
| @param {Event} event |
| */ |
| insertNewline: function(event) { |
| sendAction('enter', this, event); |
| sendAction('insert-newline', this, event); |
| }, |
| |
| /** |
| Called when the user hits escape. |
| |
| Called by the `Ember.TextSupport` mixin on keyUp if keycode matches 27. |
| Uses sendAction to send the `escape-press` action. |
| |
| @method cancel |
| @param {Event} event |
| */ |
| cancel: function(event) { |
| sendAction('escape-press', this, event); |
| }, |
| |
| /** |
| Called when the text area is focused. |
| |
| Uses sendAction to send the `focus-in` action. |
| |
| @method focusIn |
| @param {Event} event |
| */ |
| focusIn: function(event) { |
| sendAction('focus-in', this, event); |
| }, |
| |
| /** |
| Called when the text area is blurred. |
| |
| Uses sendAction to send the `focus-out` action. |
| |
| @method focusOut |
| @param {Event} event |
| */ |
| focusOut: function(event) { |
| sendAction('focus-out', this, event); |
| }, |
| |
| /** |
| Called when the user presses a key. Enabled by setting |
| the `onEvent` property to `keyPress`. |
| |
| Uses sendAction to send the `key-press` action. |
| |
| @method keyPress |
| @param {Event} event |
| */ |
| keyPress: function(event) { |
| sendAction('key-press', this, 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), |
| on = get(view, 'onEvent'), |
| 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; |
| }); |
| define("ember-handlebars/ext", |
| ["ember-metal/core", "ember-runtime/system/string", "ember-handlebars-compiler", "ember-metal/property_get", "ember-metal/binding", "ember-metal/error", "ember-metal/mixin", "ember-metal/is_empty", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| // Ember.FEATURES, Ember.assert, Ember.Handlebars, Ember.lookup |
| // var emberAssert = Ember.assert; |
| |
| var fmt = __dependency2__.fmt; |
| |
| var EmberHandlebars = __dependency3__["default"]; |
| var helpers = EmberHandlebars.helpers; |
| |
| var get = __dependency4__.get; |
| var isGlobalPath = __dependency5__.isGlobalPath; |
| var EmberError = __dependency6__["default"]; |
| var IS_BINDING = __dependency7__.IS_BINDING; |
| |
| // late bound via requireModule because of circular dependencies. |
| var resolveHelper, |
| SimpleHandlebarsView; |
| |
| var isEmpty = __dependency8__["default"]; |
| |
| var slice = [].slice, originalTemplate = EmberHandlebars.template; |
| |
| /** |
| If a path starts with a reserved keyword, returns the root |
| that should be used. |
| |
| @private |
| @method normalizePath |
| @for Ember |
| @param root {Object} |
| @param path {String} |
| @param data {Hash} |
| */ |
| function normalizePath(root, path, data) { |
| var keywords = (data && data.keywords) || {}, |
| keyword, isKeyword; |
| |
| // Get the first segment of the path. For example, if the |
| // path is "foo.bar.baz", returns "foo". |
| keyword = path.split('.', 1)[0]; |
| |
| // Test to see if the first path is a keyword that has been |
| // passed along in the view's data hash. If so, we will treat |
| // that object as the new root. |
| if (keywords.hasOwnProperty(keyword)) { |
| // Look up the value in the template's data hash. |
| root = keywords[keyword]; |
| isKeyword = true; |
| |
| // Handle cases where the entire path is the reserved |
| // word. In that case, return the object itself. |
| if (path === keyword) { |
| path = ''; |
| } else { |
| // Strip the keyword from the path and look up |
| // the remainder from the newly found root. |
| path = path.substr(keyword.length + 1); |
| } |
| } |
| |
| return { |
| root: root, |
| path: path, |
| isKeyword: isKeyword |
| }; |
| } |
| |
| |
| /** |
| 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 |
| */ |
| function handlebarsGet(root, path, options) { |
| var data = options && options.data, |
| normalizedPath = normalizePath(root, path, data), |
| value; |
| |
| |
| root = normalizedPath.root; |
| path = normalizedPath.path; |
| |
| value = get(root, path); |
| |
| if (value === undefined && root !== Ember.lookup && isGlobalPath(path)) { |
| value = get(Ember.lookup, path); |
| } |
| |
| |
| return value; |
| } |
| |
| /** |
| This method uses `Ember.Handlebars.get` to lookup a value, then ensures |
| that the value is escaped properly. |
| |
| If `unescaped` is a truthy value then the escaping will not be performed. |
| |
| @method getEscaped |
| @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 |
| @since 1.4.0 |
| */ |
| function getEscaped(root, path, options) { |
| var result = handlebarsGet(root, path, options); |
| |
| if (result === null || result === undefined) { |
| result = ""; |
| } else if (!(result instanceof Handlebars.SafeString)) { |
| result = String(result); |
| } |
| if (!options.hash.unescaped) { |
| result = Handlebars.Utils.escapeExpression(result); |
| } |
| |
| return result; |
| } |
| |
| __exports__.getEscaped = getEscaped; |
| function resolveParams(context, params, options) { |
| var resolvedParams = [], types = options.types, param, type; |
| |
| for (var i = 0, l = params.length; i < l; i++) { |
| param = params[i]; |
| type = types[i]; |
| |
| if (type === 'ID') { |
| resolvedParams.push(handlebarsGet(context, param, options)); |
| } else { |
| resolvedParams.push(param); |
| } |
| } |
| |
| return resolvedParams; |
| } |
| |
| __exports__.resolveParams = resolveParams; |
| function resolveHash(context, hash, options) { |
| var resolvedHash = {}, types = options.hashTypes, type; |
| |
| for (var key in hash) { |
| if (!hash.hasOwnProperty(key)) { |
| continue; |
| } |
| |
| type = types[key]; |
| |
| if (type === 'ID') { |
| resolvedHash[key] = handlebarsGet(context, hash[key], options); |
| } else { |
| resolvedHash[key] = hash[key]; |
| } |
| } |
| |
| return resolvedHash; |
| } |
| |
| __exports__.resolveHash = resolveHash; /** |
| Registers a helper in Handlebars that will be called if no property with the |
| given name can be found on the current context object, and no helper with |
| that name is registered. |
| |
| This throws an exception with a more helpful error message so the user can |
| track down where the problem is happening. |
| |
| @private |
| @method helperMissing |
| @for Ember.Handlebars.helpers |
| @param {String} path |
| @param {Hash} options |
| */ |
| function helperMissingHelper(path) { |
| if (!resolveHelper) { |
| resolveHelper = requireModule('ember-handlebars/helpers/binding')['resolveHelper']; |
| } |
| // ES6TODO: stupid circular dep |
| |
| var error, view = ""; |
| |
| var options = arguments[arguments.length - 1]; |
| |
| var helper = resolveHelper(options.data.view.container, path); |
| |
| if (helper) { |
| return helper.apply(this, slice.call(arguments, 1)); |
| } |
| |
| error = "%@ Handlebars error: Could not find property '%@' on object %@."; |
| if (options.data) { |
| view = options.data.view; |
| } |
| throw new EmberError(fmt(error, [view, path, this])); |
| } |
| |
| __exports__.helperMissingHelper = helperMissingHelper; /** |
| Registers a helper in Handlebars that will be called if no property with the |
| given name can be found on the current context object, and no helper with |
| that name is registered. |
| |
| This throws an exception with a more helpful error message so the user can |
| track down where the problem is happening. |
| |
| @private |
| @method helperMissing |
| @for Ember.Handlebars.helpers |
| @param {String} path |
| @param {Hash} options |
| */ |
| function blockHelperMissingHelper(path) { |
| if (!resolveHelper) { |
| resolveHelper = requireModule('ember-handlebars/helpers/binding')['resolveHelper']; |
| } |
| // ES6TODO: stupid circular dep |
| |
| var options = arguments[arguments.length - 1]; |
| |
| Ember.assert("`blockHelperMissing` was invoked without a helper name, which " + |
| "is most likely due to a mismatch between the version of " + |
| "Ember.js you're running now and the one used to precompile your " + |
| "templates. Please make sure the version of " + |
| "`ember-handlebars-compiler` you're using is up to date.", path); |
| |
| var helper = resolveHelper(options.data.view.container, path); |
| |
| if (helper) { |
| return helper.apply(this, slice.call(arguments, 1)); |
| } else { |
| return helpers.helperMissing.call(this, path); |
| } |
| } |
| |
| __exports__.blockHelperMissingHelper = blockHelperMissingHelper; /** |
| 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* |
| */ |
| function registerBoundHelper(name, fn) { |
| var boundHelperArgs = slice.call(arguments, 1), |
| boundFn = makeBoundHelper.apply(this, boundHelperArgs); |
| EmberHandlebars.registerHelper(name, boundFn); |
| } |
| |
| __exports__.registerBoundHelper = registerBoundHelper; /** |
| 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 |
| */ |
| function makeBoundHelper(fn) { |
| if (!SimpleHandlebarsView) { |
| SimpleHandlebarsView = requireModule('ember-handlebars/views/handlebars_bound_view')['SimpleHandlebarsView']; |
| } |
| // ES6TODO: stupid circular dep |
| |
| var dependentKeys = slice.call(arguments, 1); |
| |
| function helper() { |
| var properties = slice.call(arguments, 0, -1), |
| numProperties = properties.length, |
| options = arguments[arguments.length - 1], |
| normalizedProperties = [], |
| data = options.data, |
| types = data.isUnbound ? slice.call(options.types, 1) : options.types, |
| hash = options.hash, |
| view = data.view, |
| contexts = options.contexts, |
| currentContext = (contexts && contexts.length) ? contexts[0] : this, |
| prefixPathForDependentKeys = '', |
| loc, len, hashOption, |
| boundOption, property, |
| normalizedValue = SimpleHandlebarsView.prototype.normalizedValue; |
| |
| Ember.assert("registerBoundHelper-generated helpers do not support use with Handlebars blocks.", !options.fn); |
| |
| // Detect bound options (e.g. countBinding="otherCount") |
| var boundOptions = hash.boundOptions = {}; |
| for (hashOption in hash) { |
| if (IS_BINDING.test(hashOption)) { |
| // Lop off 'Binding' suffix. |
| boundOptions[hashOption.slice(0, -7)] = hash[hashOption]; |
| } |
| } |
| |
| // Expose property names on data.properties object. |
| var watchedProperties = []; |
| data.properties = []; |
| for (loc = 0; loc < numProperties; ++loc) { |
| data.properties.push(properties[loc]); |
| if (types[loc] === 'ID') { |
| var normalizedProp = normalizePath(currentContext, properties[loc], data); |
| normalizedProperties.push(normalizedProp); |
| watchedProperties.push(normalizedProp); |
| } else { |
| if (data.isUnbound) { |
| normalizedProperties.push({ |
| path: properties[loc] |
| }); |
| } else { |
| normalizedProperties.push(null); |
| } |
| } |
| } |
| |
| // Handle case when helper invocation is preceded by `unbound`, e.g. |
| // {{unbound myHelper foo}} |
| if (data.isUnbound) { |
| return evaluateUnboundHelper(this, fn, normalizedProperties, options); |
| } |
| |
| var bindView = new SimpleHandlebarsView(null, null, !options.hash.unescaped, options.data); |
| |
| // Override SimpleHandlebarsView's method for generating the view's content. |
| bindView.normalizedValue = function() { |
| var args = [], boundOption; |
| |
| // Copy over bound hash options. |
| for (boundOption in boundOptions) { |
| if (!boundOptions.hasOwnProperty(boundOption)) { |
| continue; |
| } |
| property = normalizePath(currentContext, boundOptions[boundOption], data); |
| bindView.path = property.path; |
| bindView.pathRoot = property.root; |
| hash[boundOption] = normalizedValue.call(bindView); |
| } |
| |
| for (loc = 0; loc < numProperties; ++loc) { |
| property = normalizedProperties[loc]; |
| if (property) { |
| bindView.path = property.path; |
| bindView.pathRoot = property.root; |
| args.push(normalizedValue.call(bindView)); |
| } else { |
| args.push(properties[loc]); |
| } |
| } |
| args.push(options); |
| |
| // Run the supplied helper function. |
| return fn.apply(currentContext, args); |
| }; |
| |
| view.appendChild(bindView); |
| |
| // Assemble list of watched properties that'll re-render this helper. |
| for (boundOption in boundOptions) { |
| if (boundOptions.hasOwnProperty(boundOption)) { |
| watchedProperties.push(normalizePath(currentContext, boundOptions[boundOption], data)); |
| } |
| } |
| |
| // Observe each property. |
| for (loc = 0, len = watchedProperties.length; loc < len; ++loc) { |
| property = watchedProperties[loc]; |
| view.registerObserver(property.root, property.path, bindView, bindView.rerender); |
| } |
| |
| if (types[0] !== 'ID' || normalizedProperties.length === 0) { |
| return; |
| } |
| |
| // Add dependent key observers to the first param |
| var normalized = normalizedProperties[0], |
| pathRoot = normalized.root, |
| path = normalized.path; |
| |
| if (!isEmpty(path)) { |
| prefixPathForDependentKeys = path + '.'; |
| } |
| for (var i = 0, l = dependentKeys.length; i < l; i++) { |
| view.registerObserver(pathRoot, prefixPathForDependentKeys + dependentKeys[i], bindView, bindView.rerender); |
| } |
| } |
| |
| helper._rawFunction = fn; |
| return helper; |
| } |
| |
| /** |
| Renders the unbound form of an otherwise bound helper function. |
| |
| @private |
| @method evaluateUnboundHelper |
| @param {Function} fn |
| @param {Object} context |
| @param {Array} normalizedProperties |
| @param {String} options |
| */ |
| function evaluateUnboundHelper(context, fn, normalizedProperties, options) { |
| var args = [], |
| hash = options.hash, |
| boundOptions = hash.boundOptions, |
| types = slice.call(options.types, 1), |
| loc, |
| len, |
| property, |
| propertyType, |
| boundOption; |
| |
| for (boundOption in boundOptions) { |
| if (!boundOptions.hasOwnProperty(boundOption)) { |
| continue; |
| } |
| hash[boundOption] = handlebarsGet(context, boundOptions[boundOption], options); |
| } |
| |
| for (loc = 0, len = normalizedProperties.length; loc < len; ++loc) { |
| property = normalizedProperties[loc]; |
| propertyType = types[loc]; |
| if (propertyType === "ID") { |
| args.push(handlebarsGet(property.root, property.path, options)); |
| } else { |
| args.push(property.path); |
| } |
| } |
| args.push(options); |
| return fn.apply(context, args); |
| } |
| |
| /** |
| Overrides Handlebars.template so that we can distinguish |
| user-created, top-level templates from inner contexts. |
| |
| @private |
| @method template |
| @for Ember.Handlebars |
| @param {String} spec |
| */ |
| function template(spec) { |
| var t = originalTemplate(spec); |
| t.isTop = true; |
| return t; |
| } |
| |
| __exports__.template = template; |
| __exports__.normalizePath = normalizePath; |
| __exports__.makeBoundHelper = makeBoundHelper; |
| __exports__.handlebarsGet = handlebarsGet; |
| __exports__.evaluateUnboundHelper = evaluateUnboundHelper; |
| }); |
| define("ember-handlebars/helpers/binding", |
| ["ember-metal/core", "ember-handlebars-compiler", "ember-metal/property_get", "ember-metal/property_set", "ember-metal/utils", "ember-runtime/system/string", "ember-metal/platform", "ember-metal/is_none", "ember-metal/enumerable_utils", "ember-metal/array", "ember-views/views/view", "ember-metal/run_loop", "ember-metal/observer", "ember-metal/binding", "ember-views/system/jquery", "ember-handlebars/ext", "ember-runtime/keys", "ember-handlebars/views/handlebars_bound_view", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __dependency16__, __dependency17__, __dependency18__, __exports__) { |
| "use strict"; |
| /** |
| @module ember |
| @submodule ember-handlebars |
| */ |
| |
| var Ember = __dependency1__["default"]; |
| // Ember.assert, Ember.warn, uuid |
| // var emberAssert = Ember.assert, Ember.warn = Ember.warn; |
| |
| var EmberHandlebars = __dependency2__["default"]; |
| var get = __dependency3__.get; |
| var set = __dependency4__.set; |
| var apply = __dependency5__.apply; |
| var uuid = __dependency5__.uuid; |
| var fmt = __dependency6__.fmt; |
| var o_create = __dependency7__.create; |
| var isNone = __dependency8__["default"]; |
| var EnumerableUtils = __dependency9__["default"]; |
| var forEach = __dependency10__.forEach; |
| var View = __dependency11__["default"]; |
| var run = __dependency12__["default"]; |
| var removeObserver = __dependency13__.removeObserver; |
| var isGlobalPath = __dependency14__.isGlobalPath; |
| var emberBind = __dependency14__.bind; |
| var jQuery = __dependency15__["default"]; |
| var isArray = __dependency5__.isArray; |
| var handlebarsGetEscaped = __dependency16__.getEscaped; |
| var keys = __dependency17__["default"]; |
| |
| var _HandlebarsBoundView = __dependency18__._HandlebarsBoundView; |
| var SimpleHandlebarsView = __dependency18__.SimpleHandlebarsView; |
| |
| var normalizePath = __dependency16__.normalizePath; |
| var handlebarsGet = __dependency16__.handlebarsGet; |
| var getEscaped = __dependency16__.getEscaped; |
| |
| var guidFor = __dependency5__.guidFor; |
| var typeOf = __dependency5__.typeOf; |
| |
| var helpers = EmberHandlebars.helpers; |
| var SafeString = EmberHandlebars.SafeString; |
| |
| function exists(value) { |
| return !isNone(value); |
| } |
| |
| var WithView = _HandlebarsBoundView.extend({ |
| init: function() { |
| var controller; |
| |
| apply(this, this._super, arguments); |
| |
| var keywords = this.templateData.keywords; |
| var keywordName = this.templateHash.keywordName; |
| var keywordPath = this.templateHash.keywordPath; |
| var controllerName = this.templateHash.controller; |
| var preserveContext = this.preserveContext; |
| |
| if (controllerName) { |
| var previousContext = this.previousContext; |
| controller = this.container.lookupFactory('controller:' + controllerName).create({ |
| parentController: previousContext, |
| target: previousContext |
| }); |
| |
| this._generatedController = controller; |
| |
| if (!preserveContext) { |
| this.set('controller', controller); |
| |
| this.valueNormalizerFunc = function(result) { |
| controller.set('model', result); |
| return controller; |
| }; |
| } else { |
| var controllerPath = jQuery.expando + guidFor(controller); |
| keywords[controllerPath] = controller; |
| emberBind(keywords, controllerPath + '.model', keywordPath); |
| keywordPath = controllerPath; |
| } |
| } |
| |
| if (preserveContext) { |
| emberBind(keywords, keywordName, keywordPath); |
| } |
| |
| }, |
| willDestroy: function() { |
| this._super(); |
| |
| if (this._generatedController) { |
| this._generatedController.destroy(); |
| } |
| } |
| }); |
| |
| // 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, options, preserveContext, shouldDisplay, valueNormalizer, childProperties) { |
| var data = options.data, |
| fn = options.fn, |
| inverse = options.inverse, |
| view = data.view, |
| normalized, observer, i; |
| |
| // we relied on the behavior of calling without |
| // context to mean this === window, but when running |
| // "use strict", it's possible for this to === undefined; |
| var currentContext = this || window; |
| |
| normalized = normalizePath(currentContext, property, data); |
| |
| // Set up observers for observable objects |
| if ('object' === typeof this) { |
| if (data.insideGroup) { |
| observer = function() { |
| while (view._contextView) { |
| view = view._contextView; |
| } |
| run.once(view, 'rerender'); |
| }; |
| |
| var template, context, result = handlebarsGet(currentContext, property, options); |
| |
| result = valueNormalizer ? valueNormalizer(result) : result; |
| |
| context = preserveContext ? currentContext : result; |
| if (shouldDisplay(result)) { |
| template = fn; |
| } else if (inverse) { |
| template = inverse; |
| } |
| |
| template(context, { |
| data: options.data |
| }); |
| } else { |
| var viewClass = _HandlebarsBoundView; |
| var viewOptions = { |
| preserveContext: preserveContext, |
| shouldDisplayFunc: shouldDisplay, |
| valueNormalizerFunc: valueNormalizer, |
| displayTemplate: fn, |
| inverseTemplate: inverse, |
| path: property, |
| pathRoot: currentContext, |
| previousContext: currentContext, |
| isEscaped: !options.hash.unescaped, |
| templateData: options.data, |
| templateHash: options.hash, |
| helperName: options.helperName |
| }; |
| |
| if (options.isWithHelper) { |
| viewClass = WithView; |
| } |
| |
| // 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._HandlebarsBoundView for more. |
| var bindView = view.createChildView(viewClass, viewOptions); |
| |
| view.appendChild(bindView); |
| |
| observer = function() { |
| run.scheduleOnce('render', bindView, 'rerenderIfNeeded'); |
| }; |
| } |
| |
| // Observes the given property on the context and |
| // tells the Ember._HandlebarsBoundView to re-render. If property |
| // is an empty string, we are printing the current context |
| // object ({{this}}) so updating it is not our responsibility. |
| if (normalized.path !== '') { |
| view.registerObserver(normalized.root, normalized.path, observer); |
| if (childProperties) { |
| for (i = 0; i < childProperties.length; i++) { |
| view.registerObserver(normalized.root, normalized.path + '.' + childProperties[i], observer); |
| } |
| } |
| } |
| } else { |
| // The object is not observable, so just render it out and |
| // be done with it. |
| data.buffer.push(handlebarsGetEscaped(currentContext, property, options)); |
| } |
| } |
| |
| function simpleBind(currentContext, property, options) { |
| var data = options.data, |
| view = data.view, |
| normalized, observer, pathRoot, output; |
| |
| normalized = normalizePath(currentContext, property, data); |
| pathRoot = normalized.root; |
| |
| // Set up observers for observable objects |
| if (pathRoot && ('object' === typeof pathRoot)) { |
| if (data.insideGroup) { |
| observer = function() { |
| while (view._contextView) { |
| view = view._contextView; |
| } |
| run.once(view, 'rerender'); |
| }; |
| |
| output = handlebarsGetEscaped(currentContext, property, options); |
| |
| data.buffer.push(output); |
| } else { |
| var bindView = new SimpleHandlebarsView( |
| property, currentContext, !options.hash.unescaped, options.data |
| ); |
| |
| bindView._parentView = view; |
| view.appendChild(bindView); |
| |
| observer = function() { |
| run.scheduleOnce('render', bindView, 'rerender'); |
| }; |
| } |
| |
| // Observes the given property on the context and |
| // tells the Ember._HandlebarsBoundView to re-render. If property |
| // is an empty string, we are printing the current context |
| // object ({{this}}) so updating it is not our responsibility. |
| if (normalized.path !== '') { |
| view.registerObserver(normalized.root, normalized.path, observer); |
| } |
| } else { |
| // The object is not observable, so just render it out and |
| // be done with it. |
| output = handlebarsGetEscaped(currentContext, property, options); |
| data.buffer.push(output); |
| } |
| } |
| |
| 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; |
| } |
| } |
| |
| /** |
| '_triageMustache' is used internally select between a binding, helper, or component for |
| the given context. Until this point, it would be hard to determine if the |
| mustache is a property reference or a regular helper reference. This triage |
| helper resolves that. |
| |
| This would not be typically invoked by directly. |
| |
| @private |
| @method _triageMustache |
| @for Ember.Handlebars.helpers |
| @param {String} property Property/helperID to triage |
| @param {Object} options hash of template/rendering options |
| @return {String} HTML string |
| */ |
| function _triageMustacheHelper(property, options) { |
| Ember.assert("You cannot pass more than one argument to the _triageMustache helper", arguments.length <= 2); |
| |
| var helper = EmberHandlebars.resolveHelper(options.data.view.container, property); |
| if (helper) { |
| return helper.call(this, options); |
| } |
| |
| return helpers.bind.call(this, property, options); |
| } |
| |
| /** |
| 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} |
| */ |
| function resolveHelper(container, name) { |
| if (helpers[name]) { |
| return helpers[name]; |
| } |
| |
| if (!container || name.indexOf('-') === -1) { |
| return; |
| } |
| |
| var helper = container.lookup('helper:' + name); |
| 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 = EmberHandlebars.makeViewHelper(Component); |
| container.register('helper:' + name, helper); |
| } |
| } |
| return helper; |
| } |
| |
| |
| /** |
| `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} fn Context to provide for rendering |
| @return {String} HTML string |
| */ |
| function bindHelper(property, options) { |
| Ember.assert("You cannot pass more than one argument to the bind helper", arguments.length <= 2); |
| |
| var context = (options.contexts && options.contexts.length) ? options.contexts[0] : this; |
| |
| if (!options.fn) { |
| return simpleBind(context, property, options); |
| } |
| |
| options.helperName = 'bind'; |
| |
| return bind.call(context, property, options, false, exists); |
| } |
| |
| /** |
| 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(property, fn) { |
| var context = (fn.contexts && fn.contexts.length) ? fn.contexts[0] : this; |
| |
| fn.helperName = fn.helperName || 'boundIf'; |
| |
| return bind.call(context, property, fn, 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(property, fn) { |
| var context = (fn.contexts && fn.contexts.length) ? fn.contexts[0] : this, |
| data = fn.data, |
| template = fn.fn, |
| inverse = fn.inverse, |
| normalized, propertyValue, result; |
| |
| normalized = normalizePath(context, property, data); |
| propertyValue = handlebarsGet(context, property, fn); |
| |
| if (!shouldDisplayIfHelperContent(propertyValue)) { |
| template = inverse; |
| } |
| |
| template(context, { |
| data: data |
| }); |
| } |
| |
| /** |
| Use the `{{with}}` helper when you want to scope context. Take the following code as an example: |
| |
| ```handlebars |
| <h5>{{user.name}}</h5> |
| |
| <div class="role"> |
| <h6>{{user.role.label}}</h6> |
| <span class="role-id">{{user.role.id}}</span> |
| |
| <p class="role-desc">{{user.role.description}}</p> |
| </div> |
| ``` |
| |
| `{{with}}` can be our best friend in these cases, |
| instead of writing `user.role.*` over and over, we use `{{#with user.role}}`. |
| Now the context within the `{{#with}} .. {{/with}}` block is `user.role` so you can do the following: |
| |
| ```handlebars |
| <h5>{{user.name}}</h5> |
| |
| <div class="role"> |
| {{#with user.role}} |
| <h6>{{label}}</h6> |
| <span class="role-id">{{id}}</span> |
| |
| <p class="role-desc">{{description}}</p> |
| {{/with}} |
| </div> |
| ``` |
| |
| ### `as` operator |
| |
| This operator aliases the scope 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 with the new context as its content. |
| |
| This is very similar to using an `itemController` option with the `{{each}}` helper. |
| |
| ```handlebars |
| {{#with users.posts controller='userBlogPosts'}} |
| {{!- The current context is wrapped in our controller instance }} |
| {{/with}} |
| ``` |
| |
| In the above example, the template provided to the `{{with}}` block is now wrapped in the |
| `userBlogPost` controller, which provides a very 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(context, options) { |
| var bindContext, preserveContext, controller, helperName = 'with'; |
| |
| if (arguments.length === 4) { |
| var keywordName, path, rootPath, normalized, contextPath; |
| |
| Ember.assert("If you pass more than one argument to the with helper, it must be in the form #with foo as bar", arguments[1] === "as"); |
| options = arguments[3]; |
| keywordName = arguments[2]; |
| path = arguments[0]; |
| |
| if (path) { |
| helperName += ' ' + path + ' as ' + keywordName; |
| } |
| |
| Ember.assert("You must pass a block to the with helper", options.fn && options.fn !== Handlebars.VM.noop); |
| |
| var localizedOptions = o_create(options); |
| localizedOptions.data = o_create(options.data); |
| localizedOptions.data.keywords = o_create(options.data.keywords || {}); |
| |
| if (isGlobalPath(path)) { |
| contextPath = path; |
| } else { |
| normalized = normalizePath(this, path, options.data); |
| path = normalized.path; |
| rootPath = normalized.root; |
| |
| // This is a workaround for the fact that you cannot bind separate objects |
| // together. When we implement that functionality, we should use it here. |
| var contextKey = jQuery.expando + guidFor(rootPath); |
| localizedOptions.data.keywords[contextKey] = rootPath; |
| // if the path is '' ("this"), just bind directly to the current context |
| contextPath = path ? contextKey + '.' + path : contextKey; |
| } |
| |
| localizedOptions.hash.keywordName = keywordName; |
| localizedOptions.hash.keywordPath = contextPath; |
| |
| bindContext = this; |
| context = contextPath; |
| options = localizedOptions; |
| preserveContext = true; |
| } else { |
| Ember.assert("You must pass exactly one argument to the with helper", arguments.length === 2); |
| Ember.assert("You must pass a block to the with helper", options.fn && options.fn !== Handlebars.VM.noop); |
| |
| helperName += ' ' + context; |
| bindContext = options.contexts[0]; |
| preserveContext = false; |
| } |
| |
| options.helperName = helperName; |
| options.isWithHelper = true; |
| |
| return bind.call(bindContext, context, options, preserveContext, exists); |
| } |
| /** |
| 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(context, options) { |
| Ember.assert("You must pass exactly one argument to the if helper", arguments.length === 2); |
| Ember.assert("You must pass a block to the if helper", options.fn && options.fn !== Handlebars.VM.noop); |
| |
| options.helperName = options.helperName || ('if ' + context); |
| |
| if (options.data.isUnbound) { |
| return helpers.unboundIf.call(options.contexts[0], context, options); |
| } else { |
| return helpers.boundIf.call(options.contexts[0], context, options); |
| } |
| } |
| |
| /** |
| @method unless |
| @for Ember.Handlebars.helpers |
| @param {Function} context |
| @param {Hash} options |
| @return {String} HTML string |
| */ |
| function unlessHelper(context, options) { |
| Ember.assert("You must pass exactly one argument to the unless helper", arguments.length === 2); |
| Ember.assert("You must pass a block to the unless helper", options.fn && options.fn !== Handlebars.VM.noop); |
| |
| var fn = options.fn, inverse = options.inverse, helperName = 'unless'; |
| |
| if (context) { |
| helperName += ' ' + context; |
| } |
| |
| options.fn = inverse; |
| options.inverse = fn; |
| |
| options.helperName = options.helperName || helperName; |
| |
| if (options.data.isUnbound) { |
| return helpers.unboundIf.call(options.contexts[0], context, options); |
| } else { |
| return helpers.boundIf.call(options.contexts[0], context, options); |
| } |
| } |
| |
| /** |
| `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 will |
| 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(options) { |
| var attrs = options.hash; |
| |
| Ember.assert("You must specify at least one hash argument to bind-attr", !!keys(attrs).length); |
| |
| var view = options.data.view; |
| var ret = []; |
| |
| // we relied on the behavior of calling without |
| // context to mean this === window, but when running |
| // "use strict", it's possible for this to === undefined; |
| var ctx = this || window; |
| |
| // Generate a unique id for this element. This will be added as a |
| // data attribute to the element so it can be looked up when |
| // the bound property changes. |
| var dataId = uuid(); |
| |
| // Handle classes differently, as we can bind multiple classes |
| var classBindings = attrs['class']; |
| if (classBindings != null) { |
| var classResults = bindClasses(ctx, classBindings, view, dataId, options); |
| |
| ret.push('class="' + Handlebars.Utils.escapeExpression(classResults.join(' ')) + '"'); |
| delete attrs['class']; |
| } |
| |
| var attrKeys = keys(attrs); |
| |
| // For each attribute passed, create an observer and emit the |
| // current value of the property as an attribute. |
| forEach.call(attrKeys, function(attr) { |
| var path = attrs[attr], |
| normalized; |
| |
| Ember.assert(fmt("You must provide an expression as the value of bound attribute. You specified: %@=%@", [attr, path]), typeof path === 'string'); |
| |
| normalized = normalizePath(ctx, path, options.data); |
| |
| var value = (path === 'this') ? normalized.root : handlebarsGet(ctx, path, options), |
| 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'); |
| |
| var observer; |
| |
| observer = function observer() { |
| var result = handlebarsGet(ctx, path, options); |
| |
| Ember.assert(fmt("Attributes must be numbers, strings or booleans, not %@", [result]), |
| result === null || result === undefined || typeof result === 'number' || |
| typeof result === 'string' || typeof result === 'boolean'); |
| |
| var elem = view.$("[data-bindattr-" + dataId + "='" + dataId + "']"); |
| |
| // If we aren't able to find the element, it means the element |
| // to which we were bound has been removed from the view. |
| // In that case, we can assume the template has been re-rendered |
| // and we need to clean up the observer. |
| if (!elem || elem.length === 0) { |
| removeObserver(normalized.root, normalized.path, observer); |
| return; |
| } |
| |
| View.applyAttributeBindings(elem, attr, result); |
| }; |
| |
| // Add an observer to the view for when the property changes. |
| // When the observer fires, find the element using the |
| // unique data id and update the attribute to the new value. |
| // Note: don't add observer when path is 'this' or path |
| // is whole keyword e.g. {{#each x in list}} ... {{bind-attr attr="x"}} |
| if (path !== 'this' && !(normalized.isKeyword && normalized.path === '' )) { |
| view.registerObserver(normalized.root, normalized.path, observer); |
| } |
| |
| // if this changes, also change the logic in ember-views/lib/views/view.js |
| if ((type === 'string' || (type === 'number' && !isNaN(value)))) { |
| ret.push(attr + '="' + Handlebars.Utils.escapeExpression(value) + '"'); |
| } else if (value && type === 'boolean') { |
| // The developer controls the attr name, so it should always be safe |
| ret.push(attr + '="' + attr + '"'); |
| } |
| }, this); |
| |
| // Add the unique identifier |
| // NOTE: We use all lower-case since Firefox has problems with mixed case in SVG |
| ret.push('data-bindattr-' + dataId + '="' + dataId + '"'); |
| return new SafeString(ret.join(' ')); |
| } |
| |
| /** |
| See `bind-attr` |
| |
| @method bindAttr |
| @for Ember.Handlebars.helpers |
| @deprecated |
| @param {Function} context |
| @param {Hash} options |
| @return {String} HTML string |
| */ |
| function bindAttrHelperDeprecated() { |
| Ember.warn("The 'bindAttr' view helper is deprecated in favor of 'bind-attr'"); |
| return helpers['bind-attr'].apply(this, arguments); |
| } |
| |
| /** |
| Helper that, given a space-separated string of property paths and a context, |
| returns an array of class names. Calling this method also has the side |
| effect of setting up observers at those property paths, such that if they |
| change, the correct class name will be reapplied to the DOM element. |
| |
| For example, if you pass the string "fooBar", it will first look up the |
| "fooBar" value of the context. If that value is true, it will add the |
| "foo-bar" class to the current element (i.e., the dasherized form of |
| "fooBar"). If the value is a string, it will add that string as the class. |
| Otherwise, it will not add any new class name. |
| |
| @private |
| @method bindClasses |
| @for Ember.Handlebars |
| @param {Ember.Object} context The context from which to lookup properties |
| @param {String} classBindings A string, space-separated, of class bindings |
| to use |
| @param {View} view The view in which observers should look for the |
| element to update |
| @param {Srting} bindAttrId Optional bindAttr id used to lookup elements |
| @return {Array} An array of class names to add |
| */ |
| function bindClasses(context, classBindings, view, bindAttrId, options) { |
| var ret = [], newClass, value, elem; |
| |
| // Helper method to retrieve the property from the context and |
| // determine which class string to return, based on whether it is |
| // a Boolean or not. |
| var classStringForPath = function(root, parsedPath, options) { |
| var val, |
| path = parsedPath.path; |
| |
| if (path === 'this') { |
| val = root; |
| } else if (path === '') { |
| val = true; |
| } else { |
| val = handlebarsGet(root, path, options); |
| } |
| |
| return View._classStringForValue(path, val, parsedPath.className, parsedPath.falsyClassName); |
| }; |
| |
| // For each property passed, loop through and setup |
| // an observer. |
| forEach.call(classBindings.split(' '), function(binding) { |
| |
| // 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; |
| |
| var observer; |
| |
| var parsedPath = View._parsePropertyPath(binding), |
| path = parsedPath.path, |
| pathRoot = context, |
| normalized; |
| |
| if (path !== '' && path !== 'this') { |
| normalized = normalizePath(context, path, options.data); |
| |
| pathRoot = normalized.root; |
| path = normalized.path; |
| } |
| |
| // Set up an observer on the context. If the property changes, toggle the |
| // class name. |
| observer = function() { |
| // Get the current value of the property |
| newClass = classStringForPath(context, parsedPath, options); |
| elem = bindAttrId ? view.$("[data-bindattr-" + bindAttrId + "='" + bindAttrId + "']") : view.$(); |
| |
| // If we can't find the element anymore, a parent template has been |
| // re-rendered and we've been nuked. Remove the observer. |
| if (!elem || elem.length === 0) { |
| removeObserver(pathRoot, path, observer); |
| } else { |
| // If we had previously added a class to the element, remove it. |
| if (oldClass) { |
| elem.removeClass(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; |
| } |
| } |
| }; |
| |
| if (path !== '' && path !== 'this') { |
| view.registerObserver(pathRoot, path, observer); |
| } |
| |
| // We've already setup the observer; now we just need to figure out the |
| // correct behavior right now on the first pass through. |
| value = classStringForPath(context, parsedPath, options); |
| |
| if (value) { |
| ret.push(value); |
| |
| // Make sure we save the current value so that it can be removed if the |
| // observer fires. |
| oldClass = value; |
| } |
| }); |
| |
| return ret; |
| } |
| |
| __exports__.bind = bind; |
| __exports__._triageMustacheHelper = _triageMustacheHelper; |
| __exports__.resolveHelper = resolveHelper; |
| __exports__.bindHelper = bindHelper; |
| __exports__.boundIfHelper = boundIfHelper; |
| __exports__.unboundIfHelper = unboundIfHelper; |
| __exports__.withHelper = withHelper; |
| __exports__.ifHelper = ifHelper; |
| __exports__.unlessHelper = unlessHelper; |
| __exports__.bindAttrHelper = bindAttrHelper; |
| __exports__.bindAttrHelperDeprecated = bindAttrHelperDeprecated; |
| __exports__.bindClasses = bindClasses; |
| }); |
| define("ember-handlebars/helpers/collection", |
| ["ember-metal/core", "ember-metal/utils", "ember-handlebars-compiler", "ember-runtime/system/string", "ember-metal/property_get", "ember-handlebars/ext", "ember-handlebars/helpers/view", "ember-metal/computed", "ember-views/views/collection_view", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __exports__) { |
| "use strict"; |
| /** |
| @module ember |
| @submodule ember-handlebars |
| */ |
| |
| var Ember = __dependency1__["default"]; |
| // Ember.assert, Ember.deprecate |
| var inspect = __dependency2__.inspect; |
| |
| // var emberAssert = Ember.assert; |
| // emberDeprecate = Ember.deprecate; |
| |
| var EmberHandlebars = __dependency3__["default"]; |
| var helpers = EmberHandlebars.helpers; |
| |
| var fmt = __dependency4__.fmt; |
| var get = __dependency5__.get; |
| var handlebarsGet = __dependency6__.handlebarsGet; |
| var ViewHelper = __dependency7__.ViewHelper; |
| var computed = __dependency8__.computed; |
| var CollectionView = __dependency9__["default"]; |
| |
| var alias = computed.alias; |
| /** |
| `{{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 |
| {{#collection contentBinding="App.items"}} |
| Hi {{view.content.name}} |
| {{/collection}} |
| ``` |
| |
| And the following application code |
| |
| ```javascript |
| App = Ember.Application.create() |
| App.items = [ |
| Ember.Object.create({name: 'Dave'}), |
| Ember.Object.create({name: 'Mary'}), |
| Ember.Object.create({name: 'Sara'}) |
| ] |
| ``` |
| |
| Will result in the HTML structure below |
| |
| ```html |
| <div class="ember-view"> |
| <div class="ember-view">Hi Dave</div> |
| <div class="ember-view">Hi Mary</div> |
| <div class="ember-view">Hi Sara</div> |
| </div> |
| ``` |
| |
| ### Blockless use in a collection |
| |
| If you provide an `itemViewClass` option that has its own `template` you can |
| omit the block. |
| |
| The following template: |
| |
| ```handlebars |
| {{collection contentBinding="App.items" itemViewClass="App.AnItemView"}} |
| ``` |
| |
| And application code |
| |
| ```javascript |
| App = Ember.Application.create(); |
| App.items = [ |
| Ember.Object.create({name: 'Dave'}), |
| Ember.Object.create({name: 'Mary'}), |
| Ember.Object.create({name: 'Sara'}) |
| ]; |
| |
| 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 Dave</div> |
| <div class="ember-view">Greetings Mary</div> |
| <div class="ember-view">Greetings Sara</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 App.MyCustomCollectionClass contentBinding="App.items"}} |
| Hi {{view.content.name}} |
| {{/collection}} |
| ``` |
| |
| ### 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 contentBinding="App.items" |
| 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 Dave</p> |
| <p class="ember-view greeting">Howdy Mary</p> |
| <p class="ember-view greeting">Howdy Sara</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(path, options) { |
| Ember.deprecate("Using the {{collection}} helper without specifying a class has been deprecated as the {{each}} helper now supports the same functionality.", path !== 'collection'); |
| |
| // If no path is provided, treat path param as options. |
| if (path && path.data && path.data.isRenderData) { |
| options = path; |
| path = undefined; |
| Ember.assert("You cannot pass more than one argument to the collection helper", arguments.length === 1); |
| } else { |
| Ember.assert("You cannot pass more than one argument to the collection helper", arguments.length === 2); |
| } |
| |
| var fn = options.fn; |
| var data = options.data; |
| var inverse = options.inverse; |
| var view = options.data.view; |
| |
| |
| var controller, container; |
| // If passed a path string, convert that into an object. |
| // Otherwise, just default to the standard class. |
| var collectionClass; |
| if (path) { |
| controller = data.keywords.controller; |
| container = controller && controller.container; |
| collectionClass = handlebarsGet(this, path, options) || container.lookupFactory('view:' + path); |
| Ember.assert(fmt("%@ #collection: Could not find collection class %@", [data.view, path]), !!collectionClass); |
| } else { |
| collectionClass = CollectionView; |
| } |
| |
| var hash = options.hash, itemHash = {}, match; |
| |
| // Extract item view class if provided else default to the standard class |
| var collectionPrototype = collectionClass.proto(), itemViewClass; |
| |
| if (hash.itemView) { |
| controller = data.keywords.controller; |
| Ember.assert('You specified an itemView, but the current context has no ' + |
| 'container to look the itemView up in. This probably means ' + |
| 'that you created a view manually, instead of through the ' + |
| 'container. Instead, use container.lookup("view:viewName"), ' + |
| 'which will properly instantiate your view.', |
| controller && controller.container); |
| container = controller.container; |
| itemViewClass = container.lookupFactory('view:' + hash.itemView); |
| Ember.assert('You specified the itemView ' + hash.itemView + ", but it was " + |
| "not found at " + container.describe("view:" + hash.itemView) + |
| " (and it was not registered in the container)", !!itemViewClass); |
| } else if (hash.itemViewClass) { |
| itemViewClass = handlebarsGet(collectionPrototype, hash.itemViewClass, options); |
| } else { |
| itemViewClass = collectionPrototype.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 (hash.hasOwnProperty(prop)) { |
| match = prop.match(/^item(.)(.*)$/); |
| |
| if (match && prop !== 'itemController') { |
| // Convert itemShouldFoo -> shouldFoo |
| itemHash[match[1].toLowerCase() + match[2]] = hash[prop]; |
| // Delete from hash as this will end up getting passed to the |
| // {{view}} helper method. |
| delete hash[prop]; |
| } |
| } |
| } |
| |
| if (fn) { |
| itemHash.template = fn; |
| delete options.fn; |
| } |
| |
| var emptyViewClass; |
| if (inverse && inverse !== EmberHandlebars.VM.noop) { |
| emptyViewClass = get(collectionPrototype, 'emptyViewClass'); |
| emptyViewClass = emptyViewClass.extend({ |
| template: inverse, |
| tagName: itemHash.tagName |
| }); |
| } else if (hash.emptyViewClass) { |
| emptyViewClass = handlebarsGet(this, hash.emptyViewClass, options); |
| } |
| if (emptyViewClass) { |
| hash.emptyView = emptyViewClass; |
| } |
| |
| if (hash.keyword) { |
| itemHash._context = this; |
| } else { |
| itemHash._context = alias('content'); |
| } |
| |
| var viewOptions = ViewHelper.propertiesFromHTMLOptions({ |
| data: data, |
| hash: itemHash |
| }, this); |
| hash.itemViewClass = itemViewClass.extend(viewOptions); |
| |
| options.helperName = options.helperName || 'collection'; |
| |
| return helpers.view.call(this, collectionClass, options); |
| } |
| |
| __exports__["default"] = collectionHelper; |
| }); |
| define("ember-handlebars/helpers/debug", |
| ["ember-metal/core", "ember-metal/utils", "ember-metal/logger", "ember-metal/property_get", "ember-handlebars/ext", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) { |
| "use strict"; |
| /*jshint debug:true*/ |
| |
| /** |
| @module ember |
| @submodule ember-handlebars |
| */ |
| var Ember = __dependency1__["default"]; |
| // Ember.FEATURES, |
| var inspect = __dependency2__.inspect; |
| var Logger = __dependency3__["default"]; |
| |
| var get = __dependency4__.get; |
| var normalizePath = __dependency5__.normalizePath; |
| var handlebarsGet = __dependency5__.handlebarsGet; |
| |
| var a_slice = [].slice; |
| |
| /** |
| `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() { |
| var params = a_slice.call(arguments, 0, -1), |
| options = arguments[arguments.length - 1], |
| logger = Logger.log, |
| values = [], |
| allowPrimitives = true; |
| |
| for (var i = 0; i < params.length; i++) { |
| var type = options.types[i]; |
| |
| if (type === 'ID' || !allowPrimitives) { |
| var context = (options.contexts && options.contexts[i]) || this, |
| normalized = normalizePath(context, params[i], options.data); |
| |
| if (normalized.path === 'this') { |
| values.push(normalized.root); |
| } else { |
| values.push(handlebarsGet(normalized.root, normalized.path, options)); |
| } |
| } else { |
| values.push(params[i]); |
| } |
| } |
| |
| logger.apply(logger, values); |
| } |
| |
| /** |
| 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(options) { |
| |
| // These are helpful values you can inspect while debugging. |
| var templateContext = this; |
| var typeOfTemplateContext = inspect(templateContext); |
| Ember.Logger.info('Use `this` to access the context of the calling template.'); |
| |
| debugger; |
| } |
| |
| __exports__.logHelper = logHelper; |
| __exports__.debuggerHelper = debuggerHelper; |
| }); |
| define("ember-handlebars/helpers/each", |
| ["ember-metal/core", "ember-handlebars-compiler", "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-runtime/copy", "ember-metal/run_loop", "ember-metal/events", "ember-handlebars/ext", "ember-metal/computed", "ember-metal/observer", "ember-handlebars/views/metamorph_view", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __dependency16__, __dependency17__, __exports__) { |
| "use strict"; |
| |
| /** |
| @module ember |
| @submodule ember-handlebars |
| */ |
| var Ember = __dependency1__["default"]; |
| // Ember.assert;, Ember.K |
| // var emberAssert = Ember.assert, |
| var K = Ember.K; |
| |
| var EmberHandlebars = __dependency2__["default"]; |
| var helpers = EmberHandlebars.helpers; |
| |
| var fmt = __dependency3__.fmt; |
| var get = __dependency4__.get; |
| var set = __dependency5__.set; |
| var CollectionView = __dependency6__["default"]; |
| var Binding = __dependency7__.Binding; |
| var ControllerMixin = __dependency8__["default"]; |
| var ArrayController = __dependency9__["default"]; |
| var EmberArray = __dependency10__["default"]; |
| var copy = __dependency11__["default"]; |
| var run = __dependency12__["default"]; |
| var on = __dependency13__.on; |
| var handlebarsGet = __dependency14__.handlebarsGet; |
| var computed = __dependency15__.computed; |
| |
| var addObserver = __dependency16__.addObserver; |
| var removeObserver = __dependency16__.removeObserver; |
| var addBeforeObserver = __dependency16__.addBeforeObserver; |
| var removeBeforeObserver = __dependency16__.removeBeforeObserver; |
| |
| var _Metamorph = __dependency17__._Metamorph; |
| var _MetamorphView = __dependency17__._MetamorphView; |
| |
| var EachView = 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); |
| |
| // At the moment, if a container view subclass wants |
| // to insert keywords, it is responsible for cloning |
| // the keywords hash. This will be fixed momentarily. |
| var keyword = get(this, 'keyword'); |
| var content = get(view, 'content'); |
| |
| if (keyword) { |
| var data = get(view, 'templateData'); |
| |
| data = copy(data); |
| data.keywords = view.cloneKeywords(); |
| set(view, 'templateData', data); |
| |
| // In this case, we do not bind, because the `content` of |
| // a #each item cannot change. |
| data.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; |
| } |
| }); |
| |
| // Defeatureify doesn't seem to like nested functions that need to be removed |
| function _addMetamorphCheck() { |
| EachView.reopen({ |
| _checkMetamorph: on('didInsertElement', function() { |
| Ember.assert("The metamorph tags, " + |
| this.morph.start + " and " + this.morph.end + |
| ", have different parents.\nThe browser has fixed your template to output valid HTML (for example, check that you have properly closed all tags and have used a TBODY tag when creating a table with '{{#each}}')", |
| document.getElementById( this.morph.start ).parentNode === |
| document.getElementById( this.morph.end ).parentNode |
| ); |
| }) |
| }); |
| } |
| |
| // until ember-debug is es6ed |
| var runInDebug = function(f) { |
| f(); |
| }; |
| runInDebug( function() { |
| _addMetamorphCheck(); |
| }); |
| |
| var GroupedEach = EmberHandlebars.GroupedEach = function(context, path, options) { |
| var self = this, |
| normalized = EmberHandlebars.normalizePath(context, path, options.data); |
| |
| this.context = context; |
| this.path = path; |
| this.options = options; |
| this.template = options.fn; |
| this.containingView = options.data.view; |
| this.normalizedRoot = normalized.root; |
| this.normalizedPath = normalized.path; |
| this.content = this.lookupContent(); |
| |
| this.addContentObservers(); |
| this.addArrayObservers(); |
| |
| this.containingView.on('willClearRender', function() { |
| self.destroy(); |
| }); |
| }; |
| |
| GroupedEach.prototype = { |
| contentWillChange: function() { |
| this.removeArrayObservers(); |
| }, |
| |
| contentDidChange: function() { |
| this.content = this.lookupContent(); |
| this.addArrayObservers(); |
| this.rerenderContainingView(); |
| }, |
| |
| contentArrayWillChange: K, |
| |
| contentArrayDidChange: function() { |
| this.rerenderContainingView(); |
| }, |
| |
| lookupContent: function() { |
| return handlebarsGet(this.normalizedRoot, this.normalizedPath, this.options); |
| }, |
| |
| addArrayObservers: function() { |
| if (!this.content) { |
| return; |
| } |
| |
| this.content.addArrayObserver(this, { |
| willChange: 'contentArrayWillChange', |
| didChange: 'contentArrayDidChange' |
| }); |
| }, |
| |
| removeArrayObservers: function() { |
| if (!this.content) { |
| return; |
| } |
| |
| this.content.removeArrayObserver(this, { |
| willChange: 'contentArrayWillChange', |
| didChange: 'contentArrayDidChange' |
| }); |
| }, |
| |
| addContentObservers: function() { |
| addBeforeObserver(this.normalizedRoot, this.normalizedPath, this, this.contentWillChange); |
| addObserver(this.normalizedRoot, this.normalizedPath, this, this.contentDidChange); |
| }, |
| |
| removeContentObservers: function() { |
| removeBeforeObserver(this.normalizedRoot, this.normalizedPath, this.contentWillChange); |
| removeObserver(this.normalizedRoot, this.normalizedPath, this.contentDidChange); |
| }, |
| |
| render: function() { |
| if (!this.content) { |
| return; |
| } |
| |
| var content = this.content, |
| contentLength = get(content, 'length'), |
| options = this.options, |
| data = options.data, |
| template = this.template; |
| |
| data.insideEach = true; |
| for (var i = 0; i < contentLength; i++) { |
| var context = content.objectAt(i); |
| options.data.keywords[options.hash.keyword] = context; |
| template(context, { |
| data: data |
| }); |
| } |
| }, |
| |
| rerenderContainingView: function() { |
| var self = this; |
| run.scheduleOnce('render', this, function() { |
| // It's possible it's been destroyed after we enqueued a re-render call. |
| if (!self.destroyed) { |
| self.containingView.rerender(); |
| } |
| }); |
| }, |
| |
| destroy: function() { |
| this.removeContentObservers(); |
| if (this.content) { |
| this.removeArrayObservers(); |
| } |
| this.destroyed = true; |
| } |
| }; |
| |
| /** |
| The `{{#each}}` helper loops over elements in a collection, rendering its |
| block once for each item. It is an extension of the base Handlebars `{{#each}}` |
| helper: |
| |
| ```javascript |
| Developers = [{name: 'Yehuda'},{name: 'Tom'}, {name: 'Paul'}]; |
| ``` |
| |
| ```handlebars |
| {{#each Developers}} |
| {{name}} |
| {{/each}} |
| ``` |
| |
| `{{each}}` supports an alternative syntax with element naming: |
| |
| ```handlebars |
| {{#each person in Developers}} |
| {{person.name}} |
| {{/each}} |
| ``` |
| |
| When looping over objects that do not have properties, `{{this}}` can be used |
| to render the object: |
| |
| ```javascript |
| DeveloperNames = ['Yehuda', 'Tom', 'Paul'] |
| ``` |
| |
| ```handlebars |
| {{#each DeveloperNames}} |
| {{this}} |
| {{/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 a View class for items |
| If you provide an `itemViewClass` option that references a view class |
| with its own `template` you can omit the block. |
| |
| The following template: |
| |
| ```handlebars |
| {{#view App.MyView }} |
| {{each view.items itemViewClass="App.AnItemView"}} |
| {{/view}} |
| ``` |
| |
| And application code |
| |
| ```javascript |
| App = Ember.Application.create({ |
| MyView: Ember.View.extend({ |
| items: [ |
| Ember.Object.create({name: 'Dave'}), |
| Ember.Object.create({name: 'Mary'}), |
| Ember.Object.create({name: 'Sara'}) |
| ] |
| }) |
| }); |
| |
| App.AnItemView = Ember.View.extend({ |
| template: Ember.Handlebars.compile("Greetings {{name}}") |
| }); |
| ``` |
| |
| Will result in the HTML structure below |
| |
| ```html |
| <div class="ember-view"> |
| <div class="ember-view">Greetings Dave</div> |
| <div class="ember-view">Greetings Mary</div> |
| <div class="ember-view">Greetings Sara</div> |
| </div> |
| ``` |
| |
| If an `itemViewClass` is defined on the helper, and therefore the helper is not |
| being used as a block, an `emptyViewClass` can also be provided optionally. |
| The `emptyViewClass` will match the behavior of the `{{else}}` condition |
| described above. That is, the `emptyViewClass` will render if the collection |
| is empty. |
| |
| ### Representing each item with a Controller. |
| By default the controller lookup within an `{{#each}}` block will be |
| the controller of the template where the `{{#each}}` was used. If each |
| item needs to be presented by a custom controller you can provide a |
| `itemController` option which references a controller by lookup name. |
| Each item in the loop will be wrapped in an instance of this controller |
| and the item itself will be set to the `model` property of that controller. |
| |
| This is useful in cases where properties of model objects need transformation |
| or synthesis for display: |
| |
| ```javascript |
| App.DeveloperController = Ember.ObjectController.extend({ |
| isAvailableForHire: function() { |
| return !this.get('model.isEmployed') && this.get('model.isSeekingWork'); |
| }.property('isEmployed', 'isSeekingWork') |
| }) |
| ``` |
| |
| ```handlebars |
| {{#each person in developers itemController="developer"}} |
| {{person.name}} {{#if person.isAvailableForHire}}Hire me!{{/if}} |
| {{/each}} |
| ``` |
| |
| Each itemController will receive a reference to the current controller as |
| a `parentController` property. |
| |
| ### (Experimental) Grouped Each |
| |
| When used in conjunction with the experimental [group helper](https://github.com/emberjs/group-helper), |
| you can inform Handlebars to re-render an entire group of items instead of |
| re-rendering them one at a time (in the event that they are changed en masse |
| or an item is added/removed). |
| |
| ```handlebars |
| {{#group}} |
| {{#each people}} |
| {{firstName}} {{lastName}} |
| {{/each}} |
| {{/group}} |
| ``` |
| |
| This can be faster than the normal way that Handlebars re-renders items |
| in some cases. |
| |
| If for some reason you have a group with more than one `#each`, you can make |
| one of the collections be updated in normal (non-grouped) fashion by setting |
| the option `groupedRows=true` (counter-intuitive, I know). |
| |
| For example, |
| |
| ```handlebars |
| {{dealershipName}} |
| |
| {{#group}} |
| {{#each dealers}} |
| {{firstName}} {{lastName}} |
| {{/each}} |
| |
| {{#each car in cars groupedRows=true}} |
| {{car.make}} {{car.model}} {{car.color}} |
| {{/each}} |
| {{/group}} |
| ``` |
| Any change to `dealershipName` or the `dealers` collection will cause the |
| entire group to be re-rendered. However, changes to the `cars` collection |
| will be re-rendered individually (as normal). |
| |
| Note that `group` behavior is also disabled by specifying an `itemViewClass`. |
| |
| @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.itemController] {String} name of a controller to be created for each item |
| @param [options.groupedRows] {boolean} enable normal item-by-item rendering when inside a `#group` helper |
| */ |
| function eachHelper(path, options) { |
| var ctx, helperName = 'each'; |
| |
| if (arguments.length === 4) { |
| Ember.assert("If you pass more than one argument to the each helper, it must be in the form #each foo in bar", arguments[1] === "in"); |
| |
| var keywordName = arguments[0]; |
| |
| |
| options = arguments[3]; |
| path = arguments[2]; |
| |
| helperName += ' ' + keywordName + ' in ' + path; |
| |
| if (path === '') { |
| path = "this"; |
| } |
| |
| options.hash.keyword = keywordName; |
| |
| } else if (arguments.length === 1) { |
| options = path; |
| path = 'this'; |
| } else { |
| helperName += ' ' + path; |
| } |
| |
| options.hash.dataSourceBinding = path; |
| // Set up emptyView as a metamorph with no tag |
| //options.hash.emptyViewClass = Ember._MetamorphView; |
| |
| // can't rely on this default behavior when use strict |
| ctx = this || window; |
| |
| options.helperName = options.helperName || helperName; |
| |
| if (options.data.insideGroup && !options.hash.groupedRows && !options.hash.itemViewClass) { |
| new GroupedEach(ctx, path, options).render(); |
| } else { |
| // ES6TODO: figure out how to do this without global lookup. |
| return helpers.collection.call(ctx, 'Ember.Handlebars.EachView', options); |
| } |
| } |
| |
| __exports__.EachView = EachView; |
| __exports__.GroupedEach = GroupedEach; |
| __exports__.eachHelper = eachHelper; |
| }); |
| define("ember-handlebars/helpers/loc", |
| ["ember-runtime/system/string", "exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| var loc = __dependency1__.loc; |
| |
| /** |
| @module ember |
| @submodule ember-handlebars |
| */ |
| |
| // ES6TODO: |
| // Pretty sure this can be expressed as |
| // var locHelper EmberStringUtils.loc ? |
| |
| /** |
| Calls [Ember.String.loc](/api/classes/Ember.String.html#method_loc) with the |
| provided string. |
| |
| This is a convenient way to localize text. For example: |
| |
| ```html |
| <script type="text/x-handlebars" data-template-name="home"> |
| {{loc "welcome"}} |
| </script> |
| ``` |
| |
| Take note that `"welcome"` is a string and not an object |
| reference. |
| |
| 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} |
| */ |
| __exports__["default"] = function locHelper(str) { |
| return loc(str); |
| } |
| }); |
| define("ember-handlebars/helpers/partial", |
| ["ember-metal/core", "ember-metal/is_none", "ember-handlebars/ext", "ember-handlebars/helpers/binding", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| // Ember.assert |
| // var emberAssert = Ember.assert; |
| |
| var isNone = __dependency2__.isNone; |
| var handlebarsGet = __dependency3__.handlebarsGet; |
| var bind = __dependency4__.bind; |
| |
| /** |
| @module ember |
| @submodule ember-handlebars |
| */ |
| |
| /** |
| 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. |
| |
| ## Setting the partial's context with `with` |
| |
| The `partial` helper can be used in conjunction with the `with` |
| helper to set a context that will be used by the partial: |
| |
| ```handlebars |
| {{#with currentUser}} |
| {{partial "user_info"}} |
| {{/with}} |
| ``` |
| |
| @method partial |
| @for Ember.Handlebars.helpers |
| @param {String} partialName the name of the template to render minus the leading underscore |
| */ |
| |
| __exports__["default"] = function partialHelper(name, options) { |
| |
| var context = (options.contexts && options.contexts.length) ? options.contexts[0] : this; |
| |
| options.helperName = options.helperName || 'partial'; |
| |
| if (options.types[0] === "ID") { |
| // Helper was passed a property path; we need to |
| // create a binding that will re-render whenever |
| // this property changes. |
| options.fn = function(context, fnOptions) { |
| var partialName = handlebarsGet(context, name, fnOptions); |
| renderPartial(context, partialName, fnOptions); |
| }; |
| |
| return bind.call(context, name, options, true, exists); |
| } else { |
| // Render the partial right into parent template. |
| renderPartial(context, name, options); |
| } |
| } |
| |
| function exists(value) { |
| return !isNone(value); |
| } |
| |
| function renderPartial(context, name, options) { |
| var nameParts = name.split("/"); |
| var lastPart = nameParts[nameParts.length - 1]; |
| |
| nameParts[nameParts.length - 1] = "_" + lastPart; |
| |
| var view = options.data.view; |
| var underscoredName = nameParts.join("/"); |
| var template = view.templateForName(underscoredName); |
| var deprecatedTemplate = !template && view.templateForName(name); |
| |
| Ember.assert("Unable to find partial with name '" + name + "'.", template || deprecatedTemplate); |
| |
| template = template || deprecatedTemplate; |
| |
| template(context, { |
| data: options.data |
| }); |
| } |
| }); |
| define("ember-handlebars/helpers/shared", |
| ["ember-handlebars/ext", "exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| var handlebarsGet = __dependency1__.handlebarsGet; |
| |
| __exports__["default"] = function resolvePaths(options) { |
| var ret = [], |
| contexts = options.contexts, |
| roots = options.roots, |
| data = options.data; |
| |
| for (var i = 0, l = contexts.length; i < l; i++) { |
| ret.push(handlebarsGet(roots[i], contexts[i], { |
| data: data |
| })); |
| } |
| |
| return ret; |
| } |
| }); |
| define("ember-handlebars/helpers/template", |
| ["ember-metal/core", "ember-handlebars-compiler", "exports"], |
| function(__dependency1__, __dependency2__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| // var emberDeprecate = Ember.deprecate; |
| |
| var EmberHandlebars = __dependency2__["default"]; |
| var helpers = EmberHandlebars.helpers; |
| /** |
| @module ember |
| @submodule ember-handlebars |
| */ |
| |
| /** |
| `template` allows you to render a template from inside another template. |
| This allows you to re-use the same template in multiple places. For example: |
| |
| ```html |
| <script type="text/x-handlebars" data-template-name="logged_in_user"> |
| {{#with loggedInUser}} |
| Last Login: {{lastLogin}} |
| User Info: {{template "user_info"}} |
| {{/with}} |
| </script> |
| ``` |
| |
| ```html |
| <script type="text/x-handlebars" data-template-name="user_info"> |
| Name: <em>{{name}}</em> |
| Karma: <em>{{karma}}</em> |
| </script> |
| ``` |
| |
| ```handlebars |
| {{#if isUser}} |
| {{template "user_info"}} |
| {{else}} |
| {{template "unlogged_user_info"}} |
| {{/if}} |
| ``` |
| |
| This helper looks for templates in the global `Ember.TEMPLATES` hash. If you |
| add `<script>` tags to your page with the `data-template-name` attribute set, |
| they will be compiled and placed in this hash automatically. |
| |
| You can also manually register templates by adding them to the hash: |
| |
| ```javascript |
| Ember.TEMPLATES["my_cool_template"] = Ember.Handlebars.compile('<b>{{user}}</b>'); |
| ``` |
| |
| @deprecated |
| @method template |
| @for Ember.Handlebars.helpers |
| @param {String} templateName the template to render |
| */ |
| __exports__["default"] = function templateHelper(name, options) { |
| 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 helpers.partial.apply(this, arguments); |
| } |
| }); |
| define("ember-handlebars/helpers/unbound", |
| ["ember-handlebars-compiler", "ember-handlebars/helpers/binding", "ember-handlebars/ext", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __exports__) { |
| "use strict"; |
| /*globals Handlebars */ |
| |
| /** |
| @module ember |
| @submodule ember-handlebars |
| */ |
| |
| var EmberHandlebars = __dependency1__["default"]; |
| var helpers = EmberHandlebars.helpers; |
| |
| var resolveHelper = __dependency2__.resolveHelper; |
| var handlebarsGet = __dependency3__.handlebarsGet; |
| |
| var slice = [].slice; |
| |
| /** |
| `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 |
| */ |
| __exports__["default"] = function unboundHelper(property, fn) { |
| var options = arguments[arguments.length - 1], |
| container = options.data.view.container, |
| helper, context, out, ctx; |
| |
| ctx = this; |
| if (arguments.length > 2) { |
| // Unbound helper call. |
| options.data.isUnbound = true; |
| helper = resolveHelper(container, property) || helpers.helperMissing; |
| out = helper.apply(ctx, slice.call(arguments, 1)); |
| delete options.data.isUnbound; |
| return out; |
| } |
| |
| context = (fn.contexts && fn.contexts.length) ? fn.contexts[0] : ctx; |
| return handlebarsGet(context, property, fn); |
| } |
| }); |
| define("ember-handlebars/helpers/view", |
| ["ember-metal/core", "ember-runtime/system/object", "ember-metal/property_get", "ember-metal/property_set", "ember-metal/mixin", "ember-views/system/jquery", "ember-views/views/view", "ember-metal/binding", "ember-handlebars/ext", "ember-runtime/system/string", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __exports__) { |
| "use strict"; |
| /*globals Handlebars */ |
| |
| /** |
| @module ember |
| @submodule ember-handlebars |
| */ |
| |
| var Ember = __dependency1__["default"]; |
| // Ember.warn, Ember.assert |
| // var emberWarn = Ember.warn, emberAssert = Ember.assert; |
| |
| var EmberObject = __dependency2__["default"]; |
| var get = __dependency3__.get; |
| var set = __dependency4__.set; |
| var IS_BINDING = __dependency5__.IS_BINDING; |
| var jQuery = __dependency6__["default"]; |
| var View = __dependency7__["default"]; |
| var isGlobalPath = __dependency8__.isGlobalPath; |
| var normalizePath = __dependency9__.normalizePath; |
| var handlebarsGet = __dependency9__.handlebarsGet; |
| var EmberString = __dependency10__["default"]; |
| |
| |
| var LOWERCASE_A_Z = /^[a-z]/, |
| VIEW_PREFIX = /^view\./; |
| |
| function makeBindings(thisContext, options) { |
| var hash = options.hash, |
| hashType = options.hashTypes; |
| |
| for (var prop in hash) { |
| if (hashType[prop] === 'ID') { |
| |
| var value = hash[prop]; |
| |
| if (IS_BINDING.test(prop)) { |
| Ember.warn("You're attempting to render a view by passing " + prop + "=" + value + " to a view helper, but this syntax is ambiguous. You should either surround " + value + " in quotes or remove `Binding` from " + prop + "."); |
| } else { |
| hash[prop + 'Binding'] = value; |
| hashType[prop + 'Binding'] = 'STRING'; |
| delete hash[prop]; |
| delete hashType[prop]; |
| } |
| } |
| } |
| |
| if (hash.hasOwnProperty('idBinding')) { |
| // id can't be bound, so just perform one-time lookup. |
| hash.id = handlebarsGet(thisContext, hash.idBinding, options); |
| hashType.id = 'STRING'; |
| delete hash.idBinding; |
| delete hashType.idBinding; |
| } |
| } |
| |
| var ViewHelper = EmberObject.create({ |
| |
| propertiesFromHTMLOptions: function(options) { |
| var hash = options.hash, data = options.data; |
| var extensions = {}, |
| classes = hash['class'], |
| dup = false; |
| |
| if (hash.id) { |
| extensions.elementId = hash.id; |
| dup = true; |
| } |
| |
| if (hash.tag) { |
| extensions.tagName = hash.tag; |
| dup = true; |
| } |
| |
| if (classes) { |
| classes = classes.split(' '); |
| extensions.classNames = classes; |
| dup = true; |
| } |
| |
| if (hash.classBinding) { |
| extensions.classNameBindings = hash.classBinding.split(' '); |
| dup = true; |
| } |
| |
| if (hash.classNameBindings) { |
| if (extensions.classNameBindings === undefined) |
| extensions.classNameBindings = []; |
| extensions.classNameBindings = extensions.classNameBindings.concat(hash.classNameBindings.split(' ')); |
| dup = true; |
| } |
| |
| if (hash.attributeBindings) { |
| Ember.assert("Setting 'attributeBindings' via Handlebars is not allowed. Please subclass Ember.View and set it there instead."); |
| extensions.attributeBindings = null; |
| dup = true; |
| } |
| |
| if (dup) { |
| hash = jQuery.extend({}, hash); |
| delete hash.id; |
| delete hash.tag; |
| delete hash['class']; |
| delete hash.classBinding; |
| } |
| |
| // 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 path; |
| |
| // Evaluate the context of regular attribute bindings: |
| for (var prop in hash) { |
| if (!hash.hasOwnProperty(prop)) { |
| continue; |
| } |
| |
| // Test if the property ends in "Binding" |
| if (IS_BINDING.test(prop) && typeof hash[prop] === 'string') { |
| path = this.contextualizeBindingPath(hash[prop], data); |
| if (path) { |
| hash[prop] = path; |
| } |
| } |
| } |
| |
| // Evaluate the context of class name bindings: |
| if (extensions.classNameBindings) { |
| for (var b in extensions.classNameBindings) { |
| var full = extensions.classNameBindings[b]; |
| if (typeof full === 'string') { |
| // Contextualize the path of classNameBinding so this: |
| // |
| // classNameBinding="isGreen:green" |
| // |
| // is converted to this: |
| // |
| // classNameBinding="_parentView.context.isGreen:green" |
| var parsedPath = View._parsePropertyPath(full); |
| path = this.contextualizeBindingPath(parsedPath.path, data); |
| if (path) { |
| extensions.classNameBindings[b] = path + parsedPath.classNames; |
| } |
| } |
| } |
| } |
| |
| return jQuery.extend(hash, extensions); |
| }, |
| |
| // Transform bindings from the current context to a context that can be evaluated within the view. |
| // Returns null if the path shouldn't be changed. |
| // |
| // TODO: consider the addition of a prefix that would allow this method to return `path`. |
| contextualizeBindingPath: function(path, data) { |
| var normalized = normalizePath(null, path, data); |
| if (normalized.isKeyword) { |
| return 'templateData.keywords.' + path; |
| } else if (isGlobalPath(path)) { |
| return null; |
| } else if (path === 'this' || path === '') { |
| return '_parentView.context'; |
| } else { |
| return '_parentView.context.' + path; |
| } |
| }, |
| |
| helper: function(thisContext, path, options) { |
| var data = options.data, |
| fn = options.fn, |
| newView; |
| |
| makeBindings(thisContext, options); |
| |
| if ('string' === typeof path) { |
| var lookup; |
| // TODO: this is a lame conditional, this should likely change |
| // but something along these lines will likely need to be added |
| // as deprecation warnings |
| // |
| if (options.types[0] === 'STRING' && LOWERCASE_A_Z.test(path) && !VIEW_PREFIX.test(path)) { |
| lookup = path; |
| } else { |
| newView = handlebarsGet(thisContext, path, options); |
| if (typeof newView === 'string') { |
| lookup = newView; |
| } |
| } |
| |
| if (lookup) { |
| Ember.assert("View requires a container", !!data.view.container); |
| newView = data.view.container.lookupFactory('view:' + lookup); |
| } |
| |
| Ember.assert("Unable to find view at path '" + path + "'", !!newView); |
| } else { |
| newView = path; |
| } |
| |
| Ember.assert(EmberString.fmt('You must pass a view to the #view helper, not %@ (%@)', [path, newView]), View.detect(newView) || View.detectInstance(newView)); |
| |
| var viewOptions = this.propertiesFromHTMLOptions(options, thisContext); |
| var currentView = data.view; |
| viewOptions.templateData = data; |
| var newViewProto = newView.proto ? newView.proto() : newView; |
| |
| if (fn) { |
| Ember.assert("You cannot provide a template block if you also specified a templateName", !get(viewOptions, 'templateName') && !get(newViewProto, 'templateName')); |
| viewOptions.template = fn; |
| } |
| |
| // 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 = thisContext; |
| } |
| |
| // for instrumentation |
| if (options.helperName) { |
| viewOptions.helperName = options.helperName; |
| } |
| |
| 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 "MyApp.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. |
| |
| ```handlebars |
| {{view "MyApp.ViewWithATemplateDefined"}} |
| ``` |
| |
| ### `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(path, options) { |
| Ember.assert("The view helper only takes a single argument", arguments.length <= 2); |
| |
| // If no path is provided, treat path param as options |
| // and get an instance of the registered `view:toplevel` |
| if (path && path.data && path.data.isRenderData) { |
| options = path; |
| Ember.assert('{{view}} helper requires parent view to have a container but none was found. This usually happens when you are manually-managing views.', !!options.data.view.container); |
| path = options.data.view.container.lookupFactory('view:toplevel'); |
| } |
| |
| options.helperName = options.helperName || 'view'; |
| |
| return ViewHelper.helper(this, path, options); |
| } |
| |
| __exports__.viewHelper = viewHelper; |
| }); |
| define("ember-handlebars/helpers/yield", |
| ["ember-metal/core", "ember-metal/property_get", "exports"], |
| function(__dependency1__, __dependency2__, __exports__) { |
| "use strict"; |
| /** |
| @module ember |
| @submodule ember-handlebars |
| */ |
| |
| var Ember = __dependency1__["default"]; |
| // var emberAssert = Ember.assert; |
| |
| 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 |
| */ |
| __exports__["default"] = function yieldHelper(options) { |
| var view = options.data.view; |
| |
| 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); |
| |
| view._yield(this, options); |
| } |
| }); |
| define("ember-handlebars/loader", |
| ["ember-handlebars/component_lookup", "ember-views/system/jquery", "ember-metal/error", "ember-runtime/system/lazy_load", "ember-handlebars-compiler", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) { |
| "use strict"; |
| /*globals Handlebars */ |
| |
| var ComponentLookup = __dependency1__["default"]; |
| var jQuery = __dependency2__["default"]; |
| var EmberError = __dependency3__["default"]; |
| var onLoad = __dependency4__.onLoad; |
| |
| var EmberHandlebars = __dependency5__["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) : |
| jQuery.proxy(EmberHandlebars.compile, EmberHandlebars), |
| // 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. |
| templateName = script.attr('data-template-name') || script.attr('id') || 'application', |
| 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; |
| }); |
| define("ember-handlebars/string", |
| ["ember-runtime/system/string", "exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| // required so we can extend this object. |
| var EmberStringUtils = __dependency1__["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) { |
| return new Handlebars.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__["default"] = htmlSafe; |
| }); |
| define("ember-handlebars/views/handlebars_bound_view", |
| ["ember-handlebars-compiler", "ember-metal/core", "ember-metal/error", "ember-metal/property_get", "ember-metal/property_set", "ember-metal/merge", "ember-metal/run_loop", "ember-metal/computed", "ember-views/views/view", "ember-views/views/states", "ember-handlebars/views/metamorph_view", "ember-handlebars/ext", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __exports__) { |
| "use strict"; |
| /*globals Handlebars, Metamorph:true */ |
| /*jshint newcap:false*/ |
| |
| |
| /** |
| @module ember |
| @submodule ember-handlebars |
| */ |
| |
| var EmberHandlebars = __dependency1__["default"]; |
| // EmberHandlebars.SafeString; |
| var SafeString = EmberHandlebars.SafeString; |
| |
| var Ember = __dependency2__["default"]; |
| // Ember.K |
| var K = Ember.K; |
| |
| var Metamorph = requireModule('metamorph'); |
| |
| var EmberError = __dependency3__["default"]; |
| var get = __dependency4__.get; |
| var set = __dependency5__.set; |
| var merge = __dependency6__["default"]; |
| var run = __dependency7__["default"]; |
| var computed = __dependency8__.computed; |
| var View = __dependency9__["default"]; |
| var cloneStates = __dependency10__.cloneStates; |
| var states = __dependency10__.states; |
| var viewStates = states; |
| |
| var _MetamorphView = __dependency11__["default"]; |
| var handlebarsGet = __dependency12__.handlebarsGet; |
| |
| function SimpleHandlebarsView(path, pathRoot, isEscaped, templateData) { |
| this.path = path; |
| this.pathRoot = pathRoot; |
| this.isEscaped = isEscaped; |
| this.templateData = templateData; |
| |
| this._lastNormalizedValue = undefined; |
| this.morph = Metamorph(); |
| this.state = 'preRender'; |
| this.updateId = null; |
| this._parentView = null; |
| this.buffer = null; |
| } |
| |
| SimpleHandlebarsView.prototype = { |
| isVirtual: true, |
| isView: true, |
| |
| 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 path = this.path; |
| var pathRoot = this.pathRoot; |
| var result, templateData; |
| |
| // Use the pathRoot as the result if no path is provided. This |
| // happens if the path is `this`, which gets normalized into |
| // a `pathRoot` of the current Handlebars context and a path |
| // of `''`. |
| if (path === '') { |
| result = pathRoot; |
| } else { |
| templateData = this.templateData; |
| result = handlebarsGet(pathRoot, path, { |
| data: templateData |
| }); |
| } |
| |
| return result; |
| }, |
| |
| renderToBuffer: function(buffer) { |
| var string = ''; |
| |
| string += this.morph.startTag(); |
| string += this.render(); |
| string += this.morph.endTag(); |
| |
| buffer.push(string); |
| }, |
| |
| render: function(value) { |
| // If not invoked via a triple-mustache ({{{foo}}}), escape |
| // the content of the template. |
| var escape = this.isEscaped; |
| var result = value || this.normalizedValue(); |
| this._lastNormalizedValue = result; |
| if (result === null || result === undefined) { |
| result = ""; |
| } else if (!(result instanceof SafeString)) { |
| result = String(result); |
| } |
| |
| if (escape) { |
| result = Handlebars.Utils.escapeExpression(result); |
| } |
| return result; |
| }, |
| |
| 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(); |
| if (value !== this._lastNormalizedValue) { |
| this.morph.html(this.render(value)); |
| } |
| }, |
| |
| _transitionTo: function(state) { |
| this.state = state; |
| } |
| }; |
| |
| states = cloneStates(viewStates); |
| |
| merge(states._default, { |
| rerenderIfNeeded: K |
| }); |
| |
| merge(states.inDOM, { |
| rerenderIfNeeded: function(view) { |
| if (view.normalizedValue() !== view._lastNormalizedValue) { |
| view.rerender(); |
| } |
| } |
| }); |
| |
| /** |
| `Ember._HandlebarsBoundView` 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._HandlebarsBoundView` 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 _HandlebarsBoundView |
| @namespace Ember |
| @extends Ember._MetamorphView |
| @private |
| */ |
| var _HandlebarsBoundView = _MetamorphView.extend({ |
| instrumentName: 'boundHandlebars', |
| |
| _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, |
| |
| |
| /** |
| The path to look up on `pathRoot` that is passed to |
| `shouldDisplayFunc` to determine which template to render. |
| |
| In addition, if `preserveContext` is `false,` the object at this path will |
| be passed to the template when rendering. |
| |
| @property path |
| @type String |
| @default null |
| */ |
| path: null, |
| |
| /** |
| The object from which the `path` will be looked up. Sometimes this is the |
| same as the `previousContext`, but in cases where this view has been |
| generated for paths that start with a keyword such as `view` or |
| `controller`, the path root will be that resolved object. |
| |
| @property pathRoot |
| @type Object |
| */ |
| pathRoot: null, |
| |
| normalizedValue: function() { |
| var path = get(this, 'path'), |
| pathRoot = get(this, 'pathRoot'), |
| valueNormalizer = get(this, 'valueNormalizerFunc'), |
| result, templateData; |
| |
| // Use the pathRoot as the result if no path is provided. This |
| // happens if the path is `this`, which gets normalized into |
| // a `pathRoot` of the current Handlebars context and a path |
| // of `''`. |
| if (path === '') { |
| result = pathRoot; |
| } else { |
| templateData = get(this, 'templateData'); |
| result = handlebarsGet(pathRoot, path, { |
| data: templateData |
| }); |
| } |
| |
| return valueNormalizer ? valueNormalizer(result) : result; |
| }, |
| |
| 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._HandlebarsBoundView` 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'), |
| preserveContext = get(this, 'preserveContext'), |
| context = get(this, 'previousContext'); |
| |
| var inverseTemplate = get(this, 'inverseTemplate'), |
| 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 = Handlebars.Utils.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__._HandlebarsBoundView = _HandlebarsBoundView; |
| __exports__.SimpleHandlebarsView = SimpleHandlebarsView; |
| }); |
| define("ember-handlebars/views/metamorph_view", |
| ["ember-metal/core", "ember-metal/property_get", "ember-metal/property_set", "ember-views/views/core_view", "ember-views/views/view", "ember-metal/mixin", "ember-metal/run_loop", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) { |
| "use strict"; |
| /* global Metamorph:true */ |
| |
| /*jshint newcap:false*/ |
| var Ember = __dependency1__["default"]; |
| // Ember.deprecate |
| // var emberDeprecate = Ember.deprecate; |
| |
| var get = __dependency2__.get; |
| var set = __dependency3__["default"]; |
| |
| var CoreView = __dependency4__["default"]; |
| var View = __dependency5__["default"]; |
| var Mixin = __dependency6__.Mixin; |
| var run = __dependency7__["default"]; |
| |
| /** |
| @module ember |
| @submodule ember-handlebars |
| */ |
| |
| var Metamorph = requireModule('metamorph'); |
| |
| function notifyMutationListeners() { |
| run.once(View, 'notifyMutationListeners'); |
| } |
| |
| // DOMManager should just abstract dom manipulation between jquery and metamorph |
| var DOMManager = { |
| remove: function(view) { |
| view.morph.remove(); |
| notifyMutationListeners(); |
| }, |
| |
| prepend: function(view, html) { |
| view.morph.prepend(html); |
| notifyMutationListeners(); |
| }, |
| |
| after: function(view, html) { |
| view.morph.after(html); |
| notifyMutationListeners(); |
| }, |
| |
| html: function(view, html) { |
| view.morph.html(html); |
| notifyMutationListeners(); |
| }, |
| |
| // This is messed up. |
| replace: function(view) { |
| var morph = view.morph; |
| |
| view._transitionTo('preRender'); |
| |
| run.schedule('render', this, function renderMetamorphView() { |
| if (view.isDestroying) { |
| return; |
| } |
| |
| view.clearRenderedChildren(); |
| var buffer = view.renderToBuffer(); |
| |
| view.invokeRecursively(function(view) { |
| view.propertyWillChange('element'); |
| }); |
| view.triggerRecursively('willInsertElement'); |
| |
| morph.replaceWith(buffer.string()); |
| view._transitionTo('inDOM'); |
| |
| view.invokeRecursively(function(view) { |
| view.propertyDidChange('element'); |
| }); |
| view.triggerRecursively('didInsertElement'); |
| |
| notifyMutationListeners(); |
| }); |
| }, |
| |
| empty: function(view) { |
| view.morph.html(""); |
| notifyMutationListeners(); |
| } |
| }; |
| |
| // 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(); |
| this.morph = Metamorph(); |
| 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); |
| }, |
| |
| beforeRender: function(buffer) { |
| buffer.push(this.morph.startTag()); |
| buffer.pushOpeningTag(); |
| }, |
| |
| afterRender: function(buffer) { |
| buffer.pushClosingTag(); |
| buffer.push(this.morph.endTag()); |
| }, |
| |
| createElement: function() { |
| var buffer = this.renderToBuffer(); |
| this.outerHTML = buffer.string(); |
| this.clearBuffer(); |
| }, |
| |
| domManager: DOMManager |
| }); |
| __exports__._Metamorph = _Metamorph; |
| var _wrapMap = Metamorph._wrapMap; |
| __exports__._wrapMap = _wrapMap; |
| /** |
| @class _MetamorphView |
| @namespace Ember |
| @extends Ember.View |
| @uses Ember._Metamorph |
| @private |
| */ |
| var _MetamorphView = View.extend(_Metamorph); |
| __exports__._MetamorphView = _MetamorphView; |
| /** |
| @class _SimpleMetamorphView |
| @namespace Ember |
| @extends Ember.CoreView |
| @uses Ember._Metamorph |
| @private |
| */ |
| var _SimpleMetamorphView = CoreView.extend(_Metamorph); |
| __exports__._SimpleMetamorphView = _SimpleMetamorphView; |
| __exports__["default"] = View.extend(_Metamorph); |
| }); |
| define("ember-metal", |
| ["ember-metal/core", "ember-metal/merge", "ember-metal/instrumentation", "ember-metal/utils", "ember-metal/error", "ember-metal/enumerable_utils", "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", "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__, __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 subscribe = __dependency3__.subscribe; |
| var unsubscribe = __dependency3__.unsubscribe; |
| var reset = __dependency3__.reset; |
| var generateGuid = __dependency4__.generateGuid; |
| var GUID_KEY = __dependency4__.GUID_KEY; |
| var GUID_PREFIX = __dependency4__.GUID_PREFIX; |
| var guidFor = __dependency4__.guidFor; |
| var META_DESC = __dependency4__.META_DESC; |
| var EMPTY_META = __dependency4__.EMPTY_META; |
| var meta = __dependency4__.meta; |
| var getMeta = __dependency4__.getMeta; |
| var setMeta = __dependency4__.setMeta; |
| var metaPath = __dependency4__.metaPath; |
| var inspect = __dependency4__.inspect; |
| var typeOf = __dependency4__.typeOf; |
| var tryCatchFinally = __dependency4__.tryCatchFinally; |
| var isArray = __dependency4__.isArray; |
| var makeArray = __dependency4__.makeArray; |
| var canInvoke = __dependency4__.canInvoke; |
| var tryInvoke = __dependency4__.tryInvoke; |
| var tryFinally = __dependency4__.tryFinally; |
| var wrap = __dependency4__.wrap; |
| var apply = __dependency4__.apply; |
| var applyStr = __dependency4__.applyStr; |
| var EmberError = __dependency5__["default"]; |
| var EnumerableUtils = __dependency6__["default"]; |
| |
| var create = __dependency7__.create; |
| var platform = __dependency7__.platform; |
| var map = __dependency8__.map; |
| var forEach = __dependency8__.forEach; |
| var filter = __dependency8__.filter; |
| var indexOf = __dependency8__.indexOf; |
| var Logger = __dependency9__["default"]; |
| |
| var get = __dependency10__.get; |
| var getWithDefault = __dependency10__.getWithDefault; |
| var normalizeTuple = __dependency10__.normalizeTuple; |
| var _getPath = __dependency10__._getPath; |
| |
| var on = __dependency11__.on; |
| var addListener = __dependency11__.addListener; |
| var removeListener = __dependency11__.removeListener; |
| var suspendListener = __dependency11__.suspendListener; |
| var suspendListeners = __dependency11__.suspendListeners; |
| var sendEvent = __dependency11__.sendEvent; |
| var hasListeners = __dependency11__.hasListeners; |
| var watchedEvents = __dependency11__.watchedEvents; |
| var listenersFor = __dependency11__.listenersFor; |
| var listenersDiff = __dependency11__.listenersDiff; |
| var listenersUnion = __dependency11__.listenersUnion; |
| |
| var ObserverSet = __dependency12__["default"]; |
| |
| var propertyWillChange = __dependency13__.propertyWillChange; |
| var propertyDidChange = __dependency13__.propertyDidChange; |
| var overrideChains = __dependency13__.overrideChains; |
| var beginPropertyChanges = __dependency13__.beginPropertyChanges; |
| var endPropertyChanges = __dependency13__.endPropertyChanges; |
| var changeProperties = __dependency13__.changeProperties; |
| |
| var Descriptor = __dependency14__.Descriptor; |
| var defineProperty = __dependency14__.defineProperty; |
| var set = __dependency15__.set; |
| var trySet = __dependency15__.trySet; |
| |
| var OrderedSet = __dependency16__.OrderedSet; |
| var Map = __dependency16__.Map; |
| var MapWithDefault = __dependency16__.MapWithDefault; |
| var getProperties = __dependency17__["default"]; |
| var setProperties = __dependency18__["default"]; |
| var watchKey = __dependency19__.watchKey; |
| var unwatchKey = __dependency19__.unwatchKey; |
| var flushPendingChains = __dependency20__.flushPendingChains; |
| var removeChainWatcher = __dependency20__.removeChainWatcher; |
| var ChainNode = __dependency20__.ChainNode; |
| var finishChains = __dependency20__.finishChains; |
| var watchPath = __dependency21__.watchPath; |
| var unwatchPath = __dependency21__.unwatchPath; |
| var watch = __dependency22__.watch; |
| var isWatching = __dependency22__.isWatching; |
| var unwatch = __dependency22__.unwatch; |
| var rewatch = __dependency22__.rewatch; |
| var destroy = __dependency22__.destroy; |
| var expandProperties = __dependency23__["default"]; |
| var ComputedProperty = __dependency24__.ComputedProperty; |
| var computed = __dependency24__.computed; |
| var cacheFor = __dependency24__.cacheFor; |
| |
| // side effect of defining the computed.* macros |
| |
| var addObserver = __dependency26__.addObserver; |
| var observersFor = __dependency26__.observersFor; |
| var removeObserver = __dependency26__.removeObserver; |
| var addBeforeObserver = __dependency26__.addBeforeObserver; |
| var _suspendBeforeObserver = __dependency26__._suspendBeforeObserver; |
| var _suspendObserver = __dependency26__._suspendObserver; |
| var _suspendBeforeObservers = __dependency26__._suspendBeforeObservers; |
| var _suspendObservers = __dependency26__._suspendObservers; |
| var beforeObserversFor = __dependency26__.beforeObserversFor; |
| var removeBeforeObserver = __dependency26__.removeBeforeObserver; |
| var IS_BINDING = __dependency27__.IS_BINDING; |
| var mixin = __dependency27__.mixin; |
| var Mixin = __dependency27__.Mixin; |
| var required = __dependency27__.required; |
| var aliasMethod = __dependency27__.aliasMethod; |
| var observer = __dependency27__.observer; |
| var immediateObserver = __dependency27__.immediateObserver; |
| var beforeObserver = __dependency27__.beforeObserver; |
| var Binding = __dependency28__.Binding; |
| var isGlobalPath = __dependency28__.isGlobalPath; |
| var bind = __dependency28__.bind; |
| var oneWay = __dependency28__.oneWay; |
| var run = __dependency29__["default"]; |
| var libraries = __dependency30__["default"]; |
| var isNone = __dependency31__.isNone; |
| var none = __dependency31__.none; |
| var isEmpty = __dependency32__.isEmpty; |
| var empty = __dependency32__.empty; |
| var isBlank = __dependency33__["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.generateGuid = generateGuid; |
| Ember.GUID_KEY = GUID_KEY; |
| Ember.GUID_PREFIX = GUID_PREFIX; |
| Ember.create = create; |
| Ember.platform = platform; |
| |
| 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.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.listenersDiff = listenersDiff; |
| Ember.listenersUnion = listenersUnion; |
| |
| 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; |
| |
| Ember.libraries = libraries; |
| Ember.libraries.registerCoreLibrary('Ember', Ember.VERSION); |
| |
| Ember.isNone = isNone; |
| Ember.none = none; |
| |
| Ember.isEmpty = isEmpty; |
| Ember.empty = empty; |
| |
| Ember.isBlank = isBlank; |
| |
| 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; |
| }); |
| define("ember-metal/alias", |
| ["ember-metal/property_get", "ember-metal/property_set", "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__, __exports__) { |
| "use strict"; |
| var get = __dependency1__.get; |
| var set = __dependency2__.set; |
| var EmberError = __dependency3__["default"]; |
| var Descriptor = __dependency4__.Descriptor; |
| var defineProperty = __dependency4__.defineProperty; |
| var ComputedProperty = __dependency5__.ComputedProperty; |
| var create = __dependency6__.create; |
| var meta = __dependency7__.meta; |
| var inspect = __dependency7__.inspect; |
| var addDependentKeys = __dependency8__.addDependentKeys; |
| var removeDependentKeys = __dependency8__.removeDependentKeys; |
| |
| function alias(altKey) { |
| return new AliasedProperty(altKey); |
| } |
| |
| __exports__.alias = alias; |
| 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) { |
| 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; |
| }); |
| define("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; |
| }; |
| |
| // From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/map |
| var map = isNativeFunc(ArrayPrototype.map) ? ArrayPrototype.map : function(fun /*, thisp */ |
| ) { |
| //"use strict"; |
| |
| if (this === void 0 || this === null) { |
| throw new TypeError(); |
| } |
| |
| var t = Object(this); |
| var len = t.length >>> 0; |
| if (typeof fun !== "function") { |
| throw new TypeError(); |
| } |
| |
| 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 = isNativeFunc(ArrayPrototype.forEach) ? ArrayPrototype.forEach : function(fun /*, thisp */ |
| ) { |
| //"use strict"; |
| |
| if (this === void 0 || this === null) { |
| throw new TypeError(); |
| } |
| |
| var t = Object(this); |
| var len = t.length >>> 0; |
| if (typeof fun !== "function") { |
| throw new TypeError(); |
| } |
| |
| var thisp = arguments[1]; |
| for (var i = 0; i < len; i++) { |
| if (i in t) { |
| fun.call(thisp, t[i], i, t); |
| } |
| } |
| }; |
| |
| var indexOf = isNativeFunc(ArrayPrototype.indexOf) ? 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 filter = isNativeFunc(ArrayPrototype.filter) ? ArrayPrototype.filter : function (fn, context) { |
| var i, |
| value, |
| result = [], |
| 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) { |
| if (!ArrayPrototype.map) { |
| ArrayPrototype.map = map; |
| } |
| |
| if (!ArrayPrototype.forEach) { |
| ArrayPrototype.forEach = forEach; |
| } |
| |
| if (!ArrayPrototype.filter) { |
| ArrayPrototype.filter = filter; |
| } |
| |
| if (!ArrayPrototype.indexOf) { |
| ArrayPrototype.indexOf = indexOf; |
| } |
| } |
| |
| /** |
| 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; |
| }); |
| define("ember-metal/binding", |
| ["ember-metal/core", "ember-metal/property_get", "ember-metal/property_set", "ember-metal/utils", "ember-metal/map", "ember-metal/observer", "ember-metal/run_loop", "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 set = __dependency3__.set; |
| var trySet = __dependency3__.trySet; |
| var guidFor = __dependency4__.guidFor; |
| var Map = __dependency5__.Map; |
| var addObserver = __dependency6__.addObserver; |
| var removeObserver = __dependency6__.removeObserver; |
| var _suspendObserver = __dependency6__._suspendObserver; |
| var run = __dependency7__["default"]; |
| |
| // 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; |
| |
| var IS_GLOBAL = /^([A-Z$]|([0-9][A-Z$]))/; |
| |
| /** |
| 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 isGlobalPath(path) { |
| return IS_GLOBAL.test(path); |
| } |
| |
| function getWithGlobals(obj, path) { |
| return get(isGlobalPath(path) ? Ember.lookup : obj, path); |
| } |
| |
| // .......................................................... |
| // BINDING |
| // |
| |
| function Binding(toPath, fromPath) { |
| this._direction = 'fwd'; |
| this._from = fromPath; |
| this._to = toPath; |
| this._directionMap = Map.create(); |
| 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, 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 directionMap = this._directionMap; |
| var existingDir = directionMap.get(obj); |
| |
| // if we haven't scheduled the binding yet, schedule it |
| if (!existingDir) { |
| run.schedule('sync', this, this._sync, obj); |
| directionMap.set(obj, 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') { |
| directionMap.set(obj, '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 directionMap = this._directionMap; |
| var direction = directionMap.get(obj); |
| |
| var fromPath = this._from, toPath = this._to; |
| |
| directionMap.remove(obj); |
| |
| // 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() { |
| var C = this, binding = new C(); |
| return binding.from.apply(binding, arguments); |
| }, |
| |
| /* |
| See `Ember.Binding.to`. |
| |
| @method to |
| @static |
| */ |
| to: function() { |
| var C = this, binding = new C(); |
| return binding.to.apply(binding, arguments); |
| }, |
| |
| /** |
| 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, binding = new C(null, from); |
| return binding.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 instanced 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(this.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. |
| |
| 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; |
| }); |
| define("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 meta = __dependency3__.meta; |
| var META_KEY = __dependency3__.META_KEY; |
| var forEach = __dependency4__.forEach; |
| var watchKey = __dependency5__.watchKey; |
| var unwatchKey = __dependency5__.unwatchKey; |
| |
| var metaFor = meta, |
| warn = Ember.warn, |
| 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), 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[META_KEY]; |
| 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[META_KEY]; |
| // 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), |
| paths = this._paths, path; |
| for (path in paths) { |
| if (paths[path] <= 0) { |
| continue; |
| } |
| // this check will also catch non-number vals. |
| 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, 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, node = chains[key]; |
| |
| // unchain rest of path first... |
| if (path && path.length > 1) { |
| key = firstKey(path); |
| path = path.slice(key.length + 1); |
| node.unchain(key, path); |
| } |
| |
| // 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[META_KEY], chains = m && m.chains; |
| if (chains) { |
| if (chains.value() !== obj) { |
| metaFor(obj).chains = chains = chains.copy(obj); |
| } else { |
| chains.didChange(null); |
| } |
| } |
| } |
| |
| __exports__.finishChains = finishChains; |
| __exports__.removeChainWatcher = removeChainWatcher; |
| __exports__.ChainNode = ChainNode; |
| }); |
| define("ember-metal/computed", |
| ["ember-metal/core", "ember-metal/property_get", "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__, __dependency8__, __dependency9__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| var get = __dependency2__.get; |
| var set = __dependency3__.set; |
| var meta = __dependency4__.meta; |
| var META_KEY = __dependency4__.META_KEY; |
| var inspect = __dependency4__.inspect; |
| var expandProperties = __dependency5__["default"]; |
| var EmberError = __dependency6__["default"]; |
| var Descriptor = __dependency7__.Descriptor; |
| var defineProperty = __dependency7__.defineProperty; |
| var propertyWillChange = __dependency8__.propertyWillChange; |
| var propertyDidChange = __dependency8__.propertyDidChange; |
| var addDependentKeys = __dependency9__.addDependentKeys; |
| var removeDependentKeys = __dependency9__.removeDependentKeys; |
| |
| /** |
| @module ember-metal |
| */ |
| |
| Ember.warn("The CP_DEFAULT_CACHEABLE flag has been removed and computed properties are always cached by default. Use `volatile` if you don't want caching.", Ember.ENV.CP_DEFAULT_CACHEABLE !== false); |
| |
| |
| var metaFor = meta, |
| a_slice = [].slice; |
| |
| function UNDEFINED() {} |
| |
| // .......................................................... |
| // COMPUTED PROPERTY |
| // |
| |
| /** |
| A computed property transforms an objects 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 is dependent 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 properties |
| dependencies (depending on firstName and lastName). The fullName function |
| will be called once (regardless of how many times it is accessed) as long |
| as it's dependencies have not been 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._cacheable = (opts && opts.cacheable !== undefined) ? opts.cacheable : true; |
| this._dependentKeys = opts && opts.dependentKeys; |
| this._readOnly = opts && (opts.readOnly !== undefined || !!opts.readOnly) || false; |
| } |
| |
| ComputedProperty.prototype = new Descriptor(); |
| |
| var ComputedPropertyPrototype = ComputedProperty.prototype; |
| ComputedPropertyPrototype._dependentKeys = undefined; |
| ComputedPropertyPrototype._suspended = undefined; |
| ComputedPropertyPrototype._meta = undefined; |
| |
| /** |
| 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 |
| */ |
| ComputedPropertyPrototype.cacheable = function(aFlag) { |
| 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() { |
| return this.cacheable(false); |
| }; |
| |
| /** |
| 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) { |
| this._readOnly = readOnly === undefined || !!readOnly; |
| 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. |
| |
| @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(obj, keyName, value) { |
| var cacheable = this._cacheable, |
| func = this.func, |
| meta = metaFor(obj, cacheable), |
| oldSuspended = this._suspended, |
| hadCachedValue = false, |
| cache = meta.cache, |
| funcArgLength, cachedValue, ret; |
| |
| if (this._readOnly) { |
| throw new EmberError('Cannot set read-only property "' + keyName + '" on object: ' + inspect(obj)); |
| } |
| |
| this._suspended = obj; |
| |
| try { |
| |
| 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); |
| } |
| } finally { |
| this._suspended = oldSuspended; |
| } |
| 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. |
| |
| @method computed |
| @for Ember |
| @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[META_KEY], |
| cache = meta && meta.cache, |
| 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; |
| }); |
| define("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__.isNone; |
| var alias = __dependency7__.alias; |
| |
| /** |
| @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({ |
| done: Ember.computed.empty('todos') |
| }); |
| |
| var todoList = ToDoList.create({ |
| todos: ['Unit Test', 'Documentation', 'Release'] |
| }); |
| |
| todoList.get('done'); // false |
| todoList.get('todos').clear(); |
| todoList.get('done'); // 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. |
| |
| Note: When using `computed.notEmpty` to watch an array make sure to |
| use the `array.[]` syntax so the computed can subscribe to transitions |
| from empty to non-empty states. |
| |
| 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. |
| */ |
| registerComputed('notEmpty', function(dependentKey) { |
| 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 provied 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 then 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 then 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 properties in to an array. |
| */ |
| registerComputedWithProperties('collect', function(properties) { |
| var res = []; |
| 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 propogate 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`. |
| */ |
| // 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) { |
| 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. |
| */ |
| 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); |
| } |
| }); |
| }; |
| }); |
| define("ember-metal/core", |
| ["exports"], |
| function(__exports__) { |
| "use strict"; |
| /*globals Ember:true,Em: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.7.0-beta.4+pre.4b6ff143 |
| */ |
| |
| 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; |
| var imports = Ember.imports = Ember.imports || this; |
| var exports = Ember.exports = Ember.exports || this; |
| var lookup = Ember.lookup = Ember.lookup || 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.7.0-beta.4+pre.4b6ff143' |
| @static |
| */ |
| Ember.VERSION = '1.7.0-beta.4+pre.4b6ff143'; |
| |
| /** |
| 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 before creating your application. |
| |
| You can also define `ENV.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: |
| |
| * `ENV.ENABLE_ALL_FEATURES` - force all features to be enabled. |
| * `ENV.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 `ENV.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 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} |
| */ |
| var K = function() { |
| return this; |
| }; |
| var K = K; |
| __exports__.K = K; |
| Ember.K = K; |
| //TODO: ES6 GLOBL TODO |
| |
| // Stub out the methods defined by the ember-debug package in case it's not loaded |
| |
| if ('undefined' === typeof Ember.assert) { |
| Ember.assert = Ember.K; |
| } |
| if ('undefined' === typeof Ember.warn) { |
| Ember.warn = Ember.K; |
| } |
| if ('undefined' === typeof Ember.debug) { |
| Ember.debug = Ember.K; |
| } |
| if ('undefined' === typeof Ember.runInDebug) { |
| Ember.runInDebug = Ember.K; |
| } |
| if ('undefined' === typeof Ember.deprecate) { |
| Ember.deprecate = Ember.K; |
| } |
| if ('undefined' === typeof Ember.deprecateFunc) { |
| Ember.deprecateFunc = function(_, func) { |
| return func; |
| }; |
| } |
| |
| __exports__["default"] = Ember; |
| }); |
| define("ember-metal/dependent_keys", |
| ["ember-metal/platform", "ember-metal/watching", "exports"], |
| function(__dependency1__, __dependency2__, __exports__) { |
| "use strict"; |
| var create = __dependency1__.create; |
| var watch = __dependency2__.watch; |
| var unwatch = __dependency2__.unwatch; |
| |
| /** |
| @module ember-metal |
| */ |
| |
| var o_create = create; |
| |
| // .......................................................... |
| // 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; |
| }); |
| define("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.call(obj, 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.call(obj, 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.call(obj, 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.call(obj, 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), chunk, ret = [], |
| // https://code.google.com/p/chromium/issues/detail?id=56588 |
| size = 60000, start = idx, ends = amt, count; |
| |
| 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 |
| }; |
| }); |
| define("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; |
| }); |
| define("ember-metal/events", |
| ["ember-metal/core", "ember-metal/utils", "ember-metal/platform", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __exports__) { |
| "use strict"; |
| /** |
| @module ember-metal |
| */ |
| var Ember = __dependency1__["default"]; |
| var meta = __dependency2__.meta; |
| var META_KEY = __dependency2__.META_KEY; |
| var tryFinally = __dependency2__.tryFinally; |
| var apply = __dependency2__.apply; |
| var applyStr = __dependency2__.applyStr; |
| var create = __dependency3__.create; |
| |
| var a_slice = [].slice, |
| metaFor = meta, |
| /* listener flags */ |
| ONCE = 1, 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), |
| actions; |
| |
| if (!meta.listeners) { |
| meta.listeners = {}; |
| } |
| |
| if (!meta.hasOwnProperty('listeners')) { |
| // setup inherited copy of the listeners object |
| meta.listeners = create(meta.listeners); |
| } |
| |
| actions = meta.listeners[eventName]; |
| |
| // if there are actions, but the eventName doesn't exist in our listeners, then copy them from the prototype |
| if (actions && !meta.listeners.hasOwnProperty(eventName)) { |
| actions = meta.listeners[eventName] = meta.listeners[eventName].slice(); |
| } else if (!actions) { |
| actions = meta.listeners[eventName] = []; |
| } |
| |
| return actions; |
| } |
| |
| function listenersUnion(obj, eventName, otherActions) { |
| var meta = obj[META_KEY], |
| actions = meta && meta.listeners && meta.listeners[eventName]; |
| |
| if (!actions) { |
| return; |
| } |
| for (var i = actions.length - 3; i >= 0; i -= 3) { |
| var target = actions[i], |
| method = actions[i + 1], |
| flags = actions[i + 2], |
| actionIndex = indexOf(otherActions, target, method); |
| |
| if (actionIndex === -1) { |
| otherActions.push(target, method, flags); |
| } |
| } |
| } |
| |
| __exports__.listenersUnion = listenersUnion; |
| function listenersDiff(obj, eventName, otherActions) { |
| var meta = obj[META_KEY], |
| actions = meta && meta.listeners && meta.listeners[eventName], |
| diffActions = []; |
| |
| if (!actions) { |
| return; |
| } |
| for (var i = actions.length - 3; i >= 0; i -= 3) { |
| var target = actions[i], |
| method = actions[i + 1], |
| flags = actions[i + 2], |
| actionIndex = indexOf(otherActions, target, method); |
| |
| if (actionIndex !== -1) { |
| continue; |
| } |
| |
| otherActions.push(target, method, flags); |
| diffActions.push(target, method, flags); |
| } |
| |
| return diffActions; |
| } |
| |
| __exports__.listenersDiff = listenersDiff; /** |
| Add an event listener |
| |
| @method addListener |
| @for Ember |
| @param obj |
| @param {String} eventName |
| @param {Object|Function} targetOrMethod 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), |
| actionIndex = indexOf(actions, target, method), |
| 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} targetOrMethod 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), |
| 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[META_KEY], |
| 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} targetOrMethod 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), |
| 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} eventName Array of event names |
| @param {Object|Function} targetOrMethod 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 = [], |
| actionsList = [], |
| 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[META_KEY].listeners, ret = []; |
| |
| if (listeners) { |
| for (var eventName in listeners) { |
| if (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[META_KEY]; |
| 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[META_KEY], |
| 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[META_KEY], |
| 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], |
| 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], |
| events = a_slice.call(arguments, 0, -1); |
| func.__ember_listens__ = events; |
| return func; |
| } |
| |
| __exports__.on = on; |
| __exports__.removeListener = removeListener; |
| }); |
| define("ember-metal/expand_properties", |
| ["ember-metal/error", "ember-metal/enumerable_utils", "exports"], |
| function(__dependency1__, __dependency2__, __exports__) { |
| "use strict"; |
| var EmberError = __dependency1__["default"]; |
| var forEach = __dependency2__.forEach; |
| |
| /** |
| @module ember-metal |
| */ |
| |
| var BRACE_EXPANSION = /^((?:[^\.]*\.)*)\{(.*)\}$/; |
| |
| /** |
| Expands `pattern`, invoking `callback` for each expansion. |
| |
| The only pattern supported is brace-expansion, anything else will be passed |
| once to `callback` directly. Brace expansion can only appear at the end of a |
| pattern, for an example see the last call below. |
| |
| 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' |
| ``` |
| |
| @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) { |
| var match, prefix, list; |
| |
| if (pattern.indexOf(' ') > -1) { |
| throw new EmberError('Brace expanded properties cannot contain spaces, ' + |
| 'e.g. `user.{firstName, lastName}` should be `user.{firstName,lastName}`'); |
| } |
| |
| if (match = BRACE_EXPANSION.exec(pattern)) { |
| prefix = match[1]; |
| list = match[2]; |
| |
| forEach(list.split(','), function (suffix) { |
| callback(prefix + suffix); |
| }); |
| } else { |
| callback(pattern); |
| } |
| } |
| }); |
| define("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 |
| @param obj |
| @param {String...|Array} list of keys to get |
| @return {Hash} |
| */ |
| __exports__["default"] = function getProperties(obj) { |
| var ret = {}, |
| propertyNames = arguments, |
| 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; |
| } |
| }); |
| define("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 = [], cache = {}; |
| |
| var populateListeners = function(name) { |
| var listeners = [], 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) { |
| var listeners = cache[name], timeName, ret; |
| |
| // ES6TODO: Docs. What is this? |
| if (Ember.STRUCTURED_PROFILE) { |
| timeName = name + ": " + payload.object; |
| console.time(timeName); |
| } |
| |
| if (!listeners) { |
| listeners = populateListeners(name); |
| } |
| |
| if (listeners.length === 0) { |
| ret = callback.call(binding); |
| if (Ember.STRUCTURED_PROFILE) { |
| console.timeEnd(timeName); |
| } |
| return ret; |
| } |
| |
| var beforeValues = [], listener, i, l; |
| |
| function tryable() { |
| for (i = 0, l = listeners.length; i < l; i++) { |
| listener = listeners[i]; |
| beforeValues[i] = listener.before(name, time(), payload); |
| } |
| |
| return callback.call(binding); |
| } |
| |
| function catchable(e) { |
| payload = payload || {}; |
| payload.exception = e; |
| } |
| |
| function finalizer() { |
| for (i = 0, l = listeners.length; i < l; i++) { |
| listener = listeners[i]; |
| listener.after(name, time(), payload, beforeValues[i]); |
| } |
| |
| if (Ember.STRUCTURED_PROFILE) { |
| console.timeEnd(timeName); |
| } |
| } |
| |
| return tryCatchFinally(tryable, catchable, finalizer); |
| } |
| |
| __exports__.instrument = instrument; /** |
| 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 = []; |
| cache = {}; |
| } |
| |
| __exports__.reset = reset; |
| }); |
| define("ember-metal/is_blank", |
| ["ember-metal/core", "ember-metal/is_empty", "exports"], |
| function(__dependency1__, __dependency2__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| // deprecateFunc |
| var isEmpty = __dependency2__["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); |
| } |
| }); |
| define("ember-metal/is_empty", |
| ["ember-metal/core", "ember-metal/property_get", "ember-metal/is_none", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| // deprecateFunc |
| var get = __dependency2__.get; |
| var isNone = __dependency3__["default"]; |
| |
| /** |
| Verifies that a value is `null` or an empty string, empty array, |
| or empty function. |
| |
| Constrains the rules on `Ember.isNone` by returning false 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('Adam Hawkins'); // false |
| Ember.isEmpty([0,1,2]); // false |
| ``` |
| |
| @method isEmpty |
| @for Ember |
| @param {Object} obj Value to test |
| @return {Boolean} |
| */ |
| function isEmpty(obj) { |
| return isNone(obj) || (obj.length === 0 && typeof obj !== 'function') || (typeof obj === 'object' && get(obj, 'length') === 0); |
| } |
| |
| var empty = Ember.deprecateFunc("Ember.empty is deprecated. Please use Ember.isEmpty instead.", isEmpty); |
| __exports__.empty = empty; |
| __exports__["default"] = isEmpty; |
| __exports__.isEmpty = isEmpty; |
| __exports__.empty = empty; |
| }); |
| define("ember-metal/is_none", |
| ["ember-metal/core", "exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| // deprecateFunc |
| |
| /** |
| 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; |
| } |
| |
| var none = Ember.deprecateFunc("Ember.none is deprecated. Please use Ember.isNone instead.", isNone); |
| __exports__.none = none; |
| __exports__["default"] = isNone; |
| __exports__.isNone = isNone; |
| }); |
| define("ember-metal/libraries", |
| ["ember-metal/enumerable_utils", "exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| // Provides a way to register library versions with ember. |
| var forEach = __dependency1__.forEach; |
| var indexOf = __dependency1__.indexOf; |
| |
| var libraries = function() { |
| var _libraries = []; |
| var coreLibIndex = 0; |
| |
| var getLibrary = function(name) { |
| for (var i = 0; i < _libraries.length; i++) { |
| if (_libraries[i].name === name) { |
| return _libraries[i]; |
| } |
| } |
| }; |
| |
| _libraries.register = function(name, version) { |
| if (!getLibrary(name)) { |
| _libraries.push({ |
| name: name, |
| version: version |
| }); |
| } |
| }; |
| |
| _libraries.registerCoreLibrary = function(name, version) { |
| if (!getLibrary(name)) { |
| _libraries.splice(coreLibIndex++, 0, { |
| name: name, |
| version: version |
| }); |
| } |
| }; |
| |
| _libraries.deRegister = function(name) { |
| var lib = getLibrary(name); |
| if (lib) |
| _libraries.splice(indexOf(_libraries, lib), 1); |
| }; |
| |
| _libraries.each = function (callback) { |
| forEach(_libraries, function(lib) { |
| callback(lib.name, lib.version); |
| }); |
| }; |
| |
| return _libraries; |
| }(); |
| |
| __exports__["default"] = libraries; |
| }); |
| define("ember-metal/logger", |
| ["ember-metal/core", "ember-metal/error", "exports"], |
| function(__dependency1__, __dependency2__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| var EmberError = __dependency2__["default"]; |
| |
| 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 apply, but Chrome needs it |
| 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') || Ember.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') || Ember.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') || Ember.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') || Ember.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') || Ember.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 |
| }; |
| }); |
| define("ember-metal/map", |
| ["ember-metal/property_set", "ember-metal/utils", "ember-metal/array", "ember-metal/platform", "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 two exceptions. First, because we need our implementation to be pleasant |
| on older browsers, we do not use the `delete` name (using `remove` instead). |
| Second, 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 set = __dependency1__.set; |
| var guidFor = __dependency2__.guidFor; |
| var indexOf = __dependency3__.indexOf; |
| var create = __dependency4__.create; |
| |
| function copy(obj) { |
| var output = {}; |
| |
| for (var prop in obj) { |
| if (obj.hasOwnProperty(prop)) { |
| output[prop] = obj[prop]; |
| } |
| } |
| |
| return output; |
| } |
| |
| function copyMap(original, newObject) { |
| var keys = original.keys.copy(), |
| values = copy(original.values); |
| |
| newObject.keys = keys; |
| newObject.values = values; |
| newObject.length = original.length; |
| |
| 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() { |
| this.clear(); |
| } |
| |
| /** |
| @method create |
| @static |
| @return {Ember.OrderedSet} |
| */ |
| OrderedSet.create = function() { |
| return new OrderedSet(); |
| }; |
| |
| |
| OrderedSet.prototype = { |
| /** |
| @method clear |
| */ |
| clear: function() { |
| this.presenceSet = {}; |
| this.list = []; |
| }, |
| |
| /** |
| @method add |
| @param obj |
| */ |
| add: function(obj) { |
| var guid = guidFor(obj), |
| presenceSet = this.presenceSet, |
| list = this.list; |
| |
| if (guid in presenceSet) { |
| return; |
| } |
| |
| presenceSet[guid] = true; |
| list.push(obj); |
| }, |
| |
| /** |
| @method remove |
| @param obj |
| */ |
| remove: function(obj) { |
| var guid = guidFor(obj), |
| presenceSet = this.presenceSet, |
| list = this.list; |
| |
| delete presenceSet[guid]; |
| |
| var index = indexOf.call(list, obj); |
| if (index > -1) { |
| list.splice(index, 1); |
| } |
| }, |
| |
| /** |
| @method isEmpty |
| @return {Boolean} |
| */ |
| isEmpty: function() { |
| return this.list.length === 0; |
| }, |
| |
| /** |
| @method has |
| @param obj |
| @return {Boolean} |
| */ |
| has: function(obj) { |
| var guid = guidFor(obj), |
| presenceSet = this.presenceSet; |
| |
| return guid in presenceSet; |
| }, |
| |
| /** |
| @method forEach |
| @param {Function} fn |
| @param self |
| */ |
| forEach: function(fn, self) { |
| // allow mutation during iteration |
| var list = this.toArray(); |
| |
| for (var i = 0, j = list.length; i < j; i++) { |
| fn.call(self, list[i]); |
| } |
| }, |
| |
| /** |
| @method toArray |
| @return {Array} |
| */ |
| toArray: function() { |
| return this.list.slice(); |
| }, |
| |
| /** |
| @method copy |
| @return {Ember.OrderedSet} |
| */ |
| copy: function() { |
| var set = new OrderedSet(); |
| |
| set.presenceSet = copy(this.presenceSet); |
| set.list = this.toArray(); |
| |
| return set; |
| } |
| }; |
| |
| /** |
| 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() { |
| this.keys = OrderedSet.create(); |
| this.values = {}; |
| } |
| |
| Ember.Map = Map; |
| |
| /** |
| @method create |
| @static |
| */ |
| Map.create = function() { |
| return new Map(); |
| }; |
| |
| Map.prototype = { |
| /** |
| This property will change as the number of objects in the map changes. |
| |
| @property length |
| @type number |
| @default 0 |
| */ |
| length: 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) { |
| var values = this.values, |
| 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 |
| */ |
| set: function(key, value) { |
| var keys = this.keys, |
| values = this.values, |
| guid = guidFor(key); |
| |
| keys.add(key); |
| values[guid] = value; |
| set(this, 'length', keys.list.length); |
| }, |
| |
| /** |
| 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) { |
| // don't use ES6 "delete" because it will be annoying |
| // to use in browsers that are not ES6 friendly; |
| var keys = this.keys, |
| values = this.values, |
| guid = guidFor(key); |
| |
| if (values.hasOwnProperty(guid)) { |
| keys.remove(key); |
| delete values[guid]; |
| set(this, 'length', keys.list.length); |
| 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) { |
| var values = this.values, |
| guid = guidFor(key); |
| |
| return values.hasOwnProperty(guid); |
| }, |
| |
| /** |
| Iterate over all the keys and values. Calls the function once |
| for each key, passing in the key and value, 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, self) { |
| var keys = this.keys, |
| values = this.values; |
| |
| keys.forEach(function(key) { |
| var guid = guidFor(key); |
| callback.call(self, key, values[guid]); |
| }); |
| }, |
| |
| /** |
| @method copy |
| @return {Ember.Map} |
| */ |
| copy: function() { |
| return copyMap(this, new Map()); |
| } |
| }; |
| |
| /** |
| @class MapWithDefault |
| @namespace Ember |
| @extends Ember.Map |
| @private |
| @constructor |
| @param [options] |
| @param {*} [options.defaultValue] |
| */ |
| function MapWithDefault(options) { |
| Map.call(this); |
| 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); |
| |
| /** |
| 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 Map.prototype.get.call(this, key); |
| } else { |
| var defaultValue = this.defaultValue(key); |
| this.set(key, defaultValue); |
| return defaultValue; |
| } |
| }; |
| |
| /** |
| @method copy |
| @return {Ember.MapWithDefault} |
| */ |
| MapWithDefault.prototype.copy = function() { |
| return copyMap(this, new MapWithDefault({ |
| defaultValue: this.defaultValue |
| })); |
| }; |
| |
| __exports__.OrderedSet = OrderedSet; |
| __exports__.Map = Map; |
| __exports__.MapWithDefault = MapWithDefault; |
| }); |
| define("ember-metal/merge", |
| ["exports"], |
| function(__exports__) { |
| "use strict"; |
| /** |
| 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) { |
| for (var prop in updates) { |
| if (!updates.hasOwnProperty(prop)) { |
| continue; |
| } |
| original[prop] = updates[prop]; |
| } |
| return original; |
| } |
| }); |
| define("ember-metal/mixin", |
| ["ember-metal/core", "ember-metal/merge", "ember-metal/array", "ember-metal/platform", "ember-metal/utils", "ember-metal/expand_properties", "ember-metal/properties", "ember-metal/computed", "ember-metal/binding", "ember-metal/observer", "ember-metal/events", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __exports__) { |
| "use strict"; |
| /** |
| @module ember |
| @submodule ember-metal |
| */ |
| |
| var Ember = __dependency1__["default"]; |
| // warn, assert, wrap, et; |
| var merge = __dependency2__["default"]; |
| var map = __dependency3__.map; |
| var indexOf = __dependency3__.indexOf; |
| var forEach = __dependency3__.forEach; |
| var create = __dependency4__.create; |
| var guidFor = __dependency5__.guidFor; |
| var meta = __dependency5__.meta; |
| var META_KEY = __dependency5__.META_KEY; |
| var wrap = __dependency5__.wrap; |
| var makeArray = __dependency5__.makeArray; |
| var apply = __dependency5__.apply; |
| var expandProperties = __dependency6__["default"]; |
| var Descriptor = __dependency7__.Descriptor; |
| var defineProperty = __dependency7__.defineProperty; |
| var ComputedProperty = __dependency8__.ComputedProperty; |
| var Binding = __dependency9__.Binding; |
| var addObserver = __dependency10__.addObserver; |
| var removeObserver = __dependency10__.removeObserver; |
| var addBeforeObserver = __dependency10__.addBeforeObserver; |
| var removeBeforeObserver = __dependency10__.removeBeforeObserver; |
| var addListener = __dependency11__.addListener; |
| var removeListener = __dependency11__.removeListener; |
| |
| var REQUIRED, |
| a_map = map, |
| a_indexOf = indexOf, |
| a_forEach = forEach, |
| a_slice = [].slice, |
| o_create = create, |
| metaFor = meta; |
| |
| function superFunction() { |
| var ret, func = this.__nextSuper; |
| if (func) { |
| this.__nextSuper = null; |
| ret = apply(this, func, arguments); |
| this.__nextSuper = func; |
| } |
| return ret; |
| } |
| |
| function mixinsMeta(obj) { |
| var m = metaFor(obj, true), ret = m.mixins; |
| if (!ret) { |
| ret = m.mixins = {}; |
| } else if (!m.hasOwnProperty('mixins')) { |
| ret = m.mixins = o_create(ret); |
| } |
| return ret; |
| } |
| |
| function initMixin(mixin, args) { |
| if (args && args.length > 0) { |
| mixin.mixins = a_map.call(args, function(x) { |
| if (x instanceof Mixin) { |
| return x; |
| } |
| |
| // Note: Manually setup a primitive mixin here. This is the only |
| // way to actually get a primitive mixin. This way normal creation |
| // of mixins will give you combined mixins... |
| var mixin = new Mixin(); |
| mixin.properties = x; |
| return mixin; |
| }); |
| } |
| return mixin; |
| } |
| |
| 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 || !(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; |
| } |
| |
| 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 ('function' !== typeof superMethod) { |
| return method; |
| } |
| |
| return wrap(method, superMethod); |
| } |
| |
| function applyConcatenatedProperties(obj, key, value, values) { |
| var baseValue = values[key] || obj[key]; |
| |
| if (baseValue) { |
| if ('function' === typeof baseValue.concat) { |
| 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]; |
| |
| if (!baseValue) { |
| return value; |
| } |
| |
| var newBase = merge({}, baseValue), |
| 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 connectBindings(obj, m) { |
| // TODO Mixin.apply(instance) should disconnect binding if exists |
| var bindings = m.bindings, key, binding, to; |
| if (bindings) { |
| for (key in bindings) { |
| binding = bindings[key]; |
| if (binding) { |
| to = key.slice(0, -7); // strip Binding off end |
| 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, 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 = {}, values = {}, m = metaFor(obj), |
| key, value, desc, keys = []; |
| |
| 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() { |
| return initMixin(this, arguments); |
| } |
| Mixin.prototype = { |
| properties: null, |
| mixins: null, |
| ownerConstructor: null |
| }; |
| |
| 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; |
| return initMixin(new M(), arguments); |
| }; |
| |
| var MixinPrototype = Mixin.prototype; |
| |
| /** |
| @method reopen |
| @param arguments* |
| */ |
| MixinPrototype.reopen = function() { |
| var mixin, tmp; |
| |
| if (this.properties) { |
| mixin = Mixin.create(); |
| mixin.properties = this.properties; |
| delete this.properties; |
| this.mixins = [mixin]; |
| } else if (!this.mixins) { |
| this.mixins = []; |
| } |
| |
| var len = arguments.length, mixins = this.mixins, 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 { |
| tmp = Mixin.create(); |
| tmp.properties = mixin; |
| mixins.push(tmp); |
| } |
| } |
| |
| 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, 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[META_KEY], |
| 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 = {}, seen = {}, 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[META_KEY], |
| mixins = m && m.mixins, 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; |
| }); |
| define("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} targetOrMethod |
| @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} targetOrMethod |
| @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} targetOrMethod |
| @param {Function|String} [method] |
| */ |
| function removeBeforeObserver(obj, _path, target, method) { |
| unwatch(obj, _path); |
| removeListener(obj, beforeEvent(_path), target, method); |
| |
| return this; |
| } |
| |
| __exports__.removeBeforeObserver = removeBeforeObserver; |
| }); |
| define("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, |
| observers = this.observers, |
| senderGuid = guidFor(sender), |
| keySet = observerSet[senderGuid], |
| 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, 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 = []; |
| }; |
| }); |
| define("ember-metal/platform", |
| ["ember-metal/core", "exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| /*globals Node */ |
| |
| var Ember = __dependency1__["default"]; |
| |
| /** |
| @module ember-metal |
| */ |
| |
| /** |
| Platform specific methods and feature detectors needed by the framework. |
| |
| @class platform |
| @namespace Ember |
| @static |
| */ |
| // TODO remove this |
| var platform = {}; |
| |
| /** |
| Identical to `Object.create()`. Implements if not available natively. |
| |
| @method create |
| @for Ember |
| */ |
| var create = Object.create; |
| |
| // IE8 has Object.create but it couldn't treat property descriptors. |
| if (create) { |
| if (create({ |
| a: 1 |
| }, { |
| a: { |
| value: 2 |
| } |
| }).a !== 2) { |
| create = null; |
| } |
| } |
| |
| // STUB_OBJECT_CREATE allows us to override other libraries that stub |
| // Object.create different than we would prefer |
| if (!create || Ember.ENV.STUB_OBJECT_CREATE) { |
| var K = function() {}; |
| |
| create = function(obj, props) { |
| K.prototype = obj; |
| obj = new K(); |
| if (props) { |
| K.prototype = obj; |
| for (var prop in props) { |
| K.prototype[prop] = props[prop].value; |
| } |
| obj = new K(); |
| } |
| K.prototype = null; |
| |
| return obj; |
| }; |
| |
| create.isSimulated = true; |
| } |
| |
| var defineProperty = Object.defineProperty; |
| var canRedefineProperties, canDefinePropertyOnDOM; |
| |
| // Catch IE8 where Object.defineProperty exists but only works on DOM elements |
| if (defineProperty) { |
| try { |
| defineProperty({}, 'a', { |
| get: function() {} |
| }); |
| } catch (e) { |
| defineProperty = null; |
| } |
| } |
| |
| if (defineProperty) { |
| // Detects a bug in Android <3.2 where you cannot redefine a property using |
| // Object.defineProperty once accessors have already been set. |
| canRedefineProperties = (function() { |
| var obj = {}; |
| |
| defineProperty(obj, 'a', { |
| configurable: true, |
| enumerable: true, |
| get: function() {}, |
| set: function() {} |
| }); |
| |
| defineProperty(obj, 'a', { |
| configurable: true, |
| enumerable: true, |
| writable: true, |
| value: true |
| }); |
| |
| return obj.a === true; |
| })(); |
| |
| // This is for Safari 5.0, which supports Object.defineProperty, but not |
| // on DOM nodes. |
| canDefinePropertyOnDOM = (function() { |
| try { |
| defineProperty(document.createElement('div'), 'definePropertyOnDOM', {}); |
| return true; |
| } catch (e) {} |
| |
| return false; |
| })(); |
| |
| if (!canRedefineProperties) { |
| defineProperty = null; |
| } else 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); |
| } |
| }; |
| } |
| } |
| |
| /** |
| @class platform |
| @namespace Ember |
| */ |
| |
| /** |
| 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} |
| */ |
| platform.defineProperty = defineProperty; |
| |
| /** |
| Set to true if the platform supports native getters and setters. |
| |
| @property hasPropertyAccessors |
| @final |
| */ |
| platform.hasPropertyAccessors = true; |
| |
| if (!platform.defineProperty) { |
| platform.hasPropertyAccessors = false; |
| |
| platform.defineProperty = function(obj, keyName, desc) { |
| if (!desc.get) { |
| obj[keyName] = desc.value; |
| } |
| }; |
| |
| platform.defineProperty.isSimulated = true; |
| } |
| |
| if (Ember.ENV.MANDATORY_SETTER && !platform.hasPropertyAccessors) { |
| Ember.ENV.MANDATORY_SETTER = false; |
| } |
| |
| __exports__.create = create; |
| __exports__.platform = platform; |
| }); |
| define("ember-metal/properties", |
| ["ember-metal/core", "ember-metal/utils", "ember-metal/platform", "ember-metal/property_events", "ember-metal/property_get", "ember-metal/property_set", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) { |
| "use strict"; |
| /** |
| @module ember-metal |
| */ |
| |
| var Ember = __dependency1__["default"]; |
| var META_KEY = __dependency2__.META_KEY; |
| var meta = __dependency2__.meta; |
| var platform = __dependency3__.platform; |
| var overrideChains = __dependency4__.overrideChains; |
| var get = __dependency5__.get; |
| var set = __dependency6__.set; |
| |
| var metaFor = meta, |
| objectDefineProperty = platform.defineProperty; |
| |
| var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER; |
| |
| // .......................................................... |
| // 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 |
| // |
| |
| var MANDATORY_SETTER_FUNCTION = Ember.MANDATORY_SETTER_FUNCTION = function(value) { |
| Ember.assert("You must use Ember.set() to access this property (of " + this + ")", false); |
| }; |
| |
| var DEFAULT_GETTER_FUNCTION = Ember.DEFAULT_GETTER_FUNCTION = function DEFAULT_GETTER_FUNCTION(name) { |
| return function() { |
| var meta = this[META_KEY]; |
| return meta && meta.values[name]; |
| }; |
| }; |
| |
| /** |
| 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]; |
| watching = meta.watching[keyName] > 0; |
| |
| if (existingDesc instanceof Descriptor) { |
| existingDesc.teardown(obj, keyName); |
| } |
| |
| if (desc instanceof Descriptor) { |
| value = desc; |
| |
| descs[keyName] = desc; |
| if (MANDATORY_SETTER && watching) { |
| 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 (MANDATORY_SETTER && watching) { |
| meta.values[keyName] = data; |
| objectDefineProperty(obj, keyName, { |
| configurable: true, |
| enumerable: true, |
| set: MANDATORY_SETTER_FUNCTION, |
| 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; /** |
| 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 |
| */ |
| |
| function deprecateProperty(object, deprecatedKey, newKey) { |
| function deprecate() { |
| Ember.deprecate('Usage of `' + deprecatedKey + '` is deprecated, use `' + newKey + '` instead.'); |
| } |
| |
| if (platform.hasPropertyAccessors) { |
| defineProperty(object, deprecatedKey, { |
| configurable: true, |
| enumerable: false, |
| set: function(value) { |
| deprecate(); |
| set(object, newKey, value); |
| }, |
| get: function() { |
| deprecate(); |
| return get(object, newKey); |
| } |
| }); |
| } |
| } |
| |
| __exports__.deprecateProperty = deprecateProperty; |
| }); |
| define("ember-metal/property_events", |
| ["ember-metal/utils", "ember-metal/events", "ember-metal/observer_set", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __exports__) { |
| "use strict"; |
| var META_KEY = __dependency1__.META_KEY; |
| var guidFor = __dependency1__.guidFor; |
| var tryFinally = __dependency1__.tryFinally; |
| var sendEvent = __dependency2__.sendEvent; |
| var listenersUnion = __dependency2__.listenersUnion; |
| var listenersDiff = __dependency2__.listenersDiff; |
| 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[META_KEY], |
| watching = (m && m.watching[keyName] > 0) || keyName === 'length', |
| proto = m && m.proto, |
| 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[META_KEY], |
| watching = (m && m.watching[keyName] > 0) || keyName === 'length', |
| proto = m && m.proto, |
| 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; |
| } |
| |
| 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 seen = WILL_SEEN, top = !seen; |
| if (top) { |
| seen = WILL_SEEN = {}; |
| } |
| iterDeps(propertyWillChange, obj, 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 seen = DID_SEEN, top = !seen; |
| if (top) { |
| seen = DID_SEEN = {}; |
| } |
| iterDeps(propertyDidChange, obj, depKey, seen, meta); |
| if (top) { |
| DID_SEEN = null; |
| } |
| } |
| |
| function iterDeps(method, obj, depKey, seen, meta) { |
| var guid = guidFor(obj); |
| if (!seen[guid]) |
| seen[guid] = {}; |
| if (seen[guid][depKey]) |
| return; |
| seen[guid][depKey] = true; |
| |
| var deps = meta.deps; |
| deps = deps && deps[depKey]; |
| if (deps) { |
| for (var key in deps) { |
| var desc = meta.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], |
| events = [], |
| 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], |
| events = suppressEvents ? null : [], |
| 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(cb, binding) { |
| beginPropertyChanges(); |
| tryFinally(cb, endPropertyChanges, binding); |
| } |
| |
| function notifyBeforeObservers(obj, keyName) { |
| if (obj.isDestroying) { |
| return; |
| } |
| |
| var eventName = keyName + ':before', listeners, diff; |
| if (deferred) { |
| listeners = beforeObserverSet.add(obj, keyName, eventName); |
| diff = listenersDiff(obj, eventName, listeners); |
| sendEvent(obj, eventName, [obj, keyName], diff); |
| } else { |
| sendEvent(obj, eventName, [obj, keyName]); |
| } |
| } |
| |
| function notifyObservers(obj, keyName) { |
| if (obj.isDestroying) { |
| return; |
| } |
| |
| var eventName = keyName + ':change', listeners; |
| if (deferred) { |
| listeners = observerSet.add(obj, keyName, eventName); |
| listenersUnion(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; |
| }); |
| define("ember-metal/property_get", |
| ["ember-metal/core", "ember-metal/utils", "ember-metal/error", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __exports__) { |
| "use strict"; |
| /** |
| @module ember-metal |
| */ |
| |
| var Ember = __dependency1__["default"]; |
| var META_KEY = __dependency2__.META_KEY; |
| var EmberError = __dependency3__["default"]; |
| |
| var get; |
| |
| var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER; |
| |
| var IS_GLOBAL_PATH = /^([A-Z$]|([0-9][A-Z$])).*[\.]/; |
| var HAS_THIS = 'this.'; |
| 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) { |
| return _getPath(obj, keyName); |
| } |
| |
| var meta = obj[META_KEY], desc = meta && meta.descs[keyName], ret; |
| |
| if (desc === undefined && keyName.indexOf('.') !== -1) { |
| return _getPath(obj, keyName); |
| } |
| |
| if (desc) { |
| return desc.get(obj, keyName); |
| } else { |
| if (MANDATORY_SETTER && 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; |
| } |
| }; |
| |
| // Currently used only by Ember Data tests |
| if (Ember.config.overrideAccessors) { |
| Ember.get = get; |
| Ember.config.overrideAccessors(); |
| get = Ember.get; |
| } |
| |
| /** |
| 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 captial 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 = path.indexOf(HAS_THIS) === 0, |
| isGlobal = !hasThis && IS_GLOBAL_PATH.test(path), |
| key; |
| |
| if (!target || isGlobal) |
| target = Ember.lookup; |
| if (hasThis) |
| path = path.slice(5); |
| |
| 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 && path.indexOf('.') === -1) { |
| return get(Ember.lookup, path); |
| } |
| |
| // detect complicated paths and normalize them |
| hasThis = path.indexOf(HAS_THIS) === 0; |
| |
| 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; |
| }); |
| define("ember-metal/property_set", |
| ["ember-metal/core", "ember-metal/property_get", "ember-metal/utils", "ember-metal/property_events", "ember-metal/properties", "ember-metal/error", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| var getPath = __dependency2__._getPath; |
| var META_KEY = __dependency3__.META_KEY; |
| var propertyWillChange = __dependency4__.propertyWillChange; |
| var propertyDidChange = __dependency4__.propertyDidChange; |
| var defineProperty = __dependency5__.defineProperty; |
| var EmberError = __dependency6__["default"]; |
| |
| var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER; |
| 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[META_KEY], desc = meta && meta.descs[keyName], |
| isUnknown, currentValue; |
| |
| if (desc === undefined && keyName.indexOf('.') !== -1) { |
| 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 (MANDATORY_SETTER) { |
| currentValue = meta.values[keyName]; |
| } else { |
| currentValue = obj[keyName]; |
| } |
| // only trigger a change if the value has changed |
| if (value !== currentValue) { |
| propertyWillChange(obj, keyName); |
| if (MANDATORY_SETTER) { |
| if ((currentValue === undefined && !(keyName in obj)) || !obj.propertyIsEnumerable(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; |
| }; |
| |
| // Currently used only by Ember Data tests |
| // ES6TODO: Verify still true |
| if (Ember.config.overrideAccessors) { |
| Ember.set = set; |
| Ember.config.overrideAccessors(); |
| set = Ember.set; |
| } |
| |
| 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; |
| }); |
| define("ember-metal/run_loop", |
| ["ember-metal/core", "ember-metal/utils", "ember-metal/array", "ember-metal/property_events", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| var apply = __dependency2__.apply; |
| var indexOf = __dependency3__.indexOf; |
| var beginPropertyChanges = __dependency4__.beginPropertyChanges; |
| var endPropertyChanges = __dependency4__.endPropertyChanges; |
| |
| function onBegin(current) { |
| run.currentRunLoop = current; |
| } |
| |
| function onEnd(current, next) { |
| run.currentRunLoop = next; |
| } |
| |
| // ES6TODO: should Backburner become es6? |
| var Backburner = requireModule('backburner').Backburner; |
| var backburner = new Backburner(['sync', 'actions', 'destroy'], { |
| sync: { |
| before: beginPropertyChanges, |
| after: endPropertyChanges |
| }, |
| defaultQueue: 'actions', |
| onBegin: onBegin, |
| onEnd: onEnd, |
| onErrorTarget: Ember, |
| onErrorMethod: 'onerror' |
| }); |
| var slice = [].slice; |
| var concat = [].concat; |
| |
| // .......................................................... |
| // 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 execute 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 apply(backburner, backburner.run, 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(target, method /* args */ |
| ) { |
| if (!run.currentRunLoop) { |
| return apply(Ember, run, arguments); |
| } |
| |
| var args = slice.call(arguments); |
| args.unshift('actions'); |
| apply(run, run.schedule, args); |
| }; |
| |
| /** |
| Provides a useful utility for when integrating with non-Ember libraries |
| that provide asynchronous callbacks. |
| |
| Ember utilizes a run-loop to batch and coalesce changes. This works by |
| marking the start and end of Ember-related Javascript execution. |
| |
| When using events such as a View's click handler, Ember wraps the event |
| handler in a run-loop, but when integrating with non-Ember libraries this |
| can be tedious. |
| |
| For example, the following is rather verbose but is the correct way to combine |
| third-party events and Ember code. |
| |
| ```javascript |
| var that = this; |
| jQuery(window).on('resize', function(){ |
| run(function(){ |
| that.handleResize(); |
| }); |
| }); |
| ``` |
| |
| To reduce the boilerplate, the following can be used to construct a |
| run-loop-wrapped callback handler. |
| |
| ```javascript |
| jQuery(window).on('resize', run.bind(this, this.handleResize)); |
| ``` |
| |
| @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 {Object} return value from invoking the passed function. Please note, |
| when called within an existing loop, no return value is possible. |
| @since 1.4.0 |
| */ |
| run.bind = function(target, method /* args*/ |
| ) { |
| var args = slice.call(arguments); |
| return function() { |
| return apply(run, run.join, 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 execute 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 execute 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(); |
| apply(backburner, backburner.schedule, 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 if 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 {String} a string you can use to cancel the timer in |
| `run.cancel` later. |
| */ |
| run.later = function(target, method) { |
| return apply(backburner, backburner.later, 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 args = slice.call(arguments); |
| args.unshift('actions'); |
| 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 apply(backburner, backburner.scheduleOnce, 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 apply(backburner, backburner.debounce, 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 apply(backburner, backburner.throttle, 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 an 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); |
| } |
| }; |
| }); |
| define("ember-metal/set_properties", |
| ["ember-metal/property_events", "ember-metal/property_set", "exports"], |
| function(__dependency1__, __dependency2__, __exports__) { |
| "use strict"; |
| var changeProperties = __dependency1__.changeProperties; |
| var set = __dependency2__.set; |
| |
| /** |
| 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 self |
| @param {Object} hash |
| @return self |
| */ |
| __exports__["default"] = function setProperties(self, hash) { |
| changeProperties(function() { |
| for (var prop in hash) { |
| if (hash.hasOwnProperty(prop)) { |
| set(self, prop, hash[prop]); |
| } |
| } |
| }); |
| return self; |
| } |
| }); |
| define("ember-metal/utils", |
| ["ember-metal/core", "ember-metal/platform", "ember-metal/array", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| var platform = __dependency2__.platform; |
| var 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; |
| |
| 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'; |
| |
| var o_defineProperty = platform.defineProperty; |
| var o_create = create; |
| // Used for guid generation... |
| var numberCache = []; |
| var stringCache = {}; |
| var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER; |
| |
| /** |
| 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 = '__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 = 'ember' + 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 |
| }; |
| |
| /** |
| The key used to store meta information on object for property observing. |
| |
| @property META_KEY |
| @for Ember |
| @private |
| @final |
| @type String |
| */ |
| var META_KEY = '__ember_meta__'; |
| |
| var isDefinePropertySimulated = platform.defineProperty.isSimulated; |
| |
| function Meta(obj) { |
| this.descs = {}; |
| this.watching = {}; |
| this.cache = {}; |
| this.cacheMeta = {}; |
| this.source = obj; |
| } |
| |
| Meta.prototype = { |
| descs: null, |
| deps: null, |
| watching: null, |
| listeners: null, |
| cache: null, |
| cacheMeta: null, |
| source: null, |
| mixins: null, |
| bindings: null, |
| chains: null, |
| chainWatchers: null, |
| values: null, |
| proto: null |
| }; |
| |
| if (isDefinePropertySimulated) { |
| // 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 (MANDATORY_SETTER) { |
| 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[META_KEY]; |
| if (writable === false) |
| return ret || EMPTY_META; |
| |
| if (!ret) { |
| if (!isDefinePropertySimulated) |
| o_defineProperty(obj, META_KEY, META_DESC); |
| |
| ret = new Meta(obj); |
| |
| if (MANDATORY_SETTER) { |
| ret.values = {}; |
| } |
| |
| obj[META_KEY] = ret; |
| |
| // make sure we don't accidentally try to create constructor like desc |
| ret.descs.constructor = null; |
| |
| } else if (ret.source !== obj) { |
| if (!isDefinePropertySimulated) |
| o_defineProperty(obj, META_KEY, 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 (MANDATORY_SETTER) { |
| ret.values = o_create(ret.values); |
| } |
| |
| obj[META_KEY] = 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), 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, sup = this && this.__nextSuper; |
| if (this) { |
| this.__nextSuper = superFunc; |
| } |
| ret = apply(this, func, arguments); |
| 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, returns the object. Otherwise adds the object to |
| an array. If obj is `null` or `undefined`, returns 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: Ember.K, 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, 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() { ... }"; |
| } |
| 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__.GUID_PREFIX = GUID_PREFIX; |
| __exports__.META_DESC = META_DESC; |
| __exports__.EMPTY_META = EMPTY_META; |
| __exports__.META_KEY = META_KEY; |
| __exports__.meta = meta; |
| __exports__.typeOf = typeOf; |
| __exports__.tryCatchFinally = tryCatchFinally; |
| __exports__.isArray = isArray; |
| __exports__.canInvoke = canInvoke; |
| __exports__.tryFinally = tryFinally; |
| }); |
| define("ember-metal/watch_key", |
| ["ember-metal/core", "ember-metal/utils", "ember-metal/platform", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| var meta = __dependency2__.meta; |
| var typeOf = __dependency2__.typeOf; |
| var platform = __dependency3__.platform; |
| |
| var metaFor = meta; // utils.js |
| var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER; |
| var o_defineProperty = platform.defineProperty; |
| |
| 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 (MANDATORY_SETTER && keyName in obj) { |
| m.values[keyName] = obj[keyName]; |
| o_defineProperty(obj, keyName, { |
| configurable: true, |
| enumerable: obj.propertyIsEnumerable(keyName), |
| set: Ember.MANDATORY_SETTER_FUNCTION, |
| get: Ember.DEFAULT_GETTER_FUNCTION(keyName) |
| }); |
| } |
| } else { |
| watching[keyName] = (watching[keyName] || 0) + 1; |
| } |
| } |
| |
| __exports__.watchKey = watchKey; |
| function unwatchKey(obj, keyName, meta) { |
| var m = meta || metaFor(obj), 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 (MANDATORY_SETTER && keyName in obj) { |
| o_defineProperty(obj, keyName, { |
| configurable: true, |
| enumerable: obj.propertyIsEnumerable(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: Ember.DEFAULT_GETTER_FUNCTION(keyName) |
| }); |
| } |
| } else if (watching[keyName] > 1) { |
| watching[keyName]--; |
| } |
| } |
| |
| __exports__.unwatchKey = unwatchKey; |
| }); |
| define("ember-metal/watch_path", |
| ["ember-metal/utils", "ember-metal/chains", "exports"], |
| function(__dependency1__, __dependency2__, __exports__) { |
| "use strict"; |
| var meta = __dependency1__.meta; |
| var typeOf = __dependency1__.typeOf; |
| var ChainNode = __dependency2__.ChainNode; |
| |
| var metaFor = meta; |
| |
| // 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), 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), 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), 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; |
| }); |
| define("ember-metal/watching", |
| ["ember-metal/utils", "ember-metal/chains", "ember-metal/watch_key", "ember-metal/watch_path", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { |
| "use strict"; |
| /** |
| @module ember-metal |
| */ |
| |
| var meta = __dependency1__.meta; |
| var META_KEY = __dependency1__.META_KEY; |
| var GUID_KEY = __dependency1__.GUID_KEY; |
| var typeOf = __dependency1__.typeOf; |
| var generateGuid = __dependency1__.generateGuid; |
| 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 metaFor = meta; // utils.js |
| |
| // returns true if the passed path is just a keyName |
| function isKeyName(path) { |
| return path.indexOf('.') === -1; |
| } |
| |
| /** |
| 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 (isKeyName(_keyPath)) { |
| watchKey(obj, _keyPath, m); |
| } else { |
| watchPath(obj, _keyPath, m); |
| } |
| } |
| |
| __exports__.watch = watch; |
| |
| function isWatching(obj, key) { |
| var meta = obj[META_KEY]; |
| 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 (isKeyName(_keyPath)) { |
| unwatchKey(obj, _keyPath, m); |
| } else { |
| unwatchPath(obj, _keyPath, m); |
| } |
| } |
| |
| __exports__.unwatch = unwatch; /** |
| Call on an object when you first beget it from another object. This will |
| setup any chained watchers on the object instance as needed. This method is |
| safe to call multiple times. |
| |
| @private |
| @method rewatch |
| @for Ember |
| @param obj |
| */ |
| function rewatch(obj) { |
| var m = obj[META_KEY], chains = m && m.chains; |
| |
| // make sure the object has its own guid. |
| if (GUID_KEY in obj && !obj.hasOwnProperty(GUID_KEY)) { |
| generateGuid(obj); |
| } |
| |
| // make sure any chained watchers update. |
| if (chains && chains.value() !== obj) { |
| m.chains = chains.copy(obj); |
| } |
| } |
| |
| __exports__.rewatch = rewatch; |
| 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[META_KEY], node, nodes, key, nodeObject; |
| if (meta) { |
| obj[META_KEY] = 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; |
| }); |
| define("ember-routing-handlebars", |
| ["ember-metal/core", "ember-handlebars", "ember-routing/system/router", "ember-routing-handlebars/helpers/shared", "ember-routing-handlebars/helpers/link_to", "ember-routing-handlebars/helpers/outlet", "ember-routing-handlebars/helpers/render", "ember-routing-handlebars/helpers/action", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) { |
| "use strict"; |
| /** |
| Ember Routing Handlebars |
| |
| @module ember |
| @submodule ember-routing-handlebars |
| @requires ember-views |
| */ |
| |
| var Ember = __dependency1__["default"]; |
| var EmberHandlebars = __dependency2__["default"]; |
| var Router = __dependency3__["default"]; |
| |
| var resolvePaths = __dependency4__.resolvePaths; |
| var resolveParams = __dependency4__.resolveParams; |
| |
| var deprecatedLinkToHelper = __dependency5__.deprecatedLinkToHelper; |
| var linkToHelper = __dependency5__.linkToHelper; |
| var LinkView = __dependency5__.LinkView; |
| |
| var outletHelper = __dependency6__.outletHelper; |
| var OutletView = __dependency6__.OutletView; |
| |
| var renderHelper = __dependency7__["default"]; |
| |
| var ActionHelper = __dependency8__.ActionHelper; |
| var actionHelper = __dependency8__.actionHelper; |
| |
| Router.resolveParams = resolveParams; |
| Router.resolvePaths = resolvePaths; |
| |
| Ember.LinkView = LinkView; |
| EmberHandlebars.ActionHelper = ActionHelper; |
| EmberHandlebars.OutletView = OutletView; |
| |
| EmberHandlebars.registerHelper('render', renderHelper); |
| EmberHandlebars.registerHelper('action', actionHelper); |
| EmberHandlebars.registerHelper('outlet', outletHelper); |
| EmberHandlebars.registerHelper('link-to', linkToHelper); |
| EmberHandlebars.registerHelper('linkTo', deprecatedLinkToHelper); |
| |
| __exports__["default"] = Ember; |
| }); |
| define("ember-routing-handlebars/helpers/action", |
| ["ember-metal/core", "ember-metal/property_get", "ember-metal/array", "ember-metal/utils", "ember-metal/run_loop", "ember-views/system/utils", "ember-routing/system/router", "ember-handlebars", "ember-handlebars/ext", "ember-handlebars/helpers/view", "ember-routing-handlebars/helpers/shared", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| // Handlebars, uuid, FEATURES, assert, deprecate |
| var get = __dependency2__.get; |
| var forEach = __dependency3__.forEach; |
| var uuid = __dependency4__.uuid; |
| var run = __dependency5__["default"]; |
| |
| var isSimpleClick = __dependency6__.isSimpleClick; |
| var EmberRouter = __dependency7__["default"]; |
| |
| var EmberHandlebars = __dependency8__["default"]; |
| var handlebarsGet = __dependency9__.handlebarsGet; |
| var viewHelper = __dependency10__.viewHelper; |
| var resolveParams = __dependency11__.resolveParams; |
| var resolvePath = __dependency11__.resolvePath; |
| |
| /** |
| @module ember |
| @submodule ember-routing |
| */ |
| |
| var SafeString = EmberHandlebars.SafeString; |
| var a_slice = Array.prototype.slice; |
| |
| function args(options, actionName) { |
| var ret = []; |
| if (actionName) { |
| ret.push(actionName); |
| } |
| |
| var types = options.options.types.slice(1), |
| data = options.options.data; |
| |
| return ret.concat(resolveParams(options.context, options.params, { |
| types: types, |
| data: data |
| })); |
| } |
| |
| var ActionHelper = { |
| 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; |
| } |
| |
| var allowed = true; |
| |
| forEach.call(keys, function(key) { |
| if (event[key + "Key"] && allowedKeys.indexOf(key) === -1) { |
| allowed = false; |
| } |
| }); |
| |
| return allowed; |
| }; |
| |
| ActionHelper.registerAction = function(actionNameOrPath, options, allowedKeys) { |
| var actionId = uuid(); |
| |
| ActionHelper.registeredActions[actionId] = { |
| eventName: options.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, |
| parameters = options.parameters, |
| actionName; |
| |
| if (target.target) { |
| target = handlebarsGet(target.root, target.target, target.options); |
| } else { |
| target = target.root; |
| } |
| |
| if (options.boundProperty) { |
| actionName = resolveParams(parameters.context, [actionNameOrPath], { |
| types: ['ID'], |
| data: parameters.options.data |
| })[0]; |
| |
| if (typeof actionName === 'undefined' || typeof actionName === 'function') { |
| Ember.assert("You specified a quoteless path to the {{action}} helper '" + actionNameOrPath + "' which did not resolve to an actionName. Perhaps you meant to use a quoted actionName? (e.g. {{action '" + actionNameOrPath + "'}}).", true); |
| actionName = actionNameOrPath; |
| } |
| } |
| |
| if (!actionName) { |
| actionName = actionNameOrPath; |
| } |
| |
| run(function runRegisteredAction() { |
| if (target.send) { |
| target.send.apply(target, args(parameters, actionName)); |
| } else { |
| Ember.assert("The action '" + actionName + "' did not exist on " + target, typeof target[actionName] === 'function'); |
| target[actionName].apply(target, args(parameters)); |
| } |
| }); |
| } |
| }; |
| |
| options.view.on('willClearRender', function() { |
| delete ActionHelper.registeredActions[actionId]; |
| }); |
| |
| return actionId; |
| }; |
| |
| /** |
| The `{{action}}` helper registers an HTML element within a template for DOM |
| event handling and forwards that interaction to the templates's controller |
| or supplied `target` option (see 'Specifying a Target'). |
| |
| If the controller does not implement the event, the event is sent |
| to the current route, and it bubbles up the route hierarchy from there. |
| |
| User interaction with that element will invoke the supplied action name on |
| the appropriate target. Specifying a non-quoted action name will result in |
| a bound property lookup at the time the event will be triggered. |
| |
| 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. |
| |
| NOTE: Because `{{action}}` depends on Ember's event dispatch system it will |
| only function if an `Ember.EventDispatcher` instance is available. An |
| `Ember.EventDispatcher` instance will be created when a new `Ember.Application` |
| is created. Having an instance of `Ember.Application` will satisfy this |
| requirement. |
| |
| ### 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 views 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(actionName) { |
| var options = arguments[arguments.length - 1], |
| contexts = a_slice.call(arguments, 1, -1); |
| |
| var hash = options.hash, |
| controller = options.data.keywords.controller; |
| |
| // create a hash to pass along to registerAction |
| var action = { |
| eventName: hash.on || "click", |
| parameters: { |
| context: this, |
| options: options, |
| params: contexts |
| }, |
| view: options.data.view, |
| bubbles: hash.bubbles, |
| preventDefault: hash.preventDefault, |
| target: { |
| options: options |
| }, |
| boundProperty: options.types[0] === "ID" |
| }; |
| |
| if (hash.target) { |
| action.target.root = this; |
| action.target.target = hash.target; |
| } else if (controller) { |
| action.target.root = controller; |
| } |
| |
| var actionId = ActionHelper.registerAction(actionName, action, hash.allowedKeys); |
| return new SafeString('data-ember-action="' + actionId + '"'); |
| } |
| |
| __exports__.actionHelper = actionHelper; |
| }); |
| define("ember-routing-handlebars/helpers/link_to", |
| ["ember-metal/core", "ember-metal/property_get", "ember-metal/property_set", "ember-metal/merge", "ember-metal/run_loop", "ember-metal/computed", "ember-runtime/system/lazy_load", "ember-runtime/system/string", "ember-runtime/system/object", "ember-runtime/keys", "ember-views/system/utils", "ember-views/views/component", "ember-handlebars", "ember-handlebars/helpers/view", "ember-routing/system/router", "ember-routing-handlebars/helpers/shared", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __dependency16__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| // FEATURES, Logger, Handlebars, warn, assert |
| var get = __dependency2__.get; |
| var set = __dependency3__.set; |
| var merge = __dependency4__["default"]; |
| var run = __dependency5__["default"]; |
| var computed = __dependency6__.computed; |
| |
| var onLoad = __dependency7__.onLoad; |
| var fmt = __dependency8__.fmt; |
| var EmberObject = __dependency9__["default"]; |
| var keys = __dependency10__["default"]; |
| var isSimpleClick = __dependency11__.isSimpleClick; |
| var EmberComponent = __dependency12__["default"]; |
| var EmberHandlebars = __dependency13__["default"]; |
| var viewHelper = __dependency14__.viewHelper; |
| var EmberRouter = __dependency15__["default"]; |
| var resolveParams = __dependency16__.resolveParams; |
| var resolvePaths = __dependency16__.resolvePaths; |
| var routeArgs = __dependency16__.routeArgs; |
| |
| /** |
| @module ember |
| @submodule ember-routing |
| */ |
| |
| var slice = [].slice; |
| |
| requireModule('ember-handlebars'); |
| |
| 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; |
| }; |
| |
| var QueryParams = EmberObject.extend({ |
| values: null |
| }); |
| |
| function getResolvedPaths(options) { |
| |
| var types = options.options.types, |
| data = options.options.data; |
| |
| return resolvePaths(options.context, options.params, { |
| types: types, |
| data: data |
| }); |
| } |
| |
| /** |
| `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', |
| currentWhen: 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, |
| |
| /** |
| 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 discourage that you override these defaults, |
| however you can push onto the array if needed. |
| |
| @property attributeBindings |
| @type Array | String |
| @default ['href', 'title', 'rel'] |
| **/ |
| attributeBindings: ['href', 'title', 'rel'], |
| |
| /** |
| 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); |
| |
| // 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 helperParameters = this.parameters, |
| linkTextPath = helperParameters.options.linkTextPath, |
| paths = getResolvedPaths(helperParameters), |
| length = paths.length, |
| path, i, normalizedPath; |
| |
| if (linkTextPath) { |
| normalizedPath = getNormalizedPath(linkTextPath, helperParameters); |
| this.registerObserver(normalizedPath.root, normalizedPath.path, this, this.rerender); |
| } |
| |
| for (i = 0; i < length; i++) { |
| path = paths[i]; |
| if (null === path) { |
| // A literal value was provided, not a path, so nothing to observe. |
| continue; |
| } |
| |
| normalizedPath = getNormalizedPath(path, helperParameters); |
| this.registerObserver(normalizedPath.root, normalizedPath.path, this, this._paramsChanged); |
| } |
| |
| var queryParamsObject = this.queryParamsObject; |
| if (queryParamsObject) { |
| var values = queryParamsObject.values; |
| |
| // Install observers for all of the hash options |
| // provided in the (query-params) subexpression. |
| for (var k in values) { |
| if (!values.hasOwnProperty(k)) { |
| continue; |
| } |
| |
| if (queryParamsObject.types[k] === 'ID') { |
| normalizedPath = getNormalizedPath(values[k], helperParameters); |
| this.registerObserver(normalizedPath.root, normalizedPath.path, this, this._paramsChanged); |
| } |
| } |
| } |
| }, |
| |
| 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. |
| |
| @property active |
| **/ |
| active: computed('loadedParams', function computeLinkViewActive() { |
| if (get(this, 'loading')) { |
| return false; |
| } |
| |
| var router = get(this, 'router'), |
| loadedParams = get(this, 'loadedParams'), |
| contexts = loadedParams.models, |
| currentWhen = this.currentWhen || loadedParams.targetRouteName, |
| handlers = router.router.recognizer.handlersFor(currentWhen), |
| leafName = handlers[handlers.length-1].handler, |
| maximumContexts = numberOfContextsAcceptedByHandler(currentWhen, 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) { |
| currentWhen = leafName; |
| } |
| |
| var args = routeArgs(currentWhen, contexts, null); |
| var isActive = router.isActive.apply(router, args); |
| if (!isActive) { |
| return false; |
| } |
| |
| |
| if (!this.currentWhen && leafName === loadedParams.targetRouteName) { |
| var visibleQueryParams = {}; |
| merge(visibleQueryParams, loadedParams.queryParams); |
| router._prepareQueryParams(loadedParams.targetRouteName, loadedParams.models, visibleQueryParams); |
| isActive = shallowEqual(visibleQueryParams, router.router.state.queryParams); |
| } |
| |
| |
| if (isActive) { |
| 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() { |
| return get(this, '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) { |
| |
| 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 router = get(this, 'router'), |
| 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 parameters = this.parameters, |
| options = parameters.options, |
| types = options.types, |
| data = options.data, |
| targetRouteName, models; |
| |
| var onlyQueryParamsSupplied = (parameters.params.length === 0); |
| if (onlyQueryParamsSupplied) { |
| var appController = this.container.lookup('controller:application'); |
| targetRouteName = get(appController, 'currentRouteName'); |
| models = []; |
| } else { |
| models = resolveParams(parameters.context, parameters.params, { |
| types: types, |
| data: data |
| }); |
| targetRouteName = models.shift(); |
| } |
| |
| 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 resolvedParams = get(this, 'resolvedParams'), |
| router = get(this, 'router'), |
| 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'), |
| 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"; |
| }; |
| |
| |
| /** |
| 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}} |
| ``` |
| |
| ```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(name) { |
| var options = slice.call(arguments, -1)[0], |
| params = slice.call(arguments, 0, -1), |
| hash = options.hash; |
| |
| Ember.assert("You must provide one or more parameters to the link-to helper.", params.length); |
| |
| if (params[params.length - 1] instanceof QueryParams) { |
| hash.queryParamsObject = params.pop(); |
| } |
| |
| hash.disabledBinding = hash.disabledWhen; |
| |
| if (!options.fn) { |
| var linkTitle = params.shift(); |
| var linkType = options.types.shift(); |
| var context = this; |
| if (linkType === 'ID') { |
| options.linkTextPath = linkTitle; |
| options.fn = function() { |
| return EmberHandlebars.getEscaped(context, linkTitle, options); |
| }; |
| } else { |
| options.fn = function() { |
| return linkTitle; |
| }; |
| } |
| } |
| |
| hash.parameters = { |
| context: this, |
| options: options, |
| params: params |
| }; |
| |
| options.helperName = options.helperName || 'link-to'; |
| |
| return viewHelper.call(this, LinkView, options); |
| } |
| |
| |
| |
| EmberHandlebars.registerHelper('query-params', function queryParamsHelper(options) { |
| Ember.assert(fmt("The `query-params` helper only accepts hash parameters, e.g. (query-params queryParamPropertyName='%@') as opposed to just (query-params '%@')", [options, options]), arguments.length === 1); |
| |
| return QueryParams.create({ |
| values: options.hash, |
| types: options.hashTypes |
| }); |
| }); |
| |
| |
| /** |
| 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() { |
| Ember.warn("The 'linkTo' view helper is deprecated in favor of 'link-to'"); |
| return linkToHelper.apply(this, arguments); |
| } |
| |
| function getResolvedQueryParams(linkView, targetRouteName) { |
| var helperParameters = linkView.parameters, |
| queryParamsObject = linkView.queryParamsObject, |
| resolvedQueryParams = {}; |
| |
| if (!queryParamsObject) { |
| return resolvedQueryParams; |
| } |
| var rawParams = queryParamsObject.values; |
| |
| for (var key in rawParams) { |
| if (!rawParams.hasOwnProperty(key)) { |
| continue; |
| } |
| |
| var value = rawParams[key], |
| type = queryParamsObject.types[key]; |
| |
| if (type === 'ID') { |
| var normalizedPath = getNormalizedPath(value, helperParameters); |
| value = EmberHandlebars.get(normalizedPath.root, normalizedPath.path, helperParameters.options); |
| } |
| resolvedQueryParams[key] = value; |
| } |
| return resolvedQueryParams; |
| } |
| |
| function getNormalizedPath(path, helperParameters) { |
| return EmberHandlebars.normalizePath(helperParameters.context, path, helperParameters.options.data); |
| } |
| |
| 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; |
| __exports__.deprecatedLinkToHelper = deprecatedLinkToHelper; |
| __exports__.linkToHelper = linkToHelper; |
| }); |
| define("ember-routing-handlebars/helpers/outlet", |
| ["ember-metal/core", "ember-metal/property_get", "ember-metal/property_set", "ember-runtime/system/lazy_load", "ember-views/views/container_view", "ember-handlebars/views/metamorph_view", "ember-handlebars/helpers/view", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| // assert |
| var get = __dependency2__.get; |
| var set = __dependency3__.set; |
| var onLoad = __dependency4__.onLoad; |
| var ContainerView = __dependency5__["default"]; |
| var _Metamorph = __dependency6__._Metamorph; |
| var viewHelper = __dependency7__.viewHelper; |
| |
| /** |
| @module ember |
| @submodule ember-routing |
| */ |
| |
| /** |
| @module ember |
| @submodule ember-routing |
| */ |
| |
| var OutletView = ContainerView.extend(_Metamorph); |
| __exports__.OutletView = 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(property, options) { |
| var outletSource; |
| var container; |
| var viewName; |
| var viewClass; |
| var viewFullName; |
| |
| if (property && property.data && property.data.isRenderData) { |
| options = property; |
| property = 'main'; |
| } |
| |
| container = options.data.view.container; |
| |
| outletSource = options.data.view; |
| while (!outletSource.get('template.isTop')) { |
| outletSource = outletSource.get('_parentView'); |
| } |
| |
| // provide controller override |
| viewName = options.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 \"" + viewName + "\"}}.", options.hashTypes.view !== 'ID'); |
| Ember.assert("The view name you supplied '" + viewName + "' did not resolve to a view.", container.has(viewFullName)); |
| } |
| |
| viewClass = viewName ? container.lookupFactory(viewFullName) : options.hash.viewClass || OutletView; |
| |
| options.data.view.set('outletSource', outletSource); |
| options.hash.currentViewBinding = '_view.outletSource._outlets.' + property; |
| |
| options.helperName = options.helperName || 'outlet'; |
| |
| return viewHelper.call(this, viewClass, options); |
| } |
| |
| __exports__.outletHelper = outletHelper; |
| }); |
| define("ember-routing-handlebars/helpers/render", |
| ["ember-metal/core", "ember-metal/error", "ember-metal/property_get", "ember-metal/property_set", "ember-runtime/system/string", "ember-routing/system/generate_controller", "ember-handlebars/ext", "ember-handlebars/helpers/view", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| // assert, deprecate |
| var EmberError = __dependency2__["default"]; |
| var get = __dependency3__.get; |
| var set = __dependency4__.set; |
| var camelize = __dependency5__.camelize; |
| var generateControllerFactory = __dependency6__.generateControllerFactory; |
| var generateController = __dependency6__["default"]; |
| var handlebarsGet = __dependency7__.handlebarsGet; |
| var viewHelper = __dependency8__.viewHelper; |
| |
| |
| /** |
| @module ember |
| @submodule ember-routing |
| */ |
| |
| /** |
| 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}}. |
| ``` |
| |
| ```handelbars |
| <!-- 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?} contextString |
| @param {Hash} options |
| @return {String} HTML string |
| */ |
| __exports__["default"] = function renderHelper(name, contextString, options) { |
| var length = arguments.length; |
| |
| var contextProvided = length === 3, |
| container, router, controller, view, context, lookupOptions; |
| |
| container = (options || contextString).data.keywords.controller.container; |
| router = container.lookup('router:main'); |
| |
| if (length === 2) { |
| // use the singleton controller |
| options = contextString; |
| contextString = undefined; |
| 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 (length === 3) { |
| // create a new controller |
| context = handlebarsGet(options.contexts[1], contextString, options); |
| } else { |
| throw new EmberError("You must pass a templateName to render"); |
| } |
| |
| Ember.deprecate("Using a quoteless parameter with {{render}} is deprecated. Please update to quoted usage '{{render \"" + name + "\"}}.", options.types[0] !== 'ID'); |
| |
| // # 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 = options.hash.controller || name; |
| var controllerFullName = 'controller:' + controllerName; |
| |
| if (options.hash.controller) { |
| Ember.assert("The controller name you supplied '" + controllerName + "' did not resolve to a controller.", container.has(controllerFullName)); |
| } |
| |
| var parentController = options.data.keywords.controller; |
| |
| // choose name |
| if (length > 2) { |
| var factory = container.lookupFactory(controllerFullName) || |
| generateControllerFactory(container, controllerName, context); |
| |
| controller = factory.create({ |
| model: context, |
| parentController: parentController, |
| target: parentController |
| }); |
| |
| view.one('willDestroyElement', function() { |
| controller.destroy(); |
| }); |
| } else { |
| controller = container.lookup(controllerFullName) || |
| generateController(container, controllerName); |
| |
| controller.setProperties({ |
| target: parentController, |
| parentController: parentController |
| }); |
| } |
| |
| var root = options.contexts[1]; |
| |
| if (root) { |
| view.registerObserver(root, contextString, function() { |
| controller.set('model', handlebarsGet(root, contextString, options)); |
| }); |
| } |
| |
| options.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.fn); |
| options.hash.template = container.lookup(templateName); |
| |
| options.hash.controller = controller; |
| |
| if (router && !context) { |
| router._connectActiveView(name, view); |
| } |
| |
| options.helperName = options.helperName || ('render "' + name + '"'); |
| |
| viewHelper.call(this, view, options); |
| } |
| }); |
| define("ember-routing-handlebars/helpers/shared", |
| ["ember-metal/property_get", "ember-metal/array", "ember-runtime/mixins/controller", "ember-handlebars/ext", "ember-metal/utils", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) { |
| "use strict"; |
| var get = __dependency1__.get; |
| var map = __dependency2__.map; |
| var ControllerMixin = __dependency3__["default"]; |
| var handlebarsResolve = __dependency4__.resolveParams; |
| var handlebarsGet = __dependency4__.handlebarsGet; |
| var typeOf = __dependency5__.typeOf; |
| var get = __dependency1__.get; |
| |
| 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 resolveParams(context, params, options) { |
| return map.call(resolvePaths(context, params, options), function(path, i) { |
| if (null === path) { |
| // Param was string/number, not a path, so just return raw string/number. |
| return params[i]; |
| } else { |
| return handlebarsGet(context, path, options); |
| } |
| }); |
| } |
| |
| __exports__.resolveParams = resolveParams; |
| 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; |
| function resolvePaths(context, params, options) { |
| var resolved = handlebarsResolve(context, params, options), |
| types = options.types; |
| |
| return map.call(resolved, function(object, i) { |
| if (types[i] === 'ID') { |
| return unwrap(object, params[i]); |
| } else { |
| return null; |
| } |
| }); |
| |
| function unwrap(object, path) { |
| if (path === 'controller') { |
| return path; |
| } |
| |
| if (ControllerMixin.detect(object)) { |
| return unwrap(get(object, 'model'), path ? path + '.model' : 'model'); |
| } else { |
| return path; |
| } |
| } |
| } |
| |
| __exports__.resolvePaths = resolvePaths; |
| }); |
| define("ember-routing", |
| ["ember-handlebars", "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__, __dependency15__, __exports__) { |
| "use strict"; |
| /** |
| Ember Routing |
| |
| @module ember |
| @submodule ember-routing |
| @requires ember-views |
| */ |
| |
| var EmberHandlebars = __dependency1__["default"]; |
| var Ember = __dependency2__["default"]; |
| |
| // ES6TODO: Cleanup modules with side-effects below |
| |
| var EmberLocation = __dependency6__["default"]; |
| var NoneLocation = __dependency7__["default"]; |
| var HashLocation = __dependency8__["default"]; |
| var HistoryLocation = __dependency9__["default"]; |
| var AutoLocation = __dependency10__["default"]; |
| |
| var generateControllerFactory = __dependency11__.generateControllerFactory; |
| var generateController = __dependency11__["default"]; |
| var controllerFor = __dependency12__["default"]; |
| var RouterDSL = __dependency13__["default"]; |
| var Router = __dependency14__["default"]; |
| var Route = __dependency15__["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; |
| }); |
| define("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-metal/enumerable_utils", "ember-runtime/mixins/controller", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __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 map = __dependency7__.map; |
| |
| var ControllerMixin = __dependency8__["default"]; |
| |
| /** |
| @module ember |
| @submodule ember-routing |
| */ |
| |
| ControllerMixin.reopen({ |
| /** |
| 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'); |
| ``` |
| |
| 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. |
| @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; |
| |
| |
| ControllerMixin.reopen({ |
| init: function() { |
| this._super.apply(this, arguments); |
| listenForQueryParamChanges(this); |
| }, |
| |
| concatenatedProperties: ['queryParams', '_pCacheMeta'], |
| queryParams: null, |
| |
| _qpDelegate: null, |
| _normalizedQueryParams: computed(function() { |
| var m = meta(this); |
| if (m.proto !== this) { |
| return get(m.proto, '_normalizedQueryParams'); |
| } |
| |
| var queryParams = 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; |
| }), |
| |
| _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; |
| }), |
| |
| _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; |
| var value = cache.lookup(cacheKey, prop, propMeta.def); |
| |
| set(this, prop, value); |
| } |
| }, |
| |
| _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 |
| 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); |
| } |
| }, |
| |
| _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, '-'); |
| } |
| }); |
| |
| |
| function accumulateQueryParamDescriptors(_desc, accum) { |
| var desc = _desc, 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; |
| }); |
| define("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. |
| |
| var queues = run.queues; |
| run._addQueue('routerTransitions', 'actions'); |
| }); |
| define("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() { |
| set(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; |
| }); |
| define("ember-routing/location/api", |
| ["ember-metal/core", "ember-metal/property_get", "ember-metal/property_set", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| // deprecate, assert |
| var get = __dependency2__.get; |
| var set = __dependency3__.set; |
| |
| /** |
| @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); |
| } |
| } |
| }; |
| }); |
| define("ember-routing/location/auto_location", |
| ["ember-metal/core", "ember-metal/property_get", "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__, __dependency7__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| // FEATURES |
| var get = __dependency2__.get; |
| var set = __dependency3__.set; |
| |
| var EmberLocation = __dependency4__["default"]; |
| var HistoryLocation = __dependency5__["default"]; |
| var HashLocation = __dependency6__["default"]; |
| var NoneLocation = __dependency7__["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 { |
| 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; |
| } |
| }; |
| }); |
| define("ember-routing/location/hash_location", |
| ["ember-metal/property_get", "ember-metal/property_set", "ember-metal/run_loop", "ember-metal/utils", "ember-runtime/system/object", "ember-routing/location/api", "ember-views/system/jquery", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) { |
| "use strict"; |
| var get = __dependency1__.get; |
| var set = __dependency2__.set; |
| var run = __dependency3__["default"]; |
| var guidFor = __dependency4__.guidFor; |
| |
| var EmberObject = __dependency5__["default"]; |
| var EmberLocation = __dependency6__["default"]; |
| var jQuery = __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 current `location.hash`, minus the '#' at the front. |
| |
| @private |
| @method getURL |
| */ |
| getURL: function() { |
| return this.getHash().substr(1); |
| }, |
| |
| /** |
| 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); |
| |
| jQuery(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); |
| |
| jQuery(window).off('hashchange.ember-location-' + guid); |
| } |
| }); |
| }); |
| define("ember-routing/location/history_location", |
| ["ember-metal/core", "ember-metal/property_get", "ember-metal/property_set", "ember-metal/utils", "ember-runtime/system/object", "ember-views/system/jquery", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| // FEATURES |
| var get = __dependency2__.get; |
| var set = __dependency3__.set; |
| var guidFor = __dependency4__.guidFor; |
| |
| var EmberObject = __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; |
| |
| |
| 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); |
| } |
| }); |
| }); |
| define("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; |
| } |
| }); |
| }); |
| define("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 |
| }); |
| }); |
| define("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); |
| } |
| }); |
| define("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 (typeof options.path !== 'string') { |
| options.path = "/" + name; |
| } |
| |
| if (canNest(this) && options.resetNamespace !== true) { |
| name = this.parent + "." + name; |
| } |
| |
| if (callback) { |
| var dsl = new DSL(name); |
| route(dsl, 'loading'); |
| route(dsl, 'error', { |
| path: "/_unused_dummy_error_path_route_" + name + "/:error" |
| }); |
| |
| if (callback) { |
| callback.call(dsl); |
| } |
| |
| this.push(options.path, name, dsl.generate()); |
| } else { |
| this.push(options.path, name, null); |
| } |
| |
| }, |
| |
| 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) { |
| route(this, "index", { |
| path: "/" |
| }); |
| } |
| |
| return function(match) { |
| for (var i = 0, l = dslMatches.length; i < l; i++) { |
| var dslMatch = dslMatches[i]; |
| var matchObj = match(dslMatch[0]).to(dslMatch[1], dslMatch[2]); |
| } |
| }; |
| } |
| }; |
| |
| function canNest(dsl) { |
| return dsl.parent && dsl.parent !== 'application'; |
| } |
| |
| function route(dsl, name, options) { |
| Ember.assert("You must use `this.resource` to nest", typeof options !== 'function'); |
| |
| options = options || {}; |
| |
| if (typeof options.path !== 'string') { |
| options.path = "/" + name; |
| } |
| |
| if (canNest(dsl) && options.resetNamespace !== true) { |
| name = dsl.parent + "." + name; |
| } |
| |
| dsl.push(options.path, name, null); |
| } |
| |
| DSL.map = function(callback) { |
| var dsl = new DSL(); |
| callback.call(dsl); |
| return dsl; |
| }; |
| }); |
| define("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, instance, name, 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; |
| } |
| }); |
| define("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-runtime/keys", "ember-runtime/copy", "ember-runtime/system/string", "ember-runtime/system/object", "ember-runtime/mixins/action_handler", "ember-routing/system/generate_controller", "ember-routing-handlebars/helpers/shared", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __dependency16__, __dependency17__, __dependency18__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| // FEATURES, K, 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__.isNone; |
| 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 fmt = __dependency14__.fmt; |
| var EmberObject = __dependency15__["default"]; |
| var ActionHandler = __dependency16__["default"]; |
| var generateController = __dependency17__["default"]; |
| var stashParamNames = __dependency18__.stashParamNames; |
| |
| /** |
| @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, { |
| |
| /** |
| @private |
| |
| @method exit |
| */ |
| exit: function() { |
| this.deactivate(); |
| this.teardownViews(); |
| }, |
| |
| /** |
| @private |
| |
| @method reset |
| */ |
| reset: function(isExiting, transition) { |
| |
| var controller = this.controller; |
| controller._qpDelegate = get(this, '_qp.states.inactive'); |
| this.resetController(this.controller, isExiting, transition); |
| |
| }, |
| |
| /** |
| @private |
| |
| @method enter |
| */ |
| enter: function() { |
| this.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: |
| |
| ```js |
| 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. |
| |
| ```js |
| 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: |
| |
| ```js |
| 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. |
| |
| ```js |
| 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. |
| |
| ```js |
| 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: |
| |
| ```js |
| 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`: |
| |
| ```js |
| 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 totalChanged = keys(changed).concat(keys(removed)); |
| for (var i = 0, len = totalChanged.length; i < len; ++i) { |
| var urlKey = totalChanged[i], |
| options = get(this.queryParams, urlKey) || {}; |
| if (get(options, '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) { |
| // Controller overrode this value in 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 = qp.def; |
| if (isArray(value)) { |
| value = Ember.A(value.slice()); |
| } |
| } |
| } |
| |
| controller._qpDelegate = get(this, '_qp.states.inactive'); |
| |
| var thisQueryParamChanged = (svalue !== qp.svalue); |
| if (thisQueryParamChanged) { |
| var options = get(route, 'queryParams.' + qp.urlKey) || {}; |
| |
| if (transition.queryParamsOnly && replaceUrl !== false) { |
| 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, |
| |
| mergedProperties: ['events'], |
| |
| /** |
| 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: Ember.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: Ember.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'); |
| ``` |
| |
| 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); |
| } |
| 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"}, |
| breakfast = {breakfastId: "CerealAndMilk"}; |
| |
| this.transitionTo('cereal', breakfast, cereal); |
| } |
| } |
| }); |
| ``` |
| |
| @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. |
| @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() { |
| return this.router.send.apply(this.router, arguments); |
| }, |
| |
| /** |
| 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. |
| controller._qpDelegate = states.changingKeys; |
| controller._updateCacheParams(transition.params); |
| } |
| controller._qpDelegate = states.allowOverrides; |
| |
| 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. |
| |
| ```js |
| 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): |
| |
| ```js |
| 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 either |
| return Ember.RSVP.reject(e); |
| }); |
| } |
| } |
| }); |
| ``` |
| |
| @method beforeModel |
| @param {Transition} transition |
| @param {Object} queryParams the active query params for this route |
| @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: Ember.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. |
| |
| ```js |
| 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 |
| @param {Object} queryParams the active query params for this handler |
| @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: Ember.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 |
| */ |
| redirect: Ember.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. |
| |
| ```js |
| App.Router.map(function() { |
| this.resource('post', {path: '/posts/:post_id'}); |
| }); |
| ``` |
| |
| The model for the `post` route is `App.Post.find(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 only |
| executed when entered via the URL. If the route is entered |
| through a transition (e.g. when using the `link-to` Handlebars |
| helper), then a model context is already provided and this hook |
| is not called. Routes without dynamic segments will always |
| execute the model hook. |
| |
| 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 |
| |
| ```js |
| App.PostRoute = Ember.Route.extend({ |
| model: function(params) { |
| return App.Post.find(params.post_id); |
| } |
| }); |
| ``` |
| |
| @method model |
| @param {Object} params the parameters extracted from the URL |
| @param {Transition} transition |
| @param {Object} queryParams the query params for this route |
| @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; |
| |
| 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 !== transition.state.handlerInfos.length-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. |
| |
| ```js |
| 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 jQuery.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 (/_id$/.test(name) && params.length === 1) { |
| 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`: |
| |
| ```js |
| App.PhotosRoute = Ember.Route.extend({ |
| model: function() { |
| return App.Photo.find(); |
| }, |
| |
| 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: |
| |
| ```js |
| 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 |
| |
| ```js |
| 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`. |
| |
| ```js |
| 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 |
| |
| ```js |
| 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 |
| |
| ```js |
| 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. |
| |
| ```js |
| 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: |
| |
| ```js |
| 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`: |
| |
| ```js |
| // posts route |
| Ember.Route.extend({ |
| renderTemplate: function(){ |
| this.render('posts', { |
| 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. |
| |
| |
| ```js |
| // 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 and `templateName` properties. |
| |
| For example: |
| |
| ```js |
| // router |
| Router.map(function() { |
| this.route('index'); |
| this.resource('post', {path: '/posts/:post_id'}); |
| }); |
| ``` |
| |
| ```js |
| // 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`: |
| |
| ```js |
| // |
| 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; |
| |
| if (typeof name === 'object' && !options) { |
| options = name; |
| name = this.routeName; |
| } |
| |
| options = options || {}; |
| options.namePassed = namePassed; |
| |
| var templateName; |
| |
| if (name) { |
| name = name.replace(/\//g, '.'); |
| templateName = name; |
| } else { |
| name = this.routeName; |
| templateName = this.templateName || name; |
| } |
| |
| var viewName = options.view || namePassed && name || this.viewName || name; |
| |
| var container = this.container; |
| var view = container.lookup('view:' + viewName); |
| var template = view ? view.get('template') : null; |
| |
| if (!template) { |
| template = container.lookup('template:' + templateName); |
| } |
| |
| if (!view && !template) { |
| Ember.assert("Could not find \"" + name + "\" template or view.", Ember.isEmpty(arguments[0])); |
| if (get(this.router, 'namespace.LOG_VIEW_LOOKUPS')) { |
| Ember.Logger.info("Could not find \"" + name + "\" template or view. Nothing will be rendered", { |
| fullName: 'template:' + name |
| }); |
| } |
| return; |
| } |
| |
| options = normalizeOptions(this, name, template, options); |
| view = setupView(view, container, options); |
| |
| if (options.outlet === 'main') { |
| this.lastRenderedTemplate = name; |
| } |
| |
| appendView(this, view, options); |
| }, |
| |
| /** |
| 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: |
| |
| ```js |
| 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: |
| |
| ```js |
| 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; |
| } |
| }); |
| |
| var defaultQPMeta = { |
| qps: [], |
| map: {}, |
| states: {} |
| }; |
| |
| |
| Route.reopen({ |
| /** |
| 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`): |
| |
| ```js |
| 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: {}, |
| |
| _qp: computed(function() { |
| var controllerName = this.controllerName || this.routeName; |
| var fullName = this.container.normalize('controller:' + controllerName); |
| var controllerClass = this.container.lookupFactory(fullName); |
| |
| 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], |
| urlKey = desc.as || this.serializeQueryParamKey(propName), |
| defaultValue = get(controllerProto, propName); |
| |
| if (isArray(defaultValue)) { |
| defaultValue = Ember.A(defaultValue.slice()); |
| } |
| |
| var type = typeOf(defaultValue), |
| defaultValueSerialized = this.serializeQueryParam(defaultValue, urlKey, type), |
| fprop = controllerName + ':' + propName, |
| 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]); |
| } |
| } |
| }; |
| }), |
| |
| _names: null, |
| _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; |
| } |
| }, |
| |
| _updateSerializedQPValue: function(controller, qp) { |
| var value = get(controller, qp.prop); |
| qp.svalue = this.serializeQueryParam(value, qp.urlKey, qp.type); |
| }, |
| |
| _activeQPChanged: function(controller, qp) { |
| var value = get(controller, qp.prop); |
| this.router._queuedQPChanges[qp.fprop] = value; |
| run.once(this, this._fireQueryParamTransition); |
| }, |
| |
| //_inactiveQPChanged: function(controller, qp) { |
| //}, |
| |
| _updatingQPChanged: function(controller, qp) { |
| var router = this.router; |
| if (!router._qpUpdates) { |
| router._qpUpdates = {}; |
| } |
| router._qpUpdates[qp.urlKey] = true; |
| }, |
| |
| mergedProperties: ['queryParams'], |
| |
| 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]); |
| |
| if (!state.fullQueryParams) { |
| state.fullQueryParams = {}; |
| merge(state.fullQueryParams, state.queryParams); |
| |
| var targetRouteName = state.handlerInfos[state.handlerInfos.length-1].name; |
| this.router._deserializeQueryParams(targetRouteName, state.fullQueryParams); |
| } |
| |
| var qpMeta = get(route, '_qp'); |
| |
| if (!qpMeta) { |
| // No query params specified on the controller. |
| return params; |
| } |
| |
| // Copy over all the query params for this route/controller into params hash. |
| // TODO: is this correct? I think this won't do model dep state. |
| var qps = qpMeta.qps; |
| for (var i = 0, len = qps.length; i < len; ++i) { |
| // Put deserialized qp on params hash. |
| var qp = qps[i]; |
| if (!(qp.prop in params)) { |
| params[qp.prop] = state.fullQueryParams[qp.prop] || qp.def; |
| } |
| } |
| |
| return params; |
| }, |
| |
| serializeQueryParamKey: function(controllerPropertyName) { |
| return controllerPropertyName; |
| }, |
| |
| 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; |
| }, |
| |
| 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; |
| }, |
| |
| |
| _fireQueryParamTransition: function() { |
| this.transitionTo({ |
| queryParams: this.router._queuedQPChanges |
| }); |
| this.router._queuedQPChanges = {}; |
| }, |
| |
| /** |
| A hook you can use to reset controller values either when the model |
| changes or the route is exiting. |
| |
| ```js |
| 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 |
| */ |
| resetController: Ember.K |
| }); |
| |
| |
| 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, 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), template; |
| |
| if (!parent) { |
| return; |
| } |
| |
| if (template = parent.lastRenderedTemplate) { |
| return template; |
| } else { |
| return parentTemplate(parent); |
| } |
| } |
| |
| function normalizeOptions(route, name, template, options) { |
| options = options || {}; |
| options.into = options.into ? options.into.replace(/\//g, '.') : parentTemplate(route); |
| options.outlet = options.outlet || 'main'; |
| options.name = name; |
| options.template = template; |
| options.LOG_VIEW_LOOKUPS = get(route.router, 'namespace.LOG_VIEW_LOOKUPS'); |
| |
| Ember.assert("An outlet (" + options.outlet + ") was specified but was not found.", options.outlet === 'main' || options.into); |
| |
| var controller = options.controller, |
| model = options.model, |
| namedController; |
| |
| if (options.controller) { |
| controller = options.controller; |
| } else if (options.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 (model) { |
| controller.set('model', model); |
| } |
| |
| options.controller = controller; |
| |
| return options; |
| } |
| |
| function setupView(view, container, options) { |
| if (view) { |
| if (options.LOG_VIEW_LOOKUPS) { |
| Ember.Logger.info("Rendering " + options.name + " with " + view, { |
| fullName: 'view:' + options.name |
| }); |
| } |
| } else { |
| var defaultView = options.into ? 'view:default' : 'view:toplevel'; |
| view = container.lookup(defaultView); |
| if (options.LOG_VIEW_LOOKUPS) { |
| Ember.Logger.info("Rendering " + options.name + " with default view " + view, { |
| fullName: 'view:' + options.name |
| }); |
| } |
| } |
| |
| if (!get(view, 'templateName')) { |
| set(view, 'template', options.template); |
| |
| set(view, '_debugTemplateName', options.name); |
| } |
| |
| set(view, 'renderedName', options.name); |
| set(view, 'controller', options.controller); |
| |
| return view; |
| } |
| |
| 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); |
| }; |
| } |
| |
| __exports__["default"] = Route; |
| }); |
| define("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-metal/enumerable_utils", "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-handlebars/views/metamorph_view", "ember-routing-handlebars/helpers/shared", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __dependency16__, __dependency17__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| // FEATURES, Logger, K, 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 forEach = __dependency9__.forEach; |
| |
| var fmt = __dependency10__.fmt; |
| var EmberObject = __dependency11__["default"]; |
| var Evented = __dependency12__["default"]; |
| var EmberRouterDSL = __dependency13__["default"]; |
| var EmberView = __dependency14__["default"]; |
| var EmberLocation = __dependency15__["default"]; |
| var _MetamorphView = __dependency16__["default"]; |
| var routeArgs = __dependency17__.routeArgs; |
| var getActiveTargetName = __dependency17__.getActiveTargetName; |
| var stashParamNames = __dependency17__.stashParamNames; |
| |
| // requireModule("ember-handlebars"); |
| // requireModule("ember-runtime"); |
| // requireModule("ember-views"); |
| |
| /** |
| @module ember |
| @submodule ember-routing |
| */ |
| |
| // // side effect of loading some Ember globals, for now |
| // requireModule("ember-handlebars"); |
| // requireModule("ember-runtime"); |
| // requireModule("ember-views"); |
| |
| var Router = requireModule("router")['default']; |
| var Transition = requireModule("router/transition").Transition; |
| |
| 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 |
| */ |
| 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(Ember.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(Ember.K); |
| |
| var router = this.router; |
| var location = get(this, 'location'); |
| var container = this.container; |
| var self = this; |
| var initialURL = get(this, 'initialURL'); |
| |
| // 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(); |
| } |
| |
| this.handleURL(initialURL); |
| }, |
| |
| /** |
| 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) { |
| 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), 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 isActive |
| @param routeName |
| @param models |
| @param queryParams |
| @return {Boolean} |
| @private |
| */ |
| 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 (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 = {}, 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]; |
| if (qps.length > 1) { |
| var qp0 = qps[0].qp, qp1 = qps[1].qp; |
| 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-%@' }`", [qp0.fprop, qp1.fprop, qp0.urlKey, qp0.prop, qp0.prop]), false); |
| } |
| 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 = [], qpCache = this._qpCache[leafRouteName] = { |
| map: map, |
| qps: qps |
| }; |
| |
| var routerjs = this.router, |
| 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; |
| } |
| } else { |
| // Don't fire an assertion if we found an error substate. |
| 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, |
| 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, |
| map: function(callback) { |
| var router = this.router; |
| if (!router) { |
| router = new Router(); |
| |
| |
| router._triggerWillChangeContext = Ember.K; |
| router._triggerWillLeave = Ember.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; |
| } |
| |
| if (error.name === "UnrecognizedURLError") { |
| Ember.assert("The URL '" + error.message + "' did not match any routes in your application"); |
| } else if (error.name === 'TransitionAborted') { |
| // just ignore TransitionAborted here |
| } else { |
| logError(error); |
| } |
| |
| 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), |
| qps = qpCache.qps; |
| |
| for (var key in queryParams) { |
| if (!queryParams.hasOwnProperty(key)) { |
| continue; |
| } |
| var value = queryParams[key], |
| qp = qpCache.map[key]; |
| |
| if (qp) { |
| callback(key, value, qp); |
| } |
| } |
| } |
| |
| __exports__["default"] = EmberRouter; |
| }); |
| define("ember-runtime", |
| ["ember-metal", "ember-runtime/core", "ember-runtime/keys", "ember-runtime/compare", "ember-runtime/copy", "ember-runtime/system/namespace", "ember-runtime/system/object", "ember-runtime/system/tracked_array", "ember-runtime/system/subarray", "ember-runtime/system/container", "ember-runtime/system/application", "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/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/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__, __exports__) { |
| "use strict"; |
| /** |
| Ember Runtime |
| |
| @module ember |
| @submodule ember-runtime |
| @requires ember-metal |
| */ |
| |
| // BEGIN IMPORTS |
| var Ember = __dependency1__["default"]; |
| var isEqual = __dependency2__.isEqual; |
| var keys = __dependency3__["default"]; |
| var compare = __dependency4__["default"]; |
| var copy = __dependency5__["default"]; |
| |
| var Namespace = __dependency6__["default"]; |
| var EmberObject = __dependency7__["default"]; |
| var TrackedArray = __dependency8__["default"]; |
| var SubArray = __dependency9__["default"]; |
| var Container = __dependency10__["default"]; |
| var Application = __dependency11__["default"]; |
| var ArrayProxy = __dependency12__["default"]; |
| var ObjectProxy = __dependency13__["default"]; |
| var CoreObject = __dependency14__["default"]; |
| var EachArray = __dependency15__.EachArray; |
| var EachProxy = __dependency15__.EachProxy; |
| |
| var NativeArray = __dependency16__["default"]; |
| var Set = __dependency17__["default"]; |
| var EmberStringUtils = __dependency18__["default"]; |
| var Deferred = __dependency19__["default"]; |
| var onLoad = __dependency20__.onLoad; |
| var runLoadHooks = __dependency20__.runLoadHooks; |
| |
| var EmberArray = __dependency21__["default"]; |
| var Comparable = __dependency22__["default"]; |
| var Copyable = __dependency23__["default"]; |
| var Enumerable = __dependency24__["default"]; |
| var Freezable = __dependency25__.Freezable; |
| var FROZEN_ERROR = __dependency25__.FROZEN_ERROR; |
| |
| 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 RSVP = __dependency42__["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.keys = keys; |
| |
| 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.RSVP = RSVP; |
| // END EXPORTS |
| |
| __exports__["default"] = Ember; |
| }); |
| define("ember-runtime/compare", |
| ["ember-metal/core", "ember-metal/utils", "ember-runtime/mixins/comparable", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| // for Ember.ORDER_DEFINITION |
| var typeOf = __dependency2__.typeOf; |
| var Comparable = __dependency3__["default"]; |
| |
| // Used by Ember.compare |
| Ember.ORDER_DEFINITION = Ember.ENV.ORDER_DEFINITION || [ |
| 'undefined', |
| 'null', |
| 'boolean', |
| 'number', |
| 'string', |
| 'array', |
| 'object', |
| 'instance', |
| 'function', |
| 'class', |
| 'date' |
| ]; |
| |
| /** |
| 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.constructor)) { |
| return v.constructor.compare(v, w); |
| } |
| |
| if (type2 === 'instance' && Comparable.detect(w.constructor)) { |
| return 1 - w.constructor.compare(w, v); |
| } |
| } |
| |
| // If we haven't yet generated a reverse-mapping of Ember.ORDER_DEFINITION, |
| // do so now. |
| var mapping = Ember.ORDER_DEFINITION_MAPPING; |
| if (!mapping) { |
| var order = Ember.ORDER_DEFINITION; |
| mapping = Ember.ORDER_DEFINITION_MAPPING = {}; |
| var idx, len; |
| for (idx = 0, len = order.length; idx < len; ++idx) { |
| mapping[order[idx]] = idx; |
| } |
| |
| // We no longer need Ember.ORDER_DEFINITION. |
| delete Ember.ORDER_DEFINITION; |
| } |
| |
| var type1Index = mapping[type1]; |
| var type2Index = mapping[type2]; |
| |
| if (type1Index < type2Index) { |
| return -1; |
| } |
| if (type1Index > type2Index) { |
| return 1; |
| } |
| |
| // types are equal - so we have to check values now |
| switch (type1) { |
| case 'boolean': |
| case 'number': |
| if (v < w) { |
| return -1; |
| } |
| if (v > w) { |
| return 1; |
| } |
| return 0; |
| |
| case 'string': |
| var comp = v.localeCompare(w); |
| if (comp < 0) { |
| return -1; |
| } |
| if (comp > 0) { |
| return 1; |
| } |
| return 0; |
| |
| case 'array': |
| var vLen = v.length; |
| var wLen = w.length; |
| var l = Math.min(vLen, wLen); |
| var r = 0; |
| var i = 0; |
| while (r === 0 && i < l) { |
| r = compare(v[i], w[i]); |
| i++; |
| } |
| if (r !== 0) { |
| return r; |
| } |
| |
| // all elements are equal now |
| // shorter array should be ordered first |
| if (vLen < wLen) { |
| return -1; |
| } |
| if (vLen > wLen) { |
| return 1; |
| } |
| // arrays are equal now |
| return 0; |
| |
| case 'instance': |
| if (Comparable && Comparable.detect(v)) { |
| return v.compare(v, w); |
| } |
| return 0; |
| |
| case 'date': |
| var vNum = v.getTime(); |
| var wNum = w.getTime(); |
| if (vNum < wNum) { |
| return -1; |
| } |
| if (vNum > wNum) { |
| return 1; |
| } |
| return 0; |
| |
| default: |
| return 0; |
| } |
| } |
| }); |
| define("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 reduceComputed = __dependency2__.reduceComputed; |
| 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; |
| }); |
| define("ember-runtime/computed/reduce_computed", |
| ["ember-metal/core", "ember-metal/property_get", "ember-metal/property_set", "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", "ember-runtime/system/set", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| // Ember.assert |
| var e_get = __dependency2__.get; |
| var set = __dependency3__.set; |
| var guidFor = __dependency4__.guidFor; |
| var metaFor = __dependency4__.meta; |
| var EmberError = __dependency5__["default"]; |
| var propertyWillChange = __dependency6__.propertyWillChange; |
| var propertyDidChange = __dependency6__.propertyDidChange; |
| var expandProperties = __dependency7__["default"]; |
| var addObserver = __dependency8__.addObserver; |
| var observersFor = __dependency8__.observersFor; |
| var removeObserver = __dependency8__.removeObserver; |
| var addBeforeObserver = __dependency8__.addBeforeObserver; |
| var removeBeforeObserver = __dependency8__.removeBeforeObserver; |
| var ComputedProperty = __dependency9__.ComputedProperty; |
| var cacheFor = __dependency9__.cacheFor; |
| var o_create = __dependency10__.create; |
| var forEach = __dependency11__.forEach; |
| var TrackedArray = __dependency12__["default"]; |
| var EmberArray = __dependency13__["default"]; |
| var run = __dependency14__["default"]; |
| var Set = __dependency15__["default"]; |
| var isArray = __dependency4__.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), |
| length = get(dependentArray, 'length'), |
| 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, |
| trackedArray = this.trackedArraysByGuid[dependentKey], |
| 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)); |
| } |
| }, |
| |
| 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 changeMeta, observerContext; |
| |
| forEach(dependentArray.slice(normalizedIndex, normalizedIndex + addedCount), function (item, sliceIndex) { |
| if (itemPropertyKeys) { |
| observerContext = |
| observerContexts[sliceIndex] = |
| this.createPropertyObserverContext(dependentArray, normalizedIndex + sliceIndex, this.trackedArraysByGuid[dependentKey]); |
| 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.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, 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 = {}; |
| } |
| }; |
| |
| 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); |
| } |
| |
| function reset(cp, propertyName) { |
| var callbacks = cp._callbacks(), |
| meta; |
| |
| if (cp._hasInstanceMeta(this, propertyName)) { |
| meta = cp._instanceMeta(this, propertyName); |
| meta.setValue(cp.resetValue(meta.getValue())); |
| } else { |
| meta = cp._instanceMeta(this, propertyName); |
| } |
| |
| 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; |
| // 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.cacheable(); |
| |
| 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 dependentKeys = cp._dependentKeys, |
| meta = cp._instanceMeta(this, propertyName), |
| 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), |
| 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 |
| }; |
| } |
| 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, |
| 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, |
| args = a_slice.call(arguments), |
| propertyArgs = new Set(), |
| match, |
| dependentArrayKey, |
| itemPropertyKey; |
| |
| 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], |
| addItemPropertyKey = function (itemPropertyKey) { |
| cp.itemPropertyKey(dependentArrayKey, itemPropertyKey); |
| }; |
| |
| expandProperties(itemPropertyKeyPattern, addItemPropertyKey); |
| propertyArgs.add(dependentArrayKey); |
| } else { |
| propertyArgs.add(dependentKey); |
| } |
| }); |
| |
| return ComputedProperty.prototype.property.apply(this, propertyArgs.toArray()); |
| |
| }; |
| |
| /** |
| 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'), |
| 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; |
| }); |
| define("ember-runtime/computed/reduce_computed_macros", |
| ["ember-metal/core", "ember-metal/merge", "ember-metal/property_get", "ember-metal/property_set", "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/object_proxy", "ember-runtime/system/subarray", "ember-runtime/keys", "ember-runtime/compare", "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.assert |
| var merge = __dependency2__["default"]; |
| var get = __dependency3__.get; |
| var set = __dependency4__.set; |
| var isArray = __dependency5__.isArray; |
| var guidFor = __dependency5__.guidFor; |
| var EmberError = __dependency6__["default"]; |
| var forEach = __dependency7__.forEach; |
| var run = __dependency8__["default"]; |
| var addObserver = __dependency9__.addObserver; |
| var arrayComputed = __dependency10__.arrayComputed; |
| var reduceComputed = __dependency11__.reduceComputed; |
| var ObjectProxy = __dependency12__["default"]; |
| var SubArray = __dependency13__["default"]; |
| var keys = __dependency14__["default"]; |
| var compare = __dependency15__["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. |
| |
| ```javascript |
| function(item); |
| ``` |
| |
| Example |
| |
| ```javascript |
| var Hamster = Ember.Object.extend({ |
| excitingChores: Ember.computed.map('chores', function(chore) { |
| 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); |
| 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. |
| |
| ```javascript |
| function(item); |
| ``` |
| |
| ```javascript |
| var Hamster = Ember.Object.extend({ |
| remainingChores: Ember.computed.filter('chores', function(chore) { |
| 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), |
| 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; |
| } else { |
| ++instanceMeta.itemCounts[guid]; |
| } |
| array.addObject(item); |
| return array; |
| }, |
| removedItem: function(array, item, _, instanceMeta) { |
| var guid = guidFor(item), |
| 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 getDependentKeyGuids = function (changeMeta) { |
| return map(changeMeta.property._dependentKeys, function (dependentKey) { |
| return guidFor(dependentKey); |
| }); |
| }; |
| |
| 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), |
| dependentGuids = getDependentKeyGuids(changeMeta), |
| dependentGuid = guidFor(changeMeta.arrayChanged), |
| numberOfDependentArrays = changeMeta.property._dependentKeys.length, |
| 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), |
| dependentGuids = getDependentKeyGuids(changeMeta), |
| dependentGuid = guidFor(changeMeta.arrayChanged), |
| numberOfDependentArrays = changeMeta.property._dependentKeys.length, |
| numberOfArraysItemAppearsIn, |
| 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), |
| 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), |
| 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; |
| |
| function _guidFor(item) { |
| if (SearchProxy.detectInstance(item)) { |
| return guidFor(get(item, 'content')); |
| } |
| return guidFor(item); |
| } |
| } |
| |
| |
| var SearchProxy = ObjectProxy.extend(); |
| |
| /** |
| 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); |
| |
| var initFn, sortPropertiesKey; |
| |
| if (typeof sortDefinition === 'function') { |
| initFn = function (array, changeMeta, instanceMeta) { |
| instanceMeta.order = sortDefinition; |
| instanceMeta.binarySearch = binarySearch; |
| }; |
| } else { |
| sortPropertiesKey = sortDefinition; |
| initFn = function (array, changeMeta, instanceMeta) { |
| function setupSortProperties() { |
| var sortPropertyDefinitions = get(this, sortPropertiesKey), |
| sortProperty, |
| sortProperties = instanceMeta.sortProperties = [], |
| sortPropertyAscending = instanceMeta.sortPropertyAscending = {}, |
| 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 isProxy = itemB instanceof SearchProxy, |
| sortProperty, result, asc; |
| |
| for (var i = 0; i < this.sortProperties.length; ++i) { |
| sortProperty = this.sortProperties[i]; |
| result = compare(get(itemA, sortProperty), isProxy ? itemB[sortProperty] : get(itemB, sortProperty)); |
| |
| if (result !== 0) { |
| asc = this.sortPropertyAscending[sortProperty]; |
| return asc ? result : (-1 * result); |
| } |
| } |
| |
| return 0; |
| }; |
| |
| instanceMeta.binarySearch = binarySearch; |
| }; |
| } |
| |
| return arrayComputed(itemsKey, { |
| initialize: initFn, |
| |
| 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 proxyProperties, index, searchItem; |
| |
| if (changeMeta.previousValues) { |
| proxyProperties = merge({ |
| content: item |
| }, changeMeta.previousValues); |
| |
| searchItem = SearchProxy.create(proxyProperties); |
| } else { |
| searchItem = item; |
| } |
| |
| index = instanceMeta.binarySearch(array, searchItem); |
| array.removeAt(index); |
| return array; |
| } |
| }); |
| } |
| |
| __exports__.sort = sort; |
| }); |
| define("ember-runtime/controllers/array_controller", |
| ["ember-metal/core", "ember-metal/property_get", "ember-metal/property_set", "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__, __dependency9__, __exports__) { |
| "use strict"; |
| /** |
| @module ember |
| @submodule ember-runtime |
| */ |
| |
| var Ember = __dependency1__["default"]; |
| var get = __dependency2__.get; |
| var set = __dependency3__.set; |
| var forEach = __dependency4__.forEach; |
| var replace = __dependency4__.replace; |
| var ArrayProxy = __dependency5__["default"]; |
| var SortableMixin = __dependency6__["default"]; |
| var ControllerMixin = __dependency7__["default"]; |
| var computed = __dependency8__.computed; |
| var EmberError = __dependency9__["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 MyApp.listController}} |
| {{firstName}} {{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, { |
| |
| /** |
| The controller used to wrap items, if any. |
| |
| @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 fullName, subController, parentController; |
| |
| var container = get(this, 'container'); |
| var subControllers = this._subControllers; |
| |
| if (subControllers.length > idx) { |
| subController = subControllers[idx]; |
| |
| if (subController) { |
| return subController; |
| } |
| } |
| |
| fullName = 'controller:' + controllerClass; |
| |
| if (!container.has(fullName)) { |
| throw new EmberError('Could not resolve itemController: "' + controllerClass + '"'); |
| } |
| |
| if (this._isVirtual) { |
| parentController = get(this, 'parentController'); |
| } else { |
| parentController = this; |
| } |
| |
| 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(); |
| } |
| }); |
| }); |
| define("ember-runtime/controllers/controller", |
| ["ember-runtime/system/object", "ember-runtime/mixins/controller", "exports"], |
| function(__dependency1__, __dependency2__, __exports__) { |
| "use strict"; |
| var EmberObject = __dependency1__["default"]; |
| var Mixin = __dependency2__["default"]; |
| |
| /** |
| @module ember |
| @submodule ember-runtime |
| */ |
| |
| /** |
| @class Controller |
| @namespace Ember |
| @extends Ember.Object |
| @uses Ember.ControllerMixin |
| */ |
| __exports__["default"] = EmberObject.extend(Mixin); |
| }); |
| define("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); |
| }); |
| define("ember-runtime/copy", |
| ["ember-metal/enumerable_utils", "ember-metal/utils", "ember-runtime/system/object", "ember-runtime/mixins/copyable", "ember-metal/platform", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) { |
| "use strict"; |
| var indexOf = __dependency1__.indexOf; |
| var typeOf = __dependency2__.typeOf; |
| var EmberObject = __dependency3__["default"]; |
| var Copyable = __dependency4__["default"]; |
| var create = __dependency5__.create; |
| |
| function _copy(obj, deep, seen, copies) { |
| var ret, loc, key; |
| |
| // primitive data types are immutable, just return them. |
| if ('object' !== typeof obj || 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) { |
| if (!obj.hasOwnProperty(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 `clone()` method, then this function |
| will simply call that method and return the result. |
| |
| @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); |
| } |
| }); |
| define("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 && 'function' === typeof a.isEqual) |
| return a.isEqual(b); |
| if (a instanceof Date && b instanceof Date) { |
| return a.getTime() === b.getTime(); |
| } |
| return a === b; |
| }; |
| __exports__.isEqual = isEqual; |
| }); |
| define("ember-runtime/ext/function", |
| ["ember-metal/core", "ember-metal/expand_properties", "ember-metal/computed"], |
| function(__dependency1__, __dependency2__, __dependency3__) { |
| "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 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'); |
| |
| // Call this flag to mark the function as a property |
| }.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 addWatchedProperty = function (obs) { |
| watched.push(obs); |
| }; |
| var watched = []; |
| |
| for (var i = 0; i < arguments.length; ++i) { |
| expandProperties(arguments[i], addWatchedProperty); |
| } |
| |
| this.__ember_observes__ = watched; |
| |
| return 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() { |
| 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.", arg.indexOf('.') === -1); |
| } |
| |
| // 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 addWatchedProperty = function (obs) { |
| watched.push(obs); |
| }; |
| var watched = []; |
| |
| for (var i = 0; i < arguments.length; ++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; |
| }; |
| } |
| }); |
| define("ember-runtime/ext/rsvp", |
| ["ember-metal/core", "ember-metal/logger", "exports"], |
| function(__dependency1__, __dependency2__, __exports__) { |
| "use strict"; |
| /* globals RSVP:true */ |
| |
| var Ember = __dependency1__["default"]; |
| var Logger = __dependency2__["default"]; |
| |
| var RSVP = requireModule("rsvp"); |
| var Test, testModuleName = 'ember-testing/test'; |
| |
| RSVP.onerrorDefault = function(error) { |
| if (error instanceof Error) { |
| 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); |
| } else { |
| throw error; |
| } |
| } else if (Ember.onerror) { |
| Ember.onerror(error); |
| } else { |
| Logger.error(error.stack); |
| Ember.assert(error, false); |
| } |
| } |
| }; |
| |
| RSVP.on('error', RSVP.onerrorDefault); |
| |
| __exports__["default"] = RSVP; |
| }); |
| define("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); |
| }; |
| } |
| }); |
| define("ember-runtime/keys", |
| ["ember-metal/enumerable_utils", "ember-metal/platform", "exports"], |
| function(__dependency1__, __dependency2__, __exports__) { |
| "use strict"; |
| var EnumerableUtils = __dependency1__["default"]; |
| var create = __dependency2__.create; |
| |
| /** |
| 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 || create.isSimulated) { |
| var prototypeProperties = [ |
| 'constructor', |
| 'hasOwnProperty', |
| 'isPrototypeOf', |
| 'propertyIsEnumerable', |
| 'valueOf', |
| 'toLocaleString', |
| 'toString' |
| ], |
| pushPropertyName = function(obj, array, key) { |
| // Prevents browsers that don't respect non-enumerability from |
| // copying internal Ember properties |
| if (key.substring(0, 2) === '__') |
| return; |
| if (key === '_super') |
| return; |
| if (EnumerableUtils.indexOf(array, key) >= 0) |
| return; |
| if (typeof obj.hasOwnProperty === 'function' && !obj.hasOwnProperty(key)) |
| return; |
| |
| array.push(key); |
| }; |
| |
| keys = function keys(obj) { |
| var ret = [], key; |
| for (key in obj) { |
| pushPropertyName(obj, ret, key); |
| } |
| |
| // IE8 doesn't enumerate property that named the same as prototype properties. |
| for (var i = 0, l = prototypeProperties.length; i < l; i++) { |
| key = prototypeProperties[i]; |
| |
| pushPropertyName(obj, ret, key); |
| } |
| |
| return ret; |
| }; |
| } |
| |
| __exports__["default"] = keys; |
| }); |
| define("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), 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; |
| } |
| } else if (!Ember.FEATURES.isEnabled('ember-routing-drop-deprecated-action-style') && this.deprecatedSend && this.deprecatedSendHandles && this.deprecatedSendHandles(actionName)) { |
| Ember.warn("The current default is deprecated but will prefer to handle actions directly on the controller instead of a similarly named action in the actions hash. To turn off this deprecated feature set: Ember.FEATURES['ember-routing-drop-deprecated-action-style'] = true"); |
| if (this.deprecatedSend.apply(this, [].slice.call(arguments)) === true) { |
| // handler return 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; |
| }); |
| define("ember-runtime/mixins/array", |
| ["ember-metal/core", "ember-metal/property_get", "ember-metal/property_set", "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__, __dependency11__, __exports__) { |
| "use strict"; |
| /** |
| @module ember |
| @submodule ember-runtime |
| */ |
| |
| // .......................................................... |
| // HELPERS |
| // |
| var Ember = __dependency1__["default"]; |
| // ES6TODO: Ember.A |
| |
| var get = __dependency2__.get; |
| var set = __dependency3__.set; |
| var computed = __dependency4__.computed; |
| var cacheFor = __dependency4__.cacheFor; |
| var isNone = __dependency5__.isNone; |
| var none = __dependency5__.none; |
| var Enumerable = __dependency6__["default"]; |
| var map = __dependency7__.map; |
| var Mixin = __dependency8__.Mixin; |
| var required = __dependency8__.required; |
| var propertyWillChange = __dependency9__.propertyWillChange; |
| var propertyDidChange = __dependency9__.propertyDidChange; |
| var addListener = __dependency10__.addListener; |
| var removeListener = __dependency10__.removeListener; |
| var sendEvent = __dependency10__.sendEvent; |
| var hasListeners = __dependency10__.hasListeners; |
| var isWatching = __dependency11__.isWatching; |
| |
| // .......................................................... |
| // 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 idx, len = get(this, 'length'); |
| |
| 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 idx, len = get(this, 'length'); |
| |
| 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) { |
| var willChange = (opts && opts.willChange) || 'arrayWillChange', |
| didChange = (opts && opts.didChange) || 'arrayDidChange'; |
| |
| var hasObservers = get(this, 'hasArrayObservers'); |
| if (!hasObservers) |
| propertyWillChange(this, 'hasArrayObservers'); |
| addListener(this, '@array:before', target, willChange); |
| addListener(this, '@array:change', target, didChange); |
| if (!hasObservers) |
| propertyDidChange(this, 'hasArrayObservers'); |
| return this; |
| }, |
| |
| /** |
| 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) { |
| var willChange = (opts && opts.willChange) || 'arrayWillChange', |
| didChange = (opts && opts.didChange) || 'arrayDidChange'; |
| |
| var hasObservers = get(this, 'hasArrayObservers'); |
| if (hasObservers) |
| propertyWillChange(this, 'hasArrayObservers'); |
| removeListener(this, '@array:before', target, willChange); |
| removeListener(this, '@array:change', target, didChange); |
| if (hasObservers) |
| propertyDidChange(this, 'hasArrayObservers'); |
| return this; |
| }, |
| |
| /** |
| 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) { |
| |
| // 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]); |
| |
| var removing, lim; |
| 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) { |
| |
| // 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; |
| } |
| |
| var adding, lim; |
| 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'), |
| cachedFirst = cacheFor(this, 'firstObject'), |
| 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; |
| }) |
| }); |
| }); |
| define("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) |
| }); |
| }); |
| define("ember-runtime/mixins/controller", |
| ["ember-metal/core", "ember-metal/property_get", "ember-runtime/system/object", "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__, __dependency5__, __dependency6__, __dependency7__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| // Ember.assert, Ember.deprecate |
| var get = __dependency2__.get; |
| var EmberObject = __dependency3__["default"]; |
| var Mixin = __dependency4__.Mixin; |
| var computed = __dependency5__.computed; |
| var ActionHandler = __dependency6__["default"]; |
| var ControllerContentModelAliasDeprecation = __dependency7__["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, |
| |
| model: null, |
| content: computed.alias('model'), |
| |
| deprecatedSendHandles: function(actionName) { |
| return !!this[actionName]; |
| }, |
| |
| deprecatedSend: function(actionName) { |
| var args = [].slice.call(arguments, 1); |
| Ember.assert('' + this + " has the action " + actionName + " but it is not a function", typeof this[actionName] === 'function'); |
| Ember.deprecate('Action handlers implemented directly on controllers are deprecated in favor of action handlers on an `actions` object ( action: `' + actionName + '` on ' + this + ')', false); |
| this[actionName].apply(this, args); |
| return; |
| } |
| }); |
| }); |
| define("ember-runtime/mixins/controller_content_model_alias_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 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 |
| it's model and ember will do the right thing. |
| |
| |
| `Ember.ControllerContentModelAliasDeprecation` is used internally by Ember in |
| `Ember.Controller`. |
| |
| @class ControllerContentModelAliasDeprecation |
| @namespace Ember |
| */ |
| __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); |
| } |
| } |
| }); |
| }); |
| define("ember-runtime/mixins/copyable", |
| ["ember-metal/property_get", "ember-metal/property_set", "ember-metal/mixin", "ember-runtime/mixins/freezable", "ember-runtime/system/string", "ember-metal/error", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) { |
| "use strict"; |
| /** |
| @module ember |
| @submodule ember-runtime |
| */ |
| |
| |
| var get = __dependency1__.get; |
| var set = __dependency2__.set; |
| var required = __dependency3__.required; |
| var Freezable = __dependency4__.Freezable; |
| var Mixin = __dependency3__.Mixin; |
| var fmt = __dependency5__.fmt; |
| var EmberError = __dependency6__["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])); |
| } |
| } |
| }); |
| }); |
| define("ember-runtime/mixins/deferred", |
| ["ember-metal/core", "ember-metal/property_get", "ember-metal/mixin", "ember-metal/computed", "ember-metal/run_loop", "ember-runtime/ext/rsvp", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| // Ember.FEATURES, Ember.Test |
| var get = __dependency2__.get; |
| var Mixin = __dependency3__.Mixin; |
| var computed = __dependency4__.computed; |
| var run = __dependency5__["default"]; |
| var RSVP = __dependency6__["default"]; |
| |
| 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); |
| }; |
| |
| /** |
| @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); |
| |
| return RSVP.defer('Ember: DeferredMixin - ' + this); |
| }) |
| }); |
| }); |
| define("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(), ret; |
| 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(), idx = 0, cur, last = null; |
| 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) { |
| return this.find(function(item) { |
| return item === obj; |
| }) !== 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 len = get(this, 'length'), last = null, context = popCtx(); |
| |
| 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 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 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; |
| }, |
| hasValue = function(item) { |
| return !!get(item, key); |
| }, |
| 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 last = null, next, found = false, ret ; |
| var context = popCtx(); |
| 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'), |
| context = popCtx(), |
| found = false, |
| last = null, |
| 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} `true` if the passed function returns `true` for any item |
| @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} `true` if the passed function returns `true` for any item |
| @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} `true` if the passed function returns `true` for any item |
| @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 args, ret = Ember.A(); |
| 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"] |
| ``` |
| |
| @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', |
| 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', |
| 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], |
| propA = get(a, key), |
| 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; |
| }); |
| } |
| }); |
| }); |
| define("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); |
| } |
| }); |
| }); |
| define("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; |
| }); |
| define("ember-runtime/mixins/mutable_array", |
| ["ember-metal/property_get", "ember-metal/property_set", "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__, __dependency8__, __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 set = __dependency2__.set; |
| var isArray = __dependency3__.isArray; |
| var EmberError = __dependency4__["default"]; |
| var Mixin = __dependency5__.Mixin; |
| var required = __dependency5__.required; |
| var EmberArray = __dependency6__["default"]; |
| var MutableEnumerable = __dependency7__["default"]; |
| var Enumerable = __dependency8__["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 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 occurances 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; |
| } |
| |
| }); |
| }); |
| define("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; |
| } |
| }); |
| }); |
| define("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__.isNone; |
| |
| |
| 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 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) { |
| 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 it's |
| 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); |
| } |
| }); |
| }); |
| define("ember-runtime/mixins/promise_proxy", |
| ["ember-metal/property_get", "ember-metal/property_set", "ember-metal/computed", "ember-metal/mixin", "ember-metal/error", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) { |
| "use strict"; |
| var get = __dependency1__.get; |
| var set = __dependency2__.set; |
| 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) { |
| set(proxy, 'isFulfilled', false); |
| set(proxy, 'isRejected', false); |
| |
| return promise.then(function(value) { |
| set(proxy, 'isFulfilled', true); |
| set(proxy, 'content', value); |
| return value; |
| }, function(reason) { |
| set(proxy, 'isRejected', true); |
| set(proxy, 'reason', reason); |
| 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); |
| }; |
| } |
| }); |
| define("ember-runtime/mixins/sortable", |
| ["ember-metal/core", "ember-metal/property_get", "ember-metal/property_set", "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__, __dependency9__, __exports__) { |
| "use strict"; |
| /** |
| @module ember |
| @submodule ember-runtime |
| */ |
| |
| var Ember = __dependency1__["default"]; |
| // Ember.assert, Ember.A |
| |
| var get = __dependency2__.get; |
| var set = __dependency3__.set; |
| var forEach = __dependency4__.forEach; |
| var Mixin = __dependency5__.Mixin; |
| var MutableEnumerable = __dependency6__["default"]; |
| var compare = __dependency7__["default"]; |
| var addObserver = __dependency8__.addObserver; |
| var removeObserver = __dependency8__.removeObserver; |
| var computed = __dependency9__.computed; |
| var beforeObserver = __dependency5__.beforeObserver; |
| var observer = __dependency5__.observer; |
| //ES6TODO: should we access these directly from their package or from how thier 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 |
| modelarray: |
| |
| ```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, |
| sortProperties = get(this, 'sortProperties'), |
| sortAscending = get(this, 'sortAscending'), |
| 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'), |
| 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'), |
| isSorted = get(this, 'isSorted'), |
| sortProperties = get(this, 'sortProperties'), |
| 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'), |
| 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'), |
| 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'), |
| oldIndex = arrangedContent.indexOf(item), |
| leftItem = arrangedContent.objectAt(oldIndex - 1), |
| rightItem = arrangedContent.objectAt(oldIndex + 1), |
| leftResult = leftItem && this.orderBy(item, leftItem), |
| 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; |
| } |
| }); |
| }); |
| define("ember-runtime/mixins/target_action_support", |
| ["ember-metal/core", "ember-metal/property_get", "ember-metal/property_set", "ember-metal/utils", "ember-metal/mixin", "ember-metal/computed", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) { |
| "use strict"; |
| /** |
| @module ember |
| @submodule ember-runtime |
| */ |
| var Ember = __dependency1__["default"]; |
| // Ember.lookup, Ember.assert |
| |
| var get = __dependency2__.get; |
| var set = __dependency3__.set; |
| var typeOf = __dependency4__.typeOf; |
| var Mixin = __dependency5__.Mixin; |
| var computed = __dependency6__.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'), |
| target = opts.target || get(this, 'targetObject'), |
| 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; |
| }); |
| define("ember-runtime/system/application", |
| ["ember-runtime/system/namespace", "exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| var Namespace = __dependency1__["default"]; |
| |
| __exports__["default"] = Namespace.extend(); |
| }); |
| define("ember-runtime/system/array_proxy", |
| ["ember-metal/core", "ember-metal/property_get", "ember-metal/property_set", "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", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| // Ember.K, Ember.assert |
| var get = __dependency2__.get; |
| var set = __dependency3__.set; |
| var isArray = __dependency4__.isArray; |
| var apply = __dependency4__.apply; |
| var computed = __dependency5__.computed; |
| var beforeObserver = __dependency6__.beforeObserver; |
| var observer = __dependency6__.observer; |
| var beginPropertyChanges = __dependency7__.beginPropertyChanges; |
| var endPropertyChanges = __dependency7__.endPropertyChanges; |
| var EmberError = __dependency8__["default"]; |
| var EmberObject = __dependency9__["default"]; |
| var MutableArray = __dependency10__["default"]; |
| var Enumerable = __dependency11__["default"]; |
| var fmt = __dependency12__.fmt; |
| |
| /** |
| @module ember |
| @submodule ember-runtime |
| */ |
| |
| var OUT_OF_RANGE_EXCEPTION = "Index out of range"; |
| var EMPTY = []; |
| var alias = computed.alias; |
| var K = Ember.K; |
| |
| /** |
| 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' |
| }); |
| } |
| }, |
| |
| contentArrayWillChange: K, |
| 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'), |
| 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'), |
| 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'), |
| arrangedContent = get(this, 'arrangedContent'), |
| indices = [], 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; |
| }); |
| define("ember-runtime/system/container", |
| ["ember-metal/property_set", "exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| var set = __dependency1__["default"]; |
| |
| var Container = requireModule('container')["default"]; |
| Container.set = set; |
| |
| __exports__["default"] = Container; |
| }); |
| define("ember-runtime/system/core_object", |
| ["ember-metal/core", "ember-metal/property_get", "ember-metal/property_set", "ember-metal/utils", "ember-metal/platform", "ember-metal/watching", "ember-metal/chains", "ember-metal/events", "ember-metal/mixin", "ember-metal/enumerable_utils", "ember-metal/error", "ember-runtime/keys", "ember-runtime/mixins/action_handler", "ember-metal/properties", "ember-metal/binding", "ember-metal/computed", "ember-metal/run_loop", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __dependency16__, __dependency17__, __exports__) { |
| "use strict"; |
| /** |
| @module ember |
| @submodule ember-runtime |
| */ |
| |
| var Ember = __dependency1__["default"]; |
| // Ember.ENV.MANDATORY_SETTER, Ember.assert, Ember.K, 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 = __dependency2__.get; |
| var set = __dependency3__.set; |
| 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 META_KEY = __dependency4__.META_KEY; |
| var makeArray = __dependency4__.makeArray; |
| var rewatch = __dependency6__.rewatch; |
| var finishChains = __dependency7__.finishChains; |
| var sendEvent = __dependency8__.sendEvent; |
| var IS_BINDING = __dependency9__.IS_BINDING; |
| var Mixin = __dependency9__.Mixin; |
| var required = __dependency9__.required; |
| var indexOf = __dependency10__.indexOf; |
| var EmberError = __dependency11__["default"]; |
| var platform = __dependency5__.platform; |
| var keys = __dependency12__["default"]; |
| var ActionHandler = __dependency13__["default"]; |
| var defineProperty = __dependency14__.defineProperty; |
| var Binding = __dependency15__.Binding; |
| var ComputedProperty = __dependency16__.ComputedProperty; |
| var run = __dependency17__["default"]; |
| var destroy = __dependency6__.destroy; |
| |
| var K = __dependency1__.K; |
| var o_defineProperty = platform.defineProperty; |
| var schedule = run.schedule; |
| var applyMixin = Mixin._apply; |
| var finishPartial = Mixin.finishPartial; |
| var reopen = Mixin.prototype.reopen; |
| var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER; |
| 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, 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), 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; |
| |
| 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]; |
| if (!properties.hasOwnProperty(keyName)) { |
| continue; |
| } |
| |
| 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 && 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 (desc) { |
| desc.set(this, keyName, value); |
| } else { |
| if (typeof this.setUnknownProperty === 'function' && !(keyName in this)) { |
| this.setUnknownProperty(keyName, value); |
| } else if (MANDATORY_SETTER) { |
| defineProperty(this, keyName, null, value); // setup mandatory setter |
| } else { |
| this[keyName] = value; |
| } |
| } |
| } |
| } |
| } |
| finishPartial(this, m); |
| apply(this, this.init, arguments); |
| 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); |
| rewatch(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() { |
| applyMixin(this, arguments, 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 to Ember that 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', |
| 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; |
| }; |
| } |
| |
| if (Ember.config.overridePrototypeMixin) { |
| Ember.config.overridePrototypeMixin(CoreObject.PrototypeMixin); |
| } |
| |
| CoreObject.__super__ = null; |
| |
| var ClassMixin = Mixin.create({ |
| |
| 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() { |
| var Class = makeCtor(), 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; |
| if (arguments.length > 0) { |
| this._initMixins(arguments); |
| } |
| 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; |
| if (arguments.length > 0) { |
| this._initProperties(arguments); |
| } |
| 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(); |
| apply(this.PrototypeMixin, reopen, arguments); |
| 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() { |
| apply(this.ClassMixin, reopen, arguments); |
| 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()`. |
| |
| @method metaForProperty |
| @param key {String} property name |
| */ |
| metaForProperty: function(key) { |
| var meta = this.proto()[META_KEY], |
| 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: Ember.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. |
| |
| @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); |
| } |
| } |
| }); |
| |
| ClassMixin.ownerConstructor = CoreObject; |
| |
| if (Ember.config.overrideClassMixin) { |
| Ember.config.overrideClassMixin(ClassMixin); |
| } |
| |
| 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; |
| } |
| } |
| |
| this._super(); |
| } |
| }); |
| |
| |
| __exports__["default"] = CoreObject; |
| }); |
| define("ember-runtime/system/deferred", |
| ["ember-metal/core", "ember-runtime/mixins/deferred", "ember-metal/property_get", "ember-runtime/system/object", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| var DeferredMixin = __dependency2__["default"]; |
| var get = __dependency3__.get; |
| var EmberObject = __dependency4__["default"]; |
| |
| var Deferred = EmberObject.extend(DeferredMixin, { |
| init: function() { |
| Ember.deprecate('Usage of Ember.Deferred is deprecated.'); |
| this._super(); |
| } |
| }); |
| |
| Deferred.reopenClass({ |
| promise: function(callback, binding) { |
| var deferred = Deferred.create(); |
| callback.call(binding, deferred); |
| return deferred; |
| } |
| }); |
| |
| __exports__["default"] = Deferred; |
| }); |
| define("ember-runtime/system/each_proxy", |
| ["ember-metal/core", "ember-metal/property_get", "ember-metal/property_set", "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__, __dependency13__, __exports__) { |
| "use strict"; |
| /** |
| @module ember |
| @submodule ember-runtime |
| */ |
| |
| var Ember = __dependency1__["default"]; |
| // Ember.assert |
| |
| var get = __dependency2__.get; |
| var set = __dependency3__.set; |
| var guidFor = __dependency4__.guidFor; |
| var forEach = __dependency5__.forEach; |
| var indexOf = __dependency6__.indexOf; |
| var EmberArray = __dependency7__["default"]; |
| // ES6TODO: WAT? Circular dep? |
| var EmberObject = __dependency8__["default"]; |
| var computed = __dependency9__.computed; |
| var addObserver = __dependency10__.addObserver; |
| var addBeforeObserver = __dependency10__.addBeforeObserver; |
| var removeBeforeObserver = __dependency10__.removeBeforeObserver; |
| var removeObserver = __dependency10__.removeObserver; |
| var typeOf = __dependency4__.typeOf; |
| var watchedEvents = __dependency11__.watchedEvents; |
| var defineProperty = __dependency12__.defineProperty; |
| var beginPropertyChanges = __dependency13__.beginPropertyChanges; |
| var propertyDidChange = __dependency13__.propertyDidChange; |
| var propertyWillChange = __dependency13__.propertyWillChange; |
| var endPropertyChanges = __dependency13__.endPropertyChanges; |
| var changeProperties = __dependency13__.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, 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, 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, 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, |
| 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, |
| 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; |
| }); |
| define("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; |
| }); |
| define("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'); |
| if (name) { |
| return name; |
| } |
| |
| findNamespaces(); |
| return this[NAME_KEY]; |
| }, |
| |
| nameClasses: function() { |
| processNamespace([this.toString()], this, {}); |
| }, |
| |
| destroy: function() { |
| var namespaces = Namespace.NAMESPACES, |
| 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, obj, isNamespace; |
| |
| 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, |
| unprocessedMixins = Ember.anyUnprocessedMixins; |
| |
| if (unprocessedNamespaces) { |
| findNamespaces(); |
| Namespace.PROCESSED = true; |
| } |
| |
| if (unprocessedNamespaces || unprocessedMixins) { |
| var namespaces = Namespace.NAMESPACES, 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; |
| }); |
| define("ember-runtime/system/native_array", |
| ["ember-metal/core", "ember-metal/property_get", "ember-metal/property_set", "ember-metal/enumerable_utils", "ember-metal/mixin", "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 set = __dependency3__.set; |
| var replace = __dependency4__._replace; |
| var forEach = __dependency4__.forEach; |
| var Mixin = __dependency5__.Mixin; |
| 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 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 ; |
| }, |
| |
| // If browser did not implement indexOf natively, then override with |
| // specialized version |
| indexOf: function(object, startAt) { |
| var idx, len = this.length; |
| |
| if (startAt === undefined) |
| startAt = 0; |
| else |
| startAt = (startAt < 0) ? Math.ceil(startAt) : Math.floor(startAt); |
| if (startAt < 0) |
| startAt += len; |
| |
| for (idx = startAt; idx < len; idx++) { |
| if (this[idx] === object) |
| return idx ; |
| } |
| return -1; |
| }, |
| |
| lastIndexOf: function(object, startAt) { |
| var idx, len = this.length; |
| |
| if (startAt === undefined) |
| startAt = len-1; |
| else |
| startAt = (startAt < 0) ? Math.ceil(startAt) : Math.floor(startAt); |
| if (startAt < 0) |
| startAt += len; |
| |
| for (idx = startAt; idx >= 0; idx--) { |
| if (this[idx] === object) |
| return idx ; |
| } |
| return -1; |
| }, |
| |
| 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; |
| }); |
| define("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; |
| }); |
| define("ember-runtime/system/object_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", "ember-runtime/system/object", "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.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 observer = __dependency9__.observer; |
| var fmt = __dependency10__.fmt; |
| var EmberObject = __dependency11__["default"]; |
| |
| 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.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 |
| */ |
| var ObjectProxy = EmberObject.extend({ |
| /** |
| 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 ObjectProxy'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); |
| } |
| |
| }); |
| |
| __exports__["default"] = ObjectProxy; |
| }); |
| define("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 |
| |
| var get = __dependency2__.get; |
| var set = __dependency3__.set; |
| var guidFor = __dependency4__.guidFor; |
| var isNone = __dependency5__.isNone; |
| 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 |
| */ |
| __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) { |
| 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), |
| idx = this[guid], |
| len = get(this, 'length'), |
| 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), |
| idx = this[guid], |
| len = get(this, 'length'), |
| isFirst = idx === 0, |
| isLast = idx === len-1, |
| 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(',')]); |
| } |
| }); |
| }); |
| define("ember-runtime/system/string", |
| ["ember-metal/core", "ember-metal/utils", "exports"], |
| function(__dependency1__, __dependency2__, __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 STRING_DASHERIZE_REGEXP = (/[ _]/g); |
| var STRING_DASHERIZE_CACHE = {}; |
| 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) { |
| if (!isArray(formats) || arguments.length > 2) { |
| formats = Array.prototype.slice.call(arguments, 1); |
| } |
| |
| // 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 = formats[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 str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase(); |
| } |
| |
| function dasherize(str) { |
| var cache = STRING_DASHERIZE_CACHE, |
| hit = cache.hasOwnProperty(str), |
| ret; |
| |
| if (hit) { |
| return cache[str]; |
| } else { |
| ret = decamelize(str).replace(STRING_DASHERIZE_REGEXP, '-'); |
| cache[str] = ret; |
| } |
| |
| return ret; |
| } |
| |
| function camelize(str) { |
| return str.replace(STRING_CAMELIZE_REGEXP, function(match, separator, chr) { |
| return chr ? chr.toUpperCase() : ''; |
| }).replace(/^([A-Z])/, function(match, separator, chr) { |
| return match.toLowerCase(); |
| }); |
| } |
| |
| function classify(str) { |
| var parts = str.split("."), |
| 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("."); |
| } |
| |
| function underscore(str) { |
| return str.replace(STRING_UNDERSCORE_REGEXP_1, '$1_$2'). |
| replace(STRING_UNDERSCORE_REGEXP_2, '_').toLowerCase(); |
| } |
| |
| function capitalize(str) { |
| return str.charAt(0).toUpperCase() + str.substr(1); |
| } |
| |
| /** |
| 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; |
| }); |
| define("ember-runtime/system/subarray", |
| ["ember-metal/property_get", "ember-metal/error", "ember-metal/enumerable_utils", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __exports__) { |
| "use strict"; |
| var get = __dependency1__.get; |
| var EmberError = __dependency2__["default"]; |
| var EnumerableUtils = __dependency3__["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, |
| itemType = match ? RETAIN : FILTER, |
| 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, |
| 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 operationIndex, |
| len, |
| operation, |
| rangeStart, |
| rangeEnd, |
| seenInSubArray = 0; |
| |
| // 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], |
| 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); |
| } |
| }; |
| }); |
| define("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), |
| arrayOperation = match.operation, |
| arrayOperationIndex = match.index, |
| arrayOperationRangeStart = match.rangeStart, |
| composeIndex, |
| splitIndex, |
| splitItems, |
| splitArrayOperation, |
| 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), |
| arrayOperation = match.operation, |
| arrayOperationIndex = match.index, |
| arrayOperationRangeStart = match.rangeStart, |
| 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 = [], |
| 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 arrayOperationIndex, |
| len, |
| split = false, |
| arrayOperation, |
| arrayOperationRangeStart, |
| arrayOperationRangeEnd; |
| |
| // 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], |
| leftArrayOperation = this._operations[index-1], // may be undefined |
| rightArrayOperation = this._operations[index + 1], // may be undefined |
| leftOp = leftArrayOperation && leftArrayOperation.type, |
| 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], |
| deletesToGo = arrayOperation.count, |
| leftArrayOperation = this._operations[index-1], // may be undefined |
| leftOp = leftArrayOperation && leftArrayOperation.type, |
| nextArrayOperation, |
| nextOp, |
| nextCount, |
| removeNewAndNextOp = false, |
| 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; |
| } |
| }); |
| define("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; |
| }); |
| define("ember-testing/adapters/adapter", |
| ["ember-metal/core", "ember-metal/utils", "ember-runtime/system/object", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| // Ember.K |
| var inspect = __dependency2__.inspect; |
| var EmberObject = __dependency3__["default"]; |
| |
| /** |
| @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: Ember.K, |
| |
| /** |
| This callback will be called whenever an async operation has completed. |
| |
| @public |
| @method asyncEnd |
| */ |
| asyncEnd: Ember.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; |
| }); |
| define("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)); |
| } |
| }); |
| }); |
| define("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 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')) { |
| 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, context, type, options) { |
| if (arguments.length === 3) { |
| options = type; |
| type = context; |
| context = null; |
| } |
| |
| if (typeof options === 'undefined') { |
| options = {}; |
| } |
| |
| 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, context, type, keyCode) { |
| if (typeof keyCode === 'undefined') { |
| keyCode = type; |
| type = context; |
| context = null; |
| } |
| |
| return app.testHelpers.triggerEvent(selector, context, type, { |
| keyCode: keyCode, |
| which: keyCode |
| }); |
| } |
| |
| function fillIn(app, selector, context, text) { |
| var $el; |
| if (typeof text === 'undefined') { |
| text = context; |
| context = null; |
| } |
| $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() { |
| // 1. If the router is loading, keep polling |
| var routerIsLoading = !!app.__container__.lookup('router:main').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', username) |
| .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); |
| |
| /** |
| 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); |
| }); |
| define("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(); |
| } |
| } |
| }); |
| } |
| }); |
| }); |
| define("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); |
| } |
| }); |
| define("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); |
| }); |
| }); |
| }); |
| define("ember-testing/test", |
| ["ember-metal/core", "ember-metal/run_loop", "ember-metal/platform", "ember-runtime/compare", "ember-runtime/ext/rsvp", "ember-testing/setup_for_testing", "ember-application/system/application", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| var emberRun = __dependency2__["default"]; |
| var create = __dependency3__.create; |
| var compare = __dependency4__["default"]; |
| var RSVP = __dependency5__["default"]; |
| var setupForTesting = __dependency6__["default"]; |
| var EmberApplication = __dependency7__["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 |
| */ |
| _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) { |
| var pair; |
| if (!this.waiters) { |
| return; |
| } |
| if (arguments.length === 1) { |
| callback = context; |
| context = null; |
| } |
| pair = [context, callback]; |
| this.waiters = Ember.A(this.waiters.filter(function(elt) { |
| return compare(elt, pair) !== 0; |
| })); |
| } |
| }; |
| |
| function helper(app, name) { |
| var fn = helpers[name].method, |
| meta = helpers[name].meta; |
| |
| return function() { |
| var args = slice.call(arguments), |
| 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; |
| }); |
| define("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/view_collection", "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", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __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 setInnerHTML = __dependency3__.setInnerHTML; |
| var isSimpleClick = __dependency3__.isSimpleClick; |
| 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 ViewCollection = __dependency9__["default"]; |
| var ContainerView = __dependency10__["default"]; |
| var CollectionView = __dependency11__["default"]; |
| var Component = __dependency12__["default"]; |
| |
| var EventDispatcher = __dependency13__["default"]; |
| var ViewTargetActionSupport = __dependency14__["default"]; |
| // END IMPORTS |
| |
| /** |
| Alias for jQuery |
| |
| @method $ |
| @for Ember |
| */ |
| |
| // BEGIN EXPORTS |
| Ember.$ = jQuery; |
| |
| Ember.ViewTargetActionSupport = ViewTargetActionSupport; |
| Ember.RenderBuffer = RenderBuffer; |
| |
| var ViewUtils = Ember.ViewUtils = {}; |
| ViewUtils.setInnerHTML = setInnerHTML; |
| ViewUtils.isSimpleClick = isSimpleClick; |
| |
| Ember.CoreView = CoreView; |
| Ember.View = View; |
| Ember.View.states = states; |
| Ember.View.cloneStates = cloneStates; |
| |
| Ember._ViewCollection = ViewCollection; |
| Ember.ContainerView = ContainerView; |
| Ember.CollectionView = CollectionView; |
| Ember.Component = Component; |
| Ember.EventDispatcher = EventDispatcher; |
| // END EXPORTS |
| |
| __exports__["default"] = Ember; |
| }); |
| define("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, |
| 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']; |
| } |
| |
| if (deprecatedProperty) { |
| Ember.deprecate('Do not specify ' + deprecatedProperty + ' on a Component, use ' + replacementProperty + ' instead.', false); |
| } |
| } |
| }); |
| }); |
| define("ember-views/mixins/view_target_action_support", |
| ["ember-metal/mixin", "ember-runtime/mixins/target_action_support", "ember-metal/computed", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __exports__) { |
| "use strict"; |
| var Mixin = __dependency1__.Mixin; |
| var TargetActionSupport = __dependency2__["default"]; |
| |
| // ES6TODO: computed should have its own export path so you can do import {defaultTo} from computed |
| var computed = __dependency3__.computed; |
| var alias = computed.alias; |
| |
| /** |
| `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') |
| }); |
| }); |
| define("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/views/view", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __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__.isNone; |
| var run = __dependency5__["default"]; |
| var typeOf = __dependency6__.typeOf; |
| var fmt = __dependency7__.fmt; |
| var EmberObject = __dependency8__["default"]; |
| var jQuery = __dependency9__["default"]; |
| var View = __dependency10__["default"]; |
| |
| var ActionHelper; |
| |
| //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' |
| */ |
| 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'); |
| |
| jQuery.extend(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], |
| 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) { |
| //ES6TODO: Needed for ActionHelper (generally not available in ember-views test suite) |
| if (!ActionHelper) { |
| ActionHelper = requireModule("ember-routing-handlebars/helpers/action")["ActionHelper"]; |
| } |
| |
| var actionId = jQuery(evt.currentTarget).attr('data-ember-action'), |
| action = ActionHelper.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(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 '(EventDisptacher)'; |
| } |
| }); |
| }); |
| define("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. |
| var queues = run.queues; |
| run._addQueue('render', 'actions'); |
| run._addQueue('afterRender', 'render'); |
| }); |
| define("ember-views/system/jquery", |
| ["ember-metal/core", "ember-runtime/system/string", "ember-metal/enumerable_utils", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| // Ember.assert |
| var w = __dependency2__.w; |
| |
| // ES6TODO: the functions on EnumerableUtils need their own exports |
| var forEach = __dependency3__.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 require === 'function') { |
| jQuery = require('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 = w('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; |
| }); |
| define("ember-views/system/render_buffer", |
| ["ember-metal/core", "ember-metal/property_get", "ember-metal/property_set", "ember-views/system/utils", "ember-views/system/jquery", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) { |
| "use strict"; |
| /** |
| @module ember |
| @submodule ember-views |
| */ |
| |
| var Ember = __dependency1__["default"]; |
| // jQuery |
| |
| var get = __dependency2__.get; |
| var set = __dependency3__.set; |
| var setInnerHTML = __dependency4__.setInnerHTML; |
| var jQuery = __dependency5__["default"]; |
| |
| function ClassSet() { |
| this.seen = {}; |
| this.list = []; |
| } |
| |
| ClassSet.prototype = { |
| add: function(string) { |
| if (string in this.seen) { |
| return; |
| } |
| this.seen[string] = true; |
| |
| this.list.push(string); |
| }, |
| |
| toDOM: function() { |
| return this.list.join(" "); |
| } |
| }; |
| |
| 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'), |
| 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'); |
| ``` |
| |
| @class RenderBuffer |
| @namespace Ember |
| @constructor |
| @param {String} tagName tag name (such as 'div' or 'p') used for the buffer |
| */ |
| __exports__["default"] = function RenderBuffer(tagName) { |
| return new _RenderBuffer(tagName); // jshint ignore:line |
| } |
| |
| function _RenderBuffer(tagName) { |
| this.tagNames = [tagName || null]; |
| this.buffer = ""; |
| } |
| |
| _RenderBuffer.prototype = { |
| |
| // The root view's element |
| _element: null, |
| |
| _hasElement: true, |
| |
| /** |
| 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') |
| ``` |
| |
| @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, |
| |
| /** |
| Adds a string of HTML to the `RenderBuffer`. |
| |
| @method push |
| @param {String} string HTML to push into the buffer |
| @chainable |
| */ |
| push: function(string) { |
| this.buffer += string; |
| 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, 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; |
| }, |
| |
| begin: function(tagName) { |
| this.tagNames.push(tagName || null); |
| return this; |
| }, |
| |
| pushOpeningTag: function() { |
| var tagName = this.currentTagName(); |
| if (!tagName) { |
| return; |
| } |
| |
| if (this._hasElement && !this._element && this.buffer.length === 0) { |
| this._element = this.generateElement(); |
| return; |
| } |
| |
| var buffer = this.buffer, |
| id = this.elementId, |
| classes = this.classes, |
| attrs = this.elementAttributes, |
| props = this.elementProperties, |
| style = this.elementStyle, |
| attr, prop; |
| |
| buffer += '<' + stripTagName(tagName); |
| |
| if (id) { |
| buffer += ' id="' + escapeAttribute(id) + '"'; |
| this.elementId = null; |
| } |
| if (classes) { |
| buffer += ' class="' + escapeAttribute(classes.join(' ')) + '"'; |
| this.classes = null; |
| this.elementClasses = null; |
| } |
| |
| if (style) { |
| buffer += ' style="'; |
| |
| for (prop in style) { |
| if (style.hasOwnProperty(prop)) { |
| buffer += prop + ':' + escapeAttribute(style[prop]) + ';'; |
| } |
| } |
| |
| buffer += '"'; |
| |
| this.elementStyle = null; |
| } |
| |
| if (attrs) { |
| for (attr in attrs) { |
| if (attrs.hasOwnProperty(attr)) { |
| buffer += ' ' + attr + '="' + escapeAttribute(attrs[attr]) + '"'; |
| } |
| } |
| |
| this.elementAttributes = null; |
| } |
| |
| if (props) { |
| for (prop in props) { |
| if (props.hasOwnProperty(prop)) { |
| var value = props[prop]; |
| if (value || typeof(value) === 'number') { |
| if (value === true) { |
| buffer += ' ' + prop + '="' + prop + '"'; |
| } else { |
| buffer += ' ' + prop + '="' + escapeAttribute(props[prop]) + '"'; |
| } |
| } |
| } |
| } |
| |
| this.elementProperties = null; |
| } |
| |
| buffer += '>'; |
| this.buffer = buffer; |
| }, |
| |
| pushClosingTag: function() { |
| var tagName = this.tagNames.pop(); |
| if (tagName) { |
| this.buffer += '</' + stripTagName(tagName) + '>'; |
| } |
| }, |
| |
| currentTagName: function() { |
| return this.tagNames[this.tagNames.length-1]; |
| }, |
| |
| generateElement: function() { |
| var tagName = this.tagNames.pop(), // pop since we don't need to close |
| id = this.elementId, |
| classes = this.classes, |
| attrs = this.elementAttributes, |
| props = this.elementProperties, |
| style = this.elementStyle, |
| styleBuffer = '', 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 = document.createElement(tagString), |
| $element = jQuery(element); |
| |
| if (id) { |
| $element.attr('id', id); |
| this.elementId = null; |
| } |
| if (classes) { |
| $element.attr('class', classes.join(' ')); |
| this.classes = null; |
| this.elementClasses = null; |
| } |
| |
| if (style) { |
| for (prop in style) { |
| if (style.hasOwnProperty(prop)) { |
| styleBuffer += (prop + ':' + style[prop] + ';'); |
| } |
| } |
| |
| $element.attr('style', styleBuffer); |
| |
| this.elementStyle = null; |
| } |
| |
| if (attrs) { |
| for (attr in attrs) { |
| if (attrs.hasOwnProperty(attr)) { |
| $element.attr(attr, attrs[attr]); |
| } |
| } |
| |
| this.elementAttributes = null; |
| } |
| |
| if (props) { |
| for (prop in props) { |
| if (props.hasOwnProperty(prop)) { |
| $element.prop(prop, props[prop]); |
| } |
| } |
| |
| this.elementProperties = null; |
| } |
| |
| return element; |
| }, |
| |
| /** |
| @method element |
| @return {DOMElement} The element corresponding to the generated HTML |
| of this buffer |
| */ |
| element: function() { |
| var html = this.innerString(); |
| |
| if (html) { |
| this._element = setInnerHTML(this._element, html); |
| } |
| |
| return this._element; |
| }, |
| |
| /** |
| Generates the HTML content for this buffer. |
| |
| @method string |
| @return {String} The generated HTML |
| */ |
| string: function() { |
| if (this._hasElement && this._element) { |
| // Firefox versions < 11 do not have support for element.outerHTML. |
| var thisElement = this.element(), outerHTML = thisElement.outerHTML; |
| if (typeof outerHTML === 'undefined') { |
| return jQuery('<div/>').append(thisElement).html(); |
| } |
| return outerHTML; |
| } else { |
| return this.innerString(); |
| } |
| }, |
| |
| innerString: function() { |
| return this.buffer; |
| } |
| }; |
| }); |
| define("ember-views/system/utils", |
| ["ember-metal/core", "exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| /* globals XMLSerializer */ |
| |
| var Ember = __dependency1__["default"]; |
| // Ember.assert |
| |
| /** |
| @module ember |
| @submodule ember-views |
| */ |
| |
| /* BEGIN METAMORPH HELPERS */ |
| |
| // 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 = typeof document !== 'undefined' && (function() { |
| var testEl = document.createElement('div'); |
| testEl.innerHTML = "<div></div>"; |
| testEl.firstChild.innerHTML = "<script></script>"; |
| return testEl.firstChild.innerHTML === ''; |
| })(); |
| |
| // 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 = typeof document !== 'undefined' && (function() { |
| 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'; |
| })(); |
| |
| // Use this to find children by ID instead of using jQuery |
| var findChildById = function(element, id) { |
| if (element.getAttribute('id') === id) { |
| return element; |
| } |
| |
| var len = element.childNodes.length, idx, node, found; |
| for (idx = 0; idx < len; idx++) { |
| node = element.childNodes[idx]; |
| found = node.nodeType === 1 && findChildById(node, id); |
| if (found) { |
| return found; |
| } |
| } |
| }; |
| |
| var setInnerHTMLWithoutFix = function(element, html) { |
| if (needsShy) { |
| html = '­' + html; |
| } |
| |
| var matches = []; |
| if (movesWhitespace) { |
| // Right now we only check for script tags with ids with the |
| // goal of targeting morphs. |
| html = html.replace(/(\s+)(<script id='([^']+)')/g, function(match, spaces, tag, id) { |
| matches.push([id, spaces]); |
| return tag; |
| }); |
| } |
| |
| element.innerHTML = html; |
| |
| // If we have to do any whitespace adjustments do them now |
| if (matches.length > 0) { |
| var len = matches.length, idx; |
| for (idx = 0; idx < len; idx++) { |
| var script = findChildById(element, matches[idx][0]), |
| node = document.createTextNode(matches[idx][1]); |
| script.parentNode.insertBefore(node, script); |
| } |
| } |
| |
| if (needsShy) { |
| var shyElement = element.firstChild; |
| while (shyElement.nodeType === 1 && !shyElement.nodeName) { |
| shyElement = shyElement.firstChild; |
| } |
| if (shyElement.nodeType === 3 && shyElement.nodeValue.charAt(0) === "\u00AD") { |
| shyElement.nodeValue = shyElement.nodeValue.slice(1); |
| } |
| } |
| }; |
| |
| /* END METAMORPH HELPERS */ |
| |
| |
| var innerHTMLTags = {}; |
| var canSetInnerHTML = function(tagName) { |
| if (innerHTMLTags[tagName] !== undefined) { |
| return innerHTMLTags[tagName]; |
| } |
| |
| var canSet = true; |
| |
| // IE 8 and earlier don't allow us to do innerHTML on select |
| if (tagName.toLowerCase() === 'select') { |
| var el = document.createElement('select'); |
| setInnerHTMLWithoutFix(el, '<option value="test">Test</option>'); |
| canSet = el.options.length === 1; |
| } |
| |
| innerHTMLTags[tagName] = canSet; |
| |
| return canSet; |
| }; |
| |
| function setInnerHTML(element, html) { |
| var tagName = element.tagName; |
| |
| if (canSetInnerHTML(tagName)) { |
| setInnerHTMLWithoutFix(element, html); |
| } else { |
| // Firefox versions < 11 do not have support for element.outerHTML. |
| var outerHTML = element.outerHTML || new XMLSerializer().serializeToString(element); |
| Ember.assert("Can't set innerHTML on " + element.tagName + " in this browser", outerHTML); |
| |
| var startTag = outerHTML.match(new RegExp("<" + tagName + "([^>]*)>", 'i'))[0], |
| endTag = '</' + tagName + '>'; |
| |
| var wrapper = document.createElement('div'); |
| setInnerHTMLWithoutFix(wrapper, startTag + html + endTag); |
| element = wrapper.firstChild; |
| while (element.tagName !== tagName) { |
| element = element.nextSibling; |
| } |
| } |
| |
| return element; |
| } |
| |
| __exports__.setInnerHTML = setInnerHTML; |
| function isSimpleClick(event) { |
| var modifier = event.shiftKey || event.metaKey || event.altKey || event.ctrlKey, |
| secondaryClick = event.which > 1; // IE9 may return undefined |
| |
| return !modifier && !secondaryClick; |
| } |
| |
| __exports__.isSimpleClick = isSimpleClick; |
| }); |
| define("ember-views/views/collection_view", |
| ["ember-metal/core", "ember-metal/platform", "ember-metal/binding", "ember-metal/merge", "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-runtime/mixins/array", "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 create = __dependency2__.create; |
| var isGlobalPath = __dependency3__.isGlobalPath; |
| var merge = __dependency4__["default"]; |
| var get = __dependency5__.get; |
| var set = __dependency6__.set; |
| var fmt = __dependency7__.fmt; |
| var ContainerView = __dependency8__["default"]; |
| var CoreView = __dependency9__["default"]; |
| var View = __dependency10__["default"]; |
| var observer = __dependency11__.observer; |
| var beforeObserver = __dependency11__.beforeObserver; |
| var EmberArray = __dependency12__["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 an empty `<body>` and the following code: |
| |
| ```javascript |
| someItemsView = Ember.CollectionView.create({ |
| classNames: ['a-collection'], |
| content: ['A','B','C'], |
| itemViewClass: Ember.View.extend({ |
| template: Ember.Handlebars.compile("the letter: {{view.content}}") |
| }) |
| }); |
| |
| someItemsView.appendTo('body'); |
| ``` |
| |
| Will result in the following HTML structure |
| |
| ```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 an empty `<body>` and the following code: |
| |
| ```javascript |
| anUnorderedListView = Ember.CollectionView.create({ |
| tagName: 'ul', |
| content: ['A','B','C'], |
| itemViewClass: Ember.View.extend({ |
| template: Ember.Handlebars.compile("the letter: {{view.content}}") |
| }) |
| }); |
| |
| anUnorderedListView.appendTo('body'); |
| ``` |
| |
| Will result in the following HTML structure |
| |
| ```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 ` |
| |
| ```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 |
| 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 |
| aListWithNothing = Ember.CollectionView.create({ |
| classNames: ['nothing'] |
| content: null, |
| emptyView: Ember.View.extend({ |
| template: Ember.Handlebars.compile("The collection is empty") |
| }) |
| }); |
| |
| aListWithNothing.appendTo('body'); |
| ``` |
| |
| Will result in the following HTML structure |
| |
| ```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 occurr |
| @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, childView, idx, len; |
| |
| len = this._childViews.length; |
| |
| var removingAll = removedCount === len; |
| |
| if (removingAll) { |
| this.currentState.empty(this); |
| this.invokeRecursively(function(view) { |
| view.removedFromDOM = true; |
| }, false); |
| } |
| |
| 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 = [], view, item, idx, len, itemViewClass, |
| emptyView; |
| |
| len = content ? get(content, 'length') : 0; |
| |
| if (len) { |
| itemViewClass = get(this, 'itemViewClass'); |
| |
| if ('string' === typeof itemViewClass && isGlobalPath(itemViewClass)) { |
| itemViewClass = get(itemViewClass) || itemViewClass; |
| } |
| |
| Ember.assert(fmt("itemViewClass must be a subclass of Ember.View, not %@", |
| [itemViewClass]), |
| 'string' === typeof itemViewClass || View.detect(itemViewClass)); |
| |
| for (idx = start; idx < start + added; idx++) { |
| item = content.objectAt(idx); |
| |
| view = this.createChildView(itemViewClass, { |
| content: item, |
| contentIndex: idx |
| }); |
| |
| 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; |
| }); |
| define("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", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __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__.isNone; |
| |
| var computed = __dependency8__.computed; |
| |
| var a_slice = Array.prototype.slice; |
| |
| /** |
| @module ember |
| @submodule ember-views |
| */ |
| |
| /** |
| An `Ember.Component` is a view that is completely |
| isolated. Property access 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(); |
| set(this, 'origContext', get(this, 'context')); |
| set(this, 'context', this); |
| set(this, 'controller', this); |
| }, |
| |
| defaultLayout: function(context, options) { |
| Ember.Handlebars.helpers['yield'].call(context, options); |
| }, |
| |
| /** |
| 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'), |
| 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, |
| |
| // during render, isolate keywords |
| cloneKeywords: function() { |
| return { |
| view: this, |
| controller: this |
| }; |
| }, |
| |
| _yield: function(context, options) { |
| var view = options.data.view, |
| parentView = this._parentView, |
| 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: '', |
| _contextView: parentView, |
| template: template, |
| context: options.data.insideGroup ? get(this, 'origContext') : get(parentView, 'context'), |
| controller: get(parentView, 'controller'), |
| templateData: { |
| keywords: parentView.cloneKeywords(), |
| insideGroup: options.data.insideGroup |
| } |
| }); |
| } |
| }, |
| |
| /** |
| 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, |
| 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 |
| }); |
| } |
| }); |
| |
| __exports__["default"] = Component; |
| }); |
| define("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/view_collection", "ember-views/views/states", "ember-metal/error", "ember-metal/enumerable_utils", "ember-metal/computed", "ember-metal/run_loop", "ember-metal/properties", "ember-views/system/render_buffer", "ember-metal/mixin", "ember-runtime/system/native_array", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __dependency16__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| // Ember.assert, Ember.K |
| |
| var merge = __dependency2__["default"]; |
| var MutableArray = __dependency3__["default"]; |
| var get = __dependency4__.get; |
| var set = __dependency5__.set; |
| |
| var View = __dependency6__["default"]; |
| var ViewCollection = __dependency7__["default"]; |
| |
| var cloneStates = __dependency8__.cloneStates; |
| var EmberViewStates = __dependency8__.states; |
| |
| var EmberError = __dependency9__["default"]; |
| |
| var forEach = __dependency10__.forEach; |
| |
| var computed = __dependency11__.computed; |
| var run = __dependency12__["default"]; |
| var defineProperty = __dependency13__.defineProperty; |
| var renderBuffer = __dependency14__["default"]; |
| var observer = __dependency15__.observer; |
| var beforeObserver = __dependency15__.beforeObserver; |
| var emberA = __dependency16__.A; |
| |
| /** |
| @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, |
| |
| init: function() { |
| this._super(); |
| |
| var childViews = get(this, '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) { |
| this.forEachChildView(function(view) { |
| view.renderToBuffer(buffer); |
| }); |
| }, |
| |
| 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, get(this, 'templateData')); |
| this.currentState.childViewsDidChange(this, views, start, added); |
| } |
| this.propertyDidChange('childViews'); |
| }, |
| |
| initializeViews: function(views, parentView, templateData) { |
| forEach(views, function(view) { |
| set(view, '_parentView', parentView); |
| |
| if (!view.container && parentView) { |
| set(view, 'container', parentView.container); |
| } |
| |
| if (!get(view, 'templateData')) { |
| set(view, 'templateData', templateData); |
| } |
| }); |
| }, |
| |
| 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: Ember.K, |
| childViewsDidChange: Ember.K, |
| ensureChildrenAreInDOM: Ember.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++) { |
| views[i].remove(); |
| } |
| }, |
| |
| childViewsDidChange: function(view, views, start, added) { |
| run.scheduleOnce('render', view, '_ensureChildrenAreInDOM'); |
| }, |
| |
| ensureChildrenAreInDOM: function(view) { |
| var childViews = view._childViews, i, len, childView, previous, buffer, viewCollection = new ViewCollection(); |
| |
| for (i = 0, len = childViews.length; i < len; i++) { |
| childView = childViews[i]; |
| |
| if (!buffer) { |
| buffer = renderBuffer(); |
| buffer._hasElement = false; |
| } |
| |
| if (childView.renderToBufferIfNeeded(buffer)) { |
| viewCollection.push(childView); |
| } else if (viewCollection.length) { |
| insertViewCollection(view, viewCollection, previous, buffer); |
| buffer = null; |
| previous = childView; |
| viewCollection.clear(); |
| } else { |
| previous = childView; |
| } |
| } |
| |
| if (viewCollection.length) { |
| insertViewCollection(view, viewCollection, previous, buffer); |
| } |
| } |
| }); |
| |
| function insertViewCollection(view, viewCollection, previous, buffer) { |
| viewCollection.triggerRecursively('willInsertElement'); |
| |
| if (previous) { |
| previous.domManager.after(previous, buffer.string()); |
| } else { |
| view.domManager.prepend(view, buffer.string()); |
| } |
| |
| viewCollection.forEach(function(v) { |
| v._transitionTo('inDOM'); |
| v.propertyDidChange('element'); |
| v.triggerRecursively('didInsertElement'); |
| }); |
| } |
| |
| |
| __exports__["default"] = ContainerView; |
| }); |
| define("ember-views/views/core_view", |
| ["ember-views/views/states", "ember-runtime/system/object", "ember-runtime/mixins/evented", "ember-runtime/mixins/action_handler", "ember-metal/properties", "ember-metal/property_get", "ember-metal/property_set", "ember-metal/computed", "ember-metal/utils", "ember-metal/instrumentation", "ember-views/system/render_buffer", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __exports__) { |
| "use strict"; |
| var cloneStates = __dependency1__.cloneStates; |
| var states = __dependency1__.states; |
| var EmberObject = __dependency2__["default"]; |
| var Evented = __dependency3__["default"]; |
| var ActionHandler = __dependency4__["default"]; |
| |
| var defineProperty = __dependency5__.defineProperty; |
| var deprecateProperty = __dependency5__.deprecateProperty; |
| var get = __dependency6__.get; |
| var set = __dependency7__.set; |
| var computed = __dependency8__.computed; |
| |
| var typeOf = __dependency9__.typeOf; |
| |
| var instrument = __dependency10__.instrument; |
| |
| |
| var renderBuffer = __dependency11__["default"]; |
| |
| /** |
| `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, |
| |
| _states: cloneStates(states), |
| |
| init: function() { |
| this._super(); |
| this._transitionTo('preRender'); |
| this._isVisible = get(this, 'isVisible'); |
| |
| deprecateProperty(this, 'states', '_states'); |
| deprecateProperty(this, 'state', '_state'); |
| }, |
| |
| /** |
| 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; |
| }, |
| |
| /** |
| Invoked by the view system when this view needs to produce an HTML |
| representation. This method will create a new render buffer, if needed, |
| then apply any default attributes, such as class names and visibility. |
| Finally, the `render()` method is invoked, which is responsible for |
| doing the bulk of the rendering. |
| |
| You should not need to override this method; instead, implement the |
| `template` property, or if you need more control, override the `render` |
| method. |
| |
| @method renderToBuffer |
| @param {Ember.RenderBuffer} buffer the render buffer. If no buffer is |
| passed, a default buffer, using the current view's `tagName`, will |
| be used. |
| @private |
| */ |
| renderToBuffer: function(buffer) { |
| var name = 'render.' + this.instrumentName, |
| details = {}; |
| |
| this.instrumentDetails(details); |
| |
| return instrument(name, details, function instrumentRenderToBuffer() { |
| return this._renderToBuffer(buffer); |
| }, this); |
| }, |
| |
| _renderToBuffer: function(_buffer) { |
| // 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 = this.tagName; |
| |
| if (tagName === null || tagName === undefined) { |
| tagName = 'div'; |
| } |
| |
| var buffer = this.buffer = _buffer && _buffer.begin(tagName) || renderBuffer(tagName); |
| this._transitionTo('inBuffer', false); |
| |
| this.beforeRender(buffer); |
| this.render(buffer); |
| this.afterRender(buffer); |
| |
| return buffer; |
| }, |
| |
| /** |
| 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); |
| } |
| }, |
| |
| deprecatedSendHandles: function(actionName) { |
| return !!this[actionName]; |
| }, |
| |
| deprecatedSend: function(actionName) { |
| var args = [].slice.call(arguments, 1); |
| Ember.assert('' + this + " has the action " + actionName + " but it is not a function", typeof this[actionName] === 'function'); |
| Ember.deprecate('Action handlers implemented directly on views are deprecated in favor of action handlers on an `actions` object ( action: `' + actionName + '` on ' + this + ')', false); |
| this[actionName].apply(this, args); |
| return; |
| }, |
| |
| 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.destroyElement(); |
| } |
| |
| // 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: Ember.K, |
| triggerRecursively: Ember.K, |
| invokeRecursively: Ember.K, |
| _transitionTo: Ember.K, |
| destroyElement: Ember.K |
| }); |
| |
| __exports__["default"] = CoreView; |
| }); |
| define("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; |
| }); |
| define("ember-views/views/states/default", |
| ["ember-metal/core", "ember-metal/property_get", "ember-metal/property_set", "ember-metal/run_loop", "ember-metal/error", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| // Ember.K |
| var get = __dependency2__.get; |
| var set = __dependency3__.set; |
| var run = __dependency4__["default"]; |
| var EmberError = __dependency5__["default"]; |
| |
| /** |
| @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) { |
| set(view, 'element', null); |
| if (view._scheduledInsert) { |
| run.cancel(view._scheduledInsert); |
| view._scheduledInsert = null; |
| } |
| return view; |
| }, |
| |
| renderToBufferIfNeeded: function () { |
| return false; |
| }, |
| |
| rerender: Ember.K, |
| invokeObserver: Ember.K |
| }; |
| }); |
| define("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'])); |
| }, |
| empty: function() { |
| throw new EmberError(fmt(destroyingError, ['empty'])); |
| }, |
| |
| setElement: function() { |
| throw new EmberError(fmt(destroyingError, ["set('element', ...)"])); |
| }, |
| |
| renderToBufferIfNeeded: function() { |
| return false; |
| }, |
| |
| // Since element insertion is scheduled, don't do anything if |
| // the view has been destroyed between scheduling and execution |
| insertElement: Ember.K |
| }); |
| |
| __exports__["default"] = destroying; |
| }); |
| define("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", "ember-metal/property_set", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __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 set = __dependency8__.set; |
| |
| var hasElement = create(_default); |
| |
| merge(hasElement, { |
| $: function(view, sel) { |
| var elem = get(view, '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]; |
| }, |
| |
| setElement: function(view, value) { |
| if (value === null) { |
| view._transitionTo('preRender'); |
| } else { |
| throw new EmberError("You cannot set an element to a non-null value when the element is already in the DOM."); |
| } |
| |
| return value; |
| }, |
| |
| // once the view has been inserted into the DOM, rerendering is |
| // deferred to allow bindings to synchronize. |
| rerender: function(view) { |
| view.triggerRecursively('willClearRender'); |
| |
| view.clearRenderedChildren(); |
| |
| view.domManager.replace(view); |
| return view; |
| }, |
| |
| // 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._notifyWillDestroyElement(); |
| view.domManager.remove(view); |
| set(view, 'element', null); |
| if (view._scheduledInsert) { |
| run.cancel(view._scheduledInsert); |
| view._scheduledInsert = null; |
| } |
| return view; |
| }, |
| |
| empty: function(view) { |
| var _childViews = view._childViews, len, idx; |
| if (_childViews) { |
| len = _childViews.length; |
| for (idx = 0; idx < len; idx++) { |
| _childViews[idx]._notifyWillDestroyElement(); |
| } |
| } |
| view.domManager.empty(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; |
| }); |
| define("ember-views/views/states/in_buffer", |
| ["ember-views/views/states/default", "ember-metal/error", "ember-metal/core", "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 Ember = __dependency3__["default"]; |
| // Ember.assert |
| 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 Ember.$(); |
| }, |
| |
| // 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, _childViews = view._childViews; |
| |
| childView = view.createChildView(childView, options); |
| if (!_childViews.length) { |
| _childViews = view._childViews = _childViews.slice(); |
| } |
| _childViews.push(childView); |
| |
| childView.renderToBuffer(buffer); |
| |
| view.propertyDidChange('childViews'); |
| |
| return childView; |
| }, |
| |
| // when a view is rendered in a buffer, destroying the |
| // element will simply destroy the buffer and put the |
| // state back into the preRender state. |
| destroyElement: function(view) { |
| view.clearBuffer(); |
| var viewCollection = view._notifyWillDestroyElement(); |
| viewCollection.transitionTo('preRender', false); |
| |
| return view; |
| }, |
| |
| empty: function() { |
| Ember.assert("Emptying a view in the inBuffer state is not allowed and " + |
| "should not happen under normal circumstances. Most likely " + |
| "there is a bug in your application. This may be due to " + |
| "excessive property change notifications."); |
| }, |
| |
| renderToBufferIfNeeded: function (view, buffer) { |
| return false; |
| }, |
| |
| // It should be impossible for a rendered view to be scheduled for |
| // insertion. |
| insertElement: function() { |
| throw new EmberError("You can't insert an element that has already been rendered"); |
| }, |
| |
| setElement: function(view, value) { |
| if (value === null) { |
| view._transitionTo('preRender'); |
| } else { |
| view.clearBuffer(); |
| view._transitionTo('hasElement'); |
| } |
| |
| return value; |
| }, |
| |
| invokeObserver: function(target, observer) { |
| observer.call(target); |
| } |
| }); |
| |
| __exports__["default"] = inBuffer; |
| }); |
| define("ember-views/views/states/in_dom", |
| ["ember-metal/core", "ember-metal/platform", "ember-metal/merge", "ember-metal/error", "ember-views/views/states/has_element", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) { |
| "use strict"; |
| var Ember = __dependency1__["default"]; |
| // Ember.assert |
| var create = __dependency2__.create; |
| var merge = __dependency3__["default"]; |
| var EmberError = __dependency4__["default"]; |
| |
| var hasElement = __dependency5__["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; |
| } |
| |
| view.addBeforeObserver('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]; |
| }, |
| |
| insertElement: function(view, fn) { |
| throw new EmberError("You can't insert an element into the DOM that has already been inserted"); |
| } |
| }); |
| |
| __exports__["default"] = inDOM; |
| }); |
| define("ember-views/views/states/pre_render", |
| ["ember-views/views/states/default", "ember-metal/platform", "ember-metal/merge", "exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __exports__) { |
| "use strict"; |
| var _default = __dependency1__["default"]; |
| var create = __dependency2__.create; |
| var merge = __dependency3__["default"]; |
| |
| /** |
| @module ember |
| @submodule ember-views |
| */ |
| var preRender = create(_default); |
| |
| merge(preRender, { |
| // a view leaves the preRender state once its element has been |
| // created (createElement). |
| insertElement: function(view, fn) { |
| view.createElement(); |
| var viewCollection = view.viewHierarchyCollection(); |
| |
| viewCollection.trigger('willInsertElement'); |
| |
| fn.call(view); |
| |
| // We transition to `inDOM` if the element exists in the DOM |
| var element = view.get('element'); |
| if (document.body.contains(element)) { |
| viewCollection.transitionTo('inDOM', false); |
| viewCollection.trigger('didInsertElement'); |
| } |
| }, |
| |
| renderToBufferIfNeeded: function(view, buffer) { |
| view.renderToBuffer(buffer); |
| return true; |
| }, |
| |
| empty: Ember.K, |
| |
| setElement: function(view, value) { |
| if (value !== null) { |
| view._transitionTo('hasElement'); |
| } |
| return value; |
| } |
| }); |
| |
| __exports__["default"] = preRender; |
| }); |
| define("ember-views/views/view", |
| ["ember-metal/core", "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-metal/is_none", "container/container", "ember-runtime/system/native_array", "ember-runtime/system/string", "ember-metal/enumerable_utils", "ember-runtime/copy", "ember-metal/binding", "ember-metal/property_events", "ember-views/system/jquery", "ember-views/system/ext", "ember-views/views/core_view", "ember-views/views/view_collection", "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__, __exports__) { |
| "use strict"; |
| // Ember.assert, Ember.deprecate, Ember.warn, Ember.TEMPLATES, |
| // Ember.K, jQuery, Ember.lookup, |
| // Ember.ContainerView circular dependency |
| // Ember.ENV |
| var Ember = __dependency1__["default"]; |
| |
| var Evented = __dependency2__["default"]; |
| var EmberObject = __dependency3__["default"]; |
| var EmberError = __dependency4__["default"]; |
| var get = __dependency5__.get; |
| var set = __dependency6__.set; |
| var setProperties = __dependency7__["default"]; |
| var run = __dependency8__["default"]; |
| var addObserver = __dependency9__.addObserver; |
| var removeObserver = __dependency9__.removeObserver; |
| |
| var defineProperty = __dependency10__.defineProperty; |
| var deprecateProperty = __dependency10__.deprecateProperty; |
| var guidFor = __dependency11__.guidFor; |
| var meta = __dependency11__.meta; |
| var computed = __dependency12__.computed; |
| var observer = __dependency13__.observer; |
| |
| var typeOf = __dependency11__.typeOf; |
| var isArray = __dependency11__.isArray; |
| var isNone = __dependency14__.isNone; |
| var Mixin = __dependency13__.Mixin; |
| var Container = __dependency15__["default"]; |
| var emberA = __dependency16__.A; |
| |
| var dasherize = __dependency17__.dasherize; |
| |
| // ES6TODO: functions on EnumerableUtils should get their own export |
| var forEach = __dependency18__.forEach; |
| var addObject = __dependency18__.addObject; |
| var removeObject = __dependency18__.removeObject; |
| |
| var beforeObserver = __dependency13__.beforeObserver; |
| var copy = __dependency19__["default"]; |
| var isGlobalPath = __dependency20__.isGlobalPath; |
| |
| var propertyWillChange = __dependency21__.propertyWillChange; |
| var propertyDidChange = __dependency21__.propertyDidChange; |
| |
| var jQuery = __dependency22__["default"]; |
| // for the side effect of extending Ember.run.queues |
| |
| var CoreView = __dependency24__["default"]; |
| var ViewCollection = __dependency25__["default"]; |
| |
| /** |
| @module ember |
| @submodule ember-views |
| */ |
| |
| var ContainerView; |
| |
| function nullViewsBuffer(view) { |
| view.buffer = null; |
| |
| } |
| |
| function clearCachedElement(view) { |
| meta(view).cache.element = undefined; |
| } |
| |
| var childViewsProperty = computed(function() { |
| var childViews = this._childViews, ret = emberA(), view = this; |
| |
| 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) { |
| if (!ContainerView) { |
| ContainerView = requireModule('ember-views/views/container_view')['default']; |
| } |
| // ES6TODO: stupid circular dep |
| |
| if (view instanceof ContainerView) { |
| Ember.deprecate("Manipulating an Ember.ContainerView through its childViews property is deprecated. Please use the ContainerView instance itself as an Ember.MutableArray."); |
| return view.replace(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 |
| OuterView = Ember.View.extend({ |
| template: Ember.Handlebars.compile("outer {{#view InnerView}}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 occured |
| } |
| }) |
| }); |
| |
| 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'), |
| 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'); |
| }), |
| |
| /** |
| 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('_parentView', function(key) { |
| 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'), |
| 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) { |
| var template = get(this, 'template'); |
| if (template) { |
| template(context, options); |
| } |
| }, |
| |
| templateForName: function(name, type) { |
| if (!name) { |
| return; |
| } |
| Ember.assert("templateNames are not allowed to contain periods: " + name, name.indexOf('.') === -1); |
| |
| // the defaultContainer is deprecated |
| var container = this.container || (Container && Container.defaultContainer); |
| return container && 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) { |
| 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'), |
| 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.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'); |
| }); |
| }), |
| |
| cloneKeywords: function() { |
| var templateData = get(this, 'templateData'); |
| |
| var keywords = templateData ? copy(templateData.keywords) : {}; |
| set(keywords, 'view', this.isVirtual ? keywords.view : this); |
| set(keywords, '_view', this); |
| set(keywords, 'controller', get(this, 'controller')); |
| |
| return keywords; |
| }, |
| |
| /** |
| 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 keywords = this.cloneKeywords(); |
| var output; |
| |
| var data = { |
| view: this, |
| buffer: buffer, |
| isRenderData: true, |
| keywords: keywords, |
| insideGroup: get(this, 'templateData.insideGroup') |
| }; |
| |
| // 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. |
| |
| Ember.assert('template must be a function. Did you mean to call Ember.Handlebars.compile("...") or specify templateName instead?', typeof template === 'function'); |
| // The template should write directly to the render buffer instead |
| // of returning a string. |
| output = template(context, { |
| data: data |
| }); |
| |
| // 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); |
| }, |
| |
| clearRenderedChildren: function() { |
| var lengthBefore = this.lengthBeforeRender, |
| lengthAfter = this.lengthAfterRender; |
| |
| // If there were child views created during the last call to render(), |
| // remove them under the assumption that they will be re-created when |
| // we re-render. |
| |
| // VIEW-TODO: Unit test this path. |
| var childViews = this._childViews; |
| for (var i = lengthAfter-1; i >= lengthBefore; i--) { |
| if (childViews[i]) { |
| childViews[i].destroy(); |
| } |
| } |
| }, |
| |
| /** |
| 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, |
| 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) { |
| |
| 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']", binding.indexOf(' ') === -1); |
| |
| // 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; |
| // Extract just the property name from bindings like 'foo:bar' |
| var parsedPath = View._parsePropertyPath(binding); |
| |
| // Set up an observer on the context. If the property changes, toggle the |
| // class name. |
| var observer = function() { |
| // Get the current value of the property |
| newClass = this._classStringForProperty(binding); |
| elem = this.$(); |
| |
| // 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 = this._classStringForProperty(binding); |
| |
| 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; |
| } |
| |
| this.registerObserver(this, parsedPath.path, observer); |
| // 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, |
| unspecifiedAttributeBindings = this._unspecifiedAttributeBindings = this._unspecifiedAttributeBindings || {}; |
| |
| forEach(attributeBindings, function(binding) { |
| var split = binding.split(':'), |
| property = split[0], |
| attributeName = split[1] || property; |
| |
| 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); |
| |
| View.applyAttributeBindings(elem, attributeName, 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(property) { |
| var parsedPath = View._parsePropertyPath(property); |
| var path = parsedPath.path; |
| |
| var val = get(this, path); |
| if (val === undefined && isGlobalPath(path)) { |
| val = get(Ember.lookup, path); |
| } |
| |
| return View._classStringForValue(path, val, parsedPath.className, parsedPath.falsyClassName); |
| }, |
| |
| // .......................................................... |
| // ELEMENT SUPPORT |
| // |
| |
| /** |
| Returns the current DOM element for the view. |
| |
| @property element |
| @type DOMElement |
| */ |
| element: computed('_parentView', function(key, value) { |
| if (value !== undefined) { |
| return this.currentState.setElement(this, value); |
| } else { |
| return this.currentState.getElement(this); |
| } |
| }), |
| |
| /** |
| 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, |
| idx = childViews.length, |
| 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, |
| 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(target) { |
| // Schedule the DOM element to be created and appended to the given |
| // element after bindings have synchronized. |
| this._insertElementLater(function() { |
| Ember.assert("You tried to append to (" + target + ") but that isn't in the DOM", jQuery(target).length > 0); |
| Ember.assert("You cannot append to an existing Ember.View. Consider using Ember.ContainerView instead.", !jQuery(target).is('.ember-view') && !jQuery(target).parents().is('.ember-view')); |
| this.$().appendTo(target); |
| }); |
| |
| 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, |
| `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 |
| |
| @method replaceIn |
| @param {String|DOMElement|jQuery} target A selector, element, HTML string, or jQuery object |
| @return {Ember.View} received |
| */ |
| replaceIn: function(target) { |
| Ember.assert("You tried to replace in (" + target + ") but that isn't in the DOM", jQuery(target).length > 0); |
| Ember.assert("You cannot replace an existing Ember.View. Consider using Ember.ContainerView instead.", !jQuery(target).is('.ember-view') && !jQuery(target).parents().is('.ember-view')); |
| |
| this._insertElementLater(function() { |
| jQuery(target).empty(); |
| this.$().appendTo(target); |
| }); |
| |
| return this; |
| }, |
| |
| /** |
| Schedules a DOM operation to occur during the next render phase. This |
| ensures that all bindings have finished synchronizing before the view is |
| rendered. |
| |
| To use, pass a function that performs a DOM operation. |
| |
| Before your function is called, this view and all child views will receive |
| the `willInsertElement` event. After your function is invoked, this view |
| and all of its child views will receive the `didInsertElement` event. |
| |
| ```javascript |
| view._insertElementLater(function() { |
| this.createElement(); |
| this.$().appendTo('body'); |
| }); |
| ``` |
| |
| @method _insertElementLater |
| @param {Function} fn the function that inserts the element into the DOM |
| @private |
| */ |
| _insertElementLater: function(fn) { |
| this._scheduledInsert = run.scheduleOnce('render', this, '_insertElement', fn); |
| }, |
| |
| _insertElement: function (fn) { |
| this._scheduledInsert = null; |
| this.currentState.insertElement(this, fn); |
| }, |
| |
| /** |
| Appends the view's element to the document body. If the view does |
| not have an HTML representation yet, `createElement()` will be called |
| 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(); |
| } |
| this.invokeRecursively(function(view) { |
| if (view.clearRenderedChildren) { |
| view.clearRenderedChildren(); |
| } |
| }); |
| }, |
| |
| 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 created, `didInsertElement` will |
| be called on this view and all of its child views. |
| |
| @method createElement |
| @return {Ember.View} receiver |
| */ |
| createElement: function() { |
| if (get(this, 'element')) { |
| return this; |
| } |
| |
| var buffer = this.renderToBuffer(); |
| set(this, 'element', buffer.element()); |
| |
| return this; |
| }, |
| |
| /** |
| Called when a view is going to insert an element into the DOM. |
| |
| @event willInsertElement |
| */ |
| willInsertElement: Ember.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. |
| |
| @event didInsertElement |
| */ |
| didInsertElement: Ember.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: Ember.K, |
| |
| /** |
| Run this callback on the current view (unless includeSelf is false) and recursively on child views. |
| |
| @method invokeRecursively |
| @param fn {Function} |
| @param includeSelf {Boolean} Includes itself if true. |
| @private |
| */ |
| invokeRecursively: function(fn, includeSelf) { |
| var childViews = (includeSelf === false) ? this._childViews : [this]; |
| var currentViews, view, currentChildViews; |
| |
| while (childViews.length) { |
| currentViews = childViews.slice(); |
| childViews = []; |
| |
| for (var i = 0, l = currentViews.length; i < l; i++) { |
| view = currentViews[i]; |
| currentChildViews = view._childViews ? view._childViews.slice(0) : null; |
| fn(view); |
| if (currentChildViews) { |
| childViews.push.apply(childViews, currentChildViews); |
| } |
| } |
| } |
| }, |
| |
| triggerRecursively: function(eventName) { |
| var childViews = [this], currentViews, view, currentChildViews; |
| |
| while (childViews.length) { |
| currentViews = childViews.slice(); |
| childViews = []; |
| |
| for (var i = 0, l = currentViews.length; i < l; i++) { |
| view = currentViews[i]; |
| currentChildViews = view._childViews ? view._childViews.slice(0) : null; |
| if (view.trigger) { |
| view.trigger(eventName); |
| } |
| if (currentChildViews) { |
| childViews.push.apply(childViews, currentChildViews); |
| } |
| |
| } |
| } |
| }, |
| |
| viewHierarchyCollection: function() { |
| var currentView, viewCollection = new ViewCollection([this]); |
| |
| for (var i = 0; i < viewCollection.length; i++) { |
| currentView = viewCollection.objectAt(i); |
| if (currentView._childViews) { |
| viewCollection.push.apply(viewCollection, currentView._childViews); |
| } |
| } |
| |
| return viewCollection; |
| }, |
| |
| /** |
| 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. |
| |
| @event willDestroyElement |
| */ |
| willDestroyElement: Ember.K, |
| |
| /** |
| Triggers the `willDestroyElement` event (which invokes the |
| `willDestroyElement()` method if it exists) on this view and all child |
| views. |
| |
| Before triggering `willDestroyElement`, it first triggers the |
| `willClearRender` event recursively. |
| |
| @method _notifyWillDestroyElement |
| @private |
| */ |
| _notifyWillDestroyElement: function() { |
| var viewCollection = this.viewHierarchyCollection(); |
| viewCollection.trigger('willClearRender'); |
| viewCollection.trigger('willDestroyElement'); |
| return viewCollection; |
| }, |
| |
| /** |
| If this view's element changes, we need to invalidate the caches of our |
| child views so that we do not retain references to DOM elements that are |
| no longer needed. |
| |
| @method _elementDidChange |
| @private |
| */ |
| _elementDidChange: observer('element', function() { |
| this.forEachChildView(clearCachedElement); |
| }), |
| |
| /** |
| Called when the parentView property has changed. |
| |
| @event parentViewDidChange |
| */ |
| parentViewDidChange: Ember.K, |
| |
| instrumentName: 'view', |
| |
| instrumentDetails: function(hash) { |
| hash.template = get(this, 'templateName'); |
| this._super(hash); |
| }, |
| |
| _renderToBuffer: function(buffer) { |
| this.lengthBeforeRender = this._childViews.length; |
| buffer = this._super(buffer); |
| this.lengthAfterRender = this._childViews.length; |
| |
| return buffer; |
| }, |
| |
| renderToBufferIfNeeded: function (buffer) { |
| return this.currentState.renderToBufferIfNeeded(this, buffer); |
| }, |
| |
| beforeRender: function(buffer) { |
| this.applyAttributesToBuffer(buffer); |
| buffer.pushOpeningTag(); |
| }, |
| |
| afterRender: function(buffer) { |
| buffer.pushClosingTag(); |
| }, |
| |
| applyAttributesToBuffer: function(buffer) { |
| // Creates observers for all registered class name and attribute bindings, |
| // then adds them to the element. |
| var classNameBindings = get(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 = get(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() { |
| 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(); |
| |
| Ember.assert("Only arrays are allowed for 'classNameBindings'", typeOf(this.classNameBindings) === 'array'); |
| this.classNameBindings = emberA(this.classNameBindings.slice()); |
| |
| Ember.assert("Only arrays are allowed for 'classNames'", 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() { |
| var childViews = this._childViews, |
| // get parentView before calling super because it'll be destroyed |
| nonVirtualParentView = get(this, 'parentView'), |
| viewName = this.viewName, |
| childLen, i; |
| |
| if (!this._super()) { |
| return; |
| } |
| |
| childLen = childViews.length; |
| for (i = childLen-1; i >= 0; i--) { |
| childViews[i].removedFromDOM = true; |
| } |
| |
| // remove from non-virtual parent view if viewName was specified |
| if (viewName && nonVirtualParentView) { |
| nonVirtualParentView.set(viewName, null); |
| } |
| |
| childLen = childViews.length; |
| for (i = childLen-1; i >= 0; i--) { |
| childViews[i].destroy(); |
| } |
| |
| 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.templateData = attrs.templateData || get(this, 'templateData'); |
| |
| 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); |
| |
| attrs.templateData = get(this, 'templateData'); |
| view = ViewKlass.create(attrs); |
| } else { |
| Ember.assert('You must pass instance or subclass of View', view.isView); |
| attrs.container = this.container; |
| |
| if (!get(view, 'templateData')) { |
| attrs.templateData = get(this, 'templateData'); |
| } |
| |
| setProperties(view, attrs); |
| |
| } |
| |
| return view; |
| }, |
| |
| becameVisible: Ember.K, |
| becameHidden: Ember.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.$(); |
| if (!$el) { |
| return; |
| } |
| |
| var isVisible = get(this, 'isVisible'); |
| |
| if (this._isVisible === isVisible) { |
| return ; |
| } |
| |
| $el.toggle(isVisible); |
| |
| this._isVisible = 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; |
| }, |
| |
| clearBuffer: function() { |
| this.invokeRecursively(nullViewsBuffer); |
| }, |
| 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); |
| } |
| if (state === 'inDOM') { |
| meta(this).cache.element = undefined; |
| } |
| |
| if (children !== false) { |
| this.forEachChildView(function(view) { |
| view._transitionTo(state); |
| }); |
| } |
| }, |
| |
| // ....................................................... |
| // 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 view = this, |
| stateCheckedObserver = function() { |
| view.currentState.invokeObserver(this, observer); |
| }, |
| scheduledObserver = function() { |
| run.scheduleOnce('render', this, stateCheckedObserver); |
| }; |
| |
| addObserver(root, path, target, scheduledObserver); |
| |
| this.one('willClearRender', function() { |
| removeObserver(root, path, target, scheduledObserver); |
| }); |
| } |
| |
| }); |
| |
| /* |
| 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. |
| |
| function notifyMutationListeners() { |
| run.once(View, 'notifyMutationListeners'); |
| } |
| |
| var DOMManager = { |
| prepend: function(view, html) { |
| view.$().prepend(html); |
| notifyMutationListeners(); |
| }, |
| |
| after: function(view, html) { |
| view.$().after(html); |
| notifyMutationListeners(); |
| }, |
| |
| html: function(view, html) { |
| view.$().html(html); |
| notifyMutationListeners(); |
| }, |
| |
| replace: function(view) { |
| var element = get(view, 'element'); |
| |
| set(view, 'element', null); |
| |
| view._insertElementLater(function() { |
| jQuery(element).replaceWith(get(view, 'element')); |
| notifyMutationListeners(); |
| }); |
| }, |
| |
| remove: function(view) { |
| view.$().remove(); |
| notifyMutationListeners(); |
| }, |
| |
| empty: function(view) { |
| view.$().empty(); |
| notifyMutationListeners(); |
| } |
| }; |
| |
| View.reopen({ |
| domManager: DOMManager |
| }); |
| |
| View.reopenClass({ |
| |
| /** |
| 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 |
| */ |
| _parsePropertyPath: function(path) { |
| var split = path.split(':'), |
| propertyPath = split[0], |
| classNames = "", |
| 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 |
| }; |
| }, |
| |
| /** |
| 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 |
| */ |
| _classStringForValue: function(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; |
| } |
| } |
| }); |
| |
| var mutation = EmberObject.extend(Evented).create(); |
| |
| 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; |
| |
| View.applyAttributeBindings = function(elem, name, value) { |
| 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; |
| }); |
| define("ember-views/views/view_collection", |
| ["ember-metal/enumerable_utils", "exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| var forEach = __dependency1__.forEach; |
| |
| function ViewCollection(initialViews) { |
| var views = this.views = initialViews || []; |
| this.length = views.length; |
| } |
| |
| ViewCollection.prototype = { |
| length: 0, |
| |
| trigger: function(eventName) { |
| var views = this.views, view; |
| for (var i = 0, l = views.length; i < l; i++) { |
| view = views[i]; |
| if (view.trigger) { |
| view.trigger(eventName); |
| } |
| } |
| }, |
| |
| triggerRecursively: function(eventName) { |
| var views = this.views; |
| for (var i = 0, l = views.length; i < l; i++) { |
| views[i].triggerRecursively(eventName); |
| } |
| }, |
| |
| invokeRecursively: function(fn) { |
| var views = this.views, view; |
| |
| for (var i = 0, l = views.length; i < l; i++) { |
| view = views[i]; |
| fn(view); |
| } |
| }, |
| |
| transitionTo: function(state, children) { |
| var views = this.views; |
| for (var i = 0, l = views.length; i < l; i++) { |
| views[i]._transitionTo(state, children); |
| } |
| }, |
| |
| push: function() { |
| this.length += arguments.length; |
| var views = this.views; |
| return views.push.apply(views, arguments); |
| }, |
| |
| objectAt: function(idx) { |
| return this.views[idx]; |
| }, |
| |
| forEach: function(callback) { |
| var views = this.views; |
| return forEach(views, callback); |
| }, |
| |
| clear: function() { |
| this.length = 0; |
| this.views.length = 0; |
| } |
| }; |
| |
| __exports__["default"] = ViewCollection; |
| }); |
| define("ember", |
| ["ember-metal", "ember-runtime", "ember-handlebars", "ember-views", "ember-routing", "ember-routing-handlebars", "ember-application", "ember-extension-support"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__) { |
| "use strict"; |
| // require the main entry points for each of these packages |
| // this is so that the global exports occur properly |
| |
| // 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'); |
| } |
| |
| /** |
| Ember |
| |
| @module ember |
| */ |
| |
| function throwWithMessage(msg) { |
| return function() { |
| throw new Ember.Error(msg); |
| }; |
| } |
| |
| function generateRemovedClass(className) { |
| var msg = " has been moved into a plugin: https://github.com/emberjs/ember-states"; |
| |
| return { |
| extend: throwWithMessage(className + msg), |
| create: throwWithMessage(className + msg) |
| }; |
| } |
| |
| Ember.StateManager = generateRemovedClass("Ember.StateManager"); |
| |
| /** |
| This was exported to ember-states plugin for v 1.0.0 release. See: https://github.com/emberjs/ember-states |
| |
| @class StateManager |
| @namespace Ember |
| */ |
| |
| Ember.State = generateRemovedClass("Ember.State"); |
| |
| /** |
| This was exported to ember-states plugin for v 1.0.0 release. See: https://github.com/emberjs/ember-states |
| |
| @class State |
| @namespace Ember |
| */ |
| }); |
| define("metamorph", |
| [], |
| function() { |
| "use strict"; |
| // ========================================================================== |
| // Project: metamorph |
| // Copyright: ©2014 Tilde, Inc. All rights reserved. |
| // ========================================================================== |
| |
| var K = function() {}, |
| guid = 0, |
| disableRange = (function() { |
| if ('undefined' !== typeof MetamorphENV) { |
| return MetamorphENV.DISABLE_RANGE_API; |
| } else if ('undefined' !== ENV) { |
| return ENV.DISABLE_RANGE_API; |
| } else { |
| return false; |
| } |
| })(), |
| // Feature-detect the W3C range API, the extended check is for IE9 which only partially supports ranges |
| supportsRange = (!disableRange) && typeof document !== 'undefined' && ('createRange' in document) && (typeof Range !== 'undefined') && Range.prototype.createContextualFragment, |
| // 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 ­ |
| needsShy = typeof document !== 'undefined' && (function() { |
| var testEl = document.createElement('div'); |
| testEl.innerHTML = "<div></div>"; |
| testEl.firstChild.innerHTML = "<script></script>"; |
| return testEl.firstChild.innerHTML === ''; |
| })(), |
| |
| // 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. |
| movesWhitespace = document && (function() { |
| 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'; |
| })(); |
| |
| // Constructor that supports either Metamorph('foo') or new |
| // Metamorph('foo'); |
| // |
| // Takes a string of HTML as the argument. |
| |
| var Metamorph = function(html) { |
| var self; |
| |
| if (this instanceof Metamorph) { |
| self = this; |
| } else { |
| self = new K(); |
| } |
| |
| self.innerHTML = html; |
| var myGuid = 'metamorph-' + (guid++); |
| self.start = myGuid + '-start'; |
| self.end = myGuid + '-end'; |
| |
| return self; |
| }; |
| |
| K.prototype = Metamorph.prototype; |
| |
| var rangeFor, htmlFunc, removeFunc, outerHTMLFunc, appendToFunc, afterFunc, prependFunc, startTagFunc, endTagFunc; |
| |
| outerHTMLFunc = function() { |
| return this.startTag() + this.innerHTML + this.endTag(); |
| }; |
| |
| startTagFunc = function() { |
| /* |
| * We replace chevron by its hex code in order to prevent escaping problems. |
| * Check this thread for more explaination: |
| * http://stackoverflow.com/questions/8231048/why-use-x3c-instead-of-when-generating-html-from-javascript |
| */ |
| return "<script id='" + this.start + "' type='text/x-placeholder'>\x3C/script>"; |
| }; |
| |
| endTagFunc = function() { |
| /* |
| * We replace chevron by its hex code in order to prevent escaping problems. |
| * Check this thread for more explaination: |
| * http://stackoverflow.com/questions/8231048/why-use-x3c-instead-of-when-generating-html-from-javascript |
| */ |
| return "<script id='" + this.end + "' type='text/x-placeholder'>\x3C/script>"; |
| }; |
| |
| // If we have the W3C range API, this process is relatively straight forward. |
| if (supportsRange) { |
| |
| // Get a range for the current morph. Optionally include the starting and |
| // ending placeholders. |
| rangeFor = function(morph, outerToo) { |
| var range = document.createRange(); |
| var before = document.getElementById(morph.start); |
| var after = document.getElementById(morph.end); |
| |
| if (outerToo) { |
| range.setStartBefore(before); |
| range.setEndAfter(after); |
| } else { |
| range.setStartAfter(before); |
| range.setEndBefore(after); |
| } |
| |
| return range; |
| }; |
| |
| htmlFunc = function(html, outerToo) { |
| // get a range for the current metamorph object |
| var range = rangeFor(this, outerToo); |
| |
| // delete the contents of the range, which will be the |
| // nodes between the starting and ending placeholder. |
| range.deleteContents(); |
| |
| // create a new document fragment for the HTML |
| var fragment = range.createContextualFragment(html); |
| |
| // insert the fragment into the range |
| range.insertNode(fragment); |
| }; |
| |
| /** |
| * @public |
| * |
| * Remove this object (including starting and ending |
| * placeholders). |
| * |
| * @method remove |
| */ |
| removeFunc = function() { |
| // get a range for the current metamorph object including |
| // the starting and ending placeholders. |
| var range = rangeFor(this, true); |
| |
| // delete the entire range. |
| range.deleteContents(); |
| }; |
| |
| appendToFunc = function(node) { |
| var range = document.createRange(); |
| range.setStart(node); |
| range.collapse(false); |
| var frag = range.createContextualFragment(this.outerHTML()); |
| node.appendChild(frag); |
| }; |
| |
| afterFunc = function(html) { |
| var range = document.createRange(); |
| var after = document.getElementById(this.end); |
| |
| range.setStartAfter(after); |
| range.setEndAfter(after); |
| |
| var fragment = range.createContextualFragment(html); |
| range.insertNode(fragment); |
| }; |
| |
| prependFunc = function(html) { |
| var range = document.createRange(); |
| var start = document.getElementById(this.start); |
| |
| range.setStartAfter(start); |
| range.setEndAfter(start); |
| |
| var fragment = range.createContextualFragment(html); |
| range.insertNode(fragment); |
| }; |
| |
| } else { |
| /* |
| * This code is mostly taken from jQuery, with one exception. In jQuery's case, we |
| * have some HTML and we need to figure out how to convert it into some nodes. |
| * |
| * In this case, jQuery needs to scan the HTML looking for an opening tag and use |
| * that as the key for the wrap map. In our case, we know the parent node, and |
| * can use its type as the key for the wrap map. |
| **/ |
| Metamorph._wrapMap = { |
| select: [ 1, "<select multiple='multiple'>", "</select>" ], |
| fieldset: [ 1, "<fieldset>", "</fieldset>" ], |
| table: [ 1, "<table>", "</table>" ], |
| tbody: [ 2, "<table><tbody>", "</tbody></table>" ], |
| tr: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ], |
| colgroup: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ], |
| map: [ 1, "<map>", "</map>" ], |
| _default: [ 0, "", "" ] |
| }; |
| |
| var findChildById = function(element, id) { |
| if (element.getAttribute('id') === id) { |
| return element; |
| } |
| |
| var len = element.childNodes.length, idx, node, found; |
| for (idx = 0; idx < len; idx++) { |
| node = element.childNodes[idx]; |
| found = node.nodeType === 1 && findChildById(node, id); |
| if (found) { |
| return found; |
| } |
| } |
| }; |
| |
| var setInnerHTML = function(element, html) { |
| var matches = []; |
| if (movesWhitespace) { |
| // Right now we only check for script tags with ids with the |
| // goal of targeting morphs. |
| html = html.replace(/(\s+)(<script id='([^']+)')/g, function(match, spaces, tag, id) { |
| matches.push([id, spaces]); |
| return tag; |
| }); |
| } |
| |
| element.innerHTML = html; |
| |
| // If we have to do any whitespace adjustments do them now |
| if (matches.length > 0) { |
| var len = matches.length, idx; |
| for (idx = 0; idx < len; idx++) { |
| var script = findChildById(element, matches[idx][0]), |
| node = document.createTextNode(matches[idx][1]); |
| script.parentNode.insertBefore(node, script); |
| } |
| } |
| }; |
| |
| /* |
| * Given a parent node and some HTML, generate a set of nodes. Return the first |
| * node, which will allow us to traverse the rest using nextSibling. |
| * |
| * We need to do this because innerHTML in IE does not really parse the nodes. |
| */ |
| var firstNodeFor = function(parentNode, html) { |
| var wrapMap = Metamorph._wrapMap; |
| var arr = wrapMap[parentNode.tagName.toLowerCase()] || wrapMap._default; |
| var depth = arr[0], start = arr[1], end = arr[2]; |
| |
| if (needsShy) { |
| html = '­' + html; |
| } |
| |
| var element = document.createElement('div'); |
| |
| setInnerHTML(element, start + html + end); |
| |
| for (var i = 0; i <= depth; i++) { |
| element = element.firstChild; |
| } |
| |
| // Look for ­ to remove it. |
| if (needsShy) { |
| var shyElement = element; |
| |
| // Sometimes we get nameless elements with the shy inside |
| 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") { |
| shyElement.nodeValue = shyElement.nodeValue.slice(1); |
| } |
| } |
| |
| return element; |
| }; |
| |
| /* |
| * In some cases, Internet Explorer can create an anonymous node in |
| * the hierarchy with no tagName. You can create this scenario via: |
| * |
| * div = document.createElement("div"); |
| * div.innerHTML = "<table>­<script></script><tr><td>hi</td></tr></table>"; |
| * div.firstChild.firstChild.tagName //=> "" |
| * |
| * If our script markers are inside such a node, we need to find that |
| * node and use *it* as the marker. |
| */ |
| var realNode = function(start) { |
| while (start.parentNode.tagName === "") { |
| start = start.parentNode; |
| } |
| |
| return start; |
| }; |
| |
| /* |
| * When automatically adding a tbody, Internet Explorer inserts the |
| * tbody immediately before the first <tr>. Other browsers create it |
| * before the first node, no matter what. |
| * |
| * This means the the following code: |
| * |
| * div = document.createElement("div"); |
| * div.innerHTML = "<table><script id='first'></script><tr><td>hi</td></tr><script id='last'></script></table> |
| * |
| * Generates the following DOM in IE: |
| * |
| * + div |
| * + table |
| * - script id='first' |
| * + tbody |
| * + tr |
| * + td |
| * - "hi" |
| * - script id='last' |
| * |
| * Which means that the two script tags, even though they were |
| * inserted at the same point in the hierarchy in the original |
| * HTML, now have different parents. |
| * |
| * This code reparents the first script tag by making it the tbody's |
| * first child. |
| * |
| */ |
| var fixParentage = function(start, end) { |
| if (start.parentNode !== end.parentNode) { |
| end.parentNode.insertBefore(start, end.parentNode.firstChild); |
| } |
| }; |
| |
| htmlFunc = function(html, outerToo) { |
| // get the real starting node. see realNode for details. |
| var start = realNode(document.getElementById(this.start)); |
| var end = document.getElementById(this.end); |
| var parentNode = end.parentNode; |
| var node, nextSibling, last; |
| |
| // make sure that the start and end nodes share the same |
| // parent. If not, fix it. |
| fixParentage(start, end); |
| |
| // remove all of the nodes after the starting placeholder and |
| // before the ending placeholder. |
| node = start.nextSibling; |
| while (node) { |
| nextSibling = node.nextSibling; |
| last = node === end; |
| |
| // if this is the last node, and we want to remove it as well, |
| // set the `end` node to the next sibling. This is because |
| // for the rest of the function, we insert the new nodes |
| // before the end (note that insertBefore(node, null) is |
| // the same as appendChild(node)). |
| // |
| // if we do not want to remove it, just break. |
| if (last) { |
| if (outerToo) { |
| end = node.nextSibling; |
| } else { |
| break; |
| } |
| } |
| |
| node.parentNode.removeChild(node); |
| |
| // if this is the last node and we didn't break before |
| // (because we wanted to remove the outer nodes), break |
| // now. |
| if (last) { |
| break; |
| } |
| |
| node = nextSibling; |
| } |
| |
| // get the first node for the HTML string, even in cases like |
| // tables and lists where a simple innerHTML on a div would |
| // swallow some of the content. |
| node = firstNodeFor(start.parentNode, html); |
| |
| if (outerToo) { |
| start.parentNode.removeChild(start); |
| } |
| |
| // copy the nodes for the HTML between the starting and ending |
| // placeholder. |
| while (node) { |
| nextSibling = node.nextSibling; |
| parentNode.insertBefore(node, end); |
| node = nextSibling; |
| } |
| }; |
| |
| // remove the nodes in the DOM representing this metamorph. |
| // |
| // this includes the starting and ending placeholders. |
| removeFunc = function() { |
| var start = realNode(document.getElementById(this.start)); |
| var end = document.getElementById(this.end); |
| |
| this.html(''); |
| start.parentNode.removeChild(start); |
| end.parentNode.removeChild(end); |
| }; |
| |
| appendToFunc = function(parentNode) { |
| var node = firstNodeFor(parentNode, this.outerHTML()); |
| var nextSibling; |
| |
| while (node) { |
| nextSibling = node.nextSibling; |
| parentNode.appendChild(node); |
| node = nextSibling; |
| } |
| }; |
| |
| afterFunc = function(html) { |
| // get the real starting node. see realNode for details. |
| var end = document.getElementById(this.end); |
| var insertBefore = end.nextSibling; |
| var parentNode = end.parentNode; |
| var nextSibling; |
| var node; |
| |
| // get the first node for the HTML string, even in cases like |
| // tables and lists where a simple innerHTML on a div would |
| // swallow some of the content. |
| node = firstNodeFor(parentNode, html); |
| |
| // copy the nodes for the HTML between the starting and ending |
| // placeholder. |
| while (node) { |
| nextSibling = node.nextSibling; |
| parentNode.insertBefore(node, insertBefore); |
| node = nextSibling; |
| } |
| }; |
| |
| prependFunc = function(html) { |
| var start = document.getElementById(this.start); |
| var parentNode = start.parentNode; |
| var nextSibling; |
| var node; |
| |
| node = firstNodeFor(parentNode, html); |
| var insertBefore = start.nextSibling; |
| |
| while (node) { |
| nextSibling = node.nextSibling; |
| parentNode.insertBefore(node, insertBefore); |
| node = nextSibling; |
| } |
| }; |
| } |
| |
| Metamorph.prototype.html = function(html) { |
| this.checkRemoved(); |
| if (html === undefined) { |
| return this.innerHTML; |
| } |
| |
| htmlFunc.call(this, html); |
| |
| this.innerHTML = html; |
| }; |
| |
| Metamorph.prototype.replaceWith = function(html) { |
| this.checkRemoved(); |
| htmlFunc.call(this, html, true); |
| }; |
| |
| Metamorph.prototype.remove = removeFunc; |
| Metamorph.prototype.outerHTML = outerHTMLFunc; |
| Metamorph.prototype.appendTo = appendToFunc; |
| Metamorph.prototype.after = afterFunc; |
| Metamorph.prototype.prepend = prependFunc; |
| Metamorph.prototype.startTag = startTagFunc; |
| Metamorph.prototype.endTag = endTagFunc; |
| |
| Metamorph.prototype.isRemoved = function() { |
| var before = document.getElementById(this.start); |
| var after = document.getElementById(this.end); |
| |
| return !before || !after; |
| }; |
| |
| Metamorph.prototype.checkRemoved = function() { |
| if (this.isRemoved()) { |
| throw new Error("Cannot perform operations on a Metamorph that is not in the DOM."); |
| } |
| }; |
| |
| return Metamorph; |
| }); |
| |
| define("route-recognizer", |
| ["exports"], |
| function(__exports__) { |
| "use strict"; |
| var specials = [ |
| '/', '.', '*', '+', '?', '|', |
| '(', ')', '[', ']', '{', '}', '\\' |
| ]; |
| |
| var escapeRegex = new RegExp('(\\' + specials.join('|\\') + ')', 'g'); |
| |
| function isArray(test) { |
| return Object.prototype.toString.call(test) === "[object Array]"; |
| } |
| |
| // A Segment represents a segment in the original route description. |
| // Each Segment type provides an `eachChar` and `regex` method. |
| // |
| // The `eachChar` method invokes the callback with one or more character |
| // specifications. A character specification consumes one or more input |
| // characters. |
| // |
| // The `regex` method returns a regex fragment for the segment. If the |
| // segment is a dynamic of star segment, the regex fragment also includes |
| // a capture. |
| // |
| // A character specification contains: |
| // |
| // * `validChars`: a String with a list of all valid characters, or |
| // * `invalidChars`: a String with a list of all invalid characters |
| // * `repeat`: true if the character specification can repeat |
| |
| function StaticSegment(string) { |
| this.string = string; |
| } |
| StaticSegment.prototype = { |
| eachChar: function(callback) { |
| var string = this.string, ch; |
| |
| for (var i = 0, l = string.length; i < l; i++) { |
| ch = string.charAt(i); |
| callback({ |
| validChars: ch |
| }); |
| } |
| }, |
| |
| regex: function() { |
| return this.string.replace(escapeRegex, '\\$1'); |
| }, |
| |
| generate: function() { |
| return this.string; |
| } |
| }; |
| |
| function DynamicSegment(name) { |
| this.name = name; |
| } |
| DynamicSegment.prototype = { |
| eachChar: function(callback) { |
| callback({ |
| invalidChars: "/", |
| repeat: true |
| }); |
| }, |
| |
| regex: function() { |
| return "([^/]+)"; |
| }, |
| |
| generate: function(params) { |
| return params[this.name]; |
| } |
| }; |
| |
| function StarSegment(name) { |
| this.name = name; |
| } |
| StarSegment.prototype = { |
| eachChar: function(callback) { |
| callback({ |
| invalidChars: "", |
| repeat: true |
| }); |
| }, |
| |
| regex: function() { |
| return "(.+)"; |
| }, |
| |
| generate: function(params) { |
| return params[this.name]; |
| } |
| }; |
| |
| function EpsilonSegment() {} |
| EpsilonSegment.prototype = { |
| eachChar: function() {}, |
| regex: function() { |
| return ""; |
| }, |
| generate: function() { |
| return ""; |
| } |
| }; |
| |
| function parse(route, names, types) { |
| // normalize route as not starting with a "/". Recognition will |
| // also normalize. |
| if (route.charAt(0) === "/") { |
| route = route.substr(1); |
| } |
| |
| var segments = route.split("/"), results = []; |
| |
| for (var i = 0, l = segments.length; i < l; i++) { |
| var segment = segments[i], match; |
| |
| if (match = segment.match(/^:([^\/]+)$/)) { |
| results.push(new DynamicSegment(match[1])); |
| names.push(match[1]); |
| types.dynamics++; |
| } else if (match = segment.match(/^\*([^\/]+)$/)) { |
| results.push(new StarSegment(match[1])); |
| names.push(match[1]); |
| types.stars++; |
| } else if (segment === "") { |
| results.push(new EpsilonSegment()); |
| } else { |
| results.push(new StaticSegment(segment)); |
| types.statics++; |
| } |
| } |
| |
| return results; |
| } |
| |
| // A State has a character specification and (`charSpec`) and a list of possible |
| // subsequent states (`nextStates`). |
| // |
| // If a State is an accepting state, it will also have several additional |
| // properties: |
| // |
| // * `regex`: A regular expression that is used to extract parameters from paths |
| // that reached this accepting state. |
| // * `handlers`: Information on how to convert the list of captures into calls |
| // to registered handlers with the specified parameters |
| // * `types`: How many static, dynamic or star segments in this route. Used to |
| // decide which route to use if multiple registered routes match a path. |
| // |
| // Currently, State is implemented naively by looping over `nextStates` and |
| // comparing a character specification against a character. A more efficient |
| // implementation would use a hash of keys pointing at one or more next states. |
| |
| function State(charSpec) { |
| this.charSpec = charSpec; |
| this.nextStates = []; |
| } |
| |
| State.prototype = { |
| get: function(charSpec) { |
| var nextStates = this.nextStates; |
| |
| for (var i = 0, l = nextStates.length; i < l; i++) { |
| var child = nextStates[i]; |
| |
| var isEqual = child.charSpec.validChars === charSpec.validChars; |
| isEqual = isEqual && child.charSpec.invalidChars === charSpec.invalidChars; |
| |
| if (isEqual) { |
| return child; |
| } |
| } |
| }, |
| |
| put: function(charSpec) { |
| var state; |
| |
| // If the character specification already exists in a child of the current |
| // state, just return that state. |
| if (state = this.get(charSpec)) { |
| return state; |
| } |
| |
| // Make a new state for the character spec |
| state = new State(charSpec); |
| |
| // Insert the new state as a child of the current state |
| this.nextStates.push(state); |
| |
| // If this character specification repeats, insert the new state as a child |
| // of itself. Note that this will not trigger an infinite loop because each |
| // transition during recognition consumes a character. |
| if (charSpec.repeat) { |
| state.nextStates.push(state); |
| } |
| |
| // Return the new state |
| return state; |
| }, |
| |
| // Find a list of child states matching the next character |
| match: function(ch) { |
| // DEBUG "Processing `" + ch + "`:" |
| var nextStates = this.nextStates, |
| child, charSpec, chars; |
| |
| // DEBUG " " + debugState(this) |
| var returned = []; |
| |
| for (var i = 0, l = nextStates.length; i < l; i++) { |
| child = nextStates[i]; |
| |
| charSpec = child.charSpec; |
| |
| if (typeof (chars = charSpec.validChars) !== 'undefined') { |
| if (chars.indexOf(ch) !== -1) { |
| returned.push(child); |
| } |
| } else if (typeof (chars = charSpec.invalidChars) !== 'undefined') { |
| if (chars.indexOf(ch) === -1) { |
| returned.push(child); |
| } |
| } |
| } |
| |
| return returned; |
| } |
| |
| /** IF DEBUG |
| , debug: function() { |
| var charSpec = this.charSpec, |
| debug = "[", |
| chars = charSpec.validChars || charSpec.invalidChars; |
| |
| if (charSpec.invalidChars) { debug += "^"; } |
| debug += chars; |
| debug += "]"; |
| |
| if (charSpec.repeat) { debug += "+"; } |
| |
| return debug; |
| } |
| END IF **/ |
| }; |
| |
| /** IF DEBUG |
| function debug(log) { |
| console.log(log); |
| } |
| |
| function debugState(state) { |
| return state.nextStates.map(function(n) { |
| if (n.nextStates.length === 0) { return "( " + n.debug() + " [accepting] )"; } |
| return "( " + n.debug() + " <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; |
| } |
| |
| // 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 = 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 = decodeURIComponent(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] ? decodeURIComponent(pair[1]) : ''; |
| } |
| if (isArray) { |
| queryParams[key].push(value); |
| } else { |
| queryParams[key] = decodeURIComponent(value); |
| } |
| } |
| return queryParams; |
| }, |
| |
| recognize: function(path) { |
| var states = [ this.rootState ], |
| pathLen, i, l, queryStart, queryParams = {}, |
| isSlashDropped = false; |
| |
| path = decodeURI(path); |
| |
| queryStart = path.indexOf('?'); |
| if (queryStart !== -1) { |
| var queryString = path.substr(queryStart + 1, path.length); |
| path = path.substr(0, queryStart); |
| queryParams = this.parseQueryString(queryString); |
| } |
| |
| // 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); |
| } |
| } |
| }; |
| |
| __exports__["default"] = RouteRecognizer; |
| |
| 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); |
| } |
| } |
| } |
| } |
| |
| RouteRecognizer.prototype.map = 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); |
| }; |
| }); |
| |
| define("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 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 handler = this.handler; |
| var result = handler[hookName] && handler[hookName].apply(handler, args); |
| |
| if (result && result.isTransition) { |
| result = null; |
| } |
| |
| return Promise.resolve(result, null, 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; |
| }); |
| define("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; |
| }); |
| define("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; |
| }); |
| define("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; |
| }); |
| define("router/handler-info/unresolved-handler-info-by-param", |
| ["../handler-info", "router/utils", "exports"], |
| function(__dependency1__, __dependency2__, __exports__) { |
| "use strict"; |
| var HandlerInfo = __dependency1__["default"]; |
| 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 hookName = typeof this.handler.deserialize === 'function' ? |
| 'deserialize' : 'model'; |
| |
| return this.runSharedModelHook(payload, hookName, [fullParams]); |
| } |
| }); |
| |
| __exports__["default"] = UnresolvedHandlerInfoByParam; |
| }); |
| define("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 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(); |
| } |
| |
| 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) { |
| |
| var wasTransitioning = !!this.activeTransition; |
| var oldState = wasTransitioning ? this.activeTransition.state : this.state; |
| var newTransition; |
| var router = this; |
| |
| try { |
| 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; |
| } 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, function(handlerInfo) { |
| var handler = handlerInfo.handler; |
| if (handler.exit) { |
| 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) { |
| 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; |
| if (handler.reset) { |
| handler.reset(true, transition); |
| } |
| if (handler.exit) { |
| 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; |
| if (handler.reset) { |
| 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 && handler.enter) { |
| handler.enter(transition); |
| } |
| if (transition && transition.isAborted) { |
| throw new TransitionAborted(); |
| } |
| |
| handler.context = context; |
| if (handler.contextDidChange) { |
| handler.contextDidChange(); |
| } |
| |
| if (handler.setup) { |
| 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, 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, oldHandler, newHandler; |
| |
| for (i = 0; i < oldHandlers.length; 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, oldHandlers.length); |
| leavingChecker = function(name) { |
| for (var h = 0; h < leaving.length; 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; |
| }); |
| define("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; |
| }); |
| define("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; |
| 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; i < handlers.length; ++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 |
| }); |
| } |
| }); |
| }); |
| define("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"; |
| } |
| }); |
| define("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 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(), 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); |
| }, 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; |
| if (handler && handler.redirect) { |
| 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, 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, promiseLabel('Proceed')); |
| } |
| } |
| }; |
| |
| __exports__["default"] = TransitionState; |
| }); |
| define("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); |
| 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[state.handlerInfos.length-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} success |
| @param {Function} failure |
| */ |
| then: function(success, failure) { |
| return this.promise.then(success, failure); |
| }, |
| |
| /** |
| @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; |
| }); |
| define("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; |
| __exports__.merge = merge; |
| __exports__.slice = slice; |
| __exports__.isParam = isParam; |
| __exports__.coerceQueryParamsToString = coerceQueryParamsToString; |
| }); |
| define("router", |
| ["./router/router", "exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| var Router = __dependency1__["default"]; |
| |
| __exports__["default"] = Router; |
| }); |
| /** |
| @class RSVP |
| @module RSVP |
| */ |
| define('rsvp/-internal', [ |
| './utils', |
| './instrument', |
| './config', |
| 'exports' |
| ], function (__dependency1__, __dependency2__, __dependency3__, __exports__) { |
| 'use strict'; |
| var objectOrFunction = __dependency1__.objectOrFunction; |
| var isFunction = __dependency1__.isFunction; |
| var now = __dependency1__.now; |
| var instrument = __dependency2__['default']; |
| var config = __dependency3__.config; |
| 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$2) { |
| var sealed = false; |
| var error = tryThen(then, thenable, function (value) { |
| if (sealed) { |
| return; |
| } |
| sealed = true; |
| if (thenable !== value) { |
| resolve(promise$2, value); |
| } else { |
| fulfill(promise$2, value); |
| } |
| }, function (reason) { |
| if (sealed) { |
| return; |
| } |
| sealed = true; |
| reject(promise$2, reason); |
| }, 'Settle: ' + (promise$2._label || ' unknown promise')); |
| if (!sealed && error) { |
| sealed = true; |
| reject(promise$2, error); |
| } |
| }, promise); |
| } |
| function handleOwnThenable(promise, thenable) { |
| promise._onerror = null; |
| 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 instanceof 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, new TypeError('A promises callback cannot return that same promise.')); |
| 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; |
| }); |
| define('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, 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; |
| }; |
| }); |
| define('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); |
| }; |
| }); |
| define('rsvp/asap', ['exports'], function (__exports__) { |
| 'use strict'; |
| var length = 0; |
| __exports__['default'] = function asap(callback, arg) { |
| queue[length] = callback; |
| queue[length + 1] = arg; |
| length += 2; |
| if (length === 2) { |
| // If length 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 browserGlobal = typeof window !== 'undefined' ? window : {}; |
| 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); |
| }; |
| } |
| 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 < length; i += 2) { |
| var callback = queue[i]; |
| var arg = queue[i + 1]; |
| callback(arg); |
| queue[i] = undefined; |
| queue[i + 1] = undefined; |
| } |
| length = 0; |
| } |
| 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 { |
| scheduleFlush = useSetTimeout(); |
| } |
| }); |
| define('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; |
| }); |
| define('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; |
| }; |
| }); |
| define('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; |
| var ABORT_ON_REJECTION = true; |
| __exports__.ABORT_ON_REJECTION = ABORT_ON_REJECTION; |
| 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); |
| }); |
| }; |
| }); |
| define('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'] = { |
| mixin: function (object) { |
| object.on = this.on; |
| object.off = this.off; |
| object.trigger = this.trigger; |
| object._promiseCallbacks = undefined; |
| return object; |
| }, |
| 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); |
| } |
| }, |
| 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); |
| } |
| }, |
| trigger: function (eventName, options) { |
| var allCallbacks = callbacksFor(this), callbacks, callbackTuple, callback, binding; |
| 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); |
| } |
| } |
| } |
| }; |
| }); |
| define('rsvp/filter', [ |
| './promise', |
| './utils', |
| 'exports' |
| ], function (__dependency1__, __dependency2__, __exports__) { |
| 'use strict'; |
| var Promise = __dependency1__['default']; |
| var isFunction = __dependency2__.isFunction; |
| var isMaybeThenable = __dependency2__.isMaybeThenable; |
| /** |
| `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$2) { |
| var results = new Array(length); |
| var newLength = 0; |
| for (var i$2 = 0; i$2 < length; i$2++) { |
| if (filtered$2[i$2]) { |
| results[newLength] = values[i$2]; |
| newLength++; |
| } |
| } |
| results.length = newLength; |
| return results; |
| }); |
| }); |
| }; |
| }); |
| define('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; |
| }; |
| }); |
| define('rsvp/hash', [ |
| './promise', |
| './promise-hash', |
| './enumerator', |
| 'exports' |
| ], function (__dependency1__, __dependency2__, __dependency3__, __exports__) { |
| 'use strict'; |
| var Promise = __dependency1__['default']; |
| var PromiseHash = __dependency2__['default']; |
| var ABORT_ON_REJECTION = __dependency3__.ABORT_ON_REJECTION; |
| /** |
| `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; |
| }; |
| }); |
| define('rsvp/instrument', [ |
| './config', |
| './utils', |
| 'exports' |
| ], function (__dependency1__, __dependency2__, __exports__) { |
| 'use strict'; |
| var config = __dependency1__.config; |
| var now = __dependency2__.now; |
| var queue = []; |
| __exports__['default'] = function instrument(eventName, promise, child) { |
| if (1 === queue.push({ |
| name: eventName, |
| payload: { |
| guid: promise._guidKey + promise._id, |
| eventName: eventName, |
| detail: promise._result, |
| childGuid: child && promise._guidKey + child._id, |
| label: promise._label, |
| timeStamp: now(), |
| stack: new Error(promise._label).stack |
| } |
| })) { |
| setTimeout(function () { |
| var entry; |
| for (var i = 0; i < queue.length; i++) { |
| entry = queue[i]; |
| config.trigger(entry.name, entry.payload); |
| } |
| queue.length = 0; |
| }, 50); |
| } |
| }; |
| }); |
| define('rsvp/map', [ |
| './promise', |
| './utils', |
| 'exports' |
| ], function (__dependency1__, __dependency2__, __exports__) { |
| 'use strict'; |
| var Promise = __dependency1__['default']; |
| var isArray = __dependency2__.isArray; |
| 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); |
| }); |
| }; |
| }); |
| define('rsvp/node', [ |
| './promise', |
| './utils', |
| 'exports' |
| ], function (__dependency1__, __dependency2__, __exports__) { |
| 'use strict'; |
| /* global arraySlice */ |
| var Promise = __dependency1__['default']; |
| var isArray = __dependency2__.isArray; |
| /** |
| `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, argumentNames) { |
| var asArray = argumentNames === true; |
| var asHash = isArray(argumentNames); |
| function denodeifiedFunction() { |
| var length = arguments.length; |
| var nodeArgs = new Array(length); |
| for (var i = 0; i < length; i++) { |
| nodeArgs[i] = arguments[i]; |
| } |
| var thisArg; |
| if (!asArray && !asHash && argumentNames) { |
| if (typeof console === 'object') { |
| console.warn('Deprecation: RSVP.denodeify() doesn\'t allow setting the ' + '"this" binding anymore. Use yourFunction.bind(yourThis) instead.'); |
| } |
| thisArg = argumentNames; |
| } else { |
| thisArg = this; |
| } |
| return Promise.all(nodeArgs).then(function (nodeArgs$2) { |
| return new Promise(resolver); |
| // sweet.js has a bug, this resolver can't be defined in the constructor |
| // or the arraySlice macro doesn't work |
| function resolver(resolve, reject) { |
| function callback() { |
| var length$2 = arguments.length; |
| var args = new Array(length$2); |
| for (var i$2 = 0; i$2 < length$2; i$2++) { |
| args[i$2] = arguments[i$2]; |
| } |
| var error = args[0]; |
| var value = args[1]; |
| if (error) { |
| reject(error); |
| } else if (asArray) { |
| resolve(args.slice(1)); |
| } else if (asHash) { |
| var obj = {}; |
| var successArguments = args.slice(1); |
| var name; |
| var i$3; |
| for (i$3 = 0; i$3 < argumentNames.length; i$3++) { |
| name = argumentNames[i$3]; |
| obj[name] = successArguments[i$3]; |
| } |
| resolve(obj); |
| } else { |
| resolve(value); |
| } |
| } |
| nodeArgs$2.push(callback); |
| nodeFunc.apply(thisArg, nodeArgs$2); |
| } |
| }); |
| } |
| denodeifiedFunction.__proto__ = nodeFunc; |
| return denodeifiedFunction; |
| }; |
| }); |
| define('rsvp/promise-hash', [ |
| './enumerator', |
| './-internal', |
| './utils', |
| 'exports' |
| ], function (__dependency1__, __dependency2__, __dependency3__, __exports__) { |
| 'use strict'; |
| var Enumerator = __dependency1__['default']; |
| var PENDING = __dependency2__.PENDING; |
| var FULFILLED = __dependency2__.FULFILLED; |
| 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); |
| } |
| }; |
| }); |
| define('rsvp/promise', [ |
| './config', |
| './events', |
| './instrument', |
| './utils', |
| './-internal', |
| './promise/cast', |
| './promise/all', |
| './promise/race', |
| './promise/resolve', |
| './promise/reject', |
| 'exports' |
| ], function (__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __exports__) { |
| 'use strict'; |
| var config = __dependency1__.config; |
| var EventTarget = __dependency2__['default']; |
| var instrument = __dependency3__['default']; |
| var objectOrFunction = __dependency4__.objectOrFunction; |
| var isFunction = __dependency4__.isFunction; |
| var now = __dependency4__.now; |
| var noop = __dependency5__.noop; |
| var resolve = __dependency5__.resolve; |
| var reject = __dependency5__.reject; |
| var fulfill = __dependency5__.fulfill; |
| var subscribe = __dependency5__.subscribe; |
| var initializePromise = __dependency5__.initializePromise; |
| var invokeCallback = __dependency5__.invokeCallback; |
| var FULFILLED = __dependency5__.FULFILLED; |
| var cast = __dependency6__['default']; |
| var all = __dependency7__['default']; |
| var race = __dependency8__['default']; |
| var Resolve = __dependency9__['default']; |
| var Reject = __dependency10__['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._subscribers = []; |
| if (config.instrument) { |
| instrument('created', this); |
| } |
| if (noop !== resolver) { |
| if (!isFunction(resolver)) { |
| needsResolver(); |
| } |
| if (!(this instanceof Promise)) { |
| needsNew(); |
| } |
| initializePromise(this, resolver); |
| } |
| } |
| Promise.cast = cast; |
| Promise.all = all; |
| Promise.race = race; |
| Promise.resolve = Resolve; |
| Promise.reject = Reject; |
| Promise.prototype = { |
| constructor: Promise, |
| _id: undefined, |
| _guidKey: guidKey, |
| _label: undefined, |
| _state: undefined, |
| _result: undefined, |
| _subscribers: undefined, |
| _onerror: function (reason) { |
| config.trigger('error', reason); |
| }, |
| then: function (onFulfillment, onRejection, label) { |
| var parent = this; |
| parent._onerror = null; |
| var child = new this.constructor(noop, label); |
| var state = parent._state; |
| var result = parent._result; |
| if (config.instrument) { |
| instrument('chained', parent, child); |
| } |
| if (state === FULFILLED && onFulfillment) { |
| config.async(function () { |
| invokeCallback(state, child, onFulfillment, result); |
| }); |
| } else { |
| subscribe(parent, child, onFulfillment, onRejection); |
| } |
| return child; |
| }, |
| 'catch': function (onRejection, label) { |
| return this.then(null, onRejection, label); |
| }, |
| '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); |
| } |
| }; |
| }); |
| define('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, label).promise; |
| }; |
| }); |
| define('rsvp/promise/cast', [ |
| './resolve', |
| 'exports' |
| ], function (__dependency1__, __exports__) { |
| 'use strict'; |
| var resolve = __dependency1__['default']; |
| /** |
| @deprecated |
| |
| `RSVP.Promise.cast` coerces its argument to a promise, or returns the |
| argument if it is already a promise which shares a constructor with the caster. |
| |
| Example: |
| |
| ```javascript |
| var promise = RSVP.Promise.resolve(1); |
| var casted = RSVP.Promise.cast(promise); |
| |
| console.log(promise === casted); // true |
| ``` |
| |
| In the case of a promise whose constructor does not match, it is assimilated. |
| The resulting promise will fulfill or reject based on the outcome of the |
| promise being casted. |
| |
| Example: |
| |
| ```javascript |
| var thennable = $.getJSON('/api/foo'); |
| var casted = RSVP.Promise.cast(thennable); |
| |
| console.log(thennable === casted); // false |
| console.log(casted instanceof RSVP.Promise) // true |
| |
| casted.then(function(data) { |
| // data is the value getJSON fulfills with |
| }); |
| ``` |
| |
| In the case of a non-promise, a promise which will fulfill with that value is |
| returned. |
| |
| Example: |
| |
| ```javascript |
| var value = 1; // could be a number, boolean, string, undefined... |
| var casted = RSVP.Promise.cast(value); |
| |
| console.log(value === casted); // false |
| console.log(casted instanceof RSVP.Promise) // true |
| |
| casted.then(function(val) { |
| val === value // => true |
| }); |
| ``` |
| |
| `RSVP.Promise.cast` is similar to `RSVP.Promise.resolve`, but `RSVP.Promise.cast` differs in the |
| following ways: |
| |
| * `RSVP.Promise.cast` serves as a memory-efficient way of getting a promise, when you |
| have something that could either be a promise or a value. RSVP.resolve |
| will have the same effect but will create a new promise wrapper if the |
| argument is a promise. |
| * `RSVP.Promise.cast` is a way of casting incoming thenables or promise subclasses to |
| promises of the exact class specified, so that the resulting object's `then` is |
| ensured to have the behavior of the constructor you are calling cast on (i.e., RSVP.Promise). |
| |
| @method cast |
| @static |
| @param {Object} object to be casted |
| @param {String} label optional string for labeling the promise. |
| Useful for tooling. |
| @return {Promise} promise |
| */ |
| __exports__['default'] = resolve; |
| }); |
| define('rsvp/promise/race', [ |
| '../utils', |
| '../-internal', |
| 'exports' |
| ], function (__dependency1__, __dependency2__, __exports__) { |
| 'use strict'; |
| var isArray = __dependency1__.isArray; |
| var isFunction = __dependency1__.isFunction; |
| var isMaybeThenable = __dependency1__.isMaybeThenable; |
| 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, entry; |
| 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; |
| }; |
| }); |
| define('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; |
| }; |
| }); |
| define('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; |
| }; |
| }); |
| define('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); |
| }; |
| }); |
| define('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); |
| }; |
| }); |
| define('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); |
| }; |
| }); |
| define('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; |
| }; |
| }); |
| define('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; |
| var o_create = Object.create || function (object) { |
| var o = function () {}; |
| o.prototype = object; |
| return o; |
| }; |
| __exports__.o_create = o_create; |
| }); |
| define('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; |
| 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__.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; |
| }); |
| |
| requireModule("ember"); |
| |
| })(); |