summaryrefslogtreecommitdiff
path: root/public
diff options
context:
space:
mode:
authorStefan Wintermeyer <stefan.wintermeyer@amooma.de>2013-04-03 22:08:19 +0200
committerStefan Wintermeyer <stefan.wintermeyer@amooma.de>2013-04-03 22:09:33 +0200
commitcea2cc3c1cc1e48fc4600c698d52dfda2bde4505 (patch)
treee216a9634c31047e8908a882aed9ff2545ed99e6 /public
parent64653a9149eca977c16233abb0a472730b94a464 (diff)
Massive changes to the switchboard.
Diffstat (limited to 'public')
-rw-r--r--public/js/app.js45
-rw-r--r--public/js/libs/ember-data.js883
-rw-r--r--public/js/libs/ember.js3095
3 files changed, 2814 insertions, 1209 deletions
diff --git a/public/js/app.js b/public/js/app.js
index 87a4ba6..4bc8cc1 100644
--- a/public/js/app.js
+++ b/public/js/app.js
@@ -9,6 +9,11 @@ App = Ember.Application.create({
var switchboard = App.Switchboard.find(switchboard_id);
setInterval(function() {
switchboard.reload();
+
+ // var switchboard_entries = App.SwitchboardEntry.find();
+ // switchboard_entries.forEach(function(switchboard_entry) {
+ // switchboard_entry.reload();
+ // });
}, reload_interval);
}
}
@@ -29,25 +34,57 @@ App.SwitchboardRoute = Ember.Route.extend({
// Models
App.Store = DS.Store.extend({
- revision: 11
+ revision: 12
});
DS.RESTAdapter.configure("plurals", {
switchboard_entry: "switchboard_entries"
});
+DS.RESTAdapter.reopen({
+ namespace: 'api/v1'
+});
+
App.Switchboard = DS.Model.extend({
switchboardEntrys: DS.hasMany('App.SwitchboardEntry'),
- name: DS.attr('string'),
+ name: DS.attr('string')
});
App.SwitchboardEntry = DS.Model.extend({
switchboard: DS.belongsTo('App.Switchboard'),
- switchboard: DS.belongsTo('App.SipAccount'),
+ sipAccount: DS.belongsTo('App.SipAccount'),
name: DS.attr('string'),
+ path_to_user: DS.attr('string'),
+ avatar_src: DS.attr('string'),
+ callstate: DS.attr('string')
});
+App.Adapter = DS.RESTAdapter.extend();
+
+App.store = App.Store.create({
+ adapter: App.Adapter.create()
+});
+
+App.store.adapter.serializer.configure(App.SwitchboardEntry, { sideloadAs: 'switchboard_entries' });
+
App.SipAccount = DS.Model.extend({
switchboardEntrys: DS.hasMany('App.SwitchboardEntry'),
+ phoneNumbers: DS.hasMany('App.PhoneNumber'),
callerName: DS.attr('string'),
-}); \ No newline at end of file
+ authName: DS.attr('string')
+});
+
+App.PhoneNumber = DS.Model.extend({
+ name: DS.attr('string'),
+ number: DS.attr('string')
+});
+
+App.store.adapter.serializer.configure(App.PhoneNumber, { sideloadAs: 'phone_numbers' });
+
+Ember.Handlebars.registerBoundHelper('avatar_img', function(value) {
+ return new Handlebars.SafeString('<img alt="Avatar image" class="img-rounded" src="' + value + '" style="width: 100px;">');
+});
+
+Ember.Handlebars.registerBoundHelper('show_callstate', function(value) {
+ return new Handlebars.SafeString('<span class="label">' + value + '</span>');
+});
diff --git a/public/js/libs/ember-data.js b/public/js/libs/ember-data.js
index 6db1c0f..00a6e25 100644
--- a/public/js/libs/ember-data.js
+++ b/public/js/libs/ember-data.js
@@ -1,7 +1,10 @@
+// Last commit: 5fd6d65 (2013-03-28 01:13:50 +0100)
+
+
(function() {
window.DS = Ember.Namespace.create({
- // this one goes to 11
- CURRENT_API_REVISION: 11
+ // this one goes past 11
+ CURRENT_API_REVISION: 12
});
})();
@@ -746,7 +749,7 @@ var classify = Ember.String.classify, get = Ember.get;
For example, the DS.Adapter class can behave like a map, with
more semantic API, via the `map` API:
- DS.Adapter.map('App.Person', { firstName: { keyName: 'FIRST' } });
+ DS.Adapter.map('App.Person', { firstName: { key: 'FIRST' } });
Class configuration via a map-like API has a few common requirements
that differentiate it from the standard Ember.Map implementation.
@@ -846,7 +849,7 @@ DS._Mappable = Ember.Mixin.create({
instanceMap.set(transformedKey, newValue);
}
- },
+ }
});
@@ -970,6 +973,8 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
// `isLoaded` and fires a `didLoad` event.
this.loadingRecordArrays = {};
+ this._recordsToSave = Ember.OrderedSet.create();
+
set(this, 'defaultTransaction', this.transaction());
},
@@ -1542,7 +1547,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
if (!Ember.isArray(ids)) {
var adapter = this.adapterForType(type);
if (adapter && adapter.findHasMany) { adapter.findHasMany(this, record, relationship, ids); }
- else { throw fmt("Adapter is either null or does not implement `findHasMany` method", this); }
+ else if (ids !== undefined) { throw fmt("Adapter is either null or does not implement `findHasMany` method", this); }
return this.createManyArray(type, Ember.A());
}
@@ -1780,6 +1785,39 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
}
},
+ // .................
+ // . BASIC ADAPTER .
+ // .................
+
+ scheduleSave: function(record) {
+ this._recordsToSave.add(record);
+ Ember.run.once(this, 'flushSavedRecords');
+ },
+
+ flushSavedRecords: function() {
+ var created = Ember.OrderedSet.create();
+ var updated = Ember.OrderedSet.create();
+ var deleted = Ember.OrderedSet.create();
+
+ this._recordsToSave.forEach(function(record) {
+ if (get(record, 'isNew')) {
+ created.add(record);
+ } else if (get(record, 'isDeleted')) {
+ deleted.add(record);
+ } else {
+ updated.add(record);
+ }
+ });
+
+ this._recordsToSave.clear();
+
+ get(this, '_adapter').commit(this, {
+ created: created,
+ updated: updated,
+ deleted: deleted
+ });
+ },
+
// ..............
// . PERSISTING .
// ..............
@@ -2268,7 +2306,6 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
}
var content = get(array, 'content');
- var alreadyInArray = content.indexOf(clientId) !== -1;
var recordArrays = this.recordArraysForClientId(clientId);
var reference = this.referenceForClientId(clientId);
@@ -2413,7 +2450,6 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
if (prematerialized && prematerialized.id) {
id = prematerialized.id;
} else if (id === undefined) {
- var adapter = this.adapterForType(type);
id = this.preprocessData(type, data);
}
@@ -2470,7 +2506,10 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
// TODO (tomdale) this assumes that loadHasMany *always* means
// that the records for the provided IDs are loaded.
- if (relationship) { set(relationship, 'isLoaded', true); }
+ if (relationship) {
+ set(relationship, 'isLoaded', true);
+ relationship.trigger('didLoad');
+ }
},
/** @private
@@ -2492,6 +2531,8 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
clientIds = typeMap.clientIds,
cidToData = this.clientIdToData;
+ Ember.assert('The id ' + id + ' has already been used with another record of type ' + type.toString() + '.', !id || !idToClientIdMap[id]);
+
var clientId = ++this.clientIdCounter;
cidToData[clientId] = data;
@@ -2518,7 +2559,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
this.recordCache[clientId] = record = type._create({
store: this,
- clientId: clientId,
+ clientId: clientId
});
set(record, 'id', id);
@@ -2546,12 +2587,10 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
if (id) { delete typeMap.idToCid[id]; }
},
- destroy: function() {
+ willDestroy: function() {
if (get(DS, 'defaultStore') === this) {
set(DS, 'defaultStore', null);
}
-
- return this._super();
},
// ........................
@@ -2722,7 +2761,7 @@ DS.Store.reopenClass({
(function() {
-var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor,
+var get = Ember.get, set = Ember.set,
once = Ember.run.once, arrayMap = Ember.ArrayPolyfills.map;
/**
@@ -2883,14 +2922,6 @@ var stateProperty = Ember.computed(function(key) {
}
}).property();
-var isEmptyObject = function(object) {
- for (var name in object) {
- if (object.hasOwnProperty(name)) { return false; }
- }
-
- return true;
-};
-
var hasDefinedProperties = function(object) {
for (var name in object) {
if (object.hasOwnProperty(name) && object[name]) { return true; }
@@ -2917,12 +2948,6 @@ var didSetProperty = function(manager, context) {
change.sync();
};
-// Whenever a property is set, recompute all dependent filters
-var updateRecordArrays = function(manager) {
- var record = manager.get('record');
- record.updateRecordArraysLater();
-};
-
DS.State = Ember.State.extend({
isLoaded: stateProperty,
isReloading: stateProperty,
@@ -3267,7 +3292,7 @@ var states = {
exit: function(manager) {
var record = get(manager, 'record');
- Ember.run.once(function() {
+ once(function() {
record.trigger('didLoad');
});
}
@@ -3503,11 +3528,11 @@ DS.StateManager = Ember.StateManager.extend({
(function() {
var LoadPromise = DS.LoadPromise; // system/mixins/load_promise
-var get = Ember.get, set = Ember.set, none = Ember.isNone, map = Ember.EnumerableUtils.map;
+var get = Ember.get, set = Ember.set, map = Ember.EnumerableUtils.map;
-var retrieveFromCurrentState = Ember.computed(function(key) {
+var retrieveFromCurrentState = Ember.computed(function(key, value) {
return get(get(this, 'stateManager.currentState'), key);
-}).property('stateManager.currentState');
+}).property('stateManager.currentState').readOnly();
DS.Model = Ember.Object.extend(Ember.Evented, LoadPromise, {
isLoaded: retrieveFromCurrentState,
@@ -3542,6 +3567,11 @@ DS.Model = Ember.Object.extend(Ember.Evented, LoadPromise, {
return store.serialize(this, options);
},
+ toJSON: function(options) {
+ var serializer = DS.JSONSerializer.create();
+ return serializer.serialize(this, options);
+ },
+
didLoad: Ember.K,
didReload: Ember.K,
didUpdate: Ember.K,
@@ -3634,6 +3664,9 @@ DS.Model = Ember.Object.extend(Ember.Evented, LoadPromise, {
clearRelationships: function() {
this.eachRelationship(function(name, relationship) {
+ // if the relationship is unmaterialized, move on
+ if (this.cacheFor(name) === undefined) { return; }
+
if (relationship.kind === 'belongsTo') {
set(this, name, null);
} else if (relationship.kind === 'hasMany') {
@@ -3782,6 +3815,12 @@ DS.Model = Ember.Object.extend(Ember.Evented, LoadPromise, {
becameInFlight: function() {
},
+ // FOR USE BY THE BASIC ADAPTER
+
+ save: function() {
+ this.get('store').scheduleSave(this);
+ },
+
// FOR USE DURING COMMIT PROCESS
adapterDidUpdateAttribute: function(attributeName, value) {
@@ -3835,6 +3874,7 @@ var storeAlias = function(methodName) {
args = [].slice.call(arguments);
args.unshift(this);
+ Ember.assert("Your application does not have a 'Store' property defined. Attempts to call '" + methodName + "' on model classes will fail. Please provide one as with 'YourAppName.Store = DS.Store.extend()'", !!store);
return store[methodName].apply(store, args);
};
};
@@ -3843,6 +3883,7 @@ DS.Model.reopenClass({
isLoaded: storeAlias('recordIsLoaded'),
find: storeAlias('find'),
all: storeAlias('all'),
+ query: storeAlias('findQuery'),
filter: storeAlias('filter'),
_create: DS.Model.create,
@@ -3941,8 +3982,6 @@ DS.attr = function(type, options) {
};
return Ember.computed(function(key, value, oldValue) {
- var data;
-
if (arguments.length > 1) {
Ember.assert("You may not set `id` as an attribute on your model. Please remove any lines that look like: `id: DS.attr('<type>')` from " + this.constructor.toString(), key !== 'id');
} else {
@@ -4063,7 +4102,7 @@ var hasRelationship = function(type, options) {
}
ids = data[key];
- relationship = store.findMany(type, ids || [], this, meta);
+ relationship = store.findMany(type, ids, this, meta);
set(relationship, 'owner', this);
set(relationship, 'name', key);
@@ -4180,7 +4219,6 @@ DS.Model.reopenClass({
App.Blog = DS.Model.extend({
users: DS.hasMany(App.User),
owner: DS.belongsTo(App.User),
-
posts: DS.hasMany(App.Post)
});
@@ -4257,6 +4295,52 @@ DS.Model.reopenClass({
}),
/**
+ An array of types directly related to a model. Each type will be
+ included once, regardless of the number of relationships it has with
+ the model.
+
+ For example, given a model with this definition:
+
+ App.Blog = DS.Model.extend({
+ users: DS.hasMany(App.User),
+ owner: DS.belongsTo(App.User),
+ posts: DS.hasMany(App.Post)
+ });
+
+ This property would contain the following:
+
+ var relatedTypes = Ember.get(App.Blog, 'relatedTypes');
+ //=> [ App.User, App.Post ]
+
+ @type Ember.Array
+ @readOnly
+ */
+ relatedTypes: Ember.computed(function() {
+ var type,
+ types = Ember.A([]);
+
+ // Loop through each computed property on the class,
+ // and create an array of the unique types involved
+ // in relationships
+ this.eachComputedProperty(function(name, meta) {
+ if (meta.isRelationship) {
+ type = meta.type;
+
+ if (typeof type === 'string') {
+ type = get(this, type, false) || get(Ember.lookup, type);
+ }
+
+ if (!types.contains(type)) {
+ Ember.assert("Trying to sideload " + name + " on " + this.toString() + " but the type doesn't exist.", !!type);
+ types.push(type);
+ }
+ }
+ });
+
+ return types;
+ }),
+
+ /**
A map whose keys are the relationships of a model and whose values are
relationship descriptors.
@@ -4357,6 +4441,21 @@ DS.Model.reopenClass({
get(this, 'relationshipsByName').forEach(function(name, relationship) {
callback.call(binding, name, relationship);
});
+ },
+
+ /**
+ Given a callback, iterates over each of the types related to a model,
+ invoking the callback with the related type's class. Each type will be
+ returned just once, regardless of how many different relationships it has
+ with a model.
+
+ @param {Function} callback the callback to invoke
+ @param {any} binding the value to which the callback's `this` should be bound
+ */
+ eachRelatedType: function(callback, binding) {
+ get(this, 'relatedTypes').forEach(function(type) {
+ callback.call(binding, type);
+ });
}
});
@@ -4447,6 +4546,8 @@ var get = Ember.get, set = Ember.set;
var forEach = Ember.EnumerableUtils.forEach;
DS.RelationshipChange = function(options) {
+ this.parentReference = options.parentReference;
+ this.childReference = options.childReference;
this.firstRecordReference = options.firstRecordReference;
this.firstRecordKind = options.firstRecordKind;
this.firstRecordName = options.firstRecordName;
@@ -4502,7 +4603,7 @@ DS.RelationshipChange.determineRelationshipType = function(recordType, knownSide
var knownContainerType = knownSide.kind;
var options = recordType.metaForProperty(knownKey).options;
var otherType = DS._inverseTypeFor(recordType, knownKey);
-
+
if(options.inverse){
key = options.inverse;
otherContainerType = get(otherType, 'relationshipsByName').get(key).kind;
@@ -4536,10 +4637,10 @@ DS.RelationshipChange.createChange = function(firstRecordReference, secondRecord
return DS.OneToManyChange.createChange(secondRecordReference, firstRecordReference, store, options);
}
else if (changeType === "oneToNone"){
- return DS.OneToNoneChange.createChange(firstRecordReference, {}, store, options);
+ return DS.OneToNoneChange.createChange(firstRecordReference, secondRecordReference, store, options);
}
else if (changeType === "manyToNone"){
- return DS.ManyToNoneChange.createChange(firstRecordReference, {}, store, options);
+ return DS.ManyToNoneChange.createChange(firstRecordReference, secondRecordReference, store, options);
}
else if (changeType === "oneToOne"){
return DS.OneToOneChange.createChange(firstRecordReference, secondRecordReference, store, options);
@@ -4553,6 +4654,8 @@ DS.RelationshipChange.createChange = function(firstRecordReference, secondRecord
DS.OneToNoneChange.createChange = function(childReference, parentReference, store, options) {
var key = options.key;
var change = DS.RelationshipChange._createChange({
+ parentReference: parentReference,
+ childReference: childReference,
firstRecordReference: childReference,
store: store,
changeType: options.changeType,
@@ -4563,12 +4666,14 @@ DS.OneToNoneChange.createChange = function(childReference, parentReference, stor
store.addRelationshipChangeFor(childReference, key, parentReference, null, change);
return change;
-};
+};
/** @private */
DS.ManyToNoneChange.createChange = function(childReference, parentReference, store, options) {
var key = options.key;
var change = DS.RelationshipChange._createChange({
+ parentReference: childReference,
+ childReference: parentReference,
secondRecordReference: childReference,
store: store,
changeType: options.changeType,
@@ -4578,14 +4683,14 @@ DS.ManyToNoneChange.createChange = function(childReference, parentReference, sto
store.addRelationshipChangeFor(childReference, key, parentReference, null, change);
return change;
-};
+};
/** @private */
DS.ManyToManyChange.createChange = function(childReference, parentReference, store, options) {
// Get the type of the child based on the child's client ID
var childType = childReference.type, key;
-
+
// If the name of the belongsTo side of the relationship is specified,
// use that
// If the type of the parent is specified, look it up on the child's type
@@ -4593,6 +4698,8 @@ DS.ManyToManyChange.createChange = function(childReference, parentReference, sto
key = options.key;
var change = DS.RelationshipChange._createChange({
+ parentReference: parentReference,
+ childReference: childReference,
firstRecordReference: childReference,
secondRecordReference: parentReference,
firstRecordKind: "hasMany",
@@ -4612,7 +4719,7 @@ DS.ManyToManyChange.createChange = function(childReference, parentReference, sto
DS.OneToOneChange.createChange = function(childReference, parentReference, store, options) {
// Get the type of the child based on the child's client ID
var childType = childReference.type, key;
-
+
// If the name of the belongsTo side of the relationship is specified,
// use that
// If the type of the parent is specified, look it up on the child's type
@@ -4627,6 +4734,8 @@ DS.OneToOneChange.createChange = function(childReference, parentReference, store
}
var change = DS.RelationshipChange._createChange({
+ parentReference: parentReference,
+ childReference: childReference,
firstRecordReference: childReference,
secondRecordReference: parentReference,
firstRecordKind: "belongsTo",
@@ -4663,7 +4772,7 @@ DS.OneToOneChange.maintainInvariant = function(options, store, childReference, k
DS.OneToManyChange.createChange = function(childReference, parentReference, store, options) {
// Get the type of the child based on the child's client ID
var childType = childReference.type, key;
-
+
// If the name of the belongsTo side of the relationship is specified,
// use that
// If the type of the parent is specified, look it up on the child's type
@@ -4678,6 +4787,8 @@ DS.OneToManyChange.createChange = function(childReference, parentReference, stor
}
var change = DS.RelationshipChange._createChange({
+ parentReference: parentReference,
+ childReference: childReference,
firstRecordReference: childReference,
secondRecordReference: parentReference,
firstRecordKind: "belongsTo",
@@ -4764,13 +4875,13 @@ DS.RelationshipChange.prototype = {
/** @private */
destroy: function() {
- var childReference = this.firstRecordReference,
+ var childReference = this.childReference,
belongsToName = this.getFirstRecordName(),
hasManyName = this.getSecondRecordName(),
store = this.store,
child, oldParent, newParent, lastParent, transaction;
- store.removeRelationshipChangeFor(childReference, belongsToName, this.secondRecordReference, hasManyName, this.changeType);
+ store.removeRelationshipChangeFor(childReference, belongsToName, this.parentReference, hasManyName, this.changeType);
if (transaction = this.transaction) {
transaction.relationshipBecameClean(this);
@@ -4920,7 +5031,9 @@ DS.RelationshipChangeRemove.prototype.sync = function() {
if (secondRecord && firstRecord) {
if(this.secondRecordKind === "belongsTo"){
+ secondRecord.suspendRelationshipObservers(function(){
set(secondRecord, secondRecordName, null);
+ });
}
else if(this.secondRecordKind === "hasMany"){
secondRecord.suspendRelationshipObservers(function(){
@@ -5050,7 +5163,7 @@ Ember.onLoad('Ember.Application', function(Application) {
name: "store",
initialize: function(container, application) {
- container.register('store', 'main', application.Store);
+ application.register('store:main', application.Store);
// Eagerly generate the store so defaultStore is populated.
// TODO: Do this in a finisher hook
@@ -5061,9 +5174,9 @@ Ember.onLoad('Ember.Application', function(Application) {
Application.initializer({
name: "injectStore",
- initialize: function(container) {
- container.typeInjection('controller', 'store', 'store:main');
- container.typeInjection('route', 'store', 'store:main');
+ initialize: function(container, application) {
+ application.inject('controller', 'store', 'store:main');
+ application.inject('route', 'store', 'store:main');
}
});
}
@@ -5138,20 +5251,29 @@ function mustImplement(name) {
by implementing `keyForAttributeName`:
```javascript
- keyForAttributeName: function(type, name) {
- return name.underscore.toUpperCase();
- }
+ keyForAttributeName: function(type, name) {
+ return name.underscore.toUpperCase();
+ }
```
If your attribute names are not predictable, you can re-map them
- one-by-one using the `map` API:
+ one-by-one using the adapter's `map` API:
```javascript
- App.Person.map('App.Person', {
+ App.Adapter.map('App.Person', {
firstName: { key: '*API_USER_FIRST_NAME*' }
});
```
+ This API will also work for relationships and primary keys. For
+ example:
+
+ ```javascript
+ App.Adapter.map('App.Person', {
+ primaryKey: '_id'
+ });
+ ```
+
## Serialization
During the serialization process, a record or records are converted
@@ -5608,7 +5730,7 @@ DS.Serializer = Ember.Object.extend({
primaryKey: function(type) {
// If the type is `BlogPost`, this will return
// `blog_post_id`.
- var typeString = type.toString.split(".")[1].underscore();
+ var typeString = type.toString().split(".")[1].underscore();
return typeString + "_id";
}
});
@@ -5986,10 +6108,10 @@ DS.Serializer = Ember.Object.extend({
registerEnumTransform: function(type, objects) {
var transform = {
deserialize: function(deserialized) {
- return objects.objectAt(deserialized);
+ return Ember.A(objects).objectAt(deserialized);
},
serialize: function(serialized) {
- return objects.indexOf(serialized);
+ return Ember.EnumerableUtils.indexOf(objects, serialized);
},
values: objects
};
@@ -6240,8 +6362,6 @@ DS.JSONTransforms = {
return dayOfWeek + ", " + dayOfMonth + " " + month + " " + utcYear + " " +
pad(utcHours) + ":" + pad(utcMinutes) + ":" + pad(utcSeconds) + " GMT";
- } else if (date === undefined) {
- return undefined;
} else {
return null;
}
@@ -6283,6 +6403,13 @@ DS.JSONSerializer = DS.Serializer.extend({
if (sideloadAs) {
this.sideloadMapping.set(sideloadAs, type);
+
+ // Set a flag indicating that mappings may need to be normalized
+ // (i.e. converted from strings -> types) before sideloading.
+ // We can't do this conversion immediately here, because `configure`
+ // may be called before certain types have been defined.
+ this.sideloadMapping.normalized = false;
+
delete configuration.sideloadAs;
}
@@ -6352,7 +6479,7 @@ DS.JSONSerializer = DS.Serializer.extend({
if (this.embeddedType(type, name)) {
if (embeddedChild = get(record, name)) {
- value = this.serialize(embeddedChild, { include: true });
+ value = this.serialize(embeddedChild, { includeId: true });
}
hash[key] = value;
@@ -6444,46 +6571,88 @@ DS.JSONSerializer = DS.Serializer.extend({
}
},
+ /**
+ @private
+
+ Iterates over the `json` payload and attempts to load any data
+ included alongside `root`.
+
+ The keys expected for sideloaded data are based upon the types related
+ to the root model. Recursion is used to ensure that types related to
+ related types can be loaded as well. Any custom keys specified by
+ `sideloadAs` mappings will also be respected.
+
+ @param {DS.Store subclass} loader
+ @param {DS.Model subclass} type
+ @param {Object} json
+ @param {String} root
+ */
sideload: function(loader, type, json, root) {
- var sideloadedType, mappings, loaded = {};
+ var sideloadedType;
- loaded[root] = true;
+ this.normalizeSideloadMappings();
+ this.configureSideloadMappingForType(type);
for (var prop in json) {
- if (!json.hasOwnProperty(prop)) { continue; }
- if (prop === root) { continue; }
- if (prop === this.configOption(type, 'meta')) { continue; }
+ if (!json.hasOwnProperty(prop) ||
+ prop === root ||
+ prop === this.configOption(type, 'meta')) {
+ continue;
+ }
- sideloadedType = type.typeForRelationship(prop);
+ sideloadedType = this.sideloadMapping.get(prop);
+ Ember.assert("Your server returned a hash with the key " + prop +
+ " but you have no mapping for it",
+ !!sideloadedType);
- if (!sideloadedType) {
- sideloadedType = this.sideloadMapping.get(prop);
+ this.loadValue(loader, sideloadedType, json[prop]);
+ }
+ },
- if (typeof sideloadedType === 'string') {
- sideloadedType = get(Ember.lookup, sideloadedType);
- }
+ /**
+ @private
- Ember.assert("Your server returned a hash with the key " + prop + " but you have no mapping for it", !!sideloadedType);
- }
+ Iterates over all the `sideloadAs` mappings and converts any that are
+ strings to their equivalent types.
- this.sideloadRelationships(loader, sideloadedType, json, prop, loaded);
+ This is an optimization used to avoid performing lookups for every
+ call to `sideload`.
+ */
+ normalizeSideloadMappings: function() {
+ if (! this.sideloadMapping.normalized) {
+ this.sideloadMapping.forEach(function(key, value) {
+ if (typeof value === 'string') {
+ this.sideloadMapping.set(key, get(Ember.lookup, value));
+ }
+ }, this);
+ this.sideloadMapping.normalized = true;
}
},
- sideloadRelationships: function(loader, type, json, prop, loaded) {
- loaded[prop] = true;
+ /**
+ @private
- get(type, 'relationshipsByName').forEach(function(key, meta) {
- key = meta.key || key;
- if (meta.kind === 'belongsTo') {
- key = this.pluralize(key);
- }
- if (json[key] && !loaded[key]) {
- this.sideloadRelationships(loader, meta.type, json, key, loaded);
+ Configures possible sideload mappings for the types related to a
+ particular model. This recursive method ensures that sideloading
+ works for related models as well.
+
+ @param {DS.Model subclass} type
+ @param {Ember.A} configured an array of types that have already been configured
+ */
+ configureSideloadMappingForType: function(type, configured) {
+ if (!configured) {configured = Ember.A([]);}
+ configured.pushObject(type);
+
+ type.eachRelatedType(function(relatedType) {
+ if (!configured.contains(relatedType)) {
+ var root = this.sideloadMappingForType(relatedType);
+ if (!root) {
+ root = this.defaultSideloadRootForType(relatedType);
+ this.sideloadMapping.set(root, relatedType);
+ }
+ this.configureSideloadMappingForType(relatedType, configured);
}
}, this);
-
- this.loadValue(loader, type, json[prop]);
},
loadValue: function(loader, type, value) {
@@ -6505,6 +6674,36 @@ DS.JSONSerializer = DS.Serializer.extend({
return (plurals && plurals[name]) || name + "s";
},
+ // use the same plurals hash to determine
+ // special-case singularization
+ singularize: function(name) {
+ var plurals = this.configurations.get('plurals');
+ if (plurals) {
+ for (var i in plurals) {
+ if (plurals[i] === name) {
+ return i;
+ }
+ }
+ }
+ if (name.lastIndexOf('s') === name.length - 1) {
+ return name.substring(0, name.length - 1);
+ } else {
+ return name;
+ }
+ },
+
+ /**
+ @private
+
+ Determines the singular root name for a particular type.
+
+ This is an underscored, lowercase version of the model name.
+ For example, the type `App.UserGroup` will have the root
+ `user_group`.
+
+ @param {DS.Model subclass} type
+ @returns {String} name of the root element
+ */
rootForType: function(type) {
var typeString = type.toString();
@@ -6514,6 +6713,34 @@ DS.JSONSerializer = DS.Serializer.extend({
var parts = typeString.split(".");
var name = parts[parts.length - 1];
return name.replace(/([A-Z])/g, '_$1').toLowerCase().slice(1);
+ },
+
+ /**
+ @private
+
+ Determines the root name mapped to a particular sideloaded type.
+
+ @param {DS.Model subclass} type
+ @returns {String} name of the root element, if any is registered
+ */
+ sideloadMappingForType: function(type) {
+ this.sideloadMapping.forEach(function(key, value) {
+ if (type === value) {
+ return key;
+ }
+ });
+ },
+
+ /**
+ @private
+
+ The default root name for a particular sideloaded type.
+
+ @param {DS.Model subclass} type
+ @returns {String} name of the root element
+ */
+ defaultSideloadRootForType: function(type) {
+ return this.pluralize(this.rootForType(type));
}
});
@@ -6685,7 +6912,6 @@ DS.Adapter = Ember.Object.extend(DS._Mappable, {
if (payload) {
var loader = DS.loaderFor(store);
- var serializer = get(this, 'serializer');
loader.load = function(type, data, prematerialized) {
store.updateId(record, data);
@@ -7236,14 +7462,122 @@ DS.Adapter.reopenClass({
(function() {
-var get = Ember.get;
+var get = Ember.get, set = Ember.set;
+
+DS.FixtureSerializer = DS.Serializer.extend({
+ deserializeValue: function(value, attributeType) {
+ return value;
+ },
+
+ serializeValue: function(value, attributeType) {
+ return value;
+ },
+
+ addId: function(data, key, id) {
+ data[key] = id;
+ },
+
+ addAttribute: function(hash, key, value) {
+ hash[key] = value;
+ },
+
+ addBelongsTo: function(hash, record, key, relationship) {
+ var id = get(record, relationship.key+'.id');
+ if (!Ember.isNone(id)) { hash[key] = id; }
+ },
+
+ addHasMany: function(hash, record, key, relationship) {
+ var ids = get(record, relationship.key).map(function(item) {
+ return item.get('id');
+ });
+
+ hash[relationship.key] = ids;
+ },
+
+ /**
+ @private
+
+ Creates an empty hash that will be filled in by the hooks called from the
+ `serialize()` method.
+
+ @return {Object}
+ */
+ createSerializedForm: function() {
+ return {};
+ },
+
+ extract: function(loader, fixture, type, record) {
+ if (record) { loader.updateId(record, fixture); }
+ this.extractRecordRepresentation(loader, type, fixture);
+ },
+
+ extractMany: function(loader, fixtures, type, records) {
+ var objects = fixtures, references = [];
+ if (records) { records = records.toArray(); }
+
+ for (var i = 0; i < objects.length; i++) {
+ if (records) { loader.updateId(records[i], objects[i]); }
+ var reference = this.extractRecordRepresentation(loader, type, objects[i]);
+ references.push(reference);
+ }
+
+ loader.populateArray(references);
+ },
+
+ extractId: function(type, hash) {
+ var primaryKey = this._primaryKey(type);
+
+ if (hash.hasOwnProperty(primaryKey)) {
+ // Ensure that we coerce IDs to strings so that record
+ // IDs remain consistent between application runs; especially
+ // if the ID is serialized and later deserialized from the URL,
+ // when type information will have been lost.
+ return hash[primaryKey]+'';
+ } else {
+ return null;
+ }
+ },
+
+ extractAttribute: function(type, hash, attributeName) {
+ var key = this._keyForAttributeName(type, attributeName);
+ return hash[key];
+ },
+
+ extractHasMany: function(type, hash, key) {
+ return hash[key];
+ },
+
+ extractBelongsTo: function(type, hash, key) {
+ return hash[key];
+ }
+});
+})();
+
+
+
+(function() {
+var get = Ember.get, fmt = Ember.String.fmt,
+ dump = Ember.get(window, 'JSON.stringify') || function(object) { return object.toString(); };
+
+/**
+ `DS.FixtureAdapter` is an adapter that loads records from memory.
+ Its primarily used for development and testing. You can also use
+ `DS.FixtureAdapter` while working on the API but are not ready to
+ integrate yet. It is a fully functioning adapter. All CRUD methods
+ are implemented. You can also implement query logic that a remote
+ system would do. Its possible to do develop your entire application
+ with `DS.FixtureAdapter`.
+
+*/
DS.FixtureAdapter = DS.Adapter.extend({
simulateRemoteResponse: true,
latency: 50,
+ serializer: DS.FixtureSerializer,
+
/*
Implement this method in order to provide data associated with a type
*/
@@ -7252,7 +7586,7 @@ DS.FixtureAdapter = DS.Adapter.extend({
var fixtures = Ember.A(type.FIXTURES);
return fixtures.map(function(fixture){
if(!fixture.id){
- throw new Error('the id property must be defined for fixture %@'.fmt(fixture));
+ throw new Error(fmt('the id property must be defined for fixture %@', [dump(fixture)]));
}
fixture.id = fixture.id + '';
return fixture;
@@ -7265,7 +7599,19 @@ DS.FixtureAdapter = DS.Adapter.extend({
Implement this method in order to query fixtures data
*/
queryFixtures: function(fixtures, query, type) {
- return fixtures;
+ Ember.assert('Not implemented: You must override the DS.FixtureAdapter::queryFixtures method to support querying the fixture store.');
+ },
+
+ updateFixtures: function(type, fixture) {
+ if(!type.FIXTURES) {
+ type.FIXTURES = [];
+ }
+
+ var fixtures = type.FIXTURES;
+
+ this.deleteLoadedFixture(type, fixture);
+
+ fixtures.push(fixture);
},
/*
@@ -7283,18 +7629,19 @@ DS.FixtureAdapter = DS.Adapter.extend({
},
find: function(store, type, id) {
- var fixtures = this.fixturesForType(type);
+ var fixtures = this.fixturesForType(type),
+ fixture;
- Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
+ Ember.warn("Unable to find fixtures for model type " + type.toString(), fixtures);
if (fixtures) {
- fixtures = fixtures.findProperty('id', id);
+ fixture = Ember.A(fixtures).findProperty('id', id);
}
- if (fixtures) {
+ if (fixture) {
this.simulateRemoteCall(function() {
- store.load(type, fixtures);
- }, store, type);
+ this.didFindRecord(store, type, fixture, id);
+ }, this);
}
},
@@ -7311,8 +7658,8 @@ DS.FixtureAdapter = DS.Adapter.extend({
if (fixtures) {
this.simulateRemoteCall(function() {
- store.loadMany(type, fixtures);
- }, store, type);
+ this.didFindMany(store, type, fixtures);
+ }, this);
}
},
@@ -7322,9 +7669,8 @@ DS.FixtureAdapter = DS.Adapter.extend({
Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
this.simulateRemoteCall(function() {
- store.loadMany(type, fixtures);
- store.didUpdateAll(type);
- }, store, type);
+ this.didFindAll(store, type, fixtures);
+ }, this);
},
findQuery: function(store, type, query, array) {
@@ -7336,43 +7682,82 @@ DS.FixtureAdapter = DS.Adapter.extend({
if (fixtures) {
this.simulateRemoteCall(function() {
- array.load(fixtures);
- }, store, type);
+ this.didFindQuery(store, type, fixtures, array);
+ }, this);
}
},
createRecord: function(store, type, record) {
var fixture = this.mockJSON(type, record);
- fixture.id = this.generateIdForRecord(store, record);
+ this.updateFixtures(type, fixture);
this.simulateRemoteCall(function() {
- store.didSaveRecord(record, fixture);
- }, store, type, record);
+ this.didCreateRecord(store, type, record, fixture);
+ }, this);
},
updateRecord: function(store, type, record) {
var fixture = this.mockJSON(type, record);
+ this.updateFixtures(type, fixture);
+
this.simulateRemoteCall(function() {
- store.didSaveRecord(record, fixture);
- }, store, type, record);
+ this.didUpdateRecord(store, type, record, fixture);
+ }, this);
},
deleteRecord: function(store, type, record) {
+ var fixture = this.mockJSON(type, record);
+
+ this.deleteLoadedFixture(type, fixture);
+
this.simulateRemoteCall(function() {
- store.didSaveRecord(record);
- }, store, type, record);
+ this.didDeleteRecord(store, type, record);
+ }, this);
},
/*
@private
*/
- simulateRemoteCall: function(callback, store, type, record) {
+ deleteLoadedFixture: function(type, record) {
+ var id = this.extractId(type, record);
+
+ var existingFixture = this.findExistingFixture(type, record);
+
+ if(existingFixture) {
+ var index = type.FIXTURES.indexOf(existingFixture);
+ type.FIXTURES.splice(index, 1);
+ return true;
+ }
+ },
+
+ findExistingFixture: function(type, record) {
+ var fixtures = this.fixturesForType(type);
+ var id = this.extractId(type, record);
+
+ return this.findFixtureById(fixtures, id);
+ },
+
+ findFixtureById: function(fixtures, id) {
+ var adapter = this;
+
+ return Ember.A(fixtures).find(function(r) {
+ if(''+get(r, 'id') === ''+id) {
+ return true;
+ } else {
+ return false;
+ }
+ });
+ },
+
+ simulateRemoteCall: function(callback, context) {
if (get(this, 'simulateRemoteResponse')) {
- setTimeout(callback, get(this, 'latency'));
+ // Schedule with setTimeout
+ Ember.run.later(context, callback, get(this, 'latency'));
} else {
- callback();
+ // Asynchronous, but at the of the runloop with zero latency
+ Ember.run.once(context, callback);
}
}
});
@@ -7395,6 +7780,16 @@ DS.RESTSerializer = DS.JSONSerializer.extend({
}
return key + "_id";
+ },
+
+ keyForHasMany: function(type, name) {
+ var key = this.keyForAttributeName(type, name);
+
+ if (this.embeddedType(type, name)) {
+ return key;
+ }
+
+ return this.singularize(key) + "_ids";
}
});
@@ -7499,12 +7894,25 @@ DS.RESTAdapter = DS.Adapter.extend({
},
dirtyRecordsForRecordChange: function(dirtySet, record) {
+ this._dirtyTree(dirtySet, record);
+ },
+
+ dirtyRecordsForHasManyChange: function(dirtySet, record, relationship) {
+ var embeddedType = get(this, 'serializer').embeddedType(record.constructor, relationship.secondRecordName);
+
+ if (embeddedType === 'always') {
+ relationship.childReference.parent = relationship.parentReference;
+ this._dirtyTree(dirtySet, record);
+ }
+ },
+
+ _dirtyTree: function(dirtySet, record) {
dirtySet.add(record);
get(this, 'serializer').eachEmbeddedRecord(record, function(embeddedRecord, embeddedType) {
if (embeddedType !== 'always') { return; }
if (dirtySet.has(embeddedRecord)) { return; }
- this.dirtyRecordsForRecordChange(dirtySet, embeddedRecord);
+ this._dirtyTree(dirtySet, embeddedRecord);
}, this);
var reference = record.get('_reference');
@@ -7512,12 +7920,10 @@ DS.RESTAdapter = DS.Adapter.extend({
if (reference.parent) {
var store = get(record, 'store');
var parent = store.recordForReference(reference.parent);
- this.dirtyRecordsForRecordChange(dirtySet, parent);
+ this._dirtyTree(dirtySet, parent);
}
},
- dirtyRecordsForHasManyChange: Ember.K,
-
createRecords: function(store, type, records) {
if (get(this, 'bulkCommit') === false) {
return this._super(store, type, records);
@@ -7763,6 +8169,251 @@ DS.RESTAdapter = DS.Adapter.extend({
(function() {
+var camelize = Ember.String.camelize,
+ capitalize = Ember.String.capitalize,
+ get = Ember.get,
+ map = Ember.ArrayPolyfills.map,
+ registeredTransforms;
+
+var passthruTransform = {
+ serialize: function(value) { return value; },
+ deserialize: function(value) { return value; }
+};
+
+var defaultTransforms = {
+ string: passthruTransform,
+ boolean: passthruTransform,
+ number: passthruTransform
+};
+
+function camelizeKeys(json) {
+ var value;
+
+ for (var prop in json) {
+ value = json[prop];
+ delete json[prop];
+ json[camelize(prop)] = value;
+ }
+}
+
+function munge(json, callback) {
+ callback(json);
+}
+
+function applyTransforms(json, type, transformType) {
+ var transforms = registeredTransforms[transformType];
+
+ Ember.assert("You are trying to apply the '" + transformType + "' transforms, but you didn't register any transforms with that name", transforms);
+
+ get(type, 'attributes').forEach(function(name, attribute) {
+ var attributeType = attribute.type,
+ value = json[name];
+
+ var transform = transforms[attributeType] || defaultTransforms[attributeType];
+
+ Ember.assert("Your model specified the '" + attributeType + "' type for the '" + name + "' attribute, but no transform for that type was registered", transform);
+
+ json[name] = transform.deserialize(value);
+ });
+}
+
+function ObjectProcessor(json, type, store) {
+ this.json = json;
+ this.type = type;
+ this.store = store;
+}
+
+ObjectProcessor.prototype = {
+ camelizeKeys: function() {
+ camelizeKeys(this.json);
+ return this;
+ },
+
+ munge: function(callback) {
+ munge(this.json, callback);
+ return this;
+ },
+
+ applyTransforms: function(transformType) {
+ applyTransforms(this.json, this.type, transformType);
+ return this;
+ }
+};
+
+function LoadObjectProcessor() {
+ ObjectProcessor.apply(this, arguments);
+}
+
+LoadObjectProcessor.prototype = Ember.create(ObjectProcessor.prototype);
+
+LoadObjectProcessor.prototype.load = function() {
+ this.store.load(this.type, {}, this.json);
+};
+
+function loadObjectProcessorFactory(store, type) {
+ return function(json) {
+ return new LoadObjectProcessor(json, type, store);
+ };
+}
+
+function ArrayProcessor(json, type, array, store) {
+ this.json = json;
+ this.type = type;
+ this.array = array;
+ this.store = store;
+}
+
+ArrayProcessor.prototype = {
+ load: function() {
+ var store = this.store,
+ type = this.type;
+
+ var references = this.json.map(function(object) {
+ return store.load(type, {}, object);
+ });
+
+ this.array.load(references);
+ },
+
+ camelizeKeys: function() {
+ this.json.forEach(camelizeKeys);
+ return this;
+ },
+
+ munge: function(callback) {
+ this.json.forEach(function(object) {
+ munge(object, callback);
+ });
+ return this;
+ },
+
+ applyTransforms: function(transformType) {
+ var type = this.type;
+
+ this.json.forEach(function(object) {
+ applyTransforms(object, type, transformType);
+ });
+
+ return this;
+ }
+};
+
+function arrayProcessorFactory(store, type, array) {
+ return function(json) {
+ return new ArrayProcessor(json, type, array, store);
+ };
+}
+
+var HasManyProcessor = function(json, store, record, relationship) {
+ this.json = json;
+ this.store = store;
+ this.record = record;
+ this.type = record.constructor;
+ this.relationship = relationship;
+};
+
+HasManyProcessor.prototype = Ember.create(ArrayProcessor.prototype);
+
+HasManyProcessor.prototype.load = function() {
+ var store = this.store;
+ var ids = map.call(this.json, function(obj) { return obj.id; });
+
+ store.loadMany(this.relationship.type, this.json);
+ store.loadHasMany(this.record, this.relationship.key, ids);
+};
+
+function hasManyProcessorFactory(store, record, relationship) {
+ return function(json) {
+ return new HasManyProcessor(json, store, record, relationship);
+ };
+}
+
+function SaveProcessor(record, store, type, includeId) {
+ this.record = record;
+ ObjectProcessor.call(this, record.toJSON({ includeId: includeId }), type, store);
+}
+
+SaveProcessor.prototype = Ember.create(ObjectProcessor.prototype);
+
+SaveProcessor.prototype.save = function(callback) {
+ callback(this.json);
+};
+
+function saveProcessorFactory(store, type, includeId) {
+ return function(record) {
+ return new SaveProcessor(record, store, type, includeId);
+ };
+}
+
+DS.BasicAdapter = DS.Adapter.extend({
+ find: function(store, type, id) {
+ var sync = type.sync;
+
+ Ember.assert("You are trying to use the BasicAdapter to find id '" + id + "' of " + type + " but " + type + ".sync was not found", sync);
+ Ember.assert("The sync code on " + type + " does not implement find(), but you are trying to find id '" + id + "'.", sync.find);
+
+ sync.find(id, loadObjectProcessorFactory(store, type));
+ },
+
+ findQuery: function(store, type, query, recordArray) {
+ var sync = type.sync;
+
+ Ember.assert("You are trying to use the BasicAdapter to query " + type + " but " + type + ".sync was not found", sync);
+ Ember.assert("The sync code on " + type + " does not implement query(), but you are trying to query " + type + ".", sync.query);
+
+ sync.query(query, arrayProcessorFactory(store, type, recordArray));
+ },
+
+ findHasMany: function(store, record, relationship, data) {
+ var name = capitalize(relationship.key),
+ sync = record.constructor.sync,
+ processor = hasManyProcessorFactory(store, record, relationship);
+
+ var options = {
+ relationship: relationship.key,
+ data: data
+ };
+
+ if (sync['find'+name]) {
+ sync['find' + name](record, options, processor);
+ } else if (sync.findHasMany) {
+ sync.findHasMany(record, options, processor);
+ } else {
+ Ember.assert("You are trying to use the BasicAdapter to find the " + relationship.key + " has-many relationship, but " + record.constructor + ".sync did not implement findHasMany or find" + name + ".", false);
+ }
+ },
+
+ createRecord: function(store, type, record) {
+ var sync = type.sync;
+ sync.createRecord(record, saveProcessorFactory(store, type));
+ },
+
+ updateRecord: function(store, type, record) {
+ var sync = type.sync;
+ sync.updateRecord(record, saveProcessorFactory(store, type, true));
+ },
+
+ deleteRecord: function(store, type, record) {
+ var sync = type.sync;
+ sync.deleteRecord(record, saveProcessorFactory(store, type, true));
+ }
+});
+
+DS.registerTransforms = function(kind, object) {
+ registeredTransforms[kind] = object;
+};
+
+DS.clearTransforms = function() {
+ registeredTransforms = {};
+};
+
+DS.clearTransforms();
+
+})();
+
+
+
+(function() {
})();
diff --git a/public/js/libs/ember.js b/public/js/libs/ember.js
index e566b10..7f44724 100644
--- a/public/js/libs/ember.js
+++ b/public/js/libs/ember.js
@@ -1,5 +1,5 @@
-// Version: v1.0.0-rc.1
-// Last commit: 8b061b4 (2013-02-15 12:10:22 -0800)
+// Version: v1.0.0-rc.2
+// Last commit: 656fa6e (2013-03-29 13:40:38 -0700)
(function() {
@@ -140,6 +140,7 @@ Ember.deprecate = function(message, test) {
@method deprecateFunc
@param {String} message A description of the deprecation.
@param {Function} func The function to be deprecated.
+ @return {Function} a new function that wrapped the original function with a deprecation warning
*/
Ember.deprecateFunc = function(message, func) {
return function() {
@@ -150,8 +151,8 @@ Ember.deprecateFunc = function(message, func) {
})();
-// Version: v1.0.0-rc.1
-// Last commit: 8b061b4 (2013-02-15 12:10:22 -0800)
+// Version: v1.0.0-rc.2
+// Last commit: 656fa6e (2013-03-29 13:40:38 -0700)
(function() {
@@ -211,7 +212,7 @@ var define, requireModule;
@class Ember
@static
- @version 1.0.0-rc.1
+ @version 1.0.0-rc.2
*/
if ('undefined' === typeof Ember) {
@@ -238,10 +239,10 @@ Ember.toString = function() { return "Ember"; };
/**
@property VERSION
@type String
- @default '1.0.0-rc.1'
+ @default '1.0.0-rc.2'
@final
*/
-Ember.VERSION = '1.0.0-rc.1';
+Ember.VERSION = '1.0.0-rc.2';
/**
Standard environmental variables. You can define these in a global `ENV`
@@ -298,6 +299,15 @@ Ember.LOG_STACKTRACE_ON_DEPRECATION = (Ember.ENV.LOG_STACKTRACE_ON_DEPRECATION !
Ember.SHIM_ES5 = (Ember.ENV.SHIM_ES5 === false) ? false : Ember.EXTEND_PROTOTYPES;
/**
+ Determines whether Ember logs info about version of used libraries
+
+ @property LOG_VERSION
+ @type Boolean
+ @default true
+*/
+Ember.LOG_VERSION = (Ember.ENV.LOG_VERSION === false) ? false : true;
+
+/**
Empty function. Useful for some operations.
@method K
@@ -393,12 +403,12 @@ Ember.handleErrors = function(func, context) {
// so in the event that we don't have an `onerror` handler we don't wrap in a try/catch
if ('function' === typeof Ember.onerror) {
try {
- return func.apply(context || this);
+ return func.call(context || this);
} catch (error) {
Ember.onerror(error);
}
} else {
- return func.apply(context || this);
+ return func.call(context || this);
}
};
@@ -407,7 +417,60 @@ Ember.merge = function(original, updates) {
if (!updates.hasOwnProperty(prop)) { continue; }
original[prop] = updates[prop];
}
+ return original;
+};
+
+/**
+ Returns true if the passed value is null or undefined. This avoids errors
+ from JSLint complaining about use of ==, which can be technically
+ confusing.
+
+ ```javascript
+ Ember.isNone(); // true
+ Ember.isNone(null); // true
+ Ember.isNone(undefined); // true
+ Ember.isNone(''); // false
+ Ember.isNone([]); // false
+ Ember.isNone(function(){}); // false
+ ```
+
+ @method isNone
+ @for Ember
+ @param {Object} obj Value to test
+ @return {Boolean}
+*/
+Ember.isNone = function(obj) {
+ return obj === null || obj === undefined;
+};
+Ember.none = Ember.deprecateFunc("Ember.none is deprecated. Please use Ember.isNone instead.", Ember.isNone);
+
+/**
+ Verifies that a value is `null` or an empty string, empty array,
+ or empty function.
+
+ Constrains the rules on `Ember.isNone` by returning false for empty
+ string and empty arrays.
+
+ ```javascript
+ Ember.isEmpty(); // true
+ Ember.isEmpty(null); // true
+ Ember.isEmpty(undefined); // true
+ Ember.isEmpty(''); // true
+ Ember.isEmpty([]); // true
+ Ember.isEmpty('Adam Hawkins'); // false
+ Ember.isEmpty([0,1,2]); // false
+ ```
+
+ @method isEmpty
+ @for Ember
+ @param {Object} obj Value to test
+ @return {Boolean}
+*/
+Ember.isEmpty = function(obj) {
+ return obj === null || obj === undefined || (obj.length === 0 && typeof obj !== 'function') || (typeof obj === 'object' && Ember.get(obj, 'length') === 0);
};
+Ember.empty = Ember.deprecateFunc("Ember.empty is deprecated. Please use Ember.isEmpty instead.", Ember.isEmpty) ;
+
})();
@@ -651,7 +714,7 @@ Ember.generateGuid = function generateGuid(obj, prefix) {
@method guidFor
@for Ember
- @param obj {Object} any object, string, number, Element, or primitive
+ @param {Object} obj any object, string, number, Element, or primitive
@return {String} the unique guid for this instance.
*/
Ember.guidFor = function guidFor(obj) {
@@ -762,7 +825,7 @@ if (isDefinePropertySimulated) {
@param {Object} obj The object to retrieve meta for
@param {Boolean} [writable=true] Pass `false` if you do not intend to modify
the meta hash, allowing the method to avoid making an unnecessary copy.
- @return {Hash}
+ @return {Object} the meta hash for an object
*/
Ember.meta = function meta(obj, writable) {
@@ -914,7 +977,7 @@ Ember.wrap = function(func, superFunc) {
@method isArray
@for Ember
@param {Object} obj The object to test
- @return {Boolean}
+ @return {Boolean} true if the passed object is an array or Array-like
*/
Ember.isArray = function(obj) {
if (!obj || obj.setInterval) { return false; }
@@ -1001,8 +1064,8 @@ var needsFinallyFix = (function() {
@method tryFinally
@for Ember
- @param {Function} function The function to run the try callback
- @param {Function} function The function to run the finally callback
+ @param {Function} tryable The function to run the try callback
+ @param {Function} finalizer The function to run the finally callback
@param [binding]
@return {anything} The return value is the that of the finalizer,
unless that valueis undefined, in which case it is the return value
@@ -1051,9 +1114,9 @@ if (needsFinallyFix) {
@method tryCatchFinally
@for Ember
- @param {Function} function The function to run the try callback
- @param {Function} function The function to run the catchable callback
- @param {Function} function The function to run the finally callback
+ @param {Function} tryable The function to run the try callback
+ @param {Function} catchable The function to run the catchable callback
+ @param {Function} finalizer The function to run the finally callback
@param [binding]
@return {anything} The return value is the that of the finalizer,
unless that value is undefined, in which case it is the return value
@@ -1565,8 +1628,8 @@ OrderedSet.prototype = {
/**
@method forEach
- @param {Function} function
- @param target
+ @param {Function} fn
+ @param self
*/
forEach: function(fn, self) {
// allow mutation during iteration
@@ -1752,7 +1815,7 @@ var MapWithDefault = Ember.MapWithDefault = function(options) {
@static
@param [options]
@param {anything} [options.defaultValue]
- @return {Ember.MapWithDefault|Ember.Map} If options are passed, returns
+ @return {Ember.MapWithDefault|Ember.Map} If options are passed, returns
`Ember.MapWithDefault` otherwise returns `Ember.Map`
*/
MapWithDefault.create = function(options) {
@@ -1815,7 +1878,7 @@ var FIRST_KEY = /^([^\.\*]+)/;
// ..........................................................
// GET AND SET
//
-// If we are on a platform that supports accessors we can get use those.
+// If we are on a platform that supports accessors we can use those.
// Otherwise simulate accessors by looking up the property directly on the
// object.
@@ -1826,7 +1889,7 @@ var FIRST_KEY = /^([^\.\*]+)/;
If you plan to run on IE8 and older browsers then you should use this
method anytime you want to retrieve a property on an object that you don't
- know for sure is private. (Properties beginning with an underscore '_'
+ know for sure is private. (Properties beginning with an underscore '_'
are considered private.)
On all newer browsers, you only need to use this method to retrieve
@@ -1888,7 +1951,7 @@ get = function get(obj, keyName) {
If you plan to run on IE8 and older browsers then you should use this
method anytime you want to set a property on an object that you don't
- know for sure is private. (Properties beginning with an underscore '_'
+ know for sure is private. (Properties beginning with an underscore '_'
are considered private.)
On all newer browsers, you only need to use this method to set
@@ -2087,7 +2150,7 @@ Ember.setPath = Ember.deprecateFunc('setPath is deprecated since set now support
@method trySet
@for Ember
@param {Object} obj The object to modify.
- @param {String} keyName The property key to set
+ @param {String} path The property path to set
@param {Object} value The value to set
*/
Ember.trySet = function(root, path, value) {
@@ -2119,11 +2182,8 @@ Ember.isGlobalPath = function(path) {
@module ember-metal
*/
-var GUID_KEY = Ember.GUID_KEY,
- META_KEY = Ember.META_KEY,
- EMPTY_META = Ember.EMPTY_META,
+var META_KEY = Ember.META_KEY,
metaFor = Ember.meta,
- o_create = Ember.create,
objectDefineProperty = Ember.platform.defineProperty;
var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
@@ -2543,7 +2603,6 @@ var guidFor = Ember.guidFor, // utils.js
META_KEY = Ember.META_KEY, // utils.js
// circular reference observer depends on Ember.watch
// we should move change events to this file or its own property_events.js
- notifyObservers = Ember.notifyObservers, // observer.js
forEach = Ember.ArrayPolyfills.forEach, // array.js
FIRST_KEY = /^([^\.\*]+)/,
IS_PATH = /[\.\*]/;
@@ -3083,7 +3142,7 @@ Ember.finishChains = function(obj) {
@param {String} keyName The property key (or path) that will change.
@return {void}
*/
-function propertyWillChange(obj, keyName, value) {
+function propertyWillChange(obj, keyName) {
var m = metaFor(obj, false),
watching = m.watching[keyName] > 0 || keyName === 'length',
proto = m.proto,
@@ -3191,7 +3250,6 @@ Ember.warn("The CP_DEFAULT_CACHEABLE flag has been removed and computed properti
var get = Ember.get,
set = Ember.set,
metaFor = Ember.meta,
- guidFor = Ember.guidFor,
a_slice = [].slice,
o_create = Ember.create,
META_KEY = Ember.META_KEY,
@@ -3227,20 +3285,8 @@ function keysForDep(obj, depsMeta, depKey) {
return keys;
}
-/* return obj[META_KEY].deps */
function metaForDeps(obj, meta) {
- var deps = meta.deps;
- // If the current object has no dependencies...
- if (!deps) {
- // initialize the dependencies with a pointer back to
- // the current object
- deps = meta.deps = {};
- } else if (!meta.hasOwnProperty('deps')) {
- // otherwise if the dependencies are inherited from the
- // object's superclass, clone the deps
- deps = meta.deps = o_create(deps);
- }
- return deps;
+ return keysForDep(obj, meta, 'deps');
}
function addDependentKeys(desc, obj, keyName, meta) {
@@ -3293,8 +3339,10 @@ function removeDependentKeys(desc, obj, keyName, meta) {
*/
function ComputedProperty(func, opts) {
this.func = func;
+
this._cacheable = (opts && opts.cacheable !== undefined) ? opts.cacheable : true;
this._dependentKeys = opts && opts.dependentKeys;
+ this._readOnly = opts && (opts.readOnly !== undefined || !!opts.readOnly);
}
Ember.ComputedProperty = ComputedProperty;
@@ -3323,6 +3371,7 @@ var ComputedPropertyPrototype = ComputedProperty.prototype;
@method cacheable
@param {Boolean} aFlag optional set to `false` to disable caching
+ @return {Ember.ComputedProperty} this
@chainable
*/
ComputedPropertyPrototype.cacheable = function(aFlag) {
@@ -3343,6 +3392,7 @@ ComputedPropertyPrototype.cacheable = function(aFlag) {
```
@method volatile
+ @return {Ember.ComputedProperty} this
@chainable
*/
ComputedPropertyPrototype.volatile = function() {
@@ -3350,6 +3400,29 @@ ComputedPropertyPrototype.volatile = function() {
};
/**
+ Call on a computed property to set it into read-only mode. When in this
+ mode the computed property will throw an error when set.
+
+ ```javascript
+ MyApp.person = Ember.Object.create({
+ guid: function() {
+ return 'guid-guid-guid';
+ }.property().readOnly()
+ });
+
+ MyApp.person.set('guid', 'new-guid'); // will throw an exception
+ ```
+
+ @method readOnly
+ @return {Ember.ComputedProperty} this
+ @chainable
+*/
+ComputedPropertyPrototype.readOnly = function(readOnly) {
+ this._readOnly = readOnly === undefined || !!readOnly;
+ return this;
+};
+
+/**
Sets the dependent keys on this computed property. Pass any number of
arguments containing key paths that this computed property depends on.
@@ -3366,6 +3439,7 @@ ComputedPropertyPrototype.volatile = function() {
@method property
@param {String} path* zero or more property paths
+ @return {Ember.ComputedProperty} this
@chainable
*/
ComputedPropertyPrototype.property = function() {
@@ -3473,9 +3547,14 @@ ComputedPropertyPrototype.set = function(obj, keyName, value) {
cache = meta.cache,
cachedValue, ret;
+ if (this._readOnly) {
+ throw new Error('Cannot Set: ' + keyName + ' on: ' + obj.toString() );
+ }
+
this._suspended = obj;
try {
+
if (cacheable && cache.hasOwnProperty(keyName)) {
cachedValue = cache[keyName];
hadCachedValue = true;
@@ -3566,6 +3645,10 @@ Ember.computed = function(func) {
func = a_slice.call(arguments, -1)[0];
}
+ if ( typeof func !== "function" ) {
+ throw new Error("Computed Property declared without a property function");
+ }
+
var cp = new ComputedProperty(func);
if (args) {
@@ -3586,6 +3669,7 @@ Ember.computed = function(func) {
@param {Object} obj the object whose property you want to check
@param {String} key the name of the property whose cached value you want
to return
+ @return {any} the cached value
*/
Ember.cacheFor = function cacheFor(obj, key) {
var cache = metaFor(obj, false).cache;
@@ -3595,53 +3679,264 @@ Ember.cacheFor = function cacheFor(obj, key) {
}
};
+function getProperties(self, propertyNames) {
+ var ret = {};
+ for(var i = 0; i < propertyNames.length; i++) {
+ ret[propertyNames[i]] = get(self, propertyNames[i]);
+ }
+ return ret;
+}
+
+function registerComputed(name, macro) {
+ Ember.computed[name] = function(dependentKey) {
+ var args = a_slice.call(arguments);
+ return Ember.computed(dependentKey, function() {
+ return macro.apply(this, args);
+ });
+ };
+}
+
+function registerComputedWithProperties(name, macro) {
+ Ember.computed[name] = function() {
+ var properties = a_slice.call(arguments);
+
+ var computed = Ember.computed(function() {
+ return macro.apply(this, [getProperties(this, properties)]);
+ });
+
+ return computed.property.apply(computed, properties);
+ };
+}
+
/**
- @method computed.not
+ @method computed.empty
@for Ember
@param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which negate
+ the original value for property
*/
-Ember.computed.not = function(dependentKey) {
- return Ember.computed(dependentKey, function(key) {
- return !get(this, dependentKey);
- });
-};
+registerComputed('empty', function(dependentKey) {
+ return Ember.isEmpty(get(this, dependentKey));
+});
/**
- @method computed.empty
+ @method computed.notEmpty
@for Ember
@param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which returns true if
+ original value for property is not empty.
*/
-Ember.computed.empty = function(dependentKey) {
- return Ember.computed(dependentKey, function(key) {
- var val = get(this, dependentKey);
- return val === undefined || val === null || val === '' || (Ember.isArray(val) && get(val, 'length') === 0);
- });
-};
+registerComputed('notEmpty', function(dependentKey) {
+ return !Ember.isEmpty(get(this, dependentKey));
+});
+
+/**
+ @method computed.none
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which
+ rturns true if original value for property is null or undefined.
+*/
+registerComputed('none', function(dependentKey) {
+ return Ember.isNone(get(this, dependentKey));
+});
+
+/**
+ @method computed.not
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which returns
+ inverse of the original value for property
+*/
+registerComputed('not', function(dependentKey) {
+ return !get(this, dependentKey);
+});
/**
@method computed.bool
@for Ember
@param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which convert
+ to boolean the original value for property
*/
-Ember.computed.bool = function(dependentKey) {
- return Ember.computed(dependentKey, function(key) {
- return !!get(this, dependentKey);
- });
-};
+registerComputed('bool', function(dependentKey) {
+ return !!get(this, dependentKey);
+});
+
+/**
+ @method computed.match
+ @for Ember
+ @param {String} dependentKey
+ @param {RegExp} regexp
+ @return {Ember.ComputedProperty} computed property which match
+ the original value for property against a given RegExp
+*/
+registerComputed('match', function(dependentKey, regexp) {
+ var value = get(this, dependentKey);
+ return typeof value === 'string' ? !!value.match(regexp) : false;
+});
+
+/**
+ @method computed.equal
+ @for Ember
+ @param {String} dependentKey
+ @param {String|Number|Object} value
+ @return {Ember.ComputedProperty} computed property which returns true if
+ the original value for property is equal to the given value.
+*/
+registerComputed('equal', function(dependentKey, value) {
+ return get(this, dependentKey) === value;
+});
+
+/**
+ @method computed.gt
+ @for Ember
+ @param {String} dependentKey
+ @param {Number} value
+ @return {Ember.ComputedProperty} computed property which returns true if
+ the original value for property is greater then given value.
+*/
+registerComputed('gt', function(dependentKey, value) {
+ return get(this, dependentKey) > value;
+});
+
+/**
+ @method computed.gte
+ @for Ember
+ @param {String} dependentKey
+ @param {Number} value
+ @return {Ember.ComputedProperty} computed property which returns true if
+ the original value for property is greater or equal then given value.
+*/
+registerComputed('gte', function(dependentKey, value) {
+ return get(this, dependentKey) >= value;
+});
+
+/**
+ @method computed.lt
+ @for Ember
+ @param {String} dependentKey
+ @param {Number} value
+ @return {Ember.ComputedProperty} computed property which returns true if
+ the original value for property is less then given value.
+*/
+registerComputed('lt', function(dependentKey, value) {
+ return get(this, dependentKey) < value;
+});
+
+/**
+ @method computed.lte
+ @for Ember
+ @param {String} dependentKey
+ @param {Number} value
+ @return {Ember.ComputedProperty} computed property which returns true if
+ the original value for property is less or equal then given value.
+*/
+registerComputed('lte', function(dependentKey, value) {
+ return get(this, dependentKey) <= value;
+});
+
+/**
+ @method computed.and
+ @for Ember
+ @param {String} dependentKey, [dependentKey...]
+ @return {Ember.ComputedProperty} computed property which peforms
+ a logical `and` on the values of all the original values for properties.
+*/
+registerComputedWithProperties('and', function(properties) {
+ for (var key in properties) {
+ if (properties.hasOwnProperty(key) && !properties[key]) {
+ return false;
+ }
+ }
+ return true;
+});
+
+/**
+ @method computed.or
+ @for Ember
+ @param {String} dependentKey, [dependentKey...]
+ @return {Ember.ComputedProperty} computed property which peforms
+ a logical `or` on the values of all the original values for properties.
+*/
+registerComputedWithProperties('or', function(properties) {
+ for (var key in properties) {
+ if (properties.hasOwnProperty(key) && properties[key]) {
+ return true;
+ }
+ }
+ return false;
+});
+
+/**
+ @method computed.any
+ @for Ember
+ @param {String} dependentKey, [dependentKey...]
+ @return {Ember.ComputedProperty} computed property which returns
+ the first trouthy value of given list of properties.
+*/
+registerComputedWithProperties('any', function(properties) {
+ for (var key in properties) {
+ if (properties.hasOwnProperty(key) && properties[key]) {
+ return properties[key];
+ }
+ }
+ return null;
+});
+
+/**
+ @method computed.map
+ @for Ember
+ @param {String} dependentKey, [dependentKey...]
+ @return {Ember.ComputedProperty} computed property which maps
+ values of all passed properties in to an array.
+*/
+registerComputedWithProperties('map', function(properties) {
+ var res = [];
+ for (var key in properties) {
+ if (properties.hasOwnProperty(key)) {
+ if (Ember.isNone(properties[key])) {
+ res.push(null);
+ } else {
+ res.push(properties[key]);
+ }
+ }
+ }
+ return res;
+});
/**
@method computed.alias
@for Ember
@param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which creates an
+ alias to the original value for property.
*/
Ember.computed.alias = function(dependentKey) {
return Ember.computed(dependentKey, function(key, value){
- if (arguments.length === 1) {
- return get(this, dependentKey);
- } else {
+ if (arguments.length > 1) {
set(this, dependentKey, value);
return value;
+ } else {
+ return get(this, dependentKey);
+ }
+ });
+};
+
+/**
+ @method computed.defaultTo
+ @for Ember
+ @param {String} defaultPath
+ @return {Ember.ComputedProperty} computed property which acts like
+ a standard getter and setter, but defaults to the value from `defaultPath`.
+*/
+Ember.computed.defaultTo = function(defaultPath) {
+ return Ember.computed(function(key, newValue, cachedValue) {
+ var result;
+ if (arguments.length === 1) {
+ return cachedValue != null ? cachedValue : get(this, defaultPath);
}
+ return newValue != null ? newValue : get(this, defaultPath);
});
};
@@ -3656,7 +3951,6 @@ Ember.computed.alias = function(dependentKey) {
var o_create = Ember.create,
metaFor = Ember.meta,
- metaPath = Ember.metaPath,
META_KEY = Ember.META_KEY;
/*
@@ -3757,6 +4051,7 @@ function actionsDiff(obj, eventName, otherActions) {
@param {String} eventName
@param {Object|Function} targetOrMethod A target object or a function
@param {Function|String} method A function or the name of a function to be called on `target`
+ @param {Boolean} once A flag whether a function should only be called once
*/
function addListener(obj, eventName, target, method, once) {
Ember.assert("You must pass at least an object and event name to Ember.addListener", !!obj && !!eventName);
@@ -3942,6 +4237,7 @@ function watchedEvents(obj) {
@param obj
@param {String} eventName
@param {Array} params
+ @param {Array} actions
@return true
*/
function sendEvent(obj, eventName, params, actions) {
@@ -3970,7 +4266,7 @@ function sendEvent(obj, eventName, params, actions) {
if (params) {
method.apply(target, params);
} else {
- method.apply(target);
+ method.call(target);
}
}
return true;
@@ -4227,7 +4523,7 @@ Ember.RunLoop = RunLoop;
```javascript
Ember.run(function(){
- // code to be execute within a RunLoop
+ // code to be execute within a RunLoop
});
```
@@ -4243,8 +4539,7 @@ Ember.RunLoop = RunLoop;
@return {Object} return value from invoking the passed function.
*/
Ember.run = function(target, method) {
- var loop,
- args = arguments;
+ var args = arguments;
run.begin();
function tryable() {
@@ -4262,11 +4557,11 @@ var run = Ember.run;
/**
Begins a new RunLoop. Any deferred actions invoked after the begin will
be buffered until you invoke a matching call to `Ember.run.end()`. This is
- an lower-level way to use a RunLoop instead of using `Ember.run()`.
+ a lower-level way to use a RunLoop instead of using `Ember.run()`.
```javascript
Ember.run.begin();
- // code to be execute within a RunLoop
+ // code to be execute within a RunLoop
Ember.run.end();
```
@@ -4284,7 +4579,7 @@ Ember.run.begin = function() {
```javascript
Ember.run.begin();
- // code to be execute within a RunLoop
+ // code to be execute within a RunLoop
Ember.run.end();
```
@@ -4308,9 +4603,9 @@ Ember.run.end = function() {
@property queues
@type Array
- @default ['sync', 'actions', 'destroy', 'timers']
+ @default ['sync', 'actions', 'destroy']
*/
-Ember.run.queues = ['sync', 'actions', 'destroy', 'timers'];
+Ember.run.queues = ['sync', 'actions', 'destroy'];
/**
Adds the passed target/method and any optional arguments to the named
@@ -4323,19 +4618,19 @@ Ember.run.queues = ['sync', 'actions', 'destroy', 'timers'];
the `run.queues` property.
```javascript
- Ember.run.schedule('timers', this, function(){
- // this will be executed at the end of the RunLoop, when timers are run
- console.log("scheduled on timers queue");
- });
-
Ember.run.schedule('sync', this, function(){
- // this will be executed at the end of the RunLoop, when bindings are synced
+ // this will be executed in the first RunLoop queue, when bindings are synced
console.log("scheduled on sync queue");
});
+ Ember.run.schedule('actions', this, function(){
+ // this will be executed in the 'actions' queue, after bindings have synced.
+ console.log("scheduled on actions queue");
+ });
+
// Note the functions will be run in order based on the run queues order. Output would be:
// scheduled on sync queue
- // scheduled on timers queue
+ // scheduled on actions queue
```
@method schedule
@@ -4361,7 +4656,7 @@ function autorun() {
// Used by global test teardown
Ember.run.hasScheduledTimers = function() {
- return !!(scheduledAutorun || scheduledLater || scheduledNext);
+ return !!(scheduledAutorun || scheduledLater);
};
// Used by global test teardown
@@ -4374,10 +4669,6 @@ Ember.run.cancelTimers = function () {
clearTimeout(scheduledLater);
scheduledLater = null;
}
- if (scheduledNext) {
- clearTimeout(scheduledNext);
- scheduledNext = null;
- }
timers = {};
};
@@ -4412,7 +4703,8 @@ Ember.run.autorun = function() {
bindings in the application to sync.
You should call this method anytime you need any changed state to propagate
- throughout the app immediately without repainting the UI.
+ throughout the app immediately without repainting the UI (which happens
+ in the later 'render' queue added by the `ember-views` package).
```javascript
Ember.run.sync();
@@ -4432,25 +4724,30 @@ Ember.run.sync = function() {
var timers = {}; // active timers...
-var scheduledLater;
+var scheduledLater, scheduledLaterExpires;
function invokeLaterTimers() {
scheduledLater = null;
- var now = (+ new Date()), earliest = -1;
- for (var key in timers) {
- if (!timers.hasOwnProperty(key)) { continue; }
- var timer = timers[key];
- if (timer && timer.expires) {
- if (now >= timer.expires) {
- delete timers[key];
- invoke(timer.target, timer.method, timer.args, 2);
- } else {
- if (earliest<0 || (timer.expires < earliest)) earliest=timer.expires;
+ run(function() {
+ var now = (+ new Date()), earliest = -1;
+ for (var key in timers) {
+ if (!timers.hasOwnProperty(key)) { continue; }
+ var timer = timers[key];
+ if (timer && timer.expires) {
+ if (now >= timer.expires) {
+ delete timers[key];
+ invoke(timer.target, timer.method, timer.args, 2);
+ } else {
+ if (earliest < 0 || (timer.expires < earliest)) { earliest = timer.expires; }
+ }
}
}
- }
- // schedule next timeout to fire...
- if (earliest > 0) { scheduledLater = setTimeout(invokeLaterTimers, earliest-(+ new Date())); }
+ // schedule next timeout to fire when the earliest timer expires
+ if (earliest > 0) {
+ scheduledLater = setTimeout(invokeLaterTimers, earliest - now);
+ scheduledLaterExpires = earliest;
+ }
+ });
}
/**
@@ -4475,8 +4772,7 @@ function invokeLaterTimers() {
If you pass a string it will be resolved on the
target at the time the method is invoked.
@param {Object} [args*] Optional arguments to pass to the timeout.
- @param {Number} wait
- Number of milliseconds to wait.
+ @param {Number} wait Number of milliseconds to wait.
@return {String} a string you can use to cancel the timer in
{{#crossLink "Ember/run.cancel"}}{{/crossLink}} later.
*/
@@ -4498,7 +4794,19 @@ Ember.run.later = function(target, method) {
timer = { target: target, method: method, expires: expires, args: args };
guid = Ember.guidFor(timer);
timers[guid] = timer;
- run.once(timers, invokeLaterTimers);
+
+ if(scheduledLater && expires < scheduledLaterExpires) {
+ // Cancel later timer (then reschedule earlier timer below)
+ clearTimeout(scheduledLater);
+ scheduledLater = null;
+ }
+
+ if (!scheduledLater) {
+ // Schedule later timers to be run.
+ scheduledLater = setTimeout(invokeLaterTimers, wait);
+ scheduledLaterExpires = expires;
+ }
+
return guid;
};
@@ -4554,6 +4862,21 @@ function scheduleOnce(queue, target, method, args) {
});
```
+ Also note that passing an anonymous function to `Ember.run.once` will
+ not prevent additional calls with an identical anonymous function from
+ scheduling the items multiple times, e.g.:
+
+ ```javascript
+ function scheduleIt() {
+ Ember.run.once(myContext, function() { console.log("Closure"); });
+ }
+ scheduleIt();
+ scheduleIt();
+ // "Closure" will print twice, even though we're using `Ember.run.once`,
+ // because the function we pass to it is anonymous and won't match the
+ // previously scheduled operation.
+ ```
+
@method once
@param {Object} [target] target of method to invoke
@param {Function|String} method The method to invoke.
@@ -4570,29 +4893,55 @@ Ember.run.scheduleOnce = function(queue, target, method, args) {
return scheduleOnce(queue, target, method, slice.call(arguments, 3));
};
-var scheduledNext;
-function invokeNextTimers() {
- scheduledNext = null;
- for(var key in timers) {
- if (!timers.hasOwnProperty(key)) { continue; }
- var timer = timers[key];
- if (timer.next) {
- delete timers[key];
- invoke(timer.target, timer.method, timer.args, 2);
- }
- }
-}
-
/**
- Schedules an item to run after control has been returned to the system.
- This is often equivalent to calling `setTimeout(function() {}, 1)`.
+ Schedules an item to run from within a separate run loop, after
+ control has been returned to the system. This is equivalent to calling
+ `Ember.run.later` with a wait time of 1ms.
```javascript
Ember.run.next(myContext, function(){
- // code to be executed in the next RunLoop, which will be scheduled after the current one
+ // code to be executed in the next run loop, which will be scheduled after the current one
});
```
+ Multiple operations scheduled with `Ember.run.next` will coalesce
+ into the same later run loop, along with any other operations
+ scheduled by `Ember.run.later` that expire right around the same
+ time that `Ember.run.next` operations will fire.
+
+ Note that there are often alternatives to using `Ember.run.next`.
+ For instance, if you'd like to schedule an operation to happen
+ after all DOM element operations have completed within the current
+ run loop, you can make use of the `afterRender` run loop queue (added
+ by the `ember-views` package, along with the preceding `render` queue
+ where all the DOM element operations happen). Example:
+
+ ```javascript
+ App.MyCollectionView = Ember.CollectionView.extend({
+ didInsertElement: function() {
+ Ember.run.scheduleOnce('afterRender', this, 'processChildElements');
+ },
+ processChildElements: function() {
+ // ... do something with collectionView's child view
+ // elements after they've finished rendering, which
+ // can't be done within the CollectionView's
+ // `didInsertElement` hook because that gets run
+ // before the child elements have been added to the DOM.
+ }
+ });
+ ```
+
+ One benefit of the above approach compared to using `Ember.run.next` is
+ that you will be able to perform DOM/CSS operations before unprocessed
+ elements are rendered to the screen, which may prevent flickering or
+ other artifacts caused by delaying processing until after rendering.
+
+ The other major benefit to the above approach is that `Ember.run.next`
+ introduces an element of non-determinism, which can make things much
+ harder to test, due to its reliance on `setTimeout`; it's much harder
+ to guarantee the order of scheduled operations when they are scheduled
+ outside of the current run loop, i.e. with `Ember.run.next`.
+
@method next
@param {Object} [target] target of method to invoke
@param {Function|String} method The method to invoke.
@@ -4601,20 +4950,10 @@ function invokeNextTimers() {
@param {Object} [args*] Optional arguments to pass to the timeout.
@return {Object} timer
*/
-Ember.run.next = function(target, method) {
- var guid,
- timer = {
- target: target,
- method: method,
- args: slice.call(arguments),
- next: true
- };
-
- guid = Ember.guidFor(timer);
- timers[guid] = timer;
-
- if (!scheduledNext) { scheduledNext = setTimeout(invokeNextTimers, 1); }
- return guid;
+Ember.run.next = function() {
+ var args = slice.call(arguments);
+ args.push(1); // 1 millisecond wait
+ return run.later.apply(this, args);
};
/**
@@ -4729,7 +5068,7 @@ Binding.prototype = {
`get()` - see that method for more information.
@method from
- @param {String} propertyPath the property path to connect to
+ @param {String} path the property path to connect to
@return {Ember.Binding} `this`
*/
from: function(path) {
@@ -4747,7 +5086,7 @@ Binding.prototype = {
`get()` - see that method for more information.
@method to
- @param {String|Tuple} propertyPath A property path or tuple
+ @param {String|Tuple} path A property path or tuple
@return {Ember.Binding} `this`
*/
to: function(path) {
@@ -4769,6 +5108,10 @@ Binding.prototype = {
return this;
},
+ /**
+ @method toString
+ @return {String} string representation of binding
+ */
toString: function() {
var oneWay = this._oneWay ? '[oneWay]' : '';
return "Ember.Binding<" + guidFor(this) + ">(" + this._from + " -> " + this._to + ")" + oneWay;
@@ -5119,7 +5462,6 @@ var Mixin, REQUIRED, Alias,
a_indexOf = Ember.ArrayPolyfills.indexOf,
a_forEach = Ember.ArrayPolyfills.forEach,
a_slice = [].slice,
- EMPTY_META = {}, // dummy for non-writable meta
o_create = Ember.create,
defineProperty = Ember.defineProperty,
guidFor = Ember.guidFor;
@@ -5488,6 +5830,38 @@ Mixin.finishPartial = finishPartial;
Ember.anyUnprocessedMixins = false;
/**
+ Creates an instance of a class. Accepts either no arguments, or an object
+ containing values to initialize the newly instantiated object with.
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ helloWorld: function() {
+ alert("Hi, my name is " + this.get('name'));
+ }
+ });
+
+ var tom = App.Person.create({
+ name: 'Tom Dale'
+ });
+
+ tom.helloWorld(); // alerts "Hi, my name is Tom Dale".
+ ```
+
+ `create` will call the `init` function if defined during
+ `Ember.AnyObject.extend`
+
+ If no arguments are passed to `create`, it will not set values to the new
+ instance during initialization:
+
+ ```javascript
+ var noName = App.Person.create();
+ noName.helloWorld(); // alerts undefined
+ ```
+
+ NOTE: For performance reasons, you cannot declare methods or computed
+ properties during `create`. You should instead declare methods and computed
+ properties when using `extend`.
+
@method create
@static
@param arguments*
@@ -6003,35 +6377,35 @@ define("rsvp",
}
function all(promises) {
- var i, results = [];
- var allPromise = new Promise();
- var remaining = promises.length;
+ var i, results = [];
+ var allPromise = new Promise();
+ var remaining = promises.length;
if (remaining === 0) {
allPromise.resolve([]);
}
- var resolver = function(index) {
- return function(value) {
- resolve(index, value);
- };
- };
+ var resolver = function(index) {
+ return function(value) {
+ resolve(index, value);
+ };
+ };
- var resolve = function(index, value) {
- results[index] = value;
- if (--remaining === 0) {
- allPromise.resolve(results);
- }
- };
+ var resolve = function(index, value) {
+ results[index] = value;
+ if (--remaining === 0) {
+ allPromise.resolve(results);
+ }
+ };
- var reject = function(error) {
- allPromise.reject(error);
- };
+ var reject = function(error) {
+ allPromise.reject(error);
+ };
- for (i = 0; i < remaining; i++) {
- promises[i].then(resolver(i), reject);
- }
- return allPromise;
+ for (i = 0; i < remaining; i++) {
+ promises[i].then(resolver(i), reject);
+ }
+ return allPromise;
}
EventTarget.mixin(Promise.prototype);
@@ -6047,12 +6421,6 @@ define("container",
[],
function() {
- var objectCreate = Object.create || function(parent) {
- function F() {}
- F.prototype = parent;
- return new F();
- };
-
function InheritingDict(parent) {
this.parent = parent;
this.dict = {};
@@ -6127,26 +6495,35 @@ define("container",
register: function(type, name, factory, options) {
var fullName;
-
if (type.indexOf(':') !== -1){
options = factory;
factory = name;
fullName = type;
} else {
- Ember.deprecate('register("'+type +'", "'+ name+'") is now deprecated in-favour of register("'+type+':'+name+'");', true);
+ Ember.deprecate('register("'+type +'", "'+ name+'") is now deprecated in-favour of register("'+type+':'+name+'");', false);
fullName = type + ":" + name;
}
- this.registry.set(fullName, factory);
- this._options.set(fullName, options || {});
+ var normalizedName = this.normalize(fullName);
+
+ this.registry.set(normalizedName, factory);
+ this._options.set(normalizedName, options || {});
},
resolve: function(fullName) {
return this.resolver(fullName) || this.registry.get(fullName);
},
- lookup: function(fullName) {
- if (this.cache.has(fullName)) {
+ normalize: function(fullName) {
+ return fullName;
+ },
+
+ lookup: function(fullName, options) {
+ fullName = this.normalize(fullName);
+
+ options = options || {};
+
+ if (this.cache.has(fullName) && options.singleton !== false) {
return this.cache.get(fullName);
}
@@ -6154,7 +6531,7 @@ define("container",
if (!value) { return; }
- if (isSingleton(this, fullName)) {
+ if (isSingleton(this, fullName) && options.singleton !== false) {
this.cache.set(fullName, value);
}
@@ -6272,7 +6649,8 @@ define("container",
}
function factoryFor(container, fullName) {
- return container.resolve(fullName);
+ var name = container.normalize(fullName);
+ return container.resolve(name);
}
function instantiate(container, fullName) {
@@ -6358,8 +6736,8 @@ var toString = Object.prototype.toString;
| 'undefined' | Undefined value |
| 'function' | A function |
| 'array' | An instance of Array |
- | 'class' | A Ember class (created using Ember.Object.extend()) |
- | 'instance' | A Ember object instance |
+ | 'class' | An Ember class (created using Ember.Object.extend()) |
+ | 'instance' | An Ember object instance |
| 'error' | An instance of the Error object |
| 'object' | A JavaScript object not inheriting from Ember.Object |
@@ -6384,7 +6762,7 @@ var toString = Object.prototype.toString;
@method typeOf
@for Ember
- @param item {Object} the item to check
+ @param {Object} item the item to check
@return {String} the type
*/
Ember.typeOf = function(item) {
@@ -6404,57 +6782,6 @@ Ember.typeOf = function(item) {
};
/**
- Returns true if the passed value is null or undefined. This avoids errors
- from JSLint complaining about use of ==, which can be technically
- confusing.
-
- ```javascript
- Ember.isNone(); // true
- Ember.isNone(null); // true
- Ember.isNone(undefined); // true
- Ember.isNone(''); // false
- Ember.isNone([]); // false
- Ember.isNone(function(){}); // false
- ```
-
- @method isNone
- @for Ember
- @param {Object} obj Value to test
- @return {Boolean}
-*/
-Ember.isNone = function(obj) {
- return obj === null || obj === undefined;
-};
-Ember.none = Ember.deprecateFunc("Ember.none is deprecated. Please use Ember.isNone instead.", Ember.isNone);
-
-/**
- Verifies that a value is `null` or an empty string, empty array,
- or empty function.
-
- Constrains the rules on `Ember.isNone` by returning false for empty
- string and empty arrays.
-
- ```javascript
- Ember.isEmpty(); // true
- Ember.isEmpty(null); // true
- Ember.isEmpty(undefined); // true
- Ember.isEmpty(''); // true
- Ember.isEmpty([]); // true
- Ember.isEmpty('Adam Hawkins'); // false
- Ember.isEmpty([0,1,2]); // false
- ```
-
- @method isEmpty
- @for Ember
- @param {Object} obj Value to test
- @return {Boolean}
-*/
-Ember.isEmpty = function(obj) {
- return obj === null || obj === undefined || (obj.length === 0 && typeof obj !== 'function') || (typeof obj === 'object' && Ember.get(obj, 'length') === 0);
-};
-Ember.empty = Ember.deprecateFunc("Ember.empty is deprecated. Please use Ember.isEmpty instead.", Ember.isEmpty) ;
-
-/**
This will compare two javascript values of possibly different types.
It will tell you which one is greater than the other by returning:
@@ -6618,7 +6945,7 @@ function _copy(obj, deep, seen, copies) {
@method copy
@for Ember
- @param {Object} object The object to clone
+ @param {Object} obj The object to clone
@param {Boolean} deep If true, a deep copy of the object is made
@return {Object} The cloned object
*/
@@ -6749,6 +7076,20 @@ Ember.Error.prototype = Ember.create(Error.prototype);
(function() {
/**
+ Expose RSVP implementation
+
+ @class RSVP
+ @namespace Ember
+ @constructor
+*/
+Ember.RSVP = requireModule('rsvp');
+
+})();
+
+
+
+(function() {
+/**
@module ember
@submodule ember-runtime
*/
@@ -6895,10 +7236,11 @@ Ember.String = {
*/
dasherize: function(str) {
var cache = STRING_DASHERIZE_CACHE,
- ret = cache[str];
+ hit = cache.hasOwnProperty(str),
+ ret;
- if (ret) {
- return ret;
+ if (hit) {
+ return cache[str];
} else {
ret = Ember.String.decamelize(str).replace(STRING_DASHERIZE_REGEXP,'-');
cache[str] = ret;
@@ -6908,13 +7250,14 @@ Ember.String = {
},
/**
- Returns the lowerCaseCamel form of a string.
+ 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
@@ -6924,6 +7267,8 @@ Ember.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();
});
},
@@ -6935,7 +7280,7 @@ Ember.String = {
'action_name'.classify(); // 'ActionName'
'css-class-name'.classify(); // 'CssClassName'
'my favorite items'.classify(); // 'MyFavoriteItems'
- ```
+ ```
@method classify
@param {String} str the string to classify
@@ -6976,10 +7321,10 @@ Ember.String = {
/**
Returns the Capitalized form of a string
- 'innerHTML'.capitalize() => 'InnerHTML'
- 'action_name'.capitalize() => 'Action_name'
- 'css-class-name'.capitalize() => 'Css-class-name'
- 'my favorite items'.capitalize() => 'My favorite items'
+ 'innerHTML'.capitalize() // 'InnerHTML'
+ 'action_name'.capitalize() // 'Action_name'
+ 'css-class-name'.capitalize() // 'Css-class-name'
+ 'my favorite items'.capitalize() // 'My favorite items'
@method capitalize
@param {String} str
@@ -7323,8 +7668,7 @@ function iter(key, value) {
@extends Ember.Mixin
@since Ember 0.9
*/
-Ember.Enumerable = Ember.Mixin.create(
- /** @scope Ember.Enumerable.prototype */ {
+Ember.Enumerable = Ember.Mixin.create({
// compatibility
isEnumerable: true,
@@ -7357,7 +7701,7 @@ Ember.Enumerable = Ember.Mixin.create(
@method nextObject
@param {Number} index the current index of the iteration
- @param {Object} previousObject the value returned by the last call to
+ @param {Object} previousObject the value returned by the last call to
`nextObject`.
@param {Object} context a context object you can use to maintain state.
@return {Object} the next object in the iteration or undefined
@@ -7376,10 +7720,10 @@ Ember.Enumerable = Ember.Mixin.create(
```javascript
var arr = ["a", "b", "c"];
- arr.firstObject(); // "a"
+ arr.get('firstObject'); // "a"
var arr = [];
- arr.firstObject(); // undefined
+ arr.get('firstObject'); // undefined
```
@property firstObject
@@ -7402,10 +7746,10 @@ Ember.Enumerable = Ember.Mixin.create(
```javascript
var arr = ["a", "b", "c"];
- arr.lastObject(); // "c"
+ arr.get('lastObject'); // "c"
var arr = [];
- arr.lastObject(); // undefined
+ arr.get('lastObject'); // undefined
```
@property lastObject
@@ -7538,7 +7882,7 @@ Ember.Enumerable = Ember.Mixin.create(
@return {Array} The mapped array.
*/
map: function(callback, target) {
- var ret = [];
+ var ret = Ember.A([]);
this.forEach(function(x, idx, i) {
ret[idx] = callback.call(target, x, idx,i);
});
@@ -7588,7 +7932,7 @@ Ember.Enumerable = Ember.Mixin.create(
@return {Array} A filtered array.
*/
filter: function(callback, target) {
- var ret = [];
+ var ret = Ember.A([]);
this.forEach(function(x, idx, i) {
if (callback.call(target, x, idx, i)) ret.push(x);
});
@@ -7877,7 +8221,7 @@ Ember.Enumerable = Ember.Mixin.create(
@return {Array} return values from calling invoke.
*/
invoke: function(methodName) {
- var args, ret = [];
+ var args, ret = Ember.A([]);
if (arguments.length>1) args = a_slice.call(arguments, 1);
this.forEach(function(x, idx) {
@@ -7898,23 +8242,25 @@ Ember.Enumerable = Ember.Mixin.create(
@return {Array} the enumerable as an array.
*/
toArray: function() {
- var ret = [];
+ var ret = Ember.A([]);
this.forEach(function(o, idx) { ret[idx] = o; });
return ret ;
},
/**
- Returns a copy of the array with all null elements removed.
+ Returns a copy of the array with all null and undefined elements removed.
```javascript
- var arr = ["a", null, "c", null];
+ var arr = ["a", null, "c", undefined];
arr.compact(); // ["a", "c"]
```
@method compact
- @return {Array} the array without null elements.
+ @return {Array} the array without null and undefined elements.
*/
- compact: function() { return this.without(null); },
+ compact: function() {
+ return this.filter(function(value) { return value != null; });
+ },
/**
Returns a new enumerable that excludes the passed value. The default
@@ -7932,7 +8278,7 @@ Ember.Enumerable = Ember.Mixin.create(
*/
without: function(value) {
if (!this.contains(value)) return this; // nothing to do
- var ret = [] ;
+ var ret = Ember.A([]);
this.forEach(function(k) {
if (k !== value) ret[ret.length] = k;
}) ;
@@ -7952,7 +8298,7 @@ Ember.Enumerable = Ember.Mixin.create(
@return {Ember.Enumerable}
*/
uniq: function() {
- var ret = [];
+ var ret = Ember.A([]);
this.forEach(function(k){
if (a_indexOf(ret, k)<0) ret.push(k);
});
@@ -7969,6 +8315,7 @@ Ember.Enumerable = Ember.Mixin.create(
@property []
@type Ember.Array
+ @return this
*/
'[]': Ember.computed(function(key, value) {
return this;
@@ -7983,8 +8330,9 @@ Ember.Enumerable = Ember.Mixin.create(
mixin.
@method addEnumerableObserver
- @param target {Object}
- @param opts {Hash}
+ @param {Object} target
+ @param {Hash} [opts]
+ @return this
*/
addEnumerableObserver: function(target, opts) {
var willChange = (opts && opts.willChange) || 'enumerableWillChange',
@@ -8002,8 +8350,9 @@ Ember.Enumerable = Ember.Mixin.create(
Removes a registered enumerable observer.
@method removeEnumerableObserver
- @param target {Object}
- @param [opts] {Hash}
+ @param {Object} target
+ @param {Hash} [opts]
+ @return this
*/
removeEnumerableObserver: function(target, opts) {
var willChange = (opts && opts.willChange) || 'enumerableWillChange',
@@ -8082,7 +8431,7 @@ Ember.Enumerable = Ember.Mixin.create(
@chainable
*/
enumerableContentDidChange: function(removing, adding) {
- var notify = this.propertyDidChange, removeCnt, addCnt, hasDelta;
+ var removeCnt, addCnt, hasDelta;
if ('number' === typeof removing) removeCnt = removing;
else if (removing) removeCnt = get(removing, 'length');
@@ -8120,7 +8469,7 @@ Ember.Enumerable = Ember.Mixin.create(
// HELPERS
//
-var get = Ember.get, set = Ember.set, meta = Ember.meta, map = Ember.EnumerableUtils.map, cacheFor = Ember.cacheFor;
+var get = Ember.get, set = Ember.set, map = Ember.EnumerableUtils.map, cacheFor = Ember.cacheFor;
function none(obj) { return obj===null || obj===undefined; }
@@ -8193,6 +8542,7 @@ Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.protot
@method objectAt
@param {Number} idx The index of the item to return.
+ @return {any} item at index or undefined
*/
objectAt: function(idx) {
if ((idx < 0) || (idx>=get(this, 'length'))) return undefined ;
@@ -8210,6 +8560,7 @@ Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.protot
@method objectsAt
@param {Array} indexes An array of indexes of items to return.
+ @return {Array}
*/
objectsAt: function(indexes) {
var self = this;
@@ -8229,6 +8580,7 @@ Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.protot
This property overrides the default property defined in `Ember.Enumerable`.
@property []
+ @return this
*/
'[]': Ember.computed(function(key, value) {
if (value !== undefined) this.replace(0, get(this, 'length'), value) ;
@@ -8262,15 +8614,19 @@ Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.protot
```
@method slice
- @param beginIndex {Integer} (Optional) index to begin slicing from.
- @param endIndex {Integer} (Optional) index to end the slice at.
+ @param {Integer} beginIndex (Optional) index to begin slicing from.
+ @param {Integer} endIndex (Optional) index to end the slice at.
@return {Array} New array with specified slice
*/
slice: function(beginIndex, endIndex) {
- var ret = [];
+ var ret = Ember.A([]);
var length = get(this, 'length') ;
if (none(beginIndex)) beginIndex = 0 ;
if (none(endIndex) || (endIndex > length)) endIndex = length ;
+
+ if (beginIndex < 0) beginIndex = length + beginIndex;
+ if (endIndex < 0) endIndex = length + endIndex;
+
while(beginIndex < endIndex) {
ret[ret.length] = this.objectAt(beginIndex++) ;
}
@@ -8368,7 +8724,7 @@ Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.protot
@method addArrayObserver
@param {Object} target The observer object.
@param {Hash} opts Optional hash of configuration options including
- `willChange`, `didChange`, and a `context` option.
+ `willChange` and `didChange` option.
@return {Ember.Array} receiver
*/
addArrayObserver: function(target, opts) {
@@ -8390,6 +8746,8 @@ Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.protot
@method removeArrayObserver
@param {Object} target The object observing the array.
+ @param {Hash} opts Optional hash of configuration options including
+ `willChange` and `didChange` option.
@return {Ember.Array} receiver
*/
removeArrayObserver: function(target, opts) {
@@ -8422,9 +8780,9 @@ Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.protot
@method arrayContentWillChange
@param {Number} startIdx The starting index in the array that will change.
- @param {Number} removeAmt The number of items that will be removed. If you
+ @param {Number} removeAmt The number of items that will be removed. If you
pass `null` assumes 0
- @param {Number} addAmt The number of items that will be added If you
+ @param {Number} addAmt The number of items that will be added If you
pass `null` assumes 0.
@return {Ember.Array} receiver
*/
@@ -8606,7 +8964,7 @@ Ember.Copyable = Ember.Mixin.create(
an exception.
@method copy
- @param deep {Boolean} if `true`, a deep copy of the object should be made
+ @param {Boolean} deep if `true`, a deep copy of the object should be made
@return {Object} copy of receiver
*/
copy: Ember.required(Function),
@@ -8785,8 +9143,7 @@ var forEach = Ember.EnumerableUtils.forEach;
@extends Ember.Mixin
@uses Ember.Enumerable
*/
-Ember.MutableEnumerable = Ember.Mixin.create(Ember.Enumerable,
- /** @scope Ember.MutableEnumerable.prototype */ {
+Ember.MutableEnumerable = Ember.Mixin.create(Ember.Enumerable, {
/**
__Required.__ You must implement this method to apply this mixin.
@@ -8871,7 +9228,7 @@ var EMPTY = [];
// HELPERS
//
-var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach;
+var get = Ember.get, set = Ember.set;
/**
This mixin defines the API for modifying array-like objects. These methods
@@ -8898,11 +9255,11 @@ Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable,
passed array. You should also call `this.enumerableContentDidChange()`
@method replace
- @param {Number} idx Starting index in the array to replace. If
+ @param {Number} idx Starting index in the array to replace. If
idx >= length, then append to the end of the array.
- @param {Number} amt Number of elements that should be removed from
+ @param {Number} amt Number of elements that should be removed from
the array, starting at *idx*.
- @param {Array} objects An array of zero or more objects that should be
+ @param {Array} objects An array of zero or more objects that should be
inserted into the array at *idx*
*/
replace: Ember.required(),
@@ -8941,6 +9298,7 @@ Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable,
@method insertAt
@param {Number} idx index of insert the object at.
@param {Object} object object to insert
+ @return this
*/
insertAt: function(idx, object) {
if (idx > get(this, 'length')) throw new Error(OUT_OF_RANGE_EXCEPTION) ;
@@ -8994,6 +9352,7 @@ Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable,
@method pushObject
@param {anything} obj object to push
+ @return {any} the same obj passed as param
*/
pushObject: function(obj) {
this.insertAt(get(this, 'length'), obj) ;
@@ -9073,6 +9432,7 @@ Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable,
@method unshiftObject
@param {anything} obj object to unshift
+ @return {any} the same obj passed as param
*/
unshiftObject: function(obj) {
this.insertAt(0, obj) ;
@@ -9167,7 +9527,7 @@ Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable,
@submodule ember-runtime
*/
-var get = Ember.get, set = Ember.set, defineProperty = Ember.defineProperty;
+var get = Ember.get, set = Ember.set;
/**
## Overview
@@ -9272,7 +9632,7 @@ Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ {
not defined upfront.
@method get
- @param {String} key The property to retrieve
+ @param {String} keyName The property to retrieve
@return {Object} The property value or undefined.
*/
get: function(keyName) {
@@ -9353,7 +9713,7 @@ Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ {
```
@method set
- @param {String} key The property to set
+ @param {String} keyName The property to set
@param {Object} value The value to set or `null`.
@return {Ember.Observable}
*/
@@ -9430,7 +9790,7 @@ Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ {
like.
@method propertyWillChange
- @param {String} key The property key that is about to change.
+ @param {String} keyName The property key that is about to change.
@return {Ember.Observable}
*/
propertyWillChange: function(keyName){
@@ -9765,6 +10125,16 @@ Ember.TargetActionSupport = Ember.Mixin.create({
// outputs: 'Our person has greeted'
```
+ You can also chain multiple event subscriptions:
+
+ ```javascript
+ person.on('greet', function() {
+ console.log('Our person has greeted');
+ }).one('greet', function() {
+ console.log('Offer one-time special');
+ }).off('event', this, forgetThis);
+ ```
+
@class Evented
@namespace Ember
@extends Ember.Mixin
@@ -9789,9 +10159,11 @@ Ember.Evented = Ember.Mixin.create({
@param {String} name The name of the event
@param {Object} [target] The "this" binding for the callback
@param {Function} method The callback to execute
+ @return this
*/
on: function(name, target, method) {
Ember.addListener(this, name, target, method);
+ return this;
},
/**
@@ -9807,6 +10179,7 @@ Ember.Evented = Ember.Mixin.create({
@param {String} name The name of the event
@param {Object} [target] The "this" binding for the callback
@param {Function} method The callback to execute
+ @return this
*/
one: function(name, target, method) {
if (!method) {
@@ -9815,6 +10188,7 @@ Ember.Evented = Ember.Mixin.create({
}
Ember.addListener(this, name, target, method, true);
+ return this;
},
/**
@@ -9855,9 +10229,11 @@ Ember.Evented = Ember.Mixin.create({
@param {String} name The name of the event
@param {Object} target The target of the subscription
@param {Function} method The function of the subscription
+ @return this
*/
off: function(name, target, method) {
Ember.removeListener(this, name, target, method);
+ return this;
},
/**
@@ -9888,8 +10264,7 @@ RSVP.async = function(callback, binding) {
@submodule ember-runtime
*/
-var get = Ember.get,
- slice = Array.prototype.slice;
+var get = Ember.get;
/**
@class Deferred
@@ -9965,7 +10340,6 @@ Ember.Container.set = Ember.set;
var set = Ember.set, get = Ember.get,
o_create = Ember.create,
o_defineProperty = Ember.platform.defineProperty,
- a_slice = Array.prototype.slice,
GUID_KEY = Ember.GUID_KEY,
guidFor = Ember.guidFor,
generateGuid = Ember.generateGuid,
@@ -10113,6 +10487,37 @@ CoreObject.PrototypeMixin = Mixin.create({
isInstance: true,
+ /**
+ An overridable method called when objects are instantiated. By default,
+ does nothing unless it is overridden during class definition.
+
+ Example:
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ init: function() {
+ this._super();
+ alert('Name is ' + this.get('name'));
+ }
+ });
+
+ var steve = App.Person.create({
+ name: "Steve"
+ });
+
+ // alerts 'Name is Steve'.
+ ```
+
+ NOTE: If you do override `init` for a framework class like `Ember.View` or
+ `Ember.ArrayController`, be sure to call `this._super()` in your
+ `init` declaration! If you don't, Ember may not have an opportunity to
+ do important setup work, and you'll see strange behavior in your
+ application.
+
+ ```
+
+ @method init
+ */
init: function() {},
/**
@@ -10157,14 +10562,14 @@ CoreObject.PrototypeMixin = Mixin.create({
view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz']
```
Adding a single property that is not an array will just add it in the array:
-
+
```javascript
var view = App.FooBarView.create({
classNames: 'baz'
})
view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz']
```
-
+
Using the `concatenatedProperties` property, we can tell to Ember that mix
the content of the properties.
@@ -10181,12 +10586,22 @@ CoreObject.PrototypeMixin = Mixin.create({
concatenatedProperties: null,
/**
+ Destroyed object property flag.
+
+ if this property is `true` the observers and bindings were already
+ removed by the effect of calling the `destroy()` method.
+
@property isDestroyed
@default false
*/
isDestroyed: false,
/**
+ Destruction scheduled flag. The `destroy()` method has been called.
+
+ The object stays intact until the end of the run loop at which point
+ the `isDestroyed` flag is set.
+
@property isDestroying
@default false
*/
@@ -10263,7 +10678,7 @@ CoreObject.PrototypeMixin = Mixin.create({
}
});
teacher = App.Teacher.create()
- teacher.toString(); // #=> "<App.Teacher:ember1026:Tom Dale>"
+ teacher.toString(); //=> "<App.Teacher:ember1026:Tom Dale>"
@method toString
@return {String} string representation
@@ -10442,463 +10857,6 @@ Ember.CoreObject = CoreObject;
@submodule ember-runtime
*/
-var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor, none = Ember.isNone;
-
-/**
- An unordered collection of objects.
-
- A Set works a bit like an array except that its items are not ordered. You
- can create a set to efficiently test for membership for an object. You can
- also iterate through a set just like an array, even accessing objects by
- index, however there is no guarantee as to their order.
-
- All Sets are observable via the Enumerable Observer API - which works
- on any enumerable object including both Sets and Arrays.
-
- ## Creating a Set
-
- You can create a set like you would most objects using
- `new Ember.Set()`. Most new sets you create will be empty, but you can
- also initialize the set with some content by passing an array or other
- enumerable of objects to the constructor.
-
- Finally, you can pass in an existing set and the set will be copied. You
- can also create a copy of a set by calling `Ember.Set#copy()`.
-
- ```javascript
- // creates a new empty set
- var foundNames = new Ember.Set();
-
- // creates a set with four names in it.
- var names = new Ember.Set(["Charles", "Tom", "Juan", "Alex"]); // :P
-
- // creates a copy of the names set.
- var namesCopy = new Ember.Set(names);
-
- // same as above.
- var anotherNamesCopy = names.copy();
- ```
-
- ## Adding/Removing Objects
-
- You generally add or remove objects from a set using `add()` or
- `remove()`. You can add any type of object including primitives such as
- numbers, strings, and booleans.
-
- Unlike arrays, objects can only exist one time in a set. If you call `add()`
- on a set with the same object multiple times, the object will only be added
- once. Likewise, calling `remove()` with the same object multiple times will
- remove the object the first time and have no effect on future calls until
- you add the object to the set again.
-
- NOTE: You cannot add/remove `null` or `undefined` to a set. Any attempt to do
- so will be ignored.
-
- In addition to add/remove you can also call `push()`/`pop()`. Push behaves
- just like `add()` but `pop()`, unlike `remove()` will pick an arbitrary
- object, remove it and return it. This is a good way to use a set as a job
- queue when you don't care which order the jobs are executed in.
-
- ## Testing for an Object
-
- To test for an object's presence in a set you simply call
- `Ember.Set#contains()`.
-
- ## Observing changes
-
- When using `Ember.Set`, you can observe the `"[]"` property to be
- alerted whenever the content changes. You can also add an enumerable
- observer to the set to be notified of specific objects that are added and
- removed from the set. See `Ember.Enumerable` for more information on
- enumerables.
-
- This is often unhelpful. If you are filtering sets of objects, for instance,
- it is very inefficient to re-filter all of the items each time the set
- changes. It would be better if you could just adjust the filtered set based
- on what was changed on the original set. The same issue applies to merging
- sets, as well.
-
- ## Other Methods
-
- `Ember.Set` primary implements other mixin APIs. For a complete reference
- on the methods you will use with `Ember.Set`, please consult these mixins.
- The most useful ones will be `Ember.Enumerable` and
- `Ember.MutableEnumerable` which implement most of the common iterator
- methods you are used to on Array.
-
- Note that you can also use the `Ember.Copyable` and `Ember.Freezable`
- APIs on `Ember.Set` as well. Once a set is frozen it can no longer be
- modified. The benefit of this is that when you call `frozenCopy()` on it,
- Ember will avoid making copies of the set. This allows you to write
- code that can know with certainty when the underlying set data will or
- will not be modified.
-
- @class Set
- @namespace Ember
- @extends Ember.CoreObject
- @uses Ember.MutableEnumerable
- @uses Ember.Copyable
- @uses Ember.Freezable
- @since Ember 0.9
-*/
-Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Ember.Freezable,
- /** @scope Ember.Set.prototype */ {
-
- // ..........................................................
- // IMPLEMENT ENUMERABLE APIS
- //
-
- /**
- This property will change as the number of objects in the set changes.
-
- @property length
- @type number
- @default 0
- */
- length: 0,
-
- /**
- Clears the set. This is useful if you want to reuse an existing set
- without having to recreate it.
-
- ```javascript
- var colors = new Ember.Set(["red", "green", "blue"]);
- colors.length; // 3
- colors.clear();
- colors.length; // 0
- ```
-
- @method clear
- @return {Ember.Set} An empty Set
- */
- clear: function() {
- if (this.isFrozen) { throw new Error(Ember.FROZEN_ERROR); }
-
- var len = get(this, 'length');
- if (len === 0) { return this; }
-
- var guid;
-
- this.enumerableContentWillChange(len, 0);
- Ember.propertyWillChange(this, 'firstObject');
- Ember.propertyWillChange(this, 'lastObject');
-
- for (var i=0; i < len; i++){
- guid = guidFor(this[i]);
- delete this[guid];
- delete this[i];
- }
-
- set(this, 'length', 0);
-
- Ember.propertyDidChange(this, 'firstObject');
- Ember.propertyDidChange(this, 'lastObject');
- this.enumerableContentDidChange(len, 0);
-
- return this;
- },
-
- /**
- Returns true if the passed object is also an enumerable that contains the
- same objects as the receiver.
-
- ```javascript
- var colors = ["red", "green", "blue"],
- same_colors = new Ember.Set(colors);
-
- same_colors.isEqual(colors); // true
- same_colors.isEqual(["purple", "brown"]); // false
- ```
-
- @method isEqual
- @param {Ember.Set} obj the other object.
- @return {Boolean}
- */
- isEqual: function(obj) {
- // fail fast
- if (!Ember.Enumerable.detect(obj)) return false;
-
- var loc = get(this, 'length');
- if (get(obj, 'length') !== loc) return false;
-
- while(--loc >= 0) {
- if (!obj.contains(this[loc])) return false;
- }
-
- return true;
- },
-
- /**
- Adds an object to the set. Only non-`null` objects can be added to a set
- and those can only be added once. If the object is already in the set or
- the passed value is null this method will have no effect.
-
- This is an alias for `Ember.MutableEnumerable.addObject()`.
-
- ```javascript
- var colors = new Ember.Set();
- colors.add("blue"); // ["blue"]
- colors.add("blue"); // ["blue"]
- colors.add("red"); // ["blue", "red"]
- colors.add(null); // ["blue", "red"]
- colors.add(undefined); // ["blue", "red"]
- ```
-
- @method add
- @param {Object} obj The object to add.
- @return {Ember.Set} The set itself.
- */
- add: Ember.aliasMethod('addObject'),
-
- /**
- Removes the object from the set if it is found. If you pass a `null` value
- or an object that is already not in the set, this method will have no
- effect. This is an alias for `Ember.MutableEnumerable.removeObject()`.
-
- ```javascript
- var colors = new Ember.Set(["red", "green", "blue"]);
- colors.remove("red"); // ["blue", "green"]
- colors.remove("purple"); // ["blue", "green"]
- colors.remove(null); // ["blue", "green"]
- ```
-
- @method remove
- @param {Object} obj The object to remove
- @return {Ember.Set} The set itself.
- */
- remove: Ember.aliasMethod('removeObject'),
-
- /**
- Removes the last element from the set and returns it, or `null` if it's empty.
-
- ```javascript
- var colors = new Ember.Set(["green", "blue"]);
- colors.pop(); // "blue"
- colors.pop(); // "green"
- colors.pop(); // null
- ```
-
- @method pop
- @return {Object} The removed object from the set or null.
- */
- pop: function() {
- if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR);
- var obj = this.length > 0 ? this[this.length-1] : null;
- this.remove(obj);
- return obj;
- },
-
- /**
- Inserts the given object on to the end of the set. It returns
- the set itself.
-
- This is an alias for `Ember.MutableEnumerable.addObject()`.
-
- ```javascript
- var colors = new Ember.Set();
- colors.push("red"); // ["red"]
- colors.push("green"); // ["red", "green"]
- colors.push("blue"); // ["red", "green", "blue"]
- ```
-
- @method push
- @return {Ember.Set} The set itself.
- */
- push: Ember.aliasMethod('addObject'),
-
- /**
- Removes the last element from the set and returns it, or `null` if it's empty.
-
- This is an alias for `Ember.Set.pop()`.
-
- ```javascript
- var colors = new Ember.Set(["green", "blue"]);
- colors.shift(); // "blue"
- colors.shift(); // "green"
- colors.shift(); // null
- ```
-
- @method shift
- @return {Object} The removed object from the set or null.
- */
- shift: Ember.aliasMethod('pop'),
-
- /**
- Inserts the given object on to the end of the set. It returns
- the set itself.
-
- This is an alias of `Ember.Set.push()`
-
- ```javascript
- var colors = new Ember.Set();
- colors.unshift("red"); // ["red"]
- colors.unshift("green"); // ["red", "green"]
- colors.unshift("blue"); // ["red", "green", "blue"]
- ```
-
- @method unshift
- @return {Ember.Set} The set itself.
- */
- unshift: Ember.aliasMethod('push'),
-
- /**
- Adds each object in the passed enumerable to the set.
-
- This is an alias of `Ember.MutableEnumerable.addObjects()`
-
- ```javascript
- var colors = new Ember.Set();
- colors.addEach(["red", "green", "blue"]); // ["red", "green", "blue"]
- ```
-
- @method addEach
- @param {Ember.Enumerable} objects the objects to add.
- @return {Ember.Set} The set itself.
- */
- addEach: Ember.aliasMethod('addObjects'),
-
- /**
- Removes each object in the passed enumerable to the set.
-
- This is an alias of `Ember.MutableEnumerable.removeObjects()`
-
- ```javascript
- var colors = new Ember.Set(["red", "green", "blue"]);
- colors.removeEach(["red", "blue"]); // ["green"]
- ```
-
- @method removeEach
- @param {Ember.Enumerable} objects the objects to remove.
- @return {Ember.Set} The set itself.
- */
- removeEach: Ember.aliasMethod('removeObjects'),
-
- // ..........................................................
- // PRIVATE ENUMERABLE SUPPORT
- //
-
- init: function(items) {
- this._super();
- if (items) this.addObjects(items);
- },
-
- // implement Ember.Enumerable
- nextObject: function(idx) {
- return this[idx];
- },
-
- // more optimized version
- firstObject: Ember.computed(function() {
- return this.length > 0 ? this[0] : undefined;
- }),
-
- // more optimized version
- lastObject: Ember.computed(function() {
- return this.length > 0 ? this[this.length-1] : undefined;
- }),
-
- // implements Ember.MutableEnumerable
- addObject: function(obj) {
- if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR);
- if (none(obj)) return this; // nothing to do
-
- var guid = guidFor(obj),
- idx = this[guid],
- len = get(this, 'length'),
- added ;
-
- if (idx>=0 && idx<len && (this[idx] === obj)) return this; // added
-
- added = [obj];
-
- this.enumerableContentWillChange(null, added);
- Ember.propertyWillChange(this, 'lastObject');
-
- len = get(this, 'length');
- this[guid] = len;
- this[len] = obj;
- set(this, 'length', len+1);
-
- Ember.propertyDidChange(this, 'lastObject');
- this.enumerableContentDidChange(null, added);
-
- return this;
- },
-
- // implements Ember.MutableEnumerable
- removeObject: function(obj) {
- if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR);
- if (none(obj)) return this; // nothing to do
-
- var guid = guidFor(obj),
- idx = this[guid],
- len = get(this, 'length'),
- isFirst = idx === 0,
- isLast = idx === len-1,
- last, removed;
-
-
- if (idx>=0 && idx<len && (this[idx] === obj)) {
- removed = [obj];
-
- this.enumerableContentWillChange(removed, null);
- if (isFirst) { Ember.propertyWillChange(this, 'firstObject'); }
- if (isLast) { Ember.propertyWillChange(this, 'lastObject'); }
-
- // swap items - basically move the item to the end so it can be removed
- if (idx < len-1) {
- last = this[len-1];
- this[idx] = last;
- this[guidFor(last)] = idx;
- }
-
- delete this[guid];
- delete this[len-1];
- set(this, 'length', len-1);
-
- if (isFirst) { Ember.propertyDidChange(this, 'firstObject'); }
- if (isLast) { Ember.propertyDidChange(this, 'lastObject'); }
- this.enumerableContentDidChange(removed, null);
- }
-
- return this;
- },
-
- // optimized version
- contains: function(obj) {
- return this[guidFor(obj)]>=0;
- },
-
- copy: function() {
- var C = this.constructor, ret = new C(), loc = get(this, 'length');
- set(ret, 'length', loc);
- while(--loc>=0) {
- ret[loc] = this[loc];
- ret[guidFor(this[loc])] = loc;
- }
- return ret;
- },
-
- toString: function() {
- var len = this.length, idx, array = [];
- for(idx = 0; idx < len; idx++) {
- array[idx] = this[idx];
- }
- return "Ember.Set<%@>".fmt(array.join(','));
- }
-
-});
-
-})();
-
-
-
-(function() {
-/**
-@module ember
-@submodule ember-runtime
-*/
-
/**
`Ember.Object` is the main base class for all Ember objects. It is a subclass
of `Ember.CoreObject` with the `Ember.Observable` mixin applied. For details,
@@ -11122,40 +11080,8 @@ Ember.Mixin.prototype.toString = classToString;
(function() {
-/**
-@module ember
-@submodule ember-runtime
-*/
-
-/**
- Defines a namespace that will contain an executable application. This is
- very similar to a normal namespace except that it is expected to include at
- least a 'ready' function which can be run to initialize the application.
-
- Currently `Ember.Application` is very similar to `Ember.Namespace.` However,
- this class may be augmented by additional frameworks so it is important to
- use this instance when building new applications.
-
- # Example Usage
-
- ```javascript
- MyApp = Ember.Application.create({
- VERSION: '1.0.0',
- store: Ember.Store.create().from(Ember.fixtures)
- });
-
- MyApp.ready = function() {
- //..init code goes here...
- }
- ```
-
- @class Application
- @namespace Ember
- @extends Ember.Namespace
-*/
Ember.Application = Ember.Namespace.extend();
-
})();
@@ -11166,6 +11092,8 @@ Ember.Application = Ember.Namespace.extend();
@submodule ember-runtime
*/
+var OUT_OF_RANGE_EXCEPTION = "Index out of range";
+var EMPTY = [];
var get = Ember.get, set = Ember.set;
@@ -11371,12 +11299,99 @@ Ember.ArrayProxy = Ember.Object.extend(Ember.MutableArray,
// No dependencies since Enumerable notifies length of change
}),
- replace: function(idx, amt, objects) {
- Ember.assert('The content property of '+ this.constructor + ' should be set before modifying it', this.get('content'));
- if (get(this, 'content')) this.replaceContent(idx, amt, objects);
+ _replace: function(idx, amt, objects) {
+ var content = get(this, 'content');
+ Ember.assert('The content property of '+ this.constructor + ' should be set before modifying it', content);
+ if (content) this.replaceContent(idx, amt, objects);
+ return this;
+ },
+
+ replace: function() {
+ if (get(this, 'arrangedContent') === get(this, 'content')) {
+ this._replace.apply(this, arguments);
+ } else {
+ throw new Ember.Error("Using replace on an arranged ArrayProxy is not allowed.");
+ }
+ },
+
+ _insertAt: function(idx, object) {
+ if (idx > get(this, 'content.length')) throw new Error(OUT_OF_RANGE_EXCEPTION);
+ this._replace(idx, 0, [object]);
+ return this;
+ },
+
+ insertAt: function(idx, object) {
+ if (get(this, 'arrangedContent') === get(this, 'content')) {
+ return this._insertAt(idx, object);
+ } else {
+ throw new Ember.Error("Using insertAt on an arranged ArrayProxy is not allowed.");
+ }
+ },
+
+ removeAt: function(start, len) {
+ if ('number' === typeof start) {
+ var content = get(this, 'content'),
+ arrangedContent = get(this, 'arrangedContent'),
+ indices = [], i;
+
+ if ((start < 0) || (start >= get(this, 'length'))) {
+ throw new Error(OUT_OF_RANGE_EXCEPTION);
+ }
+
+ if (len === undefined) len = 1;
+
+ // Get a list of indices in original content to remove
+ for (i=start; i<start+len; i++) {
+ // Use arrangedContent here so we avoid confusion with objects transformed by objectAtContent
+ indices.push(content.indexOf(arrangedContent.objectAt(i)));
+ }
+
+ // Replace in reverse order since indices will change
+ indices.sort(function(a,b) { return b - a; });
+
+ Ember.beginPropertyChanges();
+ for (i=0; i<indices.length; i++) {
+ this._replace(indices[i], 1, EMPTY);
+ }
+ Ember.endPropertyChanges();
+ }
+
+ return this ;
+ },
+
+ pushObject: function(obj) {
+ this._insertAt(get(this, 'content.length'), obj) ;
+ return obj ;
+ },
+
+ pushObjects: function(objects) {
+ this._replace(get(this, 'length'), 0, objects);
+ return this;
+ },
+
+ setObjects: function(objects) {
+ if (objects.length === 0) return this.clear();
+
+ var len = get(this, 'length');
+ this._replace(0, len, objects);
+ return this;
+ },
+
+ unshiftObject: function(obj) {
+ this._insertAt(0, obj) ;
+ return obj ;
+ },
+
+ unshiftObjects: function(objects) {
+ this._replace(0, 0, objects);
return this;
},
+ slice: function() {
+ var arr = this.toArray();
+ return arr.slice.apply(arr, arguments);
+ },
+
arrangedContentArrayWillChange: function(item, idx, removedCnt, addedCnt) {
this.arrayContentWillChange(idx, removedCnt, addedCnt);
},
@@ -11541,6 +11556,25 @@ Ember.ObjectProxy = Ember.Object.extend(
}
});
+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);
+ }
+});
+
})();
@@ -11588,7 +11622,7 @@ function addObserverForContentKey(content, keyName, proxy, idx, loc) {
Ember.addBeforeObserver(item, keyName, proxy, 'contentKeyWillChange');
Ember.addObserver(item, keyName, proxy, 'contentKeyDidChange');
- // keep track of the indicies each item was found at so we can map
+ // keep track of the index each item was found at so we can map
// it back when the obj changes.
guid = guidFor(item);
if (!objects[guid]) objects[guid] = [];
@@ -11660,7 +11694,7 @@ Ember.EachProxy = Ember.Object.extend({
// Invokes whenever the content array itself changes.
arrayWillChange: function(content, idx, removedCnt, addedCnt) {
- var keys = this._keys, key, array, lim;
+ var keys = this._keys, key, lim;
lim = removedCnt>0 ? idx+removedCnt : -1;
Ember.beginPropertyChanges(this);
@@ -11678,7 +11712,7 @@ Ember.EachProxy = Ember.Object.extend({
},
arrayDidChange: function(content, idx, removedCnt, addedCnt) {
- var keys = this._keys, key, array, lim;
+ var keys = this._keys, key, lim;
lim = addedCnt>0 ? idx+addedCnt : -1;
Ember.beginPropertyChanges(this);
@@ -11861,7 +11895,7 @@ if (ignore.length>0) {
/**
The NativeArray mixin contains the properties needed to to make the native
Array support Ember.MutableArray and all of its dependent APIs. Unless you
- have `Ember.EXTEND_PROTOTYPES or `Ember.EXTEND_PROTOTYPES.Array` set to
+ have `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Array` set to
false, this will be applied automatically. Otherwise you can apply the mixin
at anytime by calling `Ember.NativeArray.activate`.
@@ -11913,8 +11947,464 @@ if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Array) {
(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor, none = Ember.isNone, fmt = Ember.String.fmt;
+
+/**
+ An unordered collection of objects.
+
+ A Set works a bit like an array except that its items are not ordered. You
+ can create a set to efficiently test for membership for an object. You can
+ also iterate through a set just like an array, even accessing objects by
+ index, however there is no guarantee as to their order.
+
+ All Sets are observable via the Enumerable Observer API - which works
+ on any enumerable object including both Sets and Arrays.
+
+ ## Creating a Set
+
+ You can create a set like you would most objects using
+ `new Ember.Set()`. Most new sets you create will be empty, but you can
+ also initialize the set with some content by passing an array or other
+ enumerable of objects to the constructor.
+
+ Finally, you can pass in an existing set and the set will be copied. You
+ can also create a copy of a set by calling `Ember.Set#copy()`.
+
+ ```javascript
+ // creates a new empty set
+ var foundNames = new Ember.Set();
+
+ // creates a set with four names in it.
+ var names = new Ember.Set(["Charles", "Tom", "Juan", "Alex"]); // :P
+
+ // creates a copy of the names set.
+ var namesCopy = new Ember.Set(names);
+
+ // same as above.
+ var anotherNamesCopy = names.copy();
+ ```
+
+ ## Adding/Removing Objects
+
+ You generally add or remove objects from a set using `add()` or
+ `remove()`. You can add any type of object including primitives such as
+ numbers, strings, and booleans.
+
+ Unlike arrays, objects can only exist one time in a set. If you call `add()`
+ on a set with the same object multiple times, the object will only be added
+ once. Likewise, calling `remove()` with the same object multiple times will
+ remove the object the first time and have no effect on future calls until
+ you add the object to the set again.
+
+ NOTE: You cannot add/remove `null` or `undefined` to a set. Any attempt to do
+ so will be ignored.
+
+ In addition to add/remove you can also call `push()`/`pop()`. Push behaves
+ just like `add()` but `pop()`, unlike `remove()` will pick an arbitrary
+ object, remove it and return it. This is a good way to use a set as a job
+ queue when you don't care which order the jobs are executed in.
+
+ ## Testing for an Object
+
+ To test for an object's presence in a set you simply call
+ `Ember.Set#contains()`.
+
+ ## Observing changes
+
+ When using `Ember.Set`, you can observe the `"[]"` property to be
+ alerted whenever the content changes. You can also add an enumerable
+ observer to the set to be notified of specific objects that are added and
+ removed from the set. See `Ember.Enumerable` for more information on
+ enumerables.
+
+ This is often unhelpful. If you are filtering sets of objects, for instance,
+ it is very inefficient to re-filter all of the items each time the set
+ changes. It would be better if you could just adjust the filtered set based
+ on what was changed on the original set. The same issue applies to merging
+ sets, as well.
+
+ ## Other Methods
+
+ `Ember.Set` primary implements other mixin APIs. For a complete reference
+ on the methods you will use with `Ember.Set`, please consult these mixins.
+ The most useful ones will be `Ember.Enumerable` and
+ `Ember.MutableEnumerable` which implement most of the common iterator
+ methods you are used to on Array.
+
+ Note that you can also use the `Ember.Copyable` and `Ember.Freezable`
+ APIs on `Ember.Set` as well. Once a set is frozen it can no longer be
+ modified. The benefit of this is that when you call `frozenCopy()` on it,
+ Ember will avoid making copies of the set. This allows you to write
+ code that can know with certainty when the underlying set data will or
+ will not be modified.
+
+ @class Set
+ @namespace Ember
+ @extends Ember.CoreObject
+ @uses Ember.MutableEnumerable
+ @uses Ember.Copyable
+ @uses Ember.Freezable
+ @since Ember 0.9
+*/
+Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Ember.Freezable,
+ /** @scope Ember.Set.prototype */ {
+
+ // ..........................................................
+ // IMPLEMENT ENUMERABLE APIS
+ //
+
+ /**
+ This property will change as the number of objects in the set changes.
+
+ @property length
+ @type number
+ @default 0
+ */
+ length: 0,
+
+ /**
+ Clears the set. This is useful if you want to reuse an existing set
+ without having to recreate it.
+
+ ```javascript
+ var colors = new Ember.Set(["red", "green", "blue"]);
+ colors.length; // 3
+ colors.clear();
+ colors.length; // 0
+ ```
+
+ @method clear
+ @return {Ember.Set} An empty Set
+ */
+ clear: function() {
+ if (this.isFrozen) { throw new Error(Ember.FROZEN_ERROR); }
+
+ var len = get(this, 'length');
+ if (len === 0) { return this; }
+
+ var guid;
+
+ this.enumerableContentWillChange(len, 0);
+ Ember.propertyWillChange(this, 'firstObject');
+ Ember.propertyWillChange(this, 'lastObject');
+
+ for (var i=0; i < len; i++){
+ guid = guidFor(this[i]);
+ delete this[guid];
+ delete this[i];
+ }
+
+ set(this, 'length', 0);
+
+ Ember.propertyDidChange(this, 'firstObject');
+ Ember.propertyDidChange(this, 'lastObject');
+ this.enumerableContentDidChange(len, 0);
+
+ return this;
+ },
+
+ /**
+ Returns true if the passed object is also an enumerable that contains the
+ same objects as the receiver.
+
+ ```javascript
+ var colors = ["red", "green", "blue"],
+ same_colors = new Ember.Set(colors);
+
+ same_colors.isEqual(colors); // true
+ same_colors.isEqual(["purple", "brown"]); // false
+ ```
+
+ @method isEqual
+ @param {Ember.Set} obj the other object.
+ @return {Boolean}
+ */
+ isEqual: function(obj) {
+ // fail fast
+ if (!Ember.Enumerable.detect(obj)) return false;
+
+ var loc = get(this, 'length');
+ if (get(obj, 'length') !== loc) return false;
+
+ while(--loc >= 0) {
+ if (!obj.contains(this[loc])) return false;
+ }
+
+ return true;
+ },
+
+ /**
+ Adds an object to the set. Only non-`null` objects can be added to a set
+ and those can only be added once. If the object is already in the set or
+ the passed value is null this method will have no effect.
+
+ This is an alias for `Ember.MutableEnumerable.addObject()`.
+
+ ```javascript
+ var colors = new Ember.Set();
+ colors.add("blue"); // ["blue"]
+ colors.add("blue"); // ["blue"]
+ colors.add("red"); // ["blue", "red"]
+ colors.add(null); // ["blue", "red"]
+ colors.add(undefined); // ["blue", "red"]
+ ```
+
+ @method add
+ @param {Object} obj The object to add.
+ @return {Ember.Set} The set itself.
+ */
+ add: Ember.aliasMethod('addObject'),
+
+ /**
+ Removes the object from the set if it is found. If you pass a `null` value
+ or an object that is already not in the set, this method will have no
+ effect. This is an alias for `Ember.MutableEnumerable.removeObject()`.
+
+ ```javascript
+ var colors = new Ember.Set(["red", "green", "blue"]);
+ colors.remove("red"); // ["blue", "green"]
+ colors.remove("purple"); // ["blue", "green"]
+ colors.remove(null); // ["blue", "green"]
+ ```
+
+ @method remove
+ @param {Object} obj The object to remove
+ @return {Ember.Set} The set itself.
+ */
+ remove: Ember.aliasMethod('removeObject'),
+
+ /**
+ Removes the last element from the set and returns it, or `null` if it's empty.
+
+ ```javascript
+ var colors = new Ember.Set(["green", "blue"]);
+ colors.pop(); // "blue"
+ colors.pop(); // "green"
+ colors.pop(); // null
+ ```
+
+ @method pop
+ @return {Object} The removed object from the set or null.
+ */
+ pop: function() {
+ if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR);
+ var obj = this.length > 0 ? this[this.length-1] : null;
+ this.remove(obj);
+ return obj;
+ },
+
+ /**
+ Inserts the given object on to the end of the set. It returns
+ the set itself.
+
+ This is an alias for `Ember.MutableEnumerable.addObject()`.
+
+ ```javascript
+ var colors = new Ember.Set();
+ colors.push("red"); // ["red"]
+ colors.push("green"); // ["red", "green"]
+ colors.push("blue"); // ["red", "green", "blue"]
+ ```
+
+ @method push
+ @return {Ember.Set} The set itself.
+ */
+ push: Ember.aliasMethod('addObject'),
+
+ /**
+ Removes the last element from the set and returns it, or `null` if it's empty.
+
+ This is an alias for `Ember.Set.pop()`.
+
+ ```javascript
+ var colors = new Ember.Set(["green", "blue"]);
+ colors.shift(); // "blue"
+ colors.shift(); // "green"
+ colors.shift(); // null
+ ```
+
+ @method shift
+ @return {Object} The removed object from the set or null.
+ */
+ shift: Ember.aliasMethod('pop'),
+
+ /**
+ Inserts the given object on to the end of the set. It returns
+ the set itself.
+
+ This is an alias of `Ember.Set.push()`
+
+ ```javascript
+ var colors = new Ember.Set();
+ colors.unshift("red"); // ["red"]
+ colors.unshift("green"); // ["red", "green"]
+ colors.unshift("blue"); // ["red", "green", "blue"]
+ ```
+
+ @method unshift
+ @return {Ember.Set} The set itself.
+ */
+ unshift: Ember.aliasMethod('push'),
+
+ /**
+ Adds each object in the passed enumerable to the set.
+
+ This is an alias of `Ember.MutableEnumerable.addObjects()`
+
+ ```javascript
+ var colors = new Ember.Set();
+ colors.addEach(["red", "green", "blue"]); // ["red", "green", "blue"]
+ ```
+
+ @method addEach
+ @param {Ember.Enumerable} objects the objects to add.
+ @return {Ember.Set} The set itself.
+ */
+ addEach: Ember.aliasMethod('addObjects'),
+
+ /**
+ Removes each object in the passed enumerable to the set.
+
+ This is an alias of `Ember.MutableEnumerable.removeObjects()`
+
+ ```javascript
+ var colors = new Ember.Set(["red", "green", "blue"]);
+ colors.removeEach(["red", "blue"]); // ["green"]
+ ```
+
+ @method removeEach
+ @param {Ember.Enumerable} objects the objects to remove.
+ @return {Ember.Set} The set itself.
+ */
+ removeEach: Ember.aliasMethod('removeObjects'),
+
+ // ..........................................................
+ // PRIVATE ENUMERABLE SUPPORT
+ //
+
+ init: function(items) {
+ this._super();
+ if (items) this.addObjects(items);
+ },
+
+ // implement Ember.Enumerable
+ nextObject: function(idx) {
+ return this[idx];
+ },
+
+ // more optimized version
+ firstObject: Ember.computed(function() {
+ return this.length > 0 ? this[0] : undefined;
+ }),
+
+ // more optimized version
+ lastObject: Ember.computed(function() {
+ return this.length > 0 ? this[this.length-1] : undefined;
+ }),
+
+ // implements Ember.MutableEnumerable
+ addObject: function(obj) {
+ if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR);
+ if (none(obj)) return this; // nothing to do
+
+ var guid = guidFor(obj),
+ idx = this[guid],
+ len = get(this, 'length'),
+ added ;
+
+ if (idx>=0 && idx<len && (this[idx] === obj)) return this; // added
+
+ added = [obj];
+
+ this.enumerableContentWillChange(null, added);
+ Ember.propertyWillChange(this, 'lastObject');
+
+ len = get(this, 'length');
+ this[guid] = len;
+ this[len] = obj;
+ set(this, 'length', len+1);
+
+ Ember.propertyDidChange(this, 'lastObject');
+ this.enumerableContentDidChange(null, added);
+
+ return this;
+ },
+
+ // implements Ember.MutableEnumerable
+ removeObject: function(obj) {
+ if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR);
+ if (none(obj)) return this; // nothing to do
+
+ var guid = guidFor(obj),
+ idx = this[guid],
+ len = get(this, 'length'),
+ isFirst = idx === 0,
+ isLast = idx === len-1,
+ last, removed;
+
+
+ if (idx>=0 && idx<len && (this[idx] === obj)) {
+ removed = [obj];
+
+ this.enumerableContentWillChange(removed, null);
+ if (isFirst) { Ember.propertyWillChange(this, 'firstObject'); }
+ if (isLast) { Ember.propertyWillChange(this, 'lastObject'); }
+
+ // swap items - basically move the item to the end so it can be removed
+ if (idx < len-1) {
+ last = this[len-1];
+ this[idx] = last;
+ this[guidFor(last)] = idx;
+ }
+
+ delete this[guid];
+ delete this[len-1];
+ set(this, 'length', len-1);
+
+ if (isFirst) { Ember.propertyDidChange(this, 'firstObject'); }
+ if (isLast) { Ember.propertyDidChange(this, 'lastObject'); }
+ this.enumerableContentDidChange(removed, null);
+ }
+
+ return this;
+ },
+
+ // optimized version
+ contains: function(obj) {
+ return this[guidFor(obj)]>=0;
+ },
+
+ copy: function() {
+ var C = this.constructor, ret = new C(), loc = get(this, 'length');
+ set(ret, 'length', loc);
+ while(--loc>=0) {
+ ret[loc] = this[loc];
+ ret[guidFor(this[loc])] = loc;
+ }
+ return ret;
+ },
+
+ toString: function() {
+ var len = this.length, idx, array = [];
+ for(idx = 0; idx < len; idx++) {
+ array[idx] = this[idx];
+ }
+ return fmt("Ember.Set<%@>", [array.join(',')]);
+ }
+
+});
+
+})();
+
+
+
+(function() {
var DeferredMixin = Ember.DeferredMixin, // mixins/deferred
- EmberObject = Ember.Object, // system/object
get = Ember.get;
var Deferred = Ember.Object.extend(DeferredMixin);
@@ -12238,7 +12728,6 @@ Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, {
if (isSorted) {
var addedObjects = array.slice(idx, idx+addedCount);
- var arrangedContent = get(this, 'arrangedContent');
forEach(addedObjects, function(item) {
this.insertItemSorted(item);
@@ -12308,8 +12797,8 @@ Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, {
@submodule ember-runtime
*/
-var get = Ember.get, set = Ember.set, isGlobalPath = Ember.isGlobalPath,
- forEach = Ember.EnumerableUtils.forEach, replace = Ember.EnumerableUtils.replace;
+var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach,
+ replace = Ember.EnumerableUtils.replace;
/**
`Ember.ArrayController` provides a way for you to publish a collection of
@@ -12437,7 +12926,8 @@ Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin,
objectAtContent: function(idx) {
var length = get(this, 'length'),
- object = get(this,'arrangedContent').objectAt(idx);
+ arrangedContent = get(this,'arrangedContent'),
+ object = arrangedContent && arrangedContent.objectAt(idx);
if (idx >= 0 && idx < length) {
var controllerClass = this.lookupItemController(object);
@@ -12457,20 +12947,20 @@ Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin,
arrangedContentDidChange: function() {
this._super();
- this._resetSubContainers();
+ this._resetSubControllers();
},
arrayContentDidChange: function(idx, removedCnt, addedCnt) {
- var subContainers = get(this, 'subContainers'),
- subContainersToRemove = subContainers.slice(idx, idx+removedCnt);
+ var subControllers = get(this, '_subControllers'),
+ subControllersToRemove = subControllers.slice(idx, idx+removedCnt);
- forEach(subContainersToRemove, function(subContainer) {
- if (subContainer) { subContainer.destroy(); }
+ forEach(subControllersToRemove, function(subController) {
+ if (subController) { subController.destroy(); }
});
- replace(subContainers, idx, removedCnt, new Array(addedCnt));
+ replace(subControllers, idx, removedCnt, new Array(addedCnt));
- // The shadow array of subcontainers must be updated before we trigger
+ // The shadow array of subcontrollers must be updated before we trigger
// observers, otherwise observers will get the wrong subcontainer when
// calling `objectAt`
this._super(idx, removedCnt, addedCnt);
@@ -12478,43 +12968,40 @@ Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin,
init: function() {
this._super();
- if (!this.get('content')) { this.set('content', Ember.A()); }
- this._resetSubContainers();
+ if (!this.get('content')) { Ember.defineProperty(this, 'content', undefined, Ember.A()); }
+ this.set('_subControllers', Ember.A());
},
controllerAt: function(idx, object, controllerClass) {
var container = get(this, 'container'),
- subContainers = get(this, 'subContainers'),
- subContainer = subContainers[idx],
- controller;
+ subControllers = get(this, '_subControllers'),
+ subController = subControllers[idx];
- if (!subContainer) {
- subContainer = subContainers[idx] = container.child();
+ if (!subController) {
+ subController = container.lookup("controller:" + controllerClass, { singleton: false });
+ subControllers[idx] = subController;
}
- controller = subContainer.lookup("controller:" + controllerClass);
- if (!controller) {
+ if (!subController) {
throw new Error('Could not resolve itemController: "' + controllerClass + '"');
}
- controller.set('target', this);
- controller.set('content', object);
+ subController.set('target', this);
+ subController.set('content', object);
- return controller;
+ return subController;
},
- subContainers: null,
+ _subControllers: null,
- _resetSubContainers: function() {
- var subContainers = get(this, 'subContainers');
+ _resetSubControllers: function() {
+ var subControllers = get(this, '_subControllers');
- if (subContainers) {
- forEach(subContainers, function(subContainer) {
- if (subContainer) { subContainer.destroy(); }
- });
- }
+ forEach(subControllers, function(subController) {
+ if (subController) { subController.destroy(); }
+ });
- this.set('subContainers', Ember.A());
+ this.set('_subControllers', Ember.A());
}
});
@@ -12572,7 +13059,7 @@ Ember Runtime
*/
var jQuery = Ember.imports.jQuery;
-Ember.assert("Ember Views require jQuery 1.8 or 1.9", jQuery && (jQuery().jquery.match(/^1\.(8|9)(\.\d+)?(pre|rc\d?)?/) || Ember.ENV.FORCE_JQUERY));
+Ember.assert("Ember Views require jQuery 1.8, 1.9 or 2.0", jQuery && (jQuery().jquery.match(/^((1\.(8|9))|2.0)(\.\d+)?(pre|rc\d?)?/) || Ember.ENV.FORCE_JQUERY));
/**
Alias for jQuery
@@ -12591,15 +13078,16 @@ Ember.$ = jQuery;
@module ember
@submodule ember-views
*/
+if (Ember.$) {
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#dndevents
+ var dragEvents = Ember.String.w('dragstart drag dragenter dragleave dragover drop dragend');
-// http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#dndevents
-var dragEvents = Ember.String.w('dragstart drag dragenter dragleave dragover drop dragend');
-
-// Copies the `dataTransfer` property from a browser event object onto the
-// jQuery event object for the specified events
-Ember.EnumerableUtils.forEach(dragEvents, function(eventName) {
- Ember.$.event.fixHooks[eventName] = { props: ['dataTransfer'] };
-});
+ // Copies the `dataTransfer` property from a browser event object onto the
+ // jQuery event object for the specified events
+ Ember.EnumerableUtils.forEach(dragEvents, function(eventName) {
+ Ember.$.event.fixHooks[eventName] = { props: ['dataTransfer'] };
+ });
+}
})();
@@ -12616,7 +13104,8 @@ Ember.EnumerableUtils.forEach(dragEvents, function(eventName) {
// Internet Explorer prior to 9 does not allow setting innerHTML if the first element
// is a "zero-scope" element. This problem can be worked around by making
// the first node an invisible text node. We, like Modernizr, use &shy;
-var needsShy = (function(){
+
+var needsShy = this.document && (function(){
var testEl = document.createElement('div');
testEl.innerHTML = "<div></div>";
testEl.firstChild.innerHTML = "<script></script>";
@@ -12626,7 +13115,7 @@ var needsShy = (function(){
// IE 8 (and likely earlier) likes to move whitespace preceeding
// a script tag to appear after it. This means that we can
// accidentally remove whitespace when updating a morph.
-var movesWhitespace = (function() {
+var movesWhitespace = this.document && (function() {
var testEl = document.createElement('div');
testEl.innerHTML = "Test: <script type='text/x-placeholder'></script>Value";
return testEl.childNodes[0].nodeValue === 'Test:' &&
@@ -12712,9 +13201,11 @@ var setInnerHTML = function(element, html) {
if (canSetInnerHTML(tagName)) {
setInnerHTMLWithoutFix(element, html);
} else {
- Ember.assert("Can't set innerHTML on "+element.tagName+" in this browser", element.outerHTML);
+ // Firefox versions < 11 do not have support for element.outerHTML.
+ var outerHTML = element.outerHTML || new XMLSerializer().serializeToString(element);
+ Ember.assert("Can't set innerHTML on "+element.tagName+" in this browser", outerHTML);
- var startTag = element.outerHTML.match(new RegExp("<"+tagName+"([^>]*)>", 'i'))[0],
+ var startTag = outerHTML.match(new RegExp("<"+tagName+"([^>]*)>", 'i'))[0],
endTag = '</'+tagName+'>';
var wrapper = document.createElement('div');
@@ -12751,11 +13242,6 @@ Ember.ViewUtils = {
*/
var get = Ember.get, set = Ember.set;
-var indexOf = Ember.ArrayPolyfills.indexOf;
-
-
-
-
var ClassSet = function() {
this.seen = {};
@@ -12927,7 +13413,7 @@ Ember._RenderBuffer.prototype =
*/
addClass: function(className) {
// lazily create elementClasses
- var elementClasses = this.elementClasses = (this.elementClasses || new ClassSet());
+ this.elementClasses = (this.elementClasses || new ClassSet());
this.elementClasses.add(className);
this.classes = this.elementClasses.list;
@@ -13032,7 +13518,7 @@ Ember._RenderBuffer.prototype =
@chainable
*/
style: function(name, value) {
- var style = this.elementStyle = (this.elementStyle || {});
+ this.elementStyle = (this.elementStyle || {});
this.elementStyle[name] = value;
return this;
@@ -13202,7 +13688,9 @@ Ember._RenderBuffer.prototype =
*/
string: function() {
if (this._element) {
- return this.element().outerHTML;
+ // Firefox versions < 11 do not have support for element.outerHTML.
+ return this.element().outerHTML ||
+ new XMLSerializer().serializeToString(this.element());
} else {
return this.innerString();
}
@@ -13458,8 +13946,9 @@ Ember.EventDispatcher = Ember.Object.extend(
// Add a new named queue for rendering views that happens
// after bindings have synced, and a queue for scheduling actions
// that that should occur after view rendering.
-var queues = Ember.run.queues;
-queues.splice(Ember.$.inArray('actions', queues)+1, 0, 'render', 'afterRender');
+var queues = Ember.run.queues,
+ indexOf = Ember.ArrayPolyfills.indexOf;
+queues.splice(indexOf.call(queues, 'actions')+1, 0, 'render', 'afterRender');
})();
@@ -13495,8 +13984,7 @@ Ember.ControllerMixin.reopen({
},
_modelDidChange: Ember.observer(function() {
- var containers = get(this, '_childContainers'),
- container;
+ var containers = get(this, '_childContainers');
for (var prop in containers) {
if (!containers.hasOwnProperty(prop)) { continue; }
@@ -13525,9 +14013,8 @@ var states = {};
@submodule ember-views
*/
-var get = Ember.get, set = Ember.set, addObserver = Ember.addObserver, removeObserver = Ember.removeObserver;
-var meta = Ember.meta, guidFor = Ember.guidFor, fmt = Ember.String.fmt;
-var a_slice = [].slice;
+var get = Ember.get, set = Ember.set;
+var guidFor = Ember.guidFor;
var a_forEach = Ember.EnumerableUtils.forEach;
var a_addObject = Ember.EnumerableUtils.addObject;
@@ -13544,7 +14031,7 @@ var childViewsProperty = Ember.computed(function() {
ret.replace = function (idx, removedCount, addedViews) {
if (view instanceof Ember.ContainerView) {
- Ember.deprecate("Manipulating a Ember.ContainerView through its childViews property is deprecated. Please use the ContainerView instance itself as an Ember.MutableArray.");
+ Ember.deprecate("Manipulating an Ember.ContainerView through its childViews property is deprecated. Please use the ContainerView instance itself as an Ember.MutableArray.");
return view.replace(idx, removedCount, addedViews);
}
throw new Error("childViews is immutable");
@@ -14249,7 +14736,7 @@ class:
* `mouseEnter`
* `mouseLeave`
- Form events:
+ Form events:
* `submit`
* `change`
@@ -14257,7 +14744,7 @@ class:
* `focusOut`
* `input`
- HTML5 drag and drop events:
+ HTML5 drag and drop events:
* `dragStart`
* `drag`
@@ -14803,7 +15290,6 @@ Ember.View = Ember.CoreView.extend(
// JavaScript property changes.
var observer = function() {
elem = this.$();
- if (!elem) { return; }
attributeValue = get(this, property);
@@ -14998,6 +15484,10 @@ Ember.View = Ember.CoreView.extend(
not have an HTML representation yet, `createElement()` will be called
automatically.
+ If your application uses the `rootElement` property, you must append
+ the view within that element. Rendering views outside of the `rootElement`
+ is not supported.
+
Note that this method just schedules the view to be appended; the DOM
element will not be appended to the document body until all bindings have
finished synchronizing.
@@ -15072,9 +15562,9 @@ Ember.View = Ember.CoreView.extend(
willInsertElement: Ember.K,
/**
- Called when the element of the view has been inserted into the DOM.
- Override this function to do any set up that requires an element in the
- document body.
+ Called when the element of the view has been inserted into the DOM
+ or after the view was re-rendered. Override this function to do any
+ set up that requires an element in the document body.
@event didInsertElement
*/
@@ -15671,10 +16161,23 @@ Ember.View = Ember.CoreView.extend(
},
registerObserver: function(root, path, target, observer) {
- Ember.addObserver(root, path, target, observer);
+ if (!observer && 'function' === typeof target) {
+ observer = target;
+ target = null;
+ }
+
+ var view = this,
+ stateCheckedObserver = function(){
+ view.currentState.invokeObserver(this, observer);
+ },
+ scheduledObserver = function() {
+ Ember.run.scheduleOnce('render', this, stateCheckedObserver);
+ };
+
+ Ember.addObserver(root, path, target, scheduledObserver);
this.one('willClearRender', function() {
- Ember.removeObserver(root, path, target, observer);
+ Ember.removeObserver(root, path, target, scheduledObserver);
});
}
@@ -15705,17 +16208,24 @@ Ember.View = Ember.CoreView.extend(
// once the view has been inserted into the DOM, legal manipulations
// are done on the DOM element.
+function notifyMutationListeners() {
+ Ember.run.once(Ember.View, 'notifyMutationListeners');
+}
+
var DOMManager = {
prepend: function(view, html) {
view.$().prepend(html);
+ notifyMutationListeners();
},
after: function(view, html) {
view.$().after(html);
+ notifyMutationListeners();
},
html: function(view, html) {
view.$().html(html);
+ notifyMutationListeners();
},
replace: function(view) {
@@ -15725,15 +16235,18 @@ var DOMManager = {
view._insertElementLater(function() {
Ember.$(element).replaceWith(get(view, 'element'));
+ notifyMutationListeners();
});
},
remove: function(view) {
view.$().remove();
+ notifyMutationListeners();
},
empty: function(view) {
view.$().empty();
+ notifyMutationListeners();
}
};
@@ -15794,14 +16307,14 @@ Ember.View.reopenClass({
`className` and optional `falsyClassName`.
- if a `className` or `falsyClassName` has been specified:
- - if the value is truthy and `className` has been specified,
+ - if the value is truthy and `className` has been specified,
`className` is returned
- - if the value is falsy and `falsyClassName` has been specified,
+ - if the value is falsy and `falsyClassName` has been specified,
`falsyClassName` is returned
- otherwise `null` is returned
- - if the value is `true`, the dasherized last part of the supplied path
+ - if the value is `true`, the dasherized last part of the supplied path
is returned
- - if the value is not `false`, `undefined` or `null`, the `value`
+ - if the value is not `false`, `undefined` or `null`, the `value`
is returned
- if none of the above rules apply, `null` is returned
@@ -15848,6 +16361,20 @@ Ember.View.reopenClass({
}
});
+var mutation = Ember.Object.extend(Ember.Evented).create();
+
+Ember.View.addMutationListener = function(callback) {
+ mutation.on('change', callback);
+};
+
+Ember.View.removeMutationListener = function(callback) {
+ mutation.off('change', callback);
+};
+
+Ember.View.notifyMutationListeners = function() {
+ mutation.trigger('change');
+};
+
/**
Global views hash
@@ -15873,6 +16400,9 @@ Ember.View.applyAttributeBindings = function(elem, name, value) {
elem.attr(name, value);
}
} else if (name === 'value' || type === 'boolean') {
+ // We can't set properties to undefined
+ if (value === undefined) { value = null; }
+
if (value !== elem.prop(name)) {
// value and booleans should always be properties
elem.prop(name, value);
@@ -15928,7 +16458,8 @@ Ember.View.states._default = {
return false;
},
- rerender: Ember.K
+ rerender: Ember.K,
+ invokeObserver: Ember.K
};
})();
@@ -15979,7 +16510,7 @@ Ember.merge(preRender, {
@submodule ember-views
*/
-var get = Ember.get, set = Ember.set, meta = Ember.meta;
+var get = Ember.get, set = Ember.set;
var inBuffer = Ember.View.states.inBuffer = Ember.create(Ember.View.states._default);
@@ -16049,6 +16580,10 @@ Ember.merge(inBuffer, {
}
return value;
+ },
+
+ invokeObserver: function(target, observer) {
+ observer.call(target);
}
});
@@ -16063,7 +16598,7 @@ Ember.merge(inBuffer, {
@submodule ember-views
*/
-var get = Ember.get, set = Ember.set, meta = Ember.meta;
+var get = Ember.get, set = Ember.set;
var hasElement = Ember.View.states.hasElement = Ember.create(Ember.View.states._default);
@@ -16136,6 +16671,10 @@ Ember.merge(hasElement, {
} else {
return true; // continue event propagation
}
+ },
+
+ invokeObserver: function(target, observer) {
+ observer.call(target);
}
});
@@ -16204,8 +16743,6 @@ Ember.View.cloneStates = function(from) {
into.hasElement = Ember.create(into._default);
into.inDOM = Ember.create(into.hasElement);
- var viewState;
-
for (var stateName in from) {
if (!from.hasOwnProperty(stateName)) { continue; }
Ember.merge(into[stateName], from[stateName]);
@@ -16226,7 +16763,7 @@ var states = Ember.View.cloneStates(Ember.View.states);
@submodule ember-views
*/
-var get = Ember.get, set = Ember.set, meta = Ember.meta;
+var get = Ember.get, set = Ember.set;
var forEach = Ember.EnumerableUtils.forEach;
/**
@@ -16651,7 +17188,7 @@ var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
Given an empty `<body>` and the following code:
- ```javascript
+ ```javascript
someItemsView = Ember.CollectionView.create({
classNames: ['a-collection'],
content: ['A','B','C'],
@@ -16724,7 +17261,7 @@ var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
} else {
viewClass = App.SongView;
}
- this._super(viewClass, attrs);
+ return this._super(viewClass, attrs);
}
});
```
@@ -16904,7 +17441,7 @@ Ember.CollectionView = Ember.ContainerView.extend(
*/
arrayDidChange: function(content, start, removed, added) {
var itemViewClass = get(this, 'itemViewClass'),
- addedViews = [], view, item, idx, len, itemTagName;
+ addedViews = [], view, item, idx, len;
if ('string' === typeof itemViewClass) {
itemViewClass = get(itemViewClass);
@@ -17007,15 +17544,15 @@ define("metamorph",
var K = function(){},
guid = 0,
- document = window.document,
+ document = this.document,
// Feature-detect the W3C range API, the extended check is for IE9 which only partially supports ranges
- supportsRange = ('createRange' in document) && (typeof Range !== 'undefined') && Range.prototype.createContextualFragment,
+ supportsRange = document && ('createRange' in document) && (typeof Range !== 'undefined') && Range.prototype.createContextualFragment,
// Internet Explorer prior to 9 does not allow setting innerHTML if the first element
// is a "zero-scope" element. This problem can be worked around by making
// the first node an invisible text node. We, like Modernizr, use &shy;
- needsShy = (function(){
+ needsShy = document && (function(){
var testEl = document.createElement('div');
testEl.innerHTML = "<div></div>";
testEl.firstChild.innerHTML = "<script></script>";
@@ -17026,7 +17563,7 @@ define("metamorph",
// IE 8 (and likely earlier) likes to move whitespace preceeding
// a script tag to appear after it. This means that we can
// accidentally remove whitespace when updating a morph.
- movesWhitespace = (function() {
+ movesWhitespace = document && (function() {
var testEl = document.createElement('div');
testEl.innerHTML = "Test: <script type='text/x-placeholder'></script>Value";
return testEl.childNodes[0].nodeValue === 'Test:' &&
@@ -17460,7 +17997,7 @@ define("metamorph",
(function() {
/**
@module ember
-@submodule ember-handlebars
+@submodule ember-handlebars-compiler
*/
// Eliminate dependency on any Ember to simplify precompilation workflow
@@ -17470,8 +18007,12 @@ var objectCreate = Object.create || function(parent) {
return new F();
};
-var Handlebars = this.Handlebars || Ember.imports.Handlebars;
-Ember.assert("Ember Handlebars requires Handlebars 1.0.0-rc.3 or greater", Handlebars && Handlebars.VERSION.match(/^1\.0\.[0-9](\.rc\.[23456789]+)?/));
+var Handlebars = this.Handlebars || (Ember.imports && Ember.imports.Handlebars);
+if(!Handlebars && typeof require === 'function') {
+ Handlebars = require('handlebars');
+}
+
+Ember.assert("Ember Handlebars requires Handlebars 1.0.0-rc.3 or greater. Include a SCRIPT tag in the HTML HEAD linking to the Handlebars file before you link to Ember.", Handlebars && Handlebars.COMPILER_REVISION === 2);
/**
Prepares the Handlebars templating library for use inside Ember's view
@@ -17823,7 +18364,7 @@ Ember.Handlebars.registerHelper('helperMissing', function(path, options) {
## Example with bound options
- Bound hash options are also supported. Example:
+ Bound hash options are also supported. Example:
```handlebars
{{repeat text countBinding="numRepeats"}}
@@ -17861,15 +18402,15 @@ Ember.Handlebars.registerHelper('helperMissing', function(path, options) {
{{concatenate prop1 prop2 prop3}}. If any of the properties change,
the helpr will re-render. Note that dependency keys cannot be
using in conjunction with multi-property helpers, since it is ambiguous
- which property the dependent keys would belong to.
-
+ which property the dependent keys would belong to.
+
## Use with unbound helper
- The {{unbound}} helper can be used with bound helper invocations
+ The {{unbound}} helper can be used with bound helper invocations
to render them in their unbound form, e.g.
```handlebars
- {{unbound capitalize name}}
+ {{unbound capitalize name}}
```
In this example, if the name property changes, the helper
@@ -17895,7 +18436,7 @@ Ember.Handlebars.registerBoundHelper = function(name, fn) {
view = data.view,
currentContext = (options.contexts && options.contexts[0]) || this,
normalized,
- pathRoot, path,
+ pathRoot, path,
loc, hashOption;
// Detect bound options (e.g. countBinding="otherCount")
@@ -17942,10 +18483,10 @@ Ember.Handlebars.registerBoundHelper = function(name, fn) {
view.appendChild(bindView);
- view.registerObserver(pathRoot, path, bindView, rerenderBoundHelperView);
+ view.registerObserver(pathRoot, path, bindView, bindView.rerender);
for (var i=0, l=dependentKeys.length; i<l; i++) {
- view.registerObserver(pathRoot, path + '.' + dependentKeys[i], bindView, rerenderBoundHelperView);
+ view.registerObserver(pathRoot, path + '.' + dependentKeys[i], bindView, bindView.rerender);
}
}
@@ -17965,7 +18506,6 @@ Ember.Handlebars.registerBoundHelper = function(name, fn) {
*/
function evaluateMultiPropertyBoundHelper(context, fn, normalizedProperties, options) {
var numProperties = normalizedProperties.length,
- self = this,
data = options.data,
view = data.view,
hash = options.hash,
@@ -18001,7 +18541,7 @@ function evaluateMultiPropertyBoundHelper(context, fn, normalizedProperties, opt
// Assemble liast of watched properties that'll re-render this helper.
watchedProperties = [];
for (boundOption in boundOptions) {
- if (boundOptions.hasOwnProperty(boundOption)) {
+ if (boundOptions.hasOwnProperty(boundOption)) {
watchedProperties.push(normalizePath(context, boundOptions[boundOption], data));
}
}
@@ -18010,7 +18550,7 @@ function evaluateMultiPropertyBoundHelper(context, fn, normalizedProperties, opt
// Observe each property.
for (loc = 0, len = watchedProperties.length; loc < len; ++loc) {
property = watchedProperties[loc];
- view.registerObserver(property.root, property.path, bindView, rerenderBoundHelperView);
+ view.registerObserver(property.root, property.path, bindView, bindView.rerender);
}
}
@@ -18018,17 +18558,6 @@ function evaluateMultiPropertyBoundHelper(context, fn, normalizedProperties, opt
/**
@private
- An observer function used with bound helpers which
- will schedule a re-render of the _SimpleHandlebarsView
- connected with the helper.
-*/
-function rerenderBoundHelperView() {
- Ember.run.scheduleOnce('render', this, 'rerender');
-}
-
-/**
- @private
-
Renders the unbound form of an otherwise bound helper function.
@param {Function} fn
@@ -18130,22 +18659,30 @@ Ember.Handlebars.resolvePaths = function(options) {
var set = Ember.set, get = Ember.get;
var Metamorph = requireModule('metamorph');
+function notifyMutationListeners() {
+ Ember.run.once(Ember.View, 'notifyMutationListeners');
+}
+
// DOMManager should just abstract dom manipulation between jquery and metamorph
var DOMManager = {
remove: function(view) {
view.morph.remove();
+ notifyMutationListeners();
},
prepend: function(view, html) {
view.morph.prepend(html);
+ notifyMutationListeners();
},
after: function(view, html) {
view.morph.after(html);
+ notifyMutationListeners();
},
html: function(view, html) {
view.morph.html(html);
+ notifyMutationListeners();
},
// This is messed up.
@@ -18168,11 +18705,13 @@ var DOMManager = {
morph.replaceWith(buffer.string());
view.transitionTo('inDOM');
view.triggerRecursively('didInsertElement');
+ notifyMutationListeners();
});
},
empty: function(view) {
view.morph.html("");
+ notifyMutationListeners();
}
};
@@ -18194,6 +18733,7 @@ Ember._Metamorph = Ember.Mixin.create({
init: function() {
this._super();
this.morph = Metamorph();
+ Ember.deprecate('Supplying a tagName to Metamorph views is unreliable and is deprecated. You may be setting the tagName on a Handlebars helper that creates a Metamorph.', !this.tagName);
},
beforeRender: function(buffer) {
@@ -18936,14 +19476,14 @@ EmberHandlebars.registerHelper('unless', function(context, options) {
Result in the following rendered output:
- ```html
+ ```html
<img class="aValue">
```
A boolean return value will insert a specified class name if the property
returns `true` and remove the class name if the property returns `false`.
- A class name is provided via the syntax
+ A class name is provided via the syntax
`somePropertyName:class-name-if-true`.
```javascript
@@ -19057,15 +19597,13 @@ EmberHandlebars.registerHelper('bindAttr', function(options) {
Ember.View.applyAttributeBindings(elem, attr, result);
};
- invoker = function() {
- Ember.run.scheduleOnce('render', observer);
- };
-
// Add an observer to the view for when the property changes.
// When the observer fires, find the element using the
// unique data id and update the attribute to the new value.
- if (path !== 'this') {
- view.registerObserver(normalized.root, normalized.path, invoker);
+ // Note: don't add observer when path is 'this' or path
+ // 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);
}
// if this changes, also change the logic in ember-views/lib/views/view.js
@@ -19100,9 +19638,9 @@ EmberHandlebars.registerHelper('bindAttr', function(options) {
@method bindClasses
@for Ember.Handlebars
@param {Ember.Object} context The context from which to lookup properties
- @param {String} classBindings A string, space-separated, of class bindings
+ @param {String} classBindings A string, space-separated, of class bindings
to use
- @param {Ember.View} view The view in which observers should look for the
+ @param {Ember.View} view The view in which observers should look for the
element to update
@param {Srting} bindAttrId Optional bindAttr id used to lookup elements
@return {Array} An array of class names to add
@@ -19179,12 +19717,8 @@ EmberHandlebars.bindClasses = function(context, classBindings, view, bindAttrId,
}
};
- invoker = function() {
- Ember.run.scheduleOnce('render', observer);
- };
-
if (path !== '' && path !== 'this') {
- view.registerObserver(pathRoot, path, invoker);
+ view.registerObserver(pathRoot, path, observer);
}
// We've already setup the observer; now we just need to figure out the
@@ -19218,7 +19752,6 @@ EmberHandlebars.bindClasses = function(context, classBindings, view, bindAttrId,
*/
var get = Ember.get, set = Ember.set;
-var PARENT_VIEW_PATH = /^parentView\./;
var EmberHandlebars = Ember.Handlebars;
EmberHandlebars.ViewHelper = Ember.Object.create({
@@ -19234,6 +19767,11 @@ EmberHandlebars.ViewHelper = Ember.Object.create({
dup = true;
}
+ if (hash.tag) {
+ extensions.tagName = hash.tag;
+ dup = true;
+ }
+
if (classes) {
classes = classes.split(' ');
extensions.classNames = classes;
@@ -19260,6 +19798,7 @@ EmberHandlebars.ViewHelper = Ember.Object.create({
if (dup) {
hash = Ember.$.extend({}, hash);
delete hash.id;
+ delete hash.tag;
delete hash['class'];
delete hash.classBinding;
}
@@ -19792,7 +20331,7 @@ Ember.Handlebars.registerHelper('unbound', function(property, fn) {
// Unbound helper call.
options.data.isUnbound = true;
helper = Ember.Handlebars.helpers[arguments[0]] || Ember.Handlebars.helperMissing;
- out = helper.apply(this, Array.prototype.slice.call(arguments, 1));
+ out = helper.apply(this, Array.prototype.slice.call(arguments, 1));
delete options.data.isUnbound;
return out;
}
@@ -19895,7 +20434,7 @@ Ember.Handlebars.EachView = Ember.CollectionView.extend(Ember._Metamorph, {
Ember.removeBeforeObserver(this, 'content', null, '_contentWillChange');
Ember.removeObserver(this, 'content', null, '_contentDidChange');
- callback.apply(this);
+ callback.call(this);
Ember.addBeforeObserver(this, 'content', null, '_contentWillChange');
Ember.addObserver(this, 'content', null, '_contentDidChange');
@@ -20087,7 +20626,7 @@ GroupedEach.prototype = {
```handlebars
{{#view App.MyView }}
- {{each view.items itemViewClass="App.AnItemView"}}
+ {{each view.items itemViewClass="App.AnItemView"}}
{{/view}}
```
@@ -20118,7 +20657,7 @@ GroupedEach.prototype = {
<div class="ember-view">Greetings Sara</div>
</div>
```
-
+
### Representing each item with a Controller.
By default the controller lookup within an `{{#each}}` block will be
the controller of the template where the `{{#each}}` was used. If each
@@ -20126,10 +20665,10 @@ GroupedEach.prototype = {
`itemController` option which references a controller by lookup name.
Each item in the loop will be wrapped in an instance of this controller
and the item itself will be set to the `content` property of that controller.
-
+
This is useful in cases where properties of model objects need transformation
or synthesis for display:
-
+
```javascript
App.DeveloperController = Ember.ObjectController.extend({
isAvailableForHire: function(){
@@ -20137,17 +20676,17 @@ GroupedEach.prototype = {
}.property('isEmployed', 'isSeekingWork')
})
```
-
+
```handlebars
- {{#each person in Developers itemController="developer"}}
+ {{#each person in developers itemController="developer"}}
{{person.name}} {{#if person.isAvailableForHire}}Hire me!{{/if}}
{{/each}}
```
-
+
@method each
@for Ember.Handlebars.helpers
@param [name] {String} name for item (used with `in`)
- @param path {String} path
+ @param [path] {String} path
@param [options] {Object} Handlebars key/value pairs of options
@param [options.itemViewClass] {String} a path to a view class used for each item
@param [options.itemController] {String} name of a controller to be created for each item
@@ -20206,6 +20745,14 @@ Ember.Handlebars.registerHelper('each', function(path, options) {
</script>
```
+ ```handlebars
+ {{#if isUser}}
+ {{template "user_info"}}
+ {{else}}
+ {{template "unlogged_user_info"}}
+ {{/if}}
+ ```
+
This helper looks for templates in the global `Ember.TEMPLATES` hash. If you
add `<script>` tags to your page with the `data-template-name` attribute set,
they will be compiled and placed in this hash automatically.
@@ -20222,23 +20769,59 @@ Ember.Handlebars.registerHelper('each', function(path, options) {
*/
Ember.Handlebars.registerHelper('template', function(name, options) {
- var template = Ember.TEMPLATES[name];
+ var view = options.data.view,
+ template = view.templateForName(name);
Ember.assert("Unable to find template with name '"+name+"'.", !!template);
- Ember.TEMPLATES[name](this, { data: options.data });
+ template(this, { data: options.data });
});
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-handlebars
+*/
+
+/**
+ `partial` renders a template directly using the current context.
+ If needed the context can be set using the `{{#with foo}}` helper.
+
+ ```html
+ <script type="text/x-handlebars" data-template-name="header_bar">
+ {{#with currentUser}}
+ {{partial user_info}}
+ {{/with}}
+ </script>
+
+ The `data-template-name` attribute of a partial template
+ is prefixed with an underscore.
+
+ ```html
+ <script type="text/x-handlebars" data-template-name="_user_info">
+ <span>Hello {{username}}!</span>
+ </script>
+ ```
+
+ @method partial
+ @for Ember.Handlebars.helpers
+ @param {String} partialName the name of the template to render minus the leading underscore
+*/
+
Ember.Handlebars.registerHelper('partial', function(name, options) {
var nameParts = name.split("/"),
lastPart = nameParts[nameParts.length - 1];
nameParts[nameParts.length - 1] = "_" + lastPart;
- var underscoredName = nameParts.join("/");
-
- var template = Ember.TEMPLATES[underscoredName],
- deprecatedTemplate = Ember.TEMPLATES[name];
+ var view = options.data.view,
+ underscoredName = nameParts.join("/"),
+ template = view.templateForName(underscoredName),
+ deprecatedTemplate = view.templateForName(name);
Ember.deprecate("You tried to render the partial " + name + ", which should be at '" + underscoredName + "', but Ember found '" + name + "'. Please use a leading underscore in your partials", template);
Ember.assert("Unable to find partial with name '"+name+"'.", template || deprecatedTemplate);
@@ -20392,7 +20975,7 @@ Ember.Checkbox = Ember.View.extend({
tagName: 'input',
- attributeBindings: ['type', 'checked', 'disabled', 'tabindex'],
+ attributeBindings: ['type', 'checked', 'disabled', 'tabindex', 'name'],
type: "checkbox",
checked: false,
@@ -20501,8 +21084,8 @@ var get = Ember.get, set = Ember.set;
## HTML Attributes
By default `Ember.TextField` provides support for `type`, `value`, `size`,
- `placeholder`, `disabled`, `maxlength` and `tabindex` attributes on a
- test field. If you need to support more attributes have a look at the
+ `pattern`, `placeholder`, `disabled`, `maxlength` and `tabindex` attributes
+ on a test field. If you need to support more attributes have a look at the
`attributeBindings` property in `Ember.View`'s HTML Attributes section.
To globally add support for additional attributes you can reopen
@@ -20524,7 +21107,7 @@ Ember.TextField = Ember.View.extend(Ember.TextSupport,
classNames: ['ember-text-field'],
tagName: "input",
- attributeBindings: ['type', 'value', 'size', 'pattern'],
+ attributeBindings: ['type', 'value', 'size', 'pattern', 'name'],
/**
The `value` attribute of the input element. As the user inputs text, this
@@ -20570,9 +21153,9 @@ Ember.TextField = Ember.View.extend(Ember.TextSupport,
the user presses the return key when editing a text field, and sends
the value of the field as the context.
- @property action
- @type String
- @default null
+ @property action
+ @type String
+ @default null
*/
action: null,
@@ -20784,7 +21367,7 @@ Ember.TextArea = Ember.View.extend(Ember.TextSupport, {
classNames: ['ember-text-area'],
tagName: "textarea",
- attributeBindings: ['rows', 'cols'],
+ attributeBindings: ['rows', 'cols', 'name'],
rows: null,
cols: null,
@@ -21109,7 +21692,7 @@ function program3(depth0,data) {
return buffer;
}),
- attributeBindings: ['multiple', 'disabled', 'tabindex'],
+ attributeBindings: ['multiple', 'disabled', 'tabindex', 'name'],
/**
The `multiple` attribute of the select element. Indicates whether multiple
@@ -21413,8 +21996,7 @@ Ember.Handlebars.bootstrap = function(ctx) {
Ember.$(selectors, ctx)
.each(function() {
// Get a reference to the script tag
- var script = Ember.$(this),
- type = script.attr('type');
+ var script = Ember.$(this);
var compile = (script.attr('type') === 'text/x-raw-handlebars') ?
Ember.$.proxy(Handlebars.compile, Handlebars) :
@@ -22642,7 +23224,8 @@ DSL.prototype = {
},
push: function(url, name, callback) {
- if (url === "" || url === "/") { this.explicitIndex = true; }
+ var parts = name.split('.');
+ if (url === "" || url === "/" || parts[parts.length-1] === "index") { this.explicitIndex = true; }
this.matches.push([url, name, callback]);
},
@@ -22697,32 +23280,42 @@ Ember.RouterDSL = DSL;
@submodule ember-routing
*/
-Ember.controllerFor = function(container, controllerName, context) {
- return container.lookup('controller:' + controllerName) ||
+Ember.controllerFor = function(container, controllerName, context, lookupOptions) {
+ return container.lookup('controller:' + controllerName, lookupOptions) ||
Ember.generateController(container, controllerName, context);
};
-
+/**
+ Generates a controller automatically if none was provided.
+ The type of generated controller depends on the context.
+ You can customize your generated controllers by defining
+ `App.ObjectController` and `App.ArrayController`
+*/
Ember.generateController = function(container, controllerName, context) {
- var controller;
+ var controller, DefaultController, fullName;
if (context && Ember.isArray(context)) {
- controller = Ember.ArrayController.extend({
+ DefaultController = container.resolve('controller:array');
+ controller = DefaultController.extend({
content: context
});
} else if (context) {
- controller = Ember.ObjectController.extend({
+ DefaultController = container.resolve('controller:object');
+ controller = DefaultController.extend({
content: context
});
} else {
- controller = Ember.Controller.extend();
+ DefaultController = container.resolve('controller:basic');
+ controller = DefaultController.extend();
}
controller.toString = function() {
return "(generated " + controllerName + " controller)";
};
- container.register('controller', controllerName, controller);
- return container.lookup('controller:' + controllerName);
+
+ fullName = 'controller:' + controllerName;
+ container.register(fullName, controller);
+ return container.lookup(fullName);
};
})();
@@ -22736,7 +23329,7 @@ Ember.generateController = function(container, controllerName, context) {
*/
var Router = requireModule("router");
-var get = Ember.get, set = Ember.set, classify = Ember.String.classify;
+var get = Ember.get, set = Ember.set;
var DefaultView = Ember._MetamorphView;
function setupLocation(router) {
@@ -22785,8 +23378,8 @@ Ember.Router = Ember.Object.extend({
setupRouter(this, router, location);
- container.register('view', 'default', DefaultView);
- container.register('view', 'toplevel', Ember.View.extend());
+ container.register('view:default', DefaultView);
+ container.register('view:toplevel', Ember.View.extend());
location.onUpdateURL(function(url) {
self.handleURL(url);
@@ -22878,10 +23471,13 @@ Ember.Router.reopenClass({
});
function getHandlerFunction(router) {
- var seen = {}, container = router.container;
+ var seen = {}, container = router.container,
+ DefaultRoute = container.resolve('route:basic');
return function(name) {
- var handler = container.lookup('route:' + name);
+ var routeName = 'route:' + name,
+ handler = container.lookup(routeName);
+
if (seen[name]) { return handler; }
seen[name] = true;
@@ -22890,8 +23486,8 @@ function getHandlerFunction(router) {
if (name === 'loading') { return {}; }
if (name === 'failure') { return router.constructor.defaultFailureHandler; }
- container.register('route', name, Ember.Route.extend());
- handler = container.lookup('route:' + name);
+ container.register(routeName, DefaultRoute.extend());
+ handler = container.lookup(routeName);
}
handler.routeName = name;
@@ -22899,19 +23495,6 @@ function getHandlerFunction(router) {
};
}
-function handlerIsActive(router, handlerName) {
- var handler = router.container.lookup('route:' + handlerName),
- currentHandlerInfos = router.router.currentHandlerInfos,
- handlerInfo;
-
- for (var i=0, l=currentHandlerInfos.length; i<l; i++) {
- handlerInfo = currentHandlerInfos[i];
- if (handlerInfo.handler === handler) { return true; }
- }
-
- return false;
-}
-
function routePath(handlerInfos) {
var path = [];
@@ -22996,8 +23579,7 @@ Ember.Router.reopenClass({
*/
var get = Ember.get, set = Ember.set,
- classify = Ember.String.classify,
- decamelize = Ember.String.decamelize;
+ classify = Ember.String.classify;
/**
The `Ember.Route` class is used to define individual routes. Refer to
@@ -23170,8 +23752,8 @@ Ember.Route = Ember.Object.extend({
this route.
```js
- App.Route.map(function(match) {
- match("/posts/:post_id").to("post");
+ App.Router.map(function() {
+ this.resource('post', {path: '/posts/:post_id'});
});
```
@@ -23184,6 +23766,13 @@ Ember.Route = Ember.Object.extend({
* The find method is called on the model class with the value of
the dynamic segment.
+ Note that for routes with dynamic segments, this hook is only
+ executed when entered via the URL. If the route is entered
+ through a transition (e.g. when using the `linkTo` Handlebars
+ helper), then a model context is already provided and this hook
+ is not called. Routes without dynamic segments will always
+ execute the model hook.
+
@method model
@param {Object} params the parameters extracted from the URL
*/
@@ -23205,7 +23794,7 @@ Ember.Route = Ember.Object.extend({
namespace = this.router.namespace,
modelClass = namespace[className];
- Ember.assert("You used the dynamic segment " + name + "_id in your router, but " + namespace + "." + className + " did not exist and you did not override your state's `model` hook.", modelClass);
+ Ember.assert("You used the dynamic segment " + name + "_id in your router, but " + namespace + "." + className + " did not exist and you did not override your route's `model` hook.", modelClass);
return modelClass.find(value);
},
@@ -23214,8 +23803,8 @@ Ember.Route = Ember.Object.extend({
for the URL.
```js
- App.Route.map(function(match) {
- match("/posts/:post_id").to("post");
+ App.Router.map(function() {
+ this.resource('post', {path: '/posts/:post_id'});
});
App.PostRoute = Ember.Route.extend({
@@ -23264,8 +23853,8 @@ Ember.Route = Ember.Object.extend({
model supplied by the `model` hook.
```js
- App.Route.map(function(match) {
- match("/posts/:post_id").to("post");
+ App.Router.map(function() {
+ this.resource('post', {path: '/posts/:post_id'});
});
```
@@ -23365,9 +23954,9 @@ Ember.Route = Ember.Object.extend({
For example:
```js
- App.Router.map(function(match) {
- match("/").to("index");
- match("/posts/:post_id").to("post");
+ App.Router.map(function() {
+ this.route('index');
+ this.resource('post', {path: '/posts/:post_id'});
});
App.PostRoute = App.Route.extend({
@@ -23408,6 +23997,8 @@ Ember.Route = Ember.Object.extend({
@param {Object} options the options
*/
render: function(name, options) {
+ Ember.assert("The name in the given arguments is undefined", arguments.length > 0 ? !Ember.isNone(arguments[0]) : true);
+
if (typeof name === 'object' && !options) {
options = name;
name = this.routeName;
@@ -23544,6 +24135,34 @@ function teardownView(route) {
(function() {
+Ember.onLoad('Ember.Handlebars', function() {
+ var handlebarsResolve = Ember.Handlebars.resolveParams,
+ map = Ember.ArrayPolyfills.map,
+ get = Ember.get;
+
+ function resolveParams(context, params, options) {
+ var resolved = handlebarsResolve(context, params, options);
+ return map.call(resolved, unwrap);
+
+ function unwrap(object, i) {
+ if (params[i] === 'controller') { return object; }
+
+ if (Ember.ControllerMixin.detect(object)) {
+ return unwrap(get(object, 'model'));
+ } else {
+ return object;
+ }
+ }
+ }
+
+ Ember.Router.resolveParams = resolveParams;
+});
+
+})();
+
+
+
+(function() {
/**
@module ember
@submodule ember-routing
@@ -23552,7 +24171,7 @@ function teardownView(route) {
var get = Ember.get, set = Ember.set;
Ember.onLoad('Ember.Handlebars', function(Handlebars) {
- var resolveParams = Ember.Handlebars.resolveParams,
+ var resolveParams = Ember.Router.resolveParams,
isSimpleClick = Ember.ViewUtils.isSimpleClick;
function fullRouteName(router, name) {
@@ -23581,7 +24200,25 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) {
return ret.concat(resolvedPaths(linkView.parameters));
}
- var LinkView = Ember.View.extend({
+ /**
+ Renders a link to the supplied route.
+
+ When the rendered link matches the current route, and the same object instance is passed into the helper,
+ then the link is given class="active" by default.
+
+ You may re-open LinkView in order to change the default active class:
+
+ ``` javascript
+ Ember.LinkView.reopen({
+ activeClass: "is-active"
+ })
+ ```
+
+ @class LinkView
+ @namespace Ember
+ @extends Ember.View
+ **/
+ var LinkView = Ember.LinkView = Ember.View.extend({
tagName: 'a',
namedRoute: null,
currentWhen: null,
@@ -23676,42 +24313,52 @@ var get = Ember.get, set = Ember.set;
Ember.onLoad('Ember.Handlebars', function(Handlebars) {
/**
@module ember
- @submodule ember-handlebars
+ @submodule ember-routing
*/
Handlebars.OutletView = Ember.ContainerView.extend(Ember._Metamorph);
/**
- The `outlet` helper allows you to specify that the current
- view's controller will fill in the view for a given area.
+ The `outlet` helper is a placeholder that the router will fill in with
+ the appropriate template based on the current state of the application.
``` handlebars
{{outlet}}
```
- By default, when the the current controller's `view` property changes, the
- outlet will replace its current view with the new view. You can set the
- `view` property directly, but it's normally best to use `connectOutlet`.
+ By default, a template based on Ember's naming conventions will be rendered
+ into the `outlet` (e.g. `App.PostsRoute` will render the `posts` template).
+
+ You can render a different template by using the `render()` method in the
+ route's `renderTemplate` hook. The following will render the `favoritePost`
+ template into the `outlet`.
``` javascript
- # Instantiate App.PostsView and assign to `view`, so as to render into outlet.
- controller.connectOutlet('posts');
+ App.PostsRoute = Ember.Route.extend({
+ renderTemplate: function() {
+ this.render('favoritePost');
+ }
+ });
```
- You can also specify a particular name other than `view`:
+ You can create custom named outlets for more control.
``` handlebars
- {{outlet masterView}}
- {{outlet detailView}}
+ {{outlet favoritePost}}
+ {{outlet posts}}
```
- Then, you can control several outlets from a single controller.
+ Then you can define what template is rendered into each outlet in your
+ route.
+
``` javascript
- # Instantiate App.PostsView and assign to controller.masterView.
- controller.connectOutlet('masterView', 'posts');
- # Also, instantiate App.PostInfoView and assign to controller.detailView.
- controller.connectOutlet('detailView', 'postInfo');
+ App.PostsRoute = Ember.Route.extend({
+ renderTemplate: function() {
+ this.render('favoritePost', { outlet: 'favoritePost' });
+ this.render('posts', { outlet: 'posts' });
+ }
+ });
```
@method outlet
@@ -23763,15 +24410,15 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) {
The default target for `{{action}}`s in the rendered template is the
named controller.
- @method action
+ @method render
@for Ember.Handlebars.helpers
- @param {String} actionName
- @param {Object?} model
+ @param {String} name
+ @param {Object?} contextString
@param {Hash} options
*/
Ember.Handlebars.registerHelper('render', function(name, contextString, options) {
Ember.assert("You must pass a template to render", arguments.length >= 2);
- var container, router, controller, view, context;
+ var container, router, controller, view, context, lookupOptions;
if (arguments.length === 2) {
options = contextString;
@@ -23780,20 +24427,21 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) {
if (typeof contextString === 'string') {
context = Ember.Handlebars.get(options.contexts[1], contextString, options);
+ lookupOptions = { singleton: false };
}
name = name.replace(/\//g, '.');
container = options.data.keywords.controller.container;
router = container.lookup('router:main');
- Ember.assert("This view is already rendered", !router || !router._lookupActiveView(name));
+ Ember.assert("You can only use the {{render}} helper once without a model object as its second argument, as in {{render \"post\" post}}.", context || !router || !router._lookupActiveView(name));
view = container.lookup('view:' + name) || container.lookup('view:default');
if (controller = options.hash.controller) {
- controller = container.lookup('controller:' + controller);
+ controller = container.lookup('controller:' + controller, lookupOptions);
} else {
- controller = Ember.controllerFor(container, name, context);
+ controller = Ember.controllerFor(container, name, context, lookupOptions);
}
if (controller && context) {
@@ -23814,7 +24462,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) {
options.hash.template = container.lookup('template:' + name);
options.hash.controller = controller;
- if (router) {
+ if (router && !context) {
router._connectActiveView(name, view);
}
@@ -23834,7 +24482,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) {
*/
Ember.onLoad('Ember.Handlebars', function(Handlebars) {
- var resolveParams = Ember.Handlebars.resolveParams,
+ var resolveParams = Ember.Router.resolveParams,
isSimpleClick = Ember.ViewUtils.isSimpleClick;
var EmberHandlebars = Ember.Handlebars,
@@ -23857,22 +24505,39 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) {
registeredActions: {}
};
- ActionHelper.registerAction = function(actionName, options) {
+ var keys = ["alt", "shift", "meta", "ctrl"];
+
+ var isAllowedClick = function(event, allowedKeys) {
+ if (typeof allowedKeys === "undefined") {
+ return isSimpleClick(event);
+ }
+
+ var allowed = true;
+
+ keys.forEach(function(key) {
+ if (event[key + "Key"] && allowedKeys.indexOf(key) === -1) {
+ allowed = false;
+ }
+ });
+
+ return allowed;
+ };
+
+ ActionHelper.registerAction = function(actionName, options, allowedKeys) {
var actionId = (++Ember.uuid).toString();
ActionHelper.registeredActions[actionId] = {
eventName: options.eventName,
handler: function(event) {
- if (!isSimpleClick(event)) { return true; }
+ if (!isAllowedClick(event, allowedKeys)) { return true; }
+
event.preventDefault();
if (options.bubbles === false) {
event.stopPropagation();
}
- var view = options.view,
- contexts = options.contexts,
- target = options.target;
+ var target = options.target;
if (target.target) {
target = handlebarsGet(target.root, target.target, target.options);
@@ -23995,6 +24660,21 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) {
is created. Having an instance of `Ember.Application` will satisfy this
requirement.
+ ### Specifying whitelisted modifier keys
+
+ By default the `{{action}}` helper will ignore click event with pressed modifier
+ keys. You can supply an `allowedKeys` option to specify which keys should not be ignored.
+
+ ```handlebars
+ <script type="text/x-handlebars" data-template-name='a-template'>
+ <div {{action anActionName allowedKeys="alt"}}>
+ click me
+ </div>
+ </script>
+ ```
+
+ This way the `{{action}}` will fire when clicking with the alt key pressed down.
+
### Specifying a Target
There are several possible target objects for `{{action}}` helpers:
@@ -24075,8 +24755,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) {
contexts = a_slice.call(arguments, 1, -1);
var hash = options.hash,
- view = options.data.view,
- controller, link;
+ controller;
// create a hash to pass along to registerAction
var action = {
@@ -24089,7 +24768,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) {
params: contexts
};
- action.view = view = get(view, 'concreteView');
+ action.view = options.data.view;
var root, target;
@@ -24103,7 +24782,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) {
action.target = { root: root, target: target, options: options };
action.bubbles = hash.bubbles;
- var actionId = ActionHelper.registerAction(actionName, action);
+ var actionId = ActionHelper.registerAction(actionName, action, hash.allowedKeys);
return new SafeString('data-ember-action="' + actionId + '"');
});
@@ -24460,7 +25139,7 @@ Ember.HashLocation = Ember.Object.extend({
set(self, 'lastSetURL', null);
- callback(location.hash.substr(1));
+ callback(path);
});
});
},
@@ -24501,7 +25180,7 @@ Ember.Location.registerImplementation('hash', Ember.HashLocation);
*/
var get = Ember.get, set = Ember.set;
-var popstateReady = false;
+var popstateFired = false;
/**
Ember.HistoryLocation implements the location API using the browser's
@@ -24515,6 +25194,7 @@ Ember.HistoryLocation = Ember.Object.extend({
init: function() {
set(this, 'location', get(this, 'location') || window.location);
+ this._initialUrl = this.getURL();
this.initState();
},
@@ -24567,7 +25247,6 @@ Ember.HistoryLocation = Ember.Object.extend({
path = this.formatURL(path);
if (this.getState() && this.getState().path !== path) {
- popstateReady = true;
this.pushState(path);
}
},
@@ -24585,7 +25264,6 @@ Ember.HistoryLocation = Ember.Object.extend({
path = this.formatURL(path);
if (this.getState() && this.getState().path !== path) {
- popstateReady = true;
this.replaceState(path);
}
},
@@ -24639,8 +25317,10 @@ Ember.HistoryLocation = Ember.Object.extend({
self = this;
Ember.$(window).bind('popstate.ember-location-'+guid, function(e) {
- if(!popstateReady) {
- return;
+ // Ignore initial page load popstate event in Chrome
+ if(!popstateFired) {
+ popstateFired = true;
+ if (self.getURL() === self._initialUrl) { return; }
}
callback(self.getURL());
});
@@ -24810,8 +25490,221 @@ Ember.DAG = DAG;
@submodule ember-application
*/
+var get = Ember.get,
+ classify = Ember.String.classify,
+ capitalize = Ember.String.capitalize,
+ decamelize = Ember.String.decamelize;
+
+/**
+ The DefaultResolver defines the default lookup rules to resolve
+ container lookups before consulting the container for registered
+ items:
+
+ * templates are looked up on `Ember.TEMPLATES`
+ * other names are looked up on the application after converting
+ the name. For example, `controller:post` looks up
+ `App.PostController` by default.
+ * there are some nuances (see examples below)
+
+ ### How Resolving Works
+
+ The container calls this object's `resolve` method with the
+ `fullName` argument.
+
+ It first parses the fullName into an object using `parseName`.
+
+ Then it checks for the presence of a type-specific instance
+ method of the form `resolve[Type]` and calls it if it exists.
+ For example if it was resolving 'template:post', it would call
+ the `resolveTemplate` method.
+
+ Its last resort is to call the `resolveOther` method.
+
+ The methods of this object are designed to be easy to override
+ in a subclass. For example, you could enhance how a template
+ is resolved like so:
+
+ ```javascript
+ App = Ember.Application.create({
+ resolver: Ember.DefaultResolver.extend({
+ resolveTemplate: function(parsedName) {
+ var resolvedTemplate = this._super(parsedName);
+ if (resolvedTemplate) { return resolvedTemplate; }
+ return Ember.TEMPLATES['not_found'];
+ }
+ })
+ });
+ ```
+
+ Some examples of how names are resolved:
+
+ ```
+ 'template:post' //=> Ember.TEMPLATES['post']
+ 'template:posts/byline' //=> Ember.TEMPLATES['posts/byline']
+ 'template:posts.byline' //=> Ember.TEMPLATES['posts/byline']
+ 'template:blogPost' //=> Ember.TEMPLATES['blogPost']
+ // OR
+ // Ember.TEMPLATES['blog_post']
+ 'controller:post' //=> App.PostController
+ 'controller:posts.index' //=> App.PostsIndexController
+ 'controller:blog/post' //=> Blog.PostController
+ 'controller:basic' //=> Ember.Controller
+ 'route:post' //=> App.PostRoute
+ 'route:posts.index' //=> App.PostsIndexRoute
+ 'route:blog/post' //=> Blog.PostRoute
+ 'route:basic' //=> Ember.Route
+ 'view:post' //=> App.PostView
+ 'view:posts.index' //=> App.PostsIndexView
+ 'view:blog/post' //=> Blog.PostView
+ 'view:basic' //=> Ember.View
+ 'foo:post' //=> App.PostFoo
+ ```
+
+ @class DefaultResolver
+ @namespace Ember
+ @extends Ember.Object
+*/
+Ember.DefaultResolver = Ember.Object.extend({
+ /**
+ This will be set to the Application instance when it is
+ created.
+
+ @property namespace
+ */
+ namespace: null,
+ /**
+ This method is called via the container's resolver method.
+ It parses the provided `fullName` and then looks up and
+ returns the appropriate template or class.
+
+ @method resolve
+ @param {String} fullName the lookup string
+ @return {Object} the resolved factory
+ */
+ resolve: function(fullName) {
+ var parsedName = this.parseName(fullName),
+ typeSpecificResolveMethod = this[parsedName.resolveMethodName];
+ if (typeSpecificResolveMethod) {
+ var resolved = typeSpecificResolveMethod.call(this, parsedName);
+ if (resolved) { return resolved; }
+ }
+ return this.resolveOther(parsedName);
+ },
+ /**
+ Convert the string name of the form "type:name" to
+ a Javascript object with the parsed aspects of the name
+ broken out.
+
+ @protected
+ @method parseName
+ */
+ parseName: function(fullName) {
+ var nameParts = fullName.split(":"),
+ type = nameParts[0], fullNameWithoutType = nameParts[1],
+ name = fullNameWithoutType,
+ namespace = get(this, 'namespace'),
+ root = namespace;
+
+ if (type !== 'template' && name.indexOf('/') !== -1) {
+ var parts = name.split('/');
+ name = parts[parts.length - 1];
+ var namespaceName = capitalize(parts.slice(0, -1).join('.'));
+ root = Ember.Namespace.byName(namespaceName);
+
+ Ember.assert('You are looking for a ' + name + ' ' + type + ' in the ' + namespaceName + ' namespace, but the namespace could not be found', root);
+ }
+
+ return {
+ fullName: fullName,
+ type: type,
+ fullNameWithoutType: fullNameWithoutType,
+ name: name,
+ root: root,
+ resolveMethodName: "resolve" + classify(type)
+ };
+ },
+ /**
+ Look up the template in Ember.TEMPLATES
+
+ @protected
+ @method resolveTemplate
+ */
+ resolveTemplate: function(parsedName) {
+ var templateName = parsedName.fullNameWithoutType.replace(/\./g, '/');
+
+ if (Ember.TEMPLATES[templateName]) {
+ return Ember.TEMPLATES[templateName];
+ }
+
+ templateName = decamelize(templateName);
+ if (Ember.TEMPLATES[templateName]) {
+ return Ember.TEMPLATES[templateName];
+ }
+ },
+ /**
+ Given a parseName object (output from `parseName`), apply
+ the conventions expected by `Ember.Router`
+
+ @protected
+ @method useRouterNaming
+ */
+ useRouterNaming: function(parsedName) {
+ parsedName.name = parsedName.name.replace(/\./g, '_');
+ if (parsedName.name === 'basic') {
+ parsedName.name = '';
+ }
+ },
+ /**
+ @protected
+ @method resolveController
+ */
+ resolveController: function(parsedName) {
+ this.useRouterNaming(parsedName);
+ return this.resolveOther(parsedName);
+ },
+ /**
+ @protected
+ @method resolveRoute
+ */
+ resolveRoute: function(parsedName) {
+ this.useRouterNaming(parsedName);
+ return this.resolveOther(parsedName);
+ },
+ /**
+ @protected
+ @method resolveView
+ */
+ resolveView: function(parsedName) {
+ this.useRouterNaming(parsedName);
+ return this.resolveOther(parsedName);
+ },
+ /**
+ Look up the specified object (from parsedName) on the appropriate
+ namespace (usually on the Application)
+
+ @protected
+ @method resolveOther
+ */
+ resolveOther: function(parsedName) {
+ var className = classify(parsedName.name) + classify(parsedName.type),
+ factory = get(parsedName.root, className);
+ if (factory) { return factory; }
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-application
+*/
+
var get = Ember.get, set = Ember.set,
classify = Ember.String.classify,
+ capitalize = Ember.String.capitalize,
decamelize = Ember.String.decamelize;
/**
@@ -24837,11 +25730,14 @@ var get = Ember.get, set = Ember.set,
```
By default, calling `Ember.Application.create()` will automatically initialize
- your application by calling the `Ember.Application.initialize()` method. If
+ your application by calling the `Ember.Application.initialize()` method. If
you need to delay initialization, you can call your app's `deferReadiness()`
method. When you are ready for your app to be initialized, call its
`advanceReadiness()` method.
+ You can define a `ready` method on the `Ember.Application` instance, which
+ will be run by Ember when the application is initialized.
+
Because `Ember.Application` inherits from `Ember.Namespace`, any classes
you create will have useful string representations when calling `toString()`.
See the `Ember.Namespace` documentation for more information.
@@ -24902,7 +25798,7 @@ var get = Ember.get, set = Ember.set,
To learn more about the advantages of event delegation and the Ember view
layer, and a list of the event listeners that are setup by default, visit the
- [Ember View Layer guide](http://emberjs.com/guides/view_layer#toc_event-delegation).
+ [Ember View Layer guide](http://emberjs.com/guides/understanding-ember/the-view-layer/#toc_event-delegation).
### Initializers
@@ -24913,7 +25809,7 @@ var get = Ember.get, set = Ember.set,
name: "store",
initialize: function(container, application) {
- container.register('store', 'main', application.Store);
+ container.register('store:main', application.Store);
}
});
```
@@ -24955,7 +25851,8 @@ var get = Ember.get, set = Ember.set,
@namespace Ember
@extends Ember.Namespace
*/
-var Application = Ember.Application = Ember.Namespace.extend({
+
+var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin, {
/**
The root DOM element of the Application. This can be specified as an
@@ -25017,8 +25914,6 @@ var Application = Ember.Application = Ember.Namespace.extend({
*/
customEvents: null,
- isInitialized: false,
-
// Start off the number of deferrals at 1. This will be
// decremented by the Application's own `initialize` method.
_readinessDeferrals: 1,
@@ -25032,14 +25927,16 @@ var Application = Ember.Application = Ember.Namespace.extend({
this._super();
- this.deferUntilDOMReady();
this.scheduleInitialize();
- Ember.debug('-------------------------------');
- Ember.debug('Ember.VERSION : ' + Ember.VERSION);
- Ember.debug('Handlebars.VERSION : ' + Ember.Handlebars.VERSION);
- Ember.debug('jQuery.VERSION : ' + Ember.$().jquery);
- Ember.debug('-------------------------------');
+ if ( Ember.LOG_VERSION ) {
+ Ember.LOG_VERSION = false; // we only need to see this once per Application#init
+ Ember.debug('-------------------------------');
+ Ember.debug('Ember.VERSION : ' + Ember.VERSION);
+ Ember.debug('Handlebars.VERSION : ' + Ember.Handlebars.VERSION);
+ Ember.debug('jQuery.VERSION : ' + Ember.$().jquery);
+ Ember.debug('-------------------------------');
+ }
},
/**
@@ -25091,28 +25988,10 @@ var Application = Ember.Application = Ember.Namespace.extend({
/**
@private
- Defer Ember readiness until DOM readiness. By default, Ember
- will wait for both DOM readiness and application initialization,
- as well as any deferrals registered by initializers.
-
- @method deferUntilDOMReady
- */
- deferUntilDOMReady: function() {
- this.deferReadiness();
-
- var self = this;
- this.$().ready(function() {
- self.advanceReadiness();
- });
- },
-
- /**
- @private
-
Automatically initialize the application once the DOM has
become ready.
- The initialization itself is deferred using Ember.run.once,
+ The initialization itself is scheduled on the actions queue
which ensures that application loading finishes before
booting.
@@ -25125,10 +26004,14 @@ var Application = Ember.Application = Ember.Namespace.extend({
*/
scheduleInitialize: function() {
var self = this;
- this.$().ready(function() {
- if (self.isDestroyed || self.isInitialized) return;
- Ember.run.once(self, 'initialize');
- });
+
+ if (!this.$ || this.$.isReady) {
+ Ember.run.schedule('actions', self, '_initialize');
+ } else {
+ this.$().ready(function(){
+ Ember.run(self, '_initialize');
+ });
+ }
},
/**
@@ -25221,6 +26104,20 @@ var Application = Ember.Application = Ember.Namespace.extend({
/**
@private
+ @deprecated
+
+ Calling initialize manually is not supported.
+
+ Please see Ember.Application#advanceReadiness and
+ Ember.Application#deferReadiness.
+
+ @method initialize
+ **/
+ initialize: function(){
+ Ember.deprecate('Calling initialize manually is not supported. Please see Ember.Application#advanceReadiness and Ember.Application#deferReadiness');
+ },
+ /**
+ @private
Initialize the application. This happens automatically.
@@ -25228,15 +26125,13 @@ var Application = Ember.Application = Ember.Namespace.extend({
choose to defer readiness. For example, an authentication hook might want
to defer readiness until the auth token has been retrieved.
- @method initialize
+ @method _initialize
*/
- initialize: function() {
- Ember.assert("Application initialize may only be called once", !this.isInitialized);
- Ember.assert("Cannot initialize a destroyed application", !this.isDestroyed);
- this.isInitialized = true;
+ _initialize: function() {
+ if (this.isDestroyed) { return; }
// At this point, the App.Router must already be assigned
- this.__container__.register('router', 'main', this.Router);
+ this.register('router:main', this.Router);
this.runInitializers();
Ember.runLoadHooks('application', this);
@@ -25253,9 +26148,10 @@ var Application = Ember.Application = Ember.Namespace.extend({
get(this, '__container__').destroy();
this.buildContainer();
- this.isInitialized = false;
- this.initialize();
- this.startRouting();
+ Ember.run.schedule('actions', this, function(){
+ this._initialize();
+ this.startRouting();
+ });
},
/**
@@ -25267,7 +26163,7 @@ var Application = Ember.Application = Ember.Namespace.extend({
container = this.__container__,
graph = new Ember.DAG(),
namespace = this,
- properties, i, initializer;
+ i, initializer;
for (i=0; i<initializers.length; i++) {
initializer = initializers[i];
@@ -25276,6 +26172,7 @@ var Application = Ember.Application = Ember.Namespace.extend({
graph.topsort(function (vertex) {
var initializer = vertex.value;
+ Ember.assert("No application initializer named '"+vertex.name+"'", initializer);
initializer(container, namespace);
});
},
@@ -25294,6 +26191,8 @@ var Application = Ember.Application = Ember.Namespace.extend({
Ember.Namespace.processAll();
Ember.BOOTED = true;
}
+
+ this.resolve(this);
},
/**
@@ -25359,6 +26258,13 @@ var Application = Ember.Application = Ember.Namespace.extend({
*/
ready: Ember.K,
+ /**
+ Set this to provide an alternate class to `Ember.DefaultResolver`
+
+ @property resolver
+ */
+ resolver: null,
+
willDestroy: function() {
Ember.BOOTED = false;
@@ -25417,10 +26323,17 @@ Ember.Application.reopenClass({
Ember.Container.defaultContainer = Ember.Container.defaultContainer || container;
container.set = Ember.set;
+ container.normalize = normalize;
container.resolver = resolverFor(namespace);
container.optionsForType('view', { singleton: false });
container.optionsForType('template', { instantiate: false });
- container.register('application', 'main', namespace, { instantiate: false });
+ container.register('application:main', namespace, { instantiate: false });
+
+ container.register('controller:basic', Ember.Controller, { instantiate: false });
+ container.register('controller:object', Ember.ObjectController, { instantiate: false });
+ container.register('controller:array', Ember.ArrayController, { instantiate: false });
+ container.register('route:basic', Ember.Route, { instantiate: false });
+
container.injection('router:main', 'namespace', 'application:main');
container.typeInjection('controller', 'target', 'router:main');
@@ -25449,36 +26362,40 @@ Ember.Application.reopenClass({
@return {any} the resolved value for a given lookup
*/
function resolverFor(namespace) {
+ var resolverClass = namespace.get('resolver') || Ember.DefaultResolver;
+ var resolver = resolverClass.create({
+ namespace: namespace
+ });
return function(fullName) {
- var nameParts = fullName.split(":"),
- type = nameParts[0], name = nameParts[1];
+ return resolver.resolve(fullName);
+ };
+}
- if (type === 'template') {
- var templateName = name.replace(/\./g, '/');
- if (Ember.TEMPLATES[templateName]) {
- return Ember.TEMPLATES[templateName];
- }
+function normalize(fullName) {
+ var split = fullName.split(':'),
+ type = split[0],
+ name = split[1];
- templateName = decamelize(templateName);
- if (Ember.TEMPLATES[templateName]) {
- return Ember.TEMPLATES[templateName];
- }
- }
- if (type === 'controller' || type === 'route' || type === 'view') {
- name = name.replace(/\./g, '_');
+ if (type !== 'template') {
+ var result = name;
+
+ if (result.indexOf('.') > -1) {
+ result = result.replace(/\.(.)/g, function(m) { return m.charAt(1).toUpperCase(); });
}
- var className = classify(name) + classify(type);
- var factory = get(namespace, className);
+ if (name.indexOf('_') > -1) {
+ result = result.replace(/_(.)/g, function(m) { return m.charAt(1).toUpperCase(); });
+ }
- if (factory) { return factory; }
- };
+ return type + ':' + result;
+ } else {
+ return fullName;
+ }
}
Ember.runLoadHooks('Ember.Application', Ember.Application);
-
})();
@@ -25492,7 +26409,7 @@ Ember.runLoadHooks('Ember.Application', Ember.Application);
(function() {
/**
@module ember
-@submodule ember-routing
+@submodule ember-application
*/
var get = Ember.get, set = Ember.set;
@@ -25548,7 +26465,7 @@ Ember.ControllerMixin.reopen({
},
controllerFor: function(controllerName) {
- Ember.deprecate("Controller#controllerFor is depcrecated, please use Controller#needs instead");
+ Ember.deprecate("Controller#controllerFor is deprecated, please use Controller#needs instead");
var container = get(this, 'container');
return container.lookup('controller:' + controllerName);
},
@@ -25648,7 +26565,7 @@ Ember.State = Ember.Object.extend(Ember.Evented,
},
init: function() {
- var states = get(this, 'states'), foundStates;
+ var states = get(this, 'states');
set(this, 'childStates', Ember.A());
set(this, 'eventTransitions', get(this, 'eventTransitions') || {});
@@ -25798,7 +26715,7 @@ Ember.State.reopenClass({
transitionTo: function(target) {
var transitionFunction = function(stateManager, contextOrEvent) {
- var contexts = [], transitionArgs,
+ var contexts = [],
Event = Ember.$ && Ember.$.Event;
if (contextOrEvent && (Event && contextOrEvent instanceof Event)) {
@@ -26824,8 +27741,8 @@ Ember States
})();
-// Version: v1.0.0-rc.1
-// Last commit: 8b061b4 (2013-02-15 12:10:22 -0800)
+// Version: v1.0.0-rc.2
+// Last commit: 656fa6e (2013-03-29 13:40:38 -0700)
(function() {