diff --git a/assets/javascripts/vendor/ember-data.js b/assets/javascripts/vendor/ember-data.js index f856cb4a..eabd46f5 100644 --- a/assets/javascripts/vendor/ember-data.js +++ b/assets/javascripts/vendor/ember-data.js @@ -1387,6 +1387,10 @@ DS.Store = Ember.Object.extend({ return array; }, + recordIsLoaded: function(type, id) { + return !Ember.none(this.typeMapFor(type).idToCid[id]); + }, + // ............ // . UPDATING . // ............ @@ -2387,7 +2391,7 @@ var DirtyState = DS.State.extend({ errors = get(record, 'errors'), key = context.key; - delete errors[key]; + set(errors, key, null); if (!hasDefinedProperties(errors)) { manager.send('becameValid'); @@ -3172,6 +3176,7 @@ var storeAlias = function(methodName) { }; DS.Model.reopenClass({ + isLoaded: storeAlias('recordIsLoaded'), find: storeAlias('find'), filter: storeAlias('filter'), @@ -3699,34 +3704,132 @@ Ember.onLoad('application', function(app) { (function() { -DS.fixtureAdapter = DS.Adapter.create({ - find: function(store, type, id) { - var fixtures = type.FIXTURES; +var get = Ember.get; - Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures); - if (fixtures.hasLoaded) { return; } +DS.FixtureAdapter = DS.Adapter.extend({ - setTimeout(function() { - store.loadMany(type, fixtures); - fixtures.hasLoaded = true; - }, 300); + simulateRemoteResponse: true, + + latency: 50, + + /* + Implement this method in order to provide data associated with a type + */ + fixturesForType: function(type) { + return type.FIXTURES ? Ember.A(type.FIXTURES) : null; }, - findMany: function() { - this.find.apply(this, arguments); + /* + Implement this method in order to query fixtures data + */ + queryFixtures: function(fixtures, query) { + return fixtures; + }, + + /* + Implement this method in order to provide provide json for CRUD methods + */ + mockJSON: function(type, record) { + return record.toJSON({associations: true}); + }, + + /* + Adapter methods + */ + generateIdForRecord: function(store, record) { + return Ember.guidFor(record); + }, + + find: function(store, type, id) { + var fixtures = this.fixturesForType(type); + + if (fixtures) { + fixtures = fixtures.findProperty('id', id); + } + + Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures); + + this.simulateRemoteCall(function() { + store.load(type, fixtures); + }, store, type); + }, + + findMany: function(store, type, ids) { + var fixtures = this.fixturesForType(type); + + if (fixtures) { + fixtures = fixtures.filter(function(item) { + return ids.indexOf(item.id) !== -1; + }); + } + + Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures); + + this.simulateRemoteCall(function() { + store.loadMany(type, fixtures); + }, store, type); }, findAll: function(store, type) { - var fixtures = type.FIXTURES; + var fixtures = this.fixturesForType(type); Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures); - var ids = fixtures.map(function(item, index, self){ return item.id; }); - store.loadMany(type, ids, fixtures); - } + this.simulateRemoteCall(function() { + store.loadMany(type, fixtures); + }, store, type); + }, + findQuery: function(store, type, query, array) { + var fixtures = this.fixturesForType(type); + + fixtures = this.queryFixtures(fixtures, query); + + Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures); + + this.simulateRemoteCall(function() { + array.load(fixtures); + }, store, type); + }, + + createRecord: function(store, type, record) { + var fixture = this.mockJSON(type, record); + + fixture.id = this.generateIdForRecord(store, record); + + this.simulateRemoteCall(function() { + store.didCreateRecord(record, fixture); + }, store, type, record); + }, + + updateRecord: function(store, type, record) { + var fixture = this.mockJSON(type, record); + + this.simulateRemoteCall(function() { + store.didUpdateRecord(record, fixture); + }, store, type, record); + }, + + deleteRecord: function(store, type, record) { + this.simulateRemoteCall(function() { + store.didDeleteRecord(record); + }, store, type, record); + }, + + /* + @private + */ + simulateRemoteCall: function(callback, store, type, record) { + if (get(this, 'simulateRemoteResponse')) { + setTimeout(callback, get(this, 'latency')); + } else { + callback(); + } + } }); +DS.fixtureAdapter = DS.FixtureAdapter.create(); + })(); diff --git a/assets/javascripts/vendor/ember.js b/assets/javascripts/vendor/ember.js index ebb9ee22..de9c6a4c 100644 --- a/assets/javascripts/vendor/ember.js +++ b/assets/javascripts/vendor/ember.js @@ -1,6 +1,5 @@ -// Version: v0.9.8.1-468-g3097ea8 -// Last commit: 3097ea8 (2012-07-04 14:42:40 -0700) - +// Version: v0.9.8.1-617-g2b213df +// Last commit: 2b213df (2012-07-14 16:44:53 -0700) (function() { /*global __fail__*/ @@ -13,6 +12,12 @@ if ('undefined' === typeof Ember) { } } +Ember.ENV = 'undefined' === typeof ENV ? {} : ENV; + +if (!('MANDATORY_SETTER' in Ember.ENV)) { + Ember.ENV.MANDATORY_SETTER = true; // default to true for debug dist +} + /** Define an assertion that will throw an exception if the condition is not met. Ember build tools will remove any calls to Ember.assert() when @@ -78,14 +83,13 @@ Ember.deprecate = function(message, test) { if (Ember && Ember.ENV.RAISE_ON_DEPRECATION) { throw new Error(message); } - var error, stackStr = ''; + var error; // When using new Error, we can't do the arguments check for Chrome. Alternatives are welcome try { __fail__.fail(); } catch (e) { error = e; } - if (error.stack) { - var stack; - + if (Ember.LOG_STACKTRACE_ON_DEPRECATION && error.stack) { + var stack, stackStr = ''; if (error['arguments']) { // Chrome stack = error.stack.replace(/^\s+at\s+/gm, ''). @@ -99,9 +103,10 @@ Ember.deprecate = function(message, test) { } stackStr = "\n " + stack.slice(2).join("\n "); + message = message + stackStr; } - Ember.Logger.warn("DEPRECATION: "+message+stackStr); + Ember.Logger.warn("DEPRECATION: "+message); }; @@ -136,8 +141,8 @@ window.ember_deprecateFunc = Ember.deprecateFunc("ember_deprecateFunc is deprec })(); -// Version: v0.9.8.1-484-g73ac0a4 -// Last commit: 73ac0a4 (2012-07-06 11:52:32 -0700) +// Version: v0.9.8.1-617-g2b213df +// Last commit: 2b213df (2012-07-14 16:44:53 -0700) (function() { @@ -229,6 +234,15 @@ Ember.config = Ember.config || {}; */ Ember.EXTEND_PROTOTYPES = (Ember.ENV.EXTEND_PROTOTYPES !== false); +/** + @static + @type Boolean + @default true + @constant + + Determines whether Ember logs a full stack trace during deprecation warnings +*/ +Ember.LOG_STACKTRACE_ON_DEPRECATION = (Ember.ENV.LOG_STACKTRACE_ON_DEPRECATION !== false); /** @static @@ -335,7 +349,7 @@ if ('undefined' === typeof ember_deprecateFunc) { Inside Ember-Metal, simply uses the window.console object. Override this to provide more robust logging functionality. */ -Ember.Logger = window.console || { log: Ember.K, warn: Ember.K, error: Ember.K }; +Ember.Logger = window.console || { log: Ember.K, warn: Ember.K, error: Ember.K, info: Ember.K, debug: Ember.K }; })(); @@ -618,6 +632,10 @@ if (!platform.defineProperty) { platform.defineProperty.isSimulated = true; } +if (Ember.ENV.MANDATORY_SETTER && !platform.hasPropertyAccessors) { + Ember.ENV.MANDATORY_SETTER = false; +} + })(); @@ -636,6 +654,8 @@ var o_defineProperty = Ember.platform.defineProperty, numberCache = [], stringCache = {}; +var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER; + /** @private @static @@ -651,6 +671,13 @@ var o_defineProperty = Ember.platform.defineProperty, */ Ember.GUID_KEY = GUID_KEY; +var GUID_DESC = { + writable: false, + configurable: false, + enumerable: false, + value: null +}; + /** @private @@ -671,17 +698,13 @@ Ember.GUID_KEY = GUID_KEY; @returns {String} the guid */ -Ember.generateGuid = function(obj, prefix) { +Ember.generateGuid = function generateGuid(obj, prefix) { if (!prefix) prefix = 'ember'; var ret = (prefix + (uuid++)); if (obj) { - o_defineProperty(obj, GUID_KEY, { - configurable: true, - writable: true, - value: ret - }); + GUID_DESC.value = ret; + o_defineProperty(obj, GUID_KEY, GUID_DESC); } - return ret ; }; @@ -698,7 +721,7 @@ Ember.generateGuid = function(obj, prefix) { @param obj {Object} any object, string, number, Element, or primitive @returns {String} the unique guid for this instance. */ -Ember.guidFor = function(obj) { +Ember.guidFor = function guidFor(obj) { // special cases where we don't want to add a key to object if (obj === undefined) return "(undefined)"; @@ -726,11 +749,13 @@ Ember.guidFor = function(obj) { if (obj[GUID_KEY]) return obj[GUID_KEY]; if (obj === Object) return '(Object)'; if (obj === Array) return '(Array)'; - return Ember.generateGuid(obj, 'ember'); + ret = 'ember'+(uuid++); + GUID_DESC.value = ret; + o_defineProperty(obj, GUID_KEY, GUID_DESC); + return ret; } }; - // .......................................................... // META // @@ -758,9 +783,28 @@ var EMPTY_META = { watching: {} }; +if (MANDATORY_SETTER) { EMPTY_META.values = {}; } + +Ember.EMPTY_META = EMPTY_META; + if (Object.freeze) Object.freeze(EMPTY_META); -var createMeta = Ember.platform.defineProperty.isSimulated ? o_create : (function(meta) { return meta; }); +var isDefinePropertySimulated = Ember.platform.defineProperty.isSimulated; + +function Meta(obj) { + this.descs = {}; + this.watching = {}; + this.cache = {}; + this.source = obj; +} + +if (isDefinePropertySimulated) { + // on platforms that don't support enumerable false + // make meta fail jQuery.isPlainObject() to hide from + // jQuery.extend() by having a property that fails + // hasOwnProperty check. + Meta.prototype.__preventPlainObject__ = true; +} /** @private @@ -789,28 +833,29 @@ Ember.meta = function meta(obj, writable) { if (writable===false) return ret || EMPTY_META; if (!ret) { - o_defineProperty(obj, META_KEY, META_DESC); - ret = obj[META_KEY] = createMeta({ - descs: {}, - watching: {}, - values: {}, - cache: {}, - source: obj - }); + if (!isDefinePropertySimulated) o_defineProperty(obj, META_KEY, META_DESC); + + ret = new Meta(obj); + + if (MANDATORY_SETTER) { ret.values = {}; } + + obj[META_KEY] = ret; // make sure we don't accidentally try to create constructor like desc ret.descs.constructor = null; } else if (ret.source !== obj) { + if (!isDefinePropertySimulated) o_defineProperty(obj, META_KEY, META_DESC); + ret = o_create(ret); ret.descs = o_create(ret.descs); - ret.values = o_create(ret.values); ret.watching = o_create(ret.watching); ret.cache = {}; ret.source = obj; - o_defineProperty(obj, META_KEY, META_DESC); - ret = obj[META_KEY] = createMeta(ret); + if (MANDATORY_SETTER) { ret.values = o_create(ret.values); } + + obj[META_KEY] = ret; } return ret; }; @@ -856,7 +901,7 @@ Ember.setMeta = function setMeta(obj, property, value) { (or meta property) if one does not already exist or if it's shared with its constructor */ -Ember.metaPath = function(obj, path, writable) { +Ember.metaPath = function metaPath(obj, path, writable) { var meta = Ember.meta(obj, writable), keyName, value; for (var i=0, l=path.length; i [] Ember.makeArray(null); => [] Ember.makeArray(undefined); => [] - Ember.makeArray('lindsay'); => ['lindsay'] + Ember.makeArray('lindsay'); => ['lindsay'] Ember.makeArray([1,2,42]); => [1,2,42] var controller = Ember.ArrayProxy.create({ content: [] }); @@ -1236,10 +1281,9 @@ MapWithDefault.prototype.copy = function() { // Copyright: ©2011 Strobe Inc. and contributors. // License: Licensed under MIT license (see license.js) // ========================================================================== -var USE_ACCESSORS = Ember.platform.hasPropertyAccessors && Ember.ENV.USE_ACCESSORS; -Ember.USE_ACCESSORS = !!USE_ACCESSORS; +var META_KEY = Ember.META_KEY, get, set; -var meta = Ember.meta; +var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER; // .......................................................... // GET AND SET @@ -1248,53 +1292,67 @@ var meta = Ember.meta; // Otherwise simulate accessors by looking up the property directly on the // object. -var get, set; - /** @private */ -var basicGet = function get(obj, keyName) { - var ret = obj[keyName]; - if (ret !== undefined) { return ret; } - - // if the property's value is undefined and the object defines - // unknownProperty, invoke unknownProperty - if ('function' === typeof obj.unknownProperty) { - return obj.unknownProperty(keyName); - } -}; - -/** @private */ -var basicSet = function set(obj, keyName, value) { - var isObject = 'object' === typeof obj; - var hasProp = isObject && !(keyName in obj); - - // setUnknownProperty is called if `obj` is an object, - // the property does not already exist, and the - // `setUnknownProperty` method exists on the object - var unknownProp = hasProp && 'function' === typeof obj.setUnknownProperty; - - if (unknownProp) { - obj.setUnknownProperty(keyName, value); - } else { - obj[keyName] = value; - } -}; - -/** @private */ -get = function(obj, keyName) { +get = function get(obj, keyName) { Ember.assert("You need to provide an object and key to `get`.", !!obj && keyName); - var desc = meta(obj, false).descs[keyName]; - if (desc) { return desc.get(obj, keyName); } - else { return basicGet(obj, keyName); } + var meta = obj[META_KEY], desc = meta && meta.descs[keyName], ret; + if (desc) { + return desc.get(obj, keyName); + } else { + if (MANDATORY_SETTER && meta && meta.watching[keyName] > 0) { + ret = meta.values[keyName]; + } else { + ret = obj[keyName]; + } + + if (ret === undefined && + 'object' === typeof obj && !(keyName in obj) && 'function' === typeof obj.unknownProperty) { + return obj.unknownProperty(keyName); + } + + return ret; + } }; /** @private */ -set = function(obj, keyName, value) { +set = function set(obj, keyName, value) { Ember.assert("You need to provide an object and key to `set`.", !!obj && keyName !== undefined); + Ember.assert('calling set on destroyed object', !obj.isDestroyed); - var desc = meta(obj, false).descs[keyName]; - if (desc) { desc.set(obj, keyName, value); } - else { basicSet(obj, keyName, value); } + var meta = obj[META_KEY], desc = meta && meta.descs[keyName], + isUnknown, currentValue; + if (desc) { + desc.set(obj, keyName, value); + } + else { + isUnknown = 'object' === typeof obj && !(keyName in obj); + + // setUnknownProperty is called if `obj` is an object, + // the property does not already exist, and the + // `setUnknownProperty` method exists on the object + if (isUnknown && 'function' === typeof obj.setUnknownProperty) { + obj.setUnknownProperty(keyName, value); + } else if (meta && meta.watching[keyName] > 0) { + if (MANDATORY_SETTER) { + currentValue = meta.values[keyName]; + } else { + currentValue = obj[keyName]; + } + // only trigger a change if the value has changed + if (value !== currentValue) { + Ember.propertyWillChange(obj, keyName); + if (MANDATORY_SETTER) { + meta.values[keyName] = value; + } else { + obj[keyName] = value; + } + Ember.propertyDidChange(obj, keyName); + } + } else { + obj[keyName] = value; + } + } return value; }; @@ -1379,7 +1437,6 @@ function getPath(target, path) { return target; } -var TUPLE_RET = []; var IS_GLOBAL = /^([A-Z$]|([0-9][A-Z$]))/; var IS_GLOBAL_PATH = /^([A-Z$]|([0-9][A-Z$])).*[\.\*]/; var HAS_THIS = /^this[\.\*]/; @@ -1409,9 +1466,7 @@ function normalizeTuple(target, path) { // must return some kind of path to be valid else other things will break. if (!path || path.length===0) throw new Error('Invalid Path'); - TUPLE_RET[0] = target; - TUPLE_RET[1] = path; - return TUPLE_RET; + return [ target, path ]; } /** @@ -1545,12 +1600,14 @@ Ember.isGlobalPath = function(path) { // Copyright: ©2011 Strobe Inc. and contributors. // License: Licensed under MIT license (see license.js) // ========================================================================== -var USE_ACCESSORS = Ember.USE_ACCESSORS, - GUID_KEY = Ember.GUID_KEY, +var GUID_KEY = Ember.GUID_KEY, META_KEY = Ember.META_KEY, - meta = Ember.meta, - objectDefineProperty = Ember.platform.defineProperty, - SIMPLE_PROPERTY, WATCHED_PROPERTY; + EMPTY_META = Ember.EMPTY_META, + metaFor = Ember.meta, + o_create = Ember.create, + objectDefineProperty = Ember.platform.defineProperty; + +var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER; // .......................................................... // DESCRIPTOR @@ -1567,238 +1624,10 @@ var USE_ACCESSORS = Ember.USE_ACCESSORS, */ var Descriptor = Ember.Descriptor = function() {}; -var setup = Descriptor.setup = function(obj, keyName, value) { - objectDefineProperty(obj, keyName, { - writable: true, - configurable: true, - enumerable: true, - value: value - }); -}; - -var DescriptorPrototype = Ember.Descriptor.prototype; - -/** - Called whenever we want to set the property value. Should set the value - and return the actual set value (which is usually the same but may be - different in the case of computed properties.) - - @param {Object} obj - The object to set the value on. - - @param {String} keyName - The key to set. - - @param {Object} value - The new value - - @returns {Object} value actual set value -*/ -DescriptorPrototype.set = function(obj, keyName, value) { - obj[keyName] = value; - return value; -}; - -/** - Called whenever we want to get the property value. Should retrieve the - current value. - - @param {Object} obj - The object to get the value on. - - @param {String} keyName - The key to retrieve - - @returns {Object} the current value -*/ -DescriptorPrototype.get = function(obj, keyName) { - return get(obj, keyName, obj); -}; - -/** - This is called on the descriptor to set it up on the object. The - descriptor is responsible for actually defining the property on the object - here. - - The passed `value` is the transferValue returned from any previous - descriptor. - - @param {Object} obj - The object to set the value on. - - @param {String} keyName - The key to set. - - @param {Object} value - The transfer value from any previous descriptor. - - @returns {void} -*/ -DescriptorPrototype.setup = setup; - -/** - This is called on the descriptor just before another descriptor takes its - place. This method should at least return the 'transfer value' of the - property - which is the value you want to passed as the input to the new - descriptor's setup() method. - - It is not generally necessary to actually 'undefine' the property as a new - property descriptor will redefine it immediately after this method returns. - - @param {Object} obj - The object to set the value on. - - @param {String} keyName - The key to set. - - @returns {Object} transfer value -*/ -DescriptorPrototype.teardown = function(obj, keyName) { - return obj[keyName]; -}; - -DescriptorPrototype.val = function(obj, keyName) { - return obj[keyName]; -}; - -// .......................................................... -// SIMPLE AND WATCHED PROPERTIES -// - -// The exception to this is that any objects managed by Ember but not a descendant -// of Ember.Object will not throw an exception, instead failing silently. This -// prevent errors with other libraries that may attempt to access special -// properties on standard objects like Array. Usually this happens when copying -// an object by looping over all properties. -// -// QUESTION: What is this scenario exactly? -var mandatorySetter = Ember.Descriptor.MUST_USE_SETTER = function() { - if (this instanceof Ember.Object) { - if (this.isDestroyed) { - Ember.assert('You cannot set observed properties on destroyed objects', false); - } else { - Ember.assert('Must use Ember.set() to access this property', false); - } - } -}; - -var WATCHED_DESC = { - configurable: true, - enumerable: true, - set: mandatorySetter -}; - -/** @private */ -function rawGet(obj, keyName, values) { - var ret = values[keyName]; - if (ret === undefined && obj.unknownProperty) { - ret = obj.unknownProperty(keyName); - } - return ret; -} - -function get(obj, keyName) { - return rawGet(obj, keyName, obj); -} - -var emptyObject = {}; - -function watchedGet(obj, keyName) { - return rawGet(obj, keyName, meta(obj, false).values || emptyObject); -} - -var hasGetters = Ember.platform.hasPropertyAccessors, rawSet; - -rawSet = function(obj, keyName, value, values) { - values[keyName] = value; -}; - -// if there are no getters, keep the raw property up to date -if (!hasGetters) { - rawSet = function(obj, keyName, value, values) { - obj[keyName] = value; - values[keyName] = value; - }; -} - -/** @private */ -function watchedSet(obj, keyName, value) { - var m = meta(obj), - values = m.values, - changed = value !== values[keyName]; - - if (changed) { - Ember.propertyWillChange(obj, keyName); - rawSet(obj, keyName, value, m.values); - Ember.propertyDidChange(obj, keyName); - } - - return value; -} - -/** @private */ -function makeWatchedGetter(keyName) { - return function() { - return watchedGet(this, keyName); - }; -} - -/** @private */ -function makeWatchedSetter(keyName) { - return function(value) { - return watchedSet(this, keyName, value); - }; -} - -/** - @private - - Private version of simple property that invokes property change callbacks. -*/ -WATCHED_PROPERTY = new Ember.Descriptor(); -WATCHED_PROPERTY.get = watchedGet ; -WATCHED_PROPERTY.set = watchedSet ; - -WATCHED_PROPERTY.setup = function(obj, keyName, value) { - objectDefineProperty(obj, keyName, { - configurable: true, - enumerable: true, - set: mandatorySetter, - get: makeWatchedGetter(keyName) - }); - - meta(obj).values[keyName] = value; -}; - -WATCHED_PROPERTY.teardown = function(obj, keyName) { - var ret = meta(obj).values[keyName]; - delete meta(obj).values[keyName]; - return ret; -}; - -/** - The default descriptor for simple properties. Pass as the third argument - to Ember.defineProperty() along with a value to set a simple value. - - @static - @default Ember.Descriptor -*/ -Ember.SIMPLE_PROPERTY = new Ember.Descriptor(); -SIMPLE_PROPERTY = Ember.SIMPLE_PROPERTY; - -SIMPLE_PROPERTY.unwatched = WATCHED_PROPERTY.unwatched = SIMPLE_PROPERTY; -SIMPLE_PROPERTY.watched = WATCHED_PROPERTY.watched = WATCHED_PROPERTY; - // .......................................................... // DEFINING PROPERTIES API // -/** @private */ -function hasDesc(descs, keyName) { - if (keyName === 'toString') return 'function' !== typeof descs.toString; - else return !!descs[keyName]; -} - /** @private @@ -1826,53 +1655,66 @@ function hasDesc(descs, keyName) { }); // define a simple property - Ember.defineProperty(contact, 'lastName', Ember.SIMPLE_PROPERTY, 'Jolley'); + Ember.defineProperty(contact, 'lastName', undefined, 'Jolley'); // define a computed property Ember.defineProperty(contact, 'fullName', Ember.computed(function() { return this.firstName+' '+this.lastName; }).property('firstName', 'lastName').cacheable()); */ -Ember.defineProperty = function(obj, keyName, desc, val) { - var m = meta(obj, false), - descs = m.descs, - watching = m.watching[keyName]>0, - override = true; +Ember.defineProperty = function(obj, keyName, desc, val, meta) { + var descs, existingDesc, watching; - if (val === undefined) { - // if a value wasn't provided, the value is the old value - // (which can be obtained by calling teardown on a property - // with a descriptor). - override = false; - val = hasDesc(descs, keyName) ? descs[keyName].teardown(obj, keyName) : obj[keyName]; - } else if (hasDesc(descs, keyName)) { - // otherwise, tear down the descriptor, but use the provided - // value as the new value instead of the descriptor's current - // value. - descs[keyName].teardown(obj, keyName); - } + if (!meta) meta = metaFor(obj); + descs = meta.descs; + existingDesc = meta.descs[keyName]; + watching = meta.watching[keyName] > 0; - if (!desc) { - desc = SIMPLE_PROPERTY; + if (existingDesc instanceof Ember.Descriptor) { + existingDesc.teardown(obj, keyName); } if (desc instanceof Ember.Descriptor) { - m = meta(obj, true); - descs = m.descs; - - desc = (watching ? desc.watched : desc.unwatched) || desc; descs[keyName] = desc; - desc.setup(obj, keyName, val, watching); - - // compatibility with ES5 + if (MANDATORY_SETTER && watching) { + objectDefineProperty(obj, keyName, { + configurable: true, + enumerable: true, + writable: true, + value: undefined // make enumerable + }); + } else { + obj[keyName] = undefined; // make enumerable + } + desc.setup(obj, keyName); } else { - if (descs[keyName]) meta(obj).descs[keyName] = null; - objectDefineProperty(obj, keyName, desc); + descs[keyName] = undefined; // shadow descriptor in proto + if (desc == null) { + if (MANDATORY_SETTER && watching) { + meta.values[keyName] = val; + objectDefineProperty(obj, keyName, { + configurable: true, + enumerable: true, + set: function() { + Ember.assert('Must use Ember.set() to access this property', false); + }, + get: function() { + var meta = this[META_KEY]; + return meta && meta.values[keyName]; + } + }); + } else { + obj[keyName] = val; + } + } else { + // compatibility with ES5 + objectDefineProperty(obj, keyName, desc); + } } // if key is being watched, override chains that // were initialized with the prototype - if (override && watching) Ember.overrideChains(obj, keyName, m); + if (watching) { Ember.overrideChains(obj, keyName, meta); } return this; }; @@ -1882,439 +1724,6 @@ Ember.defineProperty = function(obj, keyName, desc, val) { -(function() { -// ========================================================================== -// Project: Ember Metal -// Copyright: ©2011 Strobe Inc. and contributors. -// License: Licensed under MIT license (see license.js) -// ========================================================================== -Ember.warn("Computed properties will soon be cacheable by default. To enable this in your app, set `ENV.CP_DEFAULT_CACHEABLE = true`.", Ember.CP_DEFAULT_CACHEABLE); - - -var get = Ember.get, - getPath = Ember.getPath, - meta = Ember.meta, - guidFor = Ember.guidFor, - USE_ACCESSORS = Ember.USE_ACCESSORS, - a_slice = [].slice, - o_create = Ember.create, - o_defineProperty = Ember.platform.defineProperty; - -// .......................................................... -// DEPENDENT KEYS -// - -// data structure: -// meta.deps = { -// 'depKey': { -// 'keyName': count, -// __emberproto__: SRC_OBJ [to detect clones] -// }, -// __emberproto__: SRC_OBJ -// } - -/** - @private - - This function returns a map of unique dependencies for a - given object and key. -*/ -function uniqDeps(obj, depKey) { - var m = meta(obj), deps = m.deps, ret; - - // If the current object has no dependencies... - if (!deps) { - // initialize the dependencies with a pointer back to - // the current object - deps = m.deps = { __emberproto__: obj }; - } else if (deps.__emberproto__ !== obj) { - // otherwise if the dependencies are inherited from the - // object's superclass, clone the deps - deps = m.deps = o_create(deps); - deps.__emberproto__ = obj; - } - - ret = deps[depKey]; - - if (!ret) { - // if there are no dependencies yet for a the given key - // create a new empty list of dependencies for the key - ret = deps[depKey] = { __emberproto__: obj }; - } else if (ret.__emberproto__ !== obj) { - // otherwise if the dependency list is inherited from - // a superclass, clone the hash - ret = deps[depKey] = o_create(ret); - ret.__emberproto__ = obj; - } - - return ret; -} - -/** - @private - - This method allows us to register that a particular - property depends on another property. -*/ -function addDependentKey(obj, keyName, depKey) { - var deps = uniqDeps(obj, depKey); - - // Increment the number of times depKey depends on - // keyName. - deps[keyName] = (deps[keyName] || 0) + 1; - - // Watch the depKey - Ember.watch(obj, depKey); -} - -/** - @private - - This method removes a dependency. -*/ -function removeDependentKey(obj, keyName, depKey) { - var deps = uniqDeps(obj, depKey); - - // Decrement the number of times depKey depends - // on keyName. - deps[keyName] = (deps[keyName] || 0) - 1; - - // Unwatch the depKey - Ember.unwatch(obj, depKey); -} - -/** @private */ -function addDependentKeys(desc, obj, keyName) { - // the descriptor has a list of dependent keys, so - // add all of its dependent keys. - var keys = desc._dependentKeys, - len = keys ? keys.length : 0; - - for(var idx=0; idx 0, - oldSuspended = desc._suspended, - ret; - - desc._suspended = this; - - if (watched) { Ember.propertyWillChange(this, keyName); } - if (cacheable) delete m.cache[keyName]; - ret = func.call(this, keyName, value); - if (cacheable) { m.cache[keyName] = ret; } - if (watched) { Ember.propertyDidChange(this, keyName); } - desc._suspended = oldSuspended; - return ret; - }; -} - -/** - @extends Ember.ComputedProperty - @private -*/ -var ComputedPropertyPrototype = ComputedProperty.prototype; - -/** - Call on a computed property to set it into cacheable mode. When in this - mode the computed property will automatically cache the return value of - your function until one of the dependent keys changes. - - MyApp.president = Ember.Object.create({ - fullName: function() { - return this.get('firstName') + ' ' + this.get('lastName'); - - // After calculating the value of this function, Ember.js will - // return that value without re-executing this function until - // one of the dependent properties change. - }.property('firstName', 'lastName').cacheable() - }); - - Properties are cacheable by default. - - @name Ember.ComputedProperty.cacheable - @param {Boolean} aFlag optional set to false to disable caching - @returns {Ember.ComputedProperty} receiver -*/ -ComputedPropertyPrototype.cacheable = function(aFlag) { - this._cacheable = aFlag !== false; - return this; -}; - -/** - Call on a computed property to set it into non-cached mode. When in this - mode the computed property will not automatically cache the return value. - - MyApp.outsideService = Ember.Object.create({ - value: function() { - return OutsideService.getValue(); - }.property().volatile() - }); - - @name Ember.ComputedProperty.volatile - @returns {Ember.ComputedProperty} receiver -*/ -ComputedPropertyPrototype.volatile = function() { - return this.cacheable(false); -}; - -/** - Sets the dependent keys on this computed property. Pass any number of - arguments containing key paths that this computed property depends on. - - MyApp.president = Ember.Object.create({ - fullName: Ember.computed(function() { - return this.get('firstName') + ' ' + this.get('lastName'); - - // Tell Ember.js that this computed property depends on firstName - // and lastName - }).property('firstName', 'lastName') - }); - - @name Ember.ComputedProperty.property - @param {String} path... zero or more property paths - @returns {Ember.ComputedProperty} receiver -*/ -ComputedPropertyPrototype.property = function() { - this._dependentKeys = a_slice.call(arguments); - return this; -}; - -/** - In some cases, you may want to annotate computed properties with additional - metadata about how they function or what values they operate on. For example, - computed property functions may close over variables that are then no longer - available for introspection. - - You can pass a hash of these values to a computed property like this: - - person: function() { - var personId = this.get('personId'); - return App.Person.create({ id: personId }); - }.property().meta({ type: App.Person }) - - The hash that you pass to the `meta()` function will be saved on the - computed property descriptor under the `_meta` key. Ember runtime - exposes a public API for retrieving these values from classes, - via the `metaForProperty()` function. - - @name Ember.ComputedProperty.meta - @param {Hash} metadata - @returns {Ember.ComputedProperty} property descriptor instance -*/ - -ComputedPropertyPrototype.meta = function(meta) { - this._meta = meta; - return this; -}; - -/** @private - impl descriptor API */ -ComputedPropertyPrototype.setup = function(obj, keyName, value) { - CP_DESC.get = mkCpGetter(keyName, this); - CP_DESC.set = mkCpSetter(keyName, this); - o_defineProperty(obj, keyName, CP_DESC); - CP_DESC.get = CP_DESC.set = null; - addDependentKeys(this, obj, keyName); -}; - -/** @private - impl descriptor API */ -ComputedPropertyPrototype.teardown = function(obj, keyName) { - var keys = this._dependentKeys, - len = keys ? keys.length : 0; - - for(var idx=0; idx < len; idx++) { - removeDependentKey(obj, keyName, keys[idx]); - } - - if (this._cacheable) { delete meta(obj).cache[keyName]; } - - return null; // no value to restore -}; - -/** @private - impl descriptor API */ -ComputedPropertyPrototype.didChange = function(obj, keyName) { - if (this._cacheable && this._suspended !== obj) { - delete meta(obj).cache[keyName]; - } -}; - -/** @private - impl descriptor API */ -ComputedPropertyPrototype.get = function(obj, keyName) { - var ret, cache; - - if (this._cacheable) { - cache = meta(obj).cache; - if (keyName in cache) { return cache[keyName]; } - ret = cache[keyName] = this.func.call(obj, keyName); - } else { - ret = this.func.call(obj, keyName); - } - return ret; -}; - -/** @private - impl descriptor API */ -ComputedPropertyPrototype.set = function(obj, keyName, value) { - var cacheable = this._cacheable, - m = meta(obj, cacheable), - watched = m.source === obj && m.watching[keyName] > 0, - oldSuspended = this._suspended, - ret; - - this._suspended = obj; - - if (watched) { Ember.propertyWillChange(obj, keyName); } - if (cacheable) delete m.cache[keyName]; - ret = this.func.call(obj, keyName, value); - if (cacheable) { m.cache[keyName] = ret; } - if (watched) { Ember.propertyDidChange(obj, keyName); } - this._suspended = oldSuspended; - return ret; -}; - -ComputedPropertyPrototype.val = function(obj, keyName) { - return meta(obj, false).values[keyName]; -}; - -if (!Ember.platform.hasPropertyAccessors) { - ComputedPropertyPrototype.setup = function(obj, keyName, value) { - obj[keyName] = undefined; // so it shows up in key iteration - addDependentKeys(this, obj, keyName); - }; - -} else if (!USE_ACCESSORS) { - ComputedPropertyPrototype.setup = function(obj, keyName) { - // throw exception if not using Ember.get() and Ember.set() when supported - o_defineProperty(obj, keyName, CP_DESC); - addDependentKeys(this, obj, keyName); - }; -} - -/** - This helper returns a new property descriptor that wraps the passed - computed property function. You can use this helper to define properties - with mixins or via Ember.defineProperty(). - - The function you pass will be used to both get and set property values. - The function should accept two parameters, key and value. If value is not - undefined you should set the value first. In either case return the - current value of the property. - - @param {Function} func - The computed property function. - - @returns {Ember.ComputedProperty} property descriptor instance -*/ -Ember.computed = function(func) { - var args; - - if (arguments.length > 1) { - args = a_slice.call(arguments, 0, -1); - func = a_slice.call(arguments, -1)[0]; - } - - var cp = new ComputedProperty(func); - - if (args) { - cp.property.apply(cp, args); - } - - return cp; -}; - -/** - Returns the cached value for a property, if one exists. - This can be useful for peeking at the value of a computed - property that is generated lazily, without accidentally causing - it to be created. - - @param {Object} obj the object whose property you want to check - @param {String} key the name of the property whose cached value you want - to return - -*/ -Ember.cacheFor = function(obj, key) { - var cache = meta(obj, false).cache; - - if (cache && key in cache) { - return cache[key]; - } -}; - -Ember.computed.not = function(dependentKey) { - return Ember.computed(dependentKey, function(key) { - return !getPath(this, dependentKey); - }).cacheable(); -}; - -Ember.computed.empty = function(dependentKey) { - return Ember.computed(dependentKey, function(key) { - var val = getPath(this, dependentKey); - return val === undefined || val === null || val === '' || (Ember.isArray(val) && get(val, 'length') === 0); - }).cacheable(); -}; - -Ember.computed.bool = function(dependentKey) { - return Ember.computed(dependentKey, function(key) { - return !!getPath(this, dependentKey); - }).cacheable(); -}; - -})(); - - - (function() { // ========================================================================== // Project: Ember Metal @@ -2355,7 +1764,7 @@ var DeferredEventQueue = function() { this.queue = []; }; -DeferredEventQueue.prototype.push = function(target, eventName) { +DeferredEventQueue.prototype.push = function(target, eventName, keyName) { var targetSet = this.targetSet, queue = this.queue, targetGuid = Ember.guidFor(target), @@ -2367,9 +1776,9 @@ DeferredEventQueue.prototype.push = function(target, eventName) { } index = eventNameSet[eventName]; if (index === undefined) { - eventNameSet[eventName] = queue.push(Ember.deferEvent(target, eventName)) - 1; + eventNameSet[eventName] = queue.push(Ember.deferEvent(target, eventName, [target, keyName])) - 1; } else { - queue[index] = Ember.deferEvent(target, eventName); + queue[index] = Ember.deferEvent(target, eventName, [target, keyName]); } }; @@ -2385,11 +1794,11 @@ DeferredEventQueue.prototype.flush = function() { var queue = new DeferredEventQueue(), beforeObserverSet = new ObserverSet(); /** @private */ -function notifyObservers(obj, eventName, forceNotification) { +function notifyObservers(obj, eventName, keyName, forceNotification) { if (deferred && !forceNotification) { - queue.push(obj, eventName); + queue.push(obj, eventName, keyName); } else { - Ember.sendEvent(obj, eventName); + Ember.sendEvent(obj, eventName, [obj, keyName]); } } @@ -2453,47 +1862,8 @@ function beforeEvent(keyName) { return keyName+BEFORE_OBSERVERS; } -/** @private */ -function changeKey(eventName) { - return eventName.slice(0, -7); -} - -/** @private */ -function beforeKey(eventName) { - return eventName.slice(0, -7); -} - -/** @private */ -function xformForArgs(args) { - return function (target, method, params) { - var obj = params[0], keyName = changeKey(params[1]), val; - var copy_args = args.slice(); - if (method.length>2) { - val = Ember.getPath(Ember.isGlobalPath(keyName) ? window : obj, keyName); - } - copy_args.unshift(obj, keyName, val); - method.apply(target, copy_args); - }; -} - -var xformChange = xformForArgs([]); - -/** @private */ -function xformBefore(target, method, params) { - var obj = params[0], keyName = beforeKey(params[1]), val; - if (method.length>2) val = Ember.getPath(obj, keyName); - method.call(target, obj, keyName, val); -} - Ember.addObserver = function(obj, path, target, method) { - var xform; - if (arguments.length > 4) { - var args = array_Slice.call(arguments, 4); - xform = xformForArgs(args); - } else { - xform = xformChange; - } - Ember.addListener(obj, changeEvent(path), target, method, xform); + Ember.addListener(obj, changeEvent(path), target, method); Ember.watch(obj, path); return this; }; @@ -2510,7 +1880,7 @@ Ember.removeObserver = function(obj, path, target, method) { }; Ember.addBeforeObserver = function(obj, path, target, method) { - Ember.addListener(obj, beforeEvent(path), target, method, xformBefore); + Ember.addListener(obj, beforeEvent(path), target, method); Ember.watch(obj, path); return this; }; @@ -2543,7 +1913,7 @@ Ember.removeBeforeObserver = function(obj, path, target, method) { Ember.notifyObservers = function(obj, keyName) { if (obj.isDestroying) { return; } - notifyObservers(obj, changeEvent(keyName)); + notifyObservers(obj, changeEvent(keyName), keyName); }; /** @private */ @@ -2560,7 +1930,7 @@ Ember.notifyBeforeObservers = function(obj, keyName) { } } - notifyObservers(obj, beforeEvent(keyName), forceNotification); + notifyObservers(obj, beforeEvent(keyName), keyName, forceNotification); }; @@ -2574,19 +1944,23 @@ Ember.notifyBeforeObservers = function(obj, keyName) { // Copyright: ©2011 Strobe Inc. and contributors. // License: Licensed under MIT license (see license.js) // ========================================================================== -var guidFor = Ember.guidFor, - meta = Ember.meta, - get = Ember.get, - set = Ember.set, - normalizeTuple = Ember.normalizeTuple.primitive, - SIMPLE_PROPERTY = Ember.SIMPLE_PROPERTY, - GUID_KEY = Ember.GUID_KEY, - META_KEY = Ember.META_KEY, - notifyObservers = Ember.notifyObservers, - forEach = Ember.ArrayPolyfills.forEach, +var guidFor = Ember.guidFor, // utils.js + metaFor = Ember.meta, // utils.js + get = Ember.get, // accessors.js + set = Ember.set, // accessors.js + normalizeTuple = Ember.normalizeTuple.primitive, // accessors.js + GUID_KEY = Ember.GUID_KEY, // utils.js + META_KEY = Ember.META_KEY, // utils.js + // circular reference observer depends on Ember.watch + // we should move change events to this file or its own property_events.js + notifyObservers = Ember.notifyObservers, // observer.js + forEach = Ember.ArrayPolyfills.forEach, // array.js FIRST_KEY = /^([^\.\*]+)/, IS_PATH = /[\.\*]/; +var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER, +o_defineProperty = Ember.platform.defineProperty; + /** @private */ function firstKey(path) { return path.match(FIRST_KEY)[0]; @@ -2654,7 +2028,7 @@ function dependentKeysDidChange(obj, depKey, meta) { /** @private */ function addChainWatcher(obj, keyName, node) { if (!obj || ('object' !== typeof obj)) return; // nothing to do - var m = meta(obj); + var m = metaFor(obj); var nodes = m.chainWatchers; if (!nodes || nodes.__emberproto__ !== obj) { nodes = m.chainWatchers = { __emberproto__: obj }; @@ -2668,7 +2042,7 @@ function addChainWatcher(obj, keyName, node) { /** @private */ function removeChainWatcher(obj, keyName, node) { if (!obj || 'object' !== typeof obj) { return; } // nothing to do - var m = meta(obj, false), + var m = metaFor(obj, false), nodes = m.chainWatchers; if (!nodes || nodes.__emberproto__ !== obj) { return; } //nothing to do if (nodes[keyName]) { delete nodes[keyName][guidFor(node)]; } @@ -2694,7 +2068,7 @@ function flushPendingChains() { /** @private */ function isProto(pvalue) { - return meta(pvalue, false).proto === pvalue; + return metaFor(pvalue, false).proto === pvalue; } // A ChainNode watches a single key on an object. If you provide a starting @@ -2930,7 +2304,7 @@ ChainNodePrototype.didChange = function(suppressEvent) { // the current object. /** @private */ function chainsFor(obj) { - var m = meta(obj), ret = m.chains; + var m = metaFor(obj), ret = m.chains; if (!ret) { ret = m.chains = new ChainNode(null, null, obj); } else if (ret.value() !== obj) { @@ -2972,8 +2346,6 @@ function chainsDidChange(obj, keyName, m) { // WATCH // -var WATCHED_PROPERTY = Ember.SIMPLE_PROPERTY.watched; - /** @private @@ -2987,19 +2359,33 @@ Ember.watch = function(obj, keyName) { // can't watch length on Array - it is special... if (keyName === 'length' && Ember.typeOf(obj) === 'array') { return this; } - var m = meta(obj), watching = m.watching, desc; + var m = metaFor(obj), watching = m.watching, desc; // activate watching first time if (!watching[keyName]) { watching[keyName] = 1; if (isKeyName(keyName)) { + desc = m.descs[keyName]; + if (desc && desc.willWatch) { desc.willWatch(obj, keyName); } + if ('function' === typeof obj.willWatchProperty) { obj.willWatchProperty(keyName); } - desc = m.descs[keyName]; - desc = desc ? desc.watched : WATCHED_PROPERTY; - if (desc) { Ember.defineProperty(obj, keyName, desc); } + if (MANDATORY_SETTER && keyName in obj) { + m.values[keyName] = obj[keyName]; + o_defineProperty(obj, keyName, { + configurable: true, + enumerable: true, + set: function() { + Ember.assert('Must use Ember.set() to access this property', false); + }, + get: function() { + var meta = this[META_KEY]; + return meta && meta.values[keyName]; + } + }); + } } else { chainsFor(obj).add(keyName); } @@ -3010,8 +2396,9 @@ Ember.watch = function(obj, keyName) { return this; }; -Ember.isWatching = function(obj, keyName) { - return !!meta(obj).watching[keyName]; +Ember.isWatching = function isWatching(obj, key) { + var meta = obj[META_KEY]; + return (meta && meta.watching[key]) > 0; }; Ember.watch.flushPending = flushPendingChains; @@ -3021,18 +2408,28 @@ Ember.unwatch = function(obj, keyName) { // can't watch length on Array - it is special... if (keyName === 'length' && Ember.typeOf(obj) === 'array') { return this; } - var watching = meta(obj).watching, desc, descs; + var m = metaFor(obj), watching = m.watching, desc; if (watching[keyName] === 1) { watching[keyName] = 0; + if (isKeyName(keyName)) { - desc = meta(obj).descs[keyName]; - desc = desc ? desc.unwatched : SIMPLE_PROPERTY; - if (desc) { Ember.defineProperty(obj, keyName, desc); } + desc = m.descs[keyName]; + if (desc && desc.didUnwatch) { desc.didUnwatch(obj, keyName); } if ('function' === typeof obj.didUnwatchProperty) { obj.didUnwatchProperty(keyName); } + + if (MANDATORY_SETTER && keyName in obj) { + o_defineProperty(obj, keyName, { + configurable: true, + enumerable: true, + writable: true, + value: m.values[keyName] + }); + delete m.values[keyName]; + } } else { chainsFor(obj).remove(keyName); } @@ -3052,7 +2449,7 @@ Ember.unwatch = function(obj, keyName) { safe to call multiple times. */ Ember.rewatch = function(obj) { - var m = meta(obj, false), chains = m.chains, bindings = m.bindings, key, b; + var m = metaFor(obj, false), chains = m.chains; // make sure the object has its own guid. if (GUID_KEY in obj && !obj.hasOwnProperty(GUID_KEY)) { @@ -3060,11 +2457,23 @@ Ember.rewatch = function(obj) { } // make sure any chained watchers update. - if (chains && chains.value() !== obj) { chainsFor(obj); } + if (chains && chains.value() !== obj) { + m.chains = chains.copy(obj); + } return this; }; +Ember.finishChains = function(obj) { + var m = metaFor(obj, false), chains = m.chains; + if (chains) { + if (chains.value() !== obj) { + m.chains = chains = chains.copy(obj); + } + chains.didChange(true); + } +}; + // .......................................................... // PROPERTY CHANGES // @@ -3089,7 +2498,7 @@ Ember.rewatch = function(obj) { @returns {void} */ function propertyWillChange(obj, keyName, value) { - var m = meta(obj, false), + var m = metaFor(obj, false), watching = m.watching[keyName] > 0 || keyName === 'length', proto = m.proto, desc = m.descs[keyName]; @@ -3124,7 +2533,7 @@ Ember.propertyWillChange = propertyWillChange; @returns {void} */ function propertyDidChange(obj, keyName) { - var m = meta(obj, false), + var m = metaFor(obj, false), watching = m.watching[keyName] > 0 || keyName === 'length', proto = m.proto, desc = m.descs[keyName]; @@ -3187,6 +2596,416 @@ Ember.destroy = function (obj) { +(function() { +// ========================================================================== +// Project: Ember Metal +// Copyright: ©2011 Strobe Inc. and contributors. +// License: Licensed under MIT license (see license.js) +// ========================================================================== +Ember.warn("Computed properties will soon be cacheable by default. To enable this in your app, set `ENV.CP_DEFAULT_CACHEABLE = true`.", Ember.CP_DEFAULT_CACHEABLE); + + +var get = Ember.get, + getPath = Ember.getPath, + metaFor = Ember.meta, + guidFor = Ember.guidFor, + a_slice = [].slice, + o_create = Ember.create, + META_KEY = Ember.META_KEY, + watch = Ember.watch, + unwatch = Ember.unwatch; + +// .......................................................... +// DEPENDENT KEYS +// + +// data structure: +// meta.deps = { +// 'depKey': { +// 'keyName': count, +// __emberproto__: SRC_OBJ [to detect clones] +// }, +// __emberproto__: SRC_OBJ +// } + +/** + @private + + This function returns a map of unique dependencies for a + given object and key. +*/ +function keysForDep(obj, depsMeta, depKey) { + var keys = depsMeta[depKey]; + if (!keys) { + // if there are no dependencies yet for a the given key + // create a new empty list of dependencies for the key + keys = depsMeta[depKey] = { __emberproto__: obj }; + } else if (keys.__emberproto__ !== obj) { + // otherwise if the dependency list is inherited from + // a superclass, clone the hash + keys = depsMeta[depKey] = o_create(keys); + keys.__emberproto__ = obj; + } + return keys; +} + +/** + @private + + return obj[META_KEY].deps + */ +function metaForDeps(obj, meta) { + var deps = meta.deps; + // If the current object has no dependencies... + if (!deps) { + // initialize the dependencies with a pointer back to + // the current object + deps = meta.deps = { __emberproto__: obj }; + } else if (deps.__emberproto__ !== obj) { + // otherwise if the dependencies are inherited from the + // object's superclass, clone the deps + deps = meta.deps = o_create(deps); + deps.__emberproto__ = obj; + } + return deps; +} + +/** @private */ +function addDependentKeys(desc, obj, keyName, meta) { + // the descriptor has a list of dependent keys, so + // add all of its dependent keys. + var depKeys = desc._dependentKeys, depsMeta, idx, len, depKey, keys; + if (!depKeys) return; + + depsMeta = metaForDeps(obj, meta); + + for(idx = 0, len = depKeys.length; idx < len; idx++) { + depKey = depKeys[idx]; + // Lookup keys meta for depKey + keys = keysForDep(obj, depsMeta, depKey); + // Increment the number of times depKey depends on keyName. + keys[keyName] = (keys[keyName] || 0) + 1; + // Watch the depKey + watch(obj, depKey); + } +} + +/** @private */ +function removeDependentKeys(desc, obj, keyName, meta) { + // the descriptor has a list of dependent keys, so + // add all of its dependent keys. + var depKeys = desc._dependentKeys, depsMeta, idx, len, depKey, keys; + if (!depKeys) return; + + depsMeta = metaForDeps(obj, meta); + + for(idx = 0, len = depKeys.length; idx < len; idx++) { + depKey = depKeys[idx]; + // Lookup keys meta for depKey + keys = keysForDep(obj, depsMeta, depKey); + // Increment the number of times depKey depends on keyName. + keys[keyName] = (keys[keyName] || 0) - 1; + // Watch the depKey + unwatch(obj, depKey); + } +} + +// .......................................................... +// COMPUTED PROPERTY +// + +/** @private */ +function ComputedProperty(func, opts) { + this.func = func; + this._cacheable = (opts && opts.cacheable !== undefined) ? opts.cacheable : Ember.CP_DEFAULT_CACHEABLE; + this._dependentKeys = opts && opts.dependentKeys; +} + +/** + @constructor +*/ +Ember.ComputedProperty = ComputedProperty; +ComputedProperty.prototype = new Ember.Descriptor(); + +/** + @extends Ember.ComputedProperty + @private +*/ +var ComputedPropertyPrototype = ComputedProperty.prototype; + +/** + Call on a computed property to set it into cacheable mode. When in this + mode the computed property will automatically cache the return value of + your function until one of the dependent keys changes. + + MyApp.president = Ember.Object.create({ + fullName: function() { + return this.get('firstName') + ' ' + this.get('lastName'); + + // After calculating the value of this function, Ember.js will + // return that value without re-executing this function until + // one of the dependent properties change. + }.property('firstName', 'lastName').cacheable() + }); + + Properties are cacheable by default. + + @name Ember.ComputedProperty.cacheable + @param {Boolean} aFlag optional set to false to disable caching + @returns {Ember.ComputedProperty} receiver +*/ +ComputedPropertyPrototype.cacheable = function(aFlag) { + this._cacheable = aFlag !== false; + return this; +}; + +/** + Call on a computed property to set it into non-cached mode. When in this + mode the computed property will not automatically cache the return value. + + MyApp.outsideService = Ember.Object.create({ + value: function() { + return OutsideService.getValue(); + }.property().volatile() + }); + + @name Ember.ComputedProperty.volatile + @returns {Ember.ComputedProperty} receiver +*/ +ComputedPropertyPrototype.volatile = function() { + return this.cacheable(false); +}; + +/** + Sets the dependent keys on this computed property. Pass any number of + arguments containing key paths that this computed property depends on. + + MyApp.president = Ember.Object.create({ + fullName: Ember.computed(function() { + return this.get('firstName') + ' ' + this.get('lastName'); + + // Tell Ember.js that this computed property depends on firstName + // and lastName + }).property('firstName', 'lastName') + }); + + @name Ember.ComputedProperty.property + @param {String} path... zero or more property paths + @returns {Ember.ComputedProperty} receiver +*/ +ComputedPropertyPrototype.property = function() { + var args = []; + for (var i = 0, l = arguments.length; i < l; i++) { + args.push(arguments[i]); + } + this._dependentKeys = args; + return this; +}; + +/** + In some cases, you may want to annotate computed properties with additional + metadata about how they function or what values they operate on. For example, + computed property functions may close over variables that are then no longer + available for introspection. + + You can pass a hash of these values to a computed property like this: + + person: function() { + var personId = this.get('personId'); + return App.Person.create({ id: personId }); + }.property().meta({ type: App.Person }) + + The hash that you pass to the `meta()` function will be saved on the + computed property descriptor under the `_meta` key. Ember runtime + exposes a public API for retrieving these values from classes, + via the `metaForProperty()` function. + + @name Ember.ComputedProperty.meta + @param {Hash} metadata + @returns {Ember.ComputedProperty} property descriptor instance +*/ + +ComputedPropertyPrototype.meta = function(meta) { + this._meta = meta; + return this; +}; + +/** @private - impl descriptor API */ +ComputedPropertyPrototype.willWatch = function(obj, keyName) { + // watch already creates meta for this instance + var meta = obj[META_KEY]; + Ember.assert('watch should have setup meta to be writable', meta.source === obj); + if (!(keyName in meta.cache)) { + addDependentKeys(this, obj, keyName, meta); + } +}; + +ComputedPropertyPrototype.didUnwatch = function(obj, keyName) { + var meta = obj[META_KEY]; + Ember.assert('unwatch should have setup meta to be writable', meta.source === obj); + if (!(keyName in meta.cache)) { + // unwatch already creates meta for this instance + removeDependentKeys(this, obj, keyName, meta); + } +}; + +/** @private - impl descriptor API */ +ComputedPropertyPrototype.didChange = function(obj, keyName) { + // _suspended is set via a CP.set to ensure we don't clear + // the cached value set by the setter + if (this._cacheable && this._suspended !== obj) { + var meta = metaFor(obj); + if (keyName in meta.cache) { + delete meta.cache[keyName]; + if (!meta.watching[keyName]) { + removeDependentKeys(this, obj, keyName, meta); + } + } + } +}; + +/** @private - impl descriptor API */ +ComputedPropertyPrototype.get = function(obj, keyName) { + var ret, cache, meta; + if (this._cacheable) { + meta = metaFor(obj); + cache = meta.cache; + if (keyName in cache) { return cache[keyName]; } + ret = cache[keyName] = this.func.call(obj, keyName); + if (!meta.watching[keyName]) { + addDependentKeys(this, obj, keyName, meta); + } + } else { + ret = this.func.call(obj, keyName); + } + return ret; +}; + +/** @private - impl descriptor API */ +ComputedPropertyPrototype.set = function(obj, keyName, value) { + var cacheable = this._cacheable, + meta = metaFor(obj, cacheable), + watched = meta.watching[keyName], + oldSuspended = this._suspended, + hadCachedValue, + ret; + + this._suspended = obj; + + if (watched) { Ember.propertyWillChange(obj, keyName); } + if (cacheable) { + if (keyName in meta.cache) { + delete meta.cache[keyName]; + hadCachedValue = true; + } + } + ret = this.func.call(obj, keyName, value); + if (cacheable) { + if (!watched && !hadCachedValue) { + addDependentKeys(this, obj, keyName, meta); + } + meta.cache[keyName] = ret; + } + if (watched) { Ember.propertyDidChange(obj, keyName); } + this._suspended = oldSuspended; + return ret; +}; + +/** @private - called when property is defined */ +ComputedPropertyPrototype.setup = function(obj, keyName) { + var meta = obj[META_KEY]; + if (meta && meta.watching[keyName]) { + addDependentKeys(this, obj, keyName, metaFor(obj)); + } +}; + +/** @private - called before property is overridden */ +ComputedPropertyPrototype.teardown = function(obj, keyName) { + var meta = metaFor(obj); + + if (meta.watching[keyName] || keyName in meta.cache) { + removeDependentKeys(this, obj, keyName, meta); + } + + if (this._cacheable) { delete meta.cache[keyName]; } + + return null; // no value to restore +}; + +/** + This helper returns a new property descriptor that wraps the passed + computed property function. You can use this helper to define properties + with mixins or via Ember.defineProperty(). + + The function you pass will be used to both get and set property values. + The function should accept two parameters, key and value. If value is not + undefined you should set the value first. In either case return the + current value of the property. + + @param {Function} func + The computed property function. + + @returns {Ember.ComputedProperty} property descriptor instance +*/ +Ember.computed = function(func) { + var args; + + if (arguments.length > 1) { + args = a_slice.call(arguments, 0, -1); + func = a_slice.call(arguments, -1)[0]; + } + + var cp = new ComputedProperty(func); + + if (args) { + cp.property.apply(cp, args); + } + + return cp; +}; + +/** + Returns the cached value for a property, if one exists. + This can be useful for peeking at the value of a computed + property that is generated lazily, without accidentally causing + it to be created. + + @param {Object} obj the object whose property you want to check + @param {String} key the name of the property whose cached value you want + to return + +*/ +Ember.cacheFor = function cacheFor(obj, key) { + var cache = metaFor(obj, false).cache; + + if (cache && key in cache) { + return cache[key]; + } +}; + +Ember.computed.not = function(dependentKey) { + return Ember.computed(dependentKey, function(key) { + return !getPath(this, dependentKey); + }).cacheable(); +}; + +Ember.computed.empty = function(dependentKey) { + return Ember.computed(dependentKey, function(key) { + var val = getPath(this, dependentKey); + return val === undefined || val === null || val === '' || (Ember.isArray(val) && get(val, 'length') === 0); + }).cacheable(); +}; + +Ember.computed.bool = function(dependentKey) { + return Ember.computed(dependentKey, function(key) { + return !!getPath(this, dependentKey); + }).cacheable(); +}; + +})(); + + + (function() { // ========================================================================== // Project: Ember Metal @@ -3213,8 +3032,7 @@ var o_create = Ember.create, [targetGuid]: { // variable name: `actionSet` [methodGuid]: { // variable name: `action` target: [Object object], - method: [Function function], - xform: [Function function] + method: [Function function] } } } @@ -3245,7 +3063,8 @@ function targetSetFor(obj, eventName) { var SKIP_PROPERTIES = { __ember_source__: true }; /** @private */ -function iterateSet(targetSet, callback, params) { +function iterateSet(obj, eventName, callback, params) { + var targetSet = targetSetFor(obj, eventName); if (!targetSet) { return false; } // Iterate through all elements of the target set for(var targetGuid in targetSet) { @@ -3259,7 +3078,7 @@ function iterateSet(targetSet, callback, params) { var action = actionSet[methodGuid]; if (action) { - if (callback(action, params) === true) { + if (callback(action, params, obj) === true) { return true; } } @@ -3270,37 +3089,25 @@ function iterateSet(targetSet, callback, params) { } /** @private */ -function invokeAction(action, params) { - var method = action.method, target = action.target, xform = action.xform; +function invokeAction(action, params, sender) { + var method = action.method, target = action.target; // If there is no target, the target is the object // on which the event was fired. - if (!target) { target = params[0]; } + if (!target) { target = sender; } if ('string' === typeof method) { method = target[method]; } - - // Listeners can provide an `xform` function, which can perform - // arbitrary transformations, such as changing the order of - // parameters. - // - // This is primarily used by ember-runtime's observer system, which - // provides a higher level abstraction on top of events, including - // dynamically looking up current values and passing them into the - // registered listener. - if (xform) { - xform(target, method, params); - } else { + if (params) { method.apply(target, params); + } else { + method.apply(target); } } /** - The parameters passed to an event listener are not exactly the - parameters passed to an observer. if you pass an xform function, it will - be invoked and is able to translate event listener parameters into the form - that observers are expecting. + The sendEvent arguments > 2 are passed to an event listener. @memberOf Ember */ -function addListener(obj, eventName, target, method, xform) { +function addListener(obj, eventName, target, method) { Ember.assert("You must pass at least an object and event name to Ember.addListener", !!obj && !!eventName); if (!method && 'function' === typeof target) { @@ -3312,9 +3119,7 @@ function addListener(obj, eventName, target, method, xform) { methodGuid = guidFor(method); if (!actionSet[methodGuid]) { - actionSet[methodGuid] = { target: target, method: method, xform: xform }; - } else { - actionSet[methodGuid].xform = xform; // used by observers etc to map params + actionSet[methodGuid] = { target: target, method: method }; } if ('function' === typeof obj.didAddListener) { @@ -3324,6 +3129,8 @@ function addListener(obj, eventName, target, method, xform) { /** @memberOf Ember */ function removeListener(obj, eventName, target, method) { + Ember.assert("You must pass at least an object and event name to Ember.removeListener", !!obj && !!eventName); + if (!method && 'function' === typeof target) { method = target; target = null; @@ -3336,7 +3143,7 @@ function removeListener(obj, eventName, target, method) { // re-expose the property from the prototype chain. if (actionSet && actionSet[methodGuid]) { actionSet[methodGuid] = null; } - if (obj && 'function'===typeof obj.didRemoveListener) { + if ('function' === typeof obj.didRemoveListener) { obj.didRemoveListener(eventName, target, method); } } @@ -3382,20 +3189,20 @@ function watchedEvents(obj) { } /** @memberOf Ember */ -function sendEvent(obj, eventName) { +function sendEvent(obj, eventName, params) { // first give object a chance to handle it if (obj !== Ember && 'function' === typeof obj.sendEvent) { - obj.sendEvent.apply(obj, a_slice.call(arguments, 1)); + obj.sendEvent(eventName, params); } - iterateSet(targetSetFor(obj, eventName), invokeAction, arguments); + iterateSet(obj, eventName, invokeAction, params); return true; } /** @memberOf Ember */ -function deferEvent(obj, eventName) { - var targetSet = targetSetFor(obj, eventName), actions = [], params = arguments; - iterateSet(targetSet, function (action) { +function deferEvent(obj, eventName, params) { + var actions = []; + iterateSet(obj, eventName, function (action) { actions.push(action); }); @@ -3403,19 +3210,18 @@ function deferEvent(obj, eventName) { if (obj.isDestroyed) { return; } if (obj !== Ember && 'function' === typeof obj.sendEvent) { - obj.sendEvent.apply(obj, a_slice.call(params, 1)); + obj.sendEvent(eventName, params); } for (var i=0, len=actions.length; i < len; ++i) { - invokeAction(actions[i], params); + invokeAction(actions[i], params, obj); } }; } /** @memberOf Ember */ function hasListeners(obj, eventName) { - var targetSet = targetSetFor(obj, eventName); - if (iterateSet(targetSet, function() { return true; })) { + if (iterateSet(obj, eventName, function() { return true; })) { return true; } @@ -3428,8 +3234,8 @@ function hasListeners(obj, eventName) { /** @memberOf Ember */ function listenersFor(obj, eventName) { - var targetSet = targetSetFor(obj, eventName), ret = []; - iterateSet(targetSet, function (action) { + var ret = []; + iterateSet(obj, eventName, function (action) { ret.push([action.target, action.method]); }); return ret; @@ -3642,7 +3448,7 @@ Ember.RunLoop = RunLoop; call. Ember.run(function(){ - // code to be execute within a RunLoop + // code to be execute within a RunLoop }); @name run @@ -3680,7 +3486,7 @@ var run = Ember.run; an lower-level way to use a RunLoop instead of using Ember.run(). Ember.run.begin(); - // code to be execute within a RunLoop + // code to be execute within a RunLoop Ember.run.end(); @@ -3696,7 +3502,7 @@ Ember.run.begin = function() { instead of using Ember.run(). Ember.run.begin(); - // code to be execute within a RunLoop + // code to be execute within a RunLoop Ember.run.end(); @returns {void} @@ -4195,19 +4001,17 @@ Binding.prototype = /** @scope Ember.Binding.prototype */ { connect: function(obj) { Ember.assert('Must pass a valid object to Ember.Binding.connect()', !!obj); - var twoWay = !this._oneWay; + var fromPath = this._from, toPath = this._to; + Ember.trySetPath(obj, toPath, getPathWithGlobals(obj, fromPath)); // add an observer on the object to be notified when the binding should be updated - Ember.addObserver(obj, this._from, this, this.fromDidChange); + Ember.addObserver(obj, fromPath, this, this.fromDidChange); // if the binding is a two-way binding, also set up an observer on the target - // object. - if (twoWay) { Ember.addObserver(obj, this._to, this, this.toDidChange); } - - // Don't schedule a sync if we're connecting an item on a prototype - if (Ember.meta(obj, false).proto !== obj) { this._scheduleSync(obj, 'fwd'); } + if (!this._oneWay) { Ember.addObserver(obj, toPath, this, this.toDidChange); } this._readyToSync = true; + return this; }, @@ -4291,10 +4095,10 @@ Binding.prototype = /** @scope Ember.Binding.prototype */ { Ember.Logger.log(' ', this.toString(), '->', fromValue, obj); } if (this._oneWay) { - Ember.trySetPath(Ember.isGlobalPath(toPath) ? window : obj, toPath, fromValue); + Ember.trySetPath(obj, toPath, fromValue); } else { Ember._suspendObserver(obj, toPath, this, this.toDidChange, function () { - Ember.trySetPath(Ember.isGlobalPath(toPath) ? window : obj, toPath, fromValue); + Ember.trySetPath(obj, toPath, fromValue); }); } // if we're synchronizing *to* the remote object @@ -4506,13 +4310,12 @@ var Mixin, REQUIRED, Alias, EMPTY_META = {}, // dummy for non-writable meta META_SKIP = { __emberproto__: true, __ember_count__: true }, o_create = Ember.create, + defineProperty = Ember.defineProperty, guidFor = Ember.guidFor; /** @private */ -function meta(obj, writable) { - var m = Ember.meta(obj, writable!==false), ret = m.mixins; - if (writable===false) return ret || EMPTY_META; - +function mixinsMeta(obj) { + var m = Ember.meta(obj, true), ret = m.mixins; if (!ret) { ret = m.mixins = { __emberproto__: obj }; } else if (ret.__emberproto__ !== obj) { @@ -4539,11 +4342,11 @@ function initMixin(mixin, args) { return mixin; } -var NATIVES = [Boolean, Object, Number, Array, Date, String]; /** @private */ function isMethod(obj) { - if ('function' !== typeof obj || obj.isMethod === false) { return false; } - return a_indexOf.call(NATIVES, obj) < 0; + return 'function' === typeof obj && + obj.isMethod !== false && + obj !== Boolean && obj !== Object && obj !== Number && obj !== Array && obj !== Date && obj !== String; } /** @private */ @@ -4587,7 +4390,7 @@ function mergeMixins(mixins, m, descs, values, base) { } else { // impl super if needed... if (isMethod(value)) { - ovalue = descs[key] === Ember.SIMPLE_PROPERTY && values[key]; + ovalue = descs[key] === undefined && values[key]; if (!ovalue) { ovalue = base[key]; } if ('function' !== typeof ovalue) { ovalue = null; } if (ovalue) { @@ -4601,7 +4404,7 @@ function mergeMixins(mixins, m, descs, values, base) { value = baseValue ? baseValue.concat(value) : Ember.makeArray(value); } - descs[key] = Ember.SIMPLE_PROPERTY; + descs[key] = undefined; values[key] = value; } } @@ -4628,21 +4431,10 @@ function writableReq(obj) { return req; } -/** @private */ -function getObserverPaths(value) { - return 'function' === typeof value && value.__ember_observes__; -} - -/** @private */ -function getBeforeObserverPaths(value) { - return 'function' === typeof value && value.__ember_observesBefore__; -} - var IS_BINDING = Ember.IS_BINDING = /^.+Binding$/; - /** @private */ -function detectBinding(obj, key, m) { +function detectBinding(obj, key, value, m) { if (IS_BINDING.test(key)) { var bindings = m.bindings; if (!bindings) { @@ -4651,50 +4443,55 @@ function detectBinding(obj, key, m) { bindings = m.bindings = o_create(m.bindings); bindings.__emberproto__ = obj; } - bindings[key] = true; + bindings[key] = value; } } /** @private */ function connectBindings(obj, m) { - var bindings = (m || Ember.meta(obj)).bindings, key, binding; + // TODO Mixin.apply(instance) should disconnect binding if exists + var bindings = m.bindings, key, binding, to; if (bindings) { for (key in bindings) { - binding = key !== '__emberproto__' && obj[key]; + binding = key !== '__emberproto__' && bindings[key]; if (binding) { + to = key.slice(0, -7); // strip Binding off end if (binding instanceof Ember.Binding) { binding = binding.copy(); // copy prototypes' instance - binding.to(key.slice(0, -7)); - } else { - binding = new Ember.Binding(key.slice(0, -7), binding); + binding.to(to); + } else { // binding is string path + binding = new Ember.Binding(to, binding); } binding.connect(obj); obj[key] = binding; } } + // mark as applied + m.bindings = { __emberproto__: obj }; } } +function finishPartial(obj, m) { + connectBindings(obj, m || Ember.meta(obj)); + return obj; +} + /** @private */ function applyMixin(obj, mixins, partial) { var descs = {}, values = {}, m = Ember.meta(obj), req = m.required, - key, willApply, didApply, value, desc; + key, value, desc, prevValue, paths, len, idx; // Go through all mixins and hashes passed in, and: // // * Handle concatenated properties // * Set up _super wrapping if necessary - // * Set up descriptors (simple, watched or computed properties) + // * Set up computed property descriptors // * Copying `toString` in broken browsers - mergeMixins(mixins, meta(obj), descs, values, obj); + mergeMixins(mixins, mixinsMeta(obj), descs, values, obj); - if (Ember.MixinDelegate.detect(obj)) { - willApply = values.willApplyProperty || obj.willApplyProperty; - didApply = values.didApplyProperty || obj.didApplyProperty; - } - - for(key in descs) { - if (!descs.hasOwnProperty(key)) { continue; } + for(key in values) { + if (key === 'contructor') { continue; } + if (!values.hasOwnProperty(key)) { continue; } desc = descs[key]; value = values[key]; @@ -4709,57 +4506,53 @@ function applyMixin(obj, mixins, partial) { req[key] = true; } } else { - while (desc instanceof Alias) { + while (desc && desc instanceof Alias) { var altKey = desc.methodName; - if (descs[altKey]) { + if (descs[altKey] || values[altKey]) { value = values[altKey]; desc = descs[altKey]; } else if (m.descs[altKey]) { desc = m.descs[altKey]; - value = desc.val(obj, altKey); + value = undefined; } else { + desc = undefined; value = obj[altKey]; - desc = Ember.SIMPLE_PROPERTY; } } - if (willApply) { willApply.call(obj, key); } + if (desc === undefined && value === undefined) { continue; } - var observerPaths = getObserverPaths(value), - curObserverPaths = observerPaths && getObserverPaths(obj[key]), - beforeObserverPaths = getBeforeObserverPaths(value), - curBeforeObserverPaths = beforeObserverPaths && getBeforeObserverPaths(obj[key]), - len, idx; + prevValue = obj[key]; - if (curObserverPaths) { - len = curObserverPaths.length; - for (idx=0; idx < len; idx++) { - Ember.removeObserver(obj, curObserverPaths[idx], null, key); + if ('function' === typeof prevValue) { + if ((paths = prevValue.__ember_observesBefore__)) { + len = paths.length; + for (idx=0; idx < len; idx++) { + Ember.removeBeforeObserver(obj, paths[idx], null, key); + } + } else if ((paths = prevValue.__ember_observes__)) { + len = paths.length; + for (idx=0; idx < len; idx++) { + Ember.removeObserver(obj, paths[idx], null, key); + } } } - if (curBeforeObserverPaths) { - len = curBeforeObserverPaths.length; - for (idx=0; idx < len; idx++) { - Ember.removeBeforeObserver(obj, curBeforeObserverPaths[idx], null, key); - } - } + detectBinding(obj, key, value, m); - detectBinding(obj, key, m); + defineProperty(obj, key, desc, value, m); - Ember.defineProperty(obj, key, desc, value); - - if (observerPaths) { - len = observerPaths.length; - for(idx=0; idx < len; idx++) { - Ember.addObserver(obj, observerPaths[idx], null, key); - } - } - - if (beforeObserverPaths) { - len = beforeObserverPaths.length; - for(idx=0; idx < len; idx++) { - Ember.addBeforeObserver(obj, beforeObserverPaths[idx], null, key); + if ('function' === typeof value) { + if (paths = value.__ember_observesBefore__) { + len = paths.length; + for (idx=0; idx < len; idx++) { + Ember.addBeforeObserver(obj, paths[idx], null, key); + } + } else if (paths = value.__ember_observes__) { + len = paths.length; + for (idx=0; idx < len; idx++) { + Ember.addObserver(obj, paths[idx], null, key); + } } } @@ -4768,13 +4561,11 @@ function applyMixin(obj, mixins, partial) { req.__ember_count__--; req[key] = false; } - - if (didApply) { didApply.call(obj, key); } } } if (!partial) { // don't apply to prototype - value = connectBindings(obj, m); + finishPartial(obj, m); } // Make sure no required attrs remain @@ -4792,7 +4583,8 @@ function applyMixin(obj, mixins, partial) { Ember.mixin = function(obj) { var args = a_slice.call(arguments, 1); - return applyMixin(obj, args, false); + applyMixin(obj, args, false); + return obj; }; /** @@ -4834,10 +4626,7 @@ Mixin.applyPartial = function(obj) { return applyMixin(obj, args, true); }; -Mixin.finishPartial = function(obj) { - connectBindings(obj); - return obj; -}; +Mixin.finishPartial = finishPartial; Mixin.create = function() { classToString.processed = false; @@ -4877,19 +4666,12 @@ MixinPrototype.reopen = function() { return this; }; -var TMP_ARRAY = []; MixinPrototype.apply = function(obj) { - TMP_ARRAY[0] = this; - var ret = applyMixin(obj, TMP_ARRAY, false); - TMP_ARRAY.length=0; - return ret; + return applyMixin(obj, [this], false); }; MixinPrototype.applyPartial = function(obj) { - TMP_ARRAY[0] = this; - var ret = applyMixin(obj, TMP_ARRAY, true); - TMP_ARRAY.length=0; - return ret; + return applyMixin(obj, [this], true); }; /** @private */ @@ -4910,7 +4692,11 @@ function _detect(curMixin, targetMixin, seen) { MixinPrototype.detect = function(obj) { if (!obj) { return false; } if (obj instanceof Mixin) { return _detect(obj, this, {}); } - return !!meta(obj, false)[guidFor(this)]; + var mixins = Ember.meta(obj, false).mixins; + if (mixins) { + return !!mixins[guidFor(this)]; + } + return false; }; MixinPrototype.without = function() { @@ -5048,13 +4834,15 @@ MixinPrototype.toString = classToString; // returns the mixins currently applied to the specified object // TODO: Make Ember.mixin Mixin.mixins = function(obj) { - var ret = [], mixins = meta(obj, false), key, mixin; - for(key in mixins) { - if (META_SKIP[key]) { continue; } - mixin = mixins[key]; + var ret = [], mixins = Ember.meta(obj, false).mixins, key, mixin; + if (mixins) { + for(key in mixins) { + if (META_SKIP[key]) { continue; } + mixin = mixins[key]; - // skip primitive mixins since these are always anonymous - if (!mixin.properties) { ret.push(mixins[key]); } + // skip primitive mixins since these are always anonymous + if (!mixin.properties) { ret.push(mixins[key]); } + } } return ret; }; @@ -5076,11 +4864,6 @@ Ember.alias = function(methodName) { return new Alias(methodName); }; -Ember.MixinDelegate = Mixin.create({ - willApplyProperty: Ember.required(), - didApplyProperty: Ember.required() -}); - // .......................................................... // OBSERVER HELPER // @@ -5359,6 +5142,13 @@ Ember.compare = function compare(v, w) { } return 0; + case 'date': + var vNum = v.getTime(); + var wNum = w.getTime(); + if (vNum < wNum) { return -1; } + if (vNum > wNum) { return 1; } + return 0; + default: return 0; } @@ -5444,7 +5234,7 @@ Ember.inspect = function(obj) { /** 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 + internal objects. For any other object that implements `isEqual()` it will respect that method. Ember.isEqual('hello', 'hello'); => true @@ -5474,7 +5264,8 @@ Ember.ORDER_DEFINITION = Ember.ENV.ORDER_DEFINITION || [ 'object', 'instance', 'function', - 'class' + 'class', + 'date' ]; /** @@ -5626,7 +5417,7 @@ Ember.String = { > beta > gamma - @param {String} str + @param {String} str The string to split @returns {String} split string @@ -5635,7 +5426,7 @@ Ember.String = { /** Converts a camelized string into all lower case separated by underscores. - + 'innerHTML'.decamelize() => 'inner_html' 'action_name'.decamelize() => 'action_name' 'css-class-name'.decamelize() => 'css-class-name' @@ -5652,7 +5443,7 @@ Ember.String = { /** Replaces underscores or spaces with dashes. - + 'innerHTML'.dasherize() => 'inner-html' 'action_name'.dasherize() => 'action-name' 'css-class-name'.dasherize() => 'css-class-name' @@ -5819,7 +5610,7 @@ if (Ember.EXTEND_PROTOTYPES) { /** The `property` extension of Javascript's Function prototype is available - when Ember.EXTEND_PROTOTYPES is true, which is the default. + when Ember.EXTEND_PROTOTYPES is true, which is the default. Computed properties allow you to treat a function like a property: @@ -5874,7 +5665,7 @@ if (Ember.EXTEND_PROTOTYPES) { /** The `observes` extension of Javascript's Function prototype is available - when Ember.EXTEND_PROTOTYPES is true, which is the default. + when Ember.EXTEND_PROTOTYPES is true, which is the default. You can observe property changes simply by adding the `observes` call to the end of your method declarations in classes that you write. @@ -5885,7 +5676,7 @@ if (Ember.EXTEND_PROTOTYPES) { // Executes whenever the "value" property changes }.observes('value') }); - + @see Ember.Observable */ Function.prototype.observes = function() { @@ -5895,7 +5686,7 @@ if (Ember.EXTEND_PROTOTYPES) { /** The `observesBefore` extension of Javascript's Function prototype is - available when Ember.EXTEND_PROTOTYPES is true, which is the default. + available when Ember.EXTEND_PROTOTYPES is true, which is the default. You can get notified when a property changes is about to happen by by adding the `observesBefore` call to the end of your method @@ -5906,7 +5697,7 @@ if (Ember.EXTEND_PROTOTYPES) { // Executes whenever the "value" property is about to change }.observesBefore('value') }); - + @see Ember.Observable */ Function.prototype.observesBefore = function() { @@ -5974,11 +5765,6 @@ function iter(key, value) { return i ; } -/** @private */ -function xform(target, method, params) { - method.call(target, params[0], params[2], params[3]); -} - /** @class @@ -6505,9 +6291,9 @@ Ember.Enumerable = Ember.Mixin.create( /** Returns a copy of the array with all null elements removed. - + var arr = ["a", null, "c", null]; - arr.compact(); => ["a", "c"] + arr.compact(); => ["a", "c"] @returns {Array} the array without null elements. */ @@ -6578,8 +6364,8 @@ Ember.Enumerable = Ember.Mixin.create( var hasObservers = get(this, 'hasEnumerableObservers'); if (!hasObservers) Ember.propertyWillChange(this, 'hasEnumerableObservers'); - Ember.addListener(this, '@enumerable:before', target, willChange, xform); - Ember.addListener(this, '@enumerable:change', target, didChange, xform); + Ember.addListener(this, '@enumerable:before', target, willChange); + Ember.addListener(this, '@enumerable:change', target, didChange); if (!hasObservers) Ember.propertyDidChange(this, 'hasEnumerableObservers'); return this; }, @@ -6644,7 +6430,7 @@ Ember.Enumerable = Ember.Mixin.create( Ember.propertyWillChange(this, '[]'); if (hasDelta) Ember.propertyWillChange(this, 'length'); - Ember.sendEvent(this, '@enumerable:before', removing, adding); + Ember.sendEvent(this, '@enumerable:before', [this, removing, adding]); return this; }, @@ -6686,7 +6472,7 @@ Ember.Enumerable = Ember.Mixin.create( if (removing === -1) removing = null; if (adding === -1) adding = null; - Ember.sendEvent(this, '@enumerable:change', removing, adding); + Ember.sendEvent(this, '@enumerable:change', [this, removing, adding]); if (hasDelta) Ember.propertyDidChange(this, 'length'); Ember.propertyDidChange(this, '[]'); @@ -6717,11 +6503,6 @@ var get = Ember.get, set = Ember.set, meta = Ember.meta, map = Ember.EnumerableU /** @private */ function none(obj) { return obj===null || obj===undefined; } -/** @private */ -function xform(target, method, params) { - method.call(target, params[0], params[2], params[3], params[4]); -} - // .......................................................... // ARRAY // @@ -6964,8 +6745,8 @@ Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.protot var hasObservers = get(this, 'hasArrayObservers'); if (!hasObservers) Ember.propertyWillChange(this, 'hasArrayObservers'); - Ember.addListener(this, '@array:before', target, willChange, xform); - Ember.addListener(this, '@array:change', target, didChange, xform); + Ember.addListener(this, '@array:before', target, willChange); + Ember.addListener(this, '@array:change', target, didChange); if (!hasObservers) Ember.propertyDidChange(this, 'hasArrayObservers'); return this; }, @@ -6986,8 +6767,8 @@ Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.protot var hasObservers = get(this, 'hasArrayObservers'); if (hasObservers) Ember.propertyWillChange(this, 'hasArrayObservers'); - Ember.removeListener(this, '@array:before', target, willChange, xform); - Ember.removeListener(this, '@array:change', target, didChange, xform); + Ember.removeListener(this, '@array:before', target, willChange); + Ember.removeListener(this, '@array:change', target, didChange); if (hasObservers) Ember.propertyDidChange(this, 'hasArrayObservers'); return this; }, @@ -7033,7 +6814,7 @@ Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.protot // Make sure the @each proxy is set up if anyone is observing @each if (Ember.isWatching(this, '@each')) { get(this, '@each'); } - Ember.sendEvent(this, '@array:before', startIdx, removeAmt, addAmt); + Ember.sendEvent(this, '@array:before', [this, startIdx, removeAmt, addAmt]); var removing, lim; if (startIdx>=0 && removeAmt>=0 && get(this, 'hasEnumerableObservers')) { @@ -7070,7 +6851,7 @@ Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.protot } this.enumerableContentDidChange(removeAmt, adding); - Ember.sendEvent(this, '@array:change', startIdx, removeAmt, addAmt); + Ember.sendEvent(this, '@array:change', [this, startIdx, removeAmt, addAmt]); var length = get(this, 'length'), cachedFirst = cacheFor(this, 'firstObject'), @@ -7510,7 +7291,7 @@ Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable, colors.clear(); => [] colors.length(); => 0 - @returns {Ember.Array} An empty Array. + @returns {Ember.Array} An empty Array. */ clear: function () { var len = get(this, 'length'); @@ -7664,6 +7445,20 @@ Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable, return this; }, + /** + Reverse objects in the array. Works just like reverse() but it is + KVO-compliant. + + @return {Ember.Array} receiver + */ + reverseObjects: function() { + var len = get(this, 'length'); + if (len === 0) return this; + var objects = this.toArray().reverse(); + this.replace(0, len, objects); + return this; + }, + // .......................................................... // IMPLEMENT Ember.MutableEnumerable // @@ -7698,21 +7493,21 @@ Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable, // License: Licensed under MIT license (see license.js) // ========================================================================== -var get = Ember.get, set = Ember.set; +var get = Ember.get, set = Ember.set, defineProperty = Ember.defineProperty; /** @class ## Overview - + This mixin provides properties and property observing functionality, core features of the Ember object model. - + Properties and observers allow one object to observe changes to a property on another object. This is one of the fundamental ways that models, controllers and views communicate with each other in an Ember application. - + Any object that has this mixin applied can be used in observer operations. That includes Ember.Object and most objects you will interact with as you write your Ember application. @@ -7720,16 +7515,16 @@ var get = Ember.get, set = Ember.set; Note that you will not generally apply this mixin to classes yourself, but you will use the features provided by this module frequently, so it is important to understand how to use it. - + ## Using get() and set() - + Because of Ember's support for bindings and observers, you will always access properties using the get method, and set properties using the set method. This allows the observing objects to be notified and computed properties to be handled properly. - + More documentation about `get` and `set` are below. - + ## Observing Property Changes You typically observe property changes simply by adding the `observes` @@ -7741,7 +7536,7 @@ var get = Ember.get, set = Ember.set; // Executes whenever the "value" property changes }.observes('value') }); - + Although this is the most common way to add an observer, this capability is actually built into the Ember.Object class on top of two methods defined in this mixin: `addObserver` and `removeObserver`. You can use @@ -7754,12 +7549,12 @@ var get = Ember.get, set = Ember.set; This will call the `targetAction` method on the `targetObject` to be called whenever the value of the `propertyKey` changes. - - Note that if `propertyKey` is a computed property, the observer will be - called when any of the property dependencies are changed, even if the + + Note that if `propertyKey` is a computed property, the observer will be + called when any of the property dependencies are changed, even if the resulting value of the computed property is unchanged. This is necessary because computed properties are not computed until `get` is called. - + @extends Ember.Mixin */ Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ { @@ -7773,7 +7568,7 @@ Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ { This method is usually similar to using object[keyName] or object.keyName, however it supports both computed properties and the unknownProperty handler. - + Because `get` unifies the syntax for accessing all these kinds of properties, it can make many refactorings easier, such as replacing a simple property with a computed property, or vice versa. @@ -7969,11 +7764,11 @@ Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ { Ember.propertyDidChange(this, keyName); return this; }, - + /** Convenience method to call `propertyWillChange` and `propertyDidChange` in succession. - + @param {String} keyName The property key to be notified about. @returns {Ember.Observable} */ @@ -8065,7 +7860,7 @@ Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ { This method will be called when a client attempts to get the value of a property that has not been defined in one of the typical ways. Override this method to create "virtual" properties. - + @param {String} key The name of the unknown property that was requested. @returns {Object} The property value or undefined. Default is undefined. */ @@ -8077,18 +7872,19 @@ Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ { This method will be called when a client attempts to set the value of a property that has not been defined in one of the typical ways. Override this method to create "virtual" properties. - + @param {String} key The name of the unknown property to be set. @param {Object} value The value the unknown property is to be set to. */ setUnknownProperty: function(key, value) { - this[key] = value; + defineProperty(this, key); + set(this, key, value); }, /** This is like `get`, but allows you to pass in a dot-separated property path. - + person.getPath('address.zip'); // return the zip person.getPath('children.firstObject.age'); // return the first kid's age @@ -8104,7 +7900,7 @@ Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ { /** This is like `set`, but allows you to specify the property you want to set as a dot-separated property path. - + person.setPath('address.zip', 10011); // set the zip to 10011 person.setPath('children.firstObject.age', 6); // set the first kid's age to 6 @@ -8122,9 +7918,9 @@ Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ { /** Retrieves the value of a property, or a default value in the case that the property returns undefined. - + person.getWithDefault('lastName', 'Doe'); - + @param {String} keyName The name of the property to retrieve @param {Object} defaultValue The value to return if the property value is undefined @returns {Object} The property value or the defaultValue. @@ -8135,10 +7931,10 @@ Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ { /** Set the value of a property to the current value plus some amount. - + person.incrementProperty('age'); team.incrementProperty('score', 2); - + @param {String} keyName The name of the property to increment @param {Object} increment The amount to increment by. Defaults to 1 @returns {Object} The new property value @@ -8148,13 +7944,13 @@ Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ { set(this, keyName, (get(this, keyName) || 0)+increment); return get(this, keyName); }, - + /** Set the value of a property to the current value minus some amount. - + player.decrementProperty('lives'); orc.decrementProperty('health', 5); - + @param {String} keyName The name of the property to decrement @param {Object} increment The amount to decrement by. Defaults to 1 @returns {Object} The new property value @@ -8168,9 +7964,9 @@ Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ { /** Set the value of a boolean property to the opposite of it's current value. - + starship.toggleProperty('warpDriveEnaged'); - + @param {String} keyName The name of the property to toggle @returns {Object} The new property value */ @@ -8253,14 +8049,6 @@ Ember.TargetActionSupport = Ember.Mixin.create({ (function() { -var get = Ember.get, set = Ember.set, a_slice = Array.prototype.slice; - -/** @private */ -function xform(target, method, params) { - var args = a_slice.call(params, 2); - method.apply(target, args); -} - /** @class @@ -8269,12 +8057,7 @@ function xform(target, method, params) { Ember.Evented = Ember.Mixin.create( /** @scope Ember.Evented.prototype */ { on: function(name, target, method) { - if (!method) { - method = target; - target = null; - } - - Ember.addListener(this, name, target, method, xform); + Ember.addListener(this, name, target, method); }, one: function(name, target, method) { @@ -8283,8 +8066,11 @@ Ember.Evented = Ember.Mixin.create( target = null; } + var self = this; var wrapped = function() { - Ember.removeListener(this, name, target, wrapped); + Ember.removeListener(self, name, target, wrapped); + + if ('string' === typeof method) { method = this[method]; } // Internally, a `null` target means that the target is // the first parameter to addListener. That means that @@ -8297,7 +8083,11 @@ Ember.Evented = Ember.Mixin.create( }, trigger: function(name) { - Ember.sendEvent.apply(null, [this, name].concat(a_slice.call(arguments, 1))); + var args = [], i, l; + for (i = 1, l = arguments.length; i < l; i++) { + args.push(arguments[i]); + } + Ember.sendEvent(this, name, args); }, fire: function(name) { @@ -8343,13 +8133,15 @@ Ember.Evented = Ember.Mixin.create( -var rewatch = Ember.rewatch; var classToString = Ember.Mixin.prototype.toString; var set = Ember.set, get = Ember.get; var o_create = Ember.create, o_defineProperty = Ember.platform.defineProperty, a_slice = Array.prototype.slice, - meta = Ember.meta; + meta = Ember.meta, + rewatch = Ember.rewatch, + finishChains = Ember.finishChains, + finishPartial = Ember.Mixin.finishPartial; var undefinedDescriptor = { configurable: true, @@ -8365,27 +8157,24 @@ function makeCtor() { // method a lot faster. This is glue code so we want it to be as fast as // possible. - var wasApplied = false, initMixins, init = false, hasChains = false; + var wasApplied = false, initMixins; var Class = function() { - if (!wasApplied) { Class.proto(); } // prepare prototype... + if (!wasApplied) { + Class.proto(); // prepare prototype... + } + var m = Ember.meta(this); + m.proto = this; if (initMixins) { this.reopen.apply(this, initMixins); initMixins = null; - rewatch(this); // always rewatch just in case - Ember.Mixin.finishPartial(this); - this.init.apply(this, arguments); - } else { - if (hasChains) { - rewatch(this); - } else { - o_defineProperty(this, Ember.GUID_KEY, undefinedDescriptor); - } - if (init===false) { init = this.init; } // cache for later instantiations - o_defineProperty(this, '_super', undefinedDescriptor); - Ember.Mixin.finishPartial(this); - init.apply(this, arguments); } + o_defineProperty(this, Ember.GUID_KEY, undefinedDescriptor); + o_defineProperty(this, '_super', undefinedDescriptor); + finishPartial(this, m); + delete m.proto; + finishChains(this); + this.init.apply(this, arguments); }; Class.toString = classToString; @@ -8405,8 +8194,7 @@ function makeCtor() { if (!wasApplied) { wasApplied = true; Class.PrototypeMixin.applyPartial(Class.prototype); - Ember.rewatch(Class.prototype); // setup watch chains if needed. - hasChains = !!meta(Class.prototype, false).chains; // avoid rewatch + rewatch(Class.prototype); } return this.prototype; @@ -9381,81 +9169,26 @@ Ember.ArrayProxy = Ember.Object.extend(Ember.MutableArray, (function() { var get = Ember.get, set = Ember.set, - defineProperty = Ember.defineProperty, + fmt = Ember.String.fmt, addBeforeObserver = Ember.addBeforeObserver, addObserver = Ember.addObserver, removeBeforeObserver = Ember.removeBeforeObserver, removeObserver = Ember.removeObserver, - suspendBeforeObserver = Ember._suspendBeforeObserver, - suspendObserver = Ember._suspendObserver, propertyWillChange = Ember.propertyWillChange, - propertyDidChange = Ember.propertyDidChange, - getMeta = Ember.getMeta, - delegateDesc; + propertyDidChange = Ember.propertyDidChange; -function addDelegateObservers(proxy, key) { - var delegateKey = 'content.' + key, - willChangeKey = key + 'WillChange', - didChangeKey = key + 'DidChange'; - proxy[willChangeKey] = function () { - propertyWillChange(this, key); - }; - proxy[didChangeKey] = function () { - propertyDidChange(this, key); - }; - // have to use target=null method=string so if - // willWatchProperty is call with prototype it will still work - addBeforeObserver(proxy, delegateKey, null, willChangeKey); - addObserver(proxy, delegateKey, null, didChangeKey); +function contentPropertyWillChange(content, contentKey) { + var key = contentKey.slice(8); // remove "content." + if (key in this) { return; } // if shadowed in proxy + propertyWillChange(this, key); } -function removeDelegateObservers(proxy, key) { - var delegateKey = 'content.' + key, - willChangeKey = key + 'WillChange', - didChangeKey = key + 'DidChange'; - removeBeforeObserver(proxy, delegateKey, null, willChangeKey); - removeObserver(proxy, delegateKey, null, didChangeKey); - delete proxy[willChangeKey]; - delete proxy[didChangeKey]; +function contentPropertyDidChange(content, contentKey) { + var key = contentKey.slice(8); // remove "content." + if (key in this) { return; } // if shadowed in proxy + propertyDidChange(this, key); } -function suspendDelegateObservers(proxy, key, fn) { - var delegateKey = 'content.' + key, - willChangeKey = key + 'WillChange', - didChangeKey = key + 'DidChange'; - suspendBeforeObserver(proxy, delegateKey, null, willChangeKey, function () { - suspendObserver(proxy, delegateKey, null, didChangeKey, function () { - fn.call(proxy); - }); - }); -} - -function isDelegateDesc(proxy, key) { - var descs = getMeta(proxy, 'descs'); - return descs[key] === delegateDesc; -} - -function undefineProperty(proxy, key) { - var descs = getMeta(proxy, 'descs'); - descs[key].teardown(proxy, key); - delete descs[key]; - delete proxy[key]; -} - -function delegate(key, value) { - if (arguments.length === 1) { - return this.delegateGet(key); - } else { - // CP set notifies, so if we don't suspend - // will be notified again - suspendDelegateObservers(this, key, function () { - this.delegateSet(key, value); - }); - } -} - -delegateDesc = Ember.computed(delegate).volatile(); - /** @class @@ -9519,45 +9252,33 @@ Ember.ObjectProxy = Ember.Object.extend( @default null */ content: null, - /** @private */ _contentDidChange: Ember.observer(function() { Ember.assert("Can't set ObjectProxy's content to itself", this.get('content') !== this); }, 'content'), /** @private */ - delegateGet: function (key) { + willWatchProperty: function (key) { + var contentKey = 'content.' + key; + addBeforeObserver(this, contentKey, null, contentPropertyWillChange); + addObserver(this, contentKey, null, contentPropertyDidChange); + }, + /** @private */ + didUnwatchProperty: function (key) { + var contentKey = 'content.' + key; + removeBeforeObserver(this, contentKey, null, contentPropertyWillChange); + removeObserver(this, contentKey, null, contentPropertyDidChange); + }, + /** @private */ + unknownProperty: function (key) { var content = get(this, 'content'); if (content) { return get(content, key); } }, /** @private */ - delegateSet: function (key, value) { - var content = get(this, 'content'); - if (!content) { - throw new Error('Unable to delegate set without content for property: ' + key); - } - return set(content, key, value); - }, - /** @private */ - willWatchProperty: function (key) { - if (key in this) return; - defineProperty(this, key, delegateDesc); - addDelegateObservers(this, key); - }, - /** @private */ - didUnwatchProperty: function (key) { - if (isDelegateDesc(this, key)) { - removeDelegateObservers(this, key); - undefineProperty(this, key); - } - }, - /** @private */ - unknownProperty: function (key) { - return this.delegateGet(key); - }, - /** @private */ setUnknownProperty: function (key, value) { - this.delegateSet(key, value); + var content = get(this, 'content'); + Ember.assert(fmt("Cannot delegate set('%@', %@) to the 'content' property of object proxy %@: its 'content' is undefined.", [key, value, this]), content); + return set(content, key, value); } }); @@ -9667,7 +9388,7 @@ Ember.EachProxy = Ember.Object.extend({ unknownProperty: function(keyName, value) { var ret; ret = new EachArray(this._content, keyName, this); - new Ember.Descriptor().setup(this, keyName, ret); + Ember.defineProperty(this, keyName, null, ret); this.beginObservingContentKey(keyName); return ret; }, @@ -10087,7 +9808,7 @@ Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, sortAscendingDidChange: Ember.observer(function() { if (get(this, 'sortAscending') !== this._lastSortAscending) { var arrangedContent = get(this, 'arrangedContent'); - arrangedContent.reverse(); + arrangedContent.reverseObjects(); } }, 'sortAscending'), @@ -10125,7 +9846,6 @@ Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, forEach(sortProperties, function(sortProperty) { Ember.addObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange'); }, this); - this.arrayContentDidChange(idx, 0, 1); }, this); } @@ -10412,14 +10132,14 @@ Ember.Application = Ember.Namespace.extend( rootElement = get(this, 'rootElement'), applicationController = get(router, 'applicationController'); - if (this.ApplicationView && applicationController) { - var applicationView = this.ApplicationView.create({ - controller: applicationController - }); - this._createdApplicationView = applicationView; + Ember.assert("ApplicationView and ApplicationController must be defined on your application", (this.ApplicationView && applicationController) ); - applicationView.appendTo(rootElement); - } + var applicationView = this.ApplicationView.create({ + controller: applicationController + }); + this._createdApplicationView = applicationView; + + applicationView.appendTo(rootElement); router.route(location.getURL()); location.onUpdateURL(function(url) { @@ -10631,6 +10351,11 @@ Ember.HistoryLocation = Ember.Object.extend({ set(this, '_initialURL', get(this, 'location').pathname); }, + /** + Will be pre-pended to path upon state change + */ + rootURL: '/', + /** @private @@ -10656,7 +10381,7 @@ Ember.HistoryLocation = Ember.Object.extend({ var state = window.history.state, initialURL = get(this, '_initialURL'); - if (path === "") { path = '/'; } + path = this.formatPath(path); if ((initialURL && initialURL !== path) || (state && state.path !== path)) { set(this, '_initialURL', null); @@ -10678,6 +10403,21 @@ Ember.HistoryLocation = Ember.Object.extend({ }); }, + /** + @private + + returns the given path appended to rootURL + */ + formatPath: function(path) { + var rootURL = get(this, 'rootURL'); + + if (path !== '') { + rootURL = rootURL.replace(/\/$/, ''); + } + + return rootURL + path; + }, + /** @private @@ -10757,13 +10497,34 @@ Ember.Location.registerImplementation('none', Ember.NoneLocation); // License: Licensed under MIT license (see license.js) // ========================================================================== -Ember.assert("Ember Views require jQuery 1.6 or 1.7", window.jQuery && (window.jQuery().jquery.match(/^1\.[67](\.\d+)?(pre|rc\d?)?/) || Ember.ENV.FORCE_JQUERY)); +Ember.assert("Ember Views require jQuery 1.7", window.jQuery && (window.jQuery().jquery.match(/^1\.7(\.\d+)?(pre|rc\d?)?/) || Ember.ENV.FORCE_JQUERY)); Ember.$ = window.jQuery; })(); +(function() { +// ========================================================================== +// Project: Ember - JavaScript Application Framework +// Copyright: ©2006-2011 Strobe Inc. and contributors. +// Portions ©2008-2011 Apple Inc. All rights reserved. +// License: Licensed under MIT license (see license.js) +// ========================================================================== + +// http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#dndevents +var dragEvents = Ember.String.w('dragstart drag dragenter dragleave dragover drop dragend'); + +// Copies the `dataTransfer` property from a browser event object onto the +// jQuery event object for the specified events +Ember.EnumerableUtils.forEach(dragEvents, function(eventName) { + Ember.$.event.fixHooks[eventName] = { props: ['dataTransfer'] }; +}); + +})(); + + + (function() { // ========================================================================== // Project: Ember - JavaScript Application Framework @@ -11323,7 +11084,6 @@ Ember.EventDispatcher = Ember.Object.extend( handler = action.handler; if (action.eventName === eventName) { - evt.preventDefault(); return handler(evt); } }); @@ -11460,7 +11220,12 @@ Ember.ControllerMixin.reopen({ context: App.Post.find() }); - @param {Class} name a view class to instantiate + You can write this as: + + applicationController.connectOutlet('master', 'posts', App.Post.find()); + + @param {String} outletName a name for the outlet to set + @param {String} name a view/controller pair name @param {Object} context a context object to assign to the controller's `content` property, if a controller can be found (optional) @@ -11470,6 +11235,8 @@ Ember.ControllerMixin.reopen({ // // name // name, context + // outletName, name + // outletName, name, context // options // // The options hash has the following keys: @@ -11488,6 +11255,12 @@ Ember.ControllerMixin.reopen({ var outletName, viewClass, view, controller, options; + if (Ember.typeOf(context) === 'string') { + outletName = name; + name = context; + context = arguments[2]; + } + if (arguments.length === 1) { if (Ember.typeOf(name) === 'object') { options = name; @@ -11556,7 +11329,7 @@ var a_slice = [].slice; var a_forEach = Ember.EnumerableUtils.forEach; var childViewsProperty = Ember.computed(function() { - var childViews = get(this, '_childViews'); + var childViews = this._childViews; var ret = Ember.A(); @@ -11599,7 +11372,7 @@ var invokeForState = { `Ember.View` is the class in Ember responsible for encapsulating templates of HTML content, combining templates with data to render as sections of a page's DOM, and registering and responding to user-initiated events. - + ## HTML Tag The default HTML tag name used for a view's DOM representation is `div`. This can be customized by setting the `tagName` property. The following view class: @@ -11625,7 +11398,7 @@ var invokeForState = {
`class` attribute values can also be set by providing a `classNameBindings` property - set to an array of properties names for the view. The return value of these properties + set to an array of properties names for the view. The return value of these properties will be added as part of the value for the view's `class` attribute. These properties can be computed properties: @@ -11654,7 +11427,7 @@ var invokeForState = {
- When using boolean class name bindings you can supply a string value other than the + When using boolean class name bindings you can supply a string value other than the property name for use as the `class` HTML attribute by appending the preferred value after a ":" character when defining the binding: @@ -11695,11 +11468,11 @@ var invokeForState = {
- Updates to the the value of a class name binding will result in automatic update + Updates to the the value of a class name binding will result in automatic update of the HTML `class` attribute in the view's rendered HTML representation. If the value becomes `false` or `undefined` the class name will be removed. - Both `classNames` and `classNameBindings` are concatenated properties. + Both `classNames` and `classNameBindings` are concatenated properties. See `Ember.Object` documentation for more information about concatenated properties. ## HTML Attributes @@ -11745,7 +11518,7 @@ var invokeForState = { }.property() }) - Updates to the the property of an attribute binding will result in automatic update + Updates to the the property of an attribute binding will result in automatic update of the HTML attribute in the view's rendered HTML representation. `attributeBindings` is a concatenated property. See `Ember.Object` documentation @@ -11836,7 +11609,7 @@ var invokeForState = { primary templates, layouts can be any function that accepts an optional context parameter and returns a string of HTML that will be inserted inside view's tag. Views whose HTML element is self closing (e.g. ``) cannot have a layout and this property will be ignored. - + Most typically in Ember a layout will be a compiled Ember.Handlebars template. A view's layout can be set directly with the `layout` property or reference an @@ -11861,7 +11634,7 @@ var invokeForState = { See `Handlebars.helpers.yield` for more information. ## Responding to Browser Events - Views can respond to user-initiated events in one of three ways: method implementation, + Views can respond to user-initiated events in one of three ways: method implementation, through an event manager, and through `{{action}}` helper use in their template or layout. ### Method Implementation @@ -11878,8 +11651,8 @@ var invokeForState = { ### Event Managers Views can define an object as their `eventManager` property. This object can then implement methods that match the desired event names. Matching events that occur - on the view's rendered HTML or the rendered HTML of any of its DOM descendants - will trigger this method. A `jQuery.Event` object will be passed as the first + on the view's rendered HTML or the rendered HTML of any of its DOM descendants + will trigger this method. A `jQuery.Event` object will be passed as the first argument to the method and an `Ember.View` object as the second. The `Ember.View` will be the view whose rendered HTML was interacted with. This may be the view with the `eventManager` property or one of its descendent views. @@ -11913,7 +11686,7 @@ var invokeForState = { Similarly a view's event manager will take precedence for events of any views rendered as a descendent. A method name that matches an event name will not be called - if the view instance was rendered inside the HTML representation of a view that has + if the view instance was rendered inside the HTML representation of a view that has an `eventManager` property defined that handles events of the name. Events not handled by the event manager will still trigger method calls on the descendent. @@ -11935,7 +11708,7 @@ var invokeForState = { // eventManager doesn't handle click events }, mouseEnter: function(event){ - // will never be called if rendered inside + // will never be called if rendered inside // an OuterView. } }) @@ -11956,7 +11729,7 @@ var invokeForState = { Form events: 'submit', 'change', 'focusIn', 'focusOut', 'input' HTML5 drag and drop events: 'dragStart', 'drag', 'dragEnter', 'dragLeave', 'drop', 'dragEnd' - + ## Handlebars `{{view}}` Helper Other `Ember.View` instances can be included as part of a view's template by using the `{{view}}` Handlebars helper. See `Handlebars.helpers.view` for additional information. @@ -12112,9 +11885,19 @@ Ember.View = Ember.Object.extend(Ember.Evented, Private copy of the view's template context. This can be set directly by Handlebars without triggering the observer that causes the view to be re-rendered. + + The context of a view is looked up as follows: + + 1. Specified controller + 2. Supplied context (usually by Handlebars) + 3. `parentView`'s context (for a child of a ContainerView) + + The code in Handlebars that overrides the `_context` property first + checks to see whether the view has a specified controller. This is + something of a hack and should be revisited. */ _context: Ember.computed(function(key, value) { - var parentView, controller; + var parentView, controller, context; if (arguments.length === 2) { return value; @@ -12319,8 +12102,8 @@ Ember.View = Ember.Object.extend(Ember.Evented, var templateData = get(this, 'templateData'); var keywords = templateData ? Ember.copy(templateData.keywords) : {}; - keywords.view = get(this, 'concreteView'); - keywords.controller = get(this, 'controller'); + set(keywords, 'view', get(this, 'concreteView')); + set(keywords, 'controller', get(this, 'controller')); return keywords; }, @@ -12431,7 +12214,7 @@ Ember.View = Ember.Object.extend(Ember.Evented, // we re-render. // VIEW-TODO: Unit test this path. - var childViews = get(this, '_childViews'); + var childViews = this._childViews; for (var i=lengthAfter-1; i>=lengthBefore; i--) { if (childViews[i]) { childViews[i].destroy(); } } @@ -12624,8 +12407,8 @@ Ember.View = Ember.Object.extend(Ember.Evented, /** @private */ mutateChildViews: function(callback) { - var childViews = get(this, '_childViews'), - idx = get(childViews, 'length'), + var childViews = this._childViews, + idx = childViews.length, view; while(--idx >= 0) { @@ -12638,11 +12421,11 @@ Ember.View = Ember.Object.extend(Ember.Evented, /** @private */ forEachChildView: function(callback) { - var childViews = get(this, '_childViews'); + var childViews = this._childViews; if (!childViews) { return this; } - var len = get(childViews, 'length'), + var len = childViews.length, view, idx; for(idx = 0; idx < len; idx++) { @@ -13031,13 +12814,13 @@ Ember.View = Ember.Object.extend(Ember.Evented, this.buffer = buffer; this.transitionTo('inBuffer', false); - this.lengthBeforeRender = get(get(this, '_childViews'), 'length'); + this.lengthBeforeRender = this._childViews.length; this.beforeRender(buffer); this.render(buffer); this.afterRender(buffer); - this.lengthAfterRender = get(get(this, '_childViews'), 'length'); + this.lengthAfterRender = this._childViews.length; return buffer; }, @@ -13195,12 +12978,10 @@ Ember.View = Ember.Object.extend(Ember.Evented, // Register the view for event handling. This hash is used by // Ember.EventDispatcher to dispatch incoming events. - Ember.View.views[get(this, 'elementId')] = this; - - var childViews = get(this, '_childViews').slice(); + if (!this.isVirtual) Ember.View.views[get(this, 'elementId')] = this; // setup child views. be sure to clone the child views array first - set(this, '_childViews', childViews); + this._childViews = this._childViews.slice(); Ember.assert("Only arrays are allowed for 'classNameBindings'", Ember.typeOf(this.classNameBindings) === 'array'); this.classNameBindings = Ember.A(this.classNameBindings.slice()); @@ -13237,10 +13018,11 @@ Ember.View = Ember.Object.extend(Ember.Evented, set(view, '_parentView', null); // remove view from childViews array. - var childViews = get(this, '_childViews'); + var childViews = this._childViews; + Ember.EnumerableUtils.removeObject(childViews, view); - this.propertyDidChange('childViews'); + this.propertyDidChange('childViews'); // HUH?! what happened to will change? return this; }, @@ -13287,9 +13069,8 @@ Ember.View = Ember.Object.extend(Ember.Evented, willDestroy: function() { // calling this._super() will nuke computed properties and observers, // so collect any information we need before calling super. - var childViews = get(this, '_childViews'), + var childViews = this._childViews, parent = get(this, '_parentView'), - elementId = get(this, 'elementId'), childLen; // destroy the element -- this will avoid each child view destroying @@ -13311,14 +13092,14 @@ Ember.View = Ember.Object.extend(Ember.Evented, this.state = 'destroyed'; - childLen = get(childViews, 'length'); + childLen = childViews.length; for (var i=childLen-1; i>=0; i--) { childViews[i].removedFromDOM = true; childViews[i].destroy(); } // next remove view from global hash - delete Ember.View.views[get(this, 'elementId')]; + if (!this.isVirtual) delete Ember.View.views[get(this, 'elementId')]; }, /** @@ -13440,8 +13221,13 @@ Ember.View = Ember.Object.extend(Ember.Evented, */ trigger: function(name) { this._super.apply(this, arguments); - if (this[name]) { - return this[name].apply(this, [].slice.call(arguments, 1)); + var method = this[name]; + if (method) { + var args = [], i, l; + for (i = 1, l = arguments.length; i < l; i++) { + args.push(arguments[i]); + } + return method.apply(this, args); } }, @@ -13634,15 +13420,9 @@ Ember.View.states.preRender = { empty: Ember.K, setElement: function(view, value) { - view.beginPropertyChanges(); - view.invalidateRecursively('element'); - if (value !== null) { view.transitionTo('hasElement'); } - - view.endPropertyChanges(); - return value; } }; @@ -13690,7 +13470,7 @@ Ember.View.states.inBuffer = { var buffer = view.buffer; childView = this.createChildView(childView, options); - get(view, '_childViews').push(childView); + view._childViews.push(childView); childView.renderToBuffer(buffer); @@ -13721,8 +13501,6 @@ Ember.View.states.inBuffer = { }, setElement: function(view, value) { - view.invalidateRecursively('element'); - if (value === null) { view.transitionTo('preRender'); } else { @@ -13765,8 +13543,6 @@ Ember.View.states.hasElement = { setElement: function(view, value) { if (value === null) { - view.invalidateRecursively('element'); - view.transitionTo('preRender'); } else { throw "You cannot set an element to a non-null value when the element is already in the DOM."; @@ -13797,9 +13573,9 @@ Ember.View.states.hasElement = { }, empty: function(view) { - var _childViews = get(view, '_childViews'), len, idx; + var _childViews = view._childViews, len, idx; if (_childViews) { - len = get(_childViews, 'length'); + len = _childViews.length; for (idx = 0; idx < len; idx++) { _childViews[idx]._notifyWillDestroyElement(); } @@ -14095,12 +13871,12 @@ var childViewsProperty = Ember.computed(function() { Ember.ContainerView = Ember.View.extend({ init: function() { + this._super(); + var childViews = get(this, 'childViews'); Ember.defineProperty(this, 'childViews', childViewsProperty); - this._super(); - - var _childViews = get(this, '_childViews'); + var _childViews = this._childViews; forEach(childViews, function(viewName, idx) { var view; @@ -14116,6 +13892,9 @@ Ember.ContainerView = Ember.View.extend({ _childViews[idx] = view; }, this); + var currentView = get(this, 'currentView'); + if (currentView) _childViews.push(this.createChildView(currentView)); + // Make the _childViews array observable Ember.A(_childViews); @@ -14334,7 +14113,7 @@ var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt; @class `Ember.CollectionView` is an `Ember.View` descendent responsible for managing a - collection (an array or array-like object) by maintaing a child view object and + collection (an array or array-like object) by maintaing a child view object and associated DOM representation for each item in the array and ensuring that child views and their associated rendered HTML are updated when items in the array are added, removed, or replaced. @@ -14378,7 +14157,7 @@ var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt; ## Automatic matching of parent/child tagNames - Setting the `tagName` property of a `CollectionView` to any of + Setting the `tagName` property of a `CollectionView` to any of "ul", "ol", "table", "thead", "tbody", "tfoot", "tr", or "select" will result in the item views receiving an appropriately matched `tagName` property. @@ -15231,15 +15010,15 @@ var arrayForEach = Ember.ArrayPolyfills.forEach; robotManager.getPath('currentState.name') // 'rampaging' Transition actions can also be created using the `transitionTo` method of the Ember.State class. The - following example StateManagers are equivalent: - + following example StateManagers are equivalent: + aManager = Ember.StateManager.create({ stateOne: Ember.State.create({ changeToStateTwo: Ember.State.transitionTo('stateTwo') }), stateTwo: Ember.State.create({}) }) - + bManager = Ember.StateManager.create({ stateOne: Ember.State.create({ changeToStateTwo: function(manager, context){ @@ -15320,7 +15099,7 @@ Ember.StateManager = Ember.State.extend( @default true */ errorOnUnhandledEvent: true, - + send: function(event, context) { Ember.assert('Cannot send event "' + event + '" while currentState is ' + get(this, 'currentState'), get(this, 'currentState')); return this.sendRecursively(event, get(this, 'currentState'), context); @@ -15493,28 +15272,25 @@ Ember.StateManager = Ember.State.extend( matchedContexts.unshift(useContext ? contexts.pop() : null); } - if (enterStates.length > 0) { - state = enterStates[enterStates.length - 1]; + state = enterStates[enterStates.length - 1] || resolveState; + while(true) { + initialState = get(state, 'initialState') || 'start'; + state = getPath(state, 'states.'+initialState); + if (!state) { break; } + enterStates.push(state); + matchedContexts.push(undefined); + } - while(true) { - initialState = get(state, 'initialState') || 'start'; - state = getPath(state, 'states.'+initialState); - if (!state) { break; } - enterStates.push(state); - matchedContexts.push(undefined); + while (enterStates.length > 0) { + if (enterStates[0] !== exitStates[0]) { break; } + + if (enterStates.length === matchedContexts.length) { + if (this.getStateMeta(enterStates[0], 'context') !== matchedContexts[0]) { break; } + matchedContexts.shift(); } - while (enterStates.length > 0) { - if (enterStates[0] !== exitStates[0]) { break; } - - if (enterStates.length === matchedContexts.length) { - if (this.getStateMeta(enterStates[0], 'context') !== matchedContexts[0]) { break; } - matchedContexts.shift(); - } - - resolveState = enterStates.shift(); - exitStates.shift(); - } + resolveState = enterStates.shift(); + exitStates.shift(); } } @@ -15817,6 +15593,7 @@ Ember.Routable = Ember.Mixin.create({ var modelClass, routeMatcher, param; if (modelClass = this.modelClassFor(get(manager, 'namespace'))) { + Ember.assert("Expected "+modelClass.toString()+" to implement `find` for use in '"+this.get('path')+"' `deserialize`. Please implement the `find` method or overwrite `deserialize`.", modelClass.find); return modelClass.find(params[paramForClass(modelClass)]); } @@ -16402,6 +16179,16 @@ Ember.Router = Ember.StateManager.extend( */ location: 'hash', + /** + This is only used when a history location is used so that applications that + don't live at the root of the domain can append paths to their root. + + @type String + @default '/' + */ + + rootURL: '/', + /** On router, transitionEvent should be called connectOutlets @@ -16486,9 +16273,14 @@ Ember.Router = Ember.StateManager.extend( init: function() { this._super(); - var location = get(this, 'location'); + var location = get(this, 'location'), + rootURL = get(this, 'rootURL'); + if ('string' === typeof location) { - set(this, 'location', Ember.Location.create({ implementation: location })); + set(this, 'location', Ember.Location.create({ + implementation: location, + rootURL: rootURL + })); } }, @@ -17811,7 +17603,7 @@ var EmberHandlebars = Ember.Handlebars, helpers = EmberHandlebars.helpers; // Binds a property into the DOM. This will create a hook in DOM that the // KVO system will look for and update if the property changes. /** @private */ -var bind = function(property, options, preserveContext, shouldDisplay, valueNormalizer) { +function bind(property, options, preserveContext, shouldDisplay, valueNormalizer) { var data = options.data, fn = options.fn, inverse = options.inverse, @@ -17861,7 +17653,7 @@ var bind = function(property, options, preserveContext, shouldDisplay, valueNorm // be done with it. data.buffer.push(getPath(pathRoot, path, options)); } -}; +} /** '_triageMustache' is used internally select between a binding and helper for @@ -18155,7 +17947,15 @@ EmberHandlebars.bindClasses = function(context, classBindings, view, bindAttrId, // determine which class string to return, based on whether it is // a Boolean or not. var classStringForPath = function(root, path, className, options) { - var val = path !== '' ? getPath(root, path, options) : true; + var val; + + if (path === 'this') { + val = root; + } else if (path === '') { + val = true; + } else { + val = getPath(root, path, options); + } // If the value is truthy and we're using the colon syntax, // we should return the className directly @@ -18166,7 +17966,7 @@ EmberHandlebars.bindClasses = function(context, classBindings, view, bindAttrId, // name. } else if (val === true) { // Normalize property path to be suitable for use - // as a class name. For exaple, content.foo.barBaz + // as a class name. For example, content.foo.barBaz // becomes bar-baz. var parts = path.split('.'); return Ember.String.dasherize(parts[parts.length-1]); @@ -18200,7 +18000,7 @@ EmberHandlebars.bindClasses = function(context, classBindings, view, bindAttrId, pathRoot = context, normalized; - if (path !== '') { + if (path !== '' && path !== 'this') { normalized = normalizePath(context, path, options.data); pathRoot = normalized.root; @@ -18241,7 +18041,7 @@ EmberHandlebars.bindClasses = function(context, classBindings, view, bindAttrId, Ember.run.once(observer); }; - if (path !== '') { + if (path !== '' && path !== 'this') { Ember.addObserver(pathRoot, path, invoker); } @@ -18278,6 +18078,7 @@ EmberHandlebars.bindClasses = function(context, classBindings, view, bindAttrId, var get = Ember.get, set = Ember.set; var PARENT_VIEW_PATH = /^parentView\./; var EmberHandlebars = Ember.Handlebars; +var VIEW_PRESERVES_CONTEXT = Ember.VIEW_PRESERVES_CONTEXT; /** @private */ EmberHandlebars.ViewHelper = Ember.Object.create({ @@ -18305,7 +18106,8 @@ EmberHandlebars.ViewHelper = Ember.Object.create({ } if (hash.classNameBindings) { - extensions.classNameBindings = hash.classNameBindings.split(' '); + if (extensions.classNameBindings === undefined) extensions.classNameBindings = []; + extensions.classNameBindings = extensions.classNameBindings.concat(hash.classNameBindings.split(' ')); dup = true; } @@ -18322,26 +18124,57 @@ EmberHandlebars.ViewHelper = Ember.Object.create({ delete hash.classBinding; } - // Look for bindings passed to the helper and, if they are - // local, make them relative to the current context instead of the - // view. - var path, normalized; + // Set the proper context for all bindings passed to the helper. This applies to regular attribute bindings + // as well as class name bindings. If the bindings are local, make them relative to the current context + // instead of the view. + var path; + // Evaluate the context of regular attribute bindings: for (var prop in hash) { if (!hash.hasOwnProperty(prop)) { continue; } // Test if the property ends in "Binding" if (Ember.IS_BINDING.test(prop) && typeof hash[prop] === 'string') { - path = hash[prop]; + path = this.contextualizeBindingPath(hash[prop], data); + if (path) { hash[prop] = path; } + } + } - normalized = Ember.Handlebars.normalizePath(null, path, data); - if (normalized.isKeyword) { - hash[prop] = 'templateData.keywords.'+path; - } else if (!Ember.isGlobalPath(path)) { - if (path === 'this') { - hash[prop] = 'bindingContext'; - } else { - hash[prop] = 'bindingContext.'+path; + // Evaluate the context of class name bindings: + if (extensions.classNameBindings) { + var full, + parts; + + for (var b in extensions.classNameBindings) { + full = extensions.classNameBindings[b]; + if (typeof full === 'string') { + if (full.indexOf(':') > 0) { + // When a classNameBinding contains a colon anywhere after the first character, + // then the part preceding the colon is a binding path that needs to be + // contextualized. + // + // For example: + // classNameBinding="isGreen:green" + // + // Will be converted to: + // classNameBinding="bindingContext.isGreen:green" + + parts = full.split(':'); + path = this.contextualizeBindingPath(parts[0], data); + if (path) { extensions.classNameBindings[b] = path + ':' + parts[1]; } + + } else if (full.indexOf(':') === -1 ) { + // When a classNameBinding doesn't contain any colons, then the entire binding + // needs to be contextualized. + // + // For example: + // classNameBinding="myClass" + // + // Will be converted to: + // classNameBinding="bindingContext.myClass" + + path = this.contextualizeBindingPath(full, data); + if (path) { extensions.classNameBindings[b] = path; } } } } @@ -18354,6 +18187,23 @@ EmberHandlebars.ViewHelper = Ember.Object.create({ return Ember.$.extend(hash, extensions); }, + // Transform bindings from the current context to a context that can be evaluated within the view. + // Returns null if the path shouldn't be changed. + // + // TODO: consider the addition of a prefix that would allow this method to return `path`. + contextualizeBindingPath: function(path, data) { + var normalized = Ember.Handlebars.normalizePath(null, path, data); + if (normalized.isKeyword) { + return 'templateData.keywords.' + path; + } else if (Ember.isGlobalPath(path)) { + return null; + } else if (path === 'this') { + return 'bindingContext'; + } else { + return 'bindingContext.' + path; + } + }, + helper: function(thisContext, path, options) { var inverse = options.inverse, data = options.data, @@ -18380,6 +18230,12 @@ EmberHandlebars.ViewHelper = Ember.Object.create({ viewOptions.template = fn; } + // We only want to override the `_context` computed property if there is + // no specified controller. See View#_context for more information. + if (VIEW_PRESERVES_CONTEXT && !newView.proto().controller && !newView.proto().controllerBinding && !viewOptions.controller && !viewOptions.controllerBinding) { + viewOptions._context = thisContext; + } + currentView.appendChild(newView, viewOptions); } }); @@ -18400,7 +18256,7 @@ EmberHandlebars.ViewHelper = Ember.Object.create({ Will result in HTML structure: - @@ -18422,7 +18278,7 @@ EmberHandlebars.ViewHelper = Ember.Object.create({ }) aView.appendTo('body') - + Will result in HTML structure:
@@ -18496,7 +18352,7 @@ EmberHandlebars.ViewHelper = Ember.Object.create({ Will result in the following HTML:
-
+
hi
@@ -18656,7 +18512,7 @@ var get = Ember.get, getPath = Ember.Handlebars.getPath, fmt = Ember.String.fmt;

Howdy Mary

Howdy Sara

- + @name Handlebars.helpers.collection @param {String} path @param {Hash} options @@ -18928,12 +18784,19 @@ var ActionHelper = EmberHandlebars.ActionHelper = { registeredActions: {} }; -ActionHelper.registerAction = function(actionName, eventName, target, view, context) { +ActionHelper.registerAction = function(actionName, eventName, target, view, context, link) { var actionId = (++Ember.$.uuid).toString(); ActionHelper.registeredActions[actionId] = { eventName: eventName, handler: function(event) { + if (link && (event.button !== 0 || event.shiftKey || event.metaKey || event.altKey || event.ctrlKey)) { + // Allow the browser to handle special link clicks normally + return; + } + + event.preventDefault(); + event.view = view; event.context = context; @@ -19098,7 +18961,7 @@ EmberHandlebars.registerHelper('action', function(actionName, options) { var hash = options.hash, eventName = hash.on || "click", view = options.data.view, - target, context, controller; + target, context, controller, link; view = get(view, 'concreteView'); @@ -19117,9 +18980,10 @@ EmberHandlebars.registerHelper('action', function(actionName, options) { if (hash.href && target.urlForEvent) { url = target.urlForEvent(actionName, context); output.push('href="' + url + '"'); + link = true; } - var actionId = ActionHelper.registerAction(actionName, eventName, target, view, context); + var actionId = ActionHelper.registerAction(actionName, eventName, target, view, context, link); output.push('data-ember-action="' + actionId + '"'); return new EmberHandlebars.SafeString(output.join(" ")); @@ -19271,7 +19135,7 @@ var set = Ember.set, get = Ember.get; /** @class - Creates an HTML input of type 'checkbox' with HTML related properties + Creates an HTML input of type 'checkbox' with HTML related properties applied directly to the input. {{view Ember.Checkbox classNames="applicaton-specific-checkbox"}} @@ -19290,7 +19154,7 @@ var set = Ember.set, get = Ember.get; through the Ember object or by interacting with its rendered element representation via the mouse, keyboard, or touch. Updating the value of the checkbox via jQuery will result in the checked value of the object and its element losing synchronization. - + ## Layout and LayoutName properties Because HTML `input` elements are self closing `layout` and `layoutName` properties will not be applied. See `Ember.View`'s layout section for more information. @@ -19301,7 +19165,7 @@ Ember.Checkbox = Ember.View.extend({ tagName: 'input', - attributeBindings: ['type', 'checked', 'disabled'], + attributeBindings: ['type', 'checked', 'disabled', 'tabindex'], type: "checkbox", checked: false, @@ -19338,7 +19202,7 @@ Ember.TextSupport = Ember.Mixin.create( value: "", - attributeBindings: ['placeholder', 'disabled', 'maxlength'], + attributeBindings: ['placeholder', 'disabled', 'maxlength', 'tabindex'], placeholder: null, disabled: false, maxlength: null, @@ -19402,7 +19266,7 @@ var get = Ember.get, set = Ember.set; ## Layout and LayoutName properties Because HTML `input` elements are self closing `layout` and `layoutName` properties will not be applied. See `Ember.View`'s layout section for more information. - + @extends Ember.TextSupport */ Ember.TextField = Ember.View.extend(Ember.TextSupport, @@ -19458,7 +19322,7 @@ Ember.Button = Ember.View.extend(Ember.TargetActionSupport, { propagateEvents: false, - attributeBindings: ['type', 'disabled', 'href'], + attributeBindings: ['type', 'disabled', 'href', 'tabindex'], /** @private Overrides TargetActionSupport's targetObject computed @@ -19579,7 +19443,7 @@ var get = Ember.get, set = Ember.set; ## Layout and LayoutName properties - Because HTML `textarea` elements do not contain inner HTML the `layout` and `layoutName` + Because HTML `textarea` elements do not contain inner HTML the `layout` and `layoutName` properties will not be applied. See `Ember.View`'s layout section for more information. @extends Ember.TextSupport @@ -19595,7 +19459,11 @@ Ember.TextArea = Ember.View.extend(Ember.TextSupport, cols: null, _updateElementValue: Ember.observer(function() { - this.$().val(get(this, 'value')); + // We do this check so cursor position doesn't get affected in IE + var value = get(this, 'value'); + if (value !== this.$().val()) { + this.$().val(value); + } }, 'value'), init: function() { @@ -19771,7 +19639,7 @@ Ember.Select = Ember.View.extend( tagName: 'select', classNames: ['ember-select'], defaultTemplate: Ember.Handlebars.compile('{{#if view.prompt}}{{/if}}{{#each view.content}}{{view Ember.SelectOption contentBinding="this"}}{{/each}}'), - attributeBindings: ['multiple'], + attributeBindings: ['multiple', 'tabindex'], /** The `multiple` attribute of the select element. Indicates whether multiple @@ -19793,7 +19661,7 @@ Ember.Select = Ember.View.extend( content: Ember.A([ { id: 1, firstName: 'Yehuda' }, { id: 2, firstName: 'Tom' } - ])), + ]), optionLabelPath: 'content.firstName', optionValuePath: 'content.id' @@ -20127,8 +19995,8 @@ Ember.$(document).ready( })(); -// Version: v0.9.8.1-484-g73ac0a4 -// Last commit: 73ac0a4 (2012-07-06 11:52:32 -0700) +// Version: v0.9.8.1-617-g2b213df +// Last commit: 2b213df (2012-07-14 16:44:53 -0700) (function() {