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

- old
+ new

@@ -1,17 +1,16 @@ -// ========================================================================== -// Project: Ember - JavaScript Application Framework -// Copyright: Copyright 2011-2013 Tilde Inc. and contributors -// Portions Copyright 2006-2011 Strobe Inc. -// Portions Copyright 2008-2011 Apple Inc. All rights reserved. -// License: Licensed under MIT license -// See https://raw.github.com/emberjs/ember.js/master/LICENSE -// ========================================================================== +/*! + * @overview Ember - JavaScript Application Framework + * @copyright Copyright 2011-2013 Tilde Inc. and contributors + * Portions Copyright 2006-2011 Strobe Inc. + * Portions Copyright 2008-2011 Apple Inc. All rights reserved. + * @license Licensed under MIT license + * See https://raw.github.com/emberjs/ember.js/master/LICENSE + * @version 1.4.0-beta.1+canary.dc0dc0ce + */ - // Version: 1.3.0-beta.3 - (function() { var define, requireModule, require, requirejs; (function() { var registry = {}, seen = {}; @@ -87,11 +86,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.3 + @version 1.4.0-beta.1+canary.dc0dc0ce */ 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. @@ -114,14 +113,14 @@ /** @property VERSION @type String - @default '1.3.0-beta.3' + @default '1.4.0-beta.1+canary.dc0dc0ce' @final */ -Ember.VERSION = '1.3.0-beta.3'; +Ember.VERSION = '1.4.0-beta.1+canary.dc0dc0ce'; /** Standard environmental variables. You can define these in a global `ENV` variable before loading Ember to control various configuration settings. @@ -1358,10 +1357,45 @@ } return ret; }; +/** + Convenience method to inspect an object. This method will attempt to + convert the object into a useful string description. + + It is a pretty simple implementation. If you want something more robust, + use something like JSDump: https://github.com/NV/jsDump + + @method inspect + @for Ember + @param {Object} obj The object you want to inspect. + @return {String} A description of the object +*/ +Ember.inspect = function(obj) { + var type = Ember.typeOf(obj); + if (type === 'array') { + return '[' + obj + ']'; + } + if (type !== 'object') { + return obj + ''; + } + + var v, ret = []; + for(var key in obj) { + if (obj.hasOwnProperty(key)) { + v = obj[key]; + if (v === 'toString') { continue; } // ignore useless items + if (Ember.typeOf(v) === 'function') { v = "function() { ... }"; } + ret.push(key + ": " + v); + } + } + return "{" + ret.join(", ") + "}"; +}; + + + })(); (function() { @@ -1768,11 +1802,11 @@ target = get(target, key); path = path.slice(key.length+1); } // must return some kind of path to be valid else other things will break. - if (!path || path.length===0) throw new Ember.Error('Invalid Path'); + if (!path || path.length===0) throw new Ember.Error('Path cannot be empty'); return [ target, path ]; }; var getPath = Ember._getPath = function(root, path) { @@ -3819,11 +3853,57 @@ })(); (function() { +if (Ember.FEATURES.isEnabled('propertyBraceExpansion')) { + /** + @module ember-metal + */ + var forEach = Ember.EnumerableUtils.forEach, + BRACE_EXPANSION = /^((?:[^\.]*\.)*)\{(.*)\}$/; + + /** + Expands `pattern`, invoking `callback` for each expansion. + + The only pattern supported is brace-expansion, anything else will be passed + once to `callback` directly. Brace expansion can only appear at the end of a + pattern, for example as the last item in a chain. + + Example + ```js + function echo(arg){ console.log(arg); } + + Ember.expandProperties('foo.bar', echo); //=> 'foo.bar' + Ember.expandProperties('{foo,bar}', echo); //=> 'foo', 'bar' + Ember.expandProperties('foo.{bar,baz}', echo); //=> 'foo.bar', 'foo.baz' + Ember.expandProperties('{foo,bar}.baz', echo); //=> '{foo,bar}.baz' + ``` + + @method + @private + @param {string} pattern The property pattern to expand. + @param {function} callback The callback to invoke. It is invoked once per + expansion, and is passed the expansion. + */ + Ember.expandProperties = function (pattern, callback) { + var match, prefix, list; + + if (match = BRACE_EXPANSION.exec(pattern)) { + prefix = match[1]; + list = match[2]; + + forEach(list.split(','), function (suffix) { + callback(prefix + suffix); + }); + } else { + callback(pattern); + } + }; +} + })(); (function() { @@ -4023,10 +4103,13 @@ o_create = Ember.create, META_KEY = Ember.META_KEY, watch = Ember.watch, unwatch = Ember.unwatch; +if (Ember.FEATURES.isEnabled('propertyBraceExpansion')) { + var expandProperties = Ember.expandProperties; +} // .......................................................... // DEPENDENT KEYS // @@ -4287,13 +4370,22 @@ @chainable */ ComputedPropertyPrototype.property = function() { var args; - + if (Ember.FEATURES.isEnabled('propertyBraceExpansion')) { + var addArg = function (property) { + args.push(property); + }; + + args = []; + for (var i = 0, l = arguments.length; i < l; i++) { + expandProperties(arguments[i], addArg); + } + } else { args = a_slice.call(arguments); - + } this._dependentKeys = args; return this; }; @@ -4418,11 +4510,11 @@ hadCachedValue = false, cache = meta.cache, funcArgLength, cachedValue, ret; if (this._readOnly) { - throw new Ember.Error('Cannot Set: ' + keyName + ' on: ' + obj.toString() ); + throw new Ember.Error('Cannot Set: ' + keyName + ' on: ' + Ember.inspect(obj)); } this._suspended = obj; try { @@ -5095,11 +5187,55 @@ return Ember.computed(dependentKey, function() { return get(this, dependentKey); }); }; +if (Ember.FEATURES.isEnabled('computed-read-only')) { +/** + Where `computed.oneWay` provides oneWay bindings, `computed.readOnly` provides + a readOnly one way binding. Very often when using `computed.oneWay` one does + not also want changes to propogate back up, as they will replace the value. + This prevents the reverse flow, and also throws an exception when it occurs. + + Example + + ```javascript + User = Ember.Object.extend({ + firstName: null, + lastName: null, + nickName: Ember.computed.readOnly('firstName') + }); + + user = User.create({ + firstName: 'Teddy', + lastName: 'Zeenny' + }); + + user.get('nickName'); + # 'Teddy' + + user.set('nickName', 'TeddyBear'); + # throws Exception + # throw new Ember.Error('Cannot Set: nickName on: <User:ember27288>' );` + + user.get('firstName'); + # 'Teddy' + ``` + + @method computed.readOnly + @for Ember + @param {String} dependentKey + @return {Ember.ComputedProperty} computed property which creates a + one way computed property to the original value for property. +*/ +Ember.computed.readOnly = function(dependentKey) { + return Ember.computed(dependentKey, function() { + return get(this, dependentKey); + }).readOnly(); +}; +} /** A computed property that acts like a standard getter and setter, but returns the value at the provided `defaultPath` if the property itself has not been set to a value @@ -6173,11 +6309,11 @@ @param {Object} [target] The target of the method to invoke. @param {Function|String} method The method to invoke. If you pass a string it will be resolved on the target at the time the method is invoked. @param {Object} [args*] Optional arguments to pass to the timeout. - @return {Object} timer + @return {Object} Timer information for use in cancelling, see `Ember.run.cancel`. */ Ember.run.once = function(target, method) { checkAutoRun(); var args = slice.call(arguments); args.unshift('actions'); @@ -6224,11 +6360,11 @@ @param {Object} [target] The target of the method to invoke. @param {Function|String} method The method to invoke. If you pass a string it will be resolved on the target at the time the method is invoked. @param {Object} [args*] Optional arguments to pass to the timeout. - @return {Object} timer + @return {Object} Timer information for use in cancelling, see `Ember.run.cancel`. */ Ember.run.scheduleOnce = function(queue, target, method) { checkAutoRun(); return backburner.scheduleOnce.apply(backburner, arguments); }; @@ -6286,21 +6422,22 @@ @param {Object} [target] target of method to invoke @param {Function|String} method The method to invoke. If you pass a string it will be resolved on the target at the time the method is invoked. @param {Object} [args*] Optional arguments to pass to the timeout. - @return {Object} timer + @return {Object} Timer information for use in cancelling, see `Ember.run.cancel`. */ Ember.run.next = function() { var args = slice.call(arguments); args.push(1); return backburner.later.apply(backburner, args); }; /** Cancels a scheduled item. Must be a value returned by `Ember.run.later()`, - `Ember.run.once()`, or `Ember.run.next()`. + `Ember.run.once()`, `Ember.run.next()`, `Ember.run.debounce()`, or + `Ember.run.throttle()`. ```javascript var runNext = Ember.run.next(myContext, function() { // will not be executed }); @@ -6313,15 +6450,33 @@ var runOnce = Ember.run.once(myContext, function() { // will not be executed }); Ember.run.cancel(runOnce); + + var throttle = Ember.run.throttle(myContext, function() { + // will not be executed + }, 1); + Ember.run.cancel(throttle); + + var debounce = Ember.run.debounce(myContext, function() { + // will not be executed + }, 1); + Ember.run.cancel(debounce); + + var debounceImmediate = Ember.run.debounce(myContext, function() { + // will be executed since we passed in true (immediate) + }, 100, true); + // the 100ms delay until this method can be called again will be cancelled + Ember.run.cancel(debounceImmediate); ``` + ``` + ``` @method cancel @param {Object} timer Timer object to cancel - @return {void} + @return {Boolean} true if cancelled or false/undefined if it wasn't found */ Ember.run.cancel = function(timer) { return backburner.cancel(timer); }; @@ -6349,19 +6504,47 @@ // 150ms passes // myFunc is invoked with context myContext // console logs 'debounce ran.' one time. ``` + Immediate allows you to run the function immediately, but debounce + other calls for this function until the wait time has elapsed. If + `debounce` is called again before the specified time has elapsed, + the timer is reset and the entire period msut pass again before + the method can be called again. + + ```javascript + var myFunc = function() { console.log(this.name + ' ran.'); }; + var myContext = {name: 'debounce'}; + + Ember.run.debounce(myContext, myFunc, 150, true); + + // console logs 'debounce ran.' one time immediately. + // 100ms passes + + Ember.run.debounce(myContext, myFunc, 150, true); + + // 150ms passes and nothing else is logged to the console and + // the debouncee is no longer being watched + + Ember.run.debounce(myContext, myFunc, 150, true); + + // console logs 'debounce ran.' one time immediately. + // 150ms passes and nothing else is logged tot he console and + // the debouncee is no longer being watched + + ``` + @method debounce @param {Object} [target] target of method to invoke @param {Function|String} method The method to invoke. May be a function or a string. If you pass a string then it will be looked up on the passed target. @param {Object} [args*] Optional arguments to pass to the timeout. @param {Number} wait Number of milliseconds to wait. @param {Boolean} immediate Trigger the function on the leading instead of the trailing edge of the wait interval. - @return {void} + @return {Array} Timer information for use in cancelling, see `Ember.run.cancel`. */ Ember.run.debounce = function() { return backburner.debounce.apply(backburner, arguments); }; @@ -6394,11 +6577,11 @@ @param {Function|String} method The method to invoke. May be a function or a string. If you pass a string then it will be looked up on the passed target. @param {Object} [args*] Optional arguments to pass to the timeout. @param {Number} spacing Number of milliseconds to space out requests. - @return {void} + @return {Array} Timer information for use in cancelling, see `Ember.run.cancel`. */ Ember.run.throttle = function() { return backburner.throttle.apply(backburner, arguments); }; @@ -6904,10 +7087,13 @@ a_slice = [].slice, o_create = Ember.create, defineProperty = Ember.defineProperty, guidFor = Ember.guidFor; +if (Ember.FEATURES.isEnabled('propertyBraceExpansion')) { + var expandProperties = Ember.expandProperties; +} function mixinsMeta(obj) { var m = Ember.meta(obj, true), ret = m.mixins; if (!ret) { ret = m.mixins = {}; @@ -7478,39 +7664,10 @@ this.methodName = methodName; }; Alias.prototype = new Ember.Descriptor(); /** - Makes a property or method available via an additional name. - - ```javascript - App.PaintSample = Ember.Object.extend({ - color: 'red', - colour: Ember.alias('color'), - name: function() { - return "Zed"; - }, - moniker: Ember.alias("name") - }); - - var paintSample = App.PaintSample.create() - paintSample.get('colour'); // 'red' - paintSample.moniker(); // 'Zed' - ``` - - @method alias - @for Ember - @param {String} methodName name of the method or property to alias - @return {Ember.Descriptor} - @deprecated Use `Ember.aliasMethod` or `Ember.computed.alias` instead -*/ -Ember.alias = function(methodName) { - - return new Alias(methodName); -}; - -/** Makes a method available via an additional name. ```javascript App.Person = Ember.Object.extend({ name: function() { @@ -7560,20 +7717,36 @@ */ Ember.observer = function() { var func = a_slice.call(arguments, -1)[0]; var paths; - + if (Ember.FEATURES.isEnabled('propertyBraceExpansion')) { + var addWatchedProperty = function (path) { paths.push(path); }; + var _paths = a_slice.call(arguments, 0, -1); + + if (typeof func !== "function") { + // revert to old, soft-deprecated argument ordering + + func = arguments[0]; + _paths = a_slice.call(arguments, 1); + } + + paths = []; + + for (var i=0; i<_paths.length; ++i) { + expandProperties(_paths[i], addWatchedProperty); + } + } else { paths = a_slice.call(arguments, 0, -1); if (typeof func !== "function") { // revert to old, soft-deprecated argument ordering func = arguments[0]; paths = a_slice.call(arguments, 1); } - + } if (typeof func !== "function") { throw new Ember.Error("Ember.observer called without a function"); } @@ -7658,20 +7831,37 @@ */ Ember.beforeObserver = function() { var func = a_slice.call(arguments, -1)[0]; var paths; - + if (Ember.FEATURES.isEnabled('propertyBraceExpansion')) { + var addWatchedProperty = function(path) { paths.push(path); }; + + var _paths = a_slice.call(arguments, 0, -1); + + if (typeof func !== "function") { + // revert to old, soft-deprecated argument ordering + + func = arguments[0]; + _paths = a_slice.call(arguments, 1); + } + + paths = []; + + for (var i=0; i<_paths.length; ++i) { + expandProperties(_paths[i], addWatchedProperty); + } + } else { paths = a_slice.call(arguments, 0, -1); if (typeof func !== "function") { // revert to old, soft-deprecated argument ordering func = arguments[0]; paths = a_slice.call(arguments, 1); } - + } if (typeof func !== "function") { throw new Ember.Error("Ember.beforeObserver called without a function"); } @@ -7745,11 +7935,11 @@ (function() { /** @class RSVP @module RSVP */ -define("rsvp/all", +define("rsvp/all", ["./promise","./utils","exports"], function(__dependency1__, __dependency2__, __exports__) { "use strict"; /* global toString */ @@ -7841,11 +8031,11 @@ } __exports__.all = all; }); -define("rsvp/cast", +define("rsvp/cast", ["exports"], function(__exports__) { "use strict"; /** `RSVP.Promise.cast` returns the same promise if that promise shares a constructor @@ -7912,11 +8102,11 @@ }); } __exports__.cast = cast; }); -define("rsvp/config", +define("rsvp/config", ["./events","exports"], function(__dependency1__, __exports__) { "use strict"; var EventTarget = __dependency1__.EventTarget; @@ -7943,11 +8133,11 @@ } __exports__.config = config; __exports__.configure = configure; }); -define("rsvp/defer", +define("rsvp/defer", ["./promise","exports"], function(__dependency1__, __exports__) { "use strict"; var Promise = __dependency1__.Promise; @@ -7998,11 +8188,11 @@ return deferred; } __exports__.defer = defer; }); -define("rsvp/events", +define("rsvp/events", ["exports"], function(__exports__) { "use strict"; var indexOf = function(callbacks, callback) { for (var i=0, l=callbacks.length; i<l; i++) { @@ -8207,11 +8397,11 @@ } }; __exports__.EventTarget = EventTarget; }); -define("rsvp/hash", +define("rsvp/hash", ["./promise","./utils","exports"], function(__dependency1__, __dependency2__, __exports__) { "use strict"; var Promise = __dependency1__.Promise; var isFunction = __dependency2__.isFunction; @@ -8352,11 +8542,11 @@ }); } __exports__.hash = hash; }); -define("rsvp/instrument", +define("rsvp/instrument", ["./config","./utils","exports"], function(__dependency1__, __dependency2__, __exports__) { "use strict"; var config = __dependency1__.config; var now = __dependency2__.now; @@ -8379,11 +8569,11 @@ } } __exports__.instrument = instrument; }); -define("rsvp/node", +define("rsvp/node", ["./promise","./all","exports"], function(__dependency1__, __dependency2__, __exports__) { "use strict"; var Promise = __dependency1__.Promise; var all = __dependency2__.all; @@ -8494,11 +8684,11 @@ }; } __exports__.denodeify = denodeify; }); -define("rsvp/promise", +define("rsvp/promise", ["./config","./events","./cast","./instrument","./utils","exports"], function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) { "use strict"; var config = __dependency1__.config; var EventTarget = __dependency2__.EventTarget; @@ -8743,11 +8933,11 @@ publish(promise, promise._state = REJECTED); } __exports__.Promise = Promise; }); -define("rsvp/race", +define("rsvp/race", ["./promise","./utils","exports"], function(__dependency1__, __dependency2__, __exports__) { "use strict"; /* global toString */ @@ -8835,11 +9025,11 @@ }, label); } __exports__.race = race; }); -define("rsvp/reject", +define("rsvp/reject", ["./promise","exports"], function(__dependency1__, __exports__) { "use strict"; var Promise = __dependency1__.Promise; @@ -8885,11 +9075,11 @@ }, label); } __exports__.reject = reject; }); -define("rsvp/resolve", +define("rsvp/resolve", ["./promise","exports"], function(__dependency1__, __exports__) { "use strict"; var Promise = __dependency1__.Promise; @@ -8931,11 +9121,11 @@ }, label); } __exports__.resolve = resolve; }); -define("rsvp/rethrow", +define("rsvp/rethrow", ["exports"], function(__exports__) { "use strict"; var local = (typeof global === "undefined") ? this : global; @@ -8984,11 +9174,11 @@ throw reason; } __exports__.rethrow = rethrow; }); -define("rsvp/utils", +define("rsvp/utils", ["exports"], function(__exports__) { "use strict"; function objectOrFunction(x) { return isFunction(x) || (typeof x === "object" && x !== null); @@ -9010,11 +9200,11 @@ __exports__.objectOrFunction = objectOrFunction; __exports__.isFunction = isFunction; __exports__.isArray = isArray; __exports__.now = now; }); -define("rsvp", +define("rsvp", ["./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; @@ -9054,10 +9244,11 @@ __exports__.off = off; __exports__.resolve = resolve; __exports__.reject = reject; __exports__.async = async; }); + })(); (function() { /** @private @@ -9074,10 +9265,11 @@ Ember.MODEL_FACTORY_INJECTIONS = false || !!Ember.ENV.MODEL_FACTORY_INJECTIONS; define("container", [], function() { + "use strict"; // A safe and simple inheriting object. function InheritingDict(parent) { this.parent = parent; this.dict = {}; @@ -9195,11 +9387,12 @@ this.resolver = parent && parent.resolver || function() {}; this.registry = new InheritingDict(parent && parent.registry); this.cache = new InheritingDict(parent && parent.cache); - this.factoryCache = new InheritingDict(parent && parent.cache); + this.factoryCache = new InheritingDict(parent && parent.factoryCache); + this.resolveCache = new InheritingDict(parent && parent.resolveCache); this.typeInjections = new InheritingDict(parent && parent.typeInjections); this.injections = {}; this.factoryTypeInjections = new InheritingDict(parent && parent.factoryTypeInjections); this.factoryInjections = {}; @@ -9316,13 +9509,11 @@ @param {String} fullName @param {Function} factory @param {Object} options */ register: function(fullName, factory, options) { - if (fullName.indexOf(':') === -1) { - throw new TypeError("malformed fullName, expected: `type:name` got: " + fullName + ""); - } + validateFullName(fullName); if (factory === undefined) { throw new TypeError('Attempting to register an unknown factory: `' + fullName + '`'); } @@ -9351,15 +9542,18 @@ @method unregister @param {String} fullName */ unregister: function(fullName) { + validateFullName(fullName); + var normalizedName = this.normalize(fullName); this.registry.remove(normalizedName); this.cache.remove(normalizedName); this.factoryCache.remove(normalizedName); + this.resolveCache.remove(normalizedName); this._options.remove(normalizedName); }, /** Given a fullName return the corresponding factory. @@ -9392,11 +9586,22 @@ @method resolve @param {String} fullName @return {Function} fullName's factory */ resolve: function(fullName) { - return this.resolver(fullName) || this.registry.get(fullName); + validateFullName(fullName); + + var normalizedName = this.normalize(fullName); + var cached = this.resolveCache.get(normalizedName); + + if (cached) { return cached; } + + var resolved = this.resolver(normalizedName) || this.registry.get(normalizedName); + + this.resolveCache.set(normalizedName, resolved); + + return resolved; }, /** A hook that can be used to describe how the resolver will attempt to find the factory. @@ -9473,38 +9678,24 @@ @param {String} fullName @param {Object} options @return {any} */ lookup: function(fullName, options) { - fullName = this.normalize(fullName); - - options = options || {}; - - if (this.cache.has(fullName) && options.singleton !== false) { - return this.cache.get(fullName); - } - - var value = instantiate(this, fullName); - - if (value === undefined) { return; } - - if (isSingleton(this, fullName) && options.singleton !== false) { - this.cache.set(fullName, value); - } - - return value; + validateFullName(fullName); + return lookup(this, this.normalize(fullName), options); }, /** Given a fullName return the corresponding factory. @method lookupFactory @param {String} fullName @return {any} */ lookupFactory: function(fullName) { - return factoryFor(this, fullName); + validateFullName(fullName); + return factoryFor(this, this.normalize(fullName)); }, /** Given a fullName check if the container is aware of its factory or singleton instance. @@ -9512,15 +9703,12 @@ @method has @param {String} fullName @return {Boolean} */ has: function(fullName) { - if (this.cache.has(fullName)) { - return true; - } - - return !!this.resolve(fullName); + validateFullName(fullName); + return has(this, this.normalize(fullName)); }, /** Allow registering options for all factories of a type. @@ -9598,10 +9786,11 @@ @param {String} type @param {String} property @param {String} fullName */ typeInjection: function(type, property, fullName) { + validateFullName(fullName); if (this.parent) { illegalChildOperation('typeInjection'); } addTypeInjection(this.typeInjections, type, property, fullName); }, @@ -9647,18 +9836,24 @@ @method injection @param {String} factoryName @param {String} property @param {String} injectionName */ - injection: function(factoryName, property, injectionName) { + injection: function(fullName, property, injectionName) { if (this.parent) { illegalChildOperation('injection'); } - if (factoryName.indexOf(':') === -1) { - return this.typeInjection(factoryName, property, injectionName); + validateFullName(injectionName); + var normalizedInjectionName = this.normalize(injectionName); + + if (fullName.indexOf(':') === -1) { + return this.typeInjection(fullName, property, normalizedInjectionName); } - addInjection(this.injections, factoryName, property, injectionName); + validateFullName(fullName); + var normalizedName = this.normalize(fullName); + + addInjection(this.injections, normalizedName, property, normalizedInjectionName); }, /** @private @@ -9691,11 +9886,11 @@ @param {String} fullName */ factoryTypeInjection: function(type, property, fullName) { if (this.parent) { illegalChildOperation('factoryTypeInjection'); } - addTypeInjection(this.factoryTypeInjections, type, property, fullName); + addTypeInjection(this.factoryTypeInjections, type, property, this.normalize(fullName)); }, /** Defines factory injection rules. @@ -9743,28 +9938,34 @@ @method factoryInjection @param {String} factoryName @param {String} property @param {String} injectionName */ - factoryInjection: function(factoryName, property, injectionName) { + factoryInjection: function(fullName, property, injectionName) { if (this.parent) { illegalChildOperation('injection'); } - if (factoryName.indexOf(':') === -1) { - return this.factoryTypeInjection(factoryName, property, injectionName); + var normalizedName = this.normalize(fullName); + var normalizedInjectionName = this.normalize(injectionName); + + validateFullName(injectionName); + + if (fullName.indexOf(':') === -1) { + return this.factoryTypeInjection(normalizedName, property, normalizedInjectionName); } - addInjection(this.factoryInjections, factoryName, property, injectionName); + validateFullName(fullName); + + addInjection(this.factoryInjections, normalizedName, property, normalizedInjectionName); }, /** A depth first traversal, destroying the container, its descendant containers and all their managed objects. @method destroy */ destroy: function() { - for (var i=0, l=this.children.length; i<l; i++) { this.children[i].destroy(); } this.children = []; @@ -9786,10 +9987,36 @@ } resetCache(this); } }; + function has(container, fullName){ + if (container.cache.has(fullName)) { + return true; + } + + return !!container.resolve(fullName); + } + + function lookup(container, fullName, options) { + options = options || {}; + + if (container.cache.has(fullName) && options.singleton !== false) { + return container.cache.get(fullName); + } + + var value = instantiate(container, fullName); + + if (value === undefined) { return; } + + if (isSingleton(container, fullName) && options.singleton !== false) { + container.cache.set(fullName, value); + } + + return value; + } + function illegalChildOperation(operation) { throw new Error(operation + " is not currently supported on child containers"); } function isSingleton(container, fullName) { @@ -9801,18 +10028,18 @@ function buildInjections(container, injections) { var hash = {}; if (!injections) { return hash; } - var injection, lookup; + var injection, injectable; for (var i=0, l=injections.length; i<l; i++) { injection = injections[i]; - lookup = container.lookup(injection.fullName); + injectable = lookup(container, injection.fullName); - if (lookup !== undefined) { - hash[injection.property] = lookup; + if (injectable !== undefined) { + hash[injection.property] = injectable; } else { throw new Error('Attempting to inject an unknown injection: `' + injection.fullName + '`'); } } @@ -9833,11 +10060,11 @@ return options[optionName]; } } function factoryFor(container, fullName) { - var name = container.normalize(fullName); + var name = fullName; var factory = container.resolve(name); var injectedFactory; var cache = container.factoryCache; var type = fullName.split(":")[0]; @@ -9943,10 +10170,17 @@ property: property, fullName: fullName }); } + var VALID_FULL_NAME_REGEXP = /^[^:]+.+:[^:]+$/; + function validateFullName(fullName) { + if (!VALID_FULL_NAME_REGEXP.test(fullName)) { + throw new TypeError('Invalid Fullname, expected: `type:name` got: ' + fullName); + } + } + function addInjection(rules, factoryName, property, injectionName) { var injections = rules[factoryName] = rules[factoryName] || []; injections.push({ property: property, fullName: injectionName }); } @@ -10137,43 +10371,10 @@ if (Ember.Copyable && Ember.Copyable.detect(obj)) return obj.copy(deep); return _copy(obj, deep, deep ? [] : null, deep ? [] : null); }; /** - Convenience method to inspect an object. This method will attempt to - convert the object into a useful string description. - - It is a pretty simple implementation. If you want something more robust, - use something like JSDump: https://github.com/NV/jsDump - - @method inspect - @for Ember - @param {Object} obj The object you want to inspect. - @return {String} A description of the object -*/ -Ember.inspect = function(obj) { - var type = Ember.typeOf(obj); - if (type === 'array') { - return '[' + obj + ']'; - } - if (type !== 'object') { - return obj + ''; - } - - var v, ret = []; - for(var key in obj) { - if (obj.hasOwnProperty(key)) { - v = obj[key]; - if (v === 'toString') { continue; } // ignore useless items - if (Ember.typeOf(v) === 'function') { v = "function() { ... }"; } - ret.push(key + ": " + v); - } - } - return "{" + ret.join(", ") + "}"; -}; - -/** Compares two objects, returning true if they are logically equal. This is a deeper comparison than a simple triple equal. For sets it will compare the internal objects. For any other object that implements `isEqual()` it will respect that method. @@ -10273,10 +10474,14 @@ var STRING_DASHERIZE_CACHE = {}; var STRING_DECAMELIZE_REGEXP = (/([a-z\d])([A-Z])/g); var STRING_CAMELIZE_REGEXP = (/(\-|_|\.|\s)+(.)?/g); var STRING_UNDERSCORE_REGEXP_1 = (/([a-z\d])([A-Z]+)/g); var STRING_UNDERSCORE_REGEXP_2 = (/\-|\s+/g); +var STRING_PARAMETERIZE_REGEXP_1 = (/[_|\/|\s]+/g); +var STRING_PARAMETERIZE_REGEXP_2 = (/[^a-z0-9\-]+/gi); +var STRING_PARAMETERIZE_REGEXP_3 = (/[\-]+/g); +var STRING_PARAMETERIZE_REGEXP_4 = (/^-+|-+$/g); /** Defines the hash of localized strings for the current language. Used by the `Ember.String.loc()` helper. To localize, add string values to this hash. @@ -10511,12 +10716,60 @@ capitalize: function(str) { return str.charAt(0).toUpperCase() + str.substr(1); } }; +if (Ember.FEATURES.isEnabled("string-humanize")) { + /** + Returns the Humanized form of a string + Replaces underscores with spaces, and capitializes first character + of string. Also strips "_id" suffixes. + ```javascript + 'first_name'.humanize() // 'First name' + 'user_id'.humanize() // 'User' + ``` + + @method humanize + @param {String} str The string to humanize. + @return {String} The humanized string. + */ + + Ember.String.humanize = function(str) { + return str.replace(/_id$/, ''). + replace(/_/g, ' '). + replace(/^\w/g, function(s){ + return s.toUpperCase(); + }); + }; +} + +if (Ember.FEATURES.isEnabled("string-parameterize")) { + /** + Transforms a string so that it may be used as part of a 'pretty' / SEO friendly URL. + + ```javascript + 'My favorite items.'.parameterize(); // 'my-favorite-items' + 'action_name'.parameterize(); // 'action-name' + '100 ways Ember.js is better than Angular.'.parameterize(); // '100-ways-emberjs-is-better-than-angular' + ``` + + @method parameterize + @param {String} str The string to parameterize. + @return {String} the parameterized string. + */ + Ember.String.parameterize = function(str) { + return str.replace(STRING_PARAMETERIZE_REGEXP_1, '-') // replace underscores, slashes and spaces with separator + .replace(STRING_PARAMETERIZE_REGEXP_2, '') // remove non-alphanumeric characters except the separator + .replace(STRING_PARAMETERIZE_REGEXP_3, '-') // replace multiple occurring separators + .replace(STRING_PARAMETERIZE_REGEXP_4, '') // trim leading and trailing separators + .toLowerCase(); + }; +} + + })(); (function() { @@ -10535,11 +10788,18 @@ dasherize = Ember.String.dasherize, underscore = Ember.String.underscore, capitalize = Ember.String.capitalize, classify = Ember.String.classify; +if (Ember.FEATURES.isEnabled("string-humanize")) { + var humanize = Ember.String.humanize; +} +if (Ember.FEATURES.isEnabled("string-parameterize")) { + var parameterize = Ember.String.parameterize; +} + if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) { /** See [Ember.String.fmt](/api/classes/Ember.String.html#method_fmt). @@ -10628,11 +10888,34 @@ */ String.prototype.capitalize = function() { return capitalize(this); }; - + if (Ember.FEATURES.isEnabled("string-humanize")) { + /** + See [Ember.String.humanize](/api/classes/Ember.String.html#method_humanize). + + @method humanize + @for String + */ + String.prototype.humanize = function() { + return humanize(this); + }; + } + + if (Ember.FEATURES.isEnabled("string-parameterize")) { + /** + See [Ember.String.parameterize](/api/classes/Ember.String.html#method_parameterize). + + @method parameterize + @for String + */ + String.prototype.parameterize = function() { + return parameterize(this); + }; + } + } })(); @@ -12998,11 +13281,11 @@ if (typeof callback !== "function") { throw new TypeError(); } var ret = initialValue; this.forEach(function(item, i) { - ret = callback.call(null, ret, item, i, this, reducerProperty); + ret = callback(ret, item, i, this, reducerProperty); }, this); return ret; }, @@ -13021,11 +13304,11 @@ if (arguments.length>1) args = a_slice.call(arguments, 1); this.forEach(function(x, idx) { var method = x && x[methodName]; if ('function' === typeof method) { - ret[idx] = args ? method.apply(x, args) : method.call(x); + ret[idx] = args ? method.apply(x, args) : x[methodName](); } }, this); return ret; }, @@ -13216,12 +13499,10 @@ implementing an ordered enumerable (such as an array), also pass the start and end values where the content changed so that it can be used to notify range observers. @method enumerableContentDidChange - @param {Number} [start] optional start offset for the content change. - For unordered enumerables, you should always pass -1. @param {Ember.Enumerable|Number} removing An enumerable of the objects to be removed or the number of items to be removed. @param {Ember.Enumerable|Number} adding An enumerable of the objects to be added or the number of items to be added. @chainable @@ -13427,11 +13708,11 @@ arr.slice(1, 100); // ['green', 'blue'] ``` @method slice @param {Integer} beginIndex (Optional) index to begin slicing from. - @param {Integer} endIndex (Optional) index to end the slice at. + @param {Integer} endIndex (Optional) index to end the slice at (but not included). @return {Array} New array with specified slice */ slice: function(beginIndex, endIndex) { var ret = Ember.A(); var length = get(this, 'length') ; @@ -13578,11 +13859,11 @@ /** Becomes true whenever the array currently has observers watching changes on the array. - @property Boolean + @property {Boolean} hasArrayObservers */ hasArrayObservers: Ember.computed(function() { return Ember.hasListeners(this, '@array:change') || Ember.hasListeners(this, '@array:before'); }), @@ -13727,10 +14008,13 @@ // testing, but there's no particular reason why it should be disallowed. eachPropertyPattern = /^(.*)\.@each\.(.*)/, doubleEachPropertyPattern = /(.*\.@each){2,}/, arrayBracketPattern = /\.\[\]$/; +if (Ember.FEATURES.isEnabled('propertyBraceExpansion')) { + var expandProperties = Ember.expandProperties; +} function get(obj, key) { if (key === '@this') { return obj; } @@ -14312,14 +14596,21 @@ if (doubleEachPropertyPattern.test(dependentKey)) { throw new Ember.Error("Nested @each properties not supported: " + dependentKey); } else if (match = eachPropertyPattern.exec(dependentKey)) { dependentArrayKey = match[1]; - + if (Ember.FEATURES.isEnabled('propertyBraceExpansion')) { + var itemPropertyKeyPattern = match[2], + addItemPropertyKey = function (itemPropertyKey) { + cp.itemPropertyKey(dependentArrayKey, itemPropertyKey); + }; + + expandProperties(itemPropertyKeyPattern, addItemPropertyKey); + } else { itemPropertyKey = match[2]; cp.itemPropertyKey(dependentArrayKey, itemPropertyKey); - + } propertyArgs.add(dependentArrayKey); } else { propertyArgs.add(dependentKey); } }); @@ -14426,11 +14717,11 @@ Example ```javascript Ember.computed.max = function (dependentKey) { - return Ember.reduceComputed.call(null, dependentKey, { + return Ember.reduceComputed(dependentKey, { initialValue: -Infinity, addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { return Math.max(accumulatedValue, item); }, @@ -14735,33 +15026,39 @@ /** A computed property that calculates the maximum value in the dependent array. This will return `-Infinity` when the dependent array is empty. - Example - ```javascript App.Person = Ember.Object.extend({ childAges: Ember.computed.mapBy('children', 'age'), maxChildAge: Ember.computed.max('childAges') }); var lordByron = App.Person.create({children: []}); lordByron.get('maxChildAge'); // -Infinity - lordByron.get('children').pushObject({name: 'Augusta Ada Byron', age: 7}); + lordByron.get('children').pushObject({ + name: 'Augusta Ada Byron', age: 7 + }); lordByron.get('maxChildAge'); // 7 - lordByron.get('children').pushObjects([{name: 'Allegra Byron', age: 5}, {name: 'Elizabeth Medora Leigh', age: 8}]); + lordByron.get('children').pushObjects([{ + name: 'Allegra Byron', + age: 5 + }, { + name: 'Elizabeth Medora Leigh', + age: 8 + }]); lordByron.get('maxChildAge'); // 8 ``` @method computed.max @for Ember @param {String} dependentKey @return {Ember.ComputedProperty} computes the largest value in the dependentKey's array */ Ember.computed.max = function (dependentKey) { - return Ember.reduceComputed.call(null, dependentKey, { + return Ember.reduceComputed(dependentKey, { initialValue: -Infinity, addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { return Math.max(accumulatedValue, item); }, @@ -14777,33 +15074,39 @@ /** A computed property that calculates the minimum value in the dependent array. This will return `Infinity` when the dependent array is empty. - Example - ```javascript App.Person = Ember.Object.extend({ childAges: Ember.computed.mapBy('children', 'age'), minChildAge: Ember.computed.min('childAges') }); var lordByron = App.Person.create({children: []}); lordByron.get('minChildAge'); // Infinity - lordByron.get('children').pushObject({name: 'Augusta Ada Byron', age: 7}); + lordByron.get('children').pushObject({ + name: 'Augusta Ada Byron', age: 7 + }); lordByron.get('minChildAge'); // 7 - lordByron.get('children').pushObjects([{name: 'Allegra Byron', age: 5}, {name: 'Elizabeth Medora Leigh', age: 8}]); + lordByron.get('children').pushObjects([{ + name: 'Allegra Byron', + age: 5 + }, { + name: 'Elizabeth Medora Leigh', + age: 8 + }]); lordByron.get('minChildAge'); // 5 ``` @method computed.min @for Ember @param {String} dependentKey @return {Ember.ComputedProperty} computes the smallest value in the dependentKey's array */ Ember.computed.min = function (dependentKey) { - return Ember.reduceComputed.call(null, dependentKey, { + return Ember.reduceComputed(dependentKey, { initialValue: Infinity, addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { return Math.min(accumulatedValue, item); }, @@ -14817,29 +15120,30 @@ }; /** Returns an array mapped via the callback - The callback method you provide should have the following signature: + The callback method you provide should have the following signature. + `item` is the current item in the iteration. ```javascript function(item); ``` - - `item` is the current item in the iteration. - Example ```javascript App.Hamster = Ember.Object.extend({ excitingChores: Ember.computed.map('chores', function(chore) { return chore.toUpperCase() + '!'; }) }); - var hamster = App.Hamster.create({chores: ['cook', 'clean', 'write more unit tests']}); - hamster.get('excitingChores'); // ['COOK!', 'CLEAN!', 'WRITE MORE UNIT TESTS!'] + var hamster = App.Hamster.create({ + chores: ['clean', 'write more unit tests'] + }); + hamster.get('excitingChores'); // ['CLEAN!', 'WRITE MORE UNIT TESTS!'] ``` @method computed.map @for Ember @param {String} dependentKey @@ -14863,22 +15167,26 @@ }; /** Returns an array mapped to the specified key. - Example - ```javascript App.Person = Ember.Object.extend({ childAges: Ember.computed.mapBy('children', 'age') }); var lordByron = App.Person.create({children: []}); lordByron.get('childAges'); // [] lordByron.get('children').pushObject({name: 'Augusta Ada Byron', age: 7}); lordByron.get('childAges'); // [7] - lordByron.get('children').pushObjects([{name: 'Allegra Byron', age: 5}, {name: 'Elizabeth Medora Leigh', age: 8}]); + lordByron.get('children').pushObjects([{ + name: 'Allegra Byron', + age: 5 + }, { + name: 'Elizabeth Medora Leigh', + age: 8 + }]); lordByron.get('childAges'); // [7, 5, 8] ``` @method computed.mapBy @for Ember @@ -14901,20 +15209,17 @@ Ember.computed.mapProperty = Ember.computed.mapBy; /** Filters the array by the callback. - The callback method you provide should have the following signature: + The callback method you provide should have the following signature. + `item` is the current item in the iteration. ```javascript function(item); ``` - - `item` is the current item in the iteration. - - Example - ```javascript App.Hamster = Ember.Object.extend({ remainingChores: Ember.computed.filter('chores', function(chore) { return !chore.done; }) @@ -14966,12 +15271,10 @@ }; /** Filters the array by the property and value - Example - ```javascript App.Hamster = Ember.Object.extend({ remainingChores: Ember.computed.filterBy('chores', 'done', false) }); @@ -15190,11 +15493,11 @@ */ Ember.computed.setDiff = function (setAProperty, setBProperty) { if (arguments.length !== 2) { throw new Ember.Error("setDiff requires exactly two dependent arrays."); } - return Ember.arrayComputed.call(null, setAProperty, setBProperty, { + return Ember.arrayComputed(setAProperty, setBProperty, { addedItem: function (array, item, changeMeta, instanceMeta) { var setA = get(this, setAProperty), setB = get(this, setBProperty); if (changeMeta.arrayChanged === setA) { @@ -15391,11 +15694,11 @@ instanceMeta.binarySearch = binarySearch; }; } - return Ember.arrayComputed.call(null, itemsKey, { + return Ember.arrayComputed(itemsKey, { initialize: initFn, addedItem: function (array, item, changeMeta, instanceMeta) { var index = instanceMeta.binarySearch(array, item); array.insertAt(index, item); @@ -15454,10 +15757,13 @@ @submodule ember-runtime */ var a_slice = Array.prototype.slice; +if (Ember.FEATURES.isEnabled('propertyBraceExpansion')) { + var expandProperties = Ember.expandProperties; +} if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Function) { /** The `property` extension of Javascript's Function prototype is available @@ -15550,13 +15856,22 @@ @method observes @for Function */ Function.prototype.observes = function() { - + if (Ember.FEATURES.isEnabled('propertyBraceExpansion')) { + var addWatchedProperty = function (obs) { watched.push(obs); }; + var watched = []; + + for (var i=0; i<arguments.length; ++i) { + expandProperties(arguments[i], addWatchedProperty); + } + + this.__ember_observes__ = watched; + } else { this.__ember_observes__ = a_slice.call(arguments); - + } return this; }; /** @@ -15615,13 +15930,22 @@ @method observesBefore @for Function */ Function.prototype.observesBefore = function() { - + if (Ember.FEATURES.isEnabled('propertyBraceExpansion')) { + var addWatchedProperty = function (obs) { watched.push(obs); }; + var watched = []; + + for (var i=0; i<arguments.length; ++i) { + expandProperties(arguments[i], addWatchedProperty); + } + + this.__ember_observesBefore__ = watched; + } else { this.__ember_observesBefore__ = a_slice.call(arguments); - + } return this; }; /** @@ -16588,10 +16912,15 @@ RSVP.configure('async', function(callback, promise) { Ember.run.schedule('actions', promise, callback, promise); }); +RSVP.Promise.prototype.fail = function(callback, label){ + + return this['catch'](callback, label); +}; + /** @module ember @submodule ember-runtime */ @@ -16697,10 +17026,12 @@ */ willMergeMixin: function(props) { var hashName; if (!props._actions) { + + if (typeOf(props.actions) === 'object') { hashName = 'actions'; } else if (typeOf(props.events) === 'object') { hashName = 'events'; @@ -16743,28 +17074,27 @@ (function() { var set = Ember.set, get = Ember.get, - resolve = Ember.RSVP.resolve, - rethrow = Ember.RSVP.rethrow, not = Ember.computed.not, or = Ember.computed.or; /** @module ember @submodule ember-runtime */ -function observePromise(proxy, promise) { - promise.then(function(value) { +function tap(proxy, promise) { + return promise.then(function(value) { set(proxy, 'isFulfilled', true); set(proxy, 'content', value); + return value; }, function(reason) { set(proxy, 'isRejected', true); set(proxy, 'reason', reason); - // don't re-throw, as we are merely observing + throw reason; }); } /** A low level mixin making ObjectProxy, ObjectController or ArrayController's promise aware. @@ -16838,13 +17168,11 @@ isRejected: false, isFulfilled: false, promise: Ember.computed(function(key, promise) { if (arguments.length === 2) { - promise = resolve(promise); - observePromise(this, promise); - return promise.then(); // fork the promise. + return tap(this, promise); } else { throw new Ember.Error("PromiseProxy's promise must be set"); } }), @@ -19295,11 +19623,11 @@ /** @module ember @submodule ember-views */ -var jQuery = this.jQuery || (Ember.imports && Ember.imports.jQuery); +var jQuery = (this && this.jQuery) || (Ember.imports && Ember.imports.jQuery); if (!jQuery && typeof require === 'function') { jQuery = require('jquery'); } @@ -19345,21 +19673,21 @@ // Internet Explorer prior to 9 does not allow setting innerHTML if the first element // is a "zero-scope" element. This problem can be worked around by making // the first node an invisible text node. We, like Modernizr, use &shy; -var needsShy = this.document && (function() { +var needsShy = typeof document !== 'undefined' && (function() { var testEl = document.createElement('div'); testEl.innerHTML = "<div></div>"; testEl.firstChild.innerHTML = "<script></script>"; return testEl.firstChild.innerHTML === ''; })(); // IE 8 (and likely earlier) likes to move whitespace preceeding // a script tag to appear after it. This means that we can // accidentally remove whitespace when updating a morph. -var movesWhitespace = this.document && (function() { +var movesWhitespace = typeof document !== 'undefined' && (function() { var testEl = document.createElement('div'); testEl.innerHTML = "Test: <script type='text/x-placeholder'></script>Value"; return testEl.childNodes[0].nodeValue === 'Test:' && testEl.childNodes[2].nodeValue === ' Value'; })(); @@ -22587,10 +22915,12 @@ * preRender: when a view is first instantiated, and after its element was destroyed, it is in the preRender state * inBuffer: once a view has been rendered, but before it has been inserted into the DOM, it is in the inBuffer state + * hasElement: the DOM representation of the view is created, + and is ready to be inserted * inDOM: once a view has been inserted into the DOM it is in the inDOM state. A view spends the vast majority of its existence in this state. * destroyed: once a view has been destroyed (using the destroy method), it is in this state. No further actions can be invoked @@ -23085,11 +23415,21 @@ invokeObserver: function(target, observer) { observer.call(target); } }); +})(); + + +(function() { +/** +@module ember +@submodule ember-views +*/ + +var hasElement = Ember.View.states.hasElement; var inDOM = Ember.View.states.inDOM = Ember.create(hasElement); Ember.merge(inDOM, { enter: function(view) { // Register the view for event handling. This hash is used by @@ -23652,19 +23992,19 @@ in the item views receiving an appropriately matched `tagName` property. Given an empty `<body>` and the following code: ```javascript - anUndorderedListView = Ember.CollectionView.create({ + anUnorderedListView = Ember.CollectionView.create({ tagName: 'ul', content: ['A','B','C'], itemViewClass: Ember.View.extend({ template: Ember.Handlebars.compile("the letter: {{view.content}}") }) }); - anUndorderedListView.appendTo('body'); + anUnorderedListView.appendTo('body'); ``` Will result in the following HTML structure ```html @@ -24360,20 +24700,19 @@ // Copyright: ©2011 My Company Inc. All rights reserved. // ========================================================================== var K = function() {}, guid = 0, - document = this.document, disableRange = ('undefined' === typeof ENV ? {} : ENV).DISABLE_RANGE_API, // Feature-detect the W3C range API, the extended check is for IE9 which only partially supports ranges - supportsRange = (!disableRange) && document && ('createRange' in document) && (typeof Range !== 'undefined') && Range.prototype.createContextualFragment, + supportsRange = (!disableRange) && typeof document !== 'undefined' && ('createRange' in document) && (typeof Range !== 'undefined') && Range.prototype.createContextualFragment, // Internet Explorer prior to 9 does not allow setting innerHTML if the first element // is a "zero-scope" element. This problem can be worked around by making // the first node an invisible text node. We, like Modernizr, use &shy; - needsShy = document && (function() { + needsShy = typeof document !== 'undefined' && (function() { var testEl = document.createElement('div'); testEl.innerHTML = "<div></div>"; testEl.firstChild.innerHTML = "<script></script>"; return testEl.firstChild.innerHTML === ''; })(), @@ -25189,20 +25528,34 @@ var handlebarsGet = Ember.Handlebars.get = function(root, path, options) { var data = options && options.data, normalizedPath = normalizePath(root, path, data), value; - + if (Ember.FEATURES.isEnabled("ember-handlebars-caps-lookup")) { + + // If the path starts with a capital letter, look it up on Ember.lookup, + // which defaults to the `window` object in browsers. + if (Ember.isGlobalPath(path)) { + value = Ember.get(Ember.lookup, path); + } else { + + // In cases where the path begins with a keyword, change the + // root to the value represented by that keyword, and ensure + // the path is relative to it. + value = Ember.get(normalizedPath.root, normalizedPath.path); + } + + } else { root = normalizedPath.root; path = normalizedPath.path; value = Ember.get(root, path); if (value === undefined && root !== Ember.lookup && Ember.isGlobalPath(path)) { value = Ember.get(Ember.lookup, path); } - + } return value; }; Ember.Handlebars.resolveParams = function(context, params, options) { @@ -26080,10 +26433,16 @@ var shouldDisplay = get(this, 'shouldDisplayFunc'), preserveContext = get(this, 'preserveContext'), context = get(this, 'previousContext'); + var contextController; + + if (Ember.FEATURES.isEnabled('with-controller')) { + contextController = get(this, 'contextController'); + } + var inverseTemplate = get(this, 'inverseTemplate'), displayTemplate = get(this, 'displayTemplate'); var result = this.normalizedValue(); this._lastNormalizedValue = result; @@ -26099,10 +26458,16 @@ set(this, '_context', context); } else { // Otherwise, determine if this is a block bind or not. // If so, pass the specified object to the template if (displayTemplate) { + if (Ember.FEATURES.isEnabled('with-controller')) { + if (contextController) { + set(contextController, 'content', result); + result = contextController; + } + } set(this, '_context', result); } else { // This is not a bind block, just push the result of the // expression to the render context and return. if (result === null || result === undefined) { @@ -26199,10 +26564,18 @@ previousContext: currentContext, isEscaped: !options.hash.unescaped, templateData: options.data }); + if (Ember.FEATURES.isEnabled('with-controller')) { + if (options.hash.controller) { + bindView.set('contextController', this.container.lookupFactory('controller:'+options.hash.controller).create({ + container: currentContext + })); + } + } + view.appendChild(bindView); observer = function() { Ember.run.scheduleOnce('render', bindView, 'rerenderIfNeeded'); }; @@ -26273,10 +26646,21 @@ output = handlebarsGet(currentContext, property, options); data.buffer.push((output === null || typeof output === 'undefined') ? '' : output); } } +function shouldDisplayIfHelperContent(result) { + var truthy = result && get(result, 'isTruthy'); + if (typeof truthy === 'boolean') { return truthy; } + + if (Ember.isArray(result)) { + return get(result, 'length') !== 0; + } else { + return !!result; + } +} + /** @private '_triageMustache' is used internally select between a binding, helper, or component for the given context. Until this point, it would be hard to determine if the @@ -26380,22 +26764,47 @@ @param {Function} fn Context to provide for rendering @return {String} HTML string */ 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; } - if (Ember.isArray(result)) { - return get(result, 'length') !== 0; - } else { - return !!result; - } - }; + return bind.call(context, property, fn, true, shouldDisplayIfHelperContent, shouldDisplayIfHelperContent, ['isTruthy', 'length']); +}); - return bind.call(context, property, fn, true, func, func, ['isTruthy', 'length']); + +/** + @private + + Use the `unboundIf` helper to create a conditional that evaluates once. + + ```handlebars + {{#unboundIf "content.shouldDisplayTitle"}} + {{content.title}} + {{/unboundIf}} + ``` + + @method unboundIf + @for Ember.Handlebars.helpers + @param {String} property Property to bind + @param {Function} fn Context to provide for rendering + @return {String} HTML string +*/ +EmberHandlebars.registerHelper('unboundIf', function unboundIfHelper(property, fn) { + var context = (fn.contexts && fn.contexts.length) ? fn.contexts[0] : this, + data = fn.data, + template = fn.fn, + inverse = fn.inverse, + normalized, propertyValue, result; + + normalized = normalizePath(context, property, data); + propertyValue = handlebarsGet(context, property, fn); + + if (!shouldDisplayIfHelperContent(propertyValue)) { + template = inverse; + } + + template(context, { data: data }); }); /** @method with @for Ember.Handlebars.helpers @@ -26442,22 +26851,26 @@ }); /** See [boundIf](/api/classes/Ember.Handlebars.helpers.html#method_boundIf) + and [unboundIf](/api/classes/Ember.Handlebars.helpers.html#method_unboundIf) @method if @for Ember.Handlebars.helpers @param {Function} context @param {Hash} options @return {String} HTML string */ EmberHandlebars.registerHelper('if', function ifHelper(context, options) { - - return helpers.boundIf.call(options.contexts[0], context, options); + if (options.data.isUnbound) { + return helpers.unboundIf.call(options.contexts[0], context, options); + } else { + return helpers.boundIf.call(options.contexts[0], context, options); + } }); /** @method unless @for Ember.Handlebars.helpers @@ -26472,11 +26885,15 @@ var fn = options.fn, inverse = options.inverse; options.fn = inverse; options.inverse = fn; - return helpers.boundIf.call(options.contexts[0], context, options); + if (options.data.isUnbound) { + return helpers.unboundIf.call(options.contexts[0], context, options); + } else { + return helpers.boundIf.call(options.contexts[0], context, options); + } }); /** `bind-attr` allows you to create a binding between DOM element attributes and Ember objects. For example: @@ -27241,11 +27658,11 @@ <div class="ember-view">Hi Mary</div> <div class="ember-view">Hi Sara</div> </div> ``` - ### Blockless Use + ### Blockless use in a collection If you provide an `itemViewClass` option that has its own `template` you can omit the block. The following template: @@ -27549,10 +27966,11 @@ @module ember @submodule ember-handlebars */ var get = Ember.get, set = Ember.set; +var fmt = Ember.String.fmt; Ember.Handlebars.EachView = Ember.CollectionView.extend(Ember._Metamorph, { init: function() { var itemController = get(this, 'itemController'); var binding; @@ -28540,11 +28958,11 @@ Ember.TextField = Ember.Component.extend(Ember.TextSupport, /** @scope Ember.TextField.prototype */ { classNames: ['ember-text-field'], tagName: "input", - attributeBindings: ['type', 'value', 'size', 'pattern', 'name'], + attributeBindings: ['type', 'value', 'size', 'pattern', 'name', 'min', 'max'], /** The `value` attribute of the input element. As the user inputs text, this property is updated live. @@ -28571,145 +28989,35 @@ @default null */ size: null, /** - The `pattern` the pattern attribute of input element. + The `pattern` attribute of input element. @property pattern @type String @default null */ - pattern: null -}); + pattern: null, -})(); + /** + The `min` attribute of input element used with `type="number"` or `type="range"`. - - -(function() { -/* -@module ember -@submodule ember-handlebars -*/ - -var get = Ember.get, set = Ember.set; - -/* - @class Button - @namespace Ember - @extends Ember.View - @uses Ember.TargetActionSupport - @deprecated -*/ -Ember.Button = Ember.View.extend(Ember.TargetActionSupport, { - classNames: ['ember-button'], - classNameBindings: ['isActive'], - - tagName: 'button', - - propagateEvents: false, - - attributeBindings: ['type', 'disabled', 'href', 'tabindex'], - - /* - @private - - Overrides `TargetActionSupport`'s `targetObject` computed - property to use Handlebars-specific path resolution. - - @property targetObject + @property min + @type String + @default null */ - targetObject: Ember.computed(function() { - var target = get(this, 'target'), - root = get(this, 'context'), - data = get(this, 'templateData'); + min: null, - if (typeof target !== 'string') { return target; } + /** + The `max` attribute of input element used with `type="number"` or `type="range"`. - return Ember.Handlebars.get(root, target, { data: data }); - }).property('target'), - - // Defaults to 'button' if tagName is 'input' or 'button' - type: Ember.computed(function(key) { - var tagName = this.tagName; - if (tagName === 'input' || tagName === 'button') { return 'button'; } - }), - - disabled: false, - - // Allow 'a' tags to act like buttons - href: Ember.computed(function() { - return this.tagName === 'a' ? '#' : null; - }), - - mouseDown: function() { - if (!get(this, 'disabled')) { - set(this, 'isActive', true); - this._mouseDown = true; - this._mouseEntered = true; - } - return get(this, 'propagateEvents'); - }, - - mouseLeave: function() { - if (this._mouseDown) { - set(this, 'isActive', false); - this._mouseEntered = false; - } - }, - - mouseEnter: function() { - if (this._mouseDown) { - set(this, 'isActive', true); - this._mouseEntered = true; - } - }, - - mouseUp: function(event) { - if (get(this, 'isActive')) { - // Actually invoke the button's target and action. - // This method comes from the Ember.TargetActionSupport mixin. - this.triggerAction(); - set(this, 'isActive', false); - } - - this._mouseDown = false; - this._mouseEntered = false; - return get(this, 'propagateEvents'); - }, - - keyDown: function(event) { - // Handle space or enter - if (event.keyCode === 13 || event.keyCode === 32) { - this.mouseDown(); - } - }, - - keyUp: function(event) { - // Handle space or enter - if (event.keyCode === 13 || event.keyCode === 32) { - this.mouseUp(); - } - }, - - // TODO: Handle proper touch behavior. Including should make inactive when - // finger moves more than 20x outside of the edge of the button (vs mouse - // which goes inactive as soon as mouse goes out of edges.) - - touchStart: function(touch) { - return this.mouseDown(touch); - }, - - touchEnd: function(touch) { - return this.mouseUp(touch); - }, - - init: function() { - - this._super(); - } + @property max + @type String + @default null + */ + max: null }); })(); @@ -31992,22 +32300,34 @@ } else { this.push(options.path, name, null, options.queryParams); } - }, + if (Ember.FEATURES.isEnabled("ember-routing-named-substates")) { + // For namespace-preserving nested resource (e.g. resource('foo.bar') within + // resource('foo')) we only want to use the last route name segment to determine + // the names of the error/loading substates (e.g. 'bar_loading') + name = name.split('.').pop(); + route(this, name + '_loading'); + route(this, name + '_error', { path: "/_unused_dummy_error_path_route_" + name + "/:error" }); + } + }, push: function(url, name, callback, queryParams) { var parts = name.split('.'); if (url === "" || url === "/" || parts[parts.length-1] === "index") { this.explicitIndex = true; } this.matches.push([url, name, callback, queryParams]); }, route: function(name, options) { route(this, name, options); - }, + if (Ember.FEATURES.isEnabled("ember-routing-named-substates")) { + route(this, name + '_loading'); + route(this, name + '_error', { path: "/_unused_dummy_error_path_route_" + name + "/:error" }); + } + }, generate: function() { var dslMatches = this.matches; if (!this.explicitIndex) { @@ -32016,11 +32336,16 @@ return function(match) { for (var i=0, l=dslMatches.length; i<l; i++) { var dslMatch = dslMatches[i]; var matchObj = match(dslMatch[0]).to(dslMatch[1], dslMatch[2]); - } + if (Ember.FEATURES.isEnabled("query-params")) { + if(dslMatch[3]) { + matchObj.withQueryParams.apply(matchObj, dslMatch[3]); + } + } + } }; } }; function route(dsl, name, options) { @@ -32058,11 +32383,11 @@ @module ember @submodule ember-routing */ /** - + Finds a controller instance. @for Ember @method controllerFor @private @@ -32070,21 +32395,26 @@ Ember.controllerFor = function(container, controllerName, lookupOptions) { return container.lookup('controller:' + controllerName, lookupOptions); }; /** - Generates a controller automatically if none was provided. - The type of generated controller depends on the context. + Generates a controller factory + + The type of the generated controller factory is derived + from the context. If the context is an array an array controller + is generated, if an object, an object controller otherwise, a basic + controller is generated. + You can customize your generated controllers by defining - `App.ObjectController` and `App.ArrayController` - + `App.ObjectController` or `App.ArrayController`. + @for Ember - @method generateController + @method generateControllerFactory @private */ -Ember.generateController = function(container, controllerName, context) { - var ControllerFactory, fullName, instance, name, factoryName, controllerType; +Ember.generateControllerFactory = function(container, controllerName, context) { + var Factory, fullName, instance, name, factoryName, controllerType; if (context && Ember.isArray(context)) { controllerType = 'array'; } else if (context) { controllerType = 'object'; @@ -32092,23 +32422,41 @@ controllerType = 'basic'; } factoryName = 'controller:' + controllerType; - ControllerFactory = container.lookupFactory(factoryName).extend({ + Factory = container.lookupFactory(factoryName).extend({ isGenerated: true, toString: function() { return "(generated " + controllerName + " controller)"; } }); fullName = 'controller:' + controllerName; - container.register(fullName, ControllerFactory); + container.register(fullName, Factory); - instance = container.lookup(fullName); + return Factory; +}; +/** + Generates and instantiates a controller. + + The type of the generated controller factory is derived + from the context. If the context is an array an array controller + is generated, if an object, an object controller otherwise, a basic + controller is generated. + + @for Ember + @method generateController + @private +*/ +Ember.generateController = function(container, controllerName, context) { + Ember.generateControllerFactory(container, controllerName, context); + var fullName = 'controller:' + controllerName; + var instance = container.lookup(fullName); + if (get(instance, 'namespace.LOG_ACTIVE_GENERATION')) { Ember.Logger.info("generated -> " + fullName, { fullName: fullName }); } return instance; @@ -32242,17 +32590,10 @@ */ reset: function() { this.router.reset(); }, - willDestroy: function(){ - var location = get(this, 'location'); - location.destroy(); - - this._super.apply(this, arguments); - }, - _lookupActiveView: function(templateName) { var active = this._activeViews[templateName]; return active && active[0]; }, @@ -32271,20 +32612,27 @@ view.one('willDestroyElement', this, disconnect); }, _setupLocation: function() { var location = get(this, 'location'), - rootURL = get(this, 'rootURL'), - options = {}; + rootURL = get(this, 'rootURL'); - if (typeof rootURL === 'string') { - options.rootURL = rootURL; + if ('string' === typeof location && this.container) { + var resolvedLocation = this.container.lookup('location:' + location); + + if ('undefined' !== typeof resolvedLocation) { + location = set(this, 'location', resolvedLocation); + } else { + // Allow for deprecated registration of custom location API's + var options = {implementation: location}; + + location = set(this, 'location', Ember.Location.create(options)); + } } - if ('string' === typeof location) { - options.implementation = location; - location = set(this, 'location', Ember.Location.create(options)); + if (typeof rootURL === 'string') { + location.rootURL = rootURL; } // ensure that initState is called AFTER the rootURL is set on // the location instance if (typeof location.initState === 'function') { location.initState(); } @@ -32353,11 +32701,14 @@ args[0] = args[0] || '/'; var passedName = args[0], name, self = this, isQueryParamsOnly = false; - + if (Ember.FEATURES.isEnabled("query-params")) { + isQueryParamsOnly = (args.length === 1 && args[0].hasOwnProperty('queryParams')); + } + if (!isQueryParamsOnly && passedName.charAt(0) === '/') { name = passedName; } else if (!isQueryParamsOnly) { if (!this.router.hasRoute(passedName)) { name = args[0] = passedName + '.index'; @@ -32502,11 +32853,18 @@ var router = parentRoute.router, childName, targetChildRouteName = originatingChildRoute.routeName.split('.').pop(), namespace = parentRoute.routeName === 'application' ? '' : parentRoute.routeName + '.'; - + if (Ember.FEATURES.isEnabled("ember-routing-named-substates")) { + // First, try a named loading state, e.g. 'foo_loading' + childName = namespace + targetChildRouteName + '_' + name; + if (routeHasBeenDefined(router, childName)) { + return childName; + } + } + // Second, try general loading state, e.g. 'loading' childName = namespace + name; if (routeHasBeenDefined(router, childName)) { return childName; } @@ -33115,11 +33473,14 @@ // referenced in action handlers this.controller = controller; var args = [controller, context]; - + if (Ember.FEATURES.isEnabled("query-params")) { + args.push(queryParams); + } + if (this.setupControllers) { this.setupControllers(controller, context); } else { this.setupController.apply(this, args); @@ -34146,12 +34507,19 @@ // Map desired event name to invoke function var eventName = get(this, 'eventName'), i; this.on(eventName, this, this._invoke); - }, + if (Ember.FEATURES.isEnabled("query-params")) { + var queryParams = get(this, '_potentialQueryParams') || []; + for(i=0; i < queryParams.length; i++) { + this.registerObserver(this, queryParams[i], this, this._queryParamsChanged); + } + } + }, + /** @private This method is invoked by observers installed during `init` that fire whenever the params change @@ -34328,11 +34696,19 @@ var parameters = this.parameters, options = parameters.options, types = options.types, data = options.data; - + if (Ember.FEATURES.isEnabled("query-params")) { + if (parameters.params.length === 0) { + var appController = this.container.lookup('controller:application'); + return [get(appController, 'currentRouteName')]; + } else { + return resolveParams(parameters.context, parameters.params, { types: types, data: data }); + } + } + // Original implementation if query params not enabled return resolveParams(parameters.context, parameters.params, { types: types, data: data }); }).property(), /** @@ -34360,11 +34736,16 @@ // If contexts aren't present, consider the linkView unloaded. return; } } - + if (Ember.FEATURES.isEnabled("query-params")) { + var queryParams = get(this, 'queryParams'); + + if (queryParams || queryParams === false) { resolvedParams.push({queryParams: queryParams}); } + } + return resolvedParams; }).property('resolvedParams', 'queryParams', 'router.url'), _potentialQueryParams: Ember.computed(function () { @@ -34924,67 +35305,82 @@ @param {Object?} contextString @param {Hash} options @return {String} HTML string */ Ember.Handlebars.registerHelper('render', function renderHelper(name, contextString, options) { + var length = arguments.length; - var contextProvided = arguments.length === 3, + var contextProvided = length === 3, container, router, controller, view, context, lookupOptions; - if (arguments.length === 2) { + container = (options || contextString).data.keywords.controller.container; + router = container.lookup('router:main'); + + if (length === 2) { + // use the singleton controller options = contextString; contextString = undefined; - } - if (typeof contextString === 'string') { + } else if (length === 3) { + // create a new controller context = Ember.Handlebars.get(options.contexts[1], contextString, options); - lookupOptions = { singleton: false }; + } else { + throw Ember.Error("You must pass a templateName to render"); } + // # legacy namespace name = name.replace(/\//g, '.'); - container = options.data.keywords.controller.container; - router = container.lookup('router:main'); + // \ legacy slash as namespace support view = container.lookup('view:' + name) || container.lookup('view:default'); - var controllerName = options.hash.controller; + // provide controller override + var controllerName = options.hash.controller || name; + var controllerFullName = 'controller:' + controllerName; - // Look up the controller by name, if provided. - if (controllerName) { - controller = container.lookup('controller:' + controllerName, lookupOptions); + if (options.hash.controller) { - } else { - controller = container.lookup('controller:' + name, lookupOptions) || - Ember.generateController(container, name, context); } - if (controller && contextProvided) { - controller.set('model', context); + var target = options.data.keywords.controller; + + // choose name + if (length > 2) { + var factory = container.lookupFactory(controllerFullName) || + Ember.generateControllerFactory(container, controllerName, context); + + controller = factory.create({ + model: context, + target: target + }); + + } else { + controller = container.lookup(controllerFullName) || + Ember.generateController(container, controllerName); + + controller.set('target', target); } var root = options.contexts[1]; if (root) { view.registerObserver(root, contextString, function() { controller.set('model', Ember.Handlebars.get(root, contextString, options)); }); } - controller.set('target', options.data.keywords.controller); - options.hash.viewName = Ember.String.camelize(name); options.hash.template = container.lookup('template:' + name); options.hash.controller = controller; if (router && !context) { router._connectActiveView(name, view); } Ember.Handlebars.helpers.view.call(this, view, options); }); - }); })(); @@ -35675,10 +36071,12 @@ @method registerImplementation @param {String} name @param {Object} implementation of the `location` API */ registerImplementation: function(name, implementation) { + + this.implementations[name] = implementation; }, implementations: {} }; @@ -35704,10 +36102,11 @@ @class NoneLocation @namespace Ember @extends Ember.Object */ Ember.NoneLocation = Ember.Object.extend({ + implementation: 'none', path: '', /** @private @@ -35779,12 +36178,10 @@ // helpers. return url; } }); -Ember.Location.registerImplementation('none', Ember.NoneLocation); - })(); (function() { @@ -35803,10 +36200,11 @@ @class HashLocation @namespace Ember @extends Ember.Object */ Ember.HashLocation = Ember.Object.extend({ + implementation: 'hash', init: function() { set(this, 'location', get(this, 'location') || window.location); }, @@ -35816,11 +36214,23 @@ Returns the current `location.hash`, minus the '#' at the front. @method getURL */ getURL: function() { - // Default implementation without feature flag enabled + if (Ember.FEATURES.isEnabled("query-params")) { + // location.hash is not used because it is inconsistently + // URL-decoded between browsers. + var href = get(this, 'location').href, + hashIndex = href.indexOf('#'); + + if ( hashIndex === -1 ) { + return ""; + } else { + return href.substr(hashIndex + 1); + } + } + // Default implementation without feature flag enabled return get(this, 'location').hash.substr(1); }, /** @private @@ -35904,12 +36314,10 @@ Ember.$(window).off('hashchange.ember-location-'+guid); } }); -Ember.Location.registerImplementation('hash', Ember.HashLocation); - })(); (function() { @@ -35929,10 +36337,11 @@ @class HistoryLocation @namespace Ember @extends Ember.Object */ Ember.HistoryLocation = Ember.Object.extend({ + implementation: 'history', init: function() { set(this, 'location', get(this, 'location') || window.location); }, @@ -35970,11 +36379,15 @@ path = location.pathname; rootURL = rootURL.replace(/\/$/, ''); var url = path.replace(rootURL, ''); - + if (Ember.FEATURES.isEnabled("query-params")) { + var search = location.search || ''; + url += search; + } + return url; }, /** @private @@ -36123,12 +36536,10 @@ Ember.$(window).off('popstate.ember-location-'+guid); } }); -Ember.Location.registerImplementation('history', Ember.HistoryLocation); - })(); (function() { @@ -36975,13 +37386,19 @@ Example: ```javascript App.inject(<full_name or type>, <property name>, <full_name>) - App.inject('model:user', 'email', 'model:email') - App.inject('model', 'source', 'source:main') + App.inject('controller:application', 'email', 'model:email') + App.inject('controller', 'source', 'source:main') ``` + Please note that injections on models are currently disabled. + This was done because ember-data was not ready for fully a container aware ecosystem. + + You can enable injections on models by setting `Ember.MODEL_FACTORY_INJECTIONS` flag to `true` + If model factory injections are enabled, models should not be + accessed globally (only through `container.lookupFactory('model:modelName'))`); @method inject @param factoryNameOrType {String} @param property {String} @param injectionName {String} @@ -37310,9 +37727,13 @@ container.register('route:basic', Ember.Route, { instantiate: false }); container.register('event_dispatcher:main', Ember.EventDispatcher); container.register('router:main', Ember.Router); container.injection('router:main', 'namespace', 'application:main'); + + container.register('location:hash', Ember.HashLocation); + container.register('location:history', Ember.HistoryLocation); + container.register('location:none', Ember.NoneLocation); container.injection('controller', 'target', 'router:main'); container.injection('controller', 'namespace', 'application:main'); container.injection('route', 'router', 'router:main');