dist/ember.js in ember-source-1.3.0.beta.2 vs dist/ember.js in ember-source-1.3.0.beta.3

- old
+ new

@@ -6,11 +6,11 @@ // License: Licensed under MIT license // See https://raw.github.com/emberjs/ember.js/master/LICENSE // ========================================================================== - // Version: 1.3.0-beta.2 + // Version: 1.3.0-beta.3 (function() { /*global __fail__*/ /** @@ -192,48 +192,64 @@ // License: Licensed under MIT license // See https://raw.github.com/emberjs/ember.js/master/LICENSE // ========================================================================== - // Version: 1.3.0-beta.2 + // Version: 1.3.0-beta.3 (function() { -var define, requireModule; +var define, requireModule, require, requirejs; (function() { var registry = {}, seen = {}; define = function(name, deps, callback) { registry[name] = { deps: deps, callback: callback }; }; - requireModule = function(name) { + requirejs = require = requireModule = function(name) { + requirejs._eak_seen = registry; + if (seen[name]) { return seen[name]; } seen[name] = {}; - var mod, deps, callback, reified, exports; - - mod = registry[name]; - - if (!mod) { - throw new Error("Module '" + name + "' not found."); + if (!registry[name]) { + throw new Error("Could not find module " + name); } - deps = mod.deps; - callback = mod.callback; - reified = []; + 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(deps[i])); + 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("/"); + } }; })(); (function() { /*globals Em:true ENV */ @@ -257,11 +273,11 @@ The core Runtime framework is based on the jQuery API with a number of performance optimizations. @class Ember @static - @version 1.3.0-beta.2 + @version 1.3.0-beta.3 */ 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. @@ -284,14 +300,14 @@ /** @property VERSION @type String - @default '1.3.0-beta.2' + @default '1.3.0-beta.3' @final */ -Ember.VERSION = '1.3.0-beta.2'; +Ember.VERSION = '1.3.0-beta.3'; /** Standard environmental variables. You can define these in a global `ENV` variable before loading Ember to control various configuration settings. @@ -793,10 +809,20 @@ @constructor */ Ember.Error = function() { 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]]; } }; @@ -2197,11 +2223,11 @@ /** @private Suspends multiple listeners during a callback. - + @method suspendListeners @for Ember @param obj @param {Array} eventName Array of event names @param {Object|Function} targetOrMethod A target object or a function @@ -2265,11 +2291,11 @@ /** 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. @@ -2344,18 +2370,21 @@ /** Define a property as a function that should be executed when a specified event or events are triggered. - 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!" + ``` 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 @@ -2789,25 +2818,25 @@ // get the last part of the path keyName = path.slice(path.lastIndexOf('.') + 1); // get the first part of the part - path = path.slice(0, path.length-(keyName.length+1)); + 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 Ember.Error('You passed an empty path'); + throw new Ember.Error('Property set failed: You passed an empty path'); } if (!root) { if (tolerant) { return; } - else { throw new Ember.Error('Object in path '+path+' could not be found or was destroyed.'); } + else { throw new Ember.Error('Property set failed: object in path "'+path+'" could not be found or was destroyed.'); } } return set(root, keyName, value); } @@ -3213,11 +3242,11 @@ (function() { function consoleMethod(name) { - var consoleObj; + var consoleObj, logToConsole; if (Ember.imports.console) { consoleObj = Ember.imports.console; } else if (typeof console !== 'undefined') { consoleObj = console; } @@ -3225,13 +3254,15 @@ var method = typeof consoleObj === 'object' ? consoleObj[name] : null; if (method) { // Older IE doesn't support apply, but Chrome needs it if (method.apply) { - return 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); }; @@ -3272,10 +3303,11 @@ @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 @@ -3285,12 +3317,13 @@ @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 race. + 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. ``` @@ -3298,10 +3331,11 @@ @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 @@ -3312,10 +3346,11 @@ @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 @@ -3326,14 +3361,14 @@ @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. - 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. ``` @@ -4373,15 +4408,15 @@ /** 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 - MyApp.outsideService = Ember.Object.create({ + MyApp.outsideService = Ember.Object.extend({ value: function() { return OutsideService.getValue(); }.property().volatile() - }); + }).create(); ``` @method volatile @return {Ember.ComputedProperty} this @chainable @@ -4393,16 +4428,18 @@ /** 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 - MyApp.person = Ember.Object.create({ + MyApp.Person = Ember.Object.extend({ guid: function() { return 'guid-guid-guid'; }.property().readOnly() }); + MyApp.person = MyApp.Person.create(); + MyApp.person.set('guid', 'new-guid'); // will throw an exception ``` @method readOnly @return {Ember.ComputedProperty} this @@ -4416,18 +4453,24 @@ /** Sets the dependent keys on this computed property. Pass any number of arguments containing key paths that this computed property depends on. ```javascript - MyApp.president = Ember.Object.create({ + MyApp.President = Ember.Object.extend({ fullName: Ember.computed(function() { return this.get('firstName') + ' ' + this.get('lastName'); // Tell Ember that this computed property depends on firstName // and lastName }).property('firstName', 'lastName') }); + + MyApp.president = MyApp.President.create({ + firstName: 'Barack', + lastName: 'Obama', + }); + MyApp.president.get('fullName'); // Barack Obama ``` @method property @param {String} path* zero or more property paths @return {Ember.ComputedProperty} this @@ -6112,18 +6155,20 @@ Please note: This is not for normal usage, and should be used sparingly. If invoked when not within a run loop: + ```javascript Ember.run.join(function() { // creates a new run-loop }); ``` Alternatively, if called within an existing run loop: + ```javascript Ember.run(function() { // creates a new run-loop Ember.run.join(function() { // joins with the existing run-loop, and queues for invocation on @@ -7886,20 +7931,71 @@ */ })(); (function() { +/** + @class RSVP + @module RSVP + */ define("rsvp/all", - ["rsvp/promise","exports"], - function(__dependency1__, __exports__) { + ["./promise","./utils","exports"], + function(__dependency1__, __dependency2__, __exports__) { "use strict"; - var Promise = __dependency1__.Promise; /* global toString */ + var Promise = __dependency1__.Promise; + var isArray = __dependency2__.isArray; + var isFunction = __dependency2__.isFunction; - function all(promises) { - if (Object.prototype.toString.call(promises) !== "[object Array]") { + /** + Returns a promise that is fulfilled when all the given promises have been + fulfilled, or rejected if any of them become rejected. The return promise + is fulfilled with an array that gives all the values in the order they were + passed in the `promises` array argument. + + Example: + + ```javascript + var promise1 = RSVP.resolve(1); + var promise2 = RSVP.resolve(2); + var promise3 = RSVP.resolve(3); + var promises = [ promise1, promise2, promise3 ]; + + RSVP.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.all(promises).then(function(array){ + // Code here never runs because there are rejected promises! + }, function(error) { + // error.message === "2" + }); + ``` + + @method all + @for RSVP + @param {Array} promises + @param {String} label + @return {Promise} promise that is fulfilled when all `promises` have been + fulfilled, or rejected if any of them become rejected. + */ + function all(promises, label) { + if (!isArray(promises)) { throw new TypeError('You must pass an array to all.'); } return new Promise(function(resolve, reject) { var results = [], remaining = promises.length, @@ -7923,106 +8019,78 @@ } for (var i = 0; i < promises.length; i++) { promise = promises[i]; - if (promise && typeof promise.then === 'function') { - promise.then(resolver(i), reject); + if (promise && isFunction(promise.then)) { + promise.then(resolver(i), reject, "RSVP: RSVP#all"); } else { resolveAll(i, promise); } } - }); + }, label); } - __exports__.all = all; }); -define("rsvp/async", - ["rsvp/config","exports"], - function(__dependency1__, __exports__) { + +define("rsvp/cast", + ["exports"], + function(__exports__) { "use strict"; - var config = __dependency1__.config; + /** + `RSVP.Promise.cast` returns the same promise if that promise shares a constructor + with the promise being casted. - var browserGlobal = (typeof window !== 'undefined') ? window : {}; - var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver; - var local = (typeof global !== 'undefined') ? global : this; + Example: - // node - function useNextTick() { - return function() { - process.nextTick(flush); - }; - } + ```javascript + var promise = RSVP.resolve(1); + var casted = RSVP.Promise.cast(promise); - function useMutationObserver() { - var observer = new BrowserMutationObserver(flush); - var element = document.createElement('div'); - observer.observe(element, { attributes: true }); + console.log(promise === casted); // true + ``` - // Chrome Memory Leak: https://bugs.webkit.org/show_bug.cgi?id=93661 - window.addEventListener('unload', function(){ - observer.disconnect(); - observer = null; - }, false); + 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. - return function() { - element.setAttribute('drainQueue', 'drainQueue'); - }; - } + In the case of a non-promise, a promise which will fulfill with that value is + returned. - function useSetTimeout() { - return function() { - local.setTimeout(flush, 1); - }; - } + Example: - var queue = []; - function flush() { - for (var i = 0; i < queue.length; i++) { - var tuple = queue[i]; - var callback = tuple[0], arg = tuple[1]; - callback(arg); - } - queue = []; - } + ```javascript + var value = 1; // could be a number, boolean, string, undefined... + var casted = RSVP.Promise.cast(value); - var scheduleFlush; + console.log(value === casted); // false + console.log(casted instanceof RSVP.Promise) // true - // 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 { - scheduleFlush = useSetTimeout(); - } + casted.then(function(val) { + val === value // => true + }); + ``` - function asyncDefault(callback, arg) { - var length = queue.push([callback, arg]); - if (length === 1) { - // 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(); - } - } + `RSVP.Promise.cast` is similar to `RSVP.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). - config.async = asyncDefault; + @method cast + @for RSVP + @param {Object} object to be casted + @return {Promise} promise that is fulfilled when all properties of `promises` + have been fulfilled, or rejected if any of them become rejected. + */ - function async(callback, arg) { - config.async(callback, arg); - } - - __exports__.async = async; - __exports__.asyncDefault = asyncDefault; - }); -define("rsvp/cast", - ["exports"], - function(__exports__) { - "use strict"; function cast(object) { /*jshint validthis:true */ if (object && typeof object === 'object' && object.constructor === this) { return object; } @@ -8032,96 +8100,105 @@ return new Promise(function(resolve) { resolve(object); }); } - __exports__.cast = cast; }); define("rsvp/config", - ["rsvp/events","exports"], + ["./events","exports"], function(__dependency1__, __exports__) { "use strict"; var EventTarget = __dependency1__.EventTarget; - var config = {}; + 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', function(event){ - value(event.detail); - }); - } else { + config.on('error', value); + return; + } + + if (arguments.length === 2) { config[name] = value; + } else { + return config[name]; } } - function on(){ - return config.on.apply(config, arguments); - } - - function off(){ - return config.off.apply(config, arguments); - } - - function trigger(){ - return config.trigger.apply(config, arguments); - } - - __exports__.config = config; __exports__.configure = configure; - __exports__.on = on; - __exports__.off = off; - __exports__.trigger = trigger; }); define("rsvp/defer", - ["rsvp/promise","exports"], + ["./promise","exports"], function(__dependency1__, __exports__) { "use strict"; var Promise = __dependency1__.Promise; - function defer() { + /** + `RSVP.defer` returns an object similar to jQuery's `$.Deferred` objects. + `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 + @for RSVP + @param {String} - + @return {Object} + */ + + function defer(label) { var deferred = { // pre-allocate shape resolve: undefined, reject: undefined, promise: undefined }; deferred.promise = new Promise(function(resolve, reject) { deferred.resolve = resolve; deferred.reject = reject; - }); + }, label); return deferred; } - __exports__.defer = defer; }); define("rsvp/events", ["exports"], function(__exports__) { "use strict"; - var Event = function(type, options) { - this.type = type; - - for (var option in options) { - if (!options.hasOwnProperty(option)) { continue; } - - this[option] = options[option]; - } - }; - var indexOf = function(callbacks, callback) { for (var i=0, l=callbacks.length; i<l; i++) { - if (callbacks[i][0] === callback) { return i; } + if (callbacks[i] === callback) { return i; } } return -1; }; @@ -8133,328 +8210,654 @@ } return callbacks; }; + /** + //@module RSVP + //@class EventTarget + */ var EventTarget = { + + /** + @private + `RSVP.EventTarget.mixin` extends an object with EventTarget methods. For + Example: + + ```javascript + var object = {}; + + RSVP.EventTarget.mixin(object); + + object.on("finished", function(event) { + // handle event + }); + + object.trigger("finished", { detail: value }); + ``` + + `EventTarget.mixin` also works with prototypes: + + ```javascript + var Person = function() {}; + RSVP.EventTarget.mixin(Person.prototype); + + var yehuda = new Person(); + var tom = new Person(); + + yehuda.on("poke", function(event) { + console.log("Yehuda says OW"); + }); + + tom.on("poke", function(event) { + console.log("Tom says OW"); + }); + + yehuda.trigger("poke"); + tom.trigger("poke"); + ``` + + @method mixin + @param {Object} object object to extend with EventTarget methods + */ mixin: function(object) { object.on = this.on; object.off = this.off; object.trigger = this.trigger; + object._promiseCallbacks = undefined; return object; }, - on: function(eventNames, callback, binding) { - var allCallbacks = callbacksFor(this), callbacks, eventName; - eventNames = eventNames.split(/\s+/); - binding = binding || this; + /** + @private - while (eventName = eventNames.shift()) { - callbacks = allCallbacks[eventName]; + Registers a callback to be executed when `eventName` is triggered - if (!callbacks) { - callbacks = allCallbacks[eventName] = []; - } + ```javascript + object.on('event', function(eventInfo){ + // handle the event + }); - if (indexOf(callbacks, callback) === -1) { - callbacks.push([callback, binding]); - } + object.trigger('event'); + ``` + + @method on + @param {String} eventName name of the event to listen for + @param {Function} callback function to be called when the event is triggered. + */ + on: function(eventName, callback) { + var allCallbacks = callbacksFor(this), callbacks; + + callbacks = allCallbacks[eventName]; + + if (!callbacks) { + callbacks = allCallbacks[eventName] = []; } + + if (indexOf(callbacks, callback) === -1) { + callbacks.push(callback); + } }, - off: function(eventNames, callback) { - var allCallbacks = callbacksFor(this), callbacks, eventName, index; - eventNames = eventNames.split(/\s+/); + /** + @private - while (eventName = eventNames.shift()) { - if (!callback) { - allCallbacks[eventName] = []; - continue; - } + You can use `off` to stop firing a particular callback for an event: - callbacks = allCallbacks[eventName]; + ```javascript + function doStuff() { // do stuff! } + object.on('stuff', doStuff); - index = indexOf(callbacks, callback); + object.trigger('stuff'); // doStuff will be called - if (index !== -1) { callbacks.splice(index, 1); } + // Unregister ONLY the doStuff callback + object.off('stuff', doStuff); + object.trigger('stuff'); // doStuff will NOT be called + ``` + + If you don't pass a `callback` argument to `off`, ALL callbacks for the + event will not be executed when the event fires. For example: + + ```javascript + var callback1 = function(){}; + var callback2 = function(){}; + + object.on('stuff', callback1); + object.on('stuff', callback2); + + object.trigger('stuff'); // callback1 and callback2 will be executed. + + object.off('stuff'); + object.trigger('stuff'); // callback1 and callback2 will not be executed! + ``` + + @method off + @param {String} eventName event to stop listening to + @param {Function} callback optional argument. If given, only the function + given will be removed from the event's callback queue. If no `callback` + argument is given, all callbacks will be removed from the event's callback + queue. + */ + off: function(eventName, callback) { + var allCallbacks = callbacksFor(this), callbacks, index; + + if (!callback) { + allCallbacks[eventName] = []; + return; } + + callbacks = allCallbacks[eventName]; + + index = indexOf(callbacks, callback); + + if (index !== -1) { callbacks.splice(index, 1); } }, + /** + @private + + Use `trigger` to fire custom events. For example: + + ```javascript + object.on('foo', function(){ + console.log('foo event happened!'); + }); + object.trigger('foo'); + // 'foo event happened!' logged to the console + ``` + + You can also pass a value as a second argument to `trigger` that will be + passed as an argument to all event listeners for the event: + + ```javascript + object.on('foo', function(value){ + console.log(value.name); + }); + + object.trigger('foo', { name: 'bar' }); + // 'bar' logged to the console + ``` + + @method trigger + @param {String} eventName name of the event to be triggered + @param {Any} options optional value to be passed to any event handlers for + the given `eventName` + */ trigger: function(eventName, options) { var allCallbacks = callbacksFor(this), - callbacks, callbackTuple, callback, binding, event; + 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++) { - callbackTuple = callbacks[i]; - callback = callbackTuple[0]; - binding = callbackTuple[1]; + callback = callbacks[i]; - if (typeof options !== 'object') { - options = { detail: options }; - } - - event = new Event(eventName, options); - callback.call(binding, event); + callback(options); } } } }; - __exports__.EventTarget = EventTarget; }); define("rsvp/hash", - ["rsvp/defer","exports"], - function(__dependency1__, __exports__) { + ["./promise","./utils","exports"], + function(__dependency1__, __dependency2__, __exports__) { "use strict"; - var defer = __dependency1__.defer; + var Promise = __dependency1__.Promise; + var isFunction = __dependency2__.isFunction; - function size(object) { - var s = 0; + var keysOf = Object.keys || function(object) { + var result = []; for (var prop in object) { - s++; + result.push(prop); } - return s; - } + return result; + }; - function hash(promises) { - var results = {}, deferred = defer(), remaining = size(promises); + /** + `RSVP.hash` is similar to `RSVP.all`, but takes an object instead of an array + for its `promises` argument. - if (remaining === 0) { - deferred.resolve({}); - } + 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. - var resolver = function(prop) { - return function(value) { - resolveAll(prop, value); - }; + Example: + + ```javascript + var promises = { + myPromise: RSVP.resolve(1), + yourPromise: RSVP.resolve(2), + theirPromise: RSVP.resolve(3), + notAPromise: 4 }; - var resolveAll = function(prop, value) { - results[prop] = value; - if (--remaining === 0) { - deferred.resolve(results); - } + 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 as the first argument, or as the reason to + the rejection handler. For example: + + ```javascript + var promises = { + myPromise: RSVP.resolve(1), + rejectedPromise: RSVP.reject(new Error("rejectedPromise")), + anotherRejectedPromise: RSVP.reject(new Error("anotherRejectedPromise")), }; - var rejectAll = function(error) { - deferred.reject(error); + 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") }; - for (var prop in promises) { - if (promises[prop] && typeof promises[prop].then === 'function') { - promises[prop].then(resolver(prop), rejectAll); - } else { - resolveAll(prop, promises[prop]); + 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 + @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. + */ + function hash(object, label) { + var results = {}, + keys = keysOf(object), + remaining = keys.length; + + return new Promise(function(resolve, reject){ + var promise, prop; + + if (remaining === 0) { + resolve({}); + return; } - } - return deferred.promise; - } + var resolver = function(prop) { + return function(value) { + resolveAll(prop, value); + }; + }; + var resolveAll = function(prop, value) { + results[prop] = value; + if (--remaining === 0) { + resolve(results); + } + }; + + for (var i = 0, l = keys.length; i < l; i ++) { + prop = keys[i]; + promise = object[prop]; + + if (promise && isFunction(promise.then)) { + promise.then(resolver(prop), reject, "RSVP: RSVP#hash"); + } else { + resolveAll(prop, promise); + } + } + }); + } + __exports__.hash = hash; }); +define("rsvp/instrument", + ["./config","./utils","exports"], + function(__dependency1__, __dependency2__, __exports__) { + "use strict"; + var config = __dependency1__.config; + var now = __dependency2__.now; + + function instrument(eventName, promise, child) { + // instrumentation should not disrupt normal usage. + try { + config.trigger(eventName, { + guid: promise._guidKey + promise._id, + eventName: eventName, + detail: promise._detail, + childGuid: child && promise._guidKey + child._id, + label: promise._label, + timeStamp: now() + }); + } catch(error) { + setTimeout(function(){ + throw error; + }, 0); + } + } + + __exports__.instrument = instrument; + }); define("rsvp/node", - ["rsvp/promise","rsvp/all","exports"], + ["./promise","./all","exports"], function(__dependency1__, __dependency2__, __exports__) { "use strict"; var Promise = __dependency1__.Promise; var all = __dependency2__.all; + var slice = Array.prototype.slice; + function makeNodeCallbackFor(resolve, reject) { return function (error, value) { if (error) { reject(error); } else if (arguments.length > 2) { - resolve(Array.prototype.slice.call(arguments, 1)); + resolve(slice.call(arguments, 1)); } else { resolve(value); } }; } - function denodeify(nodeFunc) { - return function() { - var nodeArgs = Array.prototype.slice.call(arguments), resolve, reject; - var thisArg = this; + /** + `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: - var promise = new Promise(function(nodeResolve, nodeReject) { - resolve = nodeResolve; - reject = nodeReject; - }); + ```javascript + var fs = require('fs'); - all(nodeArgs).then(function(nodeArgs) { - nodeArgs.push(makeNodeCallbackFor(resolve, reject)); + fs.readFile('myfile.txt', function(err, data){ + if (err) return handleError(err); + handleData(data); + }); + ``` - try { - nodeFunc.apply(thisArg, nodeArgs); - } catch(e) { - reject(e); - } + into: + + ```javascript + var fs = require('fs'); + + var readFile = RSVP.denodeify(fs.readFile); + + readFile('myfile.txt').then(handleData, handleError); + ``` + + Using `denodeify` makes it easier to compose asynchronous operations instead + of using callbacks. For example, instead of: + + ```javascript + var fs = require('fs'); + var log = require('some-async-logger'); + + fs.readFile('myfile.txt', function(err, data){ + if (err) return handleError(err); + fs.writeFile('myfile2.txt', data, function(err){ + if (err) throw err; + log('success', function(err) { + if (err) throw err; + }); }); + }); + ``` - return promise; + You can chain the operations together using `then` from the returned promise: + + ```javascript + var fs = require('fs'); + var denodeify = RSVP.denodeify; + var readFile = denodeify(fs.readFile); + var writeFile = denodeify(fs.writeFile); + var log = denodeify(require('some-async-logger')); + + readFile('myfile.txt').then(function(data){ + return writeFile('myfile2.txt', data); + }).then(function(){ + return log('SUCCESS'); + }).then(function(){ + // success handler + }, function(reason){ + // rejection handler + }); + ``` + + @method denodeify + @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 {Any} binding optional argument for binding the "this" value when + calling the `nodeFunc` function. + @return {Function} a function that wraps `nodeFunc` to return an + `RSVP.Promise` + */ + function denodeify(nodeFunc, binding) { + return function() { + var nodeArgs = slice.call(arguments), resolve, reject; + var thisArg = this || binding; + + return new Promise(function(resolve, reject) { + all(nodeArgs).then(function(nodeArgs) { + try { + nodeArgs.push(makeNodeCallbackFor(resolve, reject)); + nodeFunc.apply(thisArg, nodeArgs); + } catch(e) { + reject(e); + } + }); + }); }; } - __exports__.denodeify = denodeify; }); define("rsvp/promise", - ["rsvp/config","rsvp/events","rsvp/cast","exports"], - function(__dependency1__, __dependency2__, __dependency3__, __exports__) { + ["./config","./events","./cast","./instrument","./utils","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) { "use strict"; var config = __dependency1__.config; var EventTarget = __dependency2__.EventTarget; var cast = __dependency3__.cast; + var instrument = __dependency4__.instrument; + var objectOrFunction = __dependency5__.objectOrFunction; + var isFunction = __dependency5__.isFunction; + var now = __dependency5__.now; - function objectOrFunction(x) { - return isFunction(x) || (typeof x === "object" && x !== null); - } + var guidKey = 'rsvp_' + now() + '-'; + var counter = 0; - function isFunction(x){ - return typeof x === "function"; - } + function Promise(resolver, label) { + if (!isFunction(resolver)) { + throw new TypeError('You must pass a resolver function as the first argument to the promise constructor'); + } - function Promise(resolver) { - var promise = this, - resolved = false; - - if (typeof resolver !== 'function') { - throw new TypeError('You must pass a resolver function as the sole argument to the promise constructor'); + if (!(this instanceof Promise)) { + throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function."); } - if (!(promise instanceof Promise)) { - return new Promise(resolver); + this._id = counter++; + this._label = label; + this._subscribers = []; + + if (config.instrument) { + instrument('created', this); } - var resolvePromise = function(value) { - if (resolved) { return; } - resolved = true; + invokeResolver(resolver, this); + } + + function invokeResolver(resolver, promise) { + function resolvePromise(value) { resolve(promise, value); - }; + } - var rejectPromise = function(value) { - if (resolved) { return; } - resolved = true; - reject(promise, value); - }; + function rejectPromise(reason) { + reject(promise, reason); + } try { resolver(resolvePromise, rejectPromise); } catch(e) { rejectPromise(e); } } - var invokeCallback = function(type, promise, callback, event) { + function invokeCallback(settled, promise, callback, detail) { var hasCallback = isFunction(callback), value, error, succeeded, failed; if (hasCallback) { try { - value = callback(event.detail); + value = callback(detail); succeeded = true; } catch(e) { failed = true; error = e; } } else { - value = event.detail; + value = detail; succeeded = true; } if (handleThenable(promise, value)) { return; } else if (hasCallback && succeeded) { resolve(promise, value); } else if (failed) { reject(promise, error); - } else if (type === 'resolve') { + } else if (settled === FULFILLED) { resolve(promise, value); - } else if (type === 'reject') { + } else if (settled === REJECTED) { reject(promise, value); } - }; + } + var PENDING = void 0; + var SEALED = 0; + var FULFILLED = 1; + var REJECTED = 2; + + function subscribe(parent, child, onFulfillment, onRejection) { + var subscribers = parent._subscribers; + var length = subscribers.length; + + subscribers[length] = child; + subscribers[length + FULFILLED] = onFulfillment; + subscribers[length + REJECTED] = onRejection; + } + + function publish(promise, settled) { + var child, callback, subscribers = promise._subscribers, detail = promise._detail; + + if (config.instrument) { + instrument(settled === FULFILLED ? 'fulfilled' : 'rejected', promise); + } + + for (var i = 0; i < subscribers.length; i += 3) { + child = subscribers[i]; + callback = subscribers[i + settled]; + + invokeCallback(settled, child, callback, detail); + } + + promise._subscribers = null; + } + Promise.prototype = { constructor: Promise, - isRejected: undefined, - isFulfilled: undefined, - rejectedReason: undefined, - fulfillmentValue: undefined, + _id: undefined, + _guidKey: guidKey, + _label: undefined, + + _state: undefined, + _detail: undefined, + _subscribers: undefined, + _onerror: function (reason) { - config.trigger('error', { - detail: reason - }); + config.trigger('error', reason); }, - then: function(done, fail) { + then: function(onFulfillment, onRejection, label) { + var promise = this; this._onerror = null; - var thenPromise = new this.constructor(function() {}); + var thenPromise = new this.constructor(function() {}, label); - if (this.isFulfilled) { - config.async(function(promise) { - invokeCallback('resolve', thenPromise, done, { detail: promise.fulfillmentValue }); - }, this); + if (this._state) { + var callbacks = arguments; + config.async(function invokePromiseCallback() { + invokeCallback(promise._state, thenPromise, callbacks[promise._state - 1], promise._detail); + }); + } else { + subscribe(this, thenPromise, onFulfillment, onRejection); } - if (this.isRejected) { - config.async(function(promise) { - invokeCallback('reject', thenPromise, fail, { detail: promise.rejectedReason }); - }, this); + if (config.instrument) { + instrument('chained', promise, thenPromise); } - this.on('promise:resolved', function(event) { - invokeCallback('resolve', thenPromise, done, event); - }); - - this.on('promise:failed', function(event) { - invokeCallback('reject', thenPromise, fail, event); - }); - return thenPromise; }, - fail: function(onRejection) { - return this.then(null, onRejection); + 'catch': function(onRejection, label) { + return this.then(null, onRejection, label); }, - 'finally': function(callback) { + + 'finally': function(callback, label) { var constructor = this.constructor; return this.then(function(value) { return constructor.cast(callback()).then(function(){ return value; }); }, function(reason) { return constructor.cast(callback()).then(function(){ throw reason; }); - }); + }, label); } }; - Promise.prototype['catch'] = Promise.prototype.fail; Promise.cast = cast; - EventTarget.mixin(Promise.prototype); - - function resolve(promise, value) { - if (promise === value) { - fulfill(promise, value); - } else if (!handleThenable(promise, value)) { - fulfill(promise, value); - } - } - function handleThenable(promise, value) { var then = null, resolved; try { @@ -8478,128 +8881,373 @@ }, function(val) { if (resolved) { return true; } resolved = true; reject(promise, val); - }); + }, 'Locked onto ' + (promise._label || ' unknown promise')); return true; } } } catch (error) { + if (resolved) { return true; } reject(promise, error); return true; } return false; } + function resolve(promise, value) { + if (promise === value) { + fulfill(promise, value); + } else if (!handleThenable(promise, value)) { + fulfill(promise, value); + } + } + function fulfill(promise, value) { - config.async(function() { - promise.trigger('promise:resolved', { detail: value }); - promise.isFulfilled = true; - promise.fulfillmentValue = value; - }); + if (promise._state !== PENDING) { return; } + promise._state = SEALED; + promise._detail = value; + + config.async(publishFulfillment, promise); } - function reject(promise, value) { - config.async(function() { - if (promise._onerror) { promise._onerror(value); } - promise.trigger('promise:failed', { detail: value }); - promise.isRejected = true; - promise.rejectedReason = value; - }); + function reject(promise, reason) { + if (promise._state !== PENDING) { return; } + promise._state = SEALED; + promise._detail = reason; + + config.async(publishRejection, promise); } + function publishFulfillment(promise) { + publish(promise, promise._state = FULFILLED); + } + function publishRejection(promise) { + if (promise._onerror) { + promise._onerror(promise._detail); + } + + publish(promise, promise._state = REJECTED); + } + __exports__.Promise = Promise; }); +define("rsvp/race", + ["./promise","./utils","exports"], + function(__dependency1__, __dependency2__, __exports__) { + "use strict"; + /* global toString */ + + var Promise = __dependency1__.Promise; + var isArray = __dependency2__.isArray; + + /** + `RSVP.race` allows you to watch a series of promises and act as soon as the + first promise given to the `promises` argument fulfills or rejects. + + 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.race([promise1, promise2]).then(function(result){ + // result === "promise 2" because it was resolved before promise1 + // was resolved. + }); + ``` + + `RSVP.race` is deterministic in that only the state of the first completed + promise matters. For example, even if other promises given to the `promises` + array argument are resolved, but the first completed 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.race([promise1, promise2]).then(function(result){ + // Code here never runs because there are rejected promises! + }, function(reason){ + // reason.message === "promise2" because promise 2 became rejected before + // promise 1 became fulfilled + }); + ``` + + @method race + @for RSVP + @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 that becomes fulfilled with the value the first + completed promises is resolved with if the first completed promise was + fulfilled, or rejected with the reason that the first completed promise + was rejected with. + */ + function race(promises, label) { + if (!isArray(promises)) { + throw new TypeError('You must pass an array to race.'); + } + return new Promise(function(resolve, reject) { + var results = [], promise; + + for (var i = 0; i < promises.length; i++) { + promise = promises[i]; + + if (promise && typeof promise.then === 'function') { + promise.then(resolve, reject, "RSVP: RSVP#race"); + } else { + resolve(promise); + } + } + }, label); + } + + __exports__.race = race; + }); define("rsvp/reject", - ["rsvp/promise","exports"], + ["./promise","exports"], function(__dependency1__, __exports__) { "use strict"; var Promise = __dependency1__.Promise; - function reject(reason) { + /** + `RSVP.reject` returns a promise that will become rejected with the passed + `reason`. `RSVP.reject` is essentially 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.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 + @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 that will become rejected with the given + `reason`. + */ + function reject(reason, label) { return new Promise(function (resolve, reject) { reject(reason); - }); + }, label); } - __exports__.reject = reject; }); define("rsvp/resolve", - ["rsvp/promise","exports"], + ["./promise","exports"], function(__dependency1__, __exports__) { "use strict"; var Promise = __dependency1__.Promise; - function resolve(thenable) { - return new Promise(function(resolve, reject) { - resolve(thenable); + /** + `RSVP.resolve` returns a promise that will become fulfilled with the passed + `value`. `RSVP.resolve` is essentially 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.resolve(1); + + promise.then(function(value){ + // value === 1 + }); + ``` + + @method resolve + @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` + */ + function resolve(value, label) { + return new Promise(function(resolve, reject) { + resolve(value); + }, label); + } + __exports__.resolve = resolve; }); define("rsvp/rethrow", ["exports"], function(__exports__) { "use strict"; var local = (typeof global === "undefined") ? this : global; + /** + `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 throw the error + again so the error can be handled by the promise. + + ```javascript + function throws(){ + throw new Error('Whoops!'); + } + + var promise = new RSVP.Promise(function(resolve, reject){ + throws(); + }); + + promise.fail(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 `.fail` on the returned promise. + + @method rethrow + @for RSVP + @param {Error} reason reason the promise became rejected. + @throws Error + */ function rethrow(reason) { local.setTimeout(function() { throw reason; }); throw reason; } - __exports__.rethrow = rethrow; }); +define("rsvp/utils", + ["exports"], + function(__exports__) { + "use strict"; + function objectOrFunction(x) { + return isFunction(x) || (typeof x === "object" && x !== null); + } + + function isFunction(x) { + return typeof x === "function"; + } + + function isArray(x) { + return Object.prototype.toString.call(x) === "[object Array]"; + } + + // 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__.objectOrFunction = objectOrFunction; + __exports__.isFunction = isFunction; + __exports__.isArray = isArray; + __exports__.now = now; + }); define("rsvp", - ["rsvp/events","rsvp/promise","rsvp/node","rsvp/all","rsvp/hash","rsvp/rethrow","rsvp/defer","rsvp/config","rsvp/resolve","rsvp/reject","rsvp/async","exports"], + ["./rsvp/events","./rsvp/promise","./rsvp/node","./rsvp/all","./rsvp/race","./rsvp/hash","./rsvp/rethrow","./rsvp/defer","./rsvp/config","./rsvp/resolve","./rsvp/reject", "exports"], function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __exports__) { "use strict"; var EventTarget = __dependency1__.EventTarget; var Promise = __dependency2__.Promise; var denodeify = __dependency3__.denodeify; var all = __dependency4__.all; - var hash = __dependency5__.hash; - var rethrow = __dependency6__.rethrow; - var defer = __dependency7__.defer; - var config = __dependency8__.config; - var configure = __dependency8__.configure; - var on = __dependency8__.on; - var off = __dependency8__.off; - var trigger = __dependency8__.trigger; - var resolve = __dependency9__.resolve; - var reject = __dependency10__.reject; - var async = __dependency11__.async; - var asyncDefault = __dependency11__.asyncDefault; + var race = __dependency5__.race; + var hash = __dependency6__.hash; + var rethrow = __dependency7__.rethrow; + var defer = __dependency8__.defer; + var config = __dependency9__.config; + var configure = __dependency9__.configure; + var resolve = __dependency10__.resolve; + var reject = __dependency11__.reject; + function async(callback, arg) { + config.async(callback, arg); + } + function on() { + config.on.apply(config, arguments); + } + + function off() { + config.off.apply(config, arguments); + } + __exports__.Promise = Promise; __exports__.EventTarget = EventTarget; __exports__.all = all; + __exports__.race = race; __exports__.hash = hash; __exports__.rethrow = rethrow; __exports__.defer = defer; __exports__.denodeify = denodeify; __exports__.configure = configure; - __exports__.trigger = trigger; __exports__.on = on; __exports__.off = off; __exports__.resolve = resolve; __exports__.reject = reject; __exports__.async = async; - __exports__.asyncDefault = asyncDefault; }); - })(); (function() { /** @private @@ -8648,10 +9296,11 @@ 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; @@ -8945,10 +9594,12 @@ 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; }, @@ -8988,11 +9639,11 @@ var twitter = container.lookup('api:twitter'); twitter instanceof Twitter; // => true // by default the container will return singletons - twitter2 = container.lookup('api:twitter'); + var twitter2 = container.lookup('api:twitter'); twitter instanceof Twitter; // => true twitter === twitter2; //=> true ``` @@ -9150,12 +9801,12 @@ 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 + * Injecting one fullName on another fullName + * Injecting one fullName on a type Example: ```javascript var container = new Container(); @@ -10371,13 +11022,15 @@ set: function(keyName, value) { set(this, keyName, value); return this; }, + /** - To set multiple properties at once, call `setProperties` - with a Hash: + 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' }); ``` @@ -10669,12 +11322,12 @@ (function() { /** -@module ember -@submodule ember-runtime + @module ember + @submodule ember-runtime */ // 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. @@ -11019,31 +11672,37 @@ /** Returns a string representation which attempts to provide more information than Javascript's `toString` typically does, in a generic way for all Ember objects. - App.Person = Em.Object.extend() - person = App.Person.create() - person.toString() //=> "<App.Person:ember1024>" + ```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: - Student = App.Person.extend() - student = Student.create() - student.toString() //=> "<(subclass of App.Person):ember1025>" + ```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. - App.Teacher = App.Person.extend({ - toStringExtension: function() { - return this.get('fullName'); - } - }); - teacher = App.Teacher.create() - teacher.toString(); //=> "<App.Teacher:ember1026:Tom Dale>" + ```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() { @@ -11235,11 +11894,10 @@ 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({ @@ -11278,11 +11936,10 @@ ```javascript MyObject = Ember.Object.extend({ name: 'an object' }); - MyObject.reopenClass({ canBuild: false }); MyObject.canBuild; // false @@ -12161,11 +12818,13 @@ 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): - function(item, index, enumerable); + ```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. @@ -12467,21 +13126,21 @@ /** 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 anyBy + @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 */ isAny: function(key, value) { return this.any(iter.apply(this, arguments)); }, /** - @method someProperty + @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 */ @@ -14959,30 +15618,22 @@ })(); (function() { -/** - Expose RSVP implementation - - Documentation can be found here: https://github.com/tildeio/rsvp.js/blob/master/README.md - - @class RSVP - @namespace Ember - @constructor -*/ Ember.RSVP = requireModule('rsvp'); -Ember.RSVP.onerrorDefault = function(event) { - var error = event.detail; - +Ember.RSVP.onerrorDefault = function(error) { if (error instanceof Error) { - Ember.Logger.error(error.stack); - if (Ember.testing) { - throw error; + if (Ember.Test && Ember.Test.adapter) { + Ember.Test.adapter.exception(error); + } else { + throw error; + } } else { + Ember.Logger.error(error.stack); Ember.assert(error, false); } } }; @@ -15622,11 +16273,11 @@ /** 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` + 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"] @@ -15656,32 +16307,31 @@ /** Push the object onto the end of the array. Works just like `push()` but it is KVO-compliant. ```javascript - var colors = ["red", "green", "blue"]; - colors.pushObject("black"); // ["red", "green", "blue", "black"] - colors.pushObject(["yellow", "orange"]); // ["red", "green", "blue", "black", ["yellow", "orange"]] + var colors = ["red", "green"]; + colors.pushObject("black"); // ["red", "green", "black"] + colors.pushObject(["yellow"]); // ["red", "green", ["yellow"]] ``` @method pushObject @param {*} obj object to push - @return {*} the same obj passed as param + @return The same obj passed as param */ pushObject: function(obj) { this.insertAt(get(this, 'length'), obj) ; - return 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", "green", "blue"]; - colors.pushObjects(["black"]); // ["red", "green", "blue", "black"] - colors.pushObjects(["yellow", "orange"]); // ["red", "green", "blue", "black", "yellow", "orange"] + var colors = ["red"]; + colors.pushObjects(["yellow", "orange"]); // ["red", "yellow", "orange"] ``` @method pushObjects @param {Ember.Enumerable} objects the objects to add @return {Ember.Array} receiver @@ -15739,18 +16389,18 @@ /** Unshift an object to start of array. Works just like `unshift()` but it is KVO-compliant. ```javascript - var colors = ["red", "green", "blue"]; - colors.unshiftObject("yellow"); // ["yellow", "red", "green", "blue"] - colors.unshiftObject(["black", "white"]); // [["black", "white"], "yellow", "red", "green", "blue"] + var colors = ["red"]; + colors.unshiftObject("yellow"); // ["yellow", "red"] + colors.unshiftObject(["black"]); // [["black"], "yellow", "red"] ``` @method unshiftObject @param {*} obj object to unshift - @return {*} the same obj passed as param + @return The same obj passed as param */ unshiftObject: function(obj) { this.insertAt(0, obj) ; return obj ; }, @@ -15758,13 +16408,13 @@ /** Adds the named objects to the beginning of the array. Defers notifying observers until all objects have been added. ```javascript - var colors = ["red", "green", "blue"]; - colors.unshiftObjects(["black", "white"]); // ["black", "white", "red", "green", "blue"] - colors.unshiftObjects("yellow"); // Type Error: 'undefined' is not a function + 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 @@ -18148,36 +18798,35 @@ (function() { var forEach = Ember.ArrayPolyfills.forEach; /** -@module ember -@submodule ember-runtime + @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. -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: -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(...); -}); -``` + ``` 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 + @method onLoad + @for Ember + @param name {String} name of hook + @param callback {Function} callback to be called */ Ember.onLoad = function(name, callback) { var object; loadHooks[name] = loadHooks[name] || Ember.A(); @@ -18187,18 +18836,17 @@ callback(object); } }; /** + Called when an Ember.js package (e.g Ember.Handlebars) has finished + loading. Triggers any callbacks registered for this event. -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 + @method runLoadHooks + @for Ember + @param name {String} name of hook + @param object {Object} object to pass to callbacks */ Ember.runLoadHooks = function(name, object) { loaded[name] = object; if (loadHooks[name]) { @@ -19712,11 +20360,11 @@ */ setupHandler: function(rootElement, event, eventName) { var self = this; rootElement.on(event + '.ember', '.ember-view', function(evt, triggeringManager) { - return Ember.handleErrors(function() { + return Ember.handleErrors(function handleViewEvent() { var view = Ember.View.views[this.id], result = true, manager = null; manager = self._findNearestEventManager(view,eventName); @@ -19731,11 +20379,11 @@ return result; }, this); }); rootElement.on(event + '.ember', '[data-ember-action]', function(evt) { - return Ember.handleErrors(function() { + return Ember.handleErrors(function handleActionEvent() { var actionId = Ember.$(evt.currentTarget).attr('data-ember-action'), action = Ember.Handlebars.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 @@ -19777,11 +20425,11 @@ return result; }, _bubbleEvent: function(view, evt, eventName) { - return Ember.run(function() { + return Ember.run(function bubbleEvent() { return view.handleEvent(eventName, evt); }); }, destroy: function() { @@ -24394,11 +25042,11 @@ function F() {} F.prototype = parent; return new F(); }; -var Handlebars = this.Handlebars || (Ember.imports && Ember.imports.Handlebars); +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); @@ -24646,11 +25294,11 @@ var options = { knownHelpers: { action: true, unbound: true, - bindAttr: true, + 'bind-attr': true, template: true, view: true, _triageMustache: true }, data: true, @@ -24858,10 +25506,12 @@ var helper = Ember.Handlebars.resolveHelper(options.data.view.container, path); if (helper) { return helper.apply(this, slice.call(arguments, 1)); + } else { + return Handlebars.helpers.helperMissing.call(this, path); } return Handlebars.helpers.blockHelperMissing.apply(this, arguments); }); @@ -25910,11 +26560,11 @@ @for Ember.Handlebars.helpers @param {String} property Property to bind @param {Function} fn Context to provide for rendering @return {String} HTML string */ -EmberHandlebars.registerHelper('bind', function(property, options) { +EmberHandlebars.registerHelper('bind', 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) { @@ -25940,11 +26590,11 @@ @for Ember.Handlebars.helpers @param {String} property Property to bind @param {Function} fn Context to provide for rendering @return {String} HTML string */ -EmberHandlebars.registerHelper('boundIf', function(property, fn) { +EmberHandlebars.registerHelper('boundIf', function boundIfHelper(property, fn) { var context = (fn.contexts && fn.contexts.length) ? fn.contexts[0] : this; var func = function(result) { var truthy = result && get(result, 'isTruthy'); if (typeof truthy === 'boolean') { return truthy; } @@ -25963,11 +26613,11 @@ @for Ember.Handlebars.helpers @param {Function} context @param {Hash} options @return {String} HTML string */ -EmberHandlebars.registerHelper('with', function(context, options) { +EmberHandlebars.registerHelper('with', function withHelper(context, options) { 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]; @@ -26013,11 +26663,11 @@ @for Ember.Handlebars.helpers @param {Function} context @param {Hash} options @return {String} HTML string */ -EmberHandlebars.registerHelper('if', function(context, options) { +EmberHandlebars.registerHelper('if', 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); return helpers.boundIf.call(options.contexts[0], context, options); }); @@ -26027,11 +26677,11 @@ @for Ember.Handlebars.helpers @param {Function} context @param {Hash} options @return {String} HTML string */ -EmberHandlebars.registerHelper('unless', function(context, options) { +EmberHandlebars.registerHelper('unless', 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; @@ -26162,11 +26812,11 @@ @method bind-attr @for Ember.Handlebars.helpers @param {Hash} options @return {String} HTML string */ -EmberHandlebars.registerHelper('bind-attr', function(options) { +EmberHandlebars.registerHelper('bind-attr', function bindAttrHelper(options) { var attrs = options.hash; Ember.assert("You must specify at least one hash argument to bind-attr", !!Ember.keys(attrs).length); @@ -26258,11 +26908,14 @@ @deprecated @param {Function} context @param {Hash} options @return {String} HTML string */ -EmberHandlebars.registerHelper('bindAttr', EmberHandlebars.helpers['bind-attr']); +EmberHandlebars.registerHelper('bindAttr', function bindAttrHelper() { + Ember.warn("The 'bindAttr' view helper is deprecated in favor of 'bind-attr'"); + return EmberHandlebars.helpers['bind-attr'].apply(this, arguments); +}); /** @private Helper that, given a space-separated string of property paths and a context, @@ -26740,11 +27393,11 @@ @for Ember.Handlebars.helpers @param {String} path @param {Hash} options @return {String} HTML string */ -EmberHandlebars.registerHelper('view', function(path, options) { +EmberHandlebars.registerHelper('view', 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. if (path && path.data && path.data.isRenderData) { options = path; @@ -26888,11 +27541,11 @@ @param {String} path @param {Hash} options @return {String} HTML string @deprecated Use `{{each}}` helper instead. */ -Ember.Handlebars.registerHelper('collection', function(path, options) { +Ember.Handlebars.registerHelper('collection', 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; @@ -27011,11 +27664,11 @@ @method unbound @for Ember.Handlebars.helpers @param {String} property @return {String} HTML string */ -Ember.Handlebars.registerHelper('unbound', function(property, fn) { +Ember.Handlebars.registerHelper('unbound', function unboundHelper(property, fn) { var options = arguments[arguments.length - 1], helper, context, out; if (arguments.length > 2) { // Unbound helper call. options.data.isUnbound = true; @@ -27052,11 +27705,11 @@ @method log @for Ember.Handlebars.helpers @param {String} property */ -Ember.Handlebars.registerHelper('log', function(property, options) { +Ember.Handlebars.registerHelper('log', function logHelper(property, options) { var context = (options.contexts && options.contexts.length) ? options.contexts[0] : this, normalized = normalizePath(context, property, options.data), pathRoot = normalized.root, path = normalized.path, value = (path === 'this') ? pathRoot : handlebarsGet(pathRoot, path, options); @@ -27076,35 +27729,34 @@ 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 that describes the - type of object templateContext is, e.g. - "controller:people" + - 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>" + > templateContext.get('foo') // -> "<value of {{foo}}>" ``` @method debugger @for Ember.Handlebars.helpers @param {String} property */ -Ember.Handlebars.registerHelper('debugger', function(options) { +Ember.Handlebars.registerHelper('debugger', function debuggerHelper(options) { // These are helpful values you can inspect while debugging. var templateContext = this; - var typeOfTemplateContext = this ? get(this, '_debugContainerKey') : 'none'; + var typeOfTemplateContext = Ember.inspect(templateContext); debugger; }); @@ -27485,11 +28137,11 @@ @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 */ -Ember.Handlebars.registerHelper('each', function(path, options) { +Ember.Handlebars.registerHelper('each', function eachHelper(path, options) { 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]; @@ -27633,11 +28285,11 @@ @method partial @for Ember.Handlebars.helpers @param {String} partialName the name of the template to render minus the leading underscore */ -Ember.Handlebars.registerHelper('partial', function(name, options) { +Ember.Handlebars.registerHelper('partial', function partialHelper(name, options) { var context = (options.contexts && options.contexts.length) ? options.contexts[0] : this; if (options.types[0] === "ID") { // Helper was passed a property path; we need to @@ -27769,11 +28421,11 @@ @method yield @for Ember.Handlebars.helpers @param {Hash} options @return {String} HTML string */ -Ember.Handlebars.registerHelper('yield', function(options) { +Ember.Handlebars.registerHelper('yield', function yieldHelper(options) { var view = options.data.view; while (view && !get(view, 'layout')) { if (view._contextView) { view = view._contextView; @@ -27813,11 +28465,11 @@ @method loc @for Ember.Handlebars.helpers @param {String} str The string to format */ -Ember.Handlebars.registerHelper('loc', function(str) { +Ember.Handlebars.registerHelper('loc', function locHelper(str) { return Ember.String.loc(str); }); })(); @@ -29078,10 +29730,11 @@ arguments from the helper to `Ember.TextField`'s `create` method. You can extend the capablilties of text inputs in your applications by reopening this class. For example, if you are deploying to browsers where the `required` attribute is used, you can add this to the `TextField`'s `attributeBindings` property: + ```javascript Ember.TextField.reopen({ attributeBindings: ['required'] }); ``` @@ -29142,10 +29795,11 @@ 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'] }); ``` @@ -29300,11 +29954,11 @@ 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 deploying to browsers where the `required` attribute is used, you can globally add support for the `required` attribute - on all {{textarea}}'s' in your app by reopening `Ember.TextArea` or + 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({ @@ -30246,11 +30900,11 @@ } // TODO: separate into module? Router.Transition = Transition; - __exports__['default'] = Router; + __exports__["default"] = Router; /** Promise reject reasons passed to promise rejection handlers for failed transitions. @@ -30476,11 +31130,11 @@ if (handlerInfo.isDynamic && contexts.length > 0) { object = contexts.pop(); if (isParam(object)) { var name = recogHandler.names[0]; - if ("" + object !== this.currentParams[name]) { return false; } + if (!this.currentParams || "" + object !== this.currentParams[name]) { return false; } } else if (handlerInfo.context !== object) { return false; } } } @@ -31694,10 +32348,11 @@ */ var Router = requireModule("router")['default']; var get = Ember.get, set = Ember.set; var defineProperty = Ember.defineProperty; +var slice = Array.prototype.slice; var DefaultView = Ember._MetamorphView; /** The `Ember.Router` class manages the application state and URLs. Refer to the [routing guide](http://emberjs.com/guides/routing/) for documentation. @@ -31850,10 +32505,14 @@ if ('string' === typeof location) { options.implementation = location; location = set(this, 'location', Ember.Location.create(options)); } + + // 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, DefaultRoute = container.lookupFactory('route:basic'), @@ -31911,11 +32570,11 @@ }; }, _doTransition: function(method, args) { // Normalize blank route to root URL. - args = [].slice.call(args); + args = slice.call(args); args[0] = args[0] || '/'; var passedName = args[0], name, self = this, isQueryParamsOnly = false; @@ -32030,11 +32689,11 @@ } else { // Don't fire an assertion if we found an error substate. return; } - Ember.Logger.assert(false, 'Error while loading route: ' + Ember.inspect(error)); + Ember.Logger.error('Error while loading route: ' + error.stack); }, loading: function(transition, originRoute) { // Attempt to find an appropriate loading substate to enter. var router = originRoute.router; @@ -32110,11 +32769,11 @@ defaultActionHandlers[name].apply(null, args); return; } if (!eventWasHandled && !ignoreFailure) { - throw new Ember.Error("Nothing handled the action '" + name + "'."); + throw new Ember.Error("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 updatePaths(router) { var appController = router.container.lookup('controller:application'); @@ -32168,15 +32827,36 @@ }, _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; + } + for (var i=1, l=handlerInfos.length; i<l; i++) { var name = handlerInfos[i].name, - nameParts = name.split("."); + nameParts = name.split("."), + oldNameParts = slice.call(path); - path.push(nameParts[nameParts.length - 1]); + while (oldNameParts.length) { + if (intersectionMatches(oldNameParts, nameParts)) { + break; + } + oldNameParts.shift(); + } + + path.push.apply(path, nameParts.slice(oldNameParts.length)); } return path.join("."); } }); @@ -32839,11 +33519,11 @@ * 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 `linkTo` Handlebars + 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 @@ -32963,10 +33643,11 @@ 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"); @@ -33097,15 +33778,18 @@ return Ember.generateController(container, name, model); }, /** - Returns the current model for a given route. + 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. - This is the object returned by the `model` hook of the route - in question. - Example ```js App.Router.map(function() { this.resource('post', { path: '/post/:post_id' }, function() { @@ -33519,12 +34203,15 @@ var resolveParams = Ember.Router.resolveParams, resolvePaths = Ember.Router.resolvePaths, isSimpleClick = Ember.ViewUtils.isSimpleClick; function fullRouteName(router, name) { + var nameWithIndex; if (!router.hasRoute(name)) { - name = name + '.index'; + nameWithIndex = name + '.index'; + Ember.assert(fmt("The attempt to link-to route '%@' failed (also tried '%@'). The router did not find '%@' in its possible routes: '%@'", [name, nameWithIndex, name, Ember.keys(router.router.recognizer.names).join("', '")]), router.hasRoute(nameWithIndex)); + name = nameWithIndex; } return name; } @@ -33682,51 +34369,62 @@ */ init: function() { this._super.apply(this, arguments); // Map desired event name to invoke function - var eventName = get(this, 'eventName'); + var eventName = get(this, 'eventName'), i; this.on(eventName, this, this._invoke); + }, + + /** + @private + + This method is invoked by observers installed during `init` that fire + whenever the params change + @method _paramsChanged + */ + _paramsChanged: function() { + this.notifyPropertyChange('resolvedParams'); + }, + + /** + @private + + This is called to setup observers that will trigger a rerender. + + @method _setupPathObservers + **/ + _setupPathObservers: function(){ var helperParameters = this.parameters, - templateContext = helperParameters.context, + linkTextPath = helperParameters.options.linkTextPath, paths = getResolvedPaths(helperParameters), length = paths.length, path, i, normalizedPath; - var linkTextPath = helperParameters.options.linkTextPath; if (linkTextPath) { - normalizedPath = Ember.Handlebars.normalizePath(templateContext, linkTextPath, helperParameters.options.data); + normalizedPath = Ember.Handlebars.normalizePath(helperParameters.context, linkTextPath, helperParameters.options.data); 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 = Ember.Handlebars.normalizePath(templateContext, path, helperParameters.options.data); + normalizedPath = Ember.Handlebars.normalizePath(helperParameters.context, path, helperParameters.options.data); this.registerObserver(normalizedPath.root, normalizedPath.path, this, this._paramsChanged); } + }, - - }, - - /** - @private - - This method is invoked by observers installed during `init` that fire - whenever the params change - @method _paramsChanged - */ - _paramsChanged: function() { - this.notifyPropertyChange('resolvedParams'); + afterRender: function(){ + this._super.apply(this, arguments); + this._setupPathObservers(); }, - /** @private This method is invoked by observers installed during `init` that fire whenever the query params change @@ -33754,11 +34452,11 @@ CSS `class` to the element when the link is disabled. When `true` interactions with the element will not trigger route changes. @property disabled */ - disabled: Ember.computed(function(key, value) { + disabled: Ember.computed(function computeLinkViewDisabled(key, value) { if (value !== undefined) { this.set('_isDisabled', value); } return value ? get(this, 'disabledClass') : false; }), @@ -33770,11 +34468,11 @@ or the application's current route is the route the `LinkView` would trigger transitions into. @property active **/ - active: Ember.computed(function() { + active: Ember.computed(function computeLinkViewActive() { if (get(this, 'loading')) { return false; } var router = get(this, 'router'), routeArgs = get(this, 'routeArgs'), contexts = routeArgs.slice(1), @@ -33796,11 +34494,11 @@ 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: Ember.computed(function() { + loading: Ember.computed(function computeLinkViewLoading() { if (!get(this, 'routeArgs')) { return get(this, 'loadingClass'); } }).property('routeArgs'), /** @private @@ -33870,22 +34568,20 @@ any dynamic segments. @property @return {Array} An array with the route name and any dynamic segments */ - routeArgs: Ember.computed(function() { + routeArgs: Ember.computed(function computeLinkViewRouteArgs() { var resolvedParams = get(this, 'resolvedParams').slice(0), router = get(this, 'router'), namedRoute = resolvedParams[0]; if (!namedRoute) { return; } namedRoute = fullRouteName(router, namedRoute); resolvedParams[0] = namedRoute; - Ember.assert(fmt("The attempt to link-to route '%@' failed. The router did not find '%@' in its possible routes: '%@'", [namedRoute, namedRoute, Ember.keys(router.router.recognizer.names).join("', '")]), router.hasRoute(namedRoute)); - for (var i = 1, len = resolvedParams.length; i < len; ++i) { var param = resolvedParams[i]; if (param === null || typeof param === 'undefined') { // If contexts aren't present, consider the linkView unloaded. return; @@ -33932,11 +34628,11 @@ If the `LinkView`'s `tagName` is changed to a value other than `a`, this property will be ignored. @property href **/ - href: Ember.computed(function() { + href: Ember.computed(function computeLinkViewHref() { if (get(this, 'tagName') !== 'a') { return; } var router = get(this, 'router'), routeArgs = get(this, 'routeArgs'); @@ -33992,48 +34688,48 @@ </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. + 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 + + ```javascript Ember.LinkView.reopen({ disabled: Ember.computed(function(key, value) { - if (value !== undefined) { - this.set('_isDisabled', value === true); + 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 @@ -34216,11 +34912,11 @@ @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} */ - Ember.Handlebars.registerHelper('link-to', function(name) { + Ember.Handlebars.registerHelper('link-to', function linkToHelper(name) { var options = [].slice.call(arguments, -1)[0], params = [].slice.call(arguments, 0, -1), hash = options.hash; hash.disabledBinding = hash.disabledWhen; @@ -34258,11 +34954,14 @@ @deprecated @param {String} routeName @param {Object} [context]* @return {String} HTML string */ - Ember.Handlebars.registerHelper('linkTo', Ember.Handlebars.helpers['link-to']); + Ember.Handlebars.registerHelper('linkTo', function linkToHelper() { + Ember.warn("The 'linkTo' view helper is deprecated in favor of 'link-to'"); + return Ember.Handlebars.helpers['link-to'].apply(this, arguments); + }); }); })(); @@ -34345,11 +35044,11 @@ @for Ember.Handlebars.helpers @param {String} property the property on the controller that holds the view for this outlet @return {String} HTML string */ - Handlebars.registerHelper('outlet', function(property, options) { + Handlebars.registerHelper('outlet', function outletHelper(property, options) { var outletSource, outletContainerClass; if (property && property.data && property.data.isRenderData) { options = property; property = 'main'; @@ -34450,11 +35149,11 @@ @param {String} name @param {Object?} contextString @param {Hash} options @return {String} HTML string */ - Ember.Handlebars.registerHelper('render', function(name, contextString, options) { + Ember.Handlebars.registerHelper('render', function renderHelper(name, contextString, options) { Ember.assert("You must pass a template to render", arguments.length >= 2); var contextProvided = arguments.length === 3, container, router, controller, view, context, lookupOptions; if (arguments.length === 2) { @@ -34579,11 +35278,11 @@ ActionHelper.registerAction = function(actionName, options, allowedKeys) { var actionId = (++Ember.uuid).toString(); ActionHelper.registeredActions[actionId] = { eventName: options.eventName, - handler: function(event) { + handler: function handleRegisteredAction(event) { if (!isAllowedEvent(event, allowedKeys)) { return true; } if (options.preventDefault !== false) { event.preventDefault(); } @@ -34598,11 +35297,11 @@ target = handlebarsGet(target.root, target.target, target.options); } else { target = target.root; } - Ember.run(function() { + Ember.run(function runRegisteredAction() { if (target.send) { target.send.apply(target, args(options.parameters, actionName)); } else { Ember.assert("The action '" + actionName + "' did not exist on " + target, typeof target[actionName] === 'function'); target[actionName].apply(target, args(options.parameters)); @@ -34641,12 +35340,11 @@ ```javascript App.ApplicationController = Ember.Controller.extend({ actions: { anActionName: function() { - - } + } } }); ``` Will result in the following rendered HTML @@ -34785,11 +35483,11 @@ @for Ember.Handlebars.helpers @param {String} actionName @param {Object} [context]* @param {Hash} options */ - EmberHandlebars.registerHelper('action', function(actionName) { + EmberHandlebars.registerHelper('action', function actionHelper(actionName) { var options = arguments[arguments.length - 1], contexts = a_slice.call(arguments, 1, -1); var hash = options.hash, controller; @@ -34848,32 +35546,31 @@ /** 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'); + 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); + aController.transitionToRoute('blogPost', aPost); ``` Multiple models will be applied last to first recursively up the resource tree. ```javascript + this.resource('blogPost', {path:':blogPostId'}, function(){ + this.resource('blogComment', {path: ':blogCommentId'}); + }); - this.resource('blogPost', {path:':blogPostId'}, function(){ - this.resource('blogComment', {path: ':blogCommentId'}); - }); - - aController.transitionToRoute('blogComment', aPost, aComment); + aController.transitionToRoute('blogComment', aPost, aComment); ``` See also 'replaceRoute'. @param {String} name the name of the route @@ -34903,32 +35600,31 @@ 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'); + 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); + aController.replaceRoute('blogPost', aPost); ``` Multiple models will be applied last to first recursively up the resource tree. ```javascript + this.resource('blogPost', {path:':blogPostId'}, function(){ + this.resource('blogComment', {path: ':blogCommentId'}); + }); - this.resource('blogPost', {path:':blogPostId'}, function(){ - this.resource('blogComment', {path: ':blogCommentId'}); - }); - - aController.replaceRoute('blogComment', aPost, aComment); + aController.replaceRoute('blogComment', aPost, aComment); ``` @param {String} name the name of the route @param {...Object} models the model(s) to be used while transitioning to the route. @@ -35463,11 +36159,10 @@ */ Ember.HistoryLocation = Ember.Object.extend({ init: function() { set(this, 'location', get(this, 'location') || window.location); - this.initState(); }, /** @private @@ -36424,11 +37119,11 @@ var self = this; if (!this.$ || this.$.isReady) { Ember.run.schedule('actions', self, '_initialize'); } else { - this.$().ready(function() { + this.$().ready(function runInitialize() { Ember.run(self, '_initialize'); }); } }, @@ -36933,10 +37628,13 @@ function verifyNeedsDependencies(controller, container, needs) { var dependency, i, l; for (i=0, l=needs.length; i<l; i++) { dependency = needs[i]; + + Ember.assert(Ember.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 @@ -36944,10 +37642,35 @@ Ember.assert(Ember.inspect(controller) + " needs " + dependency + " but it does not exist", false); } } } +var defaultControllersComputedProperty = Ember.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 = Ember.inspect(controller) + '#needs does not include `' + controllerName + '`. To access the ' + controllerName + ' controller from ' + Ember.inspect(controller) + ', ' + Ember.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 " + Ember.inspect(controller)); + } + }; +}); + /** @class ControllerMixin @namespace Ember */ Ember.ControllerMixin.reopen({ @@ -37005,13 +37728,15 @@ init: function() { var needs = get(this, 'needs'), length = get(needs, 'length'); if (length > 0) { - Ember.assert(' `' + Ember.inspect(this) + ' specifies `needs`, but does not have a container. Please ensure this controller was instantiated with a container.', this.container); + Ember.assert(' `' + Ember.inspect(this) + ' specifies `needs`, but does not have a container. Please ensure this controller was instantiated with a container.', this.container || Ember.meta(this, false).descs.controllers !== defaultControllersComputedProperty); - verifyNeedsDependencies(this, this.container, needs); + if (this.container) { + verifyNeedsDependencies(this, this.container, needs); + } // if needs then initialize controllers proxy get(this, 'controllers'); } @@ -37045,31 +37770,11 @@ @see {Ember.ControllerMixin#needs} @property {Object} controllers @default null */ - controllers: Ember.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 = Ember.inspect(controller) + '#needs does not include `' + controllerName + '`. To access the ' + controllerName + ' controller from ' + Ember.inspect(controller) + ', ' + Ember.inspect(controller) + ' should have a `needs` property that is an array of the controllers it has access to.'; - throw new ReferenceError(errorMessage); - } - }; - }).readOnly() + controllers: defaultControllersComputedProperty }); })(); @@ -37933,13 +38638,10 @@ } for(var i = 0, l = injectHelpersCallbacks.length; i < l; i++) { injectHelpersCallbacks[i](this); } - - Ember.RSVP.on('error', onerror); - Ember.RSVP.off('error', Ember.RSVP.onerrorDefault); }, /** This removes all helpers that have been registered, and resets and functions that were overridden by the helpers. @@ -37956,14 +38658,11 @@ for (var name in helpers) { this.helperContainer[name] = this.originalMethods[name]; delete this.testHelpers[name]; delete this.originalMethods[name]; } - Ember.RSVP.off('error', onerror); - Ember.RSVP.on('error', Ember.RSVP.onerrorDefault); } - }); // This method is no longer needed // But still here for backwards compatibility // of helper chaining @@ -38027,14 +38726,10 @@ }); return lastPromise; } } -function onerror(event) { - Ember.Test.adapter.exception(event.detail); -} - })(); (function() { @@ -38166,12 +38861,10 @@ @public @method exception @param {String} error The exception to be raised. */ exception: function(error) { - setTimeout(function() { - throw error; - }); + throw error; } }); /** This class implements the methods defined by Ember.Test.Adapter for the