From cebe5b1e1ca760cb3240e4152e0c651addc4d5cc Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Fri, 6 Sep 2013 22:14:11 +0200 Subject: [PATCH] Revert "Update ember and ember-model" This reverts commit 408cce2d4f4735e76558d10223ba3d61ed8bf584. --- assets/scripts/vendor/ember-model.js | 773 +-- assets/scripts/vendor/ember.js | 8996 ++++++++------------------ 2 files changed, 3020 insertions(+), 6749 deletions(-) diff --git a/assets/scripts/vendor/ember-model.js b/assets/scripts/vendor/ember-model.js index c47a64c7..a5eea67b 100644 --- a/assets/scripts/vendor/ember-model.js +++ b/assets/scripts/vendor/ember-model.js @@ -2,29 +2,26 @@ function mustImplement(message) { var fn = function() { - var className = this.constructor.toString(); - - throw new Error(message.replace('{{className}}', className)); + throw new Error(message); }; fn.isUnimplemented = true; return fn; } Ember.Adapter = Ember.Object.extend({ - find: mustImplement('{{className}} must implement find'), - findQuery: mustImplement('{{className}} must implement findQuery'), - findMany: mustImplement('{{className}} must implement findMany'), - findAll: mustImplement('{{className}} must implement findAll'), - createRecord: mustImplement('{{className}} must implement createRecord'), - saveRecord: mustImplement('{{className}} must implement saveRecord'), - deleteRecord: mustImplement('{{className}} must implement deleteRecord'), + find: mustImplement('Ember.Adapter subclasses must implement find'), + findQuery: mustImplement('Ember.Adapter subclasses must implement findQuery'), + findMany: mustImplement('Ember.Adapter subclasses must implement findMany'), + findAll: mustImplement('Ember.Adapter subclasses must implement findAll'), + createRecord: mustImplement('Ember.Adapter subclasses must implement createRecord'), + saveRecord: mustImplement('Ember.Adapter subclasses must implement saveRecord'), + deleteRecord: mustImplement('Ember.Adapter subclasses must implement deleteRecord'), load: function(record, id, data) { record.load(id, data); } }); - })(); (function() { @@ -146,23 +143,12 @@ Ember.RecordArray = Ember.ArrayProxy.extend(Ember.Evented, { }, reload: function() { - var modelClass = this.get('modelClass'), - self = this, - promises; + var modelClass = this.get('modelClass'); + Ember.assert("Reload can only be called on findAll RecordArrays", + modelClass && modelClass._findAllRecordArray === this); set(this, 'isLoaded', false); - if (modelClass._findAllRecordArray === this) { - modelClass.adapter.findAll(modelClass, this); - } else if (this._query) { - modelClass.adapter.findQuery(modelClass, this, this._query); - } else { - promises = this.map(function(record) { - return record.reload(); - }); - Ember.RSVP.all(promises).then(function(data) { - self.notifyLoaded(); - }); - } + modelClass.adapter.findAll(modelClass, this); } }); @@ -185,13 +171,13 @@ Ember.FilteredRecordArray = Ember.RecordArray.extend({ throw new Error('FilteredRecordArrays must be created with filterProperties'); } + this._registeredClientIds = Ember.A([]); + var modelClass = get(this, 'modelClass'); modelClass.registerRecordArray(this); this.registerObservers(); this.updateFilter(); - - this._super(); }, updateFilter: function() { @@ -200,6 +186,8 @@ Ember.FilteredRecordArray = Ember.RecordArray.extend({ get(this, 'modelClass').forEachCachedRecord(function(record) { if (self.filterFunction(record)) { results.push(record); + } else { + results.removeObject(record); } }); this.set('content', Ember.A(results)); @@ -207,8 +195,10 @@ Ember.FilteredRecordArray = Ember.RecordArray.extend({ updateFilterForRecord: function(record) { var results = get(this, 'content'); - if (this.filterFunction(record) && !results.contains(record)) { - results.pushObject(record); + if (this.filterFunction(record)) { + if(!results.contains(record)) { + results.pushObject(record); + } } else { results.removeObject(record); } @@ -223,10 +213,14 @@ Ember.FilteredRecordArray = Ember.RecordArray.extend({ registerObserversOnRecord: function(record) { var self = this, - filterProperties = get(this, 'filterProperties'); + filterProperties = get(this, 'filterProperties'), + clientId = record._reference.clientId; - for (var i = 0, l = get(filterProperties, 'length'); i < l; i++) { - record.addObserver(filterProperties[i], self, 'updateFilterForRecord'); + if(!this._registeredClientIds.contains(clientId)) { + for (var i = 0, l = get(filterProperties, 'length'); i < l; i++) { + record.addObserver(filterProperties[i], self, 'updateFilterForRecord'); + } + this._registeredClientIds.pushObject(clientId); } } }); @@ -235,31 +229,10 @@ Ember.FilteredRecordArray = Ember.RecordArray.extend({ (function() { -var get = Ember.get, set = Ember.set; +var get = Ember.get; Ember.ManyArray = Ember.RecordArray.extend({ _records: null, - originalContent: null, - - isDirty: function() { - var originalContent = get(this, 'originalContent'), - originalContentLength = get(originalContent, 'length'), - content = get(this, 'content'), - contentLength = get(content, 'length'); - - if (originalContentLength !== contentLength) { return true; } - - var isDirty = false; - - for (var i = 0, l = contentLength; i < l; i++) { - if (!originalContent.contains(content[i])) { - isDirty = true; - break; - } - } - - return isDirty; - }.property('content.[]', 'originalContent'), objectAtContent: function(idx) { var content = get(this, 'content'); @@ -282,48 +255,6 @@ Ember.ManyArray = Ember.RecordArray.extend({ }, this); this._super(index, removed, added); - }, - - _contentWillChange: function() { - var content = get(this, 'content'); - if (content) { - content.removeArrayObserver(this); - this._setupOriginalContent(content); - } - }.observesBefore('content'), - - _contentDidChange: function() { - var content = get(this, 'content'); - if (content) { - content.addArrayObserver(this); - this.arrayDidChange(content, 0, 0, get(content, 'length')); - } - }.observes('content'), - - arrayWillChange: function(item, idx, removedCnt, addedCnt) {}, - - arrayDidChange: function(item, idx, removedCnt, addedCnt) { - var parent = get(this, 'parent'), relationshipKey = get(this, 'relationshipKey'), - isDirty = get(this, 'isDirty'); - - if (isDirty) { - parent._relationshipBecameDirty(relationshipKey); - } else { - parent._relationshipBecameClean(relationshipKey); - } - }, - - _setupOriginalContent: function(content) { - content = content || get(this, 'content'); - if (content) { - set(this, 'originalContent', content.slice()); - } - }, - - init: function() { - this._super(); - this._setupOriginalContent(); - this._contentDidChange(); } }); @@ -337,7 +268,7 @@ Ember.HasManyArray = Ember.ManyArray.extend({ if (reference.record) { record = reference.record; } else { - record = klass.find(reference.id); + record = klass.findById(reference.id); } return record; @@ -426,6 +357,36 @@ function hasCachedValue(object, key) { } } +function extractDirty(object, attrsOrRelations, dirtyAttributes) { + var key, desc, descMeta, type, dataValue, cachedValue, isDirty, dataType; + for (var i = 0, l = attrsOrRelations.length; i < l; i++) { + key = attrsOrRelations[i]; + if (!hasCachedValue(object, key)) { continue; } + cachedValue = object.cacheFor(key); + dataValue = get(object, '_data.' + object.dataKey(key)); + desc = meta(object).descs[key]; + descMeta = desc && desc.meta(); + type = descMeta.type; + dataType = Ember.Model.dataTypes[type]; + + if (type && type.isEqual) { + isDirty = !type.isEqual(dataValue, cachedValue); + } else if (dataType && dataType.isEqual) { + isDirty = !dataType.isEqual(dataValue, cachedValue); + } else if (dataValue && cachedValue instanceof Ember.Model) { // belongsTo case + isDirty = get(cachedValue, 'isDirty'); + } else if (dataValue !== cachedValue) { + isDirty = true; + } else { + isDirty = false; + } + + if (isDirty) { + dirtyAttributes.push(key); + } + } +} + Ember.run.queues.push('data'); Ember.Model = Ember.Object.extend(Ember.Evented, { @@ -446,20 +407,24 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { return value; }, - isDirty: function() { - var dirtyAttributes = get(this, '_dirtyAttributes'); - return dirtyAttributes && dirtyAttributes.length !== 0 || false; - }.property('_dirtyAttributes.length'), + isDirty: Ember.computed(function() { + var attributes = this.attributes, + relationships = this.relationships, + dirtyAttributes = Ember.A(); // just for removeObject - _relationshipBecameDirty: function(name) { - var dirtyAttributes = get(this, '_dirtyAttributes'); - if (!dirtyAttributes.contains(name)) { dirtyAttributes.pushObject(name); } - }, + extractDirty(this, attributes, dirtyAttributes); + if (relationships) { + extractDirty(this, relationships, dirtyAttributes); + } - _relationshipBecameClean: function(name) { - var dirtyAttributes = get(this, '_dirtyAttributes'); - dirtyAttributes.removeObject(name); - }, + if (dirtyAttributes.length) { + this._dirtyAttributes = dirtyAttributes; + return true; + } else { + this._dirtyAttributes = []; + return false; + } + }).property().volatile(), dataKey: function(key) { var camelizeKeys = get(this.constructor, 'camelizeKeys'); @@ -472,9 +437,6 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { init: function() { this._createReference(); - if (!this._dirtyAttributes) { - set(this, '_dirtyAttributes', []); - } this._super(); this.one('didLoad', function() { @@ -487,7 +449,7 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { id = this.getPrimaryKey(); if (!reference) { - reference = this.constructor._getOrCreateReferenceForId(id); + reference = this.constructor._referenceForId(id); reference.record = this; this._reference = reference; } @@ -507,27 +469,6 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { var data = {}; data[get(this.constructor, 'primaryKey')] = id; set(this, '_data', Ember.merge(data, hash)); - - // eagerly load embedded data - var relationships = this.constructor._relationships || [], meta = Ember.meta(this), relationshipKey, relationship, relationshipMeta, relationshipData, relationshipType; - for (var i = 0, l = relationships.length; i < l; i++) { - relationshipKey = relationships[i]; - relationship = meta.descs[relationshipKey]; - relationshipMeta = relationship.meta(); - - if (relationshipMeta.options.embedded) { - relationshipType = relationshipMeta.type; - if (typeof relationshipType === "string") { - relationshipType = Ember.get(Ember.lookup, relationshipType); - } - - relationshipData = data[relationshipKey]; - if (relationshipData) { - relationshipType.load(relationshipData); - } - } - } - set(this, 'isLoaded', true); set(this, 'isNew', false); this._createReference(); @@ -537,15 +478,13 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { didDefineProperty: function(proto, key, value) { if (value instanceof Ember.Descriptor) { var meta = value.meta(); - var klass = proto.constructor; if (meta.isAttribute) { - if (!klass._attributes) { klass._attributes = []; } - klass._attributes.push(key); + proto.attributes = proto.attributes ? proto.attributes.slice() : []; + proto.attributes.push(key); } else if (meta.isRelationship) { - if (!klass._relationships) { klass._relationships = []; } - klass._relationships.push(key); - meta.relationshipKey = key; + proto.relationships = proto.relationships ? proto.relationships.slice() : []; + proto.relationships.push(key); } } }, @@ -567,9 +506,7 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { toJSON: function() { var key, meta, json = {}, - attributes = this.constructor.getAttributes(), - relationships = this.constructor.getRelationships(), - properties = attributes ? this.getProperties(attributes) : {}, + properties = this.attributes ? this.getProperties(this.attributes) : {}, rootKey = get(this.constructor, 'rootKey'); for (key in properties) { @@ -583,11 +520,11 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { } } - if (relationships) { + if (this.relationships) { var data, relationshipKey; - for(var i = 0; i < relationships.length; i++) { - key = relationships[i]; + for(var i = 0; i < this.relationships.length; i++) { + key = this.relationships[i]; meta = this.constructor.metaForProperty(key); relationshipKey = meta.options.key || key; @@ -633,8 +570,15 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { }, revert: function() { - this.getWithDefault('_dirtyAttributes', []).clear(); - this.notifyPropertyChange('_data'); + if (this.get('isDirty')) { + var data = get(this, '_data') || {}, + reverts = {}; + for (var i = 0; i < this._dirtyAttributes.length; i++) { + var attr = this._dirtyAttributes[i]; + reverts[attr] = data[attr]; + } + setProperties(this, reverts); + } }, didCreateRecord: function() { @@ -643,7 +587,10 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { set(this, 'isNew', false); - set(this, '_dirtyAttributes', []); + if (!this.constructor.recordCache) this.constructor.recordCache = {}; + this.constructor.recordCache[id] = this; + + this._copyDirtyAttributesToData(); this.constructor.addToRecordArrays(this); this.trigger('didCreateRecord'); this.didSaveRecord(); @@ -680,7 +627,7 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { key = dirtyAttributes[i]; data[this.dataKey(key)] = this.cacheFor(key); } - set(this, '_dirtyAttributes', []); + this._dirtyAttributes = []; }, dataDidChange: Ember.observer(function() { @@ -695,16 +642,11 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { _reloadHasManys: function() { if (!this._hasManyArrays) { return; } - var i, j; - for (i = 0; i < this._hasManyArrays.length; i++) { - var array = this._hasManyArrays[i], - hasManyContent = this._getHasManyContent(get(array, 'key'), get(array, 'modelClass'), get(array, 'embedded')); - for (j = 0; j < array.get('length'); j++) { - if (array.objectAt(j).get('isNew')) { - hasManyContent.addObject(array.objectAt(j)._reference); - } - } - set(array, 'content', hasManyContent); + + var i; + for(i = 0; i < this._hasManyArrays.length; i++) { + var array = this._hasManyArrays[i]; + set(array, 'content', this._getHasManyContent(get(array, 'key'), get(array, 'modelClass'), get(array, 'embedded'))); } }, @@ -716,12 +658,12 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { if (embedded) { primaryKey = get(type, 'primaryKey'); mapFunction = function(attrs) { - reference = type._getOrCreateReferenceForId(attrs[primaryKey]); + reference = type._referenceForId(attrs[primaryKey]); reference.data = attrs; return reference; }; } else { - mapFunction = function(id) { return type._getOrCreateReferenceForId(id); }; + mapFunction = function(id) { return type._referenceForId(id); }; } content = Ember.EnumerableUtils.map(content, mapFunction); } @@ -737,77 +679,26 @@ Ember.Model.reopenClass({ _clientIdCounter: 1, - getAttributes: function() { - this.proto(); // force class "compilation" if it hasn't been done. - var attributes = this._attributes || []; - if (typeof this.superclass.getAttributes === 'function') { - attributes = this.superclass.getAttributes().concat(attributes); - } - return attributes; - }, - - getRelationships: function() { - this.proto(); // force class "compilation" if it hasn't been done. - var relationships = this._relationships || []; - if (typeof this.superclass.getRelationships === 'function') { - relationships = this.superclass.getRelationships().concat(relationships); - } - return relationships; - }, - - fetch: function(id) { - if (!arguments.length) { - return this._findFetchAll(true); - } else if (Ember.isArray(id)) { - return this._findFetchMany(id, true); - } else if (typeof id === 'object') { - return this._findFetchQuery(id, true); - } else { - return this._findFetchById(id, true); - } + fetch: function() { + return Ember.loadPromise(this.find.apply(this, arguments)); }, find: function(id) { if (!arguments.length) { - return this._findFetchAll(false); + return this.findAll(); } else if (Ember.isArray(id)) { - return this._findFetchMany(id, false); + return this.findMany(id); } else if (typeof id === 'object') { - return this._findFetchQuery(id, false); + return this.findQuery(id); } else { - return this._findFetchById(id, false); + return this.findById(id); } }, - findQuery: function(params) { - return this._findFetchQuery(params, false); - }, - - fetchQuery: function(params) { - return this._findFetchQuery(params, true); - }, - - _findFetchQuery: function(params, isFetch) { - var records = Ember.RecordArray.create({modelClass: this, _query: params}); - - var promise = this.adapter.findQuery(this, records, params); - - return isFetch ? promise : records; - }, - findMany: function(ids) { - return this._findFetchMany(ids, false); - }, + Ember.assert("findMany requires an array", Ember.isArray(ids)); - fetchMany: function(ids) { - return this._findFetchMany(ids, true); - }, - - _findFetchMany: function(ids, isFetch) { - Ember.assert("findFetchMany requires an array", Ember.isArray(ids)); - - var records = Ember.RecordArray.create({_ids: ids, modelClass: this}), - deferred; + var records = Ember.RecordArray.create({_ids: ids}); if (!this.recordArrays) { this.recordArrays = []; } this.recordArrays.push(records); @@ -820,97 +711,43 @@ Ember.Model.reopenClass({ this._currentBatchRecordArrays = [records]; } - if (isFetch) { - deferred = Ember.Deferred.create(); - Ember.set(deferred, 'resolveWith', records); - - if (!this._currentBatchDeferreds) { this._currentBatchDeferreds = []; } - this._currentBatchDeferreds.push(deferred); - } - Ember.run.scheduleOnce('data', this, this._executeBatch); - return isFetch ? deferred : records; + return records; }, findAll: function() { - return this._findFetchAll(false); - }, - - fetchAll: function() { - return this._findFetchAll(true); - }, - - _findFetchAll: function(isFetch) { - var self = this; - - if (this._findAllRecordArray) { - if (isFetch) { - return new Ember.RSVP.Promise(function(resolve) { - resolve(self._findAllRecordArray); - }); - } else { - return this._findAllRecordArray; - } - } + if (this._findAllRecordArray) { return this._findAllRecordArray; } var records = this._findAllRecordArray = Ember.RecordArray.create({modelClass: this}); - var promise = this.adapter.findAll(this, records); + this.adapter.findAll(this, records); - // Remove the cached record array if the promise is rejected - if (promise.then) { - promise.then(null, function() { - self._findAllRecordArray = null; - return Ember.RSVP.reject.apply(null, arguments); - }); - } - - return isFetch ? promise : records; - }, - - findById: function(id) { - return this._findFetchById(id, false); - }, - - fetchById: function(id) { - return this._findFetchById(id, true); - }, - - _findFetchById: function(id, isFetch) { - var record = this.cachedRecordForId(id), - isLoaded = get(record, 'isLoaded'), - adapter = get(this, 'adapter'), - deferredOrPromise; - - if (isLoaded) { - if (isFetch) { - return new Ember.RSVP.Promise(function(resolve, reject) { - resolve(record); - }); - } else { - return record; - } - } - - deferredOrPromise = this._fetchById(record, id); - - return isFetch ? deferredOrPromise : record; + return records; }, _currentBatchIds: null, _currentBatchRecordArrays: null, - _currentBatchDeferreds: null, + + findById: function(id) { + var record = this.cachedRecordForId(id); + + if (!get(record, 'isLoaded')) { + this._fetchById(record, id); + } + return record; + }, reload: function(id) { var record = this.cachedRecordForId(id); - record.set('isLoaded', false); - return this._fetchById(record, id); + + this._fetchById(record, id); + + return record; }, _fetchById: function(record, id) { - var adapter = get(this, 'adapter'), - deferred; + var adapter = get(this, 'adapter'); if (adapter.findMany && !adapter.findMany.isUnimplemented) { if (this._currentBatchIds) { @@ -920,17 +757,8 @@ Ember.Model.reopenClass({ this._currentBatchRecordArrays = []; } - deferred = Ember.Deferred.create(); - - //Attached the record to the deferred so we can resolove it later. - Ember.set(deferred, 'resolveWith', record); - - if (!this._currentBatchDeferreds) { this._currentBatchDeferreds = []; } - this._currentBatchDeferreds.push(deferred); - Ember.run.scheduleOnce('data', this, this._executeBatch); - - return deferred; + // TODO: return a promise here } else { return adapter.find(record, id); } @@ -939,7 +767,6 @@ Ember.Model.reopenClass({ _executeBatch: function() { var batchIds = this._currentBatchIds, batchRecordArrays = this._currentBatchRecordArrays, - batchDeferreds = this._currentBatchDeferreds, self = this, requestIds = [], promise, @@ -947,7 +774,6 @@ Ember.Model.reopenClass({ this._currentBatchIds = null; this._currentBatchRecordArrays = null; - this._currentBatchDeferreds = null; for (i = 0; i < batchIds.length; i++) { if (!this.cachedRecordForId(batchIds[i]).get('isLoaded')) { @@ -971,36 +797,29 @@ Ember.Model.reopenClass({ for (var i = 0, l = batchRecordArrays.length; i < l; i++) { batchRecordArrays[i].loadForFindMany(self); } - - if (batchDeferreds) { - for (i = 0, l = batchDeferreds.length; i < l; i++) { - var resolveWith = Ember.get(batchDeferreds[i], 'resolveWith'); - batchDeferreds[i].resolve(resolveWith); - } - } - }).then(null, function(errorXHR) { - if (batchDeferreds) { - for (var i = 0, l = batchDeferreds.length; i < l; i++) { - batchDeferreds[i].reject(errorXHR); - } - } }); }, - getCachedReferenceRecord: function(id){ - var ref = this._getReferenceById(id); - if(ref) return ref.record; - return undefined; + findQuery: function(params) { + var records = Ember.RecordArray.create(); + + this.adapter.findQuery(this, records, params); + + return records; }, cachedRecordForId: function(id) { - var record = this.getCachedReferenceRecord(id); + if (!this.recordCache) { this.recordCache = {}; } + var record; - if (!record) { + if (this.recordCache[id]) { + record = this.recordCache[id]; + } else { var primaryKey = get(this, 'primaryKey'), - attrs = {isLoaded: false}; + attrs = {isLoaded: false}; attrs[primaryKey] = id; record = this.create(attrs); + this.recordCache[id] = record; var sideloadedData = this.sideloadedData && this.sideloadedData[id]; if (sideloadedData) { record.load(id, sideloadedData); @@ -1010,7 +829,6 @@ Ember.Model.reopenClass({ return record; }, - addToRecordArrays: function(record) { if (this._findAllRecordArray) { this._findAllRecordArray.pushObject(record); @@ -1019,7 +837,7 @@ Ember.Model.reopenClass({ this.recordArrays.forEach(function(recordArray) { if (recordArray instanceof Ember.FilteredRecordArray) { recordArray.registerObserversOnRecord(record); - recordArray.updateFilter(); + recordArray.updateFilterForRecord(record); } else { recordArray.pushObject(record); } @@ -1027,26 +845,6 @@ Ember.Model.reopenClass({ } }, - unload: function (record) { - this.removeFromRecordArrays(record); - var primaryKey = record.get(get(this, 'primaryKey')); - this.removeFromCache(primaryKey); - }, - - clearCache: function () { - this.sideloadedData = undefined; - this._referenceCache = undefined; - }, - - removeFromCache: function (key) { - if (this.sideloadedData && this.sideloadedData[key]) { - delete this.sideloadedData[key]; - } - if(this._referenceCache && this._referenceCache[key]) { - delete this._referenceCache[key]; - } - }, - removeFromRecordArrays: function(record) { if (this._findAllRecordArray) { this._findAllRecordArray.removeObject(record); @@ -1082,39 +880,25 @@ Ember.Model.reopenClass({ }, forEachCachedRecord: function(callback) { - if (!this._referenceCache) { this._referenceCache = {}; } - var ids = Object.keys(this._referenceCache); + if (!this.recordCache) { return Ember.A([]); } + var ids = Object.keys(this.recordCache); ids.map(function(id) { - return this._getReferenceById(id).record; + return this.recordCache[id]; }, this).forEach(callback); }, load: function(hashes) { - if (Ember.typeOf(hashes) !== 'array') { hashes = [hashes]; } - if (!this.sideloadedData) { this.sideloadedData = {}; } - for (var i = 0, l = hashes.length; i < l; i++) { - var hash = hashes[i], - primaryKey = hash[get(this, 'primaryKey')], - record = this.getCachedReferenceRecord(primaryKey); - - if (record) { - record.load(primaryKey, hash); - } else { - this.sideloadedData[primaryKey] = hash; - } + var hash = hashes[i]; + this.sideloadedData[hash[get(this, 'primaryKey')]] = hash; } }, - _getReferenceById: function(id) { - if (!this._referenceCache) { this._referenceCache = {}; } - return this._referenceCache[id]; - }, - - _getOrCreateReferenceForId: function(id) { - var reference = this._getReferenceById(id); + _referenceForId: function(id) { + if (!this._idToReference) { this._idToReference = {}; } + var reference = this._idToReference[id]; if (!reference) { reference = this._createReference(id); } @@ -1123,9 +907,9 @@ Ember.Model.reopenClass({ }, _createReference: function(id) { - if (!this._referenceCache) { this._referenceCache = {}; } + if (!this._idToReference) { this._idToReference = {}; } - Ember.assert('The id ' + id + ' has alread been used with another record of type ' + this.toString() + '.', !id || !this._referenceCache[id]); + Ember.assert('The id ' + id + ' has alread been used with another record of type ' + this.toString() + '.', !id || !this._idToReference[id]); var reference = { id: id, @@ -1135,10 +919,20 @@ Ember.Model.reopenClass({ // if we're creating an item, this process will be done // later, once the object has been persisted. if (id) { - this._referenceCache[id] = reference; + this._idToReference[id] = reference; } return reference; + }, + + resetData: function() { + this._idToReference = null; + this.sideloadedData = null; + this.recordCache = null; + this.recordArrays = null; + this._currentBatchIds = null; + this._hasManyArrays = null; + this._findAllRecordArray = null; } }); @@ -1174,8 +968,7 @@ Ember.Model.reopen({ modelClass: type, content: this._getHasManyContent(key, type, embedded), embedded: embedded, - key: key, - relationshipKey: meta.relationshipKey + key: key }); this._registerHasManyArray(collection); @@ -1189,8 +982,7 @@ Ember.Model.reopen({ (function() { -var get = Ember.get, - set = Ember.set; +var get = Ember.get; function getType() { if (typeof this.type === "string") { @@ -1205,32 +997,14 @@ Ember.belongsTo = function(type, options) { var meta = { type: type, isRelationship: true, options: options, kind: 'belongsTo', getType: getType }, relationshipKey = options.key; - return Ember.computed(function(key, value, oldValue) { + return Ember.computed(function(key, value) { type = meta.getType(); - var dirtyAttributes = get(this, '_dirtyAttributes'), - createdDirtyAttributes = false; - - if (!dirtyAttributes) { - dirtyAttributes = []; - createdDirtyAttributes = true; - } - - if (arguments.length > 1) { + if (arguments.length === 2) { if (value) { Ember.assert(Ember.String.fmt('Attempted to set property of type: %@ with a value of type: %@', [value.constructor, type]), value instanceof type); - - if (oldValue !== value) { - dirtyAttributes.pushObject(key); - } else { - dirtyAttributes.removeObject(key); - } - - if (createdDirtyAttributes) { - set(this, '_dirtyAttributes', dirtyAttributes); - } } return value === undefined ? null : value; } else { @@ -1249,12 +1023,11 @@ Ember.Model.reopen({ } if (meta.options.embedded) { - var primaryKey = get(type, 'primaryKey'), - id = idOrAttrs[primaryKey]; - record = type.create({ isLoaded: false, id: id }); - record.load(id, idOrAttrs); + var primaryKey = get(type, 'primaryKey'); + record = type.create({ isLoaded: false }); + record.load(idOrAttrs[primaryKey], idOrAttrs); } else { - record = type.find(idOrAttrs); + record = type.findById(idOrAttrs); } return record; @@ -1270,15 +1043,41 @@ var get = Ember.get, set = Ember.set, meta = Ember.meta; +function wrapObject(value) { + if (Ember.isArray(value)) { + var clonedArray = value.slice(); + + // TODO: write test for recursive cloning + for (var i = 0, l = clonedArray.length; i < l; i++) { + clonedArray[i] = wrapObject(clonedArray[i]); + } + + return Ember.A(clonedArray); + } else if (value && value.constructor === Date) { + return new Date(value.toISOString()); + } else if (value && typeof value === "object") { + var clone = Ember.create(value), property; + + for (property in value) { + if (value.hasOwnProperty(property) && typeof value[property] === "object") { + clone[property] = wrapObject(value[property]); + } + } + return clone; + } else { + return value; + } +} + Ember.Model.dataTypes = {}; Ember.Model.dataTypes[Date] = { deserialize: function(string) { - if (!string) { return null; } + if(!string) { return null; } return new Date(string); }, serialize: function (date) { - if (!date) { return null; } + if(!date) { return null; } return date.toISOString(); }, isEqual: function(obj1, obj2) { @@ -1305,7 +1104,7 @@ function deserialize(value, type) { } else if (type && Ember.Model.dataTypes[type]) { return Ember.Model.dataTypes[type].deserialize(value); } else { - return value; + return wrapObject(value); } } @@ -1315,35 +1114,15 @@ Ember.attr = function(type, options) { var data = get(this, '_data'), dataKey = this.dataKey(key), dataValue = data && get(data, dataKey), - beingCreated = meta(this).proto === this, - dirtyAttributes = get(this, '_dirtyAttributes'), - createdDirtyAttributes = false; - - if (!dirtyAttributes) { - dirtyAttributes = []; - createdDirtyAttributes = true; - } + beingCreated = meta(this).proto === this; if (arguments.length === 2) { - if (beingCreated) { - if (!data) { - data = {}; - set(this, '_data', data); - } - dataValue = data[dataKey] = value; + if (beingCreated && !data) { + data = {}; + set(this, '_data', data); + data[dataKey] = value; } - - if (dataValue !== value) { - dirtyAttributes.pushObject(key); - } else { - dirtyAttributes.removeObject(key); - } - - if (createdDirtyAttributes) { - set(this, '_dirtyAttributes', dirtyAttributes); - } - - return value; + return wrapObject(value); } return this.getAttr(key, deserialize(dataValue, type)); @@ -1364,7 +1143,6 @@ Ember.RESTAdapter = Ember.Adapter.extend({ return this.ajax(url).then(function(data) { self.didFind(record, id, data); - return record; }); }, @@ -1381,7 +1159,6 @@ Ember.RESTAdapter = Ember.Adapter.extend({ return this.ajax(url).then(function(data) { self.didFindAll(klass, records, data); - return records; }); }, @@ -1398,7 +1175,6 @@ Ember.RESTAdapter = Ember.Adapter.extend({ return this.ajax(url, params).then(function(data) { self.didFindQuery(klass, records, params, data); - return records; }); }, @@ -1415,7 +1191,6 @@ Ember.RESTAdapter = Ember.Adapter.extend({ return this.ajax(url, record.toJSON(), "POST").then(function(data) { self.didCreateRecord(record, data); - return record; }); }, @@ -1423,6 +1198,7 @@ Ember.RESTAdapter = Ember.Adapter.extend({ var rootKey = get(record.constructor, 'rootKey'), primaryKey = get(record.constructor, 'primaryKey'), dataToLoad = rootKey ? data[rootKey] : data; + record.load(dataToLoad[primaryKey], dataToLoad); record.didCreateRecord(); }, @@ -1434,7 +1210,6 @@ Ember.RESTAdapter = Ember.Adapter.extend({ return this.ajax(url, record.toJSON(), "PUT").then(function(data) { // TODO: Some APIs may or may not return data self.didSaveRecord(record, data); - return record; }); }, @@ -1464,7 +1239,7 @@ Ember.RESTAdapter = Ember.Adapter.extend({ var urlRoot = get(klass, 'url'); if (!urlRoot) { throw new Error('Ember.RESTAdapter requires a `url` property to be specified'); } - if (!Ember.isEmpty(id)) { + if (id) { return urlRoot + "/" + id + ".json"; } else { return urlRoot + ".json"; @@ -1497,11 +1272,6 @@ Ember.RESTAdapter = Ember.Adapter.extend({ }; settings.error = function(jqXHR, textStatus, errorThrown) { - // https://github.com/ebryn/ember-model/issues/202 - if (jqXHR) { - jqXHR.then = null; - } - Ember.run(null, reject, jqXHR); }; @@ -1545,123 +1315,4 @@ Ember.loadPromise = function(target) { }; -})(); - -(function() { - -// This is a debug adapter for the Ember Extension, don't let the fact this is called an "adapter" confuse you. -// Most copied from: https://github.com/emberjs/data/blob/master/packages/ember-data/lib/system/debug/debug_adapter.js - -if (!Ember.DataAdapter) { return; } - -var get = Ember.get, capitalize = Ember.String.capitalize, underscore = Ember.String.underscore; - -var DebugAdapter = Ember.DataAdapter.extend({ - getFilters: function() { - return [ - { name: 'isNew', desc: 'New' }, - { name: 'isModified', desc: 'Modified' }, - { name: 'isClean', desc: 'Clean' } - ]; - }, - - detect: function(klass) { - return klass !== Ember.Model && Ember.Model.detect(klass); - }, - - columnsForType: function(type) { - var columns = [], count = 0, self = this; - Ember.A(get(type.proto(), 'attributes')).forEach(function(name, meta) { - if (count++ > self.attributeLimit) { return false; } - var desc = capitalize(underscore(name).replace('_', ' ')); - columns.push({ name: name, desc: desc }); - }); - return columns; - }, - - getRecords: function(type) { - var records = []; - type.forEachCachedRecord(function(record) { records.push(record); }); - return records; - }, - - getRecordColumnValues: function(record) { - var self = this, count = 0, - columnValues = { id: get(record, 'id') }; - - record.get('attributes').forEach(function(key) { - if (count++ > self.attributeLimit) { - return false; - } - var value = get(record, key); - columnValues[key] = value; - }); - return columnValues; - }, - - getRecordKeywords: function(record) { - var keywords = [], keys = Ember.A(['id']); - record.get('attributes').forEach(function(key) { - keys.push(key); - }); - keys.forEach(function(key) { - keywords.push(get(record, key)); - }); - return keywords; - }, - - getRecordFilterValues: function(record) { - return { - isNew: record.get('isNew'), - isModified: record.get('isDirty') && !record.get('isNew'), - isClean: !record.get('isDirty') - }; - }, - - getRecordColor: function(record) { - var color = 'black'; - if (record.get('isNew')) { - color = 'green'; - } else if (record.get('isDirty')) { - color = 'blue'; - } - return color; - }, - - observeRecord: function(record, recordUpdated) { - var releaseMethods = Ember.A(), self = this, - keysToObserve = Ember.A(['id', 'isNew', 'isDirty']); - - record.get('attributes').forEach(function(key) { - keysToObserve.push(key); - }); - - keysToObserve.forEach(function(key) { - var handler = function() { - recordUpdated(self.wrapRecord(record)); - }; - Ember.addObserver(record, key, handler); - releaseMethods.push(function() { - Ember.removeObserver(record, key, handler); - }); - }); - - var release = function() { - releaseMethods.forEach(function(fn) { fn(); } ); - }; - - return release; - } -}); - -Ember.onLoad('Ember.Application', function(Application) { - Application.initializer({ - name: "dataAdapter", - - initialize: function(container, application) { - application.register('dataAdapter:main', DebugAdapter); - } - }); -}); - })(); diff --git a/assets/scripts/vendor/ember.js b/assets/scripts/vendor/ember.js index b70d25b7..7635284e 100644 --- a/assets/scripts/vendor/ember.js +++ b/assets/scripts/vendor/ember.js @@ -1,5 +1,5 @@ -// Version: v1.0.0-53-g0515044 -// Last commit: 0515044 (2013-09-06 06:59:59 -0700) +// Version: v1.0.0-rc.7-59-g4048275 +// Last commit: 4048275 (2013-08-25 00:57:38 -0400) (function() { @@ -156,22 +156,10 @@ Ember.deprecateFunc = function(message, func) { }; }; - -// Inform the developer about the Ember Inspector if not installed. -if (!Ember.testing) { - if (typeof window !== 'undefined' && window.chrome && window.addEventListener) { - window.addEventListener("load", function() { - if (document.body && document.body.dataset && !document.body.dataset.emberExtension) { - Ember.debug('For more advanced debugging, install the Ember Inspector from https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi'); - } - }, false); - } -} - })(); -// Version: v1.0.0-53-g0515044 -// Last commit: 0515044 (2013-09-06 06:59:59 -0700) +// Version: v1.0.0-rc.7-59-g4048275 +// Last commit: 4048275 (2013-08-25 00:57:38 -0400) (function() { @@ -237,7 +225,7 @@ var define, requireModule; @class Ember @static - @version 1.0.0 + @version 1.0.0-rc.7 */ if ('undefined' === typeof Ember) { @@ -264,10 +252,10 @@ Ember.toString = function() { return "Ember"; }; /** @property VERSION @type String - @default '1.0.0' + @default '1.0.0-rc.7' @final */ -Ember.VERSION = '1.0.0'; +Ember.VERSION = '1.0.0-rc.7'; /** Standard environmental variables. You can define these in a global `ENV` @@ -292,27 +280,6 @@ Ember.ENV = Ember.ENV || ENV; Ember.config = Ember.config || {}; -/** - Hash of enabled Canary features. Add to before creating your application. - - @property FEATURES - @type Hash -*/ - -Ember.FEATURES = {}; - -/** - Test that a feature is enabled. Parsed by Ember's build tools to leave - experimental features out of beta/stable builds. - - @method isEnabled - @param {string} feature -*/ - -Ember.FEATURES.isEnabled = function(feature) { - return Ember.FEATURES[feature]; -}; - // .......................................................... // BOOTSTRAP // @@ -365,7 +332,7 @@ Ember.SHIM_ES5 = (Ember.ENV.SHIM_ES5 === false) ? false : Ember.EXTEND_PROTOTYPE Ember.LOG_VERSION = (Ember.ENV.LOG_VERSION === false) ? false : true; /** - Empty function. Useful for some operations. Always returns `this`. + Empty function. Useful for some operations. @method K @private @@ -444,87 +411,11 @@ function assertPolyfill(test, message) { @namespace Ember */ Ember.Logger = { - /** - Logs the arguments to the console. - You can pass as many arguments as you want and they will be joined together with a space. - - ```javascript - var foo = 1; - Ember.Logger.log('log value of foo:', foo); // "log value of foo: 1" will be printed to the console - ``` - - @method log - @for Ember.Logger - @param {*} arguments - */ log: consoleMethod('log') || Ember.K, - /** - Prints the arguments to the console with a warning icon. - You can pass as many arguments as you want and they will be joined together with a space. - - ```javascript - Ember.Logger.warn('Something happened!'); // "Something happened!" will be printed to the console with a warning icon. - ``` - - @method warn - @for Ember.Logger - @param {*} arguments - */ warn: consoleMethod('warn') || Ember.K, - /** - Prints the arguments to the console with an error icon, red text and a stack race. - You can pass as many arguments as you want and they will be joined together with a space. - - ```javascript - Ember.Logger.error('Danger! Danger!'); // "Danger! Danger!" will be printed to the console in red text. - ``` - - @method error - @for Ember.Logger - @param {*} arguments - */ error: consoleMethod('error') || Ember.K, - /** - Logs the arguments to the console. - You can pass as many arguments as you want and they will be joined together with a space. - - ```javascript - var foo = 1; - Ember.Logger.info('log value of foo:', foo); // "log value of foo: 1" will be printed to the console - ``` - - @method info - @for Ember.Logger - @param {*} arguments - */ info: consoleMethod('info') || Ember.K, - /** - Logs the arguments to the console in blue text. - You can pass as many arguments as you want and they will be joined together with a space. - - ```javascript - var foo = 1; - Ember.Logger.debug('log value of foo:', foo); // "log value of foo: 1" will be printed to the console - ``` - - @method debug - @for Ember.Logger - @param {*} arguments - */ debug: consoleMethod('debug') || consoleMethod('info') || Ember.K, - /** - - If the value passed into Ember.Logger.assert is not truthy it will throw an error with a stack trace. - - ```javascript - Ember.Logger.assert(true); // undefined - Ember.Logger.assert(true === false); // Throws an Assertion failed error. - ``` - - @method assert - @for Ember.Logger - @param {Boolean} bool Value to test - */ assert: consoleMethod('assert') || assertPolyfill }; @@ -538,15 +429,6 @@ Ember.Logger = { internals encounter an error. This is useful for specialized error handling and reporting code. - ```javascript - Ember.onerror = function(error) { - Em.$.ajax('/report-error', 'POST', { - stack: error.stack, - otherInformation: 'whatever app state you want to provide' - }); - }; - ``` - @event onerror @for Ember @param {Exception} error the error object @@ -577,21 +459,6 @@ Ember.handleErrors = function(func, context) { } }; -/** - Merge the contents of two objects together into the first object. - - ```javascript - Ember.merge({first: 'Tom'}, {last: 'Dale'}); // {first: 'Tom', last: 'Dale'} - var a = {first: 'Yehuda'}, b = {last: 'Katz'}; - Ember.merge(a, b); // a == {first: 'Yehuda', last: 'Katz'}, b == {last: 'Katz'} - ``` - - @method merge - @for Ember - @param {Object} original The object to merge into - @param {Object} updates The object to copy properties from - @return {Object} -*/ Ember.merge = function(original, updates) { for (var prop in updates) { if (!updates.hasOwnProperty(prop)) { continue; } @@ -894,12 +761,6 @@ var arrayIndexOf = isNativeFunc(Array.prototype.indexOf) ? Array.prototype.index return -1; }; -/** - Array polyfills to support ES5 features in older browsers. - - @namespace Ember - @property ArrayPolyfills -*/ Ember.ArrayPolyfills = { map: arrayMap, forEach: arrayForEach, @@ -1313,18 +1174,10 @@ function canInvoke(obj, methodName) { /** Checks to see if the `methodName` exists on the `obj`. - ```javascript - var foo = {bar: Ember.K, baz: null}; - Ember.canInvoke(foo, 'bar'); // true - Ember.canInvoke(foo, 'baz'); // false - Ember.canInvoke(foo, 'bat'); // false - ``` - @method canInvoke @for Ember @param {Object} obj The object to check for the method @param {String} methodName The method name to check for - @return {Boolean} */ Ember.canInvoke = canInvoke; @@ -1332,13 +1185,6 @@ Ember.canInvoke = canInvoke; Checks to see if the `methodName` exists on the `obj`, and if it does, invokes it with the arguments passed. - ```javascript - var d = new Date('03/15/2013'); - Ember.tryInvoke(d, 'getTime'); // 1363320000000 - Ember.tryInvoke(d, 'setFullYear', [2014]); // 1394856000000 - Ember.tryInvoke(d, 'noSuchMethod', [2014]); // undefined - ``` - @method tryInvoke @for Ember @param {Object} obj The object to check for the method @@ -1370,17 +1216,6 @@ var needsFinallyFix = (function() { Provides try { } finally { } functionality, while working around Safari's double finally bug. - ```javascript - var tryable = function() { - someResource.lock(); - runCallback(); // May throw error. - }; - var finalizer = function() { - someResource.unlock(); - }; - Ember.tryFinally(tryable, finalizer); - ``` - @method tryFinally @for Ember @param {Function} tryable The function to run the try callback @@ -1431,30 +1266,6 @@ if (needsFinallyFix) { Provides try { } catch finally { } functionality, while working around Safari's double finally bug. - ```javascript - var tryable = function() { - for (i=0, l=listeners.length; i size ? size : ends; - if (count <= 0) { count = 0; } - - chunk = args.splice(0, size); - chunk = [start, count].concat(chunk); - - start += size; - ends -= count; - - ret = ret.concat(splice.apply(array, chunk)); - } - return ret; - }, - replace: function(array, idx, amt, objects) { if (array.replace) { return array.replace(idx, amt, objects); } else { - return utils._replace(array, idx, amt, objects); + var args = concat.apply([idx, amt], objects); + return array.splice.apply(array, args); } }, @@ -2536,7 +2296,7 @@ var metaFor = Ember.meta, @param {String} keyName The property key (or path) that will change. @return {void} */ -function propertyWillChange(obj, keyName) { +var propertyWillChange = Ember.propertyWillChange = function(obj, keyName) { var m = metaFor(obj, false), watching = m.watching[keyName] > 0 || keyName === 'length', proto = m.proto, @@ -2548,8 +2308,7 @@ function propertyWillChange(obj, keyName) { dependentKeysWillChange(obj, keyName, m); chainsWillChange(obj, keyName, m); notifyBeforeObservers(obj, keyName); -} -Ember.propertyWillChange = propertyWillChange; +}; /** This function is called just after an object property has changed. @@ -2557,7 +2316,7 @@ Ember.propertyWillChange = propertyWillChange; Normally you will not need to call this method directly but if for some reason you can't directly watch a property you can invoke this method - manually along with `Ember.propertyWillChange()` which you should call just + manually along with `Ember.propertyWilLChange()` which you should call just before the property value changes. @method propertyDidChange @@ -2566,7 +2325,7 @@ Ember.propertyWillChange = propertyWillChange; @param {String} keyName The property key (or path) that will change. @return {void} */ -function propertyDidChange(obj, keyName) { +var propertyDidChange = Ember.propertyDidChange = function(obj, keyName) { var m = metaFor(obj, false), watching = m.watching[keyName] > 0 || keyName === 'length', proto = m.proto, @@ -2579,10 +2338,9 @@ function propertyDidChange(obj, keyName) { if (!watching && keyName !== 'length') { return; } dependentKeysDidChange(obj, keyName, m); - chainsDidChange(obj, keyName, m, false); + chainsDidChange(obj, keyName, m); notifyObservers(obj, keyName); -} -Ember.propertyDidChange = propertyDidChange; +}; var WILL_SEEN, DID_SEEN; @@ -2623,47 +2381,35 @@ function iterDeps(method, obj, depKey, seen, meta) { } } -function chainsWillChange(obj, keyName, m) { - if (!(m.hasOwnProperty('chainWatchers') && - m.chainWatchers[keyName])) { - return; +var chainsWillChange = function(obj, keyName, m, arg) { + if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do + + var nodes = m.chainWatchers; + + nodes = nodes[keyName]; + if (!nodes) { return; } + + nodes = nodes.slice(); + + for(var i = 0, l = nodes.length; i < l; i++) { + nodes[i].willChange(arg); } +}; - var nodes = m.chainWatchers[keyName], - events = [], - i, l; +var chainsDidChange = function(obj, keyName, m, arg) { + if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do - for(i = 0, l = nodes.length; i < l; i++) { - nodes[i].willChange(events); + var nodes = m.chainWatchers; + + nodes = nodes[keyName]; + if (!nodes) { return; } + + nodes = nodes.slice(); + + for(var i = 0, l = nodes.length; i < l; i++) { + nodes[i].didChange(arg); } - - for (i = 0, l = events.length; i < l; i += 2) { - propertyWillChange(events[i], events[i+1]); - } -} - -function chainsDidChange(obj, keyName, m, suppressEvents) { - if (!(m.hasOwnProperty('chainWatchers') && - m.chainWatchers[keyName])) { - return; - } - - var nodes = m.chainWatchers[keyName], - events = suppressEvents ? null : [], - i, l; - - for(i = 0, l = nodes.length; i < l; i++) { - nodes[i].didChange(events); - } - - if (suppressEvents) { - return; - } - - for (i = 0, l = events.length; i < l; i += 2) { - propertyDidChange(events[i], events[i+1]); - } -} +}; Ember.overrideChains = function(obj, keyName, m) { chainsDidChange(obj, keyName, m, true); @@ -2673,24 +2419,20 @@ Ember.overrideChains = function(obj, keyName, m) { @method beginPropertyChanges @chainable */ -function beginPropertyChanges() { +var beginPropertyChanges = Ember.beginPropertyChanges = function() { deferred++; -} - -Ember.beginPropertyChanges = beginPropertyChanges; +}; /** @method endPropertyChanges */ -function endPropertyChanges() { +var endPropertyChanges = Ember.endPropertyChanges = function() { deferred--; if (deferred<=0) { beforeObserverSet.clear(); observerSet.flush(); } -} - -Ember.endPropertyChanges = endPropertyChanges; +}; /** Make a series of property changes together in an @@ -2712,7 +2454,7 @@ Ember.changeProperties = function(cb, binding) { tryFinally(cb, endPropertyChanges, binding); }; -function notifyBeforeObservers(obj, keyName) { +var notifyBeforeObservers = function(obj, keyName) { if (obj.isDestroying) { return; } var eventName = keyName + ':before', listeners, diff; @@ -2723,9 +2465,9 @@ function notifyBeforeObservers(obj, keyName) { } else { sendEvent(obj, eventName, [obj, keyName]); } -} +}; -function notifyObservers(obj, keyName) { +var notifyObservers = function(obj, keyName) { if (obj.isDestroying) { return; } var eventName = keyName + ':change', listeners; @@ -2735,7 +2477,7 @@ function notifyObservers(obj, keyName) { } else { sendEvent(obj, eventName, [obj, keyName]); } -} +}; })(); @@ -2754,7 +2496,7 @@ var META_KEY = Ember.META_KEY, /** Sets the value of a property on an object, respecting computed properties and notifying observers and other listeners of the change. If the - property is not defined but the object implements the `setUnknownProperty` + property is not defined but the object implements the `unknownProperty` method then that will be invoked as well. If you plan to run on IE8 and older browsers then you should use this @@ -2764,7 +2506,7 @@ var META_KEY = Ember.META_KEY, On all newer browsers, you only need to use this method to set properties if the property might not be defined on the object and you want - to respect the `setUnknownProperty` handler. Otherwise you can ignore this + to respect the `unknownProperty` handler. Otherwise you can ignore this method. @method set @@ -3423,47 +3165,6 @@ Ember.defineProperty = function(obj, keyName, desc, data, meta) { -(function() { -var get = Ember.get; - -/** - To get multiple properties at once, call `Ember.getProperties` - with an object followed by a list of strings or an array: - - ```javascript - Ember.getProperties(record, 'firstName', 'lastName', 'zipCode'); // { firstName: 'John', lastName: 'Doe', zipCode: '10011' } - ``` - - is equivalent to: - - ```javascript - Ember.getProperties(record, ['firstName', 'lastName', 'zipCode']); // { firstName: 'John', lastName: 'Doe', zipCode: '10011' } - ``` - - @method getProperties - @param obj - @param {String...|Array} list of keys to get - @return {Hash} -*/ -Ember.getProperties = function(obj) { - var ret = {}, - propertyNames = arguments, - i = 1; - - if (arguments.length === 2 && Ember.typeOf(arguments[1]) === 'array') { - i = 0; - propertyNames = arguments[1]; - } - for(var len = propertyNames.length; i < len; i++) { - ret[propertyNames[i]] = get(obj, propertyNames[i]); - } - return ret; -}; - -})(); - - - (function() { var changeProperties = Ember.changeProperties, set = Ember.set; @@ -3473,14 +3174,6 @@ var changeProperties = Ember.changeProperties, a single `beginPropertyChanges` and `endPropertyChanges` batch, so observers will be buffered. - ```javascript - anObject.setProperties({ - firstName: "Stanley", - lastName: "Stuart", - age: "21" - }) - ``` - @method setProperties @param self @param {Object} hash @@ -3570,6 +3263,8 @@ var metaFor = Ember.meta, // utils.js warn = Ember.warn, watchKey = Ember.watchKey, unwatchKey = Ember.unwatchKey, + propertyWillChange = Ember.propertyWillChange, + propertyDidChange = Ember.propertyDidChange, FIRST_KEY = /^([^\.\*]+)/; function firstKey(path) { @@ -3803,50 +3498,42 @@ ChainNodePrototype.unchain = function(key, path) { }; -ChainNodePrototype.willChange = function(events) { +ChainNodePrototype.willChange = function() { var chains = this._chains; if (chains) { for(var key in chains) { if (!chains.hasOwnProperty(key)) { continue; } - chains[key].willChange(events); + chains[key].willChange(); } } - if (this._parent) { this._parent.chainWillChange(this, this._key, 1, events); } + if (this._parent) { this._parent.chainWillChange(this, this._key, 1); } }; -ChainNodePrototype.chainWillChange = function(chain, path, depth, events) { +ChainNodePrototype.chainWillChange = function(chain, path, depth) { if (this._key) { path = this._key + '.' + path; } if (this._parent) { - this._parent.chainWillChange(this, path, depth+1, events); + this._parent.chainWillChange(this, path, depth+1); } else { - if (depth > 1) { - events.push(this.value(), path); - } + if (depth > 1) { propertyWillChange(this.value(), path); } path = 'this.' + path; - if (this._paths[path] > 0) { - events.push(this.value(), path); - } + if (this._paths[path] > 0) { propertyWillChange(this.value(), path); } } }; -ChainNodePrototype.chainDidChange = function(chain, path, depth, events) { +ChainNodePrototype.chainDidChange = function(chain, path, depth) { if (this._key) { path = this._key + '.' + path; } if (this._parent) { - this._parent.chainDidChange(this, path, depth+1, events); + this._parent.chainDidChange(this, path, depth+1); } else { - if (depth > 1) { - events.push(this.value(), path); - } + if (depth > 1) { propertyDidChange(this.value(), path); } path = 'this.' + path; - if (this._paths[path] > 0) { - events.push(this.value(), path); - } + if (this._paths[path] > 0) { propertyDidChange(this.value(), path); } } }; -ChainNodePrototype.didChange = function(events) { +ChainNodePrototype.didChange = function(suppressEvent) { // invalidate my own value first. if (this._watching) { var obj = this._parent.value(); @@ -3868,15 +3555,14 @@ ChainNodePrototype.didChange = function(events) { if (chains) { for(var key in chains) { if (!chains.hasOwnProperty(key)) { continue; } - chains[key].didChange(events); + chains[key].didChange(suppressEvent); } } - // if no events are passed in then we only care about the above wiring update - if (events === null) { return; } + if (suppressEvent) { return; } // and finally tell parent about my path changing... - if (this._parent) { this._parent.chainDidChange(this, this._key, 1, events); } + if (this._parent) { this._parent.chainDidChange(this, this._key, 1); } }; Ember.finishChains = function(obj) { @@ -3885,10 +3571,9 @@ Ember.finishChains = function(obj) { if (chains.value() !== obj) { m.chains = chains = chains.copy(obj); } - chains.didChange(null); + chains.didChange(true); } }; - })(); @@ -4169,81 +3854,6 @@ function removeDependentKeys(desc, obj, keyName, meta) { // /** - A computed property transforms an objects function into a property. - - By default the function backing the computed property will only be called - once and the result will be cached. You can specify various properties - that your computed property is dependent on. This will force the cached - result to be recomputed if the dependencies are modified. - - In the following example we declare a computed property (by calling - `.property()` on the fullName function) and setup the properties - dependencies (depending on firstName and lastName). The fullName function - will be called once (regardless of how many times it is accessed) as long - as it's dependencies have not been changed. Once firstName or lastName are updated - any future calls (or anything bound) to fullName will incorporate the new - values. - - ```javascript - Person = Ember.Object.extend({ - // these will be supplied by `create` - firstName: null, - lastName: null, - - fullName: function() { - var firstName = this.get('firstName'); - var lastName = this.get('lastName'); - - return firstName + ' ' + lastName; - }.property('firstName', 'lastName') - }); - - var tom = Person.create({ - firstName: "Tom", - lastName: "Dale" - }); - - tom.get('fullName') // "Tom Dale" - ``` - - You can also define what Ember should do when setting a computed property. - If you try to set a computed property, it will be invoked with the key and - value you want to set it to. You can also accept the previous value as the - third parameter. - - ```javascript - - Person = Ember.Object.extend({ - // these will be supplied by `create` - firstName: null, - lastName: null, - - fullName: function(key, value, oldValue) { - // getter - if (arguments.length === 1) { - var firstName = this.get('firstName'); - var lastName = this.get('lastName'); - - return firstName + ' ' + lastName; - - // setter - } else { - var name = value.split(" "); - - this.set('firstName', name[0]); - this.set('lastName', name[1]); - - return value; - } - }.property('firstName', 'lastName') - }); - - var person = Person.create(); - person.set('fullName', "Peter Wagenet"); - person.get('firstName') // Peter - person.get('lastName') // Wagenet - ``` - @class ComputedProperty @namespace Ember @extends Ember.Descriptor @@ -4262,7 +3872,7 @@ ComputedProperty.prototype = new Ember.Descriptor(); var ComputedPropertyPrototype = ComputedProperty.prototype; -/** +/* Properties are cacheable by default. Computed property will automatically cache the return value of your function until one of the dependent keys changes. @@ -4404,37 +4014,11 @@ ComputedPropertyPrototype.didChange = function(obj, keyName) { function finishChains(chainNodes) { for (var i=0, l=chainNodes.length; i= 0) || + // impl super if needed... + if (isMethod(value)) { + value = giveMethodSuper(base, key, value, values, descs); + } else if ((concats && a_indexOf.call(concats, key) >= 0) || key === 'concatenatedProperties' || key === 'mergedProperties') { value = applyConcatenatedProperties(base, key, value, values); } else if ((mergings && a_indexOf.call(mergings, key) >= 0)) { value = applyMergedProperties(base, key, value, values); - } else if (isMethod(value)) { - value = giveMethodSuper(base, key, value, values, descs); } descs[key] = undefined; @@ -7072,7 +6370,6 @@ function mergeMixins(mixins, m, descs, values, base, keys) { if (props) { meta = Ember.meta(base); - if (base.willMergeMixin) { base.willMergeMixin(props); } concats = concatenatedMixinProperties('concatenatedProperties', props, values, base); mergings = concatenatedMixinProperties('mergedProperties', props, values, base); @@ -7246,7 +6543,7 @@ Ember.mixin = function(obj) { // Mix mixins into classes by passing them as the first arguments to // .extend. App.CommentView = Ember.View.extend(App.Editable, { - template: Ember.Handlebars.compile('{{#if view.isEditing}}...{{else}}...{{/if}}') + template: Ember.Handlebars.compile('{{#if isEditing}}...{{else}}...{{/if}}') }); commentView = App.CommentView.create(); @@ -7256,31 +6553,6 @@ Ember.mixin = function(obj) { Note that Mixins are created with `Ember.Mixin.create`, not `Ember.Mixin.extend`. - Note that mixins extend a constructor's prototype so arrays and object literals - defined as properties will be shared amongst objects that implement the mixin. - If you want to define an property in a mixin that is not shared, you can define - it either as a computed property or have it be created on initialization of the object. - - ```javascript - //filters array will be shared amongst any object implementing mixin - App.Filterable = Ember.Mixin.create({ - filters: Ember.A() - }); - - //filters will be a separate array for every object implementing the mixin - App.Filterable = Ember.Mixin.create({ - filters: Ember.computed(function(){return Ember.A();}) - }); - - //filters will be created as a separate array during the object's initialization - App.Filterable = Ember.Mixin.create({ - init: function() { - this._super(); - this.set("filters", Ember.A()); - } - }); - ``` - @class Mixin @namespace Ember */ @@ -7516,22 +6788,6 @@ Ember.aliasMethod = function(methodName) { // /** - Specify a method that observes property changes. - - ```javascript - Ember.Object.extend({ - valueObserver: Ember.observer(function() { - // Executes whenever the "value" property changes - }, 'value') - }); - ``` - - In the future this method may become asynchronous. If you want to ensure - synchronous behavior, use `immediateObserver`. - - Also available as `Function.prototype.observes` if prototype extensions are - enabled. - @method observer @for Ember @param {Function} func @@ -7544,23 +6800,9 @@ Ember.observer = function(func) { return func; }; +// If observers ever become asynchronous, Ember.immediateObserver +// must remain synchronous. /** - Specify a method that observes property changes. - - ```javascript - Ember.Object.extend({ - valueObserver: Ember.immediateObserver(function() { - // Executes whenever the "value" property changes - }, 'value') - }); - ``` - - In the future, `Ember.observer` may become asynchronous. In this event, - `Ember.immediateObserver` will maintain the synchronous behavior. - - Also available as `Function.prototype.observesImmediately` if prototype extensions are - enabled. - @method immediateObserver @for Ember @param {Function} func @@ -7588,31 +6830,24 @@ Ember.immediateObserver = function() { ```javascript App.PersonView = Ember.View.extend({ - friends: [{ name: 'Tom' }, { name: 'Stefan' }, { name: 'Kris' }], - - valueWillChange: Ember.beforeObserver(function(obj, keyName) { + valueWillChange: function (obj, keyName) { this.changingFrom = obj.get(keyName); - }, 'content.value'), - - valueDidChange: Ember.observer(function(obj, keyName) { + }.observesBefore('content.value'), + valueDidChange: function(obj, keyName) { // only run if updating a value already in the DOM if (this.get('state') === 'inDOM') { - var color = obj.get(keyName) > this.changingFrom ? 'green' : 'red'; - // logic + var color = obj.get(keyName) > this.changingFrom ? 'green' : 'red'; + // logic } - }, 'content.value'), - - friendsDidChange: Ember.observer(function(obj, keyName) { + }.observes('content.value'), + friendsDidChange: function(obj, keyName) { // some logic // obj.get(keyName) returns friends array - }, 'friends.@each.name') + }.observes('friends.@each.name') }); ``` - Also available as `Function.prototype.observesBefore` if prototype extensions are - enabled. - @method beforeObserver @for Ember @param {Function} func @@ -7629,52 +6864,6 @@ Ember.beforeObserver = function(func) { -(function() { -// Provides a way to register library versions with ember. - -Ember.libraries = function() { - var libraries = []; - var coreLibIndex = 0; - - var getLibrary = function(name) { - for (var i = 0; i < libraries.length; i++) { - if (libraries[i].name === name) { - return libraries[i]; - } - } - }; - - libraries.register = function(name, version) { - if (!getLibrary(name)) { - libraries.push({name: name, version: version}); - } - }; - - libraries.registerCoreLibrary = function(name, version) { - if (!getLibrary(name)) { - libraries.splice(coreLibIndex++, 0, {name: name, version: version}); - } - }; - - libraries.deRegister = function(name) { - var lib = getLibrary(name); - if (lib) libraries.splice(libraries.indexOf(lib), 1); - }; - - libraries.each = function (callback) { - libraries.forEach(function(lib) { - callback(lib.name, lib.version); - }); - }; - return libraries; -}(); - -Ember.libraries.registerCoreLibrary('Ember', Ember.VERSION); - -})(); - - - (function() { /** Ember Metal @@ -7742,7 +6931,6 @@ define("rsvp/async", var browserGlobal = (typeof window !== 'undefined') ? window : {}; var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver; var async; - var local = (typeof global !== 'undefined') ? global : this; // old node function useNextTick() { @@ -7793,7 +6981,7 @@ define("rsvp/async", function useSetTimeout() { return function(callback, arg) { - local.setTimeout(function() { + setTimeout(function() { callback(arg); }, 1); }; @@ -8176,10 +7364,6 @@ define("rsvp/promise", }); return thenPromise; - }, - - fail: function(fail) { - return this.then(null, fail); } }; @@ -8282,36 +7466,19 @@ define("rsvp/resolve", __exports__.resolve = resolve; }); -define("rsvp/rethrow", - ["exports"], - function(__exports__) { - "use strict"; - var local = (typeof global === "undefined") ? this : global; - - function rethrow(reason) { - local.setTimeout(function() { - throw reason; - }); - throw reason; - } - - - __exports__.rethrow = rethrow; - }); define("rsvp", - ["rsvp/events","rsvp/promise","rsvp/node","rsvp/all","rsvp/hash","rsvp/rethrow","rsvp/defer","rsvp/config","rsvp/resolve","rsvp/reject","exports"], - function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __exports__) { + ["rsvp/events","rsvp/promise","rsvp/node","rsvp/all","rsvp/hash","rsvp/defer","rsvp/config","rsvp/resolve","rsvp/reject","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __exports__) { "use strict"; var EventTarget = __dependency1__.EventTarget; var Promise = __dependency2__.Promise; var denodeify = __dependency3__.denodeify; var all = __dependency4__.all; var hash = __dependency5__.hash; - var rethrow = __dependency6__.rethrow; - var defer = __dependency7__.defer; - var config = __dependency8__.config; - var resolve = __dependency9__.resolve; - var reject = __dependency10__.reject; + var defer = __dependency6__.defer; + var config = __dependency7__.config; + var resolve = __dependency8__.resolve; + var reject = __dependency9__.reject; function configure(name, value) { config[name] = value; @@ -8322,30 +7489,16 @@ define("rsvp", __exports__.EventTarget = EventTarget; __exports__.all = all; __exports__.hash = hash; - __exports__.rethrow = rethrow; __exports__.defer = defer; __exports__.denodeify = denodeify; __exports__.configure = configure; __exports__.resolve = resolve; __exports__.reject = reject; }); + })(); (function() { -/** -@private -Public api for the container is still in flux. -The public api, specified on the application namespace should be considered the stable api. -// @module container -*/ - -/* - Flag to enable/disable model factory injections (disabled by default) - If model factory injections are enabled, models should not be - accessed globally (only through `container.lookupFactory('model:modelName'))`); -*/ -Ember.MODEL_FACTORY_INJECTIONS = false || !!Ember.ENV.MODEL_FACTORY_INJECTIONS; - define("container", [], function() { @@ -8353,7 +7506,7 @@ define("container", /** A safe and simple inheriting object. - // @class InheritingDict + @class InheritingDict */ function InheritingDict(parent) { this.parent = parent; @@ -8427,7 +7580,7 @@ define("container", @method has @param {String} key - @return {Boolean} + @returns {Boolean} */ has: function(key) { var dict = this.dict; @@ -8464,10 +7617,7 @@ define("container", /** A lightweight container that helps to assemble and decouple components. - @private - Public api for the container is still in flux. - The public api, specified on the application namespace should be considered the stable api. - // @class Container + @class Container */ function Container(parent) { this.parent = parent; @@ -8557,7 +7707,7 @@ define("container", to correctly inherit from the current container. @method child - @return {Container} + @returns {Container} */ child: function() { var container = new Container(this); @@ -8627,7 +7777,6 @@ define("container", container.unregister('model:user') container.lookup('model:user') === undefined //=> true - ``` @method unregister @param {String} fullName @@ -8637,7 +7786,6 @@ define("container", this.registry.remove(normalizedName); this.cache.remove(normalizedName); - this.factoryCache.remove(normalizedName); this._options.remove(normalizedName); }, @@ -8671,7 +7819,7 @@ define("container", @method resolve @param {String} fullName - @return {Function} fullName's factory + @returns {Function} fullName's factory */ resolve: function(fullName) { return this.resolver(fullName) || this.registry.get(fullName); @@ -8702,17 +7850,6 @@ define("container", return fullName; }, - /** - @method makeToString - - @param {any} factory - @param {string} fullNae - @return {function} toString function - */ - makeToString: function(factory, fullName) { - return factory.toString(); - }, - /** Given a fullName return a corresponding instance. @@ -8841,7 +7978,7 @@ define("container", this.optionsForType(type, options); }, - /** + /* @private Used only via `injection`. @@ -8883,7 +8020,7 @@ define("container", addTypeInjection(this.typeInjections, type, property, fullName); }, - /** + /* Defines injection rules. These rules are used to inject dependencies onto objects when they @@ -8891,8 +8028,8 @@ define("container", Two forms of injections are possible: - * Injecting one fullName on another fullName - * Injecting one fullName on a type + * Injecting one fullName on another fullName + * Injecting one fullName on a type Example: @@ -8938,7 +8075,7 @@ define("container", }, - /** + /* @private Used only via `factoryInjection`. @@ -8975,7 +8112,7 @@ define("container", addTypeInjection(this.factoryTypeInjections, type, property, fullName); }, - /** + /* Defines factory injection rules. Similar to regular injection rules, but are run against factories, via @@ -8986,8 +8123,8 @@ define("container", Two forms of injections are possible: - * Injecting one fullName on another fullName - * Injecting one fullName on a type + * Injecting one fullName on another fullName + * Injecting one fullName on a type Example: @@ -9119,7 +8256,6 @@ define("container", var factory = container.resolve(name); var injectedFactory; var cache = container.factoryCache; - var type = fullName.split(":")[0]; if (!factory) { return; } @@ -9127,19 +8263,13 @@ define("container", return cache.get(fullName); } - if (typeof factory.extend !== 'function' || (!Ember.MODEL_FACTORY_INJECTIONS && type === 'model')) { + if (typeof factory.extend !== 'function') { // TODO: think about a 'safe' merge style extension // for now just fallback to create time injection return factory; } else { - - var injections = injectionsFor(container, fullName); - var factoryInjections = factoryInjectionsFor(container, fullName); - - factoryInjections._toString = container.makeToString(factory, fullName); - - injectedFactory = factory.extend(injections); - injectedFactory.reopenClass(factoryInjections); + injectedFactory = factory.extend(injectionsFor(container, fullName)); + injectedFactory.reopenClass(factoryInjectionsFor(container, fullName)); cache.set(fullName, injectedFactory); @@ -9546,6 +8676,557 @@ Ember.Error.prototype = Ember.create(Error.prototype); +(function() { +/** + Expose RSVP implementation + + Documentation can be found here: https://github.com/tildeio/rsvp.js/blob/master/README.md + + @class RSVP + @namespace Ember + @constructor +*/ +Ember.RSVP = requireModule('rsvp'); + +})(); + + + +(function() { +/** +@module ember +@submodule ember-runtime +*/ + +var STRING_DASHERIZE_REGEXP = (/[ _]/g); +var STRING_DASHERIZE_CACHE = {}; +var STRING_DECAMELIZE_REGEXP = (/([a-z])([A-Z])/g); +var STRING_CAMELIZE_REGEXP = (/(\-|_|\.|\s)+(.)?/g); +var STRING_UNDERSCORE_REGEXP_1 = (/([a-z\d])([A-Z]+)/g); +var STRING_UNDERSCORE_REGEXP_2 = (/\-|\s+/g); + +/** + Defines the hash of localized strings for the current language. Used by + the `Ember.String.loc()` helper. To localize, add string values to this + hash. + + @property STRINGS + @for Ember + @type Hash +*/ +Ember.STRINGS = {}; + +/** + Defines string helper methods including string formatting and localization. + Unless `Ember.EXTEND_PROTOTYPES.String` is `false` these methods will also be + added to the `String.prototype` as well. + + @class String + @namespace Ember + @static +*/ +Ember.String = { + + /** + Apply formatting options to the string. This will look for occurrences + of "%@" in your string and substitute them with the arguments you pass into + this method. If you want to control the specific order of replacement, + you can add a number after the key as well to indicate which argument + you want to insert. + + Ordered insertions are most useful when building loc strings where values + you need to insert may appear in different orders. + + ```javascript + "Hello %@ %@".fmt('John', 'Doe'); // "Hello John Doe" + "Hello %@2, %@1".fmt('John', 'Doe'); // "Hello Doe, John" + ``` + + @method fmt + @param {String} str The string to format + @param {Array} formats An array of parameters to interpolate into string. + @return {String} formatted string + */ + fmt: function(str, formats) { + // first, replace any ORDERED replacements. + var idx = 0; // the current index for non-numerical replacements + return str.replace(/%@([0-9]+)?/g, function(s, argIndex) { + argIndex = (argIndex) ? parseInt(argIndex, 10) - 1 : idx++; + s = formats[argIndex]; + return (s === null) ? '(null)' : (s === undefined) ? '' : Ember.inspect(s); + }) ; + }, + + /** + Formats the passed string, but first looks up the string in the localized + strings hash. This is a convenient way to localize text. See + `Ember.String.fmt()` for more information on formatting. + + Note that it is traditional but not required to prefix localized string + keys with an underscore or other character so you can easily identify + localized strings. + + ```javascript + Ember.STRINGS = { + '_Hello World': 'Bonjour le monde', + '_Hello %@ %@': 'Bonjour %@ %@' + }; + + Ember.String.loc("_Hello World"); // 'Bonjour le monde'; + Ember.String.loc("_Hello %@ %@", ["John", "Smith"]); // "Bonjour John Smith"; + ``` + + @method loc + @param {String} str The string to format + @param {Array} formats Optional array of parameters to interpolate into string. + @return {String} formatted string + */ + loc: function(str, formats) { + str = Ember.STRINGS[str] || str; + return Ember.String.fmt(str, formats) ; + }, + + /** + Splits a string into separate units separated by spaces, eliminating any + empty strings in the process. This is a convenience method for split that + is mostly useful when applied to the `String.prototype`. + + ```javascript + Ember.String.w("alpha beta gamma").forEach(function(key) { + console.log(key); + }); + + // > alpha + // > beta + // > gamma + ``` + + @method w + @param {String} str The string to split + @return {String} split string + */ + w: function(str) { return str.split(/\s+/); }, + + /** + Converts a camelized string into all lower case separated by underscores. + + ```javascript + 'innerHTML'.decamelize(); // 'inner_html' + 'action_name'.decamelize(); // 'action_name' + 'css-class-name'.decamelize(); // 'css-class-name' + 'my favorite items'.decamelize(); // 'my favorite items' + ``` + + @method decamelize + @param {String} str The string to decamelize. + @return {String} the decamelized string. + */ + decamelize: function(str) { + return str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase(); + }, + + /** + Replaces underscores or spaces with dashes. + + ```javascript + 'innerHTML'.dasherize(); // 'inner-html' + 'action_name'.dasherize(); // 'action-name' + 'css-class-name'.dasherize(); // 'css-class-name' + 'my favorite items'.dasherize(); // 'my-favorite-items' + ``` + + @method dasherize + @param {String} str The string to dasherize. + @return {String} the dasherized string. + */ + dasherize: function(str) { + var cache = STRING_DASHERIZE_CACHE, + hit = cache.hasOwnProperty(str), + ret; + + if (hit) { + return cache[str]; + } else { + ret = Ember.String.decamelize(str).replace(STRING_DASHERIZE_REGEXP,'-'); + cache[str] = ret; + } + + return ret; + }, + + /** + Returns the lowerCamelCase form of a string. + + ```javascript + 'innerHTML'.camelize(); // 'innerHTML' + 'action_name'.camelize(); // 'actionName' + 'css-class-name'.camelize(); // 'cssClassName' + 'my favorite items'.camelize(); // 'myFavoriteItems' + 'My Favorite Items'.camelize(); // 'myFavoriteItems' + ``` + + @method camelize + @param {String} str The string to camelize. + @return {String} the camelized string. + */ + camelize: function(str) { + return str.replace(STRING_CAMELIZE_REGEXP, function(match, separator, chr) { + return chr ? chr.toUpperCase() : ''; + }).replace(/^([A-Z])/, function(match, separator, chr) { + return match.toLowerCase(); + }); + }, + + /** + Returns the UpperCamelCase form of a string. + + ```javascript + 'innerHTML'.classify(); // 'InnerHTML' + 'action_name'.classify(); // 'ActionName' + 'css-class-name'.classify(); // 'CssClassName' + 'my favorite items'.classify(); // 'MyFavoriteItems' + ``` + + @method classify + @param {String} str the string to classify + @return {String} the classified string + */ + classify: function(str) { + var parts = str.split("."), + out = []; + + for (var i=0, l=parts.length; i Ember.TrackedArray instances. We use - // this to lazily recompute indexes for item property observers. - this.trackedArraysByGuid = {}; - - // This is used to coalesce item changes from property observers. - this.changedItems = {}; -} - -function ItemPropertyObserverContext (dependentArray, index, trackedArray) { - Ember.assert("Internal error: trackedArray is null or undefined", trackedArray); - - this.dependentArray = dependentArray; - this.index = index; - this.item = dependentArray.objectAt(index); - this.trackedArray = trackedArray; - this.beforeObserver = null; - this.observer = null; -} - -DependentArraysObserver.prototype = { - setValue: function (newValue) { - this.instanceMeta.setValue(newValue); - }, - getValue: function () { - return this.instanceMeta.getValue(); - }, - - setupObservers: function (dependentArray, dependentKey) { - Ember.assert("dependent array must be an `Ember.Array`", Ember.Array.detect(dependentArray)); - - this.dependentKeysByGuid[guidFor(dependentArray)] = dependentKey; - - dependentArray.addArrayObserver(this, { - willChange: 'dependentArrayWillChange', - didChange: 'dependentArrayDidChange' - }); - - if (this.cp._itemPropertyKeys[dependentKey]) { - this.setupPropertyObservers(dependentKey, this.cp._itemPropertyKeys[dependentKey]); - } - }, - - teardownObservers: function (dependentArray, dependentKey) { - var itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey] || []; - - delete this.dependentKeysByGuid[guidFor(dependentArray)]; - - this.teardownPropertyObservers(dependentKey, itemPropertyKeys); - - dependentArray.removeArrayObserver(this, { - willChange: 'dependentArrayWillChange', - didChange: 'dependentArrayDidChange' - }); - }, - - setupPropertyObservers: function (dependentKey, itemPropertyKeys) { - var dependentArray = get(this.instanceMeta.context, dependentKey), - length = get(dependentArray, 'length'), - observerContexts = new Array(length); - - this.resetTransformations(dependentKey, observerContexts); - - forEach(dependentArray, function (item, index) { - var observerContext = this.createPropertyObserverContext(dependentArray, index, this.trackedArraysByGuid[dependentKey]); - observerContexts[index] = observerContext; - - forEach(itemPropertyKeys, function (propertyKey) { - addBeforeObserver(item, propertyKey, this, observerContext.beforeObserver); - addObserver(item, propertyKey, this, observerContext.observer); - }, this); - }, this); - }, - - teardownPropertyObservers: function (dependentKey, itemPropertyKeys) { - var dependentArrayObserver = this, - trackedArray = this.trackedArraysByGuid[dependentKey], - beforeObserver, - observer, - item; - - if (!trackedArray) { return; } - - trackedArray.apply(function (observerContexts, offset, operation) { - if (operation === Ember.TrackedArray.DELETE) { return; } - - forEach(observerContexts, function (observerContext) { - beforeObserver = observerContext.beforeObserver; - observer = observerContext.observer; - item = observerContext.item; - - forEach(itemPropertyKeys, function (propertyKey) { - removeBeforeObserver(item, propertyKey, dependentArrayObserver, beforeObserver); - removeObserver(item, propertyKey, dependentArrayObserver, observer); - }); - }); - }); - }, - - createPropertyObserverContext: function (dependentArray, index, trackedArray) { - var observerContext = new ItemPropertyObserverContext(dependentArray, index, trackedArray); - - this.createPropertyObserver(observerContext); - - return observerContext; - }, - - createPropertyObserver: function (observerContext) { - var dependentArrayObserver = this; - - observerContext.beforeObserver = function (obj, keyName) { - dependentArrayObserver.updateIndexes(observerContext.trackedArray, observerContext.dependentArray); - return dependentArrayObserver.itemPropertyWillChange(obj, keyName, observerContext.dependentArray, observerContext.index); - }; - observerContext.observer = function (obj, keyName) { - return dependentArrayObserver.itemPropertyDidChange(obj, keyName, observerContext.dependentArray, observerContext.index); - }; - }, - - resetTransformations: function (dependentKey, observerContexts) { - this.trackedArraysByGuid[dependentKey] = new Ember.TrackedArray(observerContexts); - }, - - addTransformation: function (dependentKey, index, newItems) { - var trackedArray = this.trackedArraysByGuid[dependentKey]; - if (trackedArray) { - trackedArray.addItems(index, newItems); - } - }, - - removeTransformation: function (dependentKey, index, removedCount) { - var trackedArray = this.trackedArraysByGuid[dependentKey]; - - if (trackedArray) { - return trackedArray.removeItems(index, removedCount); - } - - return []; - }, - - updateIndexes: function (trackedArray, array) { - var length = get(array, 'length'); - // OPTIMIZE: we could stop updating once we hit the object whose observer - // fired; ie partially apply the transformations - trackedArray.apply(function (observerContexts, offset, operation) { - // we don't even have observer contexts for removed items, even if we did, - // they no longer have any index in the array - if (operation === Ember.TrackedArray.DELETE) { return; } - if (operation === Ember.TrackedArray.RETAIN && observerContexts.length === length && offset === 0) { - // If we update many items we don't want to walk the array each time: we - // only need to update the indexes at most once per run loop. - return; - } - - forEach(observerContexts, function (context, index) { - context.index = index + offset; - }); - }); - }, - - dependentArrayWillChange: function (dependentArray, index, removedCount, addedCount) { - var removedItem = this.callbacks.removedItem, - changeMeta, - guid = guidFor(dependentArray), - dependentKey = this.dependentKeysByGuid[guid], - itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey] || [], - item, - itemIndex, - sliceIndex, - observerContexts; - - observerContexts = this.removeTransformation(dependentKey, index, removedCount); - - - function removeObservers(propertyKey) { - removeBeforeObserver(item, propertyKey, this, observerContexts[sliceIndex].beforeObserver); - removeObserver(item, propertyKey, this, observerContexts[sliceIndex].observer); - } - - for (sliceIndex = removedCount - 1; sliceIndex >= 0; --sliceIndex) { - itemIndex = index + sliceIndex; - item = dependentArray.objectAt(itemIndex); - - forEach(itemPropertyKeys, removeObservers, this); - - changeMeta = createChangeMeta(dependentArray, item, itemIndex, this.instanceMeta.propertyName, this.cp); - this.setValue( removedItem.call( - this.instanceMeta.context, this.getValue(), item, changeMeta, this.instanceMeta.sugarMeta)); - } - }, - - dependentArrayDidChange: function (dependentArray, index, removedCount, addedCount) { - var addedItem = this.callbacks.addedItem, - guid = guidFor(dependentArray), - dependentKey = this.dependentKeysByGuid[guid], - observerContexts = new Array(addedCount), - itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey], - changeMeta, - observerContext; - - forEach(dependentArray.slice(index, index + addedCount), function (item, sliceIndex) { - if (itemPropertyKeys) { - observerContext = - observerContexts[sliceIndex] = - this.createPropertyObserverContext(dependentArray, index + sliceIndex, this.trackedArraysByGuid[dependentKey]); - forEach(itemPropertyKeys, function (propertyKey) { - addBeforeObserver(item, propertyKey, this, observerContext.beforeObserver); - addObserver(item, propertyKey, this, observerContext.observer); - }, this); - } - - changeMeta = createChangeMeta(dependentArray, item, index + sliceIndex, this.instanceMeta.propertyName, this.cp); - this.setValue( addedItem.call( - this.instanceMeta.context, this.getValue(), item, changeMeta, this.instanceMeta.sugarMeta)); - }, this); - - this.addTransformation(dependentKey, index, observerContexts); - }, - - itemPropertyWillChange: function (obj, keyName, array, index) { - var guid = guidFor(obj); - - if (!this.changedItems[guid]) { - this.changedItems[guid] = { - array: array, - index: index, - obj: obj, - previousValues: {} - }; - } - - this.changedItems[guid].previousValues[keyName] = get(obj, keyName); - }, - - itemPropertyDidChange: function(obj, keyName, array, index) { - Ember.run.once(this, 'flushChanges'); - }, - - flushChanges: function() { - var changedItems = this.changedItems, key, c, changeMeta; - for (key in changedItems) { - c = changedItems[key]; - changeMeta = createChangeMeta(c.array, c.obj, c.index, this.instanceMeta.propertyName, this.cp, c.previousValues); - this.setValue( - this.callbacks.removedItem.call(this.instanceMeta.context, this.getValue(), c.obj, changeMeta, this.instanceMeta.sugarMeta)); - this.setValue( - this.callbacks.addedItem.call(this.instanceMeta.context, this.getValue(), c.obj, changeMeta, this.instanceMeta.sugarMeta)); - } - this.changedItems = {}; - } -}; - -function createChangeMeta(dependentArray, item, index, propertyName, property, previousValues) { - var meta = { - arrayChanged: dependentArray, - index: index, - item: item, - propertyName: propertyName, - property: property - }; - - if (previousValues) { - // previous values only available for item property changes - meta.previousValues = previousValues; - } - - return meta; -} - -function addItems (dependentArray, callbacks, cp, propertyName, meta) { - forEach(dependentArray, function (item, index) { - meta.setValue( callbacks.addedItem.call( - this, meta.getValue(), item, createChangeMeta(dependentArray, item, index, propertyName, cp), meta.sugarMeta)); - }, this); -} - -function reset(cp, propertyName) { - var callbacks = cp._callbacks(), - meta; - - if (cp._hasInstanceMeta(this, propertyName)) { - meta = cp._instanceMeta(this, propertyName); - meta.setValue(cp.resetValue(meta.getValue())); - } else { - meta = cp._instanceMeta(this, propertyName); - } - - if (cp.options.initialize) { - cp.options.initialize.call(this, meta.getValue(), { property: cp, propertyName: propertyName }, meta.sugarMeta); - } -} - -function ReduceComputedPropertyInstanceMeta(context, propertyName, initialValue) { - this.context = context; - this.propertyName = propertyName; - this.cache = metaFor(context).cache; - - this.dependentArrays = {}; - this.sugarMeta = {}; - - this.initialValue = initialValue; -} - -ReduceComputedPropertyInstanceMeta.prototype = { - getValue: function () { - if (this.propertyName in this.cache) { - return this.cache[this.propertyName]; - } else { - return this.initialValue; - } - }, - - setValue: function(newValue) { - // This lets sugars force a recomputation, handy for very simple - // implementations of eg max. - if (newValue !== undefined) { - this.cache[this.propertyName] = newValue; - } else { - delete this.cache[this.propertyName]; - } - } -}; - -/** - A computed property whose dependent keys are arrays and which is updated with - "one at a time" semantics. - - @class ReduceComputedProperty - @namespace Ember - @extends Ember.ComputedProperty - @constructor -*/ -function ReduceComputedProperty(options) { - var cp = this; - - this.options = options; - this._instanceMetas = {}; - - this._dependentKeys = null; - // A map of dependentKey -> [itemProperty, ...] that tracks what properties of - // items in the array we must track to update this property. - this._itemPropertyKeys = {}; - this._previousItemPropertyKeys = {}; - - this.readOnly(); - this.cacheable(); - - this.recomputeOnce = function(propertyName) { - // What we really want to do is coalesce by . - // We need a form of `scheduleOnce` that accepts an arbitrary token to - // coalesce by, in addition to the target and method. - Ember.run.once(this, recompute, propertyName); - }; - var recompute = function(propertyName) { - var dependentKeys = cp._dependentKeys, - meta = cp._instanceMeta(this, propertyName), - callbacks = cp._callbacks(); - - reset.call(this, cp, propertyName); - - forEach(cp._dependentKeys, function (dependentKey) { - var dependentArray = get(this, dependentKey), - previousDependentArray = meta.dependentArrays[dependentKey]; - - if (dependentArray === previousDependentArray) { - // The array may be the same, but our item property keys may have - // changed, so we set them up again. We can't easily tell if they've - // changed: the array may be the same object, but with different - // contents. - if (cp._previousItemPropertyKeys[dependentKey]) { - delete cp._previousItemPropertyKeys[dependentKey]; - meta.dependentArraysObserver.setupPropertyObservers(dependentKey, cp._itemPropertyKeys[dependentKey]); - } - } else { - meta.dependentArrays[dependentKey] = dependentArray; - - if (previousDependentArray) { - meta.dependentArraysObserver.teardownObservers(previousDependentArray, dependentKey); - } - - if (dependentArray) { - meta.dependentArraysObserver.setupObservers(dependentArray, dependentKey); - } - } - }, this); - - forEach(cp._dependentKeys, function(dependentKey) { - var dependentArray = get(this, dependentKey); - if (dependentArray) { - addItems.call(this, dependentArray, callbacks, cp, propertyName, meta); - } - }, this); - }; - - this.func = function (propertyName) { - Ember.assert("Computed reduce values require at least one dependent key", cp._dependentKeys); - - recompute.call(this, propertyName); - - return cp._instanceMeta(this, propertyName).getValue(); - }; -} - -Ember.ReduceComputedProperty = ReduceComputedProperty; -ReduceComputedProperty.prototype = o_create(ComputedProperty.prototype); - -function defaultCallback(computedValue) { - return computedValue; -} - -ReduceComputedProperty.prototype._callbacks = function () { - if (!this.callbacks) { - var options = this.options; - this.callbacks = { - removedItem: options.removedItem || defaultCallback, - addedItem: options.addedItem || defaultCallback - }; - } - return this.callbacks; -}; - -ReduceComputedProperty.prototype._hasInstanceMeta = function (context, propertyName) { - var guid = guidFor(context), - key = guid + ':' + propertyName; - - return !!this._instanceMetas[key]; -}; - -ReduceComputedProperty.prototype._instanceMeta = function (context, propertyName) { - var guid = guidFor(context), - key = guid + ':' + propertyName, - meta = this._instanceMetas[key]; - - if (!meta) { - meta = this._instanceMetas[key] = new ReduceComputedPropertyInstanceMeta(context, propertyName, this.initialValue()); - meta.dependentArraysObserver = new DependentArraysObserver(this._callbacks(), this, meta, context, propertyName, meta.sugarMeta); - } - - return meta; -}; - -ReduceComputedProperty.prototype.initialValue = function () { - switch (typeof this.options.initialValue) { - case 'undefined': - throw new Error("reduce computed properties require an initial value: did you forget to pass one to Ember.reduceComputed?"); - case 'function': - return this.options.initialValue(); - default: - return this.options.initialValue; - } -}; - -ReduceComputedProperty.prototype.resetValue = function (value) { - return this.initialValue(); -}; - -ReduceComputedProperty.prototype.itemPropertyKey = function (dependentArrayKey, itemPropertyKey) { - this._itemPropertyKeys[dependentArrayKey] = this._itemPropertyKeys[dependentArrayKey] || []; - this._itemPropertyKeys[dependentArrayKey].push(itemPropertyKey); -}; - -ReduceComputedProperty.prototype.clearItemPropertyKeys = function (dependentArrayKey) { - if (this._itemPropertyKeys[dependentArrayKey]) { - this._previousItemPropertyKeys[dependentArrayKey] = this._itemPropertyKeys[dependentArrayKey]; - this._itemPropertyKeys[dependentArrayKey] = []; - } -}; - -ReduceComputedProperty.prototype.property = function () { - var cp = this, - args = a_slice.call(arguments), - propertyArgs = [], - match, - dependentArrayKey, - itemPropertyKey; - - forEach(a_slice.call(arguments), function (dependentKey) { - if (doubleEachPropertyPattern.test(dependentKey)) { - throw new Error("Nested @each properties not supported: " + dependentKey); - } else if (match = eachPropertyPattern.exec(dependentKey)) { - dependentArrayKey = match[1]; - itemPropertyKey = match[2]; - cp.itemPropertyKey(dependentArrayKey, itemPropertyKey); - propertyArgs.push(dependentArrayKey); - } else { - propertyArgs.push(dependentKey); - } - }); - - return ComputedProperty.prototype.property.apply(this, propertyArgs); -}; - -/** - Creates a computed property which operates on dependent arrays and - is updated with "one at a time" semantics. When items are added or - removed from the dependent array(s) a reduce computed only operates - on the change instead of re-evaluating the entire array. - - If there are more than one arguments the first arguments are - considered to be dependent property keys. The last argument is - required to be an options object. The options object can have the - following four properties. - - `initialValue` - A value or function that will be used as the initial - value for the computed. If this property is a function the result of calling - the function will be used as the initial value. This property is required. - - `initialize` - An optional initialize function. Typically this will be used - to set up state on the instanceMeta object. - - `removedItem` - A function that is called each time an element is removed - from the array. - - `addedItem` - A function that is called each time an element is added to - the array. - - - The `initialize` function has the following signature: - - ```javascript - function (initialValue, changeMeta, instanceMeta) - ``` - - `initialValue` - The value of the `initialValue` property from the - options object. - - `changeMeta` - An object which contains meta information about the - computed. It contains the following properties: - - - `property` the computed property - - `propertyName` the name of the property on the object - - `instanceMeta` - An object that can be used to store meta - information needed for calculating your computed. For example a - unique computed might use this to store the number of times a given - element is found in the dependent array. - - - The `removedItem` and `addedItem` functions both have the following signature: - - ```javascript - function (accumulatedValue, item, changeMeta, instanceMeta) - ``` - - `accumulatedValue` - The value returned from the last time - `removedItem` or `addedItem` was called or `initialValue`. - - `item` - the element added or removed from the array - - `changeMeta` - An object which contains meta information about the - change. It contains the following properties: - - - `property` the computed property - - `propertyName` the name of the property on the object - - `index` the index of the added or removed item - - `item` the added or removed item: this is exactly the same as - the second arg - - `arrayChanged` the array that triggered the change. Can be - useful when depending on multiple arrays. - - For property changes triggered on an item property change (when - depKey is something like `someArray.@each.someProperty`), - `changeMeta` will also contain the following property: - - - `previousValues` an object whose keys are the properties that changed on - the item, and whose values are the item's previous values. - - `previousValues` is important Ember coalesces item property changes via - Ember.run.once. This means that by the time removedItem gets called, item has - the new values, but you may need the previous value (eg for sorting & - filtering). - - `instanceMeta` - An object that can be used to store meta - information needed for calculating your computed. For example a - unique computed might use this to store the number of times a given - element is found in the dependent array. - - The `removedItem` and `addedItem` functions should return the accumulated - value. It is acceptable to not return anything (ie return undefined) - to invalidate the computation. This is generally not a good idea for - arrayComputed but it's used in eg max and min. - - Example - - ```javascript - Ember.computed.max = function (dependentKey) { - return Ember.reduceComputed.call(null, dependentKey, { - initialValue: -Infinity, - - addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { - return Math.max(accumulatedValue, item); - }, - - removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { - if (item < accumulatedValue) { - return accumulatedValue; - } - } - }); - }; - ``` - - @method reduceComputed - @for Ember - @param {String} [dependentKeys*] - @param {Object} options - @return {Ember.ComputedProperty} -*/ -Ember.reduceComputed = function (options) { - var args; - - if (arguments.length > 1) { - args = a_slice.call(arguments, 0, -1); - options = a_slice.call(arguments, -1)[0]; - } - - if (typeof options !== "object") { - throw new Error("Reduce Computed Property declared without an options hash"); - } - - if (Ember.isNone(options.initialValue)) { - throw new Error("Reduce Computed Property declared without an initial value"); - } - - var cp = new ReduceComputedProperty(options); - - if (args) { - cp.property.apply(cp, args); - } - - return cp; -}; - -})(); - - - -(function() { -var ReduceComputedProperty = Ember.ReduceComputedProperty, - a_slice = [].slice, - o_create = Ember.create, - forEach = Ember.EnumerableUtils.forEach; - -function ArrayComputedProperty() { - var cp = this; - - ReduceComputedProperty.apply(this, arguments); - - this.func = (function(reduceFunc) { - return function (propertyName) { - if (!cp._hasInstanceMeta(this, propertyName)) { - // When we recompute an array computed property, we need already - // retrieved arrays to be updated; we can't simply empty the cache and - // hope the array is re-retrieved. - forEach(cp._dependentKeys, function(dependentKey) { - Ember.addObserver(this, dependentKey, function() { - cp.recomputeOnce.call(this, propertyName); - }); - }, this); - } - - return reduceFunc.apply(this, arguments); - }; - })(this.func); - - return this; -} -Ember.ArrayComputedProperty = ArrayComputedProperty; -ArrayComputedProperty.prototype = o_create(ReduceComputedProperty.prototype); -ArrayComputedProperty.prototype.initialValue = function () { - return Ember.A(); -}; -ArrayComputedProperty.prototype.resetValue = function (array) { - array.clear(); - return array; -}; - -/** - Creates a computed property which operates on dependent arrays and - is updated with "one at a time" semantics. When items are added or - removed from the dependent array(s) an array computed only operates - on the change instead of re-evaluating the entire array. This should - return an array, if you'd like to use "one at a time" semantics and - compute some value other then an array look at - `Ember.reduceComputed`. - - If there are more than one arguments the first arguments are - considered to be dependent property keys. The last argument is - required to be an options object. The options object can have the - following three properties. - - `initialize` - An optional initialize function. Typically this will be used - to set up state on the instanceMeta object. - - `removedItem` - A function that is called each time an element is - removed from the array. - - `addedItem` - A function that is called each time an element is - added to the array. - - - The `initialize` function has the following signature: - - ```javascript - function (array, changeMeta, instanceMeta) - ``` - - `array` - The initial value of the arrayComputed, an empty array. - - `changeMeta` - An object which contains meta information about the - computed. It contains the following properties: - - - `property` the computed property - - `propertyName` the name of the property on the object - - `instanceMeta` - An object that can be used to store meta - information needed for calculating your computed. For example a - unique computed might use this to store the number of times a given - element is found in the dependent array. - - - The `removedItem` and `addedItem` functions both have the following signature: - - ```javascript - function (accumulatedValue, item, changeMeta, instanceMeta) - ``` - - `accumulatedValue` - The value returned from the last time - `removedItem` or `addedItem` was called or an empty array. - - `item` - the element added or removed from the array - - `changeMeta` - An object which contains meta information about the - change. It contains the following properties: - - - `property` the computed property - - `propertyName` the name of the property on the object - - `index` the index of the added or removed item - - `item` the added or removed item: this is exactly the same as - the second arg - - `arrayChanged` the array that triggered the change. Can be - useful when depending on multiple arrays. - - For property changes triggered on an item property change (when - depKey is something like `someArray.@each.someProperty`), - `changeMeta` will also contain the following property: - - - `previousValues` an object whose keys are the properties that changed on - the item, and whose values are the item's previous values. - - `previousValues` is important Ember coalesces item property changes via - Ember.run.once. This means that by the time removedItem gets called, item has - the new values, but you may need the previous value (eg for sorting & - filtering). - - `instanceMeta` - An object that can be used to store meta - information needed for calculating your computed. For example a - unique computed might use this to store the number of times a given - element is found in the dependent array. - - The `removedItem` and `addedItem` functions should return the accumulated - value. It is acceptable to not return anything (ie return undefined) - to invalidate the computation. This is generally not a good idea for - arrayComputed but it's used in eg max and min. - - Example - - ```javascript - Ember.computed.map = function(dependentKey, callback) { - var options = { - addedItem: function(array, item, changeMeta, instanceMeta) { - var mapped = callback(item); - array.insertAt(changeMeta.index, mapped); - return array; - }, - removedItem: function(array, item, changeMeta, instanceMeta) { - array.removeAt(changeMeta.index, 1); - return array; - } - }; - - return Ember.arrayComputed(dependentKey, options); - }; - ``` - - @method arrayComputed - @for Ember - @param {String} [dependentKeys*] - @param {Object} options - @return {Ember.ComputedProperty} -*/ -Ember.arrayComputed = function (options) { - var args; - - if (arguments.length > 1) { - args = a_slice.call(arguments, 0, -1); - options = a_slice.call(arguments, -1)[0]; - } - - if (typeof options !== "object") { - throw new Error("Array Computed Property declared without an options hash"); - } - - var cp = new ArrayComputedProperty(options); - - if (args) { - cp.property.apply(cp, args); - } - - return cp; -}; - -})(); - - - -(function() { -/** -@module ember -@submodule ember-runtime -*/ - -var get = Ember.get, - set = Ember.set, - guidFor = Ember.guidFor, - merge = Ember.merge, - a_slice = [].slice, - forEach = Ember.EnumerableUtils.forEach, - map = Ember.EnumerableUtils.map; - -/** - A computed property that calculates the maximum value in the - dependent array. This will return `-Infinity` when the dependent - array is empty. - - Example - - ```javascript - App.Person = Ember.Object.extend({ - childAges: Ember.computed.mapBy('children', 'age'), - maxChildAge: Ember.computed.max('childAges') - }); - - var lordByron = App.Person.create({children: []}); - lordByron.get('maxChildAge'); // -Infinity - lordByron.get('children').pushObject({name: 'Augusta Ada Byron', age: 7}); - lordByron.get('maxChildAge'); // 7 - lordByron.get('children').pushObjects([{name: 'Allegra Byron', age: 5}, {name: 'Elizabeth Medora Leigh', age: 8}]); - lordByron.get('maxChildAge'); // 8 - ``` - - @method computed.max - @for Ember - @param {String} dependentKey - @return {Ember.ComputedProperty} computes the largest value in the dependentKey's array -*/ -Ember.computed.max = function (dependentKey) { - return Ember.reduceComputed.call(null, dependentKey, { - initialValue: -Infinity, - - addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { - return Math.max(accumulatedValue, item); - }, - - removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { - if (item < accumulatedValue) { - return accumulatedValue; - } - } - }); -}; - -/** - A computed property that calculates the minimum value in the - dependent array. This will return `Infinity` when the dependent - array is empty. - - Example - - ```javascript - App.Person = Ember.Object.extend({ - childAges: Ember.computed.mapBy('children', 'age'), - minChildAge: Ember.computed.min('childAges') - }); - - var lordByron = App.Person.create({children: []}); - lordByron.get('minChildAge'); // Infinity - lordByron.get('children').pushObject({name: 'Augusta Ada Byron', age: 7}); - lordByron.get('minChildAge'); // 7 - lordByron.get('children').pushObjects([{name: 'Allegra Byron', age: 5}, {name: 'Elizabeth Medora Leigh', age: 8}]); - lordByron.get('minChildAge'); // 5 - ``` - - @method computed.min - @for Ember - @param {String} dependentKey - @return {Ember.ComputedProperty} computes the smallest value in the dependentKey's array -*/ -Ember.computed.min = function (dependentKey) { - return Ember.reduceComputed.call(null, dependentKey, { - initialValue: Infinity, - - addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { - return Math.min(accumulatedValue, item); - }, - - removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { - if (item > accumulatedValue) { - return accumulatedValue; - } - } - }); -}; - -/** - Returns an array mapped via the callback - - The callback method you provide should have the following signature: - - ```javascript - function(item); - ``` - - - `item` is the current item in the iteration. - - Example - - ```javascript - App.Hampster = Ember.Object.extend({ - excitingChores: Ember.computed.map('chores', function(chore) { - return chore.toUpperCase() + '!'; - }) - }); - - var hampster = App.Hampster.create({chores: ['cook', 'clean', 'write more unit tests']}); - hampster.get('excitingChores'); // ['COOK!', 'CLEAN!', 'WRITE MORE UNIT TESTS!'] - ``` - - @method computed.map - @for Ember - @param {String} dependentKey - @param {Function} callback - @return {Ember.ComputedProperty} an array mapped via the callback -*/ -Ember.computed.map = function(dependentKey, callback) { - var options = { - addedItem: function(array, item, changeMeta, instanceMeta) { - var mapped = callback(item); - array.insertAt(changeMeta.index, mapped); - return array; - }, - removedItem: function(array, item, changeMeta, instanceMeta) { - array.removeAt(changeMeta.index, 1); - return array; - } - }; - - return Ember.arrayComputed(dependentKey, options); -}; - -/** - Returns an array mapped to the specified key. - - Example - - ```javascript - App.Person = Ember.Object.extend({ - childAges: Ember.computed.mapBy('children', 'age'), - minChildAge: Ember.computed.min('childAges') - }); - - var lordByron = App.Person.create({children: []}); - lordByron.get('childAge'); // [] - lordByron.get('children').pushObject({name: 'Augusta Ada Byron', age: 7}); - lordByron.get('childAge'); // [7] - lordByron.get('children').pushObjects([{name: 'Allegra Byron', age: 5}, {name: 'Elizabeth Medora Leigh', age: 8}]); - lordByron.get('childAge'); // [7, 5, 8] - ``` - - @method computed.mapBy - @for Ember - @param {String} dependentKey - @param {String} propertyKey - @return {Ember.ComputedProperty} an array mapped to the specified key -*/ -Ember.computed.mapBy = function(dependentKey, propertyKey) { - var callback = function(item) { return get(item, propertyKey); }; - return Ember.computed.map(dependentKey + '.@each.' + propertyKey, callback); -}; - -/** - @method computed.mapProperty - @for Ember - @deprecated Use `Ember.computed.mapBy` instead - @param dependentKey - @param propertyKey -*/ -Ember.computed.mapProperty = Ember.computed.mapBy; - -/** - Filters the array by the callback. - - The callback method you provide should have the following signature: - - ```javascript - function(item); - ``` - - - `item` is the current item in the iteration. - - Example - - ```javascript - App.Hampster = Ember.Object.extend({ - remainingChores: Ember.computed.filter('chores', function(chore) { - return !chore.done; - }) - }); - - var hampster = App.Hampster.create({chores: [ - {name: 'cook', done: true}, - {name: 'clean', done: true}, - {name: 'write more unit tests', done: false} - ]}); - hampster.get('remainingChores'); // [{name: 'write more unit tests', done: false}] - ``` - - @method computed.filter - @for Ember - @param {String} dependentKey - @param {Function} callback - @return {Ember.ComputedProperty} the filtered array -*/ -Ember.computed.filter = function(dependentKey, callback) { - var options = { - initialize: function (array, changeMeta, instanceMeta) { - instanceMeta.filteredArrayIndexes = new Ember.SubArray(); - }, - - addedItem: function(array, item, changeMeta, instanceMeta) { - var match = !!callback(item), - filterIndex = instanceMeta.filteredArrayIndexes.addItem(changeMeta.index, match); - - if (match) { - array.insertAt(filterIndex, item); - } - - return array; - }, - - removedItem: function(array, item, changeMeta, instanceMeta) { - var filterIndex = instanceMeta.filteredArrayIndexes.removeItem(changeMeta.index); - - if (filterIndex > -1) { - array.removeAt(filterIndex); - } - - return array; - } - }; - - return Ember.arrayComputed(dependentKey, options); -}; - -/** - Filters the array by the property and value - - Example - - ```javascript - App.Hampster = Ember.Object.extend({ - remainingChores: Ember.computed.filterBy('chores', 'done', false) - }); - - var hampster = App.Hampster.create({chores: [ - {name: 'cook', done: true}, - {name: 'clean', done: true}, - {name: 'write more unit tests', done: false} - ]}); - hampster.get('remainingChores'); // [{name: 'write more unit tests', done: false}] - ``` - - @method computed.filterBy - @for Ember - @param {String} dependentKey - @param {String} propertyKey - @param {String} value - @return {Ember.ComputedProperty} the filtered array -*/ -Ember.computed.filterBy = function(dependentKey, propertyKey, value) { - var callback; - - if (arguments.length === 2) { - callback = function(item) { - return get(item, propertyKey); - }; - } else { - callback = function(item) { - return get(item, propertyKey) === value; - }; - } - - return Ember.computed.filter(dependentKey + '.@each.' + propertyKey, callback); -}; - -/** - @method computed.filterProperty - @for Ember - @param dependentKey - @param propertyKey - @param value - @deprecated Use `Ember.computed.filterBy` instead -*/ -Ember.computed.filterProperty = Ember.computed.filterBy; - -/** - A computed property which returns a new array with all the unique - elements from one or more dependent arrays. - - Example - - ```javascript - App.Hampster = Ember.Object.extend({ - uniqueFruits: Ember.computed.uniq('fruits') - }); - - var hampster = App.Hampster.create({fruits: [ - 'banana', - 'grape', - 'kale', - 'banana' - ]}); - hampster.get('uniqueFruits'); // ['banana', 'grape', 'kale'] - ``` - - @method computed.uniq - @for Ember - @param {String} propertyKey* - @return {Ember.ComputedProperty} computes a new array with all the - unique elements from the dependent array -*/ -Ember.computed.uniq = function() { - var args = a_slice.call(arguments); - args.push({ - initialize: function(array, changeMeta, instanceMeta) { - instanceMeta.itemCounts = {}; - }, - - addedItem: function(array, item, changeMeta, instanceMeta) { - var guid = guidFor(item); - - if (!instanceMeta.itemCounts[guid]) { - instanceMeta.itemCounts[guid] = 1; - } else { - ++instanceMeta.itemCounts[guid]; - } - array.addObject(item); - return array; - }, - removedItem: function(array, item, _, instanceMeta) { - var guid = guidFor(item), - itemCounts = instanceMeta.itemCounts; - - if (--itemCounts[guid] === 0) { - array.removeObject(item); - } - return array; - } - }); - return Ember.arrayComputed.apply(null, args); -}; - -/** - Alias for [Ember.computed.uniq](/api/#method_computed_uniq). - - @method computed.union - @for Ember - @param {String} propertyKey* - @return {Ember.ComputedProperty} computes a new array with all the - unique elements from the dependent array -*/ -Ember.computed.union = Ember.computed.uniq; - -/** - A computed property which returns a new array with all the duplicated - elements from two or more dependeny arrays. - - Example - - ```javascript - var obj = Ember.Object.createWithMixins({ - adaFriends: ['Charles Babbage', 'John Hobhouse', 'William King', 'Mary Somerville'], - charlesFriends: ['William King', 'Mary Somerville', 'Ada Lovelace', 'George Peacock'], - friendsInCommon: Ember.computed.intersect('adaFriends', 'charlesFriends') - }); - - obj.get('friendsInCommon'); // ['William King', 'Mary Somerville'] - ``` - - @method computed.intersect - @for Ember - @param {String} propertyKey* - @return {Ember.ComputedProperty} computes a new array with all the - duplicated elements from the dependent arrays -*/ -Ember.computed.intersect = function () { - var getDependentKeyGuids = function (changeMeta) { - return map(changeMeta.property._dependentKeys, function (dependentKey) { - return guidFor(dependentKey); - }); - }; - - var args = a_slice.call(arguments); - args.push({ - initialize: function (array, changeMeta, instanceMeta) { - instanceMeta.itemCounts = {}; - }, - - addedItem: function(array, item, changeMeta, instanceMeta) { - var itemGuid = guidFor(item), - dependentGuids = getDependentKeyGuids(changeMeta), - dependentGuid = guidFor(changeMeta.arrayChanged), - numberOfDependentArrays = changeMeta.property._dependentKeys.length, - itemCounts = instanceMeta.itemCounts; - - if (!itemCounts[itemGuid]) { itemCounts[itemGuid] = {}; } - if (itemCounts[itemGuid][dependentGuid] === undefined) { itemCounts[itemGuid][dependentGuid] = 0; } - - if (++itemCounts[itemGuid][dependentGuid] === 1 && - numberOfDependentArrays === Ember.keys(itemCounts[itemGuid]).length) { - - array.addObject(item); - } - return array; - }, - removedItem: function(array, item, changeMeta, instanceMeta) { - var itemGuid = guidFor(item), - dependentGuids = getDependentKeyGuids(changeMeta), - dependentGuid = guidFor(changeMeta.arrayChanged), - numberOfDependentArrays = changeMeta.property._dependentKeys.length, - numberOfArraysItemAppearsIn, - itemCounts = instanceMeta.itemCounts; - - if (itemCounts[itemGuid][dependentGuid] === undefined) { itemCounts[itemGuid][dependentGuid] = 0; } - if (--itemCounts[itemGuid][dependentGuid] === 0) { - delete itemCounts[itemGuid][dependentGuid]; - numberOfArraysItemAppearsIn = Ember.keys(itemCounts[itemGuid]).length; - - if (numberOfArraysItemAppearsIn === 0) { - delete itemCounts[itemGuid]; - } - array.removeObject(item); - } - return array; - } - }); - return Ember.arrayComputed.apply(null, args); -}; - -/** - A computed property which returns a new array with all the - properties from the first dependent array that are not in the second - dependent array. - - Example - - ```javascript - App.Hampster = Ember.Object.extend({ - likes: ['banana', 'grape', 'kale'], - wants: Ember.computed.setDiff('likes', 'fruits') - }); - - var hampster = App.Hampster.create({fruits: [ - 'grape', - 'kale', - ]}); - hampster.get('wants'); // ['banana'] - ``` - - @method computed.setDiff - @for Ember - @param {String} setAProperty - @param {String} setBProperty - @return {Ember.ComputedProperty} computes a new array with all the - items from the first dependent array that are not in the second - dependent array -*/ -Ember.computed.setDiff = function (setAProperty, setBProperty) { - if (arguments.length !== 2) { - throw new Error("setDiff requires exactly two dependent arrays."); - } - return Ember.arrayComputed.call(null, setAProperty, setBProperty, { - addedItem: function (array, item, changeMeta, instanceMeta) { - var setA = get(this, setAProperty), - setB = get(this, setBProperty); - - if (changeMeta.arrayChanged === setA) { - if (!setB.contains(item)) { - array.addObject(item); - } - } else { - array.removeObject(item); - } - return array; - }, - - removedItem: function (array, item, changeMeta, instanceMeta) { - var setA = get(this, setAProperty), - setB = get(this, setBProperty); - - if (changeMeta.arrayChanged === setB) { - if (setA.contains(item)) { - array.addObject(item); - } - } else { - array.removeObject(item); - } - return array; - } - }); -}; - -function binarySearch(array, item, low, high) { - var mid, midItem, res, guidMid, guidItem; - - if (arguments.length < 4) { high = get(array, 'length'); } - if (arguments.length < 3) { low = 0; } - - if (low === high) { - return low; - } - - mid = low + Math.floor((high - low) / 2); - midItem = array.objectAt(mid); - - guidMid = _guidFor(midItem); - guidItem = _guidFor(item); - - if (guidMid === guidItem) { - return mid; - } - - res = this.order(midItem, item); - if (res === 0) { - res = guidMid < guidItem ? -1 : 1; - } - - - if (res < 0) { - return this.binarySearch(array, item, mid+1, high); - } else if (res > 0) { - return this.binarySearch(array, item, low, mid); - } - - return mid; - - function _guidFor(item) { - if (Ember.ObjectProxy.detectInstance(item)) { - return guidFor(get(item, 'content')); - } - return guidFor(item); - } -} - -/** - A computed property which returns a new array with all the - properties from the first dependent array sorted based on a property - or sort function. - - The callback method you provide should have the following signature: - - ```javascript - function(itemA, itemB); - ``` - - - `itemA` the first item to compare. - - `itemB` the second item to compare. - - This function should return `-1` when `itemA` should come before - `itemB`. It should return `1` when `itemA` should come after - `itemB`. If the `itemA` and `itemB` are equal this function should return `0`. - - Example - - ```javascript - var ToDoList = Ember.Object.extend({ - todosSorting: ['name'], - sortedTodos: Ember.computed.sort('todos', 'todosSorting'), - priorityTodos: Ember.computed.sort('todos', function(a, b){ - if (a.priority > b.priority) { - return 1; - } else if (a.priority < b.priority) { - return -1; - } - return 0; - }), - }); - var todoList = ToDoList.create({todos: [ - {name: 'Unit Test', priority: 2}, - {name: 'Documentation', priority: 3}, - {name: 'Release', priority: 1} - ]}); - - todoList.get('sortedTodos'); // [{name:'Documentation', priority:3}, {name:'Release', priority:1}, {name:'Unit Test', priority:2}] - todoList.get('priroityTodos'); // [{name:'Release', priority:1}, {name:'Unit Test', priority:2}, {name:'Documentation', priority:3}] - ``` - - @method computed.sort - @for Ember - @param {String} dependentKey - @param {String or Function} sortDefinition a dependent key to an - array of sort properties or a function to use when sorting - @return {Ember.ComputedProperty} computes a new sorted array based - on the sort property array or callback function -*/ -Ember.computed.sort = function (itemsKey, sortDefinition) { - Ember.assert("Ember.computed.sort requires two arguments: an array key to sort and either a sort properties key or sort function", arguments.length === 2); - - var initFn, sortPropertiesKey; - - if (typeof sortDefinition === 'function') { - initFn = function (array, changeMeta, instanceMeta) { - instanceMeta.order = sortDefinition; - instanceMeta.binarySearch = binarySearch; - }; - } else { - sortPropertiesKey = sortDefinition; - initFn = function (array, changeMeta, instanceMeta) { - function setupSortProperties() { - var sortPropertyDefinitions = get(this, sortPropertiesKey), - sortProperty, - sortProperties = instanceMeta.sortProperties = [], - sortPropertyAscending = instanceMeta.sortPropertyAscending = {}, - idx, - asc; - - Ember.assert("Cannot sort: '" + sortPropertiesKey + "' is not an array.", Ember.isArray(sortPropertyDefinitions)); - - changeMeta.property.clearItemPropertyKeys(itemsKey); - - forEach(sortPropertyDefinitions, function (sortPropertyDefinition) { - if ((idx = sortPropertyDefinition.indexOf(':')) !== -1) { - sortProperty = sortPropertyDefinition.substring(0, idx); - asc = sortPropertyDefinition.substring(idx+1).toLowerCase() !== 'desc'; - } else { - sortProperty = sortPropertyDefinition; - asc = true; - } - - sortProperties.push(sortProperty); - sortPropertyAscending[sortProperty] = asc; - changeMeta.property.itemPropertyKey(itemsKey, sortProperty); - }); - - sortPropertyDefinitions.addObserver('@each', this, updateSortPropertiesOnce); - } - - function updateSortPropertiesOnce() { - Ember.run.once(this, updateSortProperties, changeMeta.propertyName); - } - - function updateSortProperties(propertyName) { - setupSortProperties.call(this); - changeMeta.property.recomputeOnce.call(this, propertyName); - } - - Ember.addObserver(this, sortPropertiesKey, updateSortPropertiesOnce); - - setupSortProperties.call(this); - - - instanceMeta.order = function (itemA, itemB) { - var sortProperty, result, asc; - for (var i = 0; i < this.sortProperties.length; ++i) { - sortProperty = this.sortProperties[i]; - result = Ember.compare(get(itemA, sortProperty), get(itemB, sortProperty)); - - if (result !== 0) { - asc = this.sortPropertyAscending[sortProperty]; - return asc ? result : (-1 * result); - } - } - - return 0; - }; - - instanceMeta.binarySearch = binarySearch; - }; - } - - return Ember.arrayComputed.call(null, itemsKey, { - initialize: initFn, - - addedItem: function (array, item, changeMeta, instanceMeta) { - var index = instanceMeta.binarySearch(array, item); - array.insertAt(index, item); - return array; - }, - - removedItem: function (array, item, changeMeta, instanceMeta) { - var proxyProperties, index, searchItem; - - if (changeMeta.previousValues) { - proxyProperties = merge({ content: item }, changeMeta.previousValues); - - searchItem = Ember.ObjectProxy.create(proxyProperties); - } else { - searchItem = item; - } - - index = instanceMeta.binarySearch(array, searchItem); - array.removeAt(index); - return array; - } - }); -}; - -})(); - - - -(function() { -/** - Expose RSVP implementation - - Documentation can be found here: https://github.com/tildeio/rsvp.js/blob/master/README.md - - @class RSVP - @namespace Ember - @constructor -*/ -Ember.RSVP = requireModule('rsvp'); - -})(); - - - -(function() { -/** -@module ember -@submodule ember-runtime -*/ - -var STRING_DASHERIZE_REGEXP = (/[ _]/g); -var STRING_DASHERIZE_CACHE = {}; -var STRING_DECAMELIZE_REGEXP = (/([a-z])([A-Z])/g); -var STRING_CAMELIZE_REGEXP = (/(\-|_|\.|\s)+(.)?/g); -var STRING_UNDERSCORE_REGEXP_1 = (/([a-z\d])([A-Z]+)/g); -var STRING_UNDERSCORE_REGEXP_2 = (/\-|\s+/g); - -/** - Defines the hash of localized strings for the current language. Used by - the `Ember.String.loc()` helper. To localize, add string values to this - hash. - - @property STRINGS - @for Ember - @type Hash -*/ -Ember.STRINGS = {}; - -/** - Defines string helper methods including string formatting and localization. - Unless `Ember.EXTEND_PROTOTYPES.String` is `false` these methods will also be - added to the `String.prototype` as well. - - @class String - @namespace Ember - @static -*/ -Ember.String = { - - /** - Apply formatting options to the string. This will look for occurrences - of "%@" in your string and substitute them with the arguments you pass into - this method. If you want to control the specific order of replacement, - you can add a number after the key as well to indicate which argument - you want to insert. - - Ordered insertions are most useful when building loc strings where values - you need to insert may appear in different orders. - - ```javascript - "Hello %@ %@".fmt('John', 'Doe'); // "Hello John Doe" - "Hello %@2, %@1".fmt('John', 'Doe'); // "Hello Doe, John" - ``` - - @method fmt - @param {String} str The string to format - @param {Array} formats An array of parameters to interpolate into string. - @return {String} formatted string - */ - fmt: function(str, formats) { - // first, replace any ORDERED replacements. - var idx = 0; // the current index for non-numerical replacements - return str.replace(/%@([0-9]+)?/g, function(s, argIndex) { - argIndex = (argIndex) ? parseInt(argIndex, 10) - 1 : idx++; - s = formats[argIndex]; - return (s === null) ? '(null)' : (s === undefined) ? '' : Ember.inspect(s); - }) ; - }, - - /** - Formats the passed string, but first looks up the string in the localized - strings hash. This is a convenient way to localize text. See - `Ember.String.fmt()` for more information on formatting. - - Note that it is traditional but not required to prefix localized string - keys with an underscore or other character so you can easily identify - localized strings. - - ```javascript - Ember.STRINGS = { - '_Hello World': 'Bonjour le monde', - '_Hello %@ %@': 'Bonjour %@ %@' - }; - - Ember.String.loc("_Hello World"); // 'Bonjour le monde'; - Ember.String.loc("_Hello %@ %@", ["John", "Smith"]); // "Bonjour John Smith"; - ``` - - @method loc - @param {String} str The string to format - @param {Array} formats Optional array of parameters to interpolate into string. - @return {String} formatted string - */ - loc: function(str, formats) { - str = Ember.STRINGS[str] || str; - return Ember.String.fmt(str, formats) ; - }, - - /** - Splits a string into separate units separated by spaces, eliminating any - empty strings in the process. This is a convenience method for split that - is mostly useful when applied to the `String.prototype`. - - ```javascript - Ember.String.w("alpha beta gamma").forEach(function(key) { - console.log(key); - }); - - // > alpha - // > beta - // > gamma - ``` - - @method w - @param {String} str The string to split - @return {String} split string - */ - w: function(str) { return str.split(/\s+/); }, - - /** - Converts a camelized string into all lower case separated by underscores. - - ```javascript - 'innerHTML'.decamelize(); // 'inner_html' - 'action_name'.decamelize(); // 'action_name' - 'css-class-name'.decamelize(); // 'css-class-name' - 'my favorite items'.decamelize(); // 'my favorite items' - ``` - - @method decamelize - @param {String} str The string to decamelize. - @return {String} the decamelized string. - */ - decamelize: function(str) { - return str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase(); - }, - - /** - Replaces underscores, spaces, or camelCase with dashes. - - ```javascript - 'innerHTML'.dasherize(); // 'inner-html' - 'action_name'.dasherize(); // 'action-name' - 'css-class-name'.dasherize(); // 'css-class-name' - 'my favorite items'.dasherize(); // 'my-favorite-items' - ``` - - @method dasherize - @param {String} str The string to dasherize. - @return {String} the dasherized string. - */ - dasherize: function(str) { - var cache = STRING_DASHERIZE_CACHE, - hit = cache.hasOwnProperty(str), - ret; - - if (hit) { - return cache[str]; - } else { - ret = Ember.String.decamelize(str).replace(STRING_DASHERIZE_REGEXP,'-'); - cache[str] = ret; - } - - return ret; - }, - - /** - Returns the lowerCamelCase form of a string. - - ```javascript - 'innerHTML'.camelize(); // 'innerHTML' - 'action_name'.camelize(); // 'actionName' - 'css-class-name'.camelize(); // 'cssClassName' - 'my favorite items'.camelize(); // 'myFavoriteItems' - 'My Favorite Items'.camelize(); // 'myFavoriteItems' - ``` - - @method camelize - @param {String} str The string to camelize. - @return {String} the camelized string. - */ - camelize: function(str) { - return str.replace(STRING_CAMELIZE_REGEXP, function(match, separator, chr) { - return chr ? chr.toUpperCase() : ''; - }).replace(/^([A-Z])/, function(match, separator, chr) { - return match.toLowerCase(); - }); - }, - - /** - Returns the UpperCamelCase form of a string. - - ```javascript - 'innerHTML'.classify(); // 'InnerHTML' - 'action_name'.classify(); // 'ActionName' - 'css-class-name'.classify(); // 'CssClassName' - 'my favorite items'.classify(); // 'MyFavoriteItems' - ``` - - @method classify - @param {String} str the string to classify - @return {String} the classified string - */ - classify: function(str) { - var parts = str.split("."), - out = []; - - for (var i=0, l=parts.length; i true - controller.get('isSettled') //=> false - controller.get('isRejected') //=> false - controller.get('isFulfilled') //=> false - ``` - - When the the $.getJSON completes, and the promise is fulfilled - with json, the life cycle attributes will update accordingly. - - ```javascript - controller.get('isPending') //=> false - controller.get('isSettled') //=> true - controller.get('isRejected') //=> false - controller.get('isFulfilled') //=> true - ``` - - As the controller is an ObjectController, and the json now its content, - all the json properties will be available directly from the controller. - - ```javascript - // Assuming the following json: - { - firstName: 'Stefan', - lastName: 'Penner' - } - - // both properties will accessible on the controller - controller.get('firstName') //=> 'Stefan' - controller.get('lastName') //=> 'Penner' - ``` - - If the controller is backing a template, the attributes are - bindable from within that template - - ```handlebars - {{#if isPending}} - loading... - {{else}} - firstName: {{firstName}} - lastName: {{lastName}} - {{/if}} - ``` - @class Ember.PromiseProxyMixin -*/ -Ember.PromiseProxyMixin = Ember.Mixin.create({ - reason: null, - isPending: not('isSettled').readOnly(), - isSettled: or('isRejected', 'isFulfilled').readOnly(), - isRejected: false, - isFulfilled: false, - - promise: Ember.computed(function(key, promise) { - if (arguments.length === 2) { - promise = resolve(promise); - installPromise(this, promise); - return promise; - } else { - throw new Error("PromiseProxy's promise must be set"); - } - }), - - then: function(fulfill, reject) { - return get(this, 'promise').then(fulfill, reject); - } -}); - - -})(); - - - -(function() { - -})(); - - - -(function() { -var get = Ember.get, - forEach = Ember.EnumerableUtils.forEach, - RETAIN = 'r', - INSERT = 'i', - DELETE = 'd'; - -/** - An `Ember.TrackedArray` tracks array operations. It's useful when you want to - lazily compute the indexes of items in an array after they've been shifted by - subsequent operations. - - @class TrackedArray - @namespace Ember - @param {array} [items=[]] The array to be tracked. This is used just to get - the initial items for the starting state of retain:n. -*/ -Ember.TrackedArray = function (items) { - if (arguments.length < 1) { items = []; } - - var length = get(items, 'length'); - - if (length) { - this._content = [new ArrayOperation(RETAIN, length, items)]; - } else { - this._content = []; - } -}; - -Ember.TrackedArray.RETAIN = RETAIN; -Ember.TrackedArray.INSERT = INSERT; -Ember.TrackedArray.DELETE = DELETE; - -Ember.TrackedArray.prototype = { - - /** - Track that `newItems` were added to the tracked array at `index`. - - @method addItems - @param index - @param newItems - */ - addItems: function (index, newItems) { - var count = get(newItems, 'length'), - match = this._findArrayOperation(index), - arrayOperation = match.operation, - arrayOperationIndex = match.index, - arrayOperationRangeStart = match.rangeStart, - composeIndex, - splitIndex, - splitItems, - splitArrayOperation, - newArrayOperation; - - newArrayOperation = new ArrayOperation(INSERT, count, newItems); - - if (arrayOperation) { - if (!match.split) { - // insert left of arrayOperation - this._content.splice(arrayOperationIndex, 0, newArrayOperation); - composeIndex = arrayOperationIndex; - } else { - this._split(arrayOperationIndex, index - arrayOperationRangeStart, newArrayOperation); - composeIndex = arrayOperationIndex + 1; - } - } else { - // insert at end - this._content.push(newArrayOperation); - composeIndex = arrayOperationIndex; - } - - this._composeInsert(composeIndex); - }, - - /** - Track that `count` items were removed at `index`. - - @method removeItems - @param index - @param count - */ - removeItems: function (index, count) { - var match = this._findArrayOperation(index), - arrayOperation = match.operation, - arrayOperationIndex = match.index, - arrayOperationRangeStart = match.rangeStart, - newArrayOperation, - composeIndex; - - newArrayOperation = new ArrayOperation(DELETE, count); - if (!match.split) { - // insert left of arrayOperation - this._content.splice(arrayOperationIndex, 0, newArrayOperation); - composeIndex = arrayOperationIndex; - } else { - this._split(arrayOperationIndex, index - arrayOperationRangeStart, newArrayOperation); - composeIndex = arrayOperationIndex + 1; - } - - return this._composeDelete(composeIndex); - }, - - /** - Apply all operations, reducing them to retain:n, for `n`, the number of - items in the array. - - `callback` will be called for each operation and will be passed the following arguments: - -* {array} items The items for the given operation -* {number} offset The computed offset of the items, ie the index in the -array of the first item for this operation. -* {string} operation The type of the operation. One of `Ember.TrackedArray.{RETAIN, DELETE, INSERT}` -* - - @method apply - @param {function} callback - */ - apply: function (callback) { - var items = [], - offset = 0; - - forEach(this._content, function (arrayOperation) { - callback(arrayOperation.items, offset, arrayOperation.operation); - - if (arrayOperation.operation !== DELETE) { - offset += arrayOperation.count; - items = items.concat(arrayOperation.items); - } - }); - - this._content = [new ArrayOperation(RETAIN, items.length, items)]; - }, - - /** - Return an ArrayOperationMatch for the operation that contains the item at `index`. - - @method _findArrayOperation - - @param {number} index the index of the item whose operation information - should be returned. - @private - */ - _findArrayOperation: function (index) { - var arrayOperationIndex, - len, - split = false, - arrayOperation, - arrayOperationRangeStart, - arrayOperationRangeEnd; - - // OPTIMIZE: we could search these faster if we kept a balanced tree. - // find leftmost arrayOperation to the right of `index` - for (arrayOperationIndex = arrayOperationRangeStart = 0, len = this._content.length; arrayOperationIndex < len; ++arrayOperationIndex) { - arrayOperation = this._content[arrayOperationIndex]; - - if (arrayOperation.operation === DELETE) { continue; } - - arrayOperationRangeEnd = arrayOperationRangeStart + arrayOperation.count - 1; - - if (index === arrayOperationRangeStart) { - break; - } else if (index > arrayOperationRangeStart && index <= arrayOperationRangeEnd) { - split = true; - break; - } else { - arrayOperationRangeStart = arrayOperationRangeEnd + 1; - } - } - - return new ArrayOperationMatch(arrayOperation, arrayOperationIndex, split, arrayOperationRangeStart); - }, - - _split: function (arrayOperationIndex, splitIndex, newArrayOperation) { - var arrayOperation = this._content[arrayOperationIndex], - splitItems = arrayOperation.items.slice(splitIndex), - splitArrayOperation = new ArrayOperation(arrayOperation.operation, splitItems.length, splitItems); - - // truncate LHS - arrayOperation.count = splitIndex; - arrayOperation.items = arrayOperation.items.slice(0, splitIndex); - - this._content.splice(arrayOperationIndex + 1, 0, newArrayOperation, splitArrayOperation); - }, - - // TODO: unify _composeInsert, _composeDelete - // see SubArray for a better implementation. - _composeInsert: function (index) { - var newArrayOperation = this._content[index], - leftArrayOperation = this._content[index-1], // may be undefined - rightArrayOperation = this._content[index+1], // may be undefined - leftOp = leftArrayOperation && leftArrayOperation.operation, - rightOp = rightArrayOperation && rightArrayOperation.operation; - - if (leftOp === INSERT) { - // merge left - leftArrayOperation.count += newArrayOperation.count; - leftArrayOperation.items = leftArrayOperation.items.concat(newArrayOperation.items); - - if (rightOp === INSERT) { - // also merge right - leftArrayOperation.count += rightArrayOperation.count; - leftArrayOperation.items = leftArrayOperation.items.concat(rightArrayOperation.items); - this._content.splice(index, 2); - } else { - // only merge left - this._content.splice(index, 1); - } - } else if (rightOp === INSERT) { - // merge right - newArrayOperation.count += rightArrayOperation.count; - newArrayOperation.items = newArrayOperation.items.concat(rightArrayOperation.items); - this._content.splice(index + 1, 1); - } - }, - - _composeDelete: function (index) { - var arrayOperation = this._content[index], - deletesToGo = arrayOperation.count, - leftArrayOperation = this._content[index-1], // may be undefined - leftOp = leftArrayOperation && leftArrayOperation.operation, - nextArrayOperation, - nextOp, - nextCount, - removedItems = []; - - if (leftOp === DELETE) { - arrayOperation = leftArrayOperation; - index -= 1; - } - - for (var i = index + 1; deletesToGo > 0; ++i) { - nextArrayOperation = this._content[i]; - nextOp = nextArrayOperation.operation; - nextCount = nextArrayOperation.count; - - if (nextOp === DELETE) { - arrayOperation.count += nextCount; - continue; - } - - if (nextCount > deletesToGo) { - removedItems = removedItems.concat(nextArrayOperation.items.splice(0, deletesToGo)); - nextArrayOperation.count -= deletesToGo; - - // In the case where we truncate the last arrayOperation, we don't need to - // remove it; also the deletesToGo reduction is not the entirety of - // nextCount - i -= 1; - nextCount = deletesToGo; - - deletesToGo = 0; - } else { - removedItems = removedItems.concat(nextArrayOperation.items); - deletesToGo -= nextCount; - } - - if (nextOp === INSERT) { - arrayOperation.count -= nextCount; - } - } - - if (arrayOperation.count > 0) { - this._content.splice(index+1, i-1-index); - } else { - // The delete operation can go away; it has merely reduced some other - // operation, as in D:3 I:4 - this._content.splice(index, 1); - } - - return removedItems; - } -}; - -function ArrayOperation (operation, count, items) { - this.operation = operation; // RETAIN | INSERT | DELETE - this.count = count; - this.items = items; -} - -/** - Internal data structure used to include information when looking up operations - by item index. - - @method ArrayOperationMatch - @private - @property {ArrayOperation} operation - @property {number} index The index of `operation` in the array of operations. - @property {boolean} split Whether or not the item index searched for would - require a split for a new operation type. - @property {number} rangeStart The index of the first item in the operation, - with respect to the tracked array. The index of the last item can be computed - from `rangeStart` and `operation.count`. -*/ -function ArrayOperationMatch(operation, index, split, rangeStart) { - this.operation = operation; - this.index = index; - this.split = split; - this.rangeStart = rangeStart; -} - -})(); - - - -(function() { -var get = Ember.get, - forEach = Ember.EnumerableUtils.forEach, - RETAIN = 'r', - FILTER = 'f'; - -function Operation (type, count) { - this.type = type; - this.count = count; -} - -/** - An `Ember.SubArray` tracks an array in a way similar to, but more specialized - than, `Ember.TrackedArray`. It is useful for keeping track of the indexes of - items within a filtered array. - - @class SubArray - @namespace Ember -*/ -Ember.SubArray = function (length) { - if (arguments.length < 1) { length = 0; } - - if (length > 0) { - this._operations = [new Operation(RETAIN, length)]; - } else { - this._operations = []; - } -}; - -Ember.SubArray.prototype = { - /** - Track that an item was added to the tracked array. - - @method addItem - - @param {number} index The index of the item in the tracked array. - @param {boolean} match `true` iff the item is included in the subarray. - - @return {number} The index of the item in the subarray. - */ - addItem: function(index, match) { - var returnValue = -1, - itemType = match ? RETAIN : FILTER, - self = this; - - this._findOperation(index, function(operation, operationIndex, rangeStart, rangeEnd, seenInSubArray) { - var newOperation, splitOperation; - - if (itemType === operation.type) { - ++operation.count; - } else if (index === rangeStart) { - // insert to the left of `operation` - self._operations.splice(operationIndex, 0, new Operation(itemType, 1)); - } else { - newOperation = new Operation(itemType, 1); - splitOperation = new Operation(operation.type, rangeEnd - index + 1); - operation.count = index - rangeStart; - - self._operations.splice(operationIndex + 1, 0, newOperation, splitOperation); - } - - if (match) { - if (operation.type === RETAIN) { - returnValue = seenInSubArray + (index - rangeStart); - } else { - returnValue = seenInSubArray; - } - } - - self._composeAt(operationIndex); - }, function(seenInSubArray) { - self._operations.push(new Operation(itemType, 1)); - - if (match) { - returnValue = seenInSubArray; - } - - self._composeAt(self._operations.length-1); - }); - - return returnValue; - }, - - /** - Track that an item was removed from the tracked array. - - @method removeItem - - @param {number} index The index of the item in the tracked array. - - @return {number} The index of the item in the subarray, or `-1` if the item - was not in the subarray. - */ - removeItem: function(index) { - var returnValue = -1, - self = this; - - this._findOperation(index, function (operation, operationIndex, rangeStart, rangeEnd, seenInSubArray) { - if (operation.type === RETAIN) { - returnValue = seenInSubArray + (index - rangeStart); - } - - if (operation.count > 1) { - --operation.count; - } else { - self._operations.splice(operationIndex, 1); - self._composeAt(operationIndex); - } - }); - - return returnValue; - }, - - - _findOperation: function (index, foundCallback, notFoundCallback) { - var operationIndex, - len, - operation, - rangeStart, - rangeEnd, - seenInSubArray = 0; - - // OPTIMIZE: change to balanced tree - // find leftmost operation to the right of `index` - for (operationIndex = rangeStart = 0, len = this._operations.length; operationIndex < len; rangeStart = rangeEnd + 1, ++operationIndex) { - operation = this._operations[operationIndex]; - rangeEnd = rangeStart + operation.count - 1; - - if (index >= rangeStart && index <= rangeEnd) { - foundCallback(operation, operationIndex, rangeStart, rangeEnd, seenInSubArray); - return; - } else if (operation.type === RETAIN) { - seenInSubArray += operation.count; - } - } - - notFoundCallback(seenInSubArray); - }, - - _composeAt: function(index) { - var op = this._operations[index], - otherOp; - - if (!op) { - // Composing out of bounds is a no-op, as when removing the last operation - // in the list. - return; - } - - if (index > 0) { - otherOp = this._operations[index-1]; - if (otherOp.type === op.type) { - op.count += otherOp.count; - this._operations.splice(index-1, 1); - } - } - - if (index < this._operations.length-1) { - otherOp = this._operations[index+1]; - if (otherOp.type === op.type) { - op.count += otherOp.count; - this._operations.splice(index+1, 1); - } - } - } -}; })(); @@ -15370,7 +12245,6 @@ function makeCtor() { Ember.assert("Ember.Object.create no longer supports defining computed properties.", !(value instanceof Ember.ComputedProperty)); Ember.assert("Ember.Object.create no longer supports defining methods that call _super.", !(typeof value === 'function' && value.toString().indexOf('._super') !== -1)); - Ember.assert("`actions` must be provided at extend time, not at create time, when Ember.ActionHandler is used (i.e. views, controllers & routes).", !((keyName === 'actions') && Ember.ActionHandler.detect(this))); if (concatenatedProperties && indexOf(concatenatedProperties, keyName) >= 0) { var baseValue = this[keyName]; @@ -15401,9 +12275,9 @@ function makeCtor() { } } finishPartial(this, m); - this.init.apply(this, arguments); m.proto = proto; finishChains(this); + this.init.apply(this, arguments); sendEvent(this, "init"); }; @@ -15536,10 +12410,7 @@ CoreObject.PrototypeMixin = Mixin.create({ are also concatenated, in addition to `classNames`. This feature is available for you to use throughout the Ember object model, - although typical app developers are likely to use it infrequently. Since - it changes expectations about behavior of properties, you should properly - document its usage in each individual concatenated property (to not - mislead your users to think they can override the property in a subclass). + although typical app developers are likely to use it infrequently. @property concatenatedProperties @type Array @@ -15758,65 +12629,12 @@ var ClassMixin = Mixin.create({ return new C(); }, - /** - - Augments a constructor's prototype with additional - properties and functions: - - ```javascript - MyObject = Ember.Object.extend({ - name: 'an object' - }); - - o = MyObject.create(); - o.get('name'); // 'an object' - - MyObject.reopen({ - say: function(msg){ - console.log(msg); - } - }) - - o2 = MyObject.create(); - o2.say("hello"); // logs "hello" - - o.say("goodbye"); // logs "goodbye" - ``` - - To add functions and properties to the constructor itself, - see `reopenClass` - - @method reopen - */ reopen: function() { this.willReopen(); reopen.apply(this.PrototypeMixin, arguments); return this; }, - /** - Augments a constructor's own properties and functions: - - ```javascript - MyObject = Ember.Object.extend({ - name: 'an object' - }); - - - MyObject.reopenClass({ - canBuild: false - }); - - MyObject.canBuild; // false - o = MyObject.create(); - ``` - - To add functions and properties to instances of - a constructor by extending the constructor's prototype - see `reopen` - - @method reopenClass - */ reopenClass: function() { reopen.apply(this.ClassMixin, arguments); applyMixin(this, arguments, false); @@ -16095,8 +12913,6 @@ function classToString() { if (this[NAME_KEY]) { ret = this[NAME_KEY]; - } else if (this._toString) { - ret = this._toString; } else { var str = superClassString(this); if (str) { @@ -16493,9 +13309,7 @@ var get = Ember.get, removeBeforeObserver = Ember.removeBeforeObserver, removeObserver = Ember.removeObserver, propertyWillChange = Ember.propertyWillChange, - propertyDidChange = Ember.propertyDidChange, - meta = Ember.meta, - defineProperty = Ember.defineProperty; + propertyDidChange = Ember.propertyDidChange; function contentPropertyWillChange(content, contentKey) { var key = contentKey.slice(8); // remove "content." @@ -16613,14 +13427,6 @@ Ember.ObjectProxy = Ember.Object.extend(/** @scope Ember.ObjectProxy.prototype * }, setUnknownProperty: function (key, value) { - var m = meta(this); - if (m.proto === this) { - // if marked as prototype then just defineProperty - // rather than delegate - defineProperty(this, key, null, value); - return value; - } - var content = get(this, 'content'); Ember.assert(fmt("Cannot delegate set('%@', %@) to the 'content' property of object proxy %@: its 'content' is undefined.", [key, value, this]), content); return set(content, key, value); @@ -16628,6 +13434,25 @@ Ember.ObjectProxy = Ember.Object.extend(/** @scope Ember.ObjectProxy.prototype * }); +Ember.ObjectProxy.reopenClass({ + create: function () { + var mixin, prototype, i, l, properties, keyName; + if (arguments.length) { + prototype = this.proto(); + for (i = 0, l = arguments.length; i < l; i++) { + properties = arguments[i]; + for (keyName in properties) { + if (!properties.hasOwnProperty(keyName) || keyName in prototype) { continue; } + if (!mixin) mixin = {}; + mixin[keyName] = null; + } + } + if (mixin) this._initMixins([mixin]); + } + return this._super.apply(this, arguments); + } +}); + })(); @@ -16640,8 +13465,7 @@ Ember.ObjectProxy = Ember.Object.extend(/** @scope Ember.ObjectProxy.prototype * var set = Ember.set, get = Ember.get, guidFor = Ember.guidFor; -var forEach = Ember.EnumerableUtils.forEach, - indexOf = Ember.ArrayPolyfills.indexOf; +var forEach = Ember.EnumerableUtils.forEach; var EachArray = Ember.Object.extend(Ember.Array, { @@ -16699,7 +13523,7 @@ function removeObserverForContentKey(content, keyName, proxy, idx, loc) { guid = guidFor(item); indicies = objects[guid]; - indicies[indexOf.call(indicies, loc)] = null; + indicies[indicies.indexOf(loc)] = null; } } } @@ -16848,7 +13672,7 @@ Ember.EachProxy = Ember.Object.extend({ */ -var get = Ember.get, set = Ember.set, replace = Ember.EnumerableUtils._replace; +var get = Ember.get, set = Ember.set; // Add Ember.Array to Array.prototype. Remove methods with native // implementations and supply some more optimized versions of generic methods @@ -16870,7 +13694,7 @@ var NativeArray = Ember.Mixin.create(Ember.MutableArray, Ember.Observable, Ember // primitive for array support. replace: function(idx, amt, objects) { - if (this.isFrozen) throw Ember.FROZEN_ERROR; + if (this.isFrozen) throw Ember.FROZEN_ERROR ; // if we replaced exactly the same number of items, then pass only the // replaced range. Otherwise, pass the full remaining array length @@ -16879,13 +13703,14 @@ var NativeArray = Ember.Mixin.create(Ember.MutableArray, Ember.Observable, Ember this.arrayContentWillChange(idx, amt, len); if (!objects || objects.length === 0) { - this.splice(idx, amt); + this.splice(idx, amt) ; } else { - replace(this, idx, amt, objects); + var args = [idx, amt].concat(objects) ; + this.splice.apply(this,args) ; } this.arrayContentDidChange(idx, amt, len); - return this; + return this ; }, // If you ask for an unknown property, then try to collect the value @@ -16962,26 +13787,7 @@ Ember.NativeArray = NativeArray; /** Creates an `Ember.NativeArray` from an Array like object. - Does not modify the original object. Ember.A is not needed if - `Ember.EXTEND_PROTOTYPES` is `true` (the default value). However, - it is recommended that you use Ember.A when creating addons for - ember or when you can not garentee that `Ember.EXTEND_PROTOTYPES` - will be `true`. - - Example - - ```js - var Pagination = Ember.CollectionView.extend({ - tagName: 'ul', - classNames: ['pagination'], - init: function() { - this._super(); - if (!this.get('content')) { - this.set('content', Ember.A([])); - } - } - }); - ``` + Does not modify the original object. @method A @for Ember @@ -16994,17 +13800,7 @@ Ember.A = function(arr) { /** Activates the mixin on the Array.prototype if not already applied. Calling - this method more than once is safe. This will be called when ember is loaded - unless you have `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Array` - set to `false`. - - Example - - ```js - if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Array) { - Ember.NativeArray.activate(); - } - ``` + this method more than once is safe. @method activate @for Ember.NativeArray @@ -17099,8 +13895,8 @@ var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor, isNone = Ember.is When using `Ember.Set`, you can observe the `"[]"` property to be alerted whenever the content changes. You can also add an enumerable observer to the set to be notified of specific objects that are added and - removed from the set. See [Ember.Enumerable](/api/classes/Ember.Enumerable.html) - for more information on enumerables. + removed from the set. See `Ember.Enumerable` for more information on + enumerables. This is often unhelpful. If you are filtering sets of objects, for instance, it is very inefficient to re-filter all of the items each time the set @@ -17515,20 +14311,6 @@ var loadHooks = Ember.ENV.EMBER_LOAD_HOOKS || {}; var loaded = {}; /** - -Detects when a specific package of Ember (e.g. 'Ember.Handlebars') -has fully loaded and is available for extension. - -The provided `callback` will be called with the `name` passed -resolved from a string into the object: - -```javascript -Ember.onLoad('Ember.Handlebars' function(hbars){ - hbars.registerHelper(...); -}); -``` - - @method onLoad @for Ember @param name {String} name of hook @@ -17546,10 +14328,6 @@ Ember.onLoad = function(name, callback) { }; /** - -Called when an Ember.js package (e.g Ember.Handlebars) has finished -loading. Triggers any callbacks registered for this event. - @method runLoadHooks @for Ember @param name {String} name of hook @@ -17588,18 +14366,39 @@ var get = Ember.get; compose Ember's controller layer: `Ember.Controller`, `Ember.ArrayController`, and `Ember.ObjectController`. + Within an `Ember.Router`-managed application single shared instaces of every + Controller object in your application's namespace will be added to the + application's `Ember.Router` instance. See `Ember.Application#initialize` + for additional information. + + ## Views + + By default a controller instance will be the rendering context + for its associated `Ember.View.` This connection is made during calls to + `Ember.ControllerMixin#connectOutlet`. + + Within the view's template, the `Ember.View` instance can be accessed + through the controller with `{{view}}`. + + ## Target Forwarding + + By default a controller will target your application's `Ember.Router` + instance. Calls to `{{action}}` within the template of a controller's view + are forwarded to the router. See `Ember.Handlebars.helpers.action` for + additional information. + @class ControllerMixin @namespace Ember */ -Ember.ControllerMixin = Ember.Mixin.create(Ember.ActionHandler, { +Ember.ControllerMixin = Ember.Mixin.create({ /* ducktype as a controller */ isController: true, /** - The object to which actions from the view should be sent. + The object to which events from the view should be sent. For example, when a Handlebars template uses the `{{action}}` helper, - it will attempt to send the action to the view's controller's `target`. + it will attempt to send the event to the view's controller's `target`. By default, a controller's `target` is set to the router after it is instantiated by `Ember.Application#initialize`. @@ -17617,16 +14416,16 @@ Ember.ControllerMixin = Ember.Mixin.create(Ember.ActionHandler, { model: Ember.computed.alias('content'), - deprecatedSendHandles: function(actionName) { - return !!this[actionName]; - }, + send: function(actionName) { + var args = [].slice.call(arguments, 1), target; - deprecatedSend: function(actionName) { - var args = [].slice.call(arguments, 1); - Ember.assert('' + this + " has the action " + actionName + " but it is not a function", typeof this[actionName] === 'function'); - Ember.deprecate('Action handlers implemented directly on controllers are deprecated in favor of action handlers on an `actions` object (' + actionName + ' on ' + this + ')', false); - this[actionName].apply(this, args); - return; + if (this[actionName]) { + Ember.assert("The controller " + this + " does not have the action " + actionName, typeof this[actionName] === 'function'); + this[actionName].apply(this, args); + } else if (target = get(this, 'target')) { + Ember.assert("The target for controller " + this + " (" + target + ") did not define a `send` method", typeof target.send === 'function'); + target.send.apply(target, arguments); + } } }); @@ -17675,29 +14474,6 @@ var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach; songsController.get('firstObject'); // {trackNumber: 1, title: 'Dear Prudence'} ``` - If you add or remove the properties to sort by or change the sort direction the content - sort order will be automatically updated. - - ```javascript - songsController.set('sortProperties', ['title']); - songsController.get('firstObject'); // {trackNumber: 2, title: 'Back in the U.S.S.R.'} - - songsController.toggleProperty('sortAscending'); - songsController.get('firstObject'); // {trackNumber: 4, title: 'Ob-La-Di, Ob-La-Da'} - ``` - - SortableMixin works by sorting the arrangedContent array, which is the array that - arrayProxy displays. Due to the fact that the underlying 'content' array is not changed, that - array will not display the sorted list: - - ```javascript - songsController.get('content').get('firstObject'); // Returns the unsorted original content - songsController.get('firstObject'); // Returns the sorted content. - ``` - - Although the sorted content can also be accessed through the arrangedContent property, - it is preferable to use the proxied class and not the arrangedContent array directly. - @class SortableMixin @namespace Ember @uses Ember.MutableEnumerable @@ -17707,9 +14483,6 @@ Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, { /** Specifies which properties dictate the arrangedContent's sort order. - When specifying multiple properties the sorting will use properties - from the `sortProperties` array prioritized from first to last. - @property {Array} sortProperties */ sortProperties: null, @@ -17723,7 +14496,7 @@ Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, { /** The function used to compare two values. You can override this if you - want to do custom comparisons. Functions must be of the type expected by + want to do custom comparisons.Functions must be of the type expected by Array#sort, i.e. return 0 if the two parameters are equal, return a negative value if the first parameter is smaller than the second or @@ -17780,13 +14553,6 @@ Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, { isSorted: Ember.computed.bool('sortProperties'), - /** - Overrides the default arrangedContent from arrayProxy in order to sort by sortFunction. - Also sets up observers for each sortProperty on each item in the content Array. - - @property arrangedContent - */ - arrangedContent: Ember.computed('content', 'sortProperties.@each', function(key, value) { var content = get(this, 'content'), isSorted = get(this, 'isSorted'), @@ -18104,15 +14870,11 @@ Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin, }, init: function() { + if (!this.get('content')) { Ember.defineProperty(this, 'content', undefined, Ember.A()); } this._super(); - this.set('_subControllers', Ember.A()); }, - content: Ember.computed(function () { - return Ember.A(); - }), - controllerAt: function(idx, object, controllerClass) { var container = get(this, 'container'), subControllers = get(this, '_subControllers'), @@ -18163,11 +14925,9 @@ Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin, */ /** - `Ember.ObjectController` is part of Ember's Controller layer. It is intended - to wrap a single object, proxying unhandled attempts to `get` and `set` to the underlying - content object, and to forward unhandled action attempts to its `target`. + `Ember.ObjectController` is part of Ember's Controller layer. - `Ember.ObjectController` derives this functionality from its superclass + `Ember.ObjectController` derives its functionality from its superclass `Ember.ObjectProxy` and the `Ember.ControllerMixin` mixin. @class ObjectController @@ -18451,14 +15211,9 @@ function escapeAttribute(value) { final representation. `Ember.RenderBuffer` will generate HTML which can be pushed to the DOM. - ```javascript - var buffer = Ember.RenderBuffer('div'); - ``` - @class RenderBuffer @namespace Ember @constructor - @param {String} tagName tag name (such as 'div' or 'p') used for the buffer */ Ember.RenderBuffer = function(tagName) { return new Ember._RenderBuffer(tagName); @@ -19247,13 +16002,7 @@ Ember.warn("The VIEW_PRESERVES_CONTEXT flag has been removed and the functionali Ember.TEMPLATES = {}; /** - `Ember.CoreView` is an abstract class that exists to give view-like behavior - to both Ember's main view class `Ember.View` and other classes like - `Ember._SimpleMetamorphView` that don't need the fully functionaltiy of - `Ember.View`. - - Unless you have specific needs for `CoreView`, you will use `Ember.View` - in your applications. + `Ember.CoreView` is @class CoreView @namespace Ember @@ -19261,7 +16010,7 @@ Ember.TEMPLATES = {}; @uses Ember.Evented */ -Ember.CoreView = Ember.Object.extend(Ember.Evented, Ember.ActionHandler, { +Ember.CoreView = Ember.Object.extend(Ember.Evented, { isView: true, states: states, @@ -19376,18 +16125,6 @@ Ember.CoreView = Ember.Object.extend(Ember.Evented, Ember.ActionHandler, { } }, - deprecatedSendHandles: function(actionName) { - return !!this[actionName]; - }, - - deprecatedSend: function(actionName) { - var args = [].slice.call(arguments, 1); - Ember.assert('' + this + " has the action " + actionName + " but it is not a function", typeof this[actionName] === 'function'); - Ember.deprecate('Action handlers implemented directly on views are deprecated in favor of action handlers on an `actions` object (' + actionName + ' on ' + this + ')', false); - this[actionName].apply(this, args); - return; - }, - has: function(name) { return Ember.typeOf(this[name]) === 'function' || this._super(name); }, @@ -19489,7 +16226,7 @@ var EMPTY_ARRAY = []; 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: +class: ```javascript ParagraphView = Ember.View.extend({ @@ -19663,8 +16400,8 @@ var EMPTY_ARRAY = []; will be removed. Both `classNames` and `classNameBindings` are concatenated properties. See - [Ember.Object](/api/classes/Ember.Object.html) documentation for more - information about concatenated properties. + `Ember.Object` documentation for more information about concatenated + properties. ## HTML Attributes @@ -19724,7 +16461,7 @@ var EMPTY_ARRAY = []; Updates to the the property of an attribute binding will result in automatic update of the HTML attribute in the view's rendered HTML representation. - `attributeBindings` is a concatenated property. See [Ember.Object](/api/classes/Ember.Object.html) + `attributeBindings` is a concatenated property. See `Ember.Object` documentation for more information about concatenated properties. ## Templates @@ -19767,6 +16504,9 @@ var EMPTY_ARRAY = []; Using a value for `templateName` that does not have a Handlebars template with a matching `data-template-name` attribute will throw an error. + Assigning a value to both `template` and `templateName` properties will throw + an error. + For views classes that may have a template later defined (e.g. as the block portion of a `{{view}}` Handlebars helper call in another template or in a subclass), you can provide a `defaultTemplate` property set to compiled @@ -19872,8 +16612,7 @@ var EMPTY_ARRAY = []; ``` - See [Ember.Handlebars.helpers.yield](/api/classes/Ember.Handlebars.helpers.html#method_yield) - for more information. + See `Handlebars.helpers.yield` for more information. ## Responding to Browser Events @@ -19970,7 +16709,7 @@ var EMPTY_ARRAY = []; ### Handlebars `{{action}}` Helper - See [Handlebars.helpers.action](/api/classes/Ember.Handlebars.helpers.html#method_action). + See `Handlebars.helpers.action`. ### Event Names @@ -20025,8 +16764,8 @@ var EMPTY_ARRAY = []; ## Handlebars `{{view}}` Helper Other `Ember.View` instances can be included as part of a view's template by - using the `{{view}}` Handlebars helper. See [Ember.Handlebars.helpers.view](/api/classes/Ember.Handlebars.helpers.html#method_view) - for additional information. + using the `{{view}}` Handlebars helper. See `Handlebars.helpers.view` for + additional information. @class View @namespace Ember @@ -21067,7 +17806,7 @@ Ember.View = Ember.CoreView.extend( visually challenged users navigate rich web applications. The full list of valid WAI-ARIA roles is available at: - [http://www.w3.org/TR/wai-aria/roles#roles_categorization](http://www.w3.org/TR/wai-aria/roles#roles_categorization) + http://www.w3.org/TR/wai-aria/roles#roles_categorization @property ariaRole @type String @@ -21299,10 +18038,6 @@ Ember.View = Ember.CoreView.extend( @return {Ember.View} new instance */ createChildView: function(view, attrs) { - if (!view) { - throw new TypeError("createChildViews first argument must exist"); - } - if (view.isView && view._parentView === this && view.container === this.container) { return view; } @@ -21546,7 +18281,7 @@ Ember.View.reopenClass({ Parse a path and return an object which holds the parsed properties. - For example a path like "content.isEnabled:enabled:disabled" will return the + For example a path like "content.isEnabled:enabled:disabled" wil return the following object: ```javascript @@ -22309,7 +19044,7 @@ Ember.ContainerView = Ember.View.extend(Ember.MutableArray, { length: Ember.computed(function () { return this._childViews.length; - }).volatile(), + }), /** @private @@ -22639,6 +19374,11 @@ var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt; manipulated. Instead, add, remove, replace items from its `content` property. This will trigger appropriate changes to its rendered HTML. + ## Use in templates via the `{{collection}}` `Ember.Handlebars` helper + + `Ember.Handlebars` provides a helper specifically for adding + `CollectionView`s to templates. See `Ember.Handlebars.collection` for more + details @class CollectionView @namespace Ember @@ -22683,25 +19423,12 @@ Ember.CollectionView = Ember.ContainerView.extend(/** @scope Ember.CollectionVie */ itemViewClass: Ember.View, - /** - Setup a CollectionView - - @method init - */ init: function() { var ret = this._super(); this._contentDidChange(); return ret; }, - /** - @private - - Invoked when the content property is about to change. Notifies observers that the - entire array content will change. - - @method _contentWillChange - */ _contentWillChange: Ember.beforeObserver(function() { var content = this.get('content'); @@ -22732,22 +19459,10 @@ Ember.CollectionView = Ember.ContainerView.extend(/** @scope Ember.CollectionVie this.arrayDidChange(content, 0, null, len); }, 'content'), - /** - @private - - Ensure that the content implements Ember.Array - - @method _assertArrayLike - */ _assertArrayLike: function(content) { Ember.assert(fmt("an Ember.CollectionView's content must implement Ember.Array. You passed %@", [content]), Ember.Array.detect(content)); }, - /** - Removes the content and content observers. - - @method destroy - */ destroy: function() { if (!this._super()) { return; } @@ -22761,19 +19476,6 @@ Ember.CollectionView = Ember.ContainerView.extend(/** @scope Ember.CollectionVie return this; }, - /** - Called when a mutation to the underlying content array will occur. - - This method will remove any views that are no longer in the underlying - content array. - - Invokes whenever the content array itself will change. - - @method arrayWillChange - @param {Array} content the managed collection of objects - @param {Number} start the index at which the changes will occurr - @param {Number} removed number of object to be removed from content - */ arrayWillChange: function(content, start, removedCount) { // If the contents were empty before and this template collection has an // empty view remove it now. @@ -22864,21 +19566,6 @@ Ember.CollectionView = Ember.ContainerView.extend(/** @scope Ember.CollectionVie this.replace(start, 0, addedViews); }, - /** - Instantiates a view to be added to the childViews array during view - initialization. You generally will not call this method directly unless - you are overriding `createChildViews()`. Note that this method will - automatically configure the correct settings on the new view instance to - act as a child of the parent. - - The tag name for the view will be set to the tagName of the viewClass - passed in. - - @method createChildView - @param {Class} viewClass - @param {Hash} [attrs] Attributes to add - @return {Ember.View} new instance - */ createChildView: function(view, attrs) { view = this._super(view, attrs); @@ -22947,7 +19634,7 @@ var get = Ember.get, set = Ember.set, isNone = Ember.isNone; ```html

{{person.title}}

- +

{{person.signature}}

``` @@ -22969,18 +19656,15 @@ var get = Ember.get, set = Ember.set, isNone = Ember.isNone; If you want to customize the component, in order to handle events or actions, you implement a subclass of `Ember.Component` named after the name of the - component. Note that `Component` needs to be appended to the name of - your subclass like `AppProfileComponent`. + component. For example, you could implement the action `hello` for the `app-profile` component: - ```javascript + ```js App.AppProfileComponent = Ember.Component.extend({ - actions: { - hello: function(name) { - console.log("Hello", name); - } + hello: function(name) { + console.log("Hello", name) } }); ``` @@ -23033,8 +19717,7 @@ Ember.Component = Ember.View.extend(Ember.TargetActionSupport, { view.appendChild(Ember.View, { isVirtual: true, tagName: '', - _contextView: parentView, - template: template, + template: get(this, 'template'), context: get(parentView, 'context'), controller: get(parentView, 'controller'), templateData: { keywords: parentView.cloneKeywords() } @@ -23042,14 +19725,6 @@ Ember.Component = Ember.View.extend(Ember.TargetActionSupport, { } }, - /** - If the component is currently inserted into the DOM of a parent view, this - property will point to the controller of the parent view. - - @property targetObject - @type Ember.Controller - @default null - */ targetObject: Ember.computed(function(key) { var parentView = get(this, '_parentView'); return parentView ? get(parentView, 'controller') : null; @@ -23086,17 +19761,15 @@ Ember.Component = Ember.View.extend(Ember.TargetActionSupport, { target's action method. Example: ```javascript - App.MyTreeComponent = Ember.Component.extend({ + App.MyTree = Ember.Component.extend({ click: function() { this.sendAction('didClickTreeNode', this.get('node')); } }); App.CategoriesController = Ember.Controller.extend({ - actions: { - didClickCategory: function(category) { - //Do something with the node/category that was clicked - } + didClickCategory: function(category) { + //Do something with the node/category that was clicked } }); ``` @@ -23340,14 +20013,6 @@ define("metamorph", range.insertNode(fragment); }; - /** - * @public - * - * Remove this object (including starting and ending - * placeholders). - * - * @method remove - */ removeFunc = function() { // get a range for the current metamorph object including // the starting and ending placeholders. @@ -23388,7 +20053,7 @@ define("metamorph", }; } else { - /* + /** * This code is mostly taken from jQuery, with one exception. In jQuery's case, we * have some HTML and we need to figure out how to convert it into some nodes. * @@ -23442,12 +20107,12 @@ define("metamorph", } }; - /* + /** * Given a parent node and some HTML, generate a set of nodes. Return the first * node, which will allow us to traverse the rest using nextSibling. * * We need to do this because innerHTML in IE does not really parse the nodes. - */ + **/ var firstNodeFor = function(parentNode, html) { var arr = wrapMap[parentNode.tagName.toLowerCase()] || wrapMap._default; var depth = arr[0], start = arr[1], end = arr[2]; @@ -23480,7 +20145,7 @@ define("metamorph", return element; }; - /* + /** * In some cases, Internet Explorer can create an anonymous node in * the hierarchy with no tagName. You can create this scenario via: * @@ -23490,7 +20155,7 @@ define("metamorph", * * If our script markers are inside such a node, we need to find that * node and use *it* as the marker. - */ + **/ var realNode = function(start) { while (start.parentNode.tagName === "") { start = start.parentNode; @@ -23499,7 +20164,7 @@ define("metamorph", return start; }; - /* + /** * When automatically adding a tbody, Internet Explorer inserts the * tbody immediately before the first . Other browsers create it * before the first node, no matter what. @@ -23526,8 +20191,7 @@ define("metamorph", * * This code reparents the first script tag by making it the tbody's * first child. - * - */ + **/ var fixParentage = function(start, end) { if (start.parentNode !== end.parentNode) { end.parentNode.insertBefore(start, end.parentNode.firstChild); @@ -23794,7 +20458,16 @@ function makeBindings(options) { @param {String} dependentKeys* */ Ember.Handlebars.helper = function(name, value) { - Ember.assert("You tried to register a component named '" + name + "', but component names must include a '-'", !Ember.Component.detect(value) || name.match(/-/)); + if (Ember.Component.detect(value)) { + Ember.assert("You tried to register a component named '" + name + "', but component names must include a '-'", name.match(/-/)); + + var proto = value.proto(); + if (!proto.layoutName && !proto.templateName) { + value.reopen({ + layoutName: 'components/' + name + }); + } + } if (Ember.View.detect(value)) { Ember.Handlebars.registerHelper(name, function(options) { @@ -23847,6 +20520,7 @@ if (Handlebars.JavaScriptCompiler) { Ember.Handlebars.JavaScriptCompiler.prototype.namespace = "Ember.Handlebars"; + Ember.Handlebars.JavaScriptCompiler.prototype.initializeBuffer = function() { return "''"; }; @@ -23950,7 +20624,7 @@ if (Handlebars.compile) { var template = Ember.Handlebars.template(templateSpec); template.isMethod = false; //Make sure we don't wrap templates with ._super - + return template; }; } @@ -24214,101 +20888,61 @@ Ember.Handlebars.registerBoundHelper = function(name, fn) { numProperties = properties.length, options = arguments[arguments.length - 1], normalizedProperties = [], - types = options.types, data = options.data, hash = options.hash, view = data.view, currentContext = (options.contexts && options.contexts[0]) || this, - prefixPathForDependentKeys = '', - loc, len, hashOption, - boundOption, property, - normalizedValue = Ember._SimpleHandlebarsView.prototype.normalizedValue; + normalized, + pathRoot, path, prefixPathForDependentKeys = '', + loc, hashOption; Ember.assert("registerBoundHelper-generated helpers do not support use with Handlebars blocks.", !options.fn); // Detect bound options (e.g. countBinding="otherCount") - var boundOptions = hash.boundOptions = {}; + hash.boundOptions = {}; for (hashOption in hash) { - if (Ember.IS_BINDING.test(hashOption)) { + if (!hash.hasOwnProperty(hashOption)) { continue; } + + if (Ember.IS_BINDING.test(hashOption) && typeof hash[hashOption] === 'string') { // Lop off 'Binding' suffix. - boundOptions[hashOption.slice(0, -7)] = hash[hashOption]; + hash.boundOptions[hashOption.slice(0, -7)] = hash[hashOption]; } } // Expose property names on data.properties object. - var watchedProperties = []; data.properties = []; for (loc = 0; loc < numProperties; ++loc) { data.properties.push(properties[loc]); - if (types[loc] === 'ID') { - var normalizedProp = normalizePath(currentContext, properties[loc], data); - normalizedProperties.push(normalizedProp); - watchedProperties.push(normalizedProp); - } else { - normalizedProperties.push(null); - } + normalizedProperties.push(normalizePath(currentContext, properties[loc], data)); } - // Handle case when helper invocation is preceded by `unbound`, e.g. - // {{unbound myHelper foo}} if (data.isUnbound) { return evaluateUnboundHelper(this, fn, normalizedProperties, options); } - var bindView = new Ember._SimpleHandlebarsView(null, null, !options.hash.unescaped, options.data); + if (dependentKeys.length === 0) { + return evaluateMultiPropertyBoundHelper(currentContext, fn, normalizedProperties, options); + } + + Ember.assert("Dependent keys can only be used with single-property helpers.", properties.length === 1); + + normalized = normalizedProperties[0]; + + pathRoot = normalized.root; + path = normalized.path; + + var bindView = new Ember._SimpleHandlebarsView( + path, pathRoot, !options.hash.unescaped, options.data + ); - // Override SimpleHandlebarsView's method for generating the view's content. bindView.normalizedValue = function() { - var args = [], boundOption; - - // Copy over bound hash options. - for (boundOption in boundOptions) { - if (!boundOptions.hasOwnProperty(boundOption)) { continue; } - property = normalizePath(currentContext, boundOptions[boundOption], data); - bindView.path = property.path; - bindView.pathRoot = property.root; - hash[boundOption] = normalizedValue.call(bindView); - } - - for (loc = 0; loc < numProperties; ++loc) { - property = normalizedProperties[loc]; - if (property) { - bindView.path = property.path; - bindView.pathRoot = property.root; - args.push(normalizedValue.call(bindView)); - } else { - args.push(properties[loc]); - } - } - args.push(options); - - // Run the supplied helper function. - return fn.apply(currentContext, args); + var value = Ember._SimpleHandlebarsView.prototype.normalizedValue.call(bindView); + return fn.call(view, value, options); }; view.appendChild(bindView); - // Assemble list of watched properties that'll re-render this helper. - for (boundOption in boundOptions) { - if (boundOptions.hasOwnProperty(boundOption)) { - watchedProperties.push(normalizePath(currentContext, boundOptions[boundOption], data)); - } - } - - // Observe each property. - for (loc = 0, len = watchedProperties.length; loc < len; ++loc) { - property = watchedProperties[loc]; - view.registerObserver(property.root, property.path, bindView, bindView.rerender); - } - - if (types[0] !== 'ID' || normalizedProperties.length === 0) { - return; - } - - // Add dependent key observers to the first param - var normalized = normalizedProperties[0], - pathRoot = normalized.root, - path = normalized.path; + view.registerObserver(pathRoot, path, bindView, bindView.rerender); if(!Ember.isEmpty(path)) { prefixPathForDependentKeys = path + '.'; @@ -24322,6 +20956,68 @@ Ember.Handlebars.registerBoundHelper = function(name, fn) { Ember.Handlebars.registerHelper(name, helper); }; +/** + @private + + Renders the unbound form of an otherwise bound helper function. + + @method evaluateMultiPropertyBoundHelper + @param {Function} fn + @param {Object} context + @param {Array} normalizedProperties + @param {String} options +*/ +function evaluateMultiPropertyBoundHelper(context, fn, normalizedProperties, options) { + var numProperties = normalizedProperties.length, + data = options.data, + view = data.view, + hash = options.hash, + boundOptions = hash.boundOptions, + watchedProperties, + boundOption, bindView, loc, property, len; + + bindView = new Ember._SimpleHandlebarsView(null, null, !hash.unescaped, data); + bindView.normalizedValue = function() { + var args = [], boundOption; + + // Copy over bound options. + for (boundOption in boundOptions) { + if (!boundOptions.hasOwnProperty(boundOption)) { continue; } + property = normalizePath(context, boundOptions[boundOption], data); + bindView.path = property.path; + bindView.pathRoot = property.root; + hash[boundOption] = Ember._SimpleHandlebarsView.prototype.normalizedValue.call(bindView); + } + + for (loc = 0; loc < numProperties; ++loc) { + property = normalizedProperties[loc]; + bindView.path = property.path; + bindView.pathRoot = property.root; + args.push(Ember._SimpleHandlebarsView.prototype.normalizedValue.call(bindView)); + } + args.push(options); + return fn.apply(context, args); + }; + + view.appendChild(bindView); + + // Assemble list of watched properties that'll re-render this helper. + watchedProperties = []; + for (boundOption in boundOptions) { + if (boundOptions.hasOwnProperty(boundOption)) { + watchedProperties.push(normalizePath(context, boundOptions[boundOption], data)); + } + } + watchedProperties = watchedProperties.concat(normalizedProperties); + + // Observe each property. + for (loc = 0, len = watchedProperties.length; loc < len; ++loc) { + property = watchedProperties[loc]; + view.registerObserver(property.root, property.path, bindView, bindView.rerender); + } + +} + /** @private @@ -24371,19 +21067,19 @@ Ember.Handlebars.template = function(spec) { (function() { /** - Mark a string as safe for unescaped output with Handlebars. If you - return HTML from a Handlebars helper, use this function to - ensure Handlebars does not escape the HTML. - - ```javascript - Ember.String.htmlSafe('
someString
') - ``` - - @method htmlSafe - @for Ember.String - @static - @return {Handlebars.SafeString} a string that will not be html escaped by Handlebars -*/ + * Mark a string as safe for unescaped output with Handlebars. If you + * return HTML from a Handlebars helper, use this function to + * ensure Handlebars does not escape the HTML. + * + * ```javascript + * Ember.String.htmlSafe('
someString
') + * ``` + * + * @method htmlSafe + * @for Ember.String + * @static + * @return {Handlebars.SafeString} a string that will not be html escaped by Handlebars + */ Ember.String.htmlSafe = function(str) { return new Handlebars.SafeString(str); }; @@ -24393,18 +21089,18 @@ var htmlSafe = Ember.String.htmlSafe; if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) { /** - Mark a string as being safe for unescaped output with Handlebars. - - ```javascript - '
someString
'.htmlSafe() - ``` - - See [Ember.String.htmlSafe](/api/classes/Ember.String.html#method_htmlSafe). - - @method htmlSafe - @for String - @return {Handlebars.SafeString} a string that will not be html escaped by Handlebars - */ + * Mark a string as being safe for unescaped output with Handlebars. + * + * ```javascript + * '
someString
'.htmlSafe() + * ``` + * + * See `Ember.String.htmlSafe`. + * + * @method htmlSafe + * @for String + * @return {Handlebars.SafeString} a string that will not be html escaped by Handlebars + */ String.prototype.htmlSafe = function() { return htmlSafe(this); }; @@ -24584,8 +21280,6 @@ function SimpleHandlebarsView(path, pathRoot, isEscaped, templateData) { this.morph = Metamorph(); this.state = 'preRender'; this.updateId = null; - this._parentView = null; - this.buffer = null; } Ember._SimpleHandlebarsView = SimpleHandlebarsView; @@ -24599,11 +21293,7 @@ SimpleHandlebarsView.prototype = { Ember.run.cancel(this.updateId); this.updateId = null; } - if (this._parentView) { - this._parentView.removeChild(this); - } this.morph = null; - this.state = 'destroyed'; }, propertyWillChange: Ember.K, @@ -24658,7 +21348,7 @@ SimpleHandlebarsView.prototype = { rerender: function() { switch(this.state) { case 'preRender': - case 'destroyed': + case 'destroying': break; case 'inBuffer': throw new Ember.Error("Something you did tried to replace an {{expression}} before it was inserted into the DOM."); @@ -25173,7 +21863,7 @@ EmberHandlebars.registerHelper('with', function(context, options) { /** - See [boundIf](/api/classes/Ember.Handlebars.helpers.html#method_boundIf) + See `boundIf` @method if @for Ember.Handlebars.helpers @@ -25208,11 +21898,11 @@ EmberHandlebars.registerHelper('unless', function(context, options) { }); /** - `bind-attr` allows you to create a binding between DOM element attributes and + `bindAttr` allows you to create a binding between DOM element attributes and Ember objects. For example: ```handlebars - imageTitle + imageTitle ``` The above handlebars template will fill the ``'s `src` attribute will @@ -25234,17 +21924,17 @@ EmberHandlebars.registerHelper('unless', function(context, options) { A humorous image of a cat ``` - `bind-attr` cannot redeclare existing DOM element attributes. The use of `src` - in the following `bind-attr` example will be ignored and the hard coded value + `bindAttr` cannot redeclare existing DOM element attributes. The use of `src` + in the following `bindAttr` example will be ignored and the hard coded value of `src="/failwhale.gif"` will take precedence: ```handlebars - imageTitle + imageTitle ``` - ### `bind-attr` and the `class` attribute + ### `bindAttr` and the `class` attribute - `bind-attr` supports a special syntax for handling a number of cases unique + `bindAttr` supports a special syntax for handling a number of cases unique to the `class` DOM element attribute. The `class` attribute combines multiple discreet values into a single attribute as a space-delimited list of strings. Each string can be: @@ -25253,7 +21943,7 @@ EmberHandlebars.registerHelper('unless', function(context, options) { * a boolean return value of an object's property * a hard-coded value - A string return value works identically to other uses of `bind-attr`. The + A string return value works identically to other uses of `bindAttr`. The return value of the property will become the value of the attribute. For example, the following view and template: @@ -25266,7 +21956,7 @@ EmberHandlebars.registerHelper('unless', function(context, options) { ``` ```handlebars - ``` Result in the following rendered output: @@ -25288,7 +21978,7 @@ EmberHandlebars.registerHelper('unless', function(context, options) { ``` ```handlebars - + ``` Result in the following rendered output: @@ -25302,14 +21992,14 @@ EmberHandlebars.registerHelper('unless', function(context, options) { value changes: ```handlebars - + ``` A hard-coded value can be used by prepending `:` to the desired class name: `:class-name-to-always-apply`. ```handlebars - + ``` Results in the following rendered output: @@ -25322,19 +22012,19 @@ EmberHandlebars.registerHelper('unless', function(context, options) { hard-coded value – can be combined in a single declaration: ```handlebars - + ``` - @method bind-attr + @method bindAttr @for Ember.Handlebars.helpers @param {Hash} options @return {String} HTML string */ -EmberHandlebars.registerHelper('bind-attr', function(options) { +EmberHandlebars.registerHelper('bindAttr', function(options) { var attrs = options.hash; - Ember.assert("You must specify at least one hash argument to bind-attr", !!Ember.keys(attrs).length); + Ember.assert("You must specify at least one hash argument to bindAttr", !!Ember.keys(attrs).length); var view = options.data.view; var ret = []; @@ -25396,7 +22086,7 @@ EmberHandlebars.registerHelper('bind-attr', function(options) { // When the observer fires, find the element using the // unique data id and update the attribute to the new value. // Note: don't add observer when path is 'this' or path - // is whole keyword e.g. {{#each x in list}} ... {{bind-attr attr="x"}} + // is whole keyword e.g. {{#each x in list}} ... {{bindAttr attr="x"}} if (path !== 'this' && !(normalized.isKeyword && normalized.path === '' )) { view.registerObserver(normalized.root, normalized.path, observer); } @@ -25416,18 +22106,6 @@ EmberHandlebars.registerHelper('bind-attr', function(options) { return new EmberHandlebars.SafeString(ret.join(' ')); }); -/** - See `bind-attr` - - @method bindAttr - @for Ember.Handlebars.helpers - @deprecated - @param {Function} context - @param {Hash} options - @return {String} HTML string -*/ -EmberHandlebars.registerHelper('bindAttr', EmberHandlebars.helpers['bind-attr']); - /** @private @@ -25907,8 +22585,8 @@ var get = Ember.get, handlebarsGet = Ember.Handlebars.get, fmt = Ember.String.fm /** `{{collection}}` is a `Ember.Handlebars` helper for adding instances of - `Ember.CollectionView` to a template. See [Ember.CollectionView](/api/classes/Ember.CollectionView.html) - for additional information on how a `CollectionView` functions. + `Ember.CollectionView` to a template. See `Ember.CollectionView` for + additional information on how a `CollectionView` functions. `{{collection}}`'s primary use is as a block helper with a `contentBinding` option pointing towards an `Ember.Array`-compatible object. An `Ember.View` @@ -26361,8 +23039,6 @@ GroupedEach.prototype = { }, addArrayObservers: function() { - if (!this.content) { return; } - this.content.addArrayObserver(this, { willChange: 'contentArrayWillChange', didChange: 'contentArrayDidChange' @@ -26370,8 +23046,6 @@ GroupedEach.prototype = { }, removeArrayObservers: function() { - if (!this.content) { return; } - this.content.removeArrayObserver(this, { willChange: 'contentArrayWillChange', didChange: 'contentArrayDidChange' @@ -26389,8 +23063,6 @@ GroupedEach.prototype = { }, render: function() { - if (!this.content) { return; } - var content = this.content, contentLength = get(content, 'length'), data = this.options.data, @@ -26403,21 +23075,12 @@ GroupedEach.prototype = { }, rerenderContainingView: function() { - var self = this; - Ember.run.scheduleOnce('render', this, function() { - // It's possible it's been destroyed after we enqueued a re-render call. - if (!self.destroyed) { - self.containingView.rerender(); - } - }); + Ember.run.scheduleOnce('render', this.containingView, 'rerender'); }, destroy: function() { this.removeContentObservers(); - if (this.content) { - this.removeArrayObservers(); - } - this.destroyed = true; + this.removeArrayObservers(); } }; @@ -26541,49 +23204,6 @@ GroupedEach.prototype = { Each itemController will receive a reference to the current controller as a `parentController` property. - ### (Experimental) Grouped Each - - When used in conjunction with the experimental [group helper](https://github.com/emberjs/group-helper), - you can inform Handlebars to re-render an entire group of items instead of - re-rendering them one at a time (in the event that they are changed en masse - or an item is added/removed). - - ```handlebars - {{#group}} - {{#each people}} - {{firstName}} {{lastName}} - {{/each}} - {{/group}} - ``` - - This can be faster than the normal way that Handlebars re-renders items - in some cases. - - If for some reason you have a group with more than one `#each`, you can make - one of the collections be updated in normal (non-grouped) fashion by setting - the option `groupedRows=true` (counter-intuitive, I know). - - For example, - - ```handlebars - {{dealershipName}} - - {{#group}} - {{#each dealers}} - {{firstName}} {{lastName}} - {{/each}} - - {{#each car in cars groupedRows=true}} - {{car.make}} {{car.model}} {{car.color}} - {{/each}} - {{/group}} - ``` - Any change to `dealershipName` or the `dealers` collection will cause the - entire group to be re-rendered. However, changes to the `cars` collection - will be re-rendered individually (as normal). - - Note that `group` behavior is also disabled by specifying an `itemViewClass`. - @method each @for Ember.Handlebars.helpers @param [name] {String} name for item (used with `in`) @@ -26591,7 +23211,6 @@ GroupedEach.prototype = { @param [options] {Object} Handlebars key/value pairs of options @param [options.itemViewClass] {String} a path to a view class used for each item @param [options.itemController] {String} name of a controller to be created for each item - @param [options.groupedRows] {boolean} enable normal item-by-item rendering when inside a `#group` helper */ Ember.Handlebars.registerHelper('each', function(path, options) { if (arguments.length === 4) { @@ -26748,11 +23367,6 @@ Ember.Handlebars.registerHelper('partial', function(name, options) { var get = Ember.get, set = Ember.set; /** - - `{{yield}}` denotes an area of a template that will be rendered inside - of another template. It has two main uses: - - ### Use with `layout` When used in a Handlebars template that is assigned to an `Ember.View` instance's `layout` property Ember will render the layout template first, inserting the view's own rendered output at the `{{yield}}` location. @@ -26795,34 +23409,7 @@ var get = Ember.get, set = Ember.set; bView.appendTo('body'); // throws - // Uncaught Error: assertion failed: - // You called yield in a template that was not a layout - ``` - - ### Use with Ember.Component - When designing components `{{yield}}` is used to denote where, inside the component's - template, an optional block passed to the component should render: - - ```handlebars - - {{#labeled-textfield value=someProperty}} - First name: - {{/my-component}} - ``` - - ```handlebars - - - ``` - - Result: - - ```html -