From 01cdbc1d89d5e700b18a5a8680afba17a81bf73a Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Fri, 8 Mar 2013 16:53:59 +0100 Subject: [PATCH 1/3] Update ember and ember-data --- assets/scripts/vendor/ember-data.js | 480 +++++++++++++++++++++++++--- assets/scripts/vendor/ember.js | 269 ++++++++-------- 2 files changed, 570 insertions(+), 179 deletions(-) diff --git a/assets/scripts/vendor/ember-data.js b/assets/scripts/vendor/ember-data.js index 2fe225d5..4b83ba82 100644 --- a/assets/scripts/vendor/ember-data.js +++ b/assets/scripts/vendor/ember-data.js @@ -1,10 +1,10 @@ -// Last commit: 6d9eeaf (2013-02-20 22:27:54 +0100) +// Last commit: 0c516e4 (2013-03-08 15:59:48 +0100) (function() { window.DS = Ember.Namespace.create({ - // this one goes to 11 - CURRENT_API_REVISION: 11 + // this one goes past 11 + CURRENT_API_REVISION: 12 }); })(); @@ -849,7 +849,7 @@ DS._Mappable = Ember.Mixin.create({ instanceMap.set(transformedKey, newValue); } - }, + } }); @@ -2271,7 +2271,6 @@ DS.Store = Ember.Object.extend(DS._Mappable, { } var content = get(array, 'content'); - var alreadyInArray = content.indexOf(clientId) !== -1; var recordArrays = this.recordArraysForClientId(clientId); var reference = this.referenceForClientId(clientId); @@ -2416,7 +2415,6 @@ DS.Store = Ember.Object.extend(DS._Mappable, { if (prematerialized && prematerialized.id) { id = prematerialized.id; } else if (id === undefined) { - var adapter = this.adapterForType(type); id = this.preprocessData(type, data); } @@ -2495,6 +2493,8 @@ DS.Store = Ember.Object.extend(DS._Mappable, { clientIds = typeMap.clientIds, cidToData = this.clientIdToData; + Ember.assert('The id ' + id + ' has already been used with another record of type ' + type.toString() + '.', !id || !idToClientIdMap[id]); + var clientId = ++this.clientIdCounter; cidToData[clientId] = data; @@ -2521,7 +2521,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, { this.recordCache[clientId] = record = type._create({ store: this, - clientId: clientId, + clientId: clientId }); set(record, 'id', id); @@ -3836,6 +3836,7 @@ var storeAlias = function(methodName) { args = [].slice.call(arguments); args.unshift(this); + Ember.assert("Your application does not have a 'Store' property defined. Attempts to call '" + methodName + "' on model classes will fail. Please provide one as with 'YourAppName.Store = DS.Store.extend()'", !!store); return store[methodName].apply(store, args); }; }; @@ -3844,6 +3845,7 @@ DS.Model.reopenClass({ isLoaded: storeAlias('recordIsLoaded'), find: storeAlias('find'), all: storeAlias('all'), + query: storeAlias('findQuery'), filter: storeAlias('filter'), _create: DS.Model.create, @@ -4193,7 +4195,6 @@ DS.Model.reopenClass({ App.Blog = DS.Model.extend({ users: DS.hasMany(App.User), owner: DS.belongsTo(App.User), - posts: DS.hasMany(App.Post) }); @@ -4269,6 +4270,51 @@ DS.Model.reopenClass({ return names; }), + /** + An array of types directly related to a model. Each type will be + included once, regardless of the number of relationships it has with + the model. + + For example, given a model with this definition: + + App.Blog = DS.Model.extend({ + users: DS.hasMany(App.User), + owner: DS.belongsTo(App.User), + posts: DS.hasMany(App.Post) + }); + + This property would contain the following: + + var relatedTypes = Ember.get(App.Blog, 'relatedTypes'); + //=> [ App.User, App.Post ] + + @type Ember.Array + @readOnly + */ + relatedTypes: Ember.computed(function() { + var type, + types = Ember.A([]); + + // Loop through each computed property on the class, + // and create an array of the unique types involved + // in relationships + this.eachComputedProperty(function(name, meta) { + if (meta.isRelationship) { + type = meta.type; + + if (typeof type === 'string') { + type = get(this, type, false) || get(Ember.lookup, type); + } + + if (!types.contains(type)) { + types.push(type); + } + } + }); + + return types; + }), + /** A map whose keys are the relationships of a model and whose values are relationship descriptors. @@ -4370,6 +4416,21 @@ DS.Model.reopenClass({ get(this, 'relationshipsByName').forEach(function(name, relationship) { callback.call(binding, name, relationship); }); + }, + + /** + Given a callback, iterates over each of the types related to a model, + invoking the callback with the related type's class. Each type will be + returned just once, regardless of how many different relationships it has + with a model. + + @param {Function} callback the callback to invoke + @param {any} binding the value to which the callback's `this` should be bound + */ + eachRelatedType: function(callback, binding) { + get(this, 'relatedTypes').forEach(function(type) { + callback.call(binding, type); + }); } }); @@ -5635,7 +5696,7 @@ DS.Serializer = Ember.Object.extend({ primaryKey: function(type) { // If the type is `BlogPost`, this will return // `blog_post_id`. - var typeString = type.toString.split(".")[1].underscore(); + var typeString = type.toString().split(".")[1].underscore(); return typeString + "_id"; } }); @@ -6310,6 +6371,13 @@ DS.JSONSerializer = DS.Serializer.extend({ if (sideloadAs) { this.sideloadMapping.set(sideloadAs, type); + + // Set a flag indicating that mappings may need to be normalized + // (i.e. converted from strings -> types) before sideloading. + // We can't do this conversion immediately here, because `configure` + // may be called before certain types have been defined. + this.sideloadMapping.normalized = false; + delete configuration.sideloadAs; } @@ -6471,47 +6539,88 @@ DS.JSONSerializer = DS.Serializer.extend({ } }, - sideload: function(loader, type, json, root) { - var sideloadedType, mappings, loaded = {}; + /** + @private - loaded[root] = true; + Iterates over the `json` payload and attempts to load any data + included alongside `root`. + + The keys expected for sideloaded data are based upon the types related + to the root model. Recursion is used to ensure that types related to + related types can be loaded as well. Any custom keys specified by + `sideloadAs` mappings will also be respected. + + @param {DS.Store subclass} loader + @param {DS.Model subclass} type + @param {Object} json + @param {String} root + */ + sideload: function(loader, type, json, root) { + var sideloadedType; + + this.normalizeSideloadMappings(); + this.configureSideloadMappingForType(type); for (var prop in json) { - if (!json.hasOwnProperty(prop)) { continue; } - if (prop === root) { continue; } - if (prop === this.configOption(type, 'meta')) { continue; } - - sideloadedType = type.typeForRelationship(prop); - - if (!sideloadedType) { - sideloadedType = this.sideloadMapping.get(prop); - - if (typeof sideloadedType === 'string') { - sideloadedType = get(Ember.lookup, sideloadedType); - } - - Ember.assert("Your server returned a hash with the key " + prop + " but you have no mapping for it", !!sideloadedType); + if (!json.hasOwnProperty(prop) || + prop === root || + prop === this.configOption(type, 'meta')) { + continue; } - this.sideloadRelationships(loader, sideloadedType, json, prop, loaded); + sideloadedType = this.sideloadMapping.get(prop); + Ember.assert("Your server returned a hash with the key " + prop + + " but you have no mapping for it", + !!sideloadedType); + + this.loadValue(loader, sideloadedType, json[prop]); } }, - sideloadRelationships: function(loader, type, json, prop, loaded) { - if (loaded[prop]) { return; } - loaded[prop] = true; + /** + @private - get(type, 'relationshipsByName').forEach(function(key, meta) { - key = meta.key || key; - if (meta.kind === 'belongsTo') { - key = this.pluralize(key); - } - if (json[key]) { - this.sideloadRelationships(loader, meta.type, json, key, loaded); + Iterates over all the `sideloadAs` mappings and converts any that are + strings to their equivalent types. + + This is an optimization used to avoid performing lookups for every + call to `sideload`. + */ + normalizeSideloadMappings: function() { + if (! this.sideloadMapping.normalized) { + this.sideloadMapping.forEach(function(key, value) { + if (typeof value === 'string') { + this.sideloadMapping.set(key, get(Ember.lookup, value)); + } + }, this); + this.sideloadMapping.normalized = true; + } + }, + + /** + @private + + Configures possible sideload mappings for the types related to a + particular model. This recursive method ensures that sideloading + works for related models as well. + + @param {DS.Model subclass} type + @param {Ember.A} configured an array of types that have already been configured + */ + configureSideloadMappingForType: function(type, configured) { + if (!configured) {configured = Ember.A([]);} + configured.pushObject(type); + + type.eachRelatedType(function(relatedType) { + if (!configured.contains(relatedType)) { + var root = this.sideloadMappingForType(relatedType); + if (!root) { + root = this.defaultSideloadRootForType(relatedType); + this.sideloadMapping.set(root, relatedType); + } + this.configureSideloadMappingForType(relatedType, configured); } }, this); - - this.loadValue(loader, type, json[prop]); }, loadValue: function(loader, type, value) { @@ -6551,6 +6660,18 @@ DS.JSONSerializer = DS.Serializer.extend({ } }, + /** + @private + + Determines the singular root name for a particular type. + + This is an underscored, lowercase version of the model name. + For example, the type `App.UserGroup` will have the root + `user_group`. + + @param {DS.Model subclass} type + @returns {String} name of the root element + */ rootForType: function(type) { var typeString = type.toString(); @@ -6560,6 +6681,34 @@ DS.JSONSerializer = DS.Serializer.extend({ var parts = typeString.split("."); var name = parts[parts.length - 1]; return name.replace(/([A-Z])/g, '_$1').toLowerCase().slice(1); + }, + + /** + @private + + Determines the root name mapped to a particular sideloaded type. + + @param {DS.Model subclass} type + @returns {String} name of the root element, if any is registered + */ + sideloadMappingForType: function(type) { + this.sideloadMapping.forEach(function(key, value) { + if (type === value) { + return key; + } + }); + }, + + /** + @private + + The default root name for a particular sideloaded type. + + @param {DS.Model subclass} type + @returns {String} name of the root element + */ + defaultSideloadRootForType: function(type) { + return this.pluralize(this.rootForType(type)); } }); @@ -6731,7 +6880,6 @@ DS.Adapter = Ember.Object.extend(DS._Mappable, { if (payload) { var loader = DS.loaderFor(store); - var serializer = get(this, 'serializer'); loader.load = function(type, data, prematerialized) { store.updateId(record, data); @@ -7293,6 +7441,27 @@ DS.FixtureSerializer = DS.Serializer.extend({ return value; }, + addId: function(data, key, id) { + data[key] = id; + }, + + addAttribute: function(hash, key, value) { + hash[key] = value; + }, + + addBelongsTo: function(hash, record, key, relationship) { + var id = get(record, relationship.key+'.id'); + if (!Ember.isNone(id)) { hash[key] = id; } + }, + + addHasMany: function(hash, record, key, relationship) { + var ids = get(record, relationship.key).map(function(item) { + return item.get('id'); + }); + + hash[relationship.key] = ids; + }, + /** @private @@ -7356,7 +7525,7 @@ DS.FixtureSerializer = DS.Serializer.extend({ (function() { -var get = Ember.get; +var get = Ember.get, fmt = Ember.String.fmt; /** `DS.FixtureAdapter` is an adapter that loads records from memory. @@ -7384,7 +7553,7 @@ DS.FixtureAdapter = DS.Adapter.extend({ var fixtures = Ember.A(type.FIXTURES); return fixtures.map(function(fixture){ if(!fixture.id){ - throw new Error('the id property must be defined for fixture %@'.fmt(fixture)); + throw new Error(fmt('the id property must be defined for fixture %@', [fixture])); } fixture.id = fixture.id + ''; return fixture; @@ -7397,7 +7566,19 @@ DS.FixtureAdapter = DS.Adapter.extend({ Implement this method in order to query fixtures data */ queryFixtures: function(fixtures, query, type) { - return fixtures; + Ember.assert('Not implemented: You must override the DS.FixtureAdapter::queryFixtures method to support querying the fixture store.'); + }, + + updateFixtures: function(type, fixture) { + if(!type.FIXTURES) { + type.FIXTURES = []; + } + + var fixtures = type.FIXTURES; + + this.deleteLoadedFixture(type, fixture); + + fixtures.push(fixture); }, /* @@ -7418,7 +7599,7 @@ DS.FixtureAdapter = DS.Adapter.extend({ var fixtures = this.fixturesForType(type), fixture; - Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures); + Ember.warn("Unable to find fixtures for model type " + type.toString(), fixtures); if (fixtures) { fixture = Ember.A(fixtures).findProperty('id', id); @@ -7476,7 +7657,7 @@ DS.FixtureAdapter = DS.Adapter.extend({ createRecord: function(store, type, record) { var fixture = this.mockJSON(type, record); - fixture.id = this.generateIdForRecord(store, record); + this.updateFixtures(type, fixture); this.simulateRemoteCall(function() { this.didCreateRecord(store, type, record, fixture); @@ -7486,12 +7667,18 @@ DS.FixtureAdapter = DS.Adapter.extend({ updateRecord: function(store, type, record) { var fixture = this.mockJSON(type, record); + this.updateFixtures(type, fixture); + this.simulateRemoteCall(function() { this.didUpdateRecord(store, type, record, fixture); }, this); }, deleteRecord: function(store, type, record) { + var fixture = this.mockJSON(type, record); + + this.deleteLoadedFixture(type, fixture); + this.simulateRemoteCall(function() { this.didDeleteRecord(store, type, record); }, this); @@ -7500,15 +7687,44 @@ DS.FixtureAdapter = DS.Adapter.extend({ /* @private */ - simulateRemoteCall: function(callback, context) { - function response() { - Ember.run(context, callback); - } + deleteLoadedFixture: function(type, record) { + var id = this.extractId(type, record); + var existingFixture = this.findExistingFixture(type, record); + + if(existingFixture) { + var index = type.FIXTURES.indexOf(existingFixture); + type.FIXTURES.splice(index, 1); + return true; + } + }, + + findExistingFixture: function(type, record) { + var fixtures = this.fixturesForType(type); + var id = this.extractId(type, record); + + return this.findFixtureById(fixtures, id); + }, + + findFixtureById: function(fixtures, id) { + var adapter = this; + + return Ember.A(fixtures).find(function(r) { + if(''+get(r, 'id') === ''+id) { + return true; + } else { + return false; + } + }); + }, + + simulateRemoteCall: function(callback, context) { if (get(this, 'simulateRemoteResponse')) { - setTimeout(response, get(this, 'latency')); + // Schedule with setTimeout + Ember.run.later(context, callback, get(this, 'latency')); } else { - response(); + // Asynchronous, but at the of the runloop with zero latency + Ember.run.once(context, callback); } } }); @@ -7919,6 +8135,168 @@ DS.RESTAdapter = DS.Adapter.extend({ +(function() { +var camelize = Ember.String.camelize, + get = Ember.get, + registeredTransforms; + +var passthruTransform = { + serialize: function(value) { return value; }, + deserialize: function(value) { return value; } +}; + +var defaultTransforms = { + string: passthruTransform, + boolean: passthruTransform, + number: passthruTransform +}; + +function camelizeKeys(json) { + var value; + + for (var prop in json) { + value = json[prop]; + delete json[prop]; + json[camelize(prop)] = value; + } +} + +function munge(json, callback) { + callback(json); +} + +function applyTransforms(json, type, transformType) { + var transforms = registeredTransforms[transformType]; + + Ember.assert("You are trying to apply the '" + transformType + "' transforms, but you didn't register any transforms with that name", transforms); + + get(type, 'attributes').forEach(function(name, attribute) { + var attributeType = attribute.type, + value = json[name]; + + var transform = transforms[attributeType] || defaultTransforms[attributeType]; + + Ember.assert("Your model specified the '" + attributeType + "' type for the '" + name + "' attribute, but no transform for that type was registered", transform); + + json[name] = transform.deserialize(value); + }); +} + +function ObjectProcessor(json, type, store) { + this.json = json; + this.type = type; + this.store = store; +} + +ObjectProcessor.prototype = { + load: function() { + this.store.load(this.type, {}, this.json); + }, + + camelizeKeys: function() { + camelizeKeys(this.json); + return this; + }, + + munge: function(callback) { + munge(this.json, callback); + return this; + }, + + applyTransforms: function(transformType) { + applyTransforms(this.json, this.type, transformType); + return this; + } +}; + +function processorFactory(store, type) { + return function(json) { + return new ObjectProcessor(json, type, store); + }; +} + +function ArrayProcessor(json, type, array, store) { + this.json = json; + this.type = type; + this.array = array; + this.store = store; +} + +ArrayProcessor.prototype = { + load: function() { + var store = this.store, + type = this.type; + + var references = this.json.map(function(object) { + return store.load(type, {}, object); + }); + + this.array.load(references); + }, + + camelizeKeys: function() { + this.json.forEach(camelizeKeys); + return this; + }, + + munge: function(callback) { + this.json.forEach(function(object) { + munge(object, callback); + }); + return this; + }, + + applyTransforms: function(transformType) { + var type = this.type; + + this.json.forEach(function(object) { + applyTransforms(object, type, transformType); + }); + + return this; + } +}; + +function arrayProcessorFactory(store, type, array) { + return function(json) { + return new ArrayProcessor(json, type, array, store); + }; +} + +DS.BasicAdapter = DS.Adapter.extend({ + find: function(store, type, id) { + var sync = type.sync; + + Ember.assert("You are trying to use the BasicAdapter to find id '" + id + "' of " + type + " but " + type + ".sync was not found", sync); + Ember.assert("The sync code on " + type + " does not implement find(), but you are trying to find id '" + id + "'.", sync.find); + + sync.find(id, processorFactory(store, type)); + }, + + findQuery: function(store, type, query, recordArray) { + var sync = type.sync; + + Ember.assert("You are trying to use the BasicAdapter to query " + type + " but " + type + ".sync was not found", sync); + Ember.assert("The sync code on " + type + " does not implement query(), but you are trying to query " + type + ".", sync.query); + + sync.query(query, arrayProcessorFactory(store, type, recordArray)); + } +}); + +DS.registerTransforms = function(kind, object) { + registeredTransforms[kind] = object; +}; + +DS.clearTransforms = function() { + registeredTransforms = {}; +}; + +DS.clearTransforms(); + +})(); + + + (function() { })(); diff --git a/assets/scripts/vendor/ember.js b/assets/scripts/vendor/ember.js index 12145e89..3df0a8a4 100644 --- a/assets/scripts/vendor/ember.js +++ b/assets/scripts/vendor/ember.js @@ -1,5 +1,5 @@ -// Version: v1.0.0-rc.1-110-g1446ad7 -// Last commit: 1446ad7 (2013-03-07 19:07:55 +0100) +// Version: v1.0.0-rc.1-134-gf13ef4c +// Last commit: f13ef4c (2013-03-08 16:46:38 +0100) (function() { @@ -150,8 +150,8 @@ Ember.deprecateFunc = function(message, func) { })(); -// Version: v1.0.0-rc.1-110-g1446ad7 -// Last commit: 1446ad7 (2013-03-07 19:07:55 +0100) +// Version: v1.0.0-rc.1-134-gf13ef4c +// Last commit: f13ef4c (2013-03-08 16:46:38 +0100) (function() { @@ -418,6 +418,58 @@ Ember.merge = function(original, updates) { } }; +/** + Returns true if the passed value is null or undefined. This avoids errors + from JSLint complaining about use of ==, which can be technically + confusing. + + ```javascript + Ember.isNone(); // true + Ember.isNone(null); // true + Ember.isNone(undefined); // true + Ember.isNone(''); // false + Ember.isNone([]); // false + Ember.isNone(function(){}); // false + ``` + + @method isNone + @for Ember + @param {Object} obj Value to test + @return {Boolean} +*/ +Ember.isNone = function(obj) { + return obj === null || obj === undefined; +}; +Ember.none = Ember.deprecateFunc("Ember.none is deprecated. Please use Ember.isNone instead.", Ember.isNone); + +/** + Verifies that a value is `null` or an empty string, empty array, + or empty function. + + Constrains the rules on `Ember.isNone` by returning false for empty + string and empty arrays. + + ```javascript + Ember.isEmpty(); // true + Ember.isEmpty(null); // true + Ember.isEmpty(undefined); // true + Ember.isEmpty(''); // true + Ember.isEmpty([]); // true + Ember.isEmpty('Adam Hawkins'); // false + Ember.isEmpty([0,1,2]); // false + ``` + + @method isEmpty + @for Ember + @param {Object} obj Value to test + @return {Boolean} +*/ +Ember.isEmpty = function(obj) { + return obj === null || obj === undefined || (obj.length === 0 && typeof obj !== 'function') || (typeof obj === 'object' && Ember.get(obj, 'length') === 0); +}; +Ember.empty = Ember.deprecateFunc("Ember.empty is deprecated. Please use Ember.isEmpty instead.", Ember.isEmpty) ; + + })(); @@ -3615,6 +3667,18 @@ Ember.computed.not = function(dependentKey) { }); }; +/** + @method computed.none + @for Ember + @param {String} dependentKey +*/ +Ember.computed.none = function(dependentKey) { + return Ember.computed(dependentKey, function(key) { + var val = get(this, dependentKey); + return Ember.isNone(val); + }); +}; + /** @method computed.empty @for Ember @@ -3623,7 +3687,7 @@ Ember.computed.not = function(dependentKey) { Ember.computed.empty = function(dependentKey) { return Ember.computed(dependentKey, function(key) { var val = get(this, dependentKey); - return val === undefined || val === null || val === '' || (Ember.isArray(val) && get(val, 'length') === 0); + return Ember.isEmpty(val); }); }; @@ -4273,7 +4337,7 @@ var run = Ember.run; /** Begins a new RunLoop. Any deferred actions invoked after the begin will be buffered until you invoke a matching call to `Ember.run.end()`. This is - an lower-level way to use a RunLoop instead of using `Ember.run()`. + a lower-level way to use a RunLoop instead of using `Ember.run()`. ```javascript Ember.run.begin(); @@ -4319,9 +4383,9 @@ Ember.run.end = function() { @property queues @type Array - @default ['sync', 'actions', 'destroy', 'timers'] + @default ['sync', 'actions', 'destroy'] */ -Ember.run.queues = ['sync', 'actions', 'destroy', 'timers']; +Ember.run.queues = ['sync', 'actions', 'destroy']; /** Adds the passed target/method and any optional arguments to the named @@ -4334,19 +4398,19 @@ Ember.run.queues = ['sync', 'actions', 'destroy', 'timers']; the `run.queues` property. ```javascript - Ember.run.schedule('timers', this, function(){ - // this will be executed at the end of the RunLoop, when timers are run - console.log("scheduled on timers queue"); + Ember.run.schedule('sync', this, function(){ + // this will be executed in the first RunLoop queue, when bindings are synced + console.log("scheduled on sync queue"); }); - Ember.run.schedule('sync', this, function(){ - // this will be executed at the end of the RunLoop, when bindings are synced - console.log("scheduled on sync queue"); + Ember.run.schedule('actions', this, function(){ + // this will be executed in the 'actions' queue, after bindings have synced. + console.log("scheduled on actions queue"); }); // Note the functions will be run in order based on the run queues order. Output would be: // scheduled on sync queue - // scheduled on timers queue + // scheduled on actions queue ``` @method schedule @@ -4372,7 +4436,7 @@ function autorun() { // Used by global test teardown Ember.run.hasScheduledTimers = function() { - return !!(scheduledAutorun || scheduledLater || scheduledNext); + return !!(scheduledAutorun || scheduledLater); }; // Used by global test teardown @@ -4385,10 +4449,6 @@ Ember.run.cancelTimers = function () { clearTimeout(scheduledLater); scheduledLater = null; } - if (scheduledNext) { - clearTimeout(scheduledNext); - scheduledNext = null; - } timers = {}; }; @@ -4423,7 +4483,8 @@ Ember.run.autorun = function() { bindings in the application to sync. You should call this method anytime you need any changed state to propagate - throughout the app immediately without repainting the UI. + throughout the app immediately without repainting the UI (which happens + in the later 'render' queue added by the `ember-views` package). ```javascript Ember.run.sync(); @@ -4443,25 +4504,30 @@ Ember.run.sync = function() { var timers = {}; // active timers... -var scheduledLater; +var scheduledLater, scheduledLaterExpires; function invokeLaterTimers() { scheduledLater = null; - var now = (+ new Date()), earliest = -1; - for (var key in timers) { - if (!timers.hasOwnProperty(key)) { continue; } - var timer = timers[key]; - if (timer && timer.expires) { - if (now >= timer.expires) { - delete timers[key]; - invoke(timer.target, timer.method, timer.args, 2); - } else { - if (earliest<0 || (timer.expires < earliest)) earliest=timer.expires; + run(function() { + var now = (+ new Date()), earliest = -1; + for (var key in timers) { + if (!timers.hasOwnProperty(key)) { continue; } + var timer = timers[key]; + if (timer && timer.expires) { + if (now >= timer.expires) { + delete timers[key]; + invoke(timer.target, timer.method, timer.args, 2); + } else { + if (earliest < 0 || (timer.expires < earliest)) { earliest = timer.expires; } + } } } - } - // schedule next timeout to fire... - if (earliest > 0) { scheduledLater = setTimeout(invokeLaterTimers, earliest-(+ new Date())); } + // schedule next timeout to fire when the earliest timer expires + if (earliest > 0) { + scheduledLater = setTimeout(invokeLaterTimers, earliest - now); + scheduledLaterExpires = earliest; + } + }); } /** @@ -4509,7 +4575,19 @@ Ember.run.later = function(target, method) { timer = { target: target, method: method, expires: expires, args: args }; guid = Ember.guidFor(timer); timers[guid] = timer; - run.once(timers, invokeLaterTimers); + + if(scheduledLater && expires < scheduledLaterExpires) { + // Cancel later timer (then reschedule earlier timer below) + clearTimeout(scheduledLater); + scheduledLater = null; + } + + if (!scheduledLater) { + // Schedule later timers to be run. + scheduledLater = setTimeout(invokeLaterTimers, wait); + scheduledLaterExpires = expires; + } + return guid; }; @@ -4596,22 +4674,9 @@ Ember.run.scheduleOnce = function(queue, target, method, args) { return scheduleOnce(queue, target, method, slice.call(arguments, 3)); }; -var scheduledNext; -function invokeNextTimers() { - scheduledNext = null; - for(var key in timers) { - if (!timers.hasOwnProperty(key)) { continue; } - var timer = timers[key]; - if (timer.next) { - delete timers[key]; - invoke(timer.target, timer.method, timer.args, 2); - } - } -} - /** Schedules an item to run after control has been returned to the system. - This is often equivalent to calling `setTimeout(function() {}, 1)`. + This is equivalent to calling `Ember.run.later` with a wait time of 1ms. ```javascript Ember.run.next(myContext, function(){ @@ -4627,20 +4692,10 @@ function invokeNextTimers() { @param {Object} [args*] Optional arguments to pass to the timeout. @return {Object} timer */ -Ember.run.next = function(target, method) { - var guid, - timer = { - target: target, - method: method, - args: slice.call(arguments), - next: true - }; - - guid = Ember.guidFor(timer); - timers[guid] = timer; - - if (!scheduledNext) { scheduledNext = setTimeout(invokeNextTimers, 1); } - return guid; +Ember.run.next = function() { + var args = slice.call(arguments); + args.push(1); // 1 millisecond wait + return run.later.apply(this, args); }; /** @@ -6465,57 +6520,6 @@ Ember.typeOf = function(item) { return ret; }; -/** - Returns true if the passed value is null or undefined. This avoids errors - from JSLint complaining about use of ==, which can be technically - confusing. - - ```javascript - Ember.isNone(); // true - Ember.isNone(null); // true - Ember.isNone(undefined); // true - Ember.isNone(''); // false - Ember.isNone([]); // false - Ember.isNone(function(){}); // false - ``` - - @method isNone - @for Ember - @param {Object} obj Value to test - @return {Boolean} -*/ -Ember.isNone = function(obj) { - return obj === null || obj === undefined; -}; -Ember.none = Ember.deprecateFunc("Ember.none is deprecated. Please use Ember.isNone instead.", Ember.isNone); - -/** - Verifies that a value is `null` or an empty string, empty array, - or empty function. - - Constrains the rules on `Ember.isNone` by returning false for empty - string and empty arrays. - - ```javascript - Ember.isEmpty(); // true - Ember.isEmpty(null); // true - Ember.isEmpty(undefined); // true - Ember.isEmpty(''); // true - Ember.isEmpty([]); // true - Ember.isEmpty('Adam Hawkins'); // false - Ember.isEmpty([0,1,2]); // false - ``` - - @method isEmpty - @for Ember - @param {Object} obj Value to test - @return {Boolean} -*/ -Ember.isEmpty = function(obj) { - return obj === null || obj === undefined || (obj.length === 0 && typeof obj !== 'function') || (typeof obj === 'object' && Ember.get(obj, 'length') === 0); -}; -Ember.empty = Ember.deprecateFunc("Ember.empty is deprecated. Please use Ember.isEmpty instead.", Ember.isEmpty) ; - /** This will compare two javascript values of possibly different types. It will tell you which one is greater than the other by returning: @@ -10555,7 +10559,7 @@ Ember.CoreObject = CoreObject; @submodule ember-runtime */ -var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor, none = Ember.isNone; +var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor, none = Ember.isNone, fmt = Ember.String.fmt; /** An unordered collection of objects. @@ -10997,7 +11001,7 @@ Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Emb for(idx = 0; idx < len; idx++) { array[idx] = this[idx]; } - return "Ember.Set<%@>".fmt(array.join(',')); + return fmt("Ember.Set<%@>", [array.join(',')]); } }); @@ -15097,6 +15101,10 @@ Ember.View = Ember.CoreView.extend( Appends the view's element to the document body. If the view does not have an HTML representation yet, `createElement()` will be called automatically. + + If your application uses the `rootElement` property, you must append + the view within that element. Rendering views outside of the `rootElement` + is not supported. Note that this method just schedules the view to be appended; the DOM element will not be appended to the document body until all bindings have @@ -16005,6 +16013,9 @@ Ember.View.applyAttributeBindings = function(elem, name, value) { elem.attr(name, value); } } else if (name === 'value' || type === 'boolean') { + // We can't set properties to undefined + if (value === undefined) { value = null; } + if (value !== elem.prop(name)) { // value and booleans should always be properties elem.prop(name, value); @@ -17616,7 +17627,7 @@ if(!Handlebars && typeof require === 'function') { Handlebars = require('handlebars'); } -Ember.assert("Ember Handlebars requires Handlebars 1.0.rc.3 or greater", Handlebars && Handlebars.VERSION.match(/^1\.0(\.0)?(\.|-)rc\.[23456789]+/)); +Ember.assert("Ember Handlebars requires Handlebars 1.0.0-rc.3 or greater", Handlebars && Handlebars.VERSION.match(/^1\.0\.[0-9](\.rc\.[23456789]+)?/)); /** Prepares the Handlebars templating library for use inside Ember's view @@ -23369,7 +23380,7 @@ Ember.Route = Ember.Object.extend({ namespace = this.router.namespace, modelClass = namespace[className]; - Ember.assert("You used the dynamic segment " + name + "_id in your router, but " + namespace + "." + className + " did not exist and you did not override your state's `model` hook.", modelClass); + Ember.assert("You used the dynamic segment " + name + "_id in your router, but " + namespace + "." + className + " did not exist and you did not override your route's `model` hook.", modelClass); return modelClass.find(value); }, @@ -24736,7 +24747,7 @@ Ember.Location.registerImplementation('hash', Ember.HashLocation); */ var get = Ember.get, set = Ember.set; -var popstateReady = false; +var popstateFired = false; /** Ember.HistoryLocation implements the location API using the browser's @@ -24750,6 +24761,7 @@ Ember.HistoryLocation = Ember.Object.extend({ init: function() { set(this, 'location', get(this, 'location') || window.location); + this._initialUrl = this.getURL(); this.initState(); }, @@ -24802,7 +24814,6 @@ Ember.HistoryLocation = Ember.Object.extend({ path = this.formatURL(path); if (this.getState() && this.getState().path !== path) { - popstateReady = true; this.pushState(path); } }, @@ -24820,7 +24831,6 @@ Ember.HistoryLocation = Ember.Object.extend({ path = this.formatURL(path); if (this.getState() && this.getState().path !== path) { - popstateReady = true; this.replaceState(path); } }, @@ -24874,8 +24884,10 @@ Ember.HistoryLocation = Ember.Object.extend({ self = this; Ember.$(window).bind('popstate.ember-location-'+guid, function(e) { - if(!popstateReady) { - return; + // Ignore initial page load popstate event in Chrome + if(!popstateFired) { + popstateFired = true; + if (self.getURL() === self._initialUrl) { return; } } callback(self.getURL()); }); @@ -25276,6 +25288,7 @@ var Application = Ember.Application = Ember.Namespace.extend({ } if ( Ember.LOG_VERSION ) { + Ember.LOG_VERSION = false; // we only need to see this once per Application#init Ember.debug('-------------------------------'); Ember.debug('Ember.VERSION : ' + Ember.VERSION); Ember.debug('Handlebars.VERSION : ' + Ember.Handlebars.VERSION); @@ -25722,11 +25735,11 @@ function normalize(fullName) { var result = name; if (result.indexOf('.') > -1) { - result = result.replace(/\.(.)/g, function(m) { return m[1].toUpperCase(); }); + result = result.replace(/\.(.)/g, function(m) { return m.charAt(1).toUpperCase(); }); } if (name.indexOf('_') > -1) { - result = result.replace(/_(.)/g, function(m) { return m[1].toUpperCase(); }); + result = result.replace(/_(.)/g, function(m) { return m.charAt(1).toUpperCase(); }); } return type + ':' + result; @@ -27082,8 +27095,8 @@ Ember States })(); -// Version: v1.0.0-rc.1-110-g1446ad7 -// Last commit: 1446ad7 (2013-03-07 19:07:55 +0100) +// Version: v1.0.0-rc.1-134-gf13ef4c +// Last commit: f13ef4c (2013-03-08 16:46:38 +0100) (function() { From 44f2133488c59378611398e83e68c49e0a7e691b Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Fri, 8 Mar 2013 16:58:02 +0100 Subject: [PATCH 2/3] Update handlebars --- assets/scripts/vendor/handlebars.js | 387 +++++++++++++++++++++------- 1 file changed, 298 insertions(+), 89 deletions(-) diff --git a/assets/scripts/vendor/handlebars.js b/assets/scripts/vendor/handlebars.js index 247a3fdf..9c653ee7 100644 --- a/assets/scripts/vendor/handlebars.js +++ b/assets/scripts/vendor/handlebars.js @@ -1,3 +1,27 @@ +/* + +Copyright (C) 2011 by Yehuda Katz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + // lib/handlebars/base.js /*jshint eqnull:true*/ @@ -5,7 +29,13 @@ this.Handlebars = {}; (function(Handlebars) { -Handlebars.VERSION = "1.0.rc.2"; +Handlebars.VERSION = "1.0.0-rc.3"; +Handlebars.COMPILER_REVISION = 2; + +Handlebars.REVISION_CHANGES = { + 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it + 2: '>= 1.0.0-rc.3' +}; Handlebars.helpers = {}; Handlebars.partials = {}; @@ -618,9 +648,13 @@ return new Parser; // lib/handlebars/compiler/base.js Handlebars.Parser = handlebars; -Handlebars.parse = function(string) { +Handlebars.parse = function(input) { + + // Just return if an already-compile AST was passed in. + if(input.constructor === Handlebars.AST.ProgramNode) { return input; } + Handlebars.Parser.yy = Handlebars.AST; - return Handlebars.Parser.parse(string); + return Handlebars.Parser.parse(input); }; Handlebars.print = function(ast) { @@ -702,8 +736,11 @@ Handlebars.print = function(ast) { for(var i=0,l=parts.length; i 0) { throw new Handlebars.Exception("Invalid path: " + this.original); } + else if (part === "..") { depth++; } + else { this.isScoped = true; } + } else { dig.push(part); } } @@ -853,6 +890,26 @@ Handlebars.JavaScriptCompiler = function() {}; return out.join("\n"); }, + equals: function(other) { + var len = this.opcodes.length; + if (other.opcodes.length !== len) { + return false; + } + + for (var i = 0; i < len; i++) { + var opcode = this.opcodes[i], + otherOpcode = other.opcodes[i]; + if (opcode.opcode !== otherOpcode.opcode || opcode.args.length !== otherOpcode.args.length) { + return false; + } + for (var j = 0; j < opcode.args.length; j++) { + if (opcode.args[j] !== otherOpcode.args[j]) { + return false; + } + } + } + return true; + }, guid: 0, @@ -944,7 +1001,7 @@ Handlebars.JavaScriptCompiler = function() {}; // evaluate it by executing `blockHelperMissing` this.opcode('pushProgram', program); this.opcode('pushProgram', inverse); - this.opcode('pushHash'); + this.opcode('emptyHash'); this.opcode('blockValue'); } else { this.ambiguousMustache(mustache, program, inverse); @@ -953,7 +1010,7 @@ Handlebars.JavaScriptCompiler = function() {}; // evaluate it by executing `blockHelperMissing` this.opcode('pushProgram', program); this.opcode('pushProgram', inverse); - this.opcode('pushHash'); + this.opcode('emptyHash'); this.opcode('ambiguousBlockValue'); } @@ -977,6 +1034,7 @@ Handlebars.JavaScriptCompiler = function() {}; this.opcode('assignToHash', pair[0]); } + this.opcode('popHash'); }, partial: function(partial) { @@ -1017,17 +1075,19 @@ Handlebars.JavaScriptCompiler = function() {}; }, ambiguousMustache: function(mustache, program, inverse) { - var id = mustache.id, name = id.parts[0]; + var id = mustache.id, + name = id.parts[0], + isBlock = program != null || inverse != null; this.opcode('getContext', id.depth); this.opcode('pushProgram', program); this.opcode('pushProgram', inverse); - this.opcode('invokeAmbiguous', name); + this.opcode('invokeAmbiguous', name, isBlock); }, - simpleMustache: function(mustache, program, inverse) { + simpleMustache: function(mustache) { var id = mustache.id; if (id.type === 'DATA') { @@ -1158,7 +1218,7 @@ Handlebars.JavaScriptCompiler = function() {}; if(mustache.hash) { this.hash(mustache.hash); } else { - this.opcode('pushHash'); + this.opcode('emptyHash'); } return params; @@ -1175,7 +1235,7 @@ Handlebars.JavaScriptCompiler = function() {}; if(mustache.hash) { this.hash(mustache.hash); } else { - this.opcode('pushHash'); + this.opcode('emptyHash'); } return params; @@ -1189,7 +1249,7 @@ Handlebars.JavaScriptCompiler = function() {}; JavaScriptCompiler.prototype = { // PUBLIC API: You can override these methods in a subclass to provide // alternative compiled forms for name lookup and buffering semantics - nameLookup: function(parent, name, type) { + nameLookup: function(parent, name /* , type*/) { if (/^[0-9]+$/.test(name)) { return parent + "[" + name + "]"; } else if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) { @@ -1204,7 +1264,11 @@ Handlebars.JavaScriptCompiler = function() {}; if (this.environment.isSimple) { return "return " + string + ";"; } else { - return "buffer += " + string + ";"; + return { + appendToBuffer: true, + content: string, + toString: function() { return "buffer += " + string + ";"; } + }; } }, @@ -1225,6 +1289,7 @@ Handlebars.JavaScriptCompiler = function() {}; this.isChild = !!context; this.context = context || { programs: [], + environments: [], aliases: { } }; @@ -1234,6 +1299,7 @@ Handlebars.JavaScriptCompiler = function() {}; this.stackVars = []; this.registers = { list: [] }; this.compileStack = []; + this.inlineStack = []; this.compileChildren(environment, options); @@ -1255,11 +1321,11 @@ Handlebars.JavaScriptCompiler = function() {}; }, nextOpcode: function() { - var opcodes = this.environment.opcodes, opcode = opcodes[this.i + 1]; + var opcodes = this.environment.opcodes; return opcodes[this.i + 1]; }, - eat: function(opcode) { + eat: function() { this.i = this.i + 1; }, @@ -1297,7 +1363,6 @@ Handlebars.JavaScriptCompiler = function() {}; // Generate minimizer alias mappings if (!this.isChild) { - var aliases = []; for (var alias in this.context.aliases) { this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; } @@ -1322,16 +1387,48 @@ Handlebars.JavaScriptCompiler = function() {}; params.push("depth" + this.environment.depths.list[i]); } + // Perform a second pass over the output to merge content when possible + var source = this.mergeSource(); + + if (!this.isChild) { + var revision = Handlebars.COMPILER_REVISION, + versions = Handlebars.REVISION_CHANGES[revision]; + source = "this.compilerInfo = ["+revision+",'"+versions+"'];\n"+source; + } + if (asObject) { - params.push(this.source.join("\n ")); + params.push(source); return Function.apply(this, params); } else { - var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n ' + this.source.join("\n ") + '}'; + var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n ' + source + '}'; Handlebars.log(Handlebars.logger.DEBUG, functionSource + "\n\n"); return functionSource; } }, + mergeSource: function() { + // WARN: We are not handling the case where buffer is still populated as the source should + // not have buffer append operations as their final action. + var source = '', + buffer; + for (var i = 0, len = this.source.length; i < len; i++) { + var line = this.source[i]; + if (line.appendToBuffer) { + if (buffer) { + buffer = buffer + '\n + ' + line.content; + } else { + buffer = line.content; + } + } else { + if (buffer) { + source += 'buffer += ' + buffer + ';\n '; + buffer = undefined; + } + source += line + '\n '; + } + } + return source; + }, // [blockValue] // @@ -1369,6 +1466,9 @@ Handlebars.JavaScriptCompiler = function() {}; var current = this.topStack(); params.splice(1, 0, current); + // Use the options value generated from the invocation + params[params.length-1] = 'options'; + this.source.push("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }"); }, @@ -1392,6 +1492,9 @@ Handlebars.JavaScriptCompiler = function() {}; // If `value` is truthy, or 0, it is coerced into a string and appended // Otherwise, the empty string is appended append: function() { + // Force anything that is inlined onto the stack so we don't have duplication + // when we examine local + this.flushInline(); var local = this.popStack(); this.source.push("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }"); if (this.environment.isSimple) { @@ -1406,15 +1509,9 @@ Handlebars.JavaScriptCompiler = function() {}; // // Escape `value` and append it to the buffer appendEscaped: function() { - var opcode = this.nextOpcode(), extra = ""; this.context.aliases.escapeExpression = 'this.escapeExpression'; - if(opcode && opcode.opcode === 'appendContent') { - extra = " + " + this.quotedString(opcode.args[0]); - this.eat(opcode); - } - - this.source.push(this.appendToBuffer("escapeExpression(" + this.popStack() + ")" + extra)); + this.source.push(this.appendToBuffer("escapeExpression(" + this.popStack() + ")")); }, // [getContext] @@ -1438,7 +1535,7 @@ Handlebars.JavaScriptCompiler = function() {}; // Looks up the value of `name` on the current context and pushes // it onto the stack. lookupOnContext: function(name) { - this.pushStack(this.nameLookup('depth' + this.lastContext, name, 'context')); + this.push(this.nameLookup('depth' + this.lastContext, name, 'context')); }, // [pushContext] @@ -1486,7 +1583,7 @@ Handlebars.JavaScriptCompiler = function() {}; // // Push the result of looking up `id` on the current data lookupData: function(id) { - this.pushStack(this.nameLookup('data', id, 'data')); + this.push(this.nameLookup('data', id, 'data')); }, // [pushStringParam] @@ -1509,13 +1606,25 @@ Handlebars.JavaScriptCompiler = function() {}; } }, - pushHash: function() { - this.push('{}'); + emptyHash: function() { + this.pushStackLiteral('{}'); if (this.options.stringParams) { this.register('hashTypes', '{}'); } }, + pushHash: function() { + this.hash = {values: [], types: []}; + }, + popHash: function() { + var hash = this.hash; + this.hash = undefined; + + if (this.options.stringParams) { + this.register('hashTypes', '{' + hash.types.join(',') + '}'); + } + this.push('{\n ' + hash.values.join(',\n ') + '\n }'); + }, // [pushString] // @@ -1534,7 +1643,8 @@ Handlebars.JavaScriptCompiler = function() {}; // // Push an expression onto the stack push: function(expr) { - this.pushStack(expr); + this.inlineStack.push(expr); + return expr; }, // [pushLiteral] @@ -1577,12 +1687,14 @@ Handlebars.JavaScriptCompiler = function() {}; invokeHelper: function(paramSize, name) { this.context.aliases.helperMissing = 'helpers.helperMissing'; - var helper = this.lastHelper = this.setupHelper(paramSize, name); - this.register('foundHelper', helper.name); + var helper = this.lastHelper = this.setupHelper(paramSize, name, true); - this.pushStack("foundHelper ? foundHelper.call(" + - helper.callParams + ") " + ": helperMissing.call(" + - helper.helperMissingParams + ")"); + this.push(helper.name); + this.replaceStack(function(name) { + return name + ' ? ' + name + '.call(' + + helper.callParams + ") " + ": helperMissing.call(" + + helper.helperMissingParams + ")"; + }); }, // [invokeKnownHelper] @@ -1594,7 +1706,7 @@ Handlebars.JavaScriptCompiler = function() {}; // so a `helperMissing` fallback is not required. invokeKnownHelper: function(paramSize, name) { var helper = this.setupHelper(paramSize, name); - this.pushStack(helper.name + ".call(" + helper.callParams + ")"); + this.push(helper.name + ".call(" + helper.callParams + ")"); }, // [invokeAmbiguous] @@ -1609,19 +1721,18 @@ Handlebars.JavaScriptCompiler = function() {}; // This operation emits more code than the other options, // and can be avoided by passing the `knownHelpers` and // `knownHelpersOnly` flags at compile-time. - invokeAmbiguous: function(name) { + invokeAmbiguous: function(name, helperCall) { this.context.aliases.functionType = '"function"'; - this.pushStackLiteral('{}'); - var helper = this.setupHelper(0, name); + this.pushStackLiteral('{}'); // Hash value + var helper = this.setupHelper(0, name, helperCall); var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper'); - this.register('foundHelper', helperName); var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context'); var nextStack = this.nextStack(); - this.source.push('if (foundHelper) { ' + nextStack + ' = foundHelper.call(' + helper.callParams + '); }'); + this.source.push('if (' + nextStack + ' = ' + helperName + ') { ' + nextStack + ' = ' + nextStack + '.call(' + helper.callParams + '); }'); this.source.push('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '.apply(depth0) : ' + nextStack + '; }'); }, @@ -1640,7 +1751,7 @@ Handlebars.JavaScriptCompiler = function() {}; } this.context.aliases.self = "this"; - this.pushStack("self.invokePartial(" + params.join(", ") + ")"); + this.push("self.invokePartial(" + params.join(", ") + ")"); }, // [assignToHash] @@ -1651,17 +1762,19 @@ Handlebars.JavaScriptCompiler = function() {}; // Pops a value and hash off the stack, assigns `hash[key] = value` // and pushes the hash back onto the stack. assignToHash: function(key) { - var value = this.popStack(); + var value = this.popStack(), + type; if (this.options.stringParams) { - var type = this.popStack(); + type = this.popStack(); this.popStack(); - this.source.push("hashTypes['" + key + "'] = " + type + ";"); } - var hash = this.topStack(); - - this.source.push(hash + "['" + key + "'] = " + value + ";"); + var hash = this.hash; + if (type) { + hash.types.push("'" + key + "': " + type); + } + hash.values.push("'" + key + "': (" + value + ")"); }, // HELPERS @@ -1675,11 +1788,27 @@ Handlebars.JavaScriptCompiler = function() {}; child = children[i]; compiler = new this.compiler(); - this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children - var index = this.context.programs.length; - child.index = index; - child.name = 'program' + index; - this.context.programs[index] = compiler.compile(child, options, this.context); + var index = this.matchExistingProgram(child); + + if (index == null) { + this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children + index = this.context.programs.length; + child.index = index; + child.name = 'program' + index; + this.context.programs[index] = compiler.compile(child, options, this.context); + this.context.environments[index] = child; + } else { + child.index = index; + child.name = 'program' + index; + } + } + }, + matchExistingProgram: function(child) { + for (var i = 0, len = this.context.environments.length; i < len; i++) { + var environment = this.context.environments[i]; + if (environment && environment.equals(child)) { + return i; + } } }, @@ -1723,57 +1852,111 @@ Handlebars.JavaScriptCompiler = function() {}; }, pushStackLiteral: function(item) { - this.compileStack.push(new Literal(item)); - return item; + return this.push(new Literal(item)); }, pushStack: function(item) { + this.flushInline(); + var stack = this.incrStack(); - this.source.push(stack + " = " + item + ";"); + if (item) { + this.source.push(stack + " = " + item + ";"); + } this.compileStack.push(stack); return stack; }, replaceStack: function(callback) { - var stack = this.topStack(), - item = callback.call(this, stack); + var prefix = '', + inline = this.isInline(), + stack; - // Prevent modification of the context depth variable. Through replaceStack - if (/^depth/.test(stack)) { - stack = this.nextStack(); + // If we are currently inline then we want to merge the inline statement into the + // replacement statement via ',' + if (inline) { + var top = this.popStack(true); + + if (top instanceof Literal) { + // Literals do not need to be inlined + stack = top.value; + } else { + // Get or create the current stack name for use by the inline + var name = this.stackSlot ? this.topStackName() : this.incrStack(); + + prefix = '(' + this.push(name) + ' = ' + top + '),'; + stack = this.topStack(); + } + } else { + stack = this.topStack(); } - this.source.push(stack + " = " + item + ";"); + var item = callback.call(this, stack); + + if (inline) { + if (this.inlineStack.length || this.compileStack.length) { + this.popStack(); + } + this.push('(' + prefix + item + ')'); + } else { + // Prevent modification of the context depth variable. Through replaceStack + if (!/^stack/.test(stack)) { + stack = this.nextStack(); + } + + this.source.push(stack + " = (" + prefix + item + ");"); + } return stack; }, - nextStack: function(skipCompileStack) { - var name = this.incrStack(); - this.compileStack.push(name); - return name; + nextStack: function() { + return this.pushStack(); }, incrStack: function() { this.stackSlot++; if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } + return this.topStackName(); + }, + topStackName: function() { return "stack" + this.stackSlot; }, + flushInline: function() { + var inlineStack = this.inlineStack; + if (inlineStack.length) { + this.inlineStack = []; + for (var i = 0, len = inlineStack.length; i < len; i++) { + var entry = inlineStack[i]; + if (entry instanceof Literal) { + this.compileStack.push(entry); + } else { + this.pushStack(entry); + } + } + } + }, + isInline: function() { + return this.inlineStack.length; + }, - popStack: function() { - var item = this.compileStack.pop(); + popStack: function(wrapped) { + var inline = this.isInline(), + item = (inline ? this.inlineStack : this.compileStack).pop(); - if (item instanceof Literal) { + if (!wrapped && (item instanceof Literal)) { return item.value; } else { - this.stackSlot--; + if (!inline) { + this.stackSlot--; + } return item; } }, - topStack: function() { - var item = this.compileStack[this.compileStack.length - 1]; + topStack: function(wrapped) { + var stack = (this.isInline() ? this.inlineStack : this.compileStack), + item = stack[stack.length - 1]; - if (item instanceof Literal) { + if (!wrapped && (item instanceof Literal)) { return item.value; } else { return item; @@ -1788,22 +1971,22 @@ Handlebars.JavaScriptCompiler = function() {}; .replace(/\r/g, '\\r') + '"'; }, - setupHelper: function(paramSize, name) { + setupHelper: function(paramSize, name, missingParams) { var params = []; - this.setupParams(paramSize, params); + this.setupParams(paramSize, params, missingParams); var foundHelper = this.nameLookup('helpers', name, 'helper'); return { params: params, name: foundHelper, callParams: ["depth0"].concat(params).join(", "), - helperMissingParams: ["depth0", this.quotedString(name)].concat(params).join(", ") + helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ") }; }, // the params and contexts arguments are passed in arrays // to fill in - setupParams: function(paramSize, params) { + setupParams: function(paramSize, params, useRegister) { var options = [], contexts = [], types = [], param, inverse, program; options.push("hash:" + this.popStack()); @@ -1848,7 +2031,13 @@ Handlebars.JavaScriptCompiler = function() {}; options.push("data:data"); } - params.push("{" + options.join(",") + "}"); + options = "{" + options.join(",") + "}"; + if (useRegister) { + this.register('options', options); + params.push('options'); + } else { + params.push(options); + } return params.join(", "); } }; @@ -1886,23 +2075,23 @@ Handlebars.JavaScriptCompiler = function() {}; })(Handlebars.Compiler, Handlebars.JavaScriptCompiler); -Handlebars.precompile = function(string, options) { - if (typeof string !== 'string') { - throw new Handlebars.Exception("You must pass a string to Handlebars.compile. You passed " + string); +Handlebars.precompile = function(input, options) { + if (!input || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) { + throw new Handlebars.Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input); } options = options || {}; if (!('data' in options)) { options.data = true; } - var ast = Handlebars.parse(string); + var ast = Handlebars.parse(input); var environment = new Handlebars.Compiler().compile(ast, options); return new Handlebars.JavaScriptCompiler().compile(environment, options); }; -Handlebars.compile = function(string, options) { - if (typeof string !== 'string') { - throw new Handlebars.Exception("You must pass a string to Handlebars.compile. You passed " + string); +Handlebars.compile = function(input, options) { + if (!input || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) { + throw new Handlebars.Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input); } options = options || {}; @@ -1911,7 +2100,7 @@ Handlebars.compile = function(string, options) { } var compiled; function compile() { - var ast = Handlebars.parse(string); + var ast = Handlebars.parse(input); var environment = new Handlebars.Compiler().compile(ast, options); var templateSpec = new Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true); return Handlebars.template(templateSpec); @@ -1946,12 +2135,32 @@ Handlebars.VM = { } }, programWithDepth: Handlebars.VM.programWithDepth, - noop: Handlebars.VM.noop + noop: Handlebars.VM.noop, + compilerInfo: null }; return function(context, options) { options = options || {}; - return templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data); + var result = templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data); + + var compilerInfo = container.compilerInfo || [], + compilerRevision = compilerInfo[0] || 1, + currentRevision = Handlebars.COMPILER_REVISION; + + if (compilerRevision !== currentRevision) { + if (compilerRevision < currentRevision) { + var runtimeVersions = Handlebars.REVISION_CHANGES[currentRevision], + compilerVersions = Handlebars.REVISION_CHANGES[compilerRevision]; + throw "Template was precompiled with an older version of Handlebars than the current runtime. "+ + "Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+")."; + } else { + // Use the embedded version info since the runtime doesn't know about this revision yet + throw "Template was precompiled with a newer version of Handlebars than the current runtime. "+ + "Please update your runtime to a newer version ("+compilerInfo[1]+")."; + } + } + + return result; }; }, From 1408276609825c4082f625180c28e95bc0a15d67 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Fri, 8 Mar 2013 17:04:41 +0100 Subject: [PATCH 3/3] Changes for ember-data revision 12 Sideload changed a way of loading records associated with the main record. Now, to make it simple, sideloaded records must be always in plural form. --- assets/scripts/app/models/build.coffee | 1 - assets/scripts/app/models/job.coffee | 1 - assets/scripts/app/store.coffee | 2 +- assets/scripts/app/store/rest_adapter.coffee | 7 +++++++ 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/assets/scripts/app/models/build.coffee b/assets/scripts/app/models/build.coffee index 1f9b9079..47f87d30 100644 --- a/assets/scripts/app/models/build.coffee +++ b/assets/scripts/app/models/build.coffee @@ -16,7 +16,6 @@ require 'travis/model' repo: DS.belongsTo('Travis.Repo') commit: DS.belongsTo('Travis.Commit') - commits: DS.belongsTo('Travis.Commit') jobs: DS.hasMany('Travis.Job') config: (-> diff --git a/assets/scripts/app/models/job.coffee b/assets/scripts/app/models/job.coffee index 3e95f0d8..7ccb72b9 100644 --- a/assets/scripts/app/models/job.coffee +++ b/assets/scripts/app/models/job.coffee @@ -17,7 +17,6 @@ require 'travis/model' repo: DS.belongsTo('Travis.Repo') build: DS.belongsTo('Travis.Build') commit: DS.belongsTo('Travis.Commit') - commits: DS.belongsTo('Travis.Commit') # this is a fake relationship just to get rid # of ember data's bug: https://github.com/emberjs/data/issues/758 diff --git a/assets/scripts/app/store.coffee b/assets/scripts/app/store.coffee index d9e5ec03..92519e07 100644 --- a/assets/scripts/app/store.coffee +++ b/assets/scripts/app/store.coffee @@ -3,7 +3,7 @@ require 'store/rest_adapter' coerceId = (id) -> if id == null then null else id+'' Travis.Store = DS.Store.extend - revision: 11 + revision: 12 adapter: Travis.RestAdapter.create() init: -> diff --git a/assets/scripts/app/store/rest_adapter.coffee b/assets/scripts/app/store/rest_adapter.coffee index 6b2ac960..b240e3d4 100644 --- a/assets/scripts/app/store/rest_adapter.coffee +++ b/assets/scripts/app/store/rest_adapter.coffee @@ -102,6 +102,13 @@ Travis.RestAdapter = DS.RESTAdapter.extend merge: (store, record, serialized) -> @get('serializer').merge(record, serialized) + didFindRecord: (store, type, payload, id) -> + if (type == Travis.Build || type == Travis.Job) && payload.commit? + payload.commits = payload.commit + delete payload.commit + + @_super.apply this, arguments + didSaveRecord: (store, type, record, payload) -> # API sometimes return { result: true } response # which does not play nice with ember-data. For now