Merge branch 'ps-ember-update' of github.com:travis-ci/travis-web into ps-ember-update

This commit is contained in:
Sven Fuchs 2013-03-08 17:33:12 +01:00
commit e5eae55456
7 changed files with 876 additions and 271 deletions

View File

@ -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: (->

View File

@ -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

View File

@ -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: ->

View File

@ -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

View File

@ -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() {
})();

View File

@ -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() {

View File

@ -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<l; i++) {
var part = parts[i];
if(part === "..") { depth++; }
else if(part === "." || part === "this") { this.isScoped = true; }
if (part === ".." || part === "." || part === "this") {
if (dig.length > 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;
};
},