From 7fb090fe996d34b11b8cffba1f4c45edc84a50cd Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Tue, 30 Jul 2013 18:15:07 +0200 Subject: [PATCH] Update ember-model and ember.js --- assets/scripts/vendor/ember-model.js | 281 +++--- assets/scripts/vendor/ember.js | 1374 ++++++++++++++++---------- 2 files changed, 996 insertions(+), 659 deletions(-) diff --git a/assets/scripts/vendor/ember-model.js b/assets/scripts/vendor/ember-model.js index 4bdef9c5..9257b644 100644 --- a/assets/scripts/vendor/ember-model.js +++ b/assets/scripts/vendor/ember-model.js @@ -1,36 +1,24 @@ (function() { +function mustImplement(message) { + var fn = function() { + throw new Error(message); + }; + fn.isUnimplemented = true; + return fn; +} + Ember.Adapter = Ember.Object.extend({ - find: function(record, id) { - throw new Error('Ember.Adapter subclasses must implement find'); - }, - - findQuery: function(klass, records, params) { - throw new Error('Ember.Adapter subclasses must implement findQuery'); - }, - - findMany: function(klass, records, ids) { - throw new Error('Ember.Adapter subclasses must implement findMany'); - }, - - findAll: function(klass, records) { - throw new Error('Ember.Adapter subclasses must implement findAll'); - }, + find: mustImplement('Ember.Adapter subclasses must implement find'), + findQuery: mustImplement('Ember.Adapter subclasses must implement findQuery'), + findMany: mustImplement('Ember.Adapter subclasses must implement findMany'), + findAll: mustImplement('Ember.Adapter subclasses must implement findAll'), + createRecord: mustImplement('Ember.Adapter subclasses must implement createRecord'), + saveRecord: mustImplement('Ember.Adapter subclasses must implement saveRecord'), + deleteRecord: mustImplement('Ember.Adapter subclasses must implement deleteRecord'), load: function(record, id, data) { record.load(id, data); - }, - - createRecord: function(record) { - throw new Error('Ember.Adapter subclasses must implement createRecord'); - }, - - saveRecord: function(record) { - throw new Error('Ember.Adapter subclasses must implement saveRecord'); - }, - - deleteRecord: function(record) { - throw new Error('Ember.Adapter subclasses must implement deleteRecord'); } }); @@ -43,8 +31,9 @@ var get = Ember.get; Ember.FixtureAdapter = Ember.Adapter.extend({ _findData: function(klass, id) { var fixtures = klass.FIXTURES, + idAsString = id.toString(), primaryKey = get(klass, 'primaryKey'), - data = Ember.A(fixtures).find(function(el) { return el[primaryKey] === id; }); + data = Ember.A(fixtures).find(function(el) { return (el[primaryKey]).toString() === idAsString; }); return data; }, @@ -53,10 +42,10 @@ Ember.FixtureAdapter = Ember.Adapter.extend({ var data = this._findData(record.constructor, id); return new Ember.RSVP.Promise(function(resolve, reject) { - setTimeout(function() { + Ember.run.later(this, function() { Ember.run(record, record.load, id, data); resolve(record); - }); + }, 0); }); }, @@ -69,10 +58,10 @@ Ember.FixtureAdapter = Ember.Adapter.extend({ } return new Ember.RSVP.Promise(function(resolve, reject) { - setTimeout(function() { + Ember.run.later(this, function() { Ember.run(records, records.load, klass, requestedData); resolve(records); - }); + }, 0); }); }, @@ -80,10 +69,10 @@ Ember.FixtureAdapter = Ember.Adapter.extend({ var fixtures = klass.FIXTURES; return new Ember.RSVP.Promise(function(resolve, reject) { - setTimeout(function() { + Ember.run.later(this, function() { Ember.run(records, records.load, klass, fixtures); resolve(records); - }); + }, 0); }); }, @@ -92,29 +81,29 @@ Ember.FixtureAdapter = Ember.Adapter.extend({ fixtures = klass.FIXTURES; return new Ember.RSVP.Promise(function(resolve, reject) { - setTimeout(function() { + Ember.run.later(this, function() { fixtures.push(klass.findFromCacheOrLoad(record.toJSON())); record.didCreateRecord(); resolve(record); - }); + }, 0); }); }, saveRecord: function(record) { return new Ember.RSVP.Promise(function(resolve, reject) { - setTimeout(function() { + Ember.run.later(this, function() { record.didSaveRecord(); resolve(record); - }); + }, 0); }); }, deleteRecord: function(record) { return new Ember.RSVP.Promise(function(resolve, reject) { - setTimeout(function() { + Ember.run.later(this, function() { record.didDeleteRecord(); resolve(record); - }); + }, 0); }); } }); @@ -151,6 +140,15 @@ Ember.RecordArray = Ember.ArrayProxy.extend(Ember.Evented, { return Ember.A(data.map(function(el) { return klass.findFromCacheOrLoad(el); // FIXME })); + }, + + reload: function() { + var modelClass = this.get('modelClass'); + Ember.assert("Reload can only be called on findAll RecordArrays", + modelClass && modelClass._findAllRecordArray === this); + + set(this, 'isLoaded', false); + modelClass.adapter.findAll(modelClass, this); } }); @@ -229,15 +227,7 @@ Ember.ManyArray = Ember.RecordArray.extend({ if (!content.length) { return; } - // TODO: Create a LazilyMaterializedRecordArray class and test it - if (this._records && this._records[idx]) { return this._records[idx]; } - - var record = this.materializeRecord(idx); - - if (!this._records) { this._records = {}; } - this._records[idx] = record; - - return record; + return this.materializeRecord(idx); }, save: function() { @@ -306,7 +296,8 @@ Ember.EmbeddedHasManyArray = Ember.ManyArray.extend({ return reference.record; } else { var record = klass.create({ _reference: reference }); - if(attrs) { + reference.record = record; + if (attrs) { record.load(attrs[primaryKey], attrs); } return record; @@ -354,6 +345,36 @@ function hasCachedValue(object, key) { } } +function extractDirty(object, attrsOrRelations, dirtyAttributes) { + var key, desc, descMeta, type, dataValue, cachedValue, isDirty, dataType; + for (var i = 0, l = attrsOrRelations.length; i < l; i++) { + key = attrsOrRelations[i]; + if (!hasCachedValue(object, key)) { continue; } + cachedValue = object.cacheFor(key); + dataValue = get(object, '_data.' + object.dataKey(key)); + desc = meta(object).descs[key]; + descMeta = desc && desc.meta(); + type = descMeta.type; + dataType = Ember.Model.dataTypes[type]; + + if (type && type.isEqual) { + isDirty = !type.isEqual(dataValue, cachedValue); + } else if (dataType && dataType.isEqual) { + isDirty = !dataType.isEqual(dataValue, cachedValue); + } else if (dataValue && cachedValue instanceof Ember.Model) { // belongsTo case + isDirty = get(cachedValue, 'isDirty'); + } else if (dataValue !== cachedValue) { + isDirty = true; + } else { + isDirty = false; + } + + if (isDirty) { + dirtyAttributes.push(key); + } + } +} + Ember.run.queues.push('data'); Ember.Model = Ember.Object.extend(Ember.Evented, { @@ -376,29 +397,12 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { isDirty: Ember.computed(function() { var attributes = this.attributes, - dirtyAttributes = Ember.A(), // just for removeObject - key, cachedValue, dataValue, desc, descMeta, type, isDirty; + relationships = this.relationships, + dirtyAttributes = Ember.A(); // just for removeObject - for (var i = 0, l = attributes.length; i < l; i++) { - key = attributes[i]; - if (!hasCachedValue(this, key)) { continue; } - cachedValue = this.cacheFor(key); - dataValue = get(this, 'data.'+this.dataKey(key)); - desc = meta(this).descs[key]; - descMeta = desc && desc.meta(); - type = descMeta.type; - - if (type && type.isEqual) { - isDirty = !type.isEqual(dataValue, cachedValue); - } else if (dataValue !== cachedValue) { - isDirty = true; - } else { - isDirty = false; - } - - if (isDirty) { - dirtyAttributes.push(key); - } + extractDirty(this, attributes, dirtyAttributes); + if (relationships) { + extractDirty(this, relationships, dirtyAttributes); } if (dirtyAttributes.length) { @@ -412,6 +416,10 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { dataKey: function(key) { var camelizeKeys = get(this.constructor, 'camelizeKeys'); + var meta = this.constructor.metaForProperty(key); + if (meta.options && meta.options.key) { + return camelizeKeys ? underscore(meta.options.key) : meta.options.key; + } return camelizeKeys ? underscore(key) : key; }, @@ -444,7 +452,7 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { load: function(id, hash) { var data = {}; data[get(this.constructor, 'primaryKey')] = id; - set(this, 'data', Ember.merge(data, hash)); + set(this, '_data', Ember.merge(data, hash)); set(this, 'isLoaded', true); set(this, 'isNew', false); this._createReference(); @@ -456,10 +464,10 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { var meta = value.meta(); if (meta.isAttribute) { - if (!proto.attributes) { proto.attributes = []; } + proto.attributes = proto.attributes ? proto.attributes.slice() : []; proto.attributes.push(key); } else if (meta.isRelationship) { - if (!proto.relationships) { proto.relationships = []; } + proto.relationships = proto.relationships ? proto.relationships.slice() : []; proto.relationships.push(key); } } @@ -472,33 +480,37 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { serializeBelongsTo: function(key, meta) { if (meta.options.embedded) { var record = this.get(key); - if (record) { - return record.toJSON(); - } + return record ? record.toJSON() : null; } else { - var primaryKey = get(meta.type, 'primaryKey'); + var primaryKey = get(meta.getType(), 'primaryKey'); return this.get(key + '.' + primaryKey); } }, toJSON: function() { var key, meta, - properties = this.getProperties(this.attributes); + json = {}, + properties = this.attributes ? this.getProperties(this.attributes) : {}, + rootKey = get(this.constructor, 'rootKey'); for (key in properties) { meta = this.constructor.metaForProperty(key); if (meta.type && meta.type.serialize) { - properties[key] = meta.type.serialize(properties[key]); + json[this.dataKey(key)] = meta.type.serialize(properties[key]); } else if (meta.type && Ember.Model.dataTypes[meta.type]) { - properties[key] = Ember.Model.dataTypes[meta.type].serialize(properties[key]); + json[this.dataKey(key)] = Ember.Model.dataTypes[meta.type].serialize(properties[key]); + } else { + json[this.dataKey(key)] = properties[key]; } } if (this.relationships) { - var data; + var data, relationshipKey; + for(var i = 0; i < this.relationships.length; i++) { key = this.relationships[i]; meta = this.constructor.metaForProperty(key); + relationshipKey = meta.options.key || key; if (meta.kind === 'belongsTo') { data = this.serializeBelongsTo(key, meta); @@ -506,19 +518,17 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { data = this.serializeHasMany(key, meta); } - if (data) { - properties[key] = data; - } + json[relationshipKey] = data; + } } - if (this.constructor.rootKey) { - var json = {}; - json[this.constructor.rootKey] = properties; - - return json; + if (rootKey) { + var jsonRoot = {}; + jsonRoot[rootKey] = json; + return jsonRoot; } else { - return properties; + return json; } }, @@ -545,7 +555,7 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { revert: function() { if (this.get('isDirty')) { - var data = get(this, 'data'), + var data = get(this, '_data') || {}, reverts = {}; for (var i = 0; i < this._dirtyAttributes.length; i++) { var attr = this._dirtyAttributes[i]; @@ -564,7 +574,7 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { if (!this.constructor.recordCache) this.constructor.recordCache = {}; this.constructor.recordCache[id] = this; - this.load(id, this.getProperties(this.attributes)); + this._copyDirtyAttributesToData(); this.constructor.addToRecordArrays(this); this.trigger('didCreateRecord'); this.didSaveRecord(); @@ -589,12 +599,12 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { _copyDirtyAttributesToData: function() { if (!this._dirtyAttributes) { return; } var dirtyAttributes = this._dirtyAttributes, - data = get(this, 'data'), + data = get(this, '_data'), key; if (!data) { data = {}; - set(this, 'data', data); + set(this, '_data', data); } for (var i = 0, l = dirtyAttributes.length; i < l; i++) { // TODO: merge Object.create'd object into prototype @@ -606,7 +616,7 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { dataDidChange: Ember.observer(function() { this._reloadHasManys(); - }, 'data'), + }, '_data'), _registerHasManyArray: function(array) { if (!this._hasManyArrays) { this._hasManyArrays = Ember.A([]); } @@ -625,7 +635,7 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { }, _getHasManyContent: function(key, type, embedded) { - var content = get(this, 'data.' + key); + var content = get(this, '_data.' + key); if (content) { var mapFunction, primaryKey, reference; @@ -693,7 +703,7 @@ Ember.Model.reopenClass({ findAll: function() { if (this._findAllRecordArray) { return this._findAllRecordArray; } - var records = this._findAllRecordArray = Ember.RecordArray.create(); + var records = this._findAllRecordArray = Ember.RecordArray.create({modelClass: this}); this.adapter.findAll(this, records); @@ -723,7 +733,7 @@ Ember.Model.reopenClass({ _fetchById: function(record, id) { var adapter = get(this, 'adapter'); - if (adapter.findMany) { + if (adapter.findMany && !adapter.findMany.isUnimplemented) { if (this._currentBatchIds) { if (!contains(this._currentBatchIds, id)) { this._currentBatchIds.push(id); } } else { @@ -793,11 +803,11 @@ Ember.Model.reopenClass({ attrs = {isLoaded: false}; attrs[primaryKey] = id; record = this.create(attrs); + this.recordCache[id] = record; var sideloadedData = this.sideloadedData && this.sideloadedData[id]; if (sideloadedData) { record.load(id, sideloadedData); } - this.recordCache[id] = record; } return record; @@ -897,6 +907,16 @@ Ember.Model.reopenClass({ } return reference; + }, + + resetData: function() { + this._idToReference = null; + this.sideloadedData = null; + this.recordCache = null; + this.recordArrays = null; + this._currentBatchIds = null; + this._hasManyArrays = null; + this._findAllRecordArray = null; } }); @@ -948,31 +968,45 @@ Ember.Model.reopen({ var get = Ember.get; +function getType() { + if (typeof this.type === "string") { + this.type = Ember.get(Ember.lookup, this.type); + } + return this.type; +} + Ember.belongsTo = function(type, options) { options = options || {}; - var meta = { type: type, isRelationship: true, options: options, kind: 'belongsTo' }, - key = options.key; + var meta = { type: type, isRelationship: true, options: options, kind: 'belongsTo', getType: getType }, + relationshipKey = options.key; - return Ember.computed(function() { - if (typeof type === "string") { - type = Ember.get(Ember.lookup, type); + return Ember.computed(function(key, value) { + type = meta.getType(); + + if (arguments.length === 2) { + if (value) { + Ember.assert(Ember.String.fmt('Attempted to set property of type: %@ with a value of type: %@', + [value.constructor, type]), + value instanceof type); + } + return value === undefined ? null : value; + } else { + return this.getBelongsTo(relationshipKey, type, meta); } - - return this.getBelongsTo(key, type, meta); - }).property('data').meta(meta); + }).property('_data').meta(meta); }; Ember.Model.reopen({ getBelongsTo: function(key, type, meta) { - var idOrAttrs = get(this, 'data.' + key), + var idOrAttrs = get(this, '_data.' + key), record; - if(Ember.isNone(idOrAttrs)) { + if (Ember.isNone(idOrAttrs)) { return null; } - if(meta.options.embedded) { + if (meta.options.embedded) { var primaryKey = get(type, 'primaryKey'); record = type.create({ isLoaded: false }); record.load(idOrAttrs[primaryKey], idOrAttrs); @@ -1029,6 +1063,11 @@ Ember.Model.dataTypes[Date] = { serialize: function (date) { if(!date) { return null; } return date.toISOString(); + }, + isEqual: function(obj1, obj2) { + if (obj1 instanceof Date) { obj1 = this.serialize(obj1); } + if (obj2 instanceof Date) { obj2 = this.serialize(obj2); } + return obj1 === obj2; } }; @@ -1054,9 +1093,9 @@ function deserialize(value, type) { } -Ember.attr = function(type) { +Ember.attr = function(type, options) { return Ember.computed(function(key, value) { - var data = get(this, 'data'), + var data = get(this, '_data'), dataKey = this.dataKey(key), dataValue = data && get(data, dataKey), beingCreated = meta(this).proto === this; @@ -1064,14 +1103,14 @@ Ember.attr = function(type) { if (arguments.length === 2) { if (beingCreated && !data) { data = {}; - set(this, 'data', data); + set(this, '_data', data); data[dataKey] = value; } return wrapObject(value); } return this.getAttr(key, deserialize(dataValue, type)); - }).property('data').meta({isAttribute: true, type: type}); + }).property('_data').meta({isAttribute: true, type: type, options: options}); }; @@ -1191,12 +1230,16 @@ Ember.RESTAdapter = Ember.Adapter.extend({ } }, - _ajax: function(url, params, method) { - var settings = { + ajaxSettings: function(url, method) { + return { url: url, type: method, dataType: "json" }; + }, + + _ajax: function(url, params, method) { + var settings = this.ajaxSettings(url, method); return new Ember.RSVP.Promise(function(resolve, reject) { if (params) { @@ -1256,4 +1299,4 @@ Ember.loadPromise = function(target) { }; -})(); \ No newline at end of file +})(); diff --git a/assets/scripts/vendor/ember.js b/assets/scripts/vendor/ember.js index 5268555a..9008b148 100644 --- a/assets/scripts/vendor/ember.js +++ b/assets/scripts/vendor/ember.js @@ -1,5 +1,5 @@ -// Version: v1.0.0-rc.6-102-gf20bf05 -// Last commit: f20bf05 (2013-07-08 20:53:58 -0700) +// Version: v1.0.0-rc.6-221-g9d051c2 +// Last commit: 9d051c2 (2013-07-28 23:13:59 -0700) (function() { @@ -156,8 +156,8 @@ Ember.deprecateFunc = function(message, func) { })(); -// Version: v1.0.0-rc.6-102-gf20bf05 -// Last commit: f20bf05 (2013-07-08 20:53:58 -0700) +// Version: v1.0.0-rc.6-221-g9d051c2 +// Last commit: 9d051c2 (2013-07-28 23:13:59 -0700) (function() { @@ -251,10 +251,10 @@ Ember.toString = function() { return "Ember"; }; /** @property VERSION @type String - @default '1.0.0-rc.6' + @default '1.0.0-rc.6.1' @final */ -Ember.VERSION = '1.0.0-rc.6'; +Ember.VERSION = '1.0.0-rc.6.1'; /** Standard environmental variables. You can define these in a global `ENV` @@ -378,7 +378,7 @@ function assertPolyfill(test, message) { // attempt to preserve the stack throw new Error("assertion failed: " + message); } catch(error) { - setTimeout(function(){ + setTimeout(function() { throw error; }, 0); } @@ -460,7 +460,7 @@ Ember.merge = function(original, updates) { Ember.isNone(undefined); // true Ember.isNone(''); // false Ember.isNone([]); // false - Ember.isNone(function(){}); // false + Ember.isNone(function() {}); // false ``` @method isNone @@ -565,7 +565,7 @@ var canRedefineProperties, canDefinePropertyOnDOM; // Catch IE8 where Object.defineProperty exists but only works on DOM elements if (defineProperty) { try { - defineProperty({}, 'a',{get:function(){}}); + defineProperty({}, 'a',{get:function() {}}); } catch (e) { defineProperty = null; } @@ -596,7 +596,7 @@ if (defineProperty) { // This is for Safari 5.0, which supports Object.defineProperty, but not // on DOM nodes. - canDefinePropertyOnDOM = (function(){ + canDefinePropertyOnDOM = (function() { try { defineProperty(document.createElement('div'), 'definePropertyOnDOM', {}); return true; @@ -608,7 +608,7 @@ if (defineProperty) { if (!canRedefineProperties) { defineProperty = null; } else if (!canDefinePropertyOnDOM) { - defineProperty = function(obj, keyName, desc){ + defineProperty = function(obj, keyName, desc) { var isNode; if (typeof Node === "object") { @@ -1218,7 +1218,7 @@ if (needsFinallyFix) { } finally { try { finalResult = finalizer.call(binding); - } catch (e){ + } catch (e) { finalError = e; } } @@ -1270,7 +1270,7 @@ if (needsFinallyFix) { } finally { try { finalResult = finalizer.call(binding); - } catch (e){ + } catch (e) { finalError = e; } } @@ -1469,7 +1469,7 @@ Ember.Instrumentation.instrument = function(name, payload, callback, binding) { var beforeValues = [], listener, i, l; - function tryable(){ + function tryable() { for (i=0, l=listeners.length; i 1) { set(this, dependentKey, value); return value; @@ -5151,7 +5171,7 @@ var Backburner = requireModule('backburner').Backburner, call. ```javascript - Ember.run(function(){ + Ember.run(function() { // code to be execute within a RunLoop }); ``` @@ -5194,7 +5214,7 @@ Ember.run = function(target, method) { If invoked when not within a run loop: ```javascript - Ember.run.join(function(){ + Ember.run.join(function() { // creates a new run-loop }); ``` @@ -5202,9 +5222,9 @@ Ember.run = function(target, method) { Alternatively, if called within an existing run loop: ```javascript - Ember.run(function(){ + Ember.run(function() { // creates a new run-loop - Ember.run.join(function(){ + Ember.run.join(function() { // joins with the existing run-loop, and queues for invocation on // the existing run-loops action queue. }); @@ -5297,12 +5317,12 @@ Ember.run.end = function() { the `Ember.run.queues` property. ```javascript - Ember.run.schedule('sync', this, function(){ + Ember.run.schedule('sync', this, function() { // 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(){ + Ember.run.schedule('actions', this, function() { // this will be executed in the 'actions' queue, after bindings have synced. console.log("scheduled on actions queue"); }); @@ -5370,7 +5390,7 @@ Ember.run.sync = function() { together, which is often more efficient than using a real setTimeout. ```javascript - Ember.run.later(myContext, function(){ + Ember.run.later(myContext, function() { // code here will execute within a RunLoop in about 500ms with this == myContext }, 500); ``` @@ -5418,7 +5438,7 @@ Ember.run.once = function(target, method) { calls. ```javascript - Ember.run(function(){ + Ember.run(function() { var sayHi = function() { console.log('hi'); } Ember.run.scheduleOnce('afterRender', myContext, sayHi); Ember.run.scheduleOnce('afterRender', myContext, sayHi); @@ -5463,7 +5483,7 @@ Ember.run.scheduleOnce = function(queue, target, method) { `Ember.run.later` with a wait time of 1ms. ```javascript - Ember.run.next(myContext, function(){ + Ember.run.next(myContext, function() { // code to be executed in the next run loop, which will be scheduled after the current one }); ``` @@ -5525,17 +5545,17 @@ Ember.run.next = function() { `Ember.run.once()`, or `Ember.run.next()`. ```javascript - var runNext = Ember.run.next(myContext, function(){ + var runNext = Ember.run.next(myContext, function() { // will not be executed }); Ember.run.cancel(runNext); - var runLater = Ember.run.later(myContext, function(){ + var runLater = Ember.run.later(myContext, function() { // will not be executed }, 500); Ember.run.cancel(runLater); - var runOnce = Ember.run.once(myContext, function(){ + var runOnce = Ember.run.once(myContext, function() { // will not be executed }); Ember.run.cancel(runOnce); @@ -6116,7 +6136,8 @@ Ember.oneWay = function(obj, to, from) { (function() { /** -@module ember-metal +@module ember +@submodule ember-metal */ var Mixin, REQUIRED, Alias, @@ -6492,38 +6513,6 @@ 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* @@ -6683,7 +6672,7 @@ Alias.prototype = new Ember.Descriptor(); App.PaintSample = Ember.Object.extend({ color: 'red', colour: Ember.alias('color'), - name: function(){ + name: function() { return "Zed"; }, moniker: Ember.alias("name") @@ -6711,7 +6700,7 @@ Ember.alias = Ember.deprecateFunc("Ember.alias is deprecated. Please use Ember.a ```javascript App.Person = Ember.Object.extend({ - name: function(){ + name: function() { return 'Tomhuda Katzdale'; }, moniker: Ember.aliasMethod('name') @@ -6780,7 +6769,7 @@ Ember.immediateObserver = function() { }.observesBefore('content.value'), valueDidChange: function(obj, keyName, value) { // only run if updating a value already in the DOM - if(this.get('state') === 'inDOM') { + if (this.get('state') === 'inDOM') { var color = value > this.changingFrom ? 'green' : 'red'; // logic } @@ -6816,65 +6805,82 @@ Ember Metal (function() { define("rsvp/all", - ["rsvp/defer","exports"], + ["rsvp/promise","exports"], function(__dependency1__, __exports__) { "use strict"; - var defer = __dependency1__.defer; + var Promise = __dependency1__.Promise; + /* global toString */ + function all(promises) { - var results = [], deferred = defer(), remaining = promises.length; - - if (remaining === 0) { - deferred.resolve([]); + if (Object.prototype.toString.call(promises) !== "[object Array]") { + throw new TypeError('You must pass an array to all.'); } - var resolver = function(index) { - return function(value) { - resolveAll(index, value); - }; - }; + return new Promise(function(resolve, reject) { + var results = [], remaining = promises.length, + promise; - var resolveAll = function(index, value) { - results[index] = value; - if (--remaining === 0) { - deferred.resolve(results); + if (remaining === 0) { + resolve([]); } - }; - var rejectAll = function(error) { - deferred.reject(error); - }; - - for (var i = 0; i < promises.length; i++) { - if (promises[i] && typeof promises[i].then === 'function') { - promises[i].then(resolver(i), rejectAll); - } else { - resolveAll(i, promises[i]); + function resolver(index) { + return function(value) { + resolveAll(index, value); + }; } - } - return deferred.promise; + + function resolveAll(index, value) { + results[index] = value; + if (--remaining === 0) { + resolve(results); + } + } + + for (var i = 0; i < promises.length; i++) { + promise = promises[i]; + + if (promise && typeof promise.then === 'function') { + promise.then(resolver(i), reject); + } else { + resolveAll(i, promise); + } + } + }); } + __exports__.all = all; }); - define("rsvp/async", ["exports"], function(__exports__) { "use strict"; var browserGlobal = (typeof window !== 'undefined') ? window : {}; - var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver; var async; - if (typeof process !== 'undefined' && - {}.toString.call(process) === '[object process]') { - async = function(callback, binding) { + // old node + function useNextTick() { + return function(callback, arg) { process.nextTick(function() { - callback.call(binding); + callback(arg); }); }; - } else if (BrowserMutationObserver) { + } + + // node >= 0.10.x + function useSetImmediate() { + return function(callback, arg) { + /* global setImmediate */ + setImmediate(function(){ + callback(arg); + }); + }; + } + + function useMutationObserver() { var queue = []; var observer = new BrowserMutationObserver(function() { @@ -6882,8 +6888,8 @@ define("rsvp/async", queue = []; toProcess.forEach(function(tuple) { - var callback = tuple[0], binding = tuple[1]; - callback.call(binding); + var callback = tuple[0], arg= tuple[1]; + callback(arg); }); }); @@ -6894,24 +6900,35 @@ define("rsvp/async", window.addEventListener('unload', function(){ observer.disconnect(); observer = null; - }); + }, false); - async = function(callback, binding) { - queue.push([callback, binding]); + return function(callback, arg) { + queue.push([callback, arg]); element.setAttribute('drainQueue', 'drainQueue'); }; - } else { - async = function(callback, binding) { + } + + function useSetTimeout() { + return function(callback, arg) { setTimeout(function() { - callback.call(binding); + callback(arg); }, 1); }; } + if (typeof setImmediate === 'function') { + async = useSetImmediate(); + } else if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') { + async = useNextTick(); + } else if (BrowserMutationObserver) { + async = useMutationObserver(); + } else { + async = useSetTimeout(); + } + __exports__.async = async; }); - define("rsvp/config", ["rsvp/async","exports"], function(__dependency1__, __exports__) { @@ -6921,9 +6938,9 @@ define("rsvp/config", var config = {}; config.async = async; + __exports__.config = config; }); - define("rsvp/defer", ["rsvp/promise","exports"], function(__dependency1__, __exports__) { @@ -6931,20 +6948,24 @@ define("rsvp/defer", var Promise = __dependency1__.Promise; function defer() { - var deferred = {}; + var deferred = { + // pre-allocate shape + resolve: undefined, + reject: undefined, + promise: undefined + }; - var promise = new Promise(function(resolve, reject) { + deferred.promise = new Promise(function(resolve, reject) { deferred.resolve = resolve; deferred.reject = reject; }); - deferred.promise = promise; return deferred; } + __exports__.defer = defer; }); - define("rsvp/events", ["exports"], function(__exports__) { @@ -7046,7 +7067,6 @@ define("rsvp/events", __exports__.EventTarget = EventTarget; }); - define("rsvp/hash", ["rsvp/defer","exports"], function(__dependency1__, __exports__) { @@ -7054,13 +7074,13 @@ define("rsvp/hash", var defer = __dependency1__.defer; function size(object) { - var size = 0; + var s = 0; for (var prop in object) { - size++; + s++; } - return size; + return s; } function hash(promises) { @@ -7098,9 +7118,9 @@ define("rsvp/hash", return deferred.promise; } + __exports__.hash = hash; }); - define("rsvp/node", ["rsvp/promise","rsvp/all","exports"], function(__dependency1__, __dependency2__, __exports__) { @@ -7123,6 +7143,7 @@ define("rsvp/node", function denodeify(nodeFunc) { return function() { var nodeArgs = Array.prototype.slice.call(arguments), resolve, reject; + var thisArg = this; var promise = new Promise(function(nodeResolve, nodeReject) { resolve = nodeResolve; @@ -7133,7 +7154,7 @@ define("rsvp/node", nodeArgs.push(makeNodeCallbackFor(resolve, reject)); try { - nodeFunc.apply(this, nodeArgs); + nodeFunc.apply(thisArg, nodeArgs); } catch(e) { reject(e); } @@ -7143,9 +7164,9 @@ define("rsvp/node", }; } + __exports__.denodeify = denodeify; }); - define("rsvp/promise", ["rsvp/config","rsvp/events","exports"], function(__dependency1__, __dependency2__, __exports__) { @@ -7193,6 +7214,8 @@ define("rsvp/promise", this.trigger('error', { detail: event.detail }); }, this); + this.on('error', onerror); + try { resolver(resolvePromise, rejectPromise); } catch(e) { @@ -7200,6 +7223,12 @@ define("rsvp/promise", } }; + function onerror(event) { + if (config.onerror) { + config.onerror(event.detail); + } + } + var invokeCallback = function(type, promise, callback, event) { var hasCallback = isFunction(callback), value, error, succeeded, failed; @@ -7233,18 +7262,25 @@ define("rsvp/promise", Promise.prototype = { constructor: Promise, + isRejected: undefined, + isFulfilled: undefined, + rejectedReason: undefined, + fulfillmentValue: undefined, + then: function(done, fail) { - var thenPromise = new Promise(function() {}); + this.off('error', onerror); + + var thenPromise = new this.constructor(function() {}); if (this.isFulfilled) { - config.async(function() { - invokeCallback('resolve', thenPromise, done, { detail: this.fulfillmentValue }); + config.async(function(promise) { + invokeCallback('resolve', thenPromise, done, { detail: promise.fulfillmentValue }); }, this); } if (this.isRejected) { - config.async(function() { - invokeCallback('reject', thenPromise, fail, { detail: this.rejectedReason }); + config.async(function(promise) { + invokeCallback('reject', thenPromise, fail, { detail: promise.rejectedReason }); }, this); } @@ -7271,32 +7307,40 @@ define("rsvp/promise", } function handleThenable(promise, value) { - var then = null; + var then = null, + resolved; - if (objectOrFunction(value)) { - try { - then = value.then; - } catch(e) { - reject(promise, e); - return true; + try { + if (promise === value) { + throw new TypeError("A promises callback cannot return that same promise."); } - if (isFunction(then)) { - try { + if (objectOrFunction(value)) { + then = value.then; + + if (isFunction(then)) { then.call(value, function(val) { + if (resolved) { return true; } + resolved = true; + if (value !== val) { resolve(promise, val); } else { fulfill(promise, val); } }, function(val) { + if (resolved) { return true; } + resolved = true; + reject(promise, val); }); - } catch (e) { - reject(promise, e); + + return true; } - return true; } + } catch (error) { + reject(promise, error); + return true; } return false; @@ -7321,19 +7365,12 @@ define("rsvp/promise", __exports__.Promise = Promise; }); - define("rsvp/reject", ["rsvp/promise","exports"], function(__dependency1__, __exports__) { "use strict"; var Promise = __dependency1__.Promise; - - function objectOrFunction(x) { - return typeof x === "function" || (typeof x === "object" && x !== null); - } - - function reject(reason) { return new Promise(function (resolve, reject) { reject(reason); @@ -7343,48 +7380,21 @@ define("rsvp/reject", __exports__.reject = reject; }); - define("rsvp/resolve", ["rsvp/promise","exports"], function(__dependency1__, __exports__) { "use strict"; var Promise = __dependency1__.Promise; - - function objectOrFunction(x) { - return typeof x === "function" || (typeof x === "object" && x !== null); - } - - function resolve(thenable){ - var promise = new Promise(function(resolve, reject){ - var then; - - try { - if ( objectOrFunction(thenable) ) { - then = thenable.then; - - if (typeof then === "function") { - then.call(thenable, resolve, reject); - } else { - resolve(thenable); - } - - } else { - resolve(thenable); - } - - } catch(error) { - reject(error); - } + function resolve(thenable) { + return new Promise(function(resolve, reject) { + resolve(thenable); }); - - return promise; } __exports__.resolve = resolve; }); - define("rsvp", ["rsvp/events","rsvp/promise","rsvp/node","rsvp/all","rsvp/hash","rsvp/defer","rsvp/config","rsvp/resolve","rsvp/reject","exports"], function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __exports__) { @@ -7415,8 +7425,6 @@ define("rsvp", __exports__.reject = reject; }); - - })(); (function() { @@ -7666,7 +7674,7 @@ define("container", register: function(type, name, factory, options) { var fullName; - if (type.indexOf(':') !== -1){ + if (type.indexOf(':') !== -1) { options = factory; factory = name; fullName = type; @@ -7740,6 +7748,20 @@ define("container", return this.resolver(fullName) || this.registry.get(fullName); }, + /** + A hook that can be used to describe how the resolver will + attempt to find the factory. + + For example, the default Ember `.describe` returns the full + class name (including namespace) where Ember's resolver expects + to find the `fullName`. + + @method describe + */ + describe: function(fullName) { + return fullName; + }, + /** A hook to enable custom fullName normalization behaviour @@ -8314,7 +8336,11 @@ Ember.copy = function(obj, deep) { @return {String} A description of the object */ Ember.inspect = function(obj) { - if (typeof obj !== 'object' || obj === null) { + var type = Ember.typeOf(obj); + if (type === 'array') { + return '[' + obj + ']'; + } + if (type !== 'object') { return obj + ''; } @@ -8497,9 +8523,9 @@ Ember.String = { // first, replace any ORDERED replacements. var idx = 0; // the current index for non-numerical replacements return str.replace(/%@([0-9]+)?/g, function(s, argIndex) { - argIndex = (argIndex) ? parseInt(argIndex,0) - 1 : idx++ ; + argIndex = (argIndex) ? parseInt(argIndex, 10) - 1 : idx++; s = formats[argIndex]; - return ((s === null) ? '(null)' : (s === undefined) ? '' : s).toString(); + return (s === null) ? '(null)' : (s === undefined) ? '' : Ember.inspect(s); }) ; }, @@ -9017,7 +9043,6 @@ function iter(key, value) { @class Enumerable @namespace Ember - @extends Ember.Mixin @since Ember 0.9 */ Ember.Enumerable = Ember.Mixin.create({ @@ -9493,7 +9518,7 @@ Ember.Enumerable = Ember.Mixin.create({ @method some @param {Function} callback The callback to execute @param {Object} [target] The target object to use - @return {Array} A filtered array. + @return {Boolean} `true` if the passed function returns `true` for any item */ some: function(callback, target) { return !!this.find(function(x, idx, i) { @@ -9508,7 +9533,7 @@ Ember.Enumerable = Ember.Mixin.create({ @method someProperty @param {String} key the property to test @param {String} [value] optional value to test against. - @return {Boolean} `true` + @return {Boolean} `true` if the passed function returns `true` for any item */ someProperty: function(key, value) { return this.some(iter.apply(this, arguments)); @@ -9648,7 +9673,7 @@ Ember.Enumerable = Ember.Mixin.create({ */ uniq: function() { var ret = Ember.A(); - this.forEach(function(k){ + this.forEach(function(k) { if (a_indexOf(ret, k)<0) ret.push(k); }); return ret; @@ -9852,7 +9877,6 @@ var get = Ember.get, set = Ember.set, isNone = Ember.isNone, map = Ember.Enumera @class Array @namespace Ember - @extends Ember.Mixin @uses Ember.Enumerable @since Ember 0.9.0 */ @@ -9908,7 +9932,7 @@ Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.protot */ objectsAt: function(indexes) { var self = this; - return map(indexes, function(idx){ return self.objectAt(idx); }); + return map(indexes, function(idx) { return self.objectAt(idx); }); }, // overrides Ember.Enumerable version @@ -9940,7 +9964,7 @@ Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.protot }), // optimized version from Enumerable - contains: function(obj){ + contains: function(obj) { return this.indexOf(obj) >= 0; }, @@ -10005,7 +10029,7 @@ Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.protot if (startAt < 0) startAt += len; for(idx=startAt;idx0) { this._initMixins(arguments); } return new C(); }, + /** + 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` or use the `createWithMixins` shorthand. + + @method create + @static + @param [arguments]* + */ create: function() { var C = this; if (arguments.length>0) { this._initProperties(arguments); } @@ -12296,10 +12352,6 @@ if (Ember.config.overrideClassMixin) { CoreObject.ClassMixin = ClassMixin; ClassMixin.apply(CoreObject); -/** - @class CoreObject - @namespace Ember -*/ Ember.CoreObject = CoreObject; })(); @@ -12819,7 +12871,7 @@ Ember.ArrayProxy = Ember.Object.extend(Ember.MutableArray,/** @scope Ember.Array }, pushObjects: function(objects) { - if(!(Ember.Enumerable.detect(objects) || Ember.isArray(objects))) { + if (!(Ember.Enumerable.detect(objects) || Ember.isArray(objects))) { throw new TypeError("Must pass Ember.Enumerable to Ember.MutableArray#pushObjects"); } this._replace(get(this, 'length'), 0, objects); @@ -13074,6 +13126,7 @@ function addObserverForContentKey(content, keyName, proxy, idx, loc) { while(--loc>=idx) { var item = content.objectAt(loc); if (item) { + Ember.assert('When using @each to observe the array ' + content + ', the array must return an object', Ember.typeOf(item) === 'instance' || Ember.typeOf(item) === 'object'); Ember.addBeforeObserver(item, keyName, proxy, 'contentKeyWillChange'); Ember.addObserver(item, keyName, proxy, 'contentKeyDidChange'); @@ -13157,7 +13210,7 @@ Ember.EachProxy = Ember.Object.extend({ for(key in keys) { if (!keys.hasOwnProperty(key)) { continue; } - if (lim>0) removeObserverForContentKey(content, key, this, idx, lim); + if (lim>0) { removeObserverForContentKey(content, key, this, idx, lim); } Ember.propertyWillChange(this, key); } @@ -13167,21 +13220,20 @@ Ember.EachProxy = Ember.Object.extend({ }, arrayDidChange: function(content, idx, removedCnt, addedCnt) { - var keys = this._keys, key, lim; + var keys = this._keys, lim; lim = addedCnt>0 ? idx+addedCnt : -1; - Ember.beginPropertyChanges(this); + Ember.changeProperties(function() { + for(var key in keys) { + if (!keys.hasOwnProperty(key)) { continue; } - for(key in keys) { - if (!keys.hasOwnProperty(key)) { continue; } + if (lim>0) { addObserverForContentKey(content, key, this, idx, lim); } - if (lim>0) addObserverForContentKey(content, key, this, idx, lim); + Ember.propertyDidChange(this, key); + } - Ember.propertyDidChange(this, key); - } - - Ember.propertyDidChange(this._content, '@each'); - Ember.endPropertyChanges(this); + Ember.propertyDidChange(this._content, '@each'); + }, this); }, // .......................................................... @@ -13330,7 +13382,7 @@ var NativeArray = Ember.Mixin.create(Ember.MutableArray, Ember.Observable, Ember copy: function(deep) { if (deep) { - return this.map(function(item){ return Ember.copy(item, true); }); + return this.map(function(item) { return Ember.copy(item, true); }); } return this.slice(); @@ -13356,7 +13408,6 @@ if (ignore.length>0) { @class NativeArray @namespace Ember - @extends Ember.Mixin @uses Ember.MutableArray @uses Ember.Observable @uses Ember.Copyable @@ -13371,7 +13422,7 @@ Ember.NativeArray = NativeArray; @for Ember @return {Ember.NativeArray} */ -Ember.A = function(arr){ +Ember.A = function(arr) { if (arr === undefined) { arr = []; } return Ember.Array.detect(arr) ? arr : Ember.NativeArray.apply(arr); }; @@ -13547,7 +13598,7 @@ Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Emb Ember.propertyWillChange(this, 'firstObject'); Ember.propertyWillChange(this, 'lastObject'); - for (var i=0; i < len; i++){ + for (var i=0; i < len; i++) { guid = guidFor(this[i]); delete this[guid]; delete this[i]; @@ -13967,7 +14018,6 @@ var get = Ember.get; @class ControllerMixin @namespace Ember - @extends Ember.Mixin */ Ember.ControllerMixin = Ember.Mixin.create({ /* ducktype as a controller */ @@ -14001,7 +14051,7 @@ Ember.ControllerMixin = Ember.Mixin.create({ if (this[actionName]) { Ember.assert("The controller " + this + " does not have the action " + actionName, typeof this[actionName] === 'function'); this[actionName].apply(this, args); - } else if(target = get(this, 'target')) { + } else if (target = get(this, 'target')) { Ember.assert("The target for controller " + this + " (" + target + ") did not define a `send` method", typeof target.send === 'function'); target.send.apply(target, arguments); } @@ -14055,7 +14105,6 @@ var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach; @class SortableMixin @namespace Ember - @extends Ember.Mixin @uses Ember.MutableEnumerable */ Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, { @@ -14073,7 +14122,7 @@ Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, { @property {Boolean} sortAscending */ sortAscending: true, - + /** The function used to compare two values. You can override this if you want to do custom comparisons.Functions must be of the type expected by @@ -14083,8 +14132,8 @@ Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, { return a positive value otherwise: ```javascript - function(x,y){ // These are assumed to be integers - if(x === y) + function(x,y) { // These are assumed to be integers + if (x === y) return 0; return x < y ? -1 : 1; } @@ -14095,7 +14144,7 @@ Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, { @default Ember.compare */ sortFunction: Ember.compare, - + orderBy: function(item1, item2) { var result = 0, sortProperties = get(this, 'sortProperties'), @@ -14590,7 +14639,7 @@ if (Ember.$) { // is a "zero-scope" element. This problem can be worked around by making // the first node an invisible text node. We, like Modernizr, use ­ -var needsShy = this.document && (function(){ +var needsShy = this.document && (function() { var testEl = document.createElement('div'); testEl.innerHTML = "
"; testEl.firstChild.innerHTML = ""; @@ -14746,6 +14795,45 @@ ClassSet.prototype = { } }; +var BAD_TAG_NAME_TEST_REGEXP = /[^a-zA-Z0-9\-]/; +var BAD_TAG_NAME_REPLACE_REGEXP = /[^a-zA-Z0-9\-]/g; + +function stripTagName(tagName) { + if (!tagName) { + return tagName; + } + + if (!BAD_TAG_NAME_TEST_REGEXP.test(tagName)) { + return tagName; + } + + return tagName.replace(BAD_TAG_NAME_REPLACE_REGEXP, ''); +} + +var BAD_CHARS_REGEXP = /&(?!\w+;)|[<>"'`]/g; +var POSSIBLE_CHARS_REGEXP = /[&<>"'`]/; + +function escapeAttribute(value) { + // Stolen shamelessly from Handlebars + + var escape = { + "<": "<", + ">": ">", + '"': """, + "'": "'", + "`": "`" + }; + + var escapeChar = function(chr) { + return escape[chr] || "&"; + }; + + var string = value.toString(); + + if(!POSSIBLE_CHARS_REGEXP.test(string)) { return string; } + return string.replace(BAD_CHARS_REGEXP, escapeChar); +} + /** `Ember.RenderBuffer` gathers information regarding the a view and generates the final representation. `Ember.RenderBuffer` will generate HTML which can be pushed @@ -15033,14 +15121,14 @@ Ember._RenderBuffer.prototype = style = this.elementStyle, attr, prop; - buffer += '<' + tagName; + buffer += '<' + stripTagName(tagName); if (id) { - buffer += ' id="' + this._escapeAttribute(id) + '"'; + buffer += ' id="' + escapeAttribute(id) + '"'; this.elementId = null; } if (classes) { - buffer += ' class="' + this._escapeAttribute(classes.join(' ')) + '"'; + buffer += ' class="' + escapeAttribute(classes.join(' ')) + '"'; this.classes = null; } @@ -15049,7 +15137,7 @@ Ember._RenderBuffer.prototype = for (prop in style) { if (style.hasOwnProperty(prop)) { - buffer += prop + ':' + this._escapeAttribute(style[prop]) + ';'; + buffer += prop + ':' + escapeAttribute(style[prop]) + ';'; } } @@ -15061,7 +15149,7 @@ Ember._RenderBuffer.prototype = if (attrs) { for (attr in attrs) { if (attrs.hasOwnProperty(attr)) { - buffer += ' ' + attr + '="' + this._escapeAttribute(attrs[attr]) + '"'; + buffer += ' ' + attr + '="' + escapeAttribute(attrs[attr]) + '"'; } } @@ -15076,7 +15164,7 @@ Ember._RenderBuffer.prototype = if (value === true) { buffer += ' ' + prop + '="' + prop + '"'; } else { - buffer += ' ' + prop + '="' + this._escapeAttribute(props[prop]) + '"'; + buffer += ' ' + prop + '="' + escapeAttribute(props[prop]) + '"'; } } } @@ -15091,7 +15179,7 @@ Ember._RenderBuffer.prototype = pushClosingTag: function() { var tagName = this.tagNames.pop(); - if (tagName) { this.buffer += ''; } + if (tagName) { this.buffer += ''; } }, currentTagName: function() { @@ -15178,7 +15266,7 @@ Ember._RenderBuffer.prototype = if (this._hasElement && this._element) { // Firefox versions < 11 do not have support for element.outerHTML. var thisElement = this.element(), outerHTML = thisElement.outerHTML; - if (typeof outerHTML === 'undefined'){ + if (typeof outerHTML === 'undefined') { return Ember.$('
').append(thisElement).html(); } return outerHTML; @@ -15189,32 +15277,7 @@ Ember._RenderBuffer.prototype = innerString: function() { return this.buffer; - }, - - _escapeAttribute: function(value) { - // Stolen shamelessly from Handlebars - - var escape = { - "<": "<", - ">": ">", - '"': """, - "'": "'", - "`": "`" - }; - - var badChars = /&(?!\w+;)|[<>"'`]/g; - var possible = /[&<>"'`]/; - - var escapeChar = function(chr) { - return escape[chr] || "&"; - }; - - var string = value.toString(); - - if(!possible.test(string)) { return string; } - return string.replace(badChars, escapeChar); } - }; })(); @@ -15530,8 +15593,11 @@ var childViewsProperty = Ember.computed(function() { var childViews = this._childViews, ret = Ember.A(), view = this; a_forEach(childViews, function(view) { + var currentChildViews; if (view.isVirtual) { - ret.pushObjects(get(view, 'childViews')); + if (currentChildViews = get(view, 'childViews')) { + ret.pushObjects(currentChildViews); + } } else { ret.push(view); } @@ -15826,8 +15892,8 @@ class: MyView = Ember.View.extend({ classNameBindings: ['propertyA', 'propertyB'], propertyA: 'from-a', - propertyB: function(){ - if(someLogic){ return 'from-b'; } + propertyB: function() { + if (someLogic) { return 'from-b'; } }.property() }); ``` @@ -16008,7 +16074,7 @@ class: MyTextInput = Ember.View.extend({ tagName: 'input', attributeBindings: ['disabled'], - disabled: function(){ + disabled: function() { if (someLogic) { return true; } else { @@ -16117,7 +16183,7 @@ class: aController = Ember.Object.create({ firstName: 'Barry', - excitedGreeting: function(){ + excitedGreeting: function() { return this.get("content.firstName") + "!!!" }.property() }); @@ -16188,7 +16254,7 @@ class: ```javascript AView = Ember.View.extend({ - click: function(event){ + click: function(event) { // will be called when when an instance's // rendered element is clicked } @@ -16209,7 +16275,7 @@ class: ```javascript AView = Ember.View.extend({ eventManager: Ember.Object.create({ - doubleClick: function(event, view){ + doubleClick: function(event, view) { // will be called when when an instance's // rendered element or any rendering // of this views's descendent @@ -16224,11 +16290,11 @@ class: ```javascript AView = Ember.View.extend({ - mouseEnter: function(event){ + mouseEnter: function(event) { // will never trigger. }, eventManager: Ember.Object.create({ - mouseEnter: function(event, view){ + mouseEnter: function(event, view) { // takes presedence over AView#mouseEnter } }) @@ -16246,7 +16312,7 @@ class: OuterView = Ember.View.extend({ template: Ember.Handlebars.compile("outer {{#view InnerView}}inner{{/view}} outer"), eventManager: Ember.Object.create({ - mouseEnter: function(event, view){ + mouseEnter: function(event, view) { // view might be instance of either // OuterView or InnerView depending on // where on the page the user interaction occured @@ -16255,12 +16321,12 @@ class: }); InnerView = Ember.View.extend({ - click: function(event){ + click: function(event) { // will be called if rendered inside // an OuterView because OuterView's // eventManager doesn't handle click events }, - mouseEnter: function(event){ + mouseEnter: function(event) { // will never be called if rendered inside // an OuterView. } @@ -16470,6 +16536,14 @@ Ember.View = Ember.CoreView.extend( } }).volatile(), + /** + The parent context for this template. + */ + parentContext: function() { + var parentView = get(this, '_parentView'); + return parentView && get(parentView, '_context'); + }, + /** @private @@ -16571,7 +16645,7 @@ Ember.View = Ember.CoreView.extend( var view = get(this, 'parentView'); while (view) { - if(view instanceof klass) { return view; } + if (view instanceof klass) { return view; } view = get(view, 'parentView'); } }, @@ -16592,7 +16666,7 @@ Ember.View = Ember.CoreView.extend( function(view) { return klass.detect(view.constructor); }; while (view) { - if( isOfType(view) ) { return view; } + if (isOfType(view)) { return view; } view = get(view, 'parentView'); } }, @@ -16625,7 +16699,7 @@ Ember.View = Ember.CoreView.extend( var view = get(this, 'parentView'); while (view) { - if(get(view, 'parentView') instanceof klass) { return view; } + if (get(view, 'parentView') instanceof klass) { return view; } view = get(view, 'parentView'); } }, @@ -17148,7 +17222,7 @@ Ember.View = Ember.CoreView.extend( */ invokeRecursively: function(fn, includeSelf) { var childViews = (includeSelf === false) ? this._childViews : [this]; - var currentViews, view; + var currentViews, view, currentChildViews; while (childViews.length) { currentViews = childViews.slice(); @@ -17156,16 +17230,17 @@ Ember.View = Ember.CoreView.extend( for (var i=0, l=currentViews.length; isomeString
') + * ``` + * + * @method htmlSafe + * @for Ember.String + * @static + * @return {Handlebars.SafeString} a string that will not be html escaped by Handlebars + */ Ember.String.htmlSafe = function(str) { return new Handlebars.SafeString(str); }; @@ -20495,11 +20645,18 @@ var htmlSafe = Ember.String.htmlSafe; if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) { /** - See `Ember.String.htmlSafe`. - - @method htmlSafe - @for String - */ + * Mark a string as being safe for unescaped output with Handlebars. + * + * ```javascript + * '
someString
'.htmlSafe() + * ``` + * + * See `Ember.String.htmlSafe`. + * + * @method htmlSafe + * @for String + * @return {Handlebars.SafeString} a string that will not be html escaped by Handlebars + */ String.prototype.htmlSafe = function() { return htmlSafe(this); }; @@ -20604,7 +20761,6 @@ var DOMManager = { /** @class _Metamorph @namespace Ember - @extends Ember.Mixin @private */ Ember._Metamorph = Ember.Mixin.create({ @@ -20999,7 +21155,7 @@ var forEach = Ember.ArrayPolyfills.forEach; var EmberHandlebars = Ember.Handlebars, helpers = EmberHandlebars.helpers; -function exists(value){ +function exists(value) { return !Ember.isNone(value); } @@ -21024,7 +21180,7 @@ function bind(property, options, preserveContext, shouldDisplay, valueNormalizer var template, context, result = handlebarsGet(currentContext, property, options); - result = valueNormalizer(result); + result = valueNormalizer ? valueNormalizer(result) : result; context = preserveContext ? currentContext : result; if (shouldDisplay(result)) { @@ -21349,7 +21505,7 @@ EmberHandlebars.registerHelper('unless', function(context, options) { ```javascript AView = Ember.View.extend({ - someProperty: function(){ + someProperty: function() { return "aValue"; }.property() }) @@ -21452,7 +21608,7 @@ EmberHandlebars.registerHelper('bindAttr', function(options) { var path = attrs[attr], normalized; - Ember.assert(fmt("You must provide a String for a bound attribute, not %@", [path]), typeof path === 'string'); + Ember.assert(fmt("You must provide an expression as the value of bound attribute. You specified: %@=%@", [attr, path]), typeof path === 'string'); normalized = normalizePath(ctx, path, options.data); @@ -22136,10 +22292,10 @@ Ember.Handlebars.registerHelper('collection', function(path, options) { if (hash.itemView) { var controller = data.keywords.controller; - Ember.assert('itemView given, but no container is available', controller && controller.container); + Ember.assert('You specified an itemView, but the current context has no container to look the itemView up in. This probably means that you created a view manually, instead of through the container. Instead, use container.lookup("view:viewName"), which will properly instantiate your view.', controller && controller.container); var container = controller.container; itemViewClass = container.resolve('view:' + Ember.String.camelize(hash.itemView)); - Ember.assert('itemView not found in container', !!itemViewClass); + Ember.assert('You specified the itemView ' + hash.itemView + ", but it was not found at " + container.describe("view:" + hash.itemView) + " (and it was not registered in the container)", !!itemViewClass); } else if (hash.itemViewClass) { itemViewClass = handlebarsGet(collectionPrototype, hash.itemViewClass, options); } else { @@ -22157,7 +22313,7 @@ Ember.Handlebars.registerHelper('collection', function(path, options) { if (hash.hasOwnProperty(prop)) { match = prop.match(/^item(.)(.*)$/); - if(match && prop !== 'itemController') { + if (match && prop !== 'itemController') { // Convert itemShouldFoo -> shouldFoo itemHash[match[1].toLowerCase() + match[2]] = hash[prop]; // Delete from hash as this will end up getting passed to the @@ -22184,7 +22340,7 @@ Ember.Handlebars.registerHelper('collection', function(path, options) { } if (emptyViewClass) { hash.emptyView = emptyViewClass; } - if(!hash.keyword){ + if (!hash.keyword) { itemHash._context = Ember.computed.alias('content'); } @@ -22231,7 +22387,7 @@ var handlebarsGet = Ember.Handlebars.get; Ember.Handlebars.registerHelper('unbound', function(property, fn) { var options = arguments[arguments.length - 1], helper, context, out; - if(arguments.length > 2) { + if (arguments.length > 2) { // Unbound helper call. options.data.isUnbound = true; helper = Ember.Handlebars.helpers[arguments[0]] || Ember.Handlebars.helperMissing; @@ -22289,7 +22445,7 @@ Ember.Handlebars.registerHelper('log', function(property, options) { @for Ember.Handlebars.helpers @param {String} property */ -Ember.Handlebars.registerHelper('debugger', function() { +Ember.Handlebars.registerHelper('debugger', function(options) { debugger; }); @@ -22335,6 +22491,11 @@ Ember.Handlebars.EachView = Ember.CollectionView.extend(Ember._Metamorph, { return this._super(); }, + _assertArrayLike: function(content) { + Ember.assert("The value that #each loops over must be an Array. You passed " + content.constructor + ", but it should have been an ArrayController", !Ember.ControllerMixin.detect(content) || (content && content.isGenerated) || content instanceof Ember.ArrayController); + Ember.assert("The value that #each loops over must be an Array. You passed " + ((Ember.ControllerMixin.detect(content) && content.get('model') !== undefined) ? ("" + content.get('model') + " (wrapped in " + content + ")") : ("" + content)), Ember.Array.detect(content)); + }, + disableContentObservers: function(callback) { Ember.removeBeforeObserver(this, 'content', null, '_contentWillChange'); Ember.removeObserver(this, 'content', null, '_contentDidChange'); @@ -22565,6 +22726,12 @@ GroupedEach.prototype = { ``` + If an `itemViewClass` is defined on the helper, and therefore the helper is not + being used as a block, an `emptyViewClass` can also be provided optionally. + The `emptyViewClass` will match the behavior of the `{{else}}` condition + described above. That is, the `emptyViewClass` will render if the collection + is empty. + ### 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 @@ -22578,7 +22745,7 @@ GroupedEach.prototype = { ```javascript App.DeveloperController = Ember.ObjectController.extend({ - isAvailableForHire: function(){ + isAvailableForHire: function() { return !this.get('content.isEmployed') && this.get('content.isSeekingWork'); }.property('isEmployed', 'isSeekingWork') }) @@ -22678,18 +22845,15 @@ Ember.Handlebars.registerHelper('each', function(path, options) { Ember.TEMPLATES["my_cool_template"] = Ember.Handlebars.compile('{{user}}'); ``` + @deprecated @method template @for Ember.Handlebars.helpers @param {String} templateName the template to render */ Ember.Handlebars.registerHelper('template', function(name, options) { - var view = options.data.view, - template = view.templateForName(name); - - Ember.assert("Unable to find template with name '"+name+"'.", !!template); - - template(this, { data: options.data }); + Ember.deprecate("The `template` helper has been deprecated in favor of the `partial` helper. Please use `partial` instead, which will work the same way."); + return Ember.Handlebars.helpers.partial.apply(this, arguments); }); })(); @@ -22739,7 +22903,6 @@ Ember.Handlebars.registerHelper('partial', function(name, options) { template = view.templateForName(underscoredName), deprecatedTemplate = !template && 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); template = template || deprecatedTemplate; @@ -22811,7 +22974,7 @@ var get = Ember.get, set = Ember.set; @return {String} HTML string */ Ember.Handlebars.registerHelper('yield', function(options) { - var view = options.data.view, template; + var currentView = options.data.view, view = currentView, template; while (view && !get(view, 'layout')) { view = get(view, 'parentView'); @@ -22821,7 +22984,48 @@ Ember.Handlebars.registerHelper('yield', function(options) { template = get(view, 'template'); - if (template) { template(this, options); } + var keywords = view._parentView.cloneKeywords(); + + currentView.appendChild(Ember.View, { + isVirtual: true, + tagName: '', + template: template, + context: get(view._parentView, 'context'), + controller: get(view._parentView, 'controller'), + templateData: {keywords: keywords} + }); +}); + +})(); + + + +(function() { +/** +@module ember +@submodule ember-handlebars +*/ + +/** + `loc` looks up the string in the localized strings hash. + This is a convenient way to localize text. For example: + + ```html + + ``` + + Take note that `welcome` is a string and not an object + reference. + + @method loc + @for Ember.Handlebars.helpers + @param {String} str The string to format +*/ + +Ember.Handlebars.registerHelper('loc', function(str) { + return Ember.String.loc(str); }); })(); @@ -22891,17 +23095,23 @@ Ember.Checkbox = Ember.View.extend({ tagName: 'input', - attributeBindings: ['type', 'checked', 'disabled', 'tabindex', 'name'], + attributeBindings: ['type', 'checked', 'indeterminate', 'disabled', 'tabindex', 'name'], type: "checkbox", checked: false, disabled: false, + indeterminate: false, init: function() { this._super(); this.on("change", this, this._updateElementValue); }, + didInsertElement: function() { + this._super(); + this.get('element').indeterminate = !!this.get('indeterminate'); + }, + _updateElementValue: function() { set(this, 'checked', this.$().prop('checked')); } @@ -22924,7 +23134,6 @@ var get = Ember.get, set = Ember.set; @class TextSupport @namespace Ember - @extends Ember.Mixin @private */ Ember.TextSupport = Ember.Mixin.create({ @@ -23936,9 +24145,9 @@ function program7(depth0,data) { content = get(this, 'content'), selection = get(this, 'selection'); - if (!content){ return; } + if (!content) { return; } if (options) { - var selectedIndexes = options.map(function(){ + var selectedIndexes = options.map(function() { return this.index - offset; }).toArray(); var newSelection = content.objectsAt(selectedIndexes); @@ -24027,7 +24236,7 @@ Ember.Handlebars.registerHelper('input', function(options) { if (inputType === 'checkbox') { return Ember.Handlebars.helpers.view.call(this, Ember.Checkbox, options); } else { - hash.type = inputType; + if (inputType) { hash.type = inputType; } hash.onEvent = onEvent || 'enter'; return Ember.Handlebars.helpers.view.call(this, Ember.TextField, options); } @@ -24283,7 +24492,7 @@ define("route-recognizer", results.push(new StarSegment(match[1])); names.push(match[1]); types.stars++; - } else if(segment === "") { + } else if (segment === "") { results.push(new EpsilonSegment()); } else { results.push(new StaticSegment(segment)); @@ -24695,7 +24904,7 @@ define("route-recognizer", (function() { define("router", - ["route-recognizer", "rsvp"], + ["route-recognizer","rsvp"], function(RouteRecognizer, RSVP) { "use strict"; /** @@ -25002,6 +25211,8 @@ define("router", if (!targetHandlerInfos) { return false; } + var recogHandlers = this.recognizer.handlersFor(targetHandlerInfos[targetHandlerInfos.length - 1].name); + for (var i=targetHandlerInfos.length-1; i>=0; i--) { handlerInfo = targetHandlerInfos[i]; if (handlerInfo.name === handlerName) { found = true; } @@ -25011,7 +25222,13 @@ define("router", if (handlerInfo.isDynamic) { object = contexts.pop(); - if (handlerInfo.context !== object) { return false; } + + if (isParam(object)) { + var recogHandler = recogHandlers[i], name = recogHandler.names[0]; + if (object.toString() !== this.currentParams[name]) { return false; } + } else if (handlerInfo.context !== object) { + return false; + } } } } @@ -25549,11 +25766,12 @@ define("router", log(router, seq, "Validation succeeded, finalizing transition;"); // Collect params for URL. - var objects = []; - for (var i = 0, len = handlerInfos.length; i < len; ++i) { + var objects = [], providedModels = transition.providedModelsArray.slice(); + for (var i = handlerInfos.length - 1; i>=0; --i) { var handlerInfo = handlerInfos[i]; if (handlerInfo.isDynamic) { - objects.push(handlerInfo.context); + var providedModel = providedModels.pop(); + objects.unshift(isParam(providedModel) ? providedModel.toString() : handlerInfo.context); } } @@ -25598,8 +25816,7 @@ define("router", handlerInfo = handlerInfos[index], handler = handlerInfo.handler, handlerName = handlerInfo.name, - seq = transition.sequence, - errorAlreadyHandled = false; + seq = transition.sequence; if (index < matchPoint) { log(router, seq, handlerName + ": using context from already-active handler"); @@ -25612,21 +25829,17 @@ define("router", return RSVP.resolve().then(handleAbort) .then(beforeModel) - .then(null, handleError) .then(handleAbort) .then(model) - .then(null, handleError) .then(handleAbort) .then(afterModel) - .then(null, handleError) .then(handleAbort) - .then(proceed); + .then(proceed) + .then(null, handleError); function handleAbort(result) { - - if (transition.isAborted) { + if (transition.isAborted) { log(transition.router, transition.sequence, "detected abort."); - errorAlreadyHandled = true; return RSVP.reject(new Router.TransitionAborted()); } @@ -25634,9 +25847,13 @@ define("router", } function handleError(reason) { + if (reason instanceof Router.TransitionAborted) { + // if the transition was aborted and *no additional* error was thrown, + // reject with the Router.TransitionAborted instance + return RSVP.reject(reason); + } - if (errorAlreadyHandled) { return RSVP.reject(reason); } - errorAlreadyHandled = true; + // otherwise, we're here because of a different error transition.abort(); log(router, seq, handlerName + ": handling error: " + reason); @@ -25657,13 +25874,15 @@ define("router", log(router, seq, handlerName + ": calling beforeModel hook"); - return handler.beforeModel && handler.beforeModel(transition); + var p = handler.beforeModel && handler.beforeModel(transition); + return (p instanceof Transition) ? null : p; } function model() { log(router, seq, handlerName + ": resolving model"); - return getModel(handlerInfo, transition, handlerParams[handlerName], index >= matchPoint); + var p = getModel(handlerInfo, transition, handlerParams[handlerName], index >= matchPoint); + return (p instanceof Transition) ? null : p; } function afterModel(context) { @@ -25675,7 +25894,9 @@ define("router", // always resolve with the original `context` object. transition.resolvedModels[handlerInfo.name] = context; - return handler.afterModel && handler.afterModel(context, transition); + + var p = handler.afterModel && handler.afterModel(context, transition); + return (p instanceof Transition) ? null : p; } function proceed() { @@ -25795,6 +26016,7 @@ define("router", return object; } + return Router; }); @@ -25896,9 +26118,8 @@ var get = Ember.get; @submodule ember-routing */ -Ember.controllerFor = function(container, controllerName, context, lookupOptions) { - return container.lookup('controller:' + controllerName, lookupOptions) || - Ember.generateController(container, controllerName, context); +Ember.controllerFor = function(container, controllerName, lookupOptions) { + return container.lookup('controller:' + controllerName, lookupOptions); }; /* Generates a controller automatically if none was provided. @@ -25912,22 +26133,26 @@ Ember.generateController = function(container, controllerName, context) { if (context && Ember.isArray(context)) { DefaultController = container.resolve('controller:array'); controller = DefaultController.extend({ - content: context + isGenerated: true }); } else if (context) { DefaultController = container.resolve('controller:object'); controller = DefaultController.extend({ - content: context + isGenerated: true }); } else { DefaultController = container.resolve('controller:basic'); - controller = DefaultController.extend(); + controller = DefaultController.extend({ + isGenerated: true + }); } controller.toString = function() { return "(generated " + controllerName + " controller)"; }; + controller.isGenerated = true; + fullName = 'controller:' + controllerName; container.register(fullName, controller); @@ -26030,11 +26255,7 @@ Ember.Router = Ember.Object.extend({ handleURL: function(url) { scheduleLoadingStateEntry(this); - var self = this; - - return this.router.handleURL(url).then(function() { - transitionCompleted(self); - }); + return this.router.handleURL(url).then(transitionCompleted); }, transitionTo: function() { @@ -26203,9 +26424,7 @@ function doTransition(router, method, args) { scheduleLoadingStateEntry(router); var transitionPromise = router.router[method].apply(router.router, args); - transitionPromise.then(function() { - transitionCompleted(router); - }); + transitionPromise.then(transitionCompleted); // We want to return the configurable promise object // so that callers of this function can use `.method()` on it, @@ -26239,7 +26458,8 @@ function exitLoadingState(router) { router._loadingStateActive = false; } -function transitionCompleted(router) { +function transitionCompleted(route) { + var router = route.router; router.notifyPropertyChange('url'); exitLoadingState(router); } @@ -26247,7 +26467,7 @@ function transitionCompleted(router) { Ember.Router.reopenClass({ map: function(callback) { var router = this.router; - if (!router){ + if (!router) { router = this.router = new Router(); router.callbacks = []; } @@ -26258,7 +26478,7 @@ Ember.Router.reopenClass({ var dsl = Ember.RouterDSL.map(function() { this.resource('application', { path: "/" }, function() { - for (var i=0; i < router.callbacks.length; i++){ + for (var i=0; i < router.callbacks.length; i++) { router.callbacks[i].call(this); } callback.call(this); @@ -26363,7 +26583,7 @@ Ember.Route = Ember.Object.extend({ }); ``` - You can also redirect elsewhere by calling + You can also redirect elsewhere by calling `this.transitionTo('elsewhere')` from within `willTransition`. Note that `willTransition` will not be fired for the redirecting `transitionTo`, since `willTransition` doesn't @@ -26378,8 +26598,8 @@ Ember.Route = Ember.Object.extend({ may throw an error, or return a promise that rejects, at which point an `error` event will be fired on the partially-entered routes, allowing for per-route error handling logic, or shared - error handling logic defined on a parent route. - + error handling logic defined on a parent route. + Here is an example of an error handler that will be invoked for rejected promises / thrown errors from the various hooks on the route, as well as any unhandled errors from child @@ -26397,9 +26617,9 @@ Ember.Route = Ember.Object.extend({ error: function(error, transition) { // Assuming we got here due to the error in `beforeModel`, // we can expect that error === "bad things!", - // but a promise model rejecting would also + // but a promise model rejecting would also // call this hook, as would any errors encountered - // in `afterModel`. + // in `afterModel`. // The `error` hook is also provided the failed // `transition`, which can be stored and later @@ -26411,9 +26631,9 @@ Ember.Route = Ember.Object.extend({ }); ``` - `error` events that bubble up all the way to `ApplicationRoute` + `error` events that bubble up all the way to `ApplicationRoute` will fire a default error handler that logs the error. You can - specify your own global default error handler by overriding the + specify your own global default error handler by overriding the `error` handler on `ApplicationRoute`: ```js @@ -26493,7 +26713,11 @@ Ember.Route = Ember.Object.extend({ @method setup */ setup: function(context) { - var controller = this.controllerFor(this.controllerName || this.routeName, context); + var controllerName = this.controllerName || this.routeName, + controller = this.controllerFor(controllerName, true); + if (!controller) { + controller = this.generateController(controllerName, context); + } // Assign the route's controller so that it can more easily be // referenced in event handlers @@ -26538,20 +26762,20 @@ Ember.Route = Ember.Object.extend({ 1) A decision can be made to redirect elsewhere without needing to resolve the model first. - 2) Any async operations need to occur first before the + 2) Any async operations need to occur first before the model is attempted to be resolved. This hook is provided the current `transition` attempt as a parameter, which can be used to `.abort()` the transition, save it for a later `.retry()`, or retrieve values set on it from a previous hook. You can also just call - `this.transitionTo` to another route to implicitly - abort the `transition`. + `this.transitionTo` to another route to implicitly + abort the `transition`. You can return a promise from this hook to pause the transition until the promise resolves (or rejects). This could - be useful, for instance, for retrieving async code from - the server that is required to enter a route. + be useful, for instance, for retrieving async code from + the server that is required to enter a route. ```js App.PostRoute = Ember.Route.extend({ @@ -26563,13 +26787,13 @@ Ember.Route = Ember.Object.extend({ }); ``` - If `App.Post` doesn't exist in the above example, + If `App.Post` doesn't exist in the above example, `beforeModel` will use jQuery's `getScript`, which returns a promise that resolves after the server has successfully retrieved and executed the code from the - server. Note that if an error were to occur, it would - be passed to the `error` hook on `Ember.Route`, but - it's also possible to handle errors specific to + server. Note that if an error were to occur, it would + be passed to the `error` hook on `Ember.Route`, but + it's also possible to handle errors specific to `beforeModel` right from within the hook (to distinguish from the shared error handling behavior of the `error` hook): @@ -26584,11 +26808,11 @@ Ember.Route = Ember.Object.extend({ // Note that the above transitionTo will implicitly // halt the transition. If you were to return - // nothing from this promise reject handler, + // nothing from this promise reject handler, // according to promise semantics, that would - // convert the reject into a resolve and the + // convert the reject into a resolve and the // transition would continue. To propagate the - // error so that it'd be handled by the `error` + // error so that it'd be handled by the `error` // hook, you would have to either return Ember.RSVP.reject(e); // or @@ -26600,18 +26824,18 @@ Ember.Route = Ember.Object.extend({ ``` @method beforeModel - @param {Transition} transition + @param {Transition} transition @return {Promise} if the value returned from this hook is a promise, the transition will pause until the transition - resolves. Otherwise, non-promise return values are not + resolves. Otherwise, non-promise return values are not utilized in any way. */ beforeModel: Ember.K, /** This hook is called after this route's model has resolved. - It follows identical async/promise semantics to `beforeModel` - but is provided the route's resolved model in addition to + It follows identical async/promise semantics to `beforeModel` + but is provided the route's resolved model in addition to the `transition`, and is therefore suited to performing logic that can only take place after the model has already resolved. @@ -26628,13 +26852,13 @@ Ember.Route = Ember.Object.extend({ Refer to documentation for `beforeModel` for a description of transition-pausing semantics when a promise is returned - from this hook. + from this hook. @method afterModel - @param {Transition} transition + @param {Transition} transition @return {Promise} if the value returned from this hook is a promise, the transition will pause until the transition - resolves. Otherwise, non-promise return values are not + resolves. Otherwise, non-promise return values are not utilized in any way. */ afterModel: function(resolvedModel, transition) { @@ -26681,7 +26905,7 @@ Ember.Route = Ember.Object.extend({ This hook follows the asynchronous/promise semantics described in the documentation for `beforeModel`. In particular, - if a promise returned from `model` fails, the error will be + if a promise returned from `model` fails, the error will be handled by the `error` hook on `Ember.Route`. @method model @@ -26792,7 +27016,7 @@ Ember.Route = Ember.Object.extend({ @method setupController */ - setupController: function(controller, context){ + setupController: function(controller, context) { if (controller && (context !== undefined)) { set(controller, 'model', context); } @@ -26810,29 +27034,42 @@ Ember.Route = Ember.Object.extend({ }); ``` - By default, the controller for `post` is the shared instance of - `App.PostController`. - @method controllerFor @param {String} name the name of the route - @param {Object} model the model associated with the route (optional) @return {Ember.Controller} */ - controllerFor: function(name, model) { + controllerFor: function(name, _skipAssert) { var container = this.router.container, controller = container.lookup('controller:' + name); - if (!controller) { - model = model || this.modelFor(name); - - Ember.assert("You are trying to look up a controller that you did not define, and for which Ember does not know the model.\n\nThis is not a controller for a route, so you must explicitly define the controller ("+this.router.namespace.toString() + "." + Ember.String.capitalize(Ember.String.camelize(name))+"Controller) or pass a model as the second parameter to `controllerFor`, so that Ember knows which type of controller to create for you.", model || this.container.lookup('route:' + name)); - - controller = Ember.generateController(container, name, model); - } + // NOTE: We're specifically checking that skipAssert is true, because according + // to the old API the second parameter was model. We do not want people who + // passed a model to skip the assertion. + Ember.assert("The controller "+name+" could not be found. Make sure the controller has been generated first. This will happen the first time the associated route is entered.", controller || _skipAssert === true); return controller; }, + /** + Generates a controller for a route. + + If the optional model is passed then the controller type is determined automatically, + e.g., an ArrayController for arrays. + + @method generateController + @param {String} name the name of the controller + @param {Object} model the model to infer the type of the controller (optional) + */ + generateController: function(name, model) { + var container = this.router.container; + + model = model || this.modelFor(name); + + Ember.assert("You are trying to look up a controller that you did not define, and for which Ember does not know the model.\n\nThis is not a controller for a route, so you must explicitly define the controller ("+this.router.namespace.toString() + "." + Ember.String.capitalize(Ember.String.camelize(name))+"Controller) or pass a model as the second parameter to `controllerFor`, so that Ember knows which type of controller to create for you.", model || this.container.lookup('route:' + name)); + + return Ember.generateController(container, name, model); + }, + /** Returns the current model for a given route. @@ -26932,6 +27169,8 @@ Ember.Route = Ember.Object.extend({ render: function(name, options) { Ember.assert("The name in the given arguments is undefined", arguments.length > 0 ? !Ember.isNone(arguments[0]) : true); + var namePassed = !!name; + if (typeof name === 'object' && !options) { options = name; name = this.routeName; @@ -26944,6 +27183,7 @@ Ember.Route = Ember.Object.extend({ template = container.lookup('template:' + name); if (!view && !template) { + Ember.assert("Could not find \"" + name + "\" template or view.", !namePassed); if (get(this.router, 'namespace.LOG_VIEW_LOOKUPS')) { Ember.Logger.info("Could not find \"" + name + "\" template or view. Nothing will be rendered", { fullName: 'template:' + name }); } @@ -27003,17 +27243,17 @@ Ember.Route = Ember.Object.extend({ willDestroy: function() { this.teardownViews(); }, - + teardownViews: function() { // Tear down the top level view if (this.teardownTopLevelView) { this.teardownTopLevelView(); } - + // Tear down any outlets rendered with 'into' var teardownOutletViews = this.teardownOutletViews || []; - a_forEach(teardownOutletViews, function(teardownOutletView) { + a_forEach(teardownOutletViews, function(teardownOutletView) { teardownOutletView(); }); - + delete this.teardownTopLevelView; delete this.teardownOutletViews; delete this.lastRenderedTemplate; @@ -27034,17 +27274,15 @@ function parentRoute(route) { } } -function parentTemplate(route, isRecursive) { +function parentTemplate(route) { var parent = parentRoute(route), template; if (!parent) { return; } - Ember.warn(fmt("The immediate parent route ('%@') did not render into the main outlet and the default 'into' option ('%@') may not be expected", [get(parent, 'routeName'), get(route, 'routeName')]), !isRecursive); - if (template = parent.lastRenderedTemplate) { return template; } else { - return parentTemplate(parent, true); + return parentTemplate(parent); } } @@ -27056,7 +27294,7 @@ function normalizeOptions(route, name, template, options) { options.template = template; options.LOG_VIEW_LOOKUPS = get(route.router, 'namespace.LOG_VIEW_LOOKUPS'); - Ember.assert("An outlet ("+options.outlet+") was specified but this view will render at the root level.", options.outlet === 'main' || options.into); + Ember.assert("An outlet ("+options.outlet+") was specified but was not found.", options.outlet === 'main' || options.into); var controller = options.controller, namedController; @@ -27196,7 +27434,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { function createPath(path) { var fullPath = 'paramsContext'; - if(path !== '') { + if (path !== '') { fullPath += '.' + path; } return fullPath; @@ -27206,7 +27444,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { `Ember.LinkView` renders an element whose `click` event triggers a transition of the application's instance of `Ember.Router` to a supplied route by name. - + Instances of `LinkView` will most likely be created through the `linkTo` Handlebars helper, but properties of this class can be overridden to customize application-wide behavior. @@ -27252,7 +27490,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { /** The CSS class to apply to a `LinkView`'s element when its `disabled` property is `true`. - + @property disabledClass @type String @default disabled @@ -27262,8 +27500,9 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { /** Determines whether the `LinkView` will trigger routing via - the `replaceWith` routing strategy. + the `replaceWith` routing strategy. + @property replace @type Boolean @default false **/ @@ -27274,7 +27513,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { /** By default the `{{linkTo}}` helper responds to the `click` event. You can override this globally by setting this property to your custom - event name. + event name. This is particularly useful on mobile when one wants to avoid the 300ms click delay using some sort of custom `tap` event. @@ -27319,11 +27558,11 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { var observer = function(object, path) { var notify = true, i; for(i=0; i < paths.length; i++) { - if(!get(this, paths[i])) { + if (!get(this, paths[i])) { notify = false; } } - if(notify) { + if (notify) { this.notifyPropertyChange('routeArgs'); } }; @@ -27335,10 +27574,10 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { /** @private - + Even though this isn't a virtual view, we want to treat it as if it is so that you can access the parent with {{view.prop}} - + @method concreteView **/ concreteView: Ember.computed(function() { @@ -27346,16 +27585,16 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { }).property('parentView'), /** - + Accessed as a classname binding to apply the `LinkView`'s `disabledClass` CSS `class` to the element when the link is disabled. - + When `true` interactions with the element will not trigger route changes. @property disabled */ disabled: Ember.computed(function(key, value) { if (value !== undefined) { this.set('_isDisabled', value); } - + return value ? this.get('disabledClass') : false; }), @@ -27412,12 +27651,12 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { event.preventDefault(); if (this.bubbles === false) { event.stopPropagation(); } - + if (get(this, '_isDisabled')) { return false; } - if (get(this, 'loading')) { + if (get(this, 'loading')) { Ember.Logger.warn("This linkTo's parameters are either not yet loaded or point to an invalid route."); - return false; + return false; } var router = get(this, 'router'), @@ -27432,7 +27671,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { routeArgs: Ember.computed(function() { - var router = get(this, 'router'), + var router = get(this, 'router'), namedRoute = get(this, 'namedRoute'), routeName; if (!namedRoute && this.namedRouteBinding) { @@ -27522,7 +27761,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { ``` - To override this option for your entire application, see + To override this option for your entire application, see "Overriding Application-wide Defaults". ### Handling `href` @@ -27569,7 +27808,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { ``` - To override this option for your entire application, see + To override this option for your entire application, see "Overriding Application-wide Defaults". ### Supplying a model @@ -27578,7 +27817,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { the model context of the linked route: ```javascript - App.Router.map(function(){ + App.Router.map(function() { this.resource("photoGallery", {path: "hamster-photos/:photo_id"}); }) ``` @@ -27603,8 +27842,8 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { route with the dynamic segments: ```javascript - App.Router.map(function(){ - this.resource("photoGallery", {path: "hamster-photos/:photo_id"}, function(){ + App.Router.map(function() { + this.resource("photoGallery", {path: "hamster-photos/:photo_id"}, function() { this.route("comment", {path: "comments/:comment_id"}); }); }); @@ -27656,7 +27895,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { params = [].slice.call(arguments, 1, -1); var hash = options.hash; - + if (options.types[0] === "ID") { if (Ember.ENV.HELPER_PARAM_LOOKUPS) { hash.namedRouteBinding = name; @@ -27772,7 +28011,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { } outletSource = options.data.view; - while (!(outletSource.get('template.isTop'))){ + while (!(outletSource.get('template.isTop'))) { outletSource = outletSource.get('_parentView'); } @@ -27852,7 +28091,8 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { controller = container.lookup('controller:' + controllerName, lookupOptions); Ember.assert("The controller name you supplied '" + controllerName + "' did not resolve to a controller.", !!controller); } else { - controller = Ember.controllerFor(container, name, context, lookupOptions); + controller = container.lookup('controller:' + name, lookupOptions) || + Ember.generateController(container, name, context); } if (controller && context) { @@ -28785,7 +29025,7 @@ Ember.HistoryLocation = Ember.Object.extend({ get(this, 'history').pushState(state, null, path); // store state if browser doesn't support `history.state` - if(!supportsHistoryState) { + if (!supportsHistoryState) { this._historyState = state; } @@ -28807,7 +29047,7 @@ Ember.HistoryLocation = Ember.Object.extend({ get(this, 'history').replaceState(state, null, path); // store state if browser doesn't support `history.state` - if(!supportsHistoryState) { + if (!supportsHistoryState) { this._historyState = state; } @@ -28830,7 +29070,7 @@ Ember.HistoryLocation = Ember.Object.extend({ Ember.$(window).on('popstate.ember-location-'+guid, function(e) { // Ignore initial page load popstate event in Chrome - if(!popstateFired) { + if (!popstateFired) { popstateFired = true; if (self.getURL() === self._previousURL) { return; } } @@ -29201,7 +29441,7 @@ Ember.DefaultResolver = Ember.Object.extend({ @protected @method resolveModel */ - resolveModel: function(parsedName){ + resolveModel: function(parsedName) { var className = classify(parsedName.name), factory = get(parsedName.root, className); @@ -29218,6 +29458,19 @@ Ember.DefaultResolver = Ember.Object.extend({ var className = classify(parsedName.name) + classify(parsedName.type), factory = get(parsedName.root, className); if (factory) { return factory; } + }, + + lookupDescription: function(name) { + var parsedName = this.parseName(name); + + if (parsedName.type === 'template') { + return "template at " + parsedName.fullNameWithoutType.replace(/\./g, '/'); + } + + var description = parsedName.root + "." + classify(parsedName.name); + if (parsedName.type !== 'model') { description += classify(parsedName.type); } + + return description; } }); @@ -29467,7 +29720,7 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin this.scheduleInitialize(); - if ( Ember.LOG_VERSION ) { + 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); @@ -29546,7 +29799,7 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin if (!this.$ || this.$.isReady) { Ember.run.schedule('actions', self, '_initialize'); } else { - this.$().ready(function(){ + this.$().ready(function() { Ember.run(self, '_initialize'); }); } @@ -29635,7 +29888,7 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin @param property {String} @param injectionName {String} **/ - inject: function(){ + inject: function() { var container = this.__container__; container.injection.apply(container, arguments); }, @@ -29651,7 +29904,7 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin @method initialize **/ - initialize: function(){ + initialize: function() { Ember.deprecate('Calling initialize manually is not supported. Please see Ember.Application#advanceReadiness and Ember.Application#deferReadiness'); }, /** @@ -29697,7 +29950,7 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin var App; - Ember.run(function(){ + Ember.run(function() { App = Ember.Application.create(); }); @@ -29707,11 +29960,11 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin } }); - test("first test", function(){ + test("first test", function() { // App is freshly reset }); - test("first test", function(){ + test("first test", function() { // App is again freshly reset }); ``` @@ -29726,7 +29979,7 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin var App; - Ember.run(function(){ + Ember.run(function() { App = Ember.Application.create(); }); @@ -29739,10 +29992,10 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin } }); - test("first test", function(){ + test("first test", function() { ok(true, 'something before app is initialized'); - Ember.run(function(){ + Ember.run(function() { App.advanceReadiness(); }); ok(true, 'something after app is initialized'); @@ -29762,7 +30015,7 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin this.buildContainer(); - Ember.run.schedule('actions', this, function(){ + Ember.run.schedule('actions', this, function() { this._initialize(); }); } @@ -29924,6 +30177,7 @@ Ember.Application.reopenClass({ container.set = Ember.set; container.normalize = normalize; container.resolver = resolverFor(namespace); + container.describe = container.resolver.describe; container.optionsForType('view', { singleton: false }); container.optionsForType('template', { instantiate: false }); container.register('application:main', namespace, { instantiate: false }); @@ -29967,9 +30221,16 @@ function resolverFor(namespace) { var resolver = resolverClass.create({ namespace: namespace }); - return function(fullName) { + + function resolve(fullName) { return resolver.resolve(fullName); + } + + resolve.describe = function(fullName) { + return resolver.lookupDescription(fullName); }; + + return resolve; } function normalize(fullName) { @@ -30068,11 +30329,11 @@ Ember.ControllerMixin.reopen({ needs: ['post'] }); ``` - + The application's single instance of these other controllers are accessible by name through the `controllers` property: - + ```javascript this.get('controllers.post'); // instance of App.PostController ``` @@ -30088,15 +30349,14 @@ Ember.ControllerMixin.reopen({ this._super.apply(this, arguments); // Structure asserts to still do verification but not string concat in production - if(!verifyDependencies(this)) { + if (!verifyDependencies(this)) { Ember.assert("Missing dependencies", false); } }, controllerFor: function(controllerName) { Ember.deprecate("Controller#controllerFor is deprecated, please use Controller#needs instead"); - var container = get(this, 'container'); - return container.lookup('controller:' + controllerName); + return Ember.controllerFor(get(this, 'container'), controllerName); }, controllers: Ember.computed(function() { @@ -30348,7 +30608,7 @@ Ember.State.reopenClass({ bManager = Ember.StateManager.create({ stateOne: Ember.State.create({ - changeToStateTwo: function(manager, context){ + changeToStateTwo: function(manager, context) { manager.transitionTo('stateTwo', context) } }), @@ -30676,7 +30936,7 @@ var sendEvent = function(eventName, sendRecursiveArguments, isUnhandledPass) { ```javascript managerC = Ember.StateManager.create({ - initialState: function(){ + initialState: function() { if (someLogic) { return 'active'; } else { @@ -30719,12 +30979,12 @@ var sendEvent = function(eventName, sendRecursiveArguments, isUnhandledPass) { robotManager = Ember.StateManager.create({ initialState: 'poweredDown', poweredDown: Ember.State.create({ - exit: function(stateManager){ + exit: function(stateManager) { console.log("exiting the poweredDown state") } }), poweredUp: Ember.State.create({ - enter: function(stateManager){ + enter: function(stateManager) { console.log("entering the poweredUp state. Destroy all humans.") } }) @@ -30747,12 +31007,12 @@ var sendEvent = function(eventName, sendRecursiveArguments, isUnhandledPass) { robotManager = Ember.StateManager.create({ initialState: 'poweredDown', poweredDown: Ember.State.create({ - exit: function(stateManager){ + exit: function(stateManager) { console.log("exiting the poweredDown state") } }), poweredUp: Ember.State.create({ - enter: function(stateManager){ + enter: function(stateManager) { console.log("entering the poweredUp state. Destroy all humans.") } }) @@ -30815,37 +31075,37 @@ var sendEvent = function(eventName, sendRecursiveArguments, isUnhandledPass) { robotManager = Ember.StateManager.create({ initialState: 'poweredDown', poweredDown: Ember.State.create({ - enter: function(){}, - exit: function(){ + enter: function() {}, + exit: function() { console.log("exited poweredDown state") }, charging: Ember.State.create({ - enter: function(){}, - exit: function(){} + enter: function() {}, + exit: function() {} }), charged: Ember.State.create({ - enter: function(){ + enter: function() { console.log("entered charged state") }, - exit: function(){ + exit: function() { console.log("exited charged state") } }) }), poweredUp: Ember.State.create({ - enter: function(){ + enter: function() { console.log("entered poweredUp state") }, - exit: function(){}, + exit: function() {}, mobile: Ember.State.create({ - enter: function(){ + enter: function() { console.log("entered mobile state") }, - exit: function(){} + exit: function() {} }), stationary: Ember.State.create({ - enter: function(){}, - exit: function(){} + enter: function() {}, + exit: function() {} }) }) }) @@ -30893,7 +31153,7 @@ var sendEvent = function(eventName, sendRecursiveArguments, isUnhandledPass) { initialState: 'stateOne.substateOne.subsubstateOne', stateOne: Ember.State.create({ substateOne: Ember.State.create({ - anAction: function(manager, context){ + anAction: function(manager, context) { console.log("an action was called") }, subsubstateOne: Ember.State.create({}) @@ -30956,7 +31216,7 @@ var sendEvent = function(eventName, sendRecursiveArguments, isUnhandledPass) { }) }), stateTwo: Ember.State.create({ - anAction: function(manager, context){ + anAction: function(manager, context) { // will not be called below because it is // not a parent of the current state } @@ -30977,18 +31237,18 @@ var sendEvent = function(eventName, sendRecursiveArguments, isUnhandledPass) { initialState: 'poweredDown.charging', poweredDown: Ember.State.create({ charging: Ember.State.create({ - chargeComplete: function(manager, context){ + chargeComplete: function(manager, context) { manager.transitionTo('charged') } }), charged: Ember.State.create({ - boot: function(manager, context){ + boot: function(manager, context) { manager.transitionTo('poweredUp') } }) }), poweredUp: Ember.State.create({ - beginExtermination: function(manager, context){ + beginExtermination: function(manager, context) { manager.transitionTo('rampaging') }, rampaging: Ember.State.create() @@ -31027,7 +31287,7 @@ var sendEvent = function(eventName, sendRecursiveArguments, isUnhandledPass) { bManager = Ember.StateManager.create({ stateOne: Ember.State.create({ - changeToStateTwo: function(manager, context){ + changeToStateTwo: function(manager, context) { manager.transitionTo('stateTwo', context) } }), @@ -31415,9 +31675,9 @@ Ember.Test = { For example: ```javascript - Ember.Test.registerHelper('boot', function(app)) { + Ember.Test.registerHelper('boot', function(app) { Ember.run(app, app.deferReadiness); - } + }); ``` This helper can later be called without arguments @@ -31548,6 +31808,8 @@ Ember.Application.reopen({ testHelpers: {}, setupForTesting: function() { + Ember.testing = true; + this.deferReadiness(); this.Router.reopen({ @@ -31618,7 +31880,7 @@ $(function() { $.event.special.click = { // For checkbox, fire native event so checked state will be right trigger: function() { - if ( $.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) { + if ($.nodeName( this, "input" ) && this.type === "checkbox" && this.click) { this.click(); return false; } @@ -31712,7 +31974,7 @@ Test.QUnitAdapter = Test.Adapter.extend({ start(); }, exception: function(error) { - ok(false, error); + ok(false, Ember.inspect(error)); } }); @@ -31767,6 +32029,19 @@ function click(app, selector, context) { return wait(app); } +function keyEvent(app, selector, context, type, keyCode) { + var $el; + if (typeof keyCode === 'undefined') { + keyCode = type; + type = context; + context = null; + } + $el = findWithAssert(app, selector, context); + var event = Ember.$.Event(type, { keyCode: keyCode }); + Ember.run($el, 'trigger', event); + return wait(app); +} + function fillIn(app, selector, context, text) { var $el; if (typeof text === 'undefined') { @@ -31783,7 +32058,7 @@ function fillIn(app, selector, context, text) { function findWithAssert(app, selector, context) { var $el = find(app, selector, context); if ($el.length === 0) { - throw("Element " + selector + " not found."); + throw new Error("Element " + selector + " not found."); } return $el; } @@ -31873,7 +32148,7 @@ function chain(app, promise, fn) { * Example: * * ``` -* visit('posts/index').then(function(){ +* visit('posts/index').then(function() { * // assert something * }); * ``` @@ -31891,24 +32166,43 @@ helper('visit', visit); * Example: * * ``` -* click('.some-jQuery-selector').then(function(){ +* click('.some-jQuery-selector').then(function() { * // assert something * }); * ``` * * @method click -* @param {String} selcetor jQuery selector for finding element on the DOM +* @param {String} selector jQuery selector for finding element on the DOM * @returns {RSVP.Promise} */ helper('click', click); +/** +* Simulates a key event, e.g. `keypress`, `keydown`, `keyup` with the desired keyCode +* +* Example: +* +* ``` +* keyEvent('.some-jQuery-selector', 'keypress', 13).then(function() { +* // assert something +* }); +* ``` +* +* @method keyEvent +* @param {String} selector jQuery selector for finding element on the DOM +* @param {String} the type of key event, e.g. `keypress`, `keydown`, `keyup` +* @param {Number} the keyCode of the simulated key event +* @returns {RSVP.Promise} +*/ +helper('keyEvent', keyEvent); + /** * Fills in an input element with some text. * * Example: * * ``` -* fillIn('#email', 'you@example.com').then(function(){ +* fillIn('#email', 'you@example.com').then(function() { * // assert something * }); * ```