dist/ember.prod.js in ember-source-1.0.0.rc1.2 vs dist/ember.prod.js in ember-source-1.0.0.rc1.3

- old
+ new

@@ -140,10 +140,19 @@ @default Ember.EXTEND_PROTOTYPES */ Ember.SHIM_ES5 = (Ember.ENV.SHIM_ES5 === false) ? false : Ember.EXTEND_PROTOTYPES; /** + Determines whether Ember logs info about version of used libraries + + @property LOG_VERSION + @type Boolean + @default true +*/ +Ember.LOG_VERSION = (Ember.ENV.LOG_VERSION === false) ? false : true; + +/** Empty function. Useful for some operations. @method K @private @return {Object} @@ -251,10 +260,62 @@ if (!updates.hasOwnProperty(prop)) { continue; } original[prop] = updates[prop]; } }; +/** + 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) ; + + })(); (function() { @@ -1594,11 +1655,11 @@ /** @method create @static @param [options] @param {anything} [options.defaultValue] - @return {Ember.MapWithDefault|Ember.Map} If options are passed, returns + @return {Ember.MapWithDefault|Ember.Map} If options are passed, returns `Ember.MapWithDefault` otherwise returns `Ember.Map` */ MapWithDefault.create = function(options) { if (options) { return new MapWithDefault(options); @@ -1657,22 +1718,22 @@ var FIRST_KEY = /^([^\.\*]+)/; // .......................................................... // GET AND SET // -// If we are on a platform that supports accessors we can get use those. +// If we are on a platform that supports accessors we can use those. // Otherwise simulate accessors by looking up the property directly on the // object. /** Gets the value of a property on an object. If the property is computed, the function will be invoked. If the property is not defined but the object implements the `unknownProperty` method then that will be invoked. If you plan to run on IE8 and older browsers then you should use this method anytime you want to retrieve a property on an object that you don't - know for sure is private. (Properties beginning with an underscore '_' + know for sure is private. (Properties beginning with an underscore '_' are considered private.) On all newer browsers, you only need to use this method to retrieve properties if the property might not be defined on the object and you want to respect the `unknownProperty` handler. Otherwise you can ignore this @@ -1729,11 +1790,11 @@ property is not defined but the object implements the `unknownProperty` method then that will be invoked as well. If you plan to run on IE8 and older browsers then you should use this method anytime you want to set a property on an object that you don't - know for sure is private. (Properties beginning with an underscore '_' + know for sure is private. (Properties beginning with an underscore '_' are considered private.) On all newer browsers, you only need to use this method to set properties if the property might not be defined on the object and you want to respect the `unknownProperty` handler. Otherwise you can ignore this @@ -1959,15 +2020,12 @@ (function() { /** @module ember-metal */ -var GUID_KEY = Ember.GUID_KEY, - META_KEY = Ember.META_KEY, - EMPTY_META = Ember.EMPTY_META, +var META_KEY = Ember.META_KEY, metaFor = Ember.meta, - o_create = Ember.create, objectDefineProperty = Ember.platform.defineProperty; var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER; // .......................................................... @@ -2383,11 +2441,10 @@ normalizeTuple = Ember.normalizeTuple, // accessors.js GUID_KEY = Ember.GUID_KEY, // utils.js META_KEY = Ember.META_KEY, // utils.js // circular reference observer depends on Ember.watch // we should move change events to this file or its own property_events.js - notifyObservers = Ember.notifyObservers, // observer.js forEach = Ember.ArrayPolyfills.forEach, // array.js FIRST_KEY = /^([^\.\*]+)/, IS_PATH = /[\.\*]/; var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER, @@ -2922,11 +2979,11 @@ @for Ember @param {Object} obj The object with the property that will change @param {String} keyName The property key (or path) that will change. @return {void} */ -function propertyWillChange(obj, keyName, value) { +function propertyWillChange(obj, keyName) { var m = metaFor(obj, false), watching = m.watching[keyName] > 0 || keyName === 'length', proto = m.proto, desc = m.descs[keyName]; @@ -3029,11 +3086,10 @@ var get = Ember.get, set = Ember.set, metaFor = Ember.meta, - guidFor = Ember.guidFor, a_slice = [].slice, o_create = Ember.create, META_KEY = Ember.META_KEY, watch = Ember.watch, unwatch = Ember.unwatch; @@ -3065,24 +3121,12 @@ keys = depsMeta[depKey] = o_create(keys); } return keys; } -/* return obj[META_KEY].deps */ function metaForDeps(obj, meta) { - var deps = meta.deps; - // If the current object has no dependencies... - if (!deps) { - // initialize the dependencies with a pointer back to - // the current object - deps = meta.deps = {}; - } else if (!meta.hasOwnProperty('deps')) { - // otherwise if the dependencies are inherited from the - // object's superclass, clone the deps - deps = meta.deps = o_create(deps); - } - return deps; + return keysForDep(obj, meta, 'deps'); } function addDependentKeys(desc, obj, keyName, meta) { // the descriptor has a list of dependent keys, so // add all of its dependent keys. @@ -3131,12 +3175,14 @@ @extends Ember.Descriptor @constructor */ function ComputedProperty(func, opts) { this.func = func; + this._cacheable = (opts && opts.cacheable !== undefined) ? opts.cacheable : true; this._dependentKeys = opts && opts.dependentKeys; + this._readOnly = opts && (opts.readOnly !== undefined || !!opts.readOnly); } Ember.ComputedProperty = ComputedProperty; ComputedProperty.prototype = new Ember.Descriptor(); @@ -3188,10 +3234,32 @@ ComputedPropertyPrototype.volatile = function() { return this.cacheable(false); }; /** + Call on a computed property to set it into read-only mode. When in this + mode the computed property will throw an error when set. + + ```javascript + MyApp.person = Ember.Object.create({ + guid: function() { + return 'guid-guid-guid'; + }.property().readOnly() + }); + + MyApp.person.set('guid', 'new-guid'); // will throw an exception + ``` + + @method readOnly + @chainable +*/ +ComputedPropertyPrototype.readOnly = function(readOnly) { + this._readOnly = readOnly === undefined || !!readOnly; + return this; +}; + +/** Sets the dependent keys on this computed property. Pass any number of arguments containing key paths that this computed property depends on. ```javascript MyApp.president = Ember.Object.create({ @@ -3311,13 +3379,18 @@ oldSuspended = this._suspended, hadCachedValue = false, cache = meta.cache, cachedValue, ret; + if (this._readOnly) { + throw new Error('Cannot Set: ' + keyName + ' on: ' + obj.toString() ); + } + this._suspended = obj; try { + if (cacheable && cache.hasOwnProperty(keyName)) { cachedValue = cache[keyName]; hadCachedValue = true; } @@ -3404,10 +3477,14 @@ if (arguments.length > 1) { args = a_slice.call(arguments, 0, -1); func = a_slice.call(arguments, -1)[0]; } + if ( typeof func !== "function" ) { + throw new Error("Computed Property declared without a property function"); + } + var cp = new ComputedProperty(func); if (args) { cp.property.apply(cp, args); } @@ -3445,18 +3522,30 @@ return !get(this, 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 @param {String} 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); }); }; /** @method computed.bool @@ -3470,19 +3559,20 @@ }; /** @method computed.alias @for Ember + @param {String} dependentKey */ Ember.computed.alias = function(dependentKey) { return Ember.computed(dependentKey, function(key, value){ - if (arguments.length === 1) { - return get(this, dependentKey); - } else { + if (arguments.length > 1) { set(this, dependentKey, value); return value; + } else { + return get(this, dependentKey); } }); }; })(); @@ -3494,11 +3584,10 @@ @module ember-metal */ var o_create = Ember.create, metaFor = Ember.meta, - metaPath = Ember.metaPath, META_KEY = Ember.META_KEY; /* The event system uses a series of nested hashes to store listeners on an object. When a listener is registered, or when an event arrives, these @@ -4067,11 +4156,11 @@ libraries or plugins, you should probably wrap all of your code inside this call. ```javascript Ember.run(function(){ - // code to be execute within a RunLoop + // code to be execute within a RunLoop }); ``` @class run @namespace Ember @@ -4083,12 +4172,11 @@ then it will be looked up on the passed target. @param {Object} [args*] Any additional arguments you wish to pass to the method. @return {Object} return value from invoking the passed function. */ Ember.run = function(target, method) { - var loop, - args = arguments; + var args = arguments; run.begin(); function tryable() { if (target || method) { return invoke(target, method, args, 2); @@ -4102,15 +4190,15 @@ /** Begins a new RunLoop. Any deferred actions invoked after the begin will be buffered until you invoke a matching call to `Ember.run.end()`. This is - an lower-level way to use a RunLoop instead of using `Ember.run()`. + a lower-level way to use a RunLoop instead of using `Ember.run()`. ```javascript Ember.run.begin(); - // code to be execute within a RunLoop + // code to be execute within a RunLoop Ember.run.end(); ``` @method begin @return {void} @@ -4124,11 +4212,11 @@ `Ember.run.begin()` to flush any deferred actions. This is a lower-level way to use a RunLoop instead of using `Ember.run()`. ```javascript Ember.run.begin(); - // code to be execute within a RunLoop + // code to be execute within a RunLoop Ember.run.end(); ``` @method end @return {void} @@ -4148,13 +4236,13 @@ simply adding the queue name to this array. Normally you should not need to inspect or modify this property. @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 queue to be executed at the end of the RunLoop. If you have not already started a RunLoop when calling this method one will be started for you @@ -4163,23 +4251,23 @@ At the end of a RunLoop, any methods scheduled in this way will be invoked. Methods will be invoked in an order matching the named queues defined in the `run.queues` property. ```javascript - Ember.run.schedule('timers', this, function(){ - // this will be executed at the end of the RunLoop, when timers are run - console.log("scheduled on timers queue"); - }); - Ember.run.schedule('sync', this, function(){ - // this will be executed at the end of the RunLoop, when bindings are synced + // this will be executed in the first RunLoop queue, when bindings are synced console.log("scheduled on sync queue"); }); + Ember.run.schedule('actions', this, function(){ + // this will be executed in the 'actions' queue, after bindings have synced. + console.log("scheduled on actions queue"); + }); + // Note the functions will be run in order based on the run queues order. Output would be: // scheduled on sync queue - // scheduled on timers queue + // scheduled on actions queue ``` @method schedule @param {String} queue The name of the queue to schedule against. Default queues are 'sync' and 'actions' @@ -4201,11 +4289,11 @@ if (run.currentRunLoop) { run.end(); } } // Used by global test teardown Ember.run.hasScheduledTimers = function() { - return !!(scheduledAutorun || scheduledLater || scheduledNext); + return !!(scheduledAutorun || scheduledLater); }; // Used by global test teardown Ember.run.cancelTimers = function () { if (scheduledAutorun) { @@ -4214,14 +4302,10 @@ } if (scheduledLater) { clearTimeout(scheduledLater); scheduledLater = null; } - if (scheduledNext) { - clearTimeout(scheduledNext); - scheduledNext = null; - } timers = {}; }; /** Begins a new RunLoop if necessary and schedules a timer to flush the @@ -4252,11 +4336,12 @@ Immediately flushes any events scheduled in the 'sync' queue. Bindings use this queue so this method is a useful way to immediately force all 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(); ``` @@ -4272,29 +4357,34 @@ // TIMERS // 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; + } + }); } /** Invokes the passed target/method and optional arguments after a specified period if time. The last parameter of this method must always be a number @@ -4338,11 +4428,23 @@ expires = (+ new Date()) + wait; 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; }; function invokeOnceTimer(guid, onceTimers) { if (onceTimers[this.tguid]) { delete onceTimers[this.tguid][this.mguid]; } @@ -4394,10 +4496,25 @@ Ember.run.once(myContext, doFoo); // doFoo will only be executed once at the end of the RunLoop }); ``` + Also note that passing an anonymous function to `Ember.run.once` will + not prevent additional calls with an identical anonymous function from + scheduling the items multiple times, e.g.: + + ```javascript + function scheduleIt() { + Ember.run.once(myContext, function() { console.log("Closure"); }); + } + scheduleIt(); + scheduleIt(); + // "Closure" will print twice, even though we're using `Ember.run.once`, + // because the function we pass to it is anonymous and won't match the + // previously scheduled operation. + ``` + @method once @param {Object} [target] target of method to invoke @param {Function|String} method The method to invoke. If you pass a string it will be resolved on the target at the time the method is invoked. @@ -4410,26 +4527,13 @@ 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(){ // code to be executed in the next RunLoop, which will be scheduled after the current one }); @@ -4441,24 +4545,14 @@ If you pass a string it will be resolved on the target at the time the method is invoked. @param {Object} [args*] Optional arguments to pass to the timeout. @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); }; /** Cancels a scheduled item. Must be a value returned by `Ember.run.later()`, `Ember.run.once()`, or `Ember.run.next()`. @@ -4959,11 +5053,10 @@ var Mixin, REQUIRED, Alias, a_map = Ember.ArrayPolyfills.map, a_indexOf = Ember.ArrayPolyfills.indexOf, a_forEach = Ember.ArrayPolyfills.forEach, a_slice = [].slice, - EMPTY_META = {}, // dummy for non-writable meta o_create = Ember.create, defineProperty = Ember.defineProperty, guidFor = Ember.guidFor; function mixinsMeta(obj) { @@ -5328,10 +5421,42 @@ Mixin.finishPartial = finishPartial; Ember.anyUnprocessedMixins = false; /** + Creates an instance of a class. Accepts either no arguments, or an object + containing values to initialize the newly instantiated object with. + + ```javascript + App.Person = Ember.Object.extend({ + helloWorld: function() { + alert("Hi, my name is " + this.get('name')); + } + }); + + var tom = App.Person.create({ + name: 'Tom Dale' + }); + + tom.helloWorld(); // alerts "Hi, my name is Tom Dale". + ``` + + `create` will call the `init` function if defined during + `Ember.AnyObject.extend` + + If no arguments are passed to `create`, it will not set values to the new + instance during initialization: + + ```javascript + var noName = App.Person.create(); + noName.helloWorld(); // alerts undefined + ``` + + NOTE: For performance reasons, you cannot declare methods or computed + properties during `create`. You should instead declare methods and computed + properties when using `extend`. + @method create @static @param arguments* */ Mixin.create = function() { @@ -5843,39 +5968,39 @@ promise.rejectedValue = value; }); } function all(promises) { - var i, results = []; - var allPromise = new Promise(); - var remaining = promises.length; + var i, results = []; + var allPromise = new Promise(); + var remaining = promises.length; if (remaining === 0) { allPromise.resolve([]); } - var resolver = function(index) { - return function(value) { - resolve(index, value); - }; - }; + var resolver = function(index) { + return function(value) { + resolve(index, value); + }; + }; - var resolve = function(index, value) { - results[index] = value; - if (--remaining === 0) { - allPromise.resolve(results); - } - }; + var resolve = function(index, value) { + results[index] = value; + if (--remaining === 0) { + allPromise.resolve(results); + } + }; - var reject = function(error) { - allPromise.reject(error); - }; + var reject = function(error) { + allPromise.reject(error); + }; - for (i = 0; i < remaining; i++) { - promises[i].then(resolver(i), reject); - } - return allPromise; + for (i = 0; i < remaining; i++) { + promises[i].then(resolver(i), reject); + } + return allPromise; } EventTarget.mixin(Promise.prototype); RSVP = { async: async, Promise: Promise, Event: Event, EventTarget: EventTarget, all: all, raiseOnUncaughtExceptions: true }; @@ -6248,61 +6373,10 @@ 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: - -1 if the first is smaller than the second, - 0 if both are equal, @@ -6592,10 +6666,24 @@ (function() { /** + Expose RSVP implementation + + @class RSVP + @namespace Ember + @constructor +*/ +Ember.RSVP = requireModule('rsvp'); + +})(); + + + +(function() { +/** @module ember @submodule ember-runtime */ var STRING_DASHERIZE_REGEXP = (/[ _]/g); @@ -6738,24 +6826,25 @@ @param {String} str The string to dasherize. @return {String} the dasherized string. */ dasherize: function(str) { var cache = STRING_DASHERIZE_CACHE, - ret = cache[str]; + hit = cache.hasOwnProperty(str), + ret; - if (ret) { - return ret; + if (hit) { + return cache[str]; } else { ret = Ember.String.decamelize(str).replace(STRING_DASHERIZE_REGEXP,'-'); cache[str] = ret; } return ret; }, /** - Returns the lowerCaseCamel form of a string. + Returns the lowerCamelCase form of a string. ```javascript 'innerHTML'.camelize(); // 'innerHTML' 'action_name'.camelize(); // 'actionName' 'css-class-name'.camelize(); // 'cssClassName' @@ -6822,14 +6911,14 @@ }, /** Returns the Capitalized form of a string - 'innerHTML'.capitalize() => 'InnerHTML' - 'action_name'.capitalize() => 'Action_name' - 'css-class-name'.capitalize() => 'Css-class-name' - 'my favorite items'.capitalize() => 'My favorite items' + 'innerHTML'.capitalize() // 'InnerHTML' + 'action_name'.capitalize() // 'Action_name' + 'css-class-name'.capitalize() // 'Css-class-name' + 'my favorite items'.capitalize() // 'My favorite items' @method capitalize @param {String} str @return {String} */ @@ -7169,12 +7258,11 @@ @class Enumerable @namespace Ember @extends Ember.Mixin @since Ember 0.9 */ -Ember.Enumerable = Ember.Mixin.create( - /** @scope Ember.Enumerable.prototype */ { +Ember.Enumerable = Ember.Mixin.create({ // compatibility isEnumerable: true, /** @@ -7203,11 +7291,11 @@ The default implementation of this method simply looks up the index. This works great on any Array-like objects. @method nextObject @param {Number} index the current index of the iteration - @param {Object} previousObject the value returned by the last call to + @param {Object} previousObject the value returned by the last call to `nextObject`. @param {Object} context a context object you can use to maintain state. @return {Object} the next object in the iteration or undefined */ nextObject: Ember.required(Function), @@ -7222,14 +7310,14 @@ contains only one object, this method should always return that object. If your enumerable is empty, this method should return `undefined`. ```javascript var arr = ["a", "b", "c"]; - arr.firstObject(); // "a" + arr.get('firstObject'); // "a" var arr = []; - arr.firstObject(); // undefined + arr.get('firstObject'); // undefined ``` @property firstObject @return {Object} the object or undefined */ @@ -7248,14 +7336,14 @@ contains only one object, this method should always return that object. If your enumerable is empty, this method should return `undefined`. ```javascript var arr = ["a", "b", "c"]; - arr.lastObject(); // "c" + arr.get('lastObject'); // "c" var arr = []; - arr.lastObject(); // undefined + arr.get('lastObject'); // undefined ``` @property lastObject @return {Object} the last object or undefined */ @@ -7384,11 +7472,11 @@ @param {Function} callback The callback to execute @param {Object} [target] The target object to use @return {Array} The mapped array. */ map: function(callback, target) { - var ret = []; + var ret = Ember.A([]); this.forEach(function(x, idx, i) { ret[idx] = callback.call(target, x, idx,i); }); return ret ; }, @@ -7434,11 +7522,11 @@ @param {Function} callback The callback to execute @param {Object} [target] The target object to use @return {Array} A filtered array. */ filter: function(callback, target) { - var ret = []; + var ret = Ember.A([]); this.forEach(function(x, idx, i) { if (callback.call(target, x, idx, i)) ret.push(x); }); return ret ; }, @@ -7723,11 +7811,11 @@ @param {String} methodName the name of the method @param {Object...} args optional arguments to pass as well. @return {Array} return values from calling invoke. */ invoke: function(methodName) { - var args, ret = []; + var args, ret = Ember.A([]); if (arguments.length>1) args = a_slice.call(arguments, 1); this.forEach(function(x, idx) { var method = x && x[methodName]; if ('function' === typeof method) { @@ -7744,11 +7832,11 @@ @method toArray @return {Array} the enumerable as an array. */ toArray: function() { - var ret = []; + var ret = Ember.A([]); this.forEach(function(o, idx) { ret[idx] = o; }); return ret ; }, /** @@ -7778,11 +7866,11 @@ @param {Object} value @return {Ember.Enumerable} */ without: function(value) { if (!this.contains(value)) return this; // nothing to do - var ret = [] ; + var ret = Ember.A([]); this.forEach(function(k) { if (k !== value) ret[ret.length] = k; }) ; return ret ; }, @@ -7798,11 +7886,11 @@ @method uniq @return {Ember.Enumerable} */ uniq: function() { - var ret = []; + var ret = Ember.A([]); this.forEach(function(k){ if (a_indexOf(ret, k)<0) ret.push(k); }); return ret; }, @@ -7830,11 +7918,11 @@ Registers an enumerable observer. Must implement `Ember.EnumerableObserver` mixin. @method addEnumerableObserver @param {Object} target - @param {Hash} opts + @param {Hash} [opts] */ addEnumerableObserver: function(target, opts) { var willChange = (opts && opts.willChange) || 'enumerableWillChange', didChange = (opts && opts.didChange) || 'enumerableDidChange'; @@ -7928,11 +8016,11 @@ @param {Ember.Enumerable|Number} adding An enumerable of the objects to be added or the number of items to be added. @chainable */ enumerableContentDidChange: function(removing, adding) { - var notify = this.propertyDidChange, removeCnt, addCnt, hasDelta; + var removeCnt, addCnt, hasDelta; if ('number' === typeof removing) removeCnt = removing; else if (removing) removeCnt = get(removing, 'length'); else removeCnt = removing = -1; @@ -7966,11 +8054,11 @@ // .......................................................... // HELPERS // -var get = Ember.get, set = Ember.set, meta = Ember.meta, map = Ember.EnumerableUtils.map, cacheFor = Ember.cacheFor; +var get = Ember.get, set = Ember.set, map = Ember.EnumerableUtils.map, cacheFor = Ember.cacheFor; function none(obj) { return obj===null || obj===undefined; } // .......................................................... // ARRAY @@ -8108,19 +8196,23 @@ arr.slice(0, 2); // ['red', 'green'] arr.slice(1, 100); // ['green', 'blue'] ``` @method slice - @param beginIndex {Integer} (Optional) index to begin slicing from. - @param endIndex {Integer} (Optional) index to end the slice at. + @param {Integer} beginIndex (Optional) index to begin slicing from. + @param {Integer} endIndex (Optional) index to end the slice at. @return {Array} New array with specified slice */ slice: function(beginIndex, endIndex) { - var ret = []; + var ret = Ember.A([]); var length = get(this, 'length') ; if (none(beginIndex)) beginIndex = 0 ; if (none(endIndex) || (endIndex > length)) endIndex = length ; + + if (beginIndex < 0) beginIndex = length + beginIndex; + if (endIndex < 0) endIndex = length + endIndex; + while(beginIndex < endIndex) { ret[ret.length] = this.objectAt(beginIndex++) ; } return ret ; }, @@ -8270,13 +8362,13 @@ invalidate any related properties. Pass the starting index of the change as well as a delta of the amounts to change. @method arrayContentWillChange @param {Number} startIdx The starting index in the array that will change. - @param {Number} removeAmt The number of items that will be removed. If you + @param {Number} removeAmt The number of items that will be removed. If you pass `null` assumes 0 - @param {Number} addAmt The number of items that will be added If you + @param {Number} addAmt The number of items that will be added If you pass `null` assumes 0. @return {Ember.Array} receiver */ arrayContentWillChange: function(startIdx, removeAmt, addAmt) { @@ -8633,12 +8725,11 @@ @class MutableEnumerable @namespace Ember @extends Ember.Mixin @uses Ember.Enumerable */ -Ember.MutableEnumerable = Ember.Mixin.create(Ember.Enumerable, - /** @scope Ember.MutableEnumerable.prototype */ { +Ember.MutableEnumerable = Ember.Mixin.create(Ember.Enumerable, { /** __Required.__ You must implement this method to apply this mixin. Attempts to add the passed object to the receiver if the object is not @@ -8719,11 +8810,11 @@ // .......................................................... // HELPERS // -var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach; +var get = Ember.get, set = Ember.set; /** This mixin defines the API for modifying array-like objects. These methods can be applied only to a collection that keeps its items in an ordered set. @@ -8746,15 +8837,15 @@ This is one of the primitives you must implement to support `Ember.Array`. You should replace amt objects started at idx with the objects in the passed array. You should also call `this.enumerableContentDidChange()` @method replace - @param {Number} idx Starting index in the array to replace. If + @param {Number} idx Starting index in the array to replace. If idx >= length, then append to the end of the array. - @param {Number} amt Number of elements that should be removed from + @param {Number} amt Number of elements that should be removed from the array, starting at *idx*. - @param {Array} objects An array of zero or more objects that should be + @param {Array} objects An array of zero or more objects that should be inserted into the array at *idx* */ replace: Ember.required(), /** @@ -9015,11 +9106,11 @@ /** @module ember @submodule ember-runtime */ -var get = Ember.get, set = Ember.set, defineProperty = Ember.defineProperty; +var get = Ember.get, set = Ember.set; /** ## Overview This mixin provides properties and property observing functionality, core @@ -9613,10 +9704,20 @@ person.greet(); // outputs: 'Our person has greeted' ``` + You can also chain multiple event subscriptions: + + ```javascript + person.on('greet', function() { + console.log('Our person has greeted'); + }).one('greet', function() { + console.log('Offer one-time special'); + }).off('event', this, forgetThis); + ``` + @class Evented @namespace Ember @extends Ember.Mixin */ Ember.Evented = Ember.Mixin.create({ @@ -9640,10 +9741,11 @@ @param {Object} [target] The "this" binding for the callback @param {Function} method The callback to execute */ on: function(name, target, method) { Ember.addListener(this, name, target, method); + return this; }, /** Subscribes a function to a named event and then cancels the subscription after the first time the event is triggered. It is good to use ``one`` when @@ -9663,10 +9765,11 @@ method = target; target = null; } Ember.addListener(this, name, target, method, true); + return this; }, /** Triggers a named event for the object. Any additional arguments will be passed as parameters to the functions that are subscribed to the @@ -9706,10 +9809,11 @@ @param {Object} target The target of the subscription @param {Function} method The function of the subscription */ off: function(name, target, method) { Ember.removeListener(this, name, target, method); + return this; }, /** Checks to see if object has any subscriptions for named event. @@ -9736,12 +9840,11 @@ /** @module ember @submodule ember-runtime */ -var get = Ember.get, - slice = Array.prototype.slice; +var get = Ember.get; /** @class Deferred @namespace Ember @extends Ember.Mixin @@ -9813,11 +9916,10 @@ var set = Ember.set, get = Ember.get, o_create = Ember.create, o_defineProperty = Ember.platform.defineProperty, - a_slice = Array.prototype.slice, GUID_KEY = Ember.GUID_KEY, guidFor = Ember.guidFor, generateGuid = Ember.generateGuid, meta = Ember.meta, rewatch = Ember.rewatch, @@ -9960,10 +10062,41 @@ return this; }, isInstance: true, + /** + An overridable method called when objects are instantiated. By default, + does nothing unless it is overridden during class definition. + + Example: + + ```javascript + App.Person = Ember.Object.extend({ + init: function() { + this._super(); + alert('Name is ' + this.get('name')); + } + }); + + var steve = App.Person.create({ + name: "Steve" + }); + + // alerts 'Name is Steve'. + ``` + + NOTE: If you do override `init` for a framework class like `Ember.View` or + `Ember.ArrayController`, be sure to call `this._super()` in your + `init` declaration! If you don't, Ember may not have an opportunity to + do important setup work, and you'll see strange behavior in your + application. + + ``` + + @method init + */ init: function() {}, /** Defines the properties that will be concatenated from the superclass (instead of overridden). @@ -10004,18 +10137,18 @@ }) view.get('someNonConcatenatedProperty'); // ['baz'] view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz'] ``` Adding a single property that is not an array will just add it in the array: - + ```javascript var view = App.FooBarView.create({ classNames: 'baz' }) view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz'] ``` - + Using the `concatenatedProperties` property, we can tell to Ember that mix the content of the properties. In `Ember.View` the `classNameBindings` and `attributeBindings` properties are also concatenated, in addition to `classNames`. @@ -10110,11 +10243,11 @@ toStringExtension: function(){ return this.get('fullName'); } }); teacher = App.Teacher.create() - teacher.toString(); // #=> "<App.Teacher:ember1026:Tom Dale>" + teacher.toString(); //=> "<App.Teacher:ember1026:Tom Dale>" @method toString @return {String} string representation */ toString: function toString() { @@ -10288,468 +10421,11 @@ /** @module ember @submodule ember-runtime */ -var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor, none = Ember.isNone; - /** - An unordered collection of objects. - - A Set works a bit like an array except that its items are not ordered. You - can create a set to efficiently test for membership for an object. You can - also iterate through a set just like an array, even accessing objects by - index, however there is no guarantee as to their order. - - All Sets are observable via the Enumerable Observer API - which works - on any enumerable object including both Sets and Arrays. - - ## Creating a Set - - You can create a set like you would most objects using - `new Ember.Set()`. Most new sets you create will be empty, but you can - also initialize the set with some content by passing an array or other - enumerable of objects to the constructor. - - Finally, you can pass in an existing set and the set will be copied. You - can also create a copy of a set by calling `Ember.Set#copy()`. - - ```javascript - // creates a new empty set - var foundNames = new Ember.Set(); - - // creates a set with four names in it. - var names = new Ember.Set(["Charles", "Tom", "Juan", "Alex"]); // :P - - // creates a copy of the names set. - var namesCopy = new Ember.Set(names); - - // same as above. - var anotherNamesCopy = names.copy(); - ``` - - ## Adding/Removing Objects - - You generally add or remove objects from a set using `add()` or - `remove()`. You can add any type of object including primitives such as - numbers, strings, and booleans. - - Unlike arrays, objects can only exist one time in a set. If you call `add()` - on a set with the same object multiple times, the object will only be added - once. Likewise, calling `remove()` with the same object multiple times will - remove the object the first time and have no effect on future calls until - you add the object to the set again. - - NOTE: You cannot add/remove `null` or `undefined` to a set. Any attempt to do - so will be ignored. - - In addition to add/remove you can also call `push()`/`pop()`. Push behaves - just like `add()` but `pop()`, unlike `remove()` will pick an arbitrary - object, remove it and return it. This is a good way to use a set as a job - queue when you don't care which order the jobs are executed in. - - ## Testing for an Object - - To test for an object's presence in a set you simply call - `Ember.Set#contains()`. - - ## Observing changes - - When using `Ember.Set`, you can observe the `"[]"` property to be - alerted whenever the content changes. You can also add an enumerable - observer to the set to be notified of specific objects that are added and - removed from the set. See `Ember.Enumerable` for more information on - enumerables. - - This is often unhelpful. If you are filtering sets of objects, for instance, - it is very inefficient to re-filter all of the items each time the set - changes. It would be better if you could just adjust the filtered set based - on what was changed on the original set. The same issue applies to merging - sets, as well. - - ## Other Methods - - `Ember.Set` primary implements other mixin APIs. For a complete reference - on the methods you will use with `Ember.Set`, please consult these mixins. - The most useful ones will be `Ember.Enumerable` and - `Ember.MutableEnumerable` which implement most of the common iterator - methods you are used to on Array. - - Note that you can also use the `Ember.Copyable` and `Ember.Freezable` - APIs on `Ember.Set` as well. Once a set is frozen it can no longer be - modified. The benefit of this is that when you call `frozenCopy()` on it, - Ember will avoid making copies of the set. This allows you to write - code that can know with certainty when the underlying set data will or - will not be modified. - - @class Set - @namespace Ember - @extends Ember.CoreObject - @uses Ember.MutableEnumerable - @uses Ember.Copyable - @uses Ember.Freezable - @since Ember 0.9 -*/ -Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Ember.Freezable, - /** @scope Ember.Set.prototype */ { - - // .......................................................... - // IMPLEMENT ENUMERABLE APIS - // - - /** - This property will change as the number of objects in the set changes. - - @property length - @type number - @default 0 - */ - length: 0, - - /** - Clears the set. This is useful if you want to reuse an existing set - without having to recreate it. - - ```javascript - var colors = new Ember.Set(["red", "green", "blue"]); - colors.length; // 3 - colors.clear(); - colors.length; // 0 - ``` - - @method clear - @return {Ember.Set} An empty Set - */ - clear: function() { - if (this.isFrozen) { throw new Error(Ember.FROZEN_ERROR); } - - var len = get(this, 'length'); - if (len === 0) { return this; } - - var guid; - - this.enumerableContentWillChange(len, 0); - Ember.propertyWillChange(this, 'firstObject'); - Ember.propertyWillChange(this, 'lastObject'); - - for (var i=0; i < len; i++){ - guid = guidFor(this[i]); - delete this[guid]; - delete this[i]; - } - - set(this, 'length', 0); - - Ember.propertyDidChange(this, 'firstObject'); - Ember.propertyDidChange(this, 'lastObject'); - this.enumerableContentDidChange(len, 0); - - return this; - }, - - /** - Returns true if the passed object is also an enumerable that contains the - same objects as the receiver. - - ```javascript - var colors = ["red", "green", "blue"], - same_colors = new Ember.Set(colors); - - same_colors.isEqual(colors); // true - same_colors.isEqual(["purple", "brown"]); // false - ``` - - @method isEqual - @param {Ember.Set} obj the other object. - @return {Boolean} - */ - isEqual: function(obj) { - // fail fast - if (!Ember.Enumerable.detect(obj)) return false; - - var loc = get(this, 'length'); - if (get(obj, 'length') !== loc) return false; - - while(--loc >= 0) { - if (!obj.contains(this[loc])) return false; - } - - return true; - }, - - /** - Adds an object to the set. Only non-`null` objects can be added to a set - and those can only be added once. If the object is already in the set or - the passed value is null this method will have no effect. - - This is an alias for `Ember.MutableEnumerable.addObject()`. - - ```javascript - var colors = new Ember.Set(); - colors.add("blue"); // ["blue"] - colors.add("blue"); // ["blue"] - colors.add("red"); // ["blue", "red"] - colors.add(null); // ["blue", "red"] - colors.add(undefined); // ["blue", "red"] - ``` - - @method add - @param {Object} obj The object to add. - @return {Ember.Set} The set itself. - */ - add: Ember.aliasMethod('addObject'), - - /** - Removes the object from the set if it is found. If you pass a `null` value - or an object that is already not in the set, this method will have no - effect. This is an alias for `Ember.MutableEnumerable.removeObject()`. - - ```javascript - var colors = new Ember.Set(["red", "green", "blue"]); - colors.remove("red"); // ["blue", "green"] - colors.remove("purple"); // ["blue", "green"] - colors.remove(null); // ["blue", "green"] - ``` - - @method remove - @param {Object} obj The object to remove - @return {Ember.Set} The set itself. - */ - remove: Ember.aliasMethod('removeObject'), - - /** - Removes the last element from the set and returns it, or `null` if it's empty. - - ```javascript - var colors = new Ember.Set(["green", "blue"]); - colors.pop(); // "blue" - colors.pop(); // "green" - colors.pop(); // null - ``` - - @method pop - @return {Object} The removed object from the set or null. - */ - pop: function() { - if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR); - var obj = this.length > 0 ? this[this.length-1] : null; - this.remove(obj); - return obj; - }, - - /** - Inserts the given object on to the end of the set. It returns - the set itself. - - This is an alias for `Ember.MutableEnumerable.addObject()`. - - ```javascript - var colors = new Ember.Set(); - colors.push("red"); // ["red"] - colors.push("green"); // ["red", "green"] - colors.push("blue"); // ["red", "green", "blue"] - ``` - - @method push - @return {Ember.Set} The set itself. - */ - push: Ember.aliasMethod('addObject'), - - /** - Removes the last element from the set and returns it, or `null` if it's empty. - - This is an alias for `Ember.Set.pop()`. - - ```javascript - var colors = new Ember.Set(["green", "blue"]); - colors.shift(); // "blue" - colors.shift(); // "green" - colors.shift(); // null - ``` - - @method shift - @return {Object} The removed object from the set or null. - */ - shift: Ember.aliasMethod('pop'), - - /** - Inserts the given object on to the end of the set. It returns - the set itself. - - This is an alias of `Ember.Set.push()` - - ```javascript - var colors = new Ember.Set(); - colors.unshift("red"); // ["red"] - colors.unshift("green"); // ["red", "green"] - colors.unshift("blue"); // ["red", "green", "blue"] - ``` - - @method unshift - @return {Ember.Set} The set itself. - */ - unshift: Ember.aliasMethod('push'), - - /** - Adds each object in the passed enumerable to the set. - - This is an alias of `Ember.MutableEnumerable.addObjects()` - - ```javascript - var colors = new Ember.Set(); - colors.addEach(["red", "green", "blue"]); // ["red", "green", "blue"] - ``` - - @method addEach - @param {Ember.Enumerable} objects the objects to add. - @return {Ember.Set} The set itself. - */ - addEach: Ember.aliasMethod('addObjects'), - - /** - Removes each object in the passed enumerable to the set. - - This is an alias of `Ember.MutableEnumerable.removeObjects()` - - ```javascript - var colors = new Ember.Set(["red", "green", "blue"]); - colors.removeEach(["red", "blue"]); // ["green"] - ``` - - @method removeEach - @param {Ember.Enumerable} objects the objects to remove. - @return {Ember.Set} The set itself. - */ - removeEach: Ember.aliasMethod('removeObjects'), - - // .......................................................... - // PRIVATE ENUMERABLE SUPPORT - // - - init: function(items) { - this._super(); - if (items) this.addObjects(items); - }, - - // implement Ember.Enumerable - nextObject: function(idx) { - return this[idx]; - }, - - // more optimized version - firstObject: Ember.computed(function() { - return this.length > 0 ? this[0] : undefined; - }), - - // more optimized version - lastObject: Ember.computed(function() { - return this.length > 0 ? this[this.length-1] : undefined; - }), - - // implements Ember.MutableEnumerable - addObject: function(obj) { - if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR); - if (none(obj)) return this; // nothing to do - - var guid = guidFor(obj), - idx = this[guid], - len = get(this, 'length'), - added ; - - if (idx>=0 && idx<len && (this[idx] === obj)) return this; // added - - added = [obj]; - - this.enumerableContentWillChange(null, added); - Ember.propertyWillChange(this, 'lastObject'); - - len = get(this, 'length'); - this[guid] = len; - this[len] = obj; - set(this, 'length', len+1); - - Ember.propertyDidChange(this, 'lastObject'); - this.enumerableContentDidChange(null, added); - - return this; - }, - - // implements Ember.MutableEnumerable - removeObject: function(obj) { - if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR); - if (none(obj)) return this; // nothing to do - - var guid = guidFor(obj), - idx = this[guid], - len = get(this, 'length'), - isFirst = idx === 0, - isLast = idx === len-1, - last, removed; - - - if (idx>=0 && idx<len && (this[idx] === obj)) { - removed = [obj]; - - this.enumerableContentWillChange(removed, null); - if (isFirst) { Ember.propertyWillChange(this, 'firstObject'); } - if (isLast) { Ember.propertyWillChange(this, 'lastObject'); } - - // swap items - basically move the item to the end so it can be removed - if (idx < len-1) { - last = this[len-1]; - this[idx] = last; - this[guidFor(last)] = idx; - } - - delete this[guid]; - delete this[len-1]; - set(this, 'length', len-1); - - if (isFirst) { Ember.propertyDidChange(this, 'firstObject'); } - if (isLast) { Ember.propertyDidChange(this, 'lastObject'); } - this.enumerableContentDidChange(removed, null); - } - - return this; - }, - - // optimized version - contains: function(obj) { - return this[guidFor(obj)]>=0; - }, - - copy: function() { - var C = this.constructor, ret = new C(), loc = get(this, 'length'); - set(ret, 'length', loc); - while(--loc>=0) { - ret[loc] = this[loc]; - ret[guidFor(this[loc])] = loc; - } - return ret; - }, - - toString: function() { - var len = this.length, idx, array = []; - for(idx = 0; idx < len; idx++) { - array[idx] = this[idx]; - } - return "Ember.Set<%@>".fmt(array.join(',')); - } - -}); - -})(); - - - -(function() { -/** -@module ember -@submodule ember-runtime -*/ - -/** `Ember.Object` is the main base class for all Ember objects. It is a subclass of `Ember.CoreObject` with the `Ember.Observable` mixin applied. For details, see the documentation for each of these. @class Object @@ -10980,10 +10656,12 @@ /** @module ember @submodule ember-runtime */ +var OUT_OF_RANGE_EXCEPTION = "Index out of range"; +var EMPTY = []; var get = Ember.get, set = Ember.set; /** An ArrayProxy wraps any other object that implements `Ember.Array` and/or @@ -11183,16 +10861,103 @@ var arrangedContent = get(this, 'arrangedContent'); return arrangedContent ? get(arrangedContent, 'length') : 0; // No dependencies since Enumerable notifies length of change }), - replace: function(idx, amt, objects) { + _replace: function(idx, amt, objects) { + var content = get(this, 'content'); - if (get(this, 'content')) this.replaceContent(idx, amt, objects); + if (content) this.replaceContent(idx, amt, objects); return this; }, + replace: function() { + if (get(this, 'arrangedContent') === get(this, 'content')) { + this._replace.apply(this, arguments); + } else { + throw new Ember.Error("Using replace on an arranged ArrayProxy is not allowed."); + } + }, + + _insertAt: function(idx, object) { + if (idx > get(this, 'content.length')) throw new Error(OUT_OF_RANGE_EXCEPTION); + this._replace(idx, 0, [object]); + return this; + }, + + insertAt: function(idx, object) { + if (get(this, 'arrangedContent') === get(this, 'content')) { + return this._insertAt(idx, object); + } else { + throw new Ember.Error("Using insertAt on an arranged ArrayProxy is not allowed."); + } + }, + + removeAt: function(start, len) { + if ('number' === typeof start) { + var content = get(this, 'content'), + arrangedContent = get(this, 'arrangedContent'), + indices = [], i; + + if ((start < 0) || (start >= get(this, 'length'))) { + throw new Error(OUT_OF_RANGE_EXCEPTION); + } + + if (len === undefined) len = 1; + + // Get a list of indices in original content to remove + for (i=start; i<start+len; i++) { + // Use arrangedContent here so we avoid confusion with objects transformed by objectAtContent + indices.push(content.indexOf(arrangedContent.objectAt(i))); + } + + // Replace in reverse order since indices will change + indices.sort(function(a,b) { return b - a; }); + + Ember.beginPropertyChanges(); + for (i=0; i<indices.length; i++) { + this._replace(indices[i], 1, EMPTY); + } + Ember.endPropertyChanges(); + } + + return this ; + }, + + pushObject: function(obj) { + this._insertAt(get(this, 'content.length'), obj) ; + return obj ; + }, + + pushObjects: function(objects) { + this._replace(get(this, 'length'), 0, objects); + return this; + }, + + setObjects: function(objects) { + if (objects.length === 0) return this.clear(); + + var len = get(this, 'length'); + this._replace(0, len, objects); + return this; + }, + + unshiftObject: function(obj) { + this._insertAt(0, obj) ; + return obj ; + }, + + unshiftObjects: function(objects) { + this._replace(0, 0, objects); + return this; + }, + + slice: function() { + var arr = this.toArray(); + return arr.slice.apply(arr, arguments); + }, + arrangedContentArrayWillChange: function(item, idx, removedCnt, addedCnt) { this.arrayContentWillChange(idx, removedCnt, addedCnt); }, arrangedContentArrayDidChange: function(item, idx, removedCnt, addedCnt) { @@ -11353,10 +11118,29 @@ return set(content, key, value); } }); +Ember.ObjectProxy.reopenClass({ + create: function () { + var mixin, prototype, i, l, properties, keyName; + if (arguments.length) { + prototype = this.proto(); + for (i = 0, l = arguments.length; i < l; i++) { + properties = arguments[i]; + for (keyName in properties) { + if (!properties.hasOwnProperty(keyName) || keyName in prototype) { continue; } + if (!mixin) mixin = {}; + mixin[keyName] = null; + } + } + if (mixin) this._initMixins([mixin]); + } + return this._super.apply(this, arguments); + } +}); + })(); (function() { @@ -11400,11 +11184,11 @@ var item = content.objectAt(loc); if (item) { Ember.addBeforeObserver(item, keyName, proxy, 'contentKeyWillChange'); Ember.addObserver(item, keyName, proxy, 'contentKeyDidChange'); - // keep track of the indicies each item was found at so we can map + // keep track of the index each item was found at so we can map // it back when the obj changes. guid = guidFor(item); if (!objects[guid]) objects[guid] = []; objects[guid].push(loc); } @@ -11472,11 +11256,11 @@ // .......................................................... // ARRAY CHANGES // Invokes whenever the content array itself changes. arrayWillChange: function(content, idx, removedCnt, addedCnt) { - var keys = this._keys, key, array, lim; + var keys = this._keys, key, lim; lim = removedCnt>0 ? idx+removedCnt : -1; Ember.beginPropertyChanges(this); for(key in keys) { @@ -11490,11 +11274,11 @@ Ember.propertyWillChange(this._content, '@each'); Ember.endPropertyChanges(this); }, arrayDidChange: function(content, idx, removedCnt, addedCnt) { - var keys = this._keys, key, array, lim; + var keys = this._keys, key, lim; lim = addedCnt>0 ? idx+addedCnt : -1; Ember.beginPropertyChanges(this); for(key in keys) { @@ -11673,11 +11457,11 @@ } /** The NativeArray mixin contains the properties needed to to make the native Array support Ember.MutableArray and all of its dependent APIs. Unless you - have `Ember.EXTEND_PROTOTYPES or `Ember.EXTEND_PROTOTYPES.Array` set to + have `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Array` set to false, this will be applied automatically. Otherwise you can apply the mixin at anytime by calling `Ember.NativeArray.activate`. @class NativeArray @namespace Ember @@ -11725,12 +11509,468 @@ })(); (function() { +/** +@module ember +@submodule ember-runtime +*/ + +var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor, none = Ember.isNone, fmt = Ember.String.fmt; + +/** + An unordered collection of objects. + + A Set works a bit like an array except that its items are not ordered. You + can create a set to efficiently test for membership for an object. You can + also iterate through a set just like an array, even accessing objects by + index, however there is no guarantee as to their order. + + All Sets are observable via the Enumerable Observer API - which works + on any enumerable object including both Sets and Arrays. + + ## Creating a Set + + You can create a set like you would most objects using + `new Ember.Set()`. Most new sets you create will be empty, but you can + also initialize the set with some content by passing an array or other + enumerable of objects to the constructor. + + Finally, you can pass in an existing set and the set will be copied. You + can also create a copy of a set by calling `Ember.Set#copy()`. + + ```javascript + // creates a new empty set + var foundNames = new Ember.Set(); + + // creates a set with four names in it. + var names = new Ember.Set(["Charles", "Tom", "Juan", "Alex"]); // :P + + // creates a copy of the names set. + var namesCopy = new Ember.Set(names); + + // same as above. + var anotherNamesCopy = names.copy(); + ``` + + ## Adding/Removing Objects + + You generally add or remove objects from a set using `add()` or + `remove()`. You can add any type of object including primitives such as + numbers, strings, and booleans. + + Unlike arrays, objects can only exist one time in a set. If you call `add()` + on a set with the same object multiple times, the object will only be added + once. Likewise, calling `remove()` with the same object multiple times will + remove the object the first time and have no effect on future calls until + you add the object to the set again. + + NOTE: You cannot add/remove `null` or `undefined` to a set. Any attempt to do + so will be ignored. + + In addition to add/remove you can also call `push()`/`pop()`. Push behaves + just like `add()` but `pop()`, unlike `remove()` will pick an arbitrary + object, remove it and return it. This is a good way to use a set as a job + queue when you don't care which order the jobs are executed in. + + ## Testing for an Object + + To test for an object's presence in a set you simply call + `Ember.Set#contains()`. + + ## Observing changes + + When using `Ember.Set`, you can observe the `"[]"` property to be + alerted whenever the content changes. You can also add an enumerable + observer to the set to be notified of specific objects that are added and + removed from the set. See `Ember.Enumerable` for more information on + enumerables. + + This is often unhelpful. If you are filtering sets of objects, for instance, + it is very inefficient to re-filter all of the items each time the set + changes. It would be better if you could just adjust the filtered set based + on what was changed on the original set. The same issue applies to merging + sets, as well. + + ## Other Methods + + `Ember.Set` primary implements other mixin APIs. For a complete reference + on the methods you will use with `Ember.Set`, please consult these mixins. + The most useful ones will be `Ember.Enumerable` and + `Ember.MutableEnumerable` which implement most of the common iterator + methods you are used to on Array. + + Note that you can also use the `Ember.Copyable` and `Ember.Freezable` + APIs on `Ember.Set` as well. Once a set is frozen it can no longer be + modified. The benefit of this is that when you call `frozenCopy()` on it, + Ember will avoid making copies of the set. This allows you to write + code that can know with certainty when the underlying set data will or + will not be modified. + + @class Set + @namespace Ember + @extends Ember.CoreObject + @uses Ember.MutableEnumerable + @uses Ember.Copyable + @uses Ember.Freezable + @since Ember 0.9 +*/ +Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Ember.Freezable, + /** @scope Ember.Set.prototype */ { + + // .......................................................... + // IMPLEMENT ENUMERABLE APIS + // + + /** + This property will change as the number of objects in the set changes. + + @property length + @type number + @default 0 + */ + length: 0, + + /** + Clears the set. This is useful if you want to reuse an existing set + without having to recreate it. + + ```javascript + var colors = new Ember.Set(["red", "green", "blue"]); + colors.length; // 3 + colors.clear(); + colors.length; // 0 + ``` + + @method clear + @return {Ember.Set} An empty Set + */ + clear: function() { + if (this.isFrozen) { throw new Error(Ember.FROZEN_ERROR); } + + var len = get(this, 'length'); + if (len === 0) { return this; } + + var guid; + + this.enumerableContentWillChange(len, 0); + Ember.propertyWillChange(this, 'firstObject'); + Ember.propertyWillChange(this, 'lastObject'); + + for (var i=0; i < len; i++){ + guid = guidFor(this[i]); + delete this[guid]; + delete this[i]; + } + + set(this, 'length', 0); + + Ember.propertyDidChange(this, 'firstObject'); + Ember.propertyDidChange(this, 'lastObject'); + this.enumerableContentDidChange(len, 0); + + return this; + }, + + /** + Returns true if the passed object is also an enumerable that contains the + same objects as the receiver. + + ```javascript + var colors = ["red", "green", "blue"], + same_colors = new Ember.Set(colors); + + same_colors.isEqual(colors); // true + same_colors.isEqual(["purple", "brown"]); // false + ``` + + @method isEqual + @param {Ember.Set} obj the other object. + @return {Boolean} + */ + isEqual: function(obj) { + // fail fast + if (!Ember.Enumerable.detect(obj)) return false; + + var loc = get(this, 'length'); + if (get(obj, 'length') !== loc) return false; + + while(--loc >= 0) { + if (!obj.contains(this[loc])) return false; + } + + return true; + }, + + /** + Adds an object to the set. Only non-`null` objects can be added to a set + and those can only be added once. If the object is already in the set or + the passed value is null this method will have no effect. + + This is an alias for `Ember.MutableEnumerable.addObject()`. + + ```javascript + var colors = new Ember.Set(); + colors.add("blue"); // ["blue"] + colors.add("blue"); // ["blue"] + colors.add("red"); // ["blue", "red"] + colors.add(null); // ["blue", "red"] + colors.add(undefined); // ["blue", "red"] + ``` + + @method add + @param {Object} obj The object to add. + @return {Ember.Set} The set itself. + */ + add: Ember.aliasMethod('addObject'), + + /** + Removes the object from the set if it is found. If you pass a `null` value + or an object that is already not in the set, this method will have no + effect. This is an alias for `Ember.MutableEnumerable.removeObject()`. + + ```javascript + var colors = new Ember.Set(["red", "green", "blue"]); + colors.remove("red"); // ["blue", "green"] + colors.remove("purple"); // ["blue", "green"] + colors.remove(null); // ["blue", "green"] + ``` + + @method remove + @param {Object} obj The object to remove + @return {Ember.Set} The set itself. + */ + remove: Ember.aliasMethod('removeObject'), + + /** + Removes the last element from the set and returns it, or `null` if it's empty. + + ```javascript + var colors = new Ember.Set(["green", "blue"]); + colors.pop(); // "blue" + colors.pop(); // "green" + colors.pop(); // null + ``` + + @method pop + @return {Object} The removed object from the set or null. + */ + pop: function() { + if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR); + var obj = this.length > 0 ? this[this.length-1] : null; + this.remove(obj); + return obj; + }, + + /** + Inserts the given object on to the end of the set. It returns + the set itself. + + This is an alias for `Ember.MutableEnumerable.addObject()`. + + ```javascript + var colors = new Ember.Set(); + colors.push("red"); // ["red"] + colors.push("green"); // ["red", "green"] + colors.push("blue"); // ["red", "green", "blue"] + ``` + + @method push + @return {Ember.Set} The set itself. + */ + push: Ember.aliasMethod('addObject'), + + /** + Removes the last element from the set and returns it, or `null` if it's empty. + + This is an alias for `Ember.Set.pop()`. + + ```javascript + var colors = new Ember.Set(["green", "blue"]); + colors.shift(); // "blue" + colors.shift(); // "green" + colors.shift(); // null + ``` + + @method shift + @return {Object} The removed object from the set or null. + */ + shift: Ember.aliasMethod('pop'), + + /** + Inserts the given object on to the end of the set. It returns + the set itself. + + This is an alias of `Ember.Set.push()` + + ```javascript + var colors = new Ember.Set(); + colors.unshift("red"); // ["red"] + colors.unshift("green"); // ["red", "green"] + colors.unshift("blue"); // ["red", "green", "blue"] + ``` + + @method unshift + @return {Ember.Set} The set itself. + */ + unshift: Ember.aliasMethod('push'), + + /** + Adds each object in the passed enumerable to the set. + + This is an alias of `Ember.MutableEnumerable.addObjects()` + + ```javascript + var colors = new Ember.Set(); + colors.addEach(["red", "green", "blue"]); // ["red", "green", "blue"] + ``` + + @method addEach + @param {Ember.Enumerable} objects the objects to add. + @return {Ember.Set} The set itself. + */ + addEach: Ember.aliasMethod('addObjects'), + + /** + Removes each object in the passed enumerable to the set. + + This is an alias of `Ember.MutableEnumerable.removeObjects()` + + ```javascript + var colors = new Ember.Set(["red", "green", "blue"]); + colors.removeEach(["red", "blue"]); // ["green"] + ``` + + @method removeEach + @param {Ember.Enumerable} objects the objects to remove. + @return {Ember.Set} The set itself. + */ + removeEach: Ember.aliasMethod('removeObjects'), + + // .......................................................... + // PRIVATE ENUMERABLE SUPPORT + // + + init: function(items) { + this._super(); + if (items) this.addObjects(items); + }, + + // implement Ember.Enumerable + nextObject: function(idx) { + return this[idx]; + }, + + // more optimized version + firstObject: Ember.computed(function() { + return this.length > 0 ? this[0] : undefined; + }), + + // more optimized version + lastObject: Ember.computed(function() { + return this.length > 0 ? this[this.length-1] : undefined; + }), + + // implements Ember.MutableEnumerable + addObject: function(obj) { + if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR); + if (none(obj)) return this; // nothing to do + + var guid = guidFor(obj), + idx = this[guid], + len = get(this, 'length'), + added ; + + if (idx>=0 && idx<len && (this[idx] === obj)) return this; // added + + added = [obj]; + + this.enumerableContentWillChange(null, added); + Ember.propertyWillChange(this, 'lastObject'); + + len = get(this, 'length'); + this[guid] = len; + this[len] = obj; + set(this, 'length', len+1); + + Ember.propertyDidChange(this, 'lastObject'); + this.enumerableContentDidChange(null, added); + + return this; + }, + + // implements Ember.MutableEnumerable + removeObject: function(obj) { + if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR); + if (none(obj)) return this; // nothing to do + + var guid = guidFor(obj), + idx = this[guid], + len = get(this, 'length'), + isFirst = idx === 0, + isLast = idx === len-1, + last, removed; + + + if (idx>=0 && idx<len && (this[idx] === obj)) { + removed = [obj]; + + this.enumerableContentWillChange(removed, null); + if (isFirst) { Ember.propertyWillChange(this, 'firstObject'); } + if (isLast) { Ember.propertyWillChange(this, 'lastObject'); } + + // swap items - basically move the item to the end so it can be removed + if (idx < len-1) { + last = this[len-1]; + this[idx] = last; + this[guidFor(last)] = idx; + } + + delete this[guid]; + delete this[len-1]; + set(this, 'length', len-1); + + if (isFirst) { Ember.propertyDidChange(this, 'firstObject'); } + if (isLast) { Ember.propertyDidChange(this, 'lastObject'); } + this.enumerableContentDidChange(removed, null); + } + + return this; + }, + + // optimized version + contains: function(obj) { + return this[guidFor(obj)]>=0; + }, + + copy: function() { + var C = this.constructor, ret = new C(), loc = get(this, 'length'); + set(ret, 'length', loc); + while(--loc>=0) { + ret[loc] = this[loc]; + ret[guidFor(this[loc])] = loc; + } + return ret; + }, + + toString: function() { + var len = this.length, idx, array = []; + for(idx = 0; idx < len; idx++) { + array[idx] = this[idx]; + } + return fmt("Ember.Set<%@>", [array.join(',')]); + } + +}); + +})(); + + + +(function() { var DeferredMixin = Ember.DeferredMixin, // mixins/deferred - EmberObject = Ember.Object, // system/object get = Ember.get; var Deferred = Ember.Object.extend(DeferredMixin); Deferred.reopenClass({ @@ -12049,11 +12289,10 @@ var isSorted = get(this, 'isSorted'), sortProperties = get(this, 'sortProperties'); if (isSorted) { var addedObjects = array.slice(idx, idx+addedCount); - var arrangedContent = get(this, 'arrangedContent'); forEach(addedObjects, function(item) { this.insertItemSorted(item); forEach(sortProperties, function(sortProperty) { @@ -12119,12 +12358,12 @@ /** @module ember @submodule ember-runtime */ -var get = Ember.get, set = Ember.set, isGlobalPath = Ember.isGlobalPath, - forEach = Ember.EnumerableUtils.forEach, replace = Ember.EnumerableUtils.replace; +var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach, + replace = Ember.EnumerableUtils.replace; /** `Ember.ArrayController` provides a way for you to publish a collection of objects so that you can easily bind to the collection from a Handlebars `#each` helper, an `Ember.CollectionView`, or other controllers. @@ -12400,20 +12639,21 @@ (function() { /** @module ember @submodule ember-views */ +if (Ember.$) { + // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#dndevents + var dragEvents = Ember.String.w('dragstart drag dragenter dragleave dragover drop dragend'); -// http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#dndevents -var dragEvents = Ember.String.w('dragstart drag dragenter dragleave dragover drop dragend'); + // Copies the `dataTransfer` property from a browser event object onto the + // jQuery event object for the specified events + Ember.EnumerableUtils.forEach(dragEvents, function(eventName) { + Ember.$.event.fixHooks[eventName] = { props: ['dataTransfer'] }; + }); +} -// Copies the `dataTransfer` property from a browser event object onto the -// jQuery event object for the specified events -Ember.EnumerableUtils.forEach(dragEvents, function(eventName) { - Ember.$.event.fixHooks[eventName] = { props: ['dataTransfer'] }; -}); - })(); (function() { @@ -12425,21 +12665,22 @@ /*** BEGIN METAMORPH HELPERS ***/ // Internet Explorer prior to 9 does not allow setting innerHTML if the first element // is a "zero-scope" element. This problem can be worked around by making // the first node an invisible text node. We, like Modernizr, use &shy; -var needsShy = (function(){ + +var needsShy = this.document && (function(){ var testEl = document.createElement('div'); testEl.innerHTML = "<div></div>"; testEl.firstChild.innerHTML = "<script></script>"; return testEl.firstChild.innerHTML === ''; })(); // IE 8 (and likely earlier) likes to move whitespace preceeding // a script tag to appear after it. This means that we can // accidentally remove whitespace when updating a morph. -var movesWhitespace = (function() { +var movesWhitespace = this.document && (function() { var testEl = document.createElement('div'); testEl.innerHTML = "Test: <script type='text/x-placeholder'></script>Value"; return testEl.childNodes[0].nodeValue === 'Test:' && testEl.childNodes[2].nodeValue === ' Value'; })(); @@ -12560,16 +12801,11 @@ @module ember @submodule ember-views */ var get = Ember.get, set = Ember.set; -var indexOf = Ember.ArrayPolyfills.indexOf; - - - - var ClassSet = function() { this.seen = {}; this.list = []; }; @@ -12736,11 +12972,11 @@ @param {String} className Class name to add to the buffer @chainable */ addClass: function(className) { // lazily create elementClasses - var elementClasses = this.elementClasses = (this.elementClasses || new ClassSet()); + this.elementClasses = (this.elementClasses || new ClassSet()); this.elementClasses.add(className); this.classes = this.elementClasses.list; return this; }, @@ -12841,11 +13077,11 @@ @param {String} name Name of the style @param {String} value @chainable */ style: function(name, value) { - var style = this.elementStyle = (this.elementStyle || {}); + this.elementStyle = (this.elementStyle || {}); this.elementStyle[name] = value; return this; }, @@ -13265,12 +13501,13 @@ */ // Add a new named queue for rendering views that happens // after bindings have synced, and a queue for scheduling actions // that that should occur after view rendering. -var queues = Ember.run.queues; -queues.splice(Ember.$.inArray('actions', queues)+1, 0, 'render', 'afterRender'); +var queues = Ember.run.queues, + indexOf = Ember.ArrayPolyfills.indexOf; +queues.splice(indexOf.call(queues, 'actions')+1, 0, 'render', 'afterRender'); })(); @@ -13302,12 +13539,11 @@ this._super(); set(this, '_childContainers', {}); }, _modelDidChange: Ember.observer(function() { - var containers = get(this, '_childContainers'), - container; + var containers = get(this, '_childContainers'); for (var prop in containers) { if (!containers.hasOwnProperty(prop)) { continue; } containers[prop].destroy(); } @@ -13332,13 +13568,12 @@ /** @module ember @submodule ember-views */ -var get = Ember.get, set = Ember.set, addObserver = Ember.addObserver, removeObserver = Ember.removeObserver; -var meta = Ember.meta, guidFor = Ember.guidFor, fmt = Ember.String.fmt; -var a_slice = [].slice; +var get = Ember.get, set = Ember.set; +var guidFor = Ember.guidFor; var a_forEach = Ember.EnumerableUtils.forEach; var a_addObject = Ember.EnumerableUtils.addObject; var childViewsProperty = Ember.computed(function() { var childViews = this._childViews, ret = Ember.A(), view = this; @@ -14055,19 +14290,19 @@ * `focusIn` * `focusOut` * `mouseEnter` * `mouseLeave` - Form events: + Form events: * `submit` * `change` * `focusIn` * `focusOut` * `input` - HTML5 drag and drop events: + HTML5 drag and drop events: * `dragStart` * `drag` * `dragEnter` * `dragLeave` @@ -14605,11 +14840,10 @@ // Create an observer to add/remove/change the attribute if the // JavaScript property changes. var observer = function() { elem = this.$(); - if (!elem) { return; } attributeValue = get(this, property); Ember.View.applyAttributeBindings(elem, attributeName, attributeValue); }; @@ -14800,10 +15034,14 @@ /** 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 finished synchronizing. @method append @@ -15471,14 +15709,22 @@ handleEvent: function(eventName, evt) { return this.currentState.handleEvent(this, eventName, evt); }, registerObserver: function(root, path, target, observer) { - Ember.addObserver(root, path, target, observer); + if (!observer && 'function' === typeof target) { + observer = target; + target = null; + } + var view = this, + stateCheckedObserver = function(){ + view.currentState.invokeObserver(this, observer); + }; + Ember.addObserver(root, path, target, stateCheckedObserver); this.one('willClearRender', function() { - Ember.removeObserver(root, path, target, observer); + Ember.removeObserver(root, path, target, stateCheckedObserver); }); } }); @@ -15505,39 +15751,49 @@ // inside the buffer, legal manipulations are done on the buffer // once the view has been inserted into the DOM, legal manipulations // are done on the DOM element. +function notifyMutationListeners() { + Ember.run.once(Ember.View, 'notifyMutationListeners'); +} + var DOMManager = { prepend: function(view, html) { view.$().prepend(html); + notifyMutationListeners(); }, after: function(view, html) { view.$().after(html); + notifyMutationListeners(); }, html: function(view, html) { view.$().html(html); + notifyMutationListeners(); }, replace: function(view) { var element = get(view, 'element'); set(view, 'element', null); view._insertElementLater(function() { Ember.$(element).replaceWith(get(view, 'element')); + notifyMutationListeners(); }); }, remove: function(view) { view.$().remove(); + notifyMutationListeners(); }, empty: function(view) { view.$().empty(); + notifyMutationListeners(); } }; Ember.View.reopen({ domManager: DOMManager @@ -15594,18 +15850,18 @@ Get the class name for a given value, based on the path, optional `className` and optional `falsyClassName`. - if a `className` or `falsyClassName` has been specified: - - if the value is truthy and `className` has been specified, + - if the value is truthy and `className` has been specified, `className` is returned - - if the value is falsy and `falsyClassName` has been specified, + - if the value is falsy and `falsyClassName` has been specified, `falsyClassName` is returned - otherwise `null` is returned - - if the value is `true`, the dasherized last part of the supplied path + - if the value is `true`, the dasherized last part of the supplied path is returned - - if the value is not `false`, `undefined` or `null`, the `value` + - if the value is not `false`, `undefined` or `null`, the `value` is returned - if none of the above rules apply, `null` is returned @method _classStringForValue @param path @@ -15648,10 +15904,24 @@ return null; } } }); +var mutation = Ember.Object.extend(Ember.Evented).create(); + +Ember.View.addMutationListener = function(callback) { + mutation.on('change', callback); +}; + +Ember.View.removeMutationListener = function(callback) { + mutation.off('change', callback); +}; + +Ember.View.notifyMutationListeners = function() { + mutation.trigger('change'); +}; + /** Global views hash @property views @static @@ -15673,10 +15943,13 @@ if (name !== 'value' && (type === 'string' || (type === 'number' && !isNaN(value)))) { if (value !== elem.attr(name)) { 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); } } else if (!value) { @@ -15728,11 +16001,12 @@ renderToBufferIfNeeded: function () { return false; }, - rerender: Ember.K + rerender: Ember.K, + invokeObserver: Ember.K }; })(); @@ -15779,11 +16053,11 @@ /** @module ember @submodule ember-views */ -var get = Ember.get, set = Ember.set, meta = Ember.meta; +var get = Ember.get, set = Ember.set; var inBuffer = Ember.View.states.inBuffer = Ember.create(Ember.View.states._default); Ember.merge(inBuffer, { $: function(view, sel) { @@ -15849,10 +16123,14 @@ view.clearBuffer(); view.transitionTo('hasElement'); } return value; + }, + + invokeObserver: function(target, observer) { + observer.call(target); } }); })(); @@ -15863,11 +16141,11 @@ /** @module ember @submodule ember-views */ -var get = Ember.get, set = Ember.set, meta = Ember.meta; +var get = Ember.get, set = Ember.set; var hasElement = Ember.View.states.hasElement = Ember.create(Ember.View.states._default); Ember.merge(hasElement, { $: function(view, sel) { @@ -15936,10 +16214,14 @@ // preventDefault or stopPropagation. return view.trigger(eventName, evt); } else { return true; // continue event propagation } + }, + + invokeObserver: function(target, observer) { + observer.call(target); } }); var inDOM = Ember.View.states.inDOM = Ember.create(hasElement); @@ -16004,12 +16286,10 @@ into.destroyed = Ember.create(into._default); into.inBuffer = Ember.create(into._default); into.hasElement = Ember.create(into._default); into.inDOM = Ember.create(into.hasElement); - var viewState; - for (var stateName in from) { if (!from.hasOwnProperty(stateName)) { continue; } Ember.merge(into[stateName], from[stateName]); } @@ -16026,11 +16306,11 @@ /** @module ember @submodule ember-views */ -var get = Ember.get, set = Ember.set, meta = Ember.meta; +var get = Ember.get, set = Ember.set; var forEach = Ember.EnumerableUtils.forEach; /** A `ContainerView` is an `Ember.View` subclass that implements `Ember.MutableArray` allowing programatic management of its child views. @@ -16451,11 +16731,11 @@ instance of `Ember.View`. You can supply a different class by setting the `CollectionView`'s `itemViewClass` property. Given an empty `<body>` and the following code: - ```javascript + ```javascript someItemsView = Ember.CollectionView.create({ classNames: ['a-collection'], content: ['A','B','C'], itemViewClass: Ember.View.extend({ template: Ember.Handlebars.compile("the letter: {{view.content}}") @@ -16704,11 +16984,11 @@ @param {Array} removedObjects the objects that were removed from the content @param {Number} changeIndex the index at which the changes occurred */ arrayDidChange: function(content, start, removed, added) { var itemViewClass = get(this, 'itemViewClass'), - addedViews = [], view, item, idx, len, itemTagName; + addedViews = [], view, item, idx, len; if ('string' === typeof itemViewClass) { itemViewClass = get(itemViewClass); } @@ -16806,30 +17086,30 @@ // Copyright: ©2011 My Company Inc. All rights reserved. // ========================================================================== var K = function(){}, guid = 0, - document = window.document, + document = this.document, // Feature-detect the W3C range API, the extended check is for IE9 which only partially supports ranges - supportsRange = ('createRange' in document) && (typeof Range !== 'undefined') && Range.prototype.createContextualFragment, + supportsRange = document && ('createRange' in document) && (typeof Range !== 'undefined') && Range.prototype.createContextualFragment, // Internet Explorer prior to 9 does not allow setting innerHTML if the first element // is a "zero-scope" element. This problem can be worked around by making // the first node an invisible text node. We, like Modernizr, use &shy; - needsShy = (function(){ + needsShy = document && (function(){ var testEl = document.createElement('div'); testEl.innerHTML = "<div></div>"; testEl.firstChild.innerHTML = "<script></script>"; return testEl.firstChild.innerHTML === ''; })(), // IE 8 (and likely earlier) likes to move whitespace preceeding // a script tag to appear after it. This means that we can // accidentally remove whitespace when updating a morph. - movesWhitespace = (function() { + movesWhitespace = document && (function() { var testEl = document.createElement('div'); testEl.innerHTML = "Test: <script type='text/x-placeholder'></script>Value"; return testEl.childNodes[0].nodeValue === 'Test:' && testEl.childNodes[2].nodeValue === ' Value'; })(); @@ -17257,2218 +17537,13 @@ }); })(); (function() { -/* - -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*/ -this.Handlebars = {}; - -(function(Handlebars) { - -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 = {}; - -Handlebars.registerHelper = function(name, fn, inverse) { - if(inverse) { fn.not = inverse; } - this.helpers[name] = fn; -}; - -Handlebars.registerPartial = function(name, str) { - this.partials[name] = str; -}; - -Handlebars.registerHelper('helperMissing', function(arg) { - if(arguments.length === 2) { - return undefined; - } else { - throw new Error("Could not find property '" + arg + "'"); - } -}); - -var toString = Object.prototype.toString, functionType = "[object Function]"; - -Handlebars.registerHelper('blockHelperMissing', function(context, options) { - var inverse = options.inverse || function() {}, fn = options.fn; - - - var ret = ""; - var type = toString.call(context); - - if(type === functionType) { context = context.call(this); } - - if(context === true) { - return fn(this); - } else if(context === false || context == null) { - return inverse(this); - } else if(type === "[object Array]") { - if(context.length > 0) { - return Handlebars.helpers.each(context, options); - } else { - return inverse(this); - } - } else { - return fn(context); - } -}); - -Handlebars.K = function() {}; - -Handlebars.createFrame = Object.create || function(object) { - Handlebars.K.prototype = object; - var obj = new Handlebars.K(); - Handlebars.K.prototype = null; - return obj; -}; - -Handlebars.logger = { - DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3, - - methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'}, - - // can be overridden in the host environment - log: function(level, obj) { - if (Handlebars.logger.level <= level) { - var method = Handlebars.logger.methodMap[level]; - if (typeof console !== 'undefined' && console[method]) { - console[method].call(console, obj); - } - } - } -}; - -Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); }; - -Handlebars.registerHelper('each', function(context, options) { - var fn = options.fn, inverse = options.inverse; - var i = 0, ret = "", data; - - if (options.data) { - data = Handlebars.createFrame(options.data); - } - - if(context && typeof context === 'object') { - if(context instanceof Array){ - for(var j = context.length; i<j; i++) { - if (data) { data.index = i; } - ret = ret + fn(context[i], { data: data }); - } - } else { - for(var key in context) { - if(context.hasOwnProperty(key)) { - if(data) { data.key = key; } - ret = ret + fn(context[key], {data: data}); - i++; - } - } - } - } - - if(i === 0){ - ret = inverse(this); - } - - return ret; -}); - -Handlebars.registerHelper('if', function(context, options) { - var type = toString.call(context); - if(type === functionType) { context = context.call(this); } - - if(!context || Handlebars.Utils.isEmpty(context)) { - return options.inverse(this); - } else { - return options.fn(this); - } -}); - -Handlebars.registerHelper('unless', function(context, options) { - var fn = options.fn, inverse = options.inverse; - options.fn = inverse; - options.inverse = fn; - - return Handlebars.helpers['if'].call(this, context, options); -}); - -Handlebars.registerHelper('with', function(context, options) { - return options.fn(context); -}); - -Handlebars.registerHelper('log', function(context, options) { - var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1; - Handlebars.log(level, context); -}); - -}(this.Handlebars)); -; -// lib/handlebars/compiler/parser.js -/* Jison generated parser */ -var handlebars = (function(){ -var parser = {trace: function trace() { }, -yy: {}, -symbols_: {"error":2,"root":3,"program":4,"EOF":5,"simpleInverse":6,"statements":7,"statement":8,"openInverse":9,"closeBlock":10,"openBlock":11,"mustache":12,"partial":13,"CONTENT":14,"COMMENT":15,"OPEN_BLOCK":16,"inMustache":17,"CLOSE":18,"OPEN_INVERSE":19,"OPEN_ENDBLOCK":20,"path":21,"OPEN":22,"OPEN_UNESCAPED":23,"OPEN_PARTIAL":24,"partialName":25,"params":26,"hash":27,"DATA":28,"param":29,"STRING":30,"INTEGER":31,"BOOLEAN":32,"hashSegments":33,"hashSegment":34,"ID":35,"EQUALS":36,"PARTIAL_NAME":37,"pathSegments":38,"SEP":39,"$accept":0,"$end":1}, -terminals_: {2:"error",5:"EOF",14:"CONTENT",15:"COMMENT",16:"OPEN_BLOCK",18:"CLOSE",19:"OPEN_INVERSE",20:"OPEN_ENDBLOCK",22:"OPEN",23:"OPEN_UNESCAPED",24:"OPEN_PARTIAL",28:"DATA",30:"STRING",31:"INTEGER",32:"BOOLEAN",35:"ID",36:"EQUALS",37:"PARTIAL_NAME",39:"SEP"}, -productions_: [0,[3,2],[4,2],[4,3],[4,2],[4,1],[4,1],[4,0],[7,1],[7,2],[8,3],[8,3],[8,1],[8,1],[8,1],[8,1],[11,3],[9,3],[10,3],[12,3],[12,3],[13,3],[13,4],[6,2],[17,3],[17,2],[17,2],[17,1],[17,1],[26,2],[26,1],[29,1],[29,1],[29,1],[29,1],[29,1],[27,1],[33,2],[33,1],[34,3],[34,3],[34,3],[34,3],[34,3],[25,1],[21,1],[38,3],[38,1]], -performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) { - -var $0 = $$.length - 1; -switch (yystate) { -case 1: return $$[$0-1]; -break; -case 2: this.$ = new yy.ProgramNode([], $$[$0]); -break; -case 3: this.$ = new yy.ProgramNode($$[$0-2], $$[$0]); -break; -case 4: this.$ = new yy.ProgramNode($$[$0-1], []); -break; -case 5: this.$ = new yy.ProgramNode($$[$0]); -break; -case 6: this.$ = new yy.ProgramNode([], []); -break; -case 7: this.$ = new yy.ProgramNode([]); -break; -case 8: this.$ = [$$[$0]]; -break; -case 9: $$[$0-1].push($$[$0]); this.$ = $$[$0-1]; -break; -case 10: this.$ = new yy.BlockNode($$[$0-2], $$[$0-1].inverse, $$[$0-1], $$[$0]); -break; -case 11: this.$ = new yy.BlockNode($$[$0-2], $$[$0-1], $$[$0-1].inverse, $$[$0]); -break; -case 12: this.$ = $$[$0]; -break; -case 13: this.$ = $$[$0]; -break; -case 14: this.$ = new yy.ContentNode($$[$0]); -break; -case 15: this.$ = new yy.CommentNode($$[$0]); -break; -case 16: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1]); -break; -case 17: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1]); -break; -case 18: this.$ = $$[$0-1]; -break; -case 19: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1]); -break; -case 20: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], true); -break; -case 21: this.$ = new yy.PartialNode($$[$0-1]); -break; -case 22: this.$ = new yy.PartialNode($$[$0-2], $$[$0-1]); -break; -case 23: -break; -case 24: this.$ = [[$$[$0-2]].concat($$[$0-1]), $$[$0]]; -break; -case 25: this.$ = [[$$[$0-1]].concat($$[$0]), null]; -break; -case 26: this.$ = [[$$[$0-1]], $$[$0]]; -break; -case 27: this.$ = [[$$[$0]], null]; -break; -case 28: this.$ = [[new yy.DataNode($$[$0])], null]; -break; -case 29: $$[$0-1].push($$[$0]); this.$ = $$[$0-1]; -break; -case 30: this.$ = [$$[$0]]; -break; -case 31: this.$ = $$[$0]; -break; -case 32: this.$ = new yy.StringNode($$[$0]); -break; -case 33: this.$ = new yy.IntegerNode($$[$0]); -break; -case 34: this.$ = new yy.BooleanNode($$[$0]); -break; -case 35: this.$ = new yy.DataNode($$[$0]); -break; -case 36: this.$ = new yy.HashNode($$[$0]); -break; -case 37: $$[$0-1].push($$[$0]); this.$ = $$[$0-1]; -break; -case 38: this.$ = [$$[$0]]; -break; -case 39: this.$ = [$$[$0-2], $$[$0]]; -break; -case 40: this.$ = [$$[$0-2], new yy.StringNode($$[$0])]; -break; -case 41: this.$ = [$$[$0-2], new yy.IntegerNode($$[$0])]; -break; -case 42: this.$ = [$$[$0-2], new yy.BooleanNode($$[$0])]; -break; -case 43: this.$ = [$$[$0-2], new yy.DataNode($$[$0])]; -break; -case 44: this.$ = new yy.PartialNameNode($$[$0]); -break; -case 45: this.$ = new yy.IdNode($$[$0]); -break; -case 46: $$[$0-2].push($$[$0]); this.$ = $$[$0-2]; -break; -case 47: this.$ = [$$[$0]]; -break; -} -}, -table: [{3:1,4:2,5:[2,7],6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],22:[1,14],23:[1,15],24:[1,16]},{1:[3]},{5:[1,17]},{5:[2,6],7:18,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,6],22:[1,14],23:[1,15],24:[1,16]},{5:[2,5],6:20,8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,5],22:[1,14],23:[1,15],24:[1,16]},{17:23,18:[1,22],21:24,28:[1,25],35:[1,27],38:26},{5:[2,8],14:[2,8],15:[2,8],16:[2,8],19:[2,8],20:[2,8],22:[2,8],23:[2,8],24:[2,8]},{4:28,6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,7],22:[1,14],23:[1,15],24:[1,16]},{4:29,6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,7],22:[1,14],23:[1,15],24:[1,16]},{5:[2,12],14:[2,12],15:[2,12],16:[2,12],19:[2,12],20:[2,12],22:[2,12],23:[2,12],24:[2,12]},{5:[2,13],14:[2,13],15:[2,13],16:[2,13],19:[2,13],20:[2,13],22:[2,13],23:[2,13],24:[2,13]},{5:[2,14],14:[2,14],15:[2,14],16:[2,14],19:[2,14],20:[2,14],22:[2,14],23:[2,14],24:[2,14]},{5:[2,15],14:[2,15],15:[2,15],16:[2,15],19:[2,15],20:[2,15],22:[2,15],23:[2,15],24:[2,15]},{17:30,21:24,28:[1,25],35:[1,27],38:26},{17:31,21:24,28:[1,25],35:[1,27],38:26},{17:32,21:24,28:[1,25],35:[1,27],38:26},{25:33,37:[1,34]},{1:[2,1]},{5:[2,2],8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,2],22:[1,14],23:[1,15],24:[1,16]},{17:23,21:24,28:[1,25],35:[1,27],38:26},{5:[2,4],7:35,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,4],22:[1,14],23:[1,15],24:[1,16]},{5:[2,9],14:[2,9],15:[2,9],16:[2,9],19:[2,9],20:[2,9],22:[2,9],23:[2,9],24:[2,9]},{5:[2,23],14:[2,23],15:[2,23],16:[2,23],19:[2,23],20:[2,23],22:[2,23],23:[2,23],24:[2,23]},{18:[1,36]},{18:[2,27],21:41,26:37,27:38,28:[1,45],29:39,30:[1,42],31:[1,43],32:[1,44],33:40,34:46,35:[1,47],38:26},{18:[2,28]},{18:[2,45],28:[2,45],30:[2,45],31:[2,45],32:[2,45],35:[2,45],39:[1,48]},{18:[2,47],28:[2,47],30:[2,47],31:[2,47],32:[2,47],35:[2,47],39:[2,47]},{10:49,20:[1,50]},{10:51,20:[1,50]},{18:[1,52]},{18:[1,53]},{18:[1,54]},{18:[1,55],21:56,35:[1,27],38:26},{18:[2,44],35:[2,44]},{5:[2,3],8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,3],22:[1,14],23:[1,15],24:[1,16]},{14:[2,17],15:[2,17],16:[2,17],19:[2,17],20:[2,17],22:[2,17],23:[2,17],24:[2,17]},{18:[2,25],21:41,27:57,28:[1,45],29:58,30:[1,42],31:[1,43],32:[1,44],33:40,34:46,35:[1,47],38:26},{18:[2,26]},{18:[2,30],28:[2,30],30:[2,30],31:[2,30],32:[2,30],35:[2,30]},{18:[2,36],34:59,35:[1,60]},{18:[2,31],28:[2,31],30:[2,31],31:[2,31],32:[2,31],35:[2,31]},{18:[2,32],28:[2,32],30:[2,32],31:[2,32],32:[2,32],35:[2,32]},{18:[2,33],28:[2,33],30:[2,33],31:[2,33],32:[2,33],35:[2,33]},{18:[2,34],28:[2,34],30:[2,34],31:[2,34],32:[2,34],35:[2,34]},{18:[2,35],28:[2,35],30:[2,35],31:[2,35],32:[2,35],35:[2,35]},{18:[2,38],35:[2,38]},{18:[2,47],28:[2,47],30:[2,47],31:[2,47],32:[2,47],35:[2,47],36:[1,61],39:[2,47]},{35:[1,62]},{5:[2,10],14:[2,10],15:[2,10],16:[2,10],19:[2,10],20:[2,10],22:[2,10],23:[2,10],24:[2,10]},{21:63,35:[1,27],38:26},{5:[2,11],14:[2,11],15:[2,11],16:[2,11],19:[2,11],20:[2,11],22:[2,11],23:[2,11],24:[2,11]},{14:[2,16],15:[2,16],16:[2,16],19:[2,16],20:[2,16],22:[2,16],23:[2,16],24:[2,16]},{5:[2,19],14:[2,19],15:[2,19],16:[2,19],19:[2,19],20:[2,19],22:[2,19],23:[2,19],24:[2,19]},{5:[2,20],14:[2,20],15:[2,20],16:[2,20],19:[2,20],20:[2,20],22:[2,20],23:[2,20],24:[2,20]},{5:[2,21],14:[2,21],15:[2,21],16:[2,21],19:[2,21],20:[2,21],22:[2,21],23:[2,21],24:[2,21]},{18:[1,64]},{18:[2,24]},{18:[2,29],28:[2,29],30:[2,29],31:[2,29],32:[2,29],35:[2,29]},{18:[2,37],35:[2,37]},{36:[1,61]},{21:65,28:[1,69],30:[1,66],31:[1,67],32:[1,68],35:[1,27],38:26},{18:[2,46],28:[2,46],30:[2,46],31:[2,46],32:[2,46],35:[2,46],39:[2,46]},{18:[1,70]},{5:[2,22],14:[2,22],15:[2,22],16:[2,22],19:[2,22],20:[2,22],22:[2,22],23:[2,22],24:[2,22]},{18:[2,39],35:[2,39]},{18:[2,40],35:[2,40]},{18:[2,41],35:[2,41]},{18:[2,42],35:[2,42]},{18:[2,43],35:[2,43]},{5:[2,18],14:[2,18],15:[2,18],16:[2,18],19:[2,18],20:[2,18],22:[2,18],23:[2,18],24:[2,18]}], -defaultActions: {17:[2,1],25:[2,28],38:[2,26],57:[2,24]}, -parseError: function parseError(str, hash) { - throw new Error(str); -}, -parse: function parse(input) { - var self = this, stack = [0], vstack = [null], lstack = [], table = this.table, yytext = "", yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1; - this.lexer.setInput(input); - this.lexer.yy = this.yy; - this.yy.lexer = this.lexer; - this.yy.parser = this; - if (typeof this.lexer.yylloc == "undefined") - this.lexer.yylloc = {}; - var yyloc = this.lexer.yylloc; - lstack.push(yyloc); - var ranges = this.lexer.options && this.lexer.options.ranges; - if (typeof this.yy.parseError === "function") - this.parseError = this.yy.parseError; - function popStack(n) { - stack.length = stack.length - 2 * n; - vstack.length = vstack.length - n; - lstack.length = lstack.length - n; - } - function lex() { - var token; - token = self.lexer.lex() || 1; - if (typeof token !== "number") { - token = self.symbols_[token] || token; - } - return token; - } - var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected; - while (true) { - state = stack[stack.length - 1]; - if (this.defaultActions[state]) { - action = this.defaultActions[state]; - } else { - if (symbol === null || typeof symbol == "undefined") { - symbol = lex(); - } - action = table[state] && table[state][symbol]; - } - if (typeof action === "undefined" || !action.length || !action[0]) { - var errStr = ""; - if (!recovering) { - expected = []; - for (p in table[state]) - if (this.terminals_[p] && p > 2) { - expected.push("'" + this.terminals_[p] + "'"); - } - if (this.lexer.showPosition) { - errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'"; - } else { - errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'"); - } - this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected}); - } - } - if (action[0] instanceof Array && action.length > 1) { - throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol); - } - switch (action[0]) { - case 1: - stack.push(symbol); - vstack.push(this.lexer.yytext); - lstack.push(this.lexer.yylloc); - stack.push(action[1]); - symbol = null; - if (!preErrorSymbol) { - yyleng = this.lexer.yyleng; - yytext = this.lexer.yytext; - yylineno = this.lexer.yylineno; - yyloc = this.lexer.yylloc; - if (recovering > 0) - recovering--; - } else { - symbol = preErrorSymbol; - preErrorSymbol = null; - } - break; - case 2: - len = this.productions_[action[1]][1]; - yyval.$ = vstack[vstack.length - len]; - yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column}; - if (ranges) { - yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]]; - } - r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); - if (typeof r !== "undefined") { - return r; - } - if (len) { - stack = stack.slice(0, -1 * len * 2); - vstack = vstack.slice(0, -1 * len); - lstack = lstack.slice(0, -1 * len); - } - stack.push(this.productions_[action[1]][0]); - vstack.push(yyval.$); - lstack.push(yyval._$); - newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; - stack.push(newState); - break; - case 3: - return true; - } - } - return true; -} -}; -/* Jison generated lexer */ -var lexer = (function(){ -var lexer = ({EOF:1, -parseError:function parseError(str, hash) { - if (this.yy.parser) { - this.yy.parser.parseError(str, hash); - } else { - throw new Error(str); - } - }, -setInput:function (input) { - this._input = input; - this._more = this._less = this.done = false; - this.yylineno = this.yyleng = 0; - this.yytext = this.matched = this.match = ''; - this.conditionStack = ['INITIAL']; - this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0}; - if (this.options.ranges) this.yylloc.range = [0,0]; - this.offset = 0; - return this; - }, -input:function () { - var ch = this._input[0]; - this.yytext += ch; - this.yyleng++; - this.offset++; - this.match += ch; - this.matched += ch; - var lines = ch.match(/(?:\r\n?|\n).*/g); - if (lines) { - this.yylineno++; - this.yylloc.last_line++; - } else { - this.yylloc.last_column++; - } - if (this.options.ranges) this.yylloc.range[1]++; - - this._input = this._input.slice(1); - return ch; - }, -unput:function (ch) { - var len = ch.length; - var lines = ch.split(/(?:\r\n?|\n)/g); - - this._input = ch + this._input; - this.yytext = this.yytext.substr(0, this.yytext.length-len-1); - //this.yyleng -= len; - this.offset -= len; - var oldLines = this.match.split(/(?:\r\n?|\n)/g); - this.match = this.match.substr(0, this.match.length-1); - this.matched = this.matched.substr(0, this.matched.length-1); - - if (lines.length-1) this.yylineno -= lines.length-1; - var r = this.yylloc.range; - - this.yylloc = {first_line: this.yylloc.first_line, - last_line: this.yylineno+1, - first_column: this.yylloc.first_column, - last_column: lines ? - (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length: - this.yylloc.first_column - len - }; - - if (this.options.ranges) { - this.yylloc.range = [r[0], r[0] + this.yyleng - len]; - } - return this; - }, -more:function () { - this._more = true; - return this; - }, -less:function (n) { - this.unput(this.match.slice(n)); - }, -pastInput:function () { - var past = this.matched.substr(0, this.matched.length - this.match.length); - return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); - }, -upcomingInput:function () { - var next = this.match; - if (next.length < 20) { - next += this._input.substr(0, 20-next.length); - } - return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, ""); - }, -showPosition:function () { - var pre = this.pastInput(); - var c = new Array(pre.length + 1).join("-"); - return pre + this.upcomingInput() + "\n" + c+"^"; - }, -next:function () { - if (this.done) { - return this.EOF; - } - if (!this._input) this.done = true; - - var token, - match, - tempMatch, - index, - col, - lines; - if (!this._more) { - this.yytext = ''; - this.match = ''; - } - var rules = this._currentRules(); - for (var i=0;i < rules.length; i++) { - tempMatch = this._input.match(this.rules[rules[i]]); - if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { - match = tempMatch; - index = i; - if (!this.options.flex) break; - } - } - if (match) { - lines = match[0].match(/(?:\r\n?|\n).*/g); - if (lines) this.yylineno += lines.length; - this.yylloc = {first_line: this.yylloc.last_line, - last_line: this.yylineno+1, - first_column: this.yylloc.last_column, - last_column: lines ? lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length}; - this.yytext += match[0]; - this.match += match[0]; - this.matches = match; - this.yyleng = this.yytext.length; - if (this.options.ranges) { - this.yylloc.range = [this.offset, this.offset += this.yyleng]; - } - this._more = false; - this._input = this._input.slice(match[0].length); - this.matched += match[0]; - token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]); - if (this.done && this._input) this.done = false; - if (token) return token; - else return; - } - if (this._input === "") { - return this.EOF; - } else { - return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(), - {text: "", token: null, line: this.yylineno}); - } - }, -lex:function lex() { - var r = this.next(); - if (typeof r !== 'undefined') { - return r; - } else { - return this.lex(); - } - }, -begin:function begin(condition) { - this.conditionStack.push(condition); - }, -popState:function popState() { - return this.conditionStack.pop(); - }, -_currentRules:function _currentRules() { - return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules; - }, -topState:function () { - return this.conditionStack[this.conditionStack.length-2]; - }, -pushState:function begin(condition) { - this.begin(condition); - }}); -lexer.options = {}; -lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { - -var YYSTATE=YY_START -switch($avoiding_name_collisions) { -case 0: - if(yy_.yytext.slice(-1) !== "\\") this.begin("mu"); - if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1), this.begin("emu"); - if(yy_.yytext) return 14; - -break; -case 1: return 14; -break; -case 2: - if(yy_.yytext.slice(-1) !== "\\") this.popState(); - if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1); - return 14; - -break; -case 3: yy_.yytext = yy_.yytext.substr(0, yy_.yyleng-4); this.popState(); return 15; -break; -case 4: this.begin("par"); return 24; -break; -case 5: return 16; -break; -case 6: return 20; -break; -case 7: return 19; -break; -case 8: return 19; -break; -case 9: return 23; -break; -case 10: return 23; -break; -case 11: this.popState(); this.begin('com'); -break; -case 12: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return 15; -break; -case 13: return 22; -break; -case 14: return 36; -break; -case 15: return 35; -break; -case 16: return 35; -break; -case 17: return 39; -break; -case 18: /*ignore whitespace*/ -break; -case 19: this.popState(); return 18; -break; -case 20: this.popState(); return 18; -break; -case 21: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 30; -break; -case 22: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\'/g,"'"); return 30; -break; -case 23: yy_.yytext = yy_.yytext.substr(1); return 28; -break; -case 24: return 32; -break; -case 25: return 32; -break; -case 26: return 31; -break; -case 27: return 35; -break; -case 28: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 35; -break; -case 29: return 'INVALID'; -break; -case 30: /*ignore whitespace*/ -break; -case 31: this.popState(); return 37; -break; -case 32: return 5; -break; -} -}; -lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[} ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@[a-zA-Z]+)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:[0-9]+(?=[}\s]))/,/^(?:[a-zA-Z0-9_$-]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:\s+)/,/^(?:[a-zA-Z0-9_$-/]+)/,/^(?:$)/]; -lexer.conditions = {"mu":{"rules":[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,32],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"com":{"rules":[3],"inclusive":false},"par":{"rules":[30,31],"inclusive":false},"INITIAL":{"rules":[0,1,32],"inclusive":true}}; -return lexer;})() -parser.lexer = lexer; -function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser; -return new Parser; -})();; -// lib/handlebars/compiler/base.js -Handlebars.Parser = handlebars; - -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(input); -}; - -Handlebars.print = function(ast) { - return new Handlebars.PrintVisitor().accept(ast); -};; -// lib/handlebars/compiler/ast.js -(function() { - - Handlebars.AST = {}; - - Handlebars.AST.ProgramNode = function(statements, inverse) { - this.type = "program"; - this.statements = statements; - if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); } - }; - - Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) { - this.type = "mustache"; - this.escaped = !unescaped; - this.hash = hash; - - var id = this.id = rawParams[0]; - var params = this.params = rawParams.slice(1); - - // a mustache is an eligible helper if: - // * its id is simple (a single part, not `this` or `..`) - var eligibleHelper = this.eligibleHelper = id.isSimple; - - // a mustache is definitely a helper if: - // * it is an eligible helper, and - // * it has at least one parameter or hash segment - this.isHelper = eligibleHelper && (params.length || hash); - - // if a mustache is an eligible helper but not a definite - // helper, it is ambiguous, and will be resolved in a later - // pass or at runtime. - }; - - Handlebars.AST.PartialNode = function(partialName, context) { - this.type = "partial"; - this.partialName = partialName; - this.context = context; - }; - - var verifyMatch = function(open, close) { - if(open.original !== close.original) { - throw new Handlebars.Exception(open.original + " doesn't match " + close.original); - } - }; - - Handlebars.AST.BlockNode = function(mustache, program, inverse, close) { - verifyMatch(mustache.id, close); - this.type = "block"; - this.mustache = mustache; - this.program = program; - this.inverse = inverse; - - if (this.inverse && !this.program) { - this.isInverse = true; - } - }; - - Handlebars.AST.ContentNode = function(string) { - this.type = "content"; - this.string = string; - }; - - Handlebars.AST.HashNode = function(pairs) { - this.type = "hash"; - this.pairs = pairs; - }; - - Handlebars.AST.IdNode = function(parts) { - this.type = "ID"; - this.original = parts.join("."); - - var dig = [], depth = 0; - - for(var i=0,l=parts.length; i<l; i++) { - var part = parts[i]; - - 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); } - } - - this.parts = dig; - this.string = dig.join('.'); - this.depth = depth; - - // an ID is simple if it only has one part, and that part is not - // `..` or `this`. - this.isSimple = parts.length === 1 && !this.isScoped && depth === 0; - - this.stringModeValue = this.string; - }; - - Handlebars.AST.PartialNameNode = function(name) { - this.type = "PARTIAL_NAME"; - this.name = name; - }; - - Handlebars.AST.DataNode = function(id) { - this.type = "DATA"; - this.id = id; - }; - - Handlebars.AST.StringNode = function(string) { - this.type = "STRING"; - this.string = string; - this.stringModeValue = string; - }; - - Handlebars.AST.IntegerNode = function(integer) { - this.type = "INTEGER"; - this.integer = integer; - this.stringModeValue = Number(integer); - }; - - Handlebars.AST.BooleanNode = function(bool) { - this.type = "BOOLEAN"; - this.bool = bool; - this.stringModeValue = bool === "true"; - }; - - Handlebars.AST.CommentNode = function(comment) { - this.type = "comment"; - this.comment = comment; - }; - -})();; -// lib/handlebars/utils.js - -var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; - -Handlebars.Exception = function(message) { - var tmp = Error.prototype.constructor.apply(this, arguments); - - // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. - for (var idx = 0; idx < errorProps.length; idx++) { - this[errorProps[idx]] = tmp[errorProps[idx]]; - } -}; -Handlebars.Exception.prototype = new Error(); - -// Build out our basic SafeString type -Handlebars.SafeString = function(string) { - this.string = string; -}; -Handlebars.SafeString.prototype.toString = function() { - return this.string.toString(); -}; - -(function() { - var escape = { - "&": "&amp;", - "<": "&lt;", - ">": "&gt;", - '"': "&quot;", - "'": "&#x27;", - "`": "&#x60;" - }; - - var badChars = /[&<>"'`]/g; - var possible = /[&<>"'`]/; - - var escapeChar = function(chr) { - return escape[chr] || "&amp;"; - }; - - Handlebars.Utils = { - escapeExpression: function(string) { - // don't escape SafeStrings, since they're already safe - if (string instanceof Handlebars.SafeString) { - return string.toString(); - } else if (string == null || string === false) { - return ""; - } - - if(!possible.test(string)) { return string; } - return string.replace(badChars, escapeChar); - }, - - isEmpty: function(value) { - if (!value && value !== 0) { - return true; - } else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) { - return true; - } else { - return false; - } - } - }; -})();; -// lib/handlebars/compiler/compiler.js - -/*jshint eqnull:true*/ -Handlebars.Compiler = function() {}; -Handlebars.JavaScriptCompiler = function() {}; - -(function(Compiler, JavaScriptCompiler) { - // the foundHelper register will disambiguate helper lookup from finding a - // function in a context. This is necessary for mustache compatibility, which - // requires that context functions in blocks are evaluated by blockHelperMissing, - // and then proceed as if the resulting value was provided to blockHelperMissing. - - Compiler.prototype = { - compiler: Compiler, - - disassemble: function() { - var opcodes = this.opcodes, opcode, out = [], params, param; - - for (var i=0, l=opcodes.length; i<l; i++) { - opcode = opcodes[i]; - - if (opcode.opcode === 'DECLARE') { - out.push("DECLARE " + opcode.name + "=" + opcode.value); - } else { - params = []; - for (var j=0; j<opcode.args.length; j++) { - param = opcode.args[j]; - if (typeof param === "string") { - param = "\"" + param.replace("\n", "\\n") + "\""; - } - params.push(param); - } - out.push(opcode.opcode + " " + params.join(" ")); - } - } - - 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, - - compile: function(program, options) { - this.children = []; - this.depths = {list: []}; - this.options = options; - - // These changes will propagate to the other compiler components - var knownHelpers = this.options.knownHelpers; - this.options.knownHelpers = { - 'helperMissing': true, - 'blockHelperMissing': true, - 'each': true, - 'if': true, - 'unless': true, - 'with': true, - 'log': true - }; - if (knownHelpers) { - for (var name in knownHelpers) { - this.options.knownHelpers[name] = knownHelpers[name]; - } - } - - return this.program(program); - }, - - accept: function(node) { - return this[node.type](node); - }, - - program: function(program) { - var statements = program.statements, statement; - this.opcodes = []; - - for(var i=0, l=statements.length; i<l; i++) { - statement = statements[i]; - this[statement.type](statement); - } - this.isSimple = l === 1; - - this.depths.list = this.depths.list.sort(function(a, b) { - return a - b; - }); - - return this; - }, - - compileProgram: function(program) { - var result = new this.compiler().compile(program, this.options); - var guid = this.guid++, depth; - - this.usePartial = this.usePartial || result.usePartial; - - this.children[guid] = result; - - for(var i=0, l=result.depths.list.length; i<l; i++) { - depth = result.depths.list[i]; - - if(depth < 2) { continue; } - else { this.addDepth(depth - 1); } - } - - return guid; - }, - - block: function(block) { - var mustache = block.mustache, - program = block.program, - inverse = block.inverse; - - if (program) { - program = this.compileProgram(program); - } - - if (inverse) { - inverse = this.compileProgram(inverse); - } - - var type = this.classifyMustache(mustache); - - if (type === "helper") { - this.helperMustache(mustache, program, inverse); - } else if (type === "simple") { - this.simpleMustache(mustache); - - // now that the simple mustache is resolved, we need to - // evaluate it by executing `blockHelperMissing` - this.opcode('pushProgram', program); - this.opcode('pushProgram', inverse); - this.opcode('emptyHash'); - this.opcode('blockValue'); - } else { - this.ambiguousMustache(mustache, program, inverse); - - // now that the simple mustache is resolved, we need to - // evaluate it by executing `blockHelperMissing` - this.opcode('pushProgram', program); - this.opcode('pushProgram', inverse); - this.opcode('emptyHash'); - this.opcode('ambiguousBlockValue'); - } - - this.opcode('append'); - }, - - hash: function(hash) { - var pairs = hash.pairs, pair, val; - - this.opcode('pushHash'); - - for(var i=0, l=pairs.length; i<l; i++) { - pair = pairs[i]; - val = pair[1]; - - if (this.options.stringParams) { - this.opcode('pushStringParam', val.stringModeValue, val.type); - } else { - this.accept(val); - } - - this.opcode('assignToHash', pair[0]); - } - this.opcode('popHash'); - }, - - partial: function(partial) { - var partialName = partial.partialName; - this.usePartial = true; - - if(partial.context) { - this.ID(partial.context); - } else { - this.opcode('push', 'depth0'); - } - - this.opcode('invokePartial', partialName.name); - this.opcode('append'); - }, - - content: function(content) { - this.opcode('appendContent', content.string); - }, - - mustache: function(mustache) { - var options = this.options; - var type = this.classifyMustache(mustache); - - if (type === "simple") { - this.simpleMustache(mustache); - } else if (type === "helper") { - this.helperMustache(mustache); - } else { - this.ambiguousMustache(mustache); - } - - if(mustache.escaped && !options.noEscape) { - this.opcode('appendEscaped'); - } else { - this.opcode('append'); - } - }, - - ambiguousMustache: function(mustache, program, inverse) { - 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, isBlock); - }, - - simpleMustache: function(mustache) { - var id = mustache.id; - - if (id.type === 'DATA') { - this.DATA(id); - } else if (id.parts.length) { - this.ID(id); - } else { - // Simplified ID for `this` - this.addDepth(id.depth); - this.opcode('getContext', id.depth); - this.opcode('pushContext'); - } - - this.opcode('resolvePossibleLambda'); - }, - - helperMustache: function(mustache, program, inverse) { - var params = this.setupFullMustacheParams(mustache, program, inverse), - name = mustache.id.parts[0]; - - if (this.options.knownHelpers[name]) { - this.opcode('invokeKnownHelper', params.length, name); - } else if (this.knownHelpersOnly) { - throw new Error("You specified knownHelpersOnly, but used the unknown helper " + name); - } else { - this.opcode('invokeHelper', params.length, name); - } - }, - - ID: function(id) { - this.addDepth(id.depth); - this.opcode('getContext', id.depth); - - var name = id.parts[0]; - if (!name) { - this.opcode('pushContext'); - } else { - this.opcode('lookupOnContext', id.parts[0]); - } - - for(var i=1, l=id.parts.length; i<l; i++) { - this.opcode('lookup', id.parts[i]); - } - }, - - DATA: function(data) { - this.options.data = true; - this.opcode('lookupData', data.id); - }, - - STRING: function(string) { - this.opcode('pushString', string.string); - }, - - INTEGER: function(integer) { - this.opcode('pushLiteral', integer.integer); - }, - - BOOLEAN: function(bool) { - this.opcode('pushLiteral', bool.bool); - }, - - comment: function() {}, - - // HELPERS - opcode: function(name) { - this.opcodes.push({ opcode: name, args: [].slice.call(arguments, 1) }); - }, - - declare: function(name, value) { - this.opcodes.push({ opcode: 'DECLARE', name: name, value: value }); - }, - - addDepth: function(depth) { - if(isNaN(depth)) { throw new Error("EWOT"); } - if(depth === 0) { return; } - - if(!this.depths[depth]) { - this.depths[depth] = true; - this.depths.list.push(depth); - } - }, - - classifyMustache: function(mustache) { - var isHelper = mustache.isHelper; - var isEligible = mustache.eligibleHelper; - var options = this.options; - - // if ambiguous, we can possibly resolve the ambiguity now - if (isEligible && !isHelper) { - var name = mustache.id.parts[0]; - - if (options.knownHelpers[name]) { - isHelper = true; - } else if (options.knownHelpersOnly) { - isEligible = false; - } - } - - if (isHelper) { return "helper"; } - else if (isEligible) { return "ambiguous"; } - else { return "simple"; } - }, - - pushParams: function(params) { - var i = params.length, param; - - while(i--) { - param = params[i]; - - if(this.options.stringParams) { - if(param.depth) { - this.addDepth(param.depth); - } - - this.opcode('getContext', param.depth || 0); - this.opcode('pushStringParam', param.stringModeValue, param.type); - } else { - this[param.type](param); - } - } - }, - - setupMustacheParams: function(mustache) { - var params = mustache.params; - this.pushParams(params); - - if(mustache.hash) { - this.hash(mustache.hash); - } else { - this.opcode('emptyHash'); - } - - return params; - }, - - // this will replace setupMustacheParams when we're done - setupFullMustacheParams: function(mustache, program, inverse) { - var params = mustache.params; - this.pushParams(params); - - this.opcode('pushProgram', program); - this.opcode('pushProgram', inverse); - - if(mustache.hash) { - this.hash(mustache.hash); - } else { - this.opcode('emptyHash'); - } - - return params; - } - }; - - var Literal = function(value) { - this.value = value; - }; - - 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*/) { - if (/^[0-9]+$/.test(name)) { - return parent + "[" + name + "]"; - } else if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) { - return parent + "." + name; - } - else { - return parent + "['" + name + "']"; - } - }, - - appendToBuffer: function(string) { - if (this.environment.isSimple) { - return "return " + string + ";"; - } else { - return { - appendToBuffer: true, - content: string, - toString: function() { return "buffer += " + string + ";"; } - }; - } - }, - - initializeBuffer: function() { - return this.quotedString(""); - }, - - namespace: "Handlebars", - // END PUBLIC API - - compile: function(environment, options, context, asObject) { - this.environment = environment; - this.options = options || {}; - - Handlebars.log(Handlebars.logger.DEBUG, this.environment.disassemble() + "\n\n"); - - this.name = this.environment.name; - this.isChild = !!context; - this.context = context || { - programs: [], - environments: [], - aliases: { } - }; - - this.preamble(); - - this.stackSlot = 0; - this.stackVars = []; - this.registers = { list: [] }; - this.compileStack = []; - this.inlineStack = []; - - this.compileChildren(environment, options); - - var opcodes = environment.opcodes, opcode; - - this.i = 0; - - for(l=opcodes.length; this.i<l; this.i++) { - opcode = opcodes[this.i]; - - if(opcode.opcode === 'DECLARE') { - this[opcode.name] = opcode.value; - } else { - this[opcode.opcode].apply(this, opcode.args); - } - } - - return this.createFunctionContext(asObject); - }, - - nextOpcode: function() { - var opcodes = this.environment.opcodes; - return opcodes[this.i + 1]; - }, - - eat: function() { - this.i = this.i + 1; - }, - - preamble: function() { - var out = []; - - if (!this.isChild) { - var namespace = this.namespace; - var copies = "helpers = helpers || " + namespace + ".helpers;"; - if (this.environment.usePartial) { copies = copies + " partials = partials || " + namespace + ".partials;"; } - if (this.options.data) { copies = copies + " data = data || {};"; } - out.push(copies); - } else { - out.push(''); - } - - if (!this.environment.isSimple) { - out.push(", buffer = " + this.initializeBuffer()); - } else { - out.push(""); - } - - // track the last context pushed into place to allow skipping the - // getContext opcode when it would be a noop - this.lastContext = 0; - this.source = out; - }, - - createFunctionContext: function(asObject) { - var locals = this.stackVars.concat(this.registers.list); - - if(locals.length > 0) { - this.source[1] = this.source[1] + ", " + locals.join(", "); - } - - // Generate minimizer alias mappings - if (!this.isChild) { - for (var alias in this.context.aliases) { - this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; - } - } - - if (this.source[1]) { - this.source[1] = "var " + this.source[1].substring(2) + ";"; - } - - // Merge children - if (!this.isChild) { - this.source[1] += '\n' + this.context.programs.join('\n') + '\n'; - } - - if (!this.environment.isSimple) { - this.source.push("return buffer;"); - } - - var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"]; - - for(var i=0, l=this.environment.depths.list.length; i<l; i++) { - 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(source); - - return Function.apply(this, params); - } else { - 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] - // - // On stack, before: hash, inverse, program, value - // On stack, after: return value of blockHelperMissing - // - // The purpose of this opcode is to take a block of the form - // `{{#foo}}...{{/foo}}`, resolve the value of `foo`, and - // replace it on the stack with the result of properly - // invoking blockHelperMissing. - blockValue: function() { - this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing'; - - var params = ["depth0"]; - this.setupParams(0, params); - - this.replaceStack(function(current) { - params.splice(1, 0, current); - return "blockHelperMissing.call(" + params.join(", ") + ")"; - }); - }, - - // [ambiguousBlockValue] - // - // On stack, before: hash, inverse, program, value - // Compiler value, before: lastHelper=value of last found helper, if any - // On stack, after, if no lastHelper: same as [blockValue] - // On stack, after, if lastHelper: value - ambiguousBlockValue: function() { - this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing'; - - var params = ["depth0"]; - this.setupParams(0, params); - - 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(", ") + "); }"); - }, - - // [appendContent] - // - // On stack, before: ... - // On stack, after: ... - // - // Appends the string value of `content` to the current buffer - appendContent: function(content) { - this.source.push(this.appendToBuffer(this.quotedString(content))); - }, - - // [append] - // - // On stack, before: value, ... - // On stack, after: ... - // - // Coerces `value` to a String and appends it to the current buffer. - // - // 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) { - this.source.push("else { " + this.appendToBuffer("''") + " }"); - } - }, - - // [appendEscaped] - // - // On stack, before: value, ... - // On stack, after: ... - // - // Escape `value` and append it to the buffer - appendEscaped: function() { - this.context.aliases.escapeExpression = 'this.escapeExpression'; - - this.source.push(this.appendToBuffer("escapeExpression(" + this.popStack() + ")")); - }, - - // [getContext] - // - // On stack, before: ... - // On stack, after: ... - // Compiler value, after: lastContext=depth - // - // Set the value of the `lastContext` compiler value to the depth - getContext: function(depth) { - if(this.lastContext !== depth) { - this.lastContext = depth; - } - }, - - // [lookupOnContext] - // - // On stack, before: ... - // On stack, after: currentContext[name], ... - // - // Looks up the value of `name` on the current context and pushes - // it onto the stack. - lookupOnContext: function(name) { - this.push(this.nameLookup('depth' + this.lastContext, name, 'context')); - }, - - // [pushContext] - // - // On stack, before: ... - // On stack, after: currentContext, ... - // - // Pushes the value of the current context onto the stack. - pushContext: function() { - this.pushStackLiteral('depth' + this.lastContext); - }, - - // [resolvePossibleLambda] - // - // On stack, before: value, ... - // On stack, after: resolved value, ... - // - // If the `value` is a lambda, replace it on the stack by - // the return value of the lambda - resolvePossibleLambda: function() { - this.context.aliases.functionType = '"function"'; - - this.replaceStack(function(current) { - return "typeof " + current + " === functionType ? " + current + ".apply(depth0) : " + current; - }); - }, - - // [lookup] - // - // On stack, before: value, ... - // On stack, after: value[name], ... - // - // Replace the value on the stack with the result of looking - // up `name` on `value` - lookup: function(name) { - this.replaceStack(function(current) { - return current + " == null || " + current + " === false ? " + current + " : " + this.nameLookup(current, name, 'context'); - }); - }, - - // [lookupData] - // - // On stack, before: ... - // On stack, after: data[id], ... - // - // Push the result of looking up `id` on the current data - lookupData: function(id) { - this.push(this.nameLookup('data', id, 'data')); - }, - - // [pushStringParam] - // - // On stack, before: ... - // On stack, after: string, currentContext, ... - // - // This opcode is designed for use in string mode, which - // provides the string value of a parameter along with its - // depth rather than resolving it immediately. - pushStringParam: function(string, type) { - this.pushStackLiteral('depth' + this.lastContext); - - this.pushString(type); - - if (typeof string === 'string') { - this.pushString(string); - } else { - this.pushStackLiteral(string); - } - }, - - 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] - // - // On stack, before: ... - // On stack, after: quotedString(string), ... - // - // Push a quoted version of `string` onto the stack - pushString: function(string) { - this.pushStackLiteral(this.quotedString(string)); - }, - - // [push] - // - // On stack, before: ... - // On stack, after: expr, ... - // - // Push an expression onto the stack - push: function(expr) { - this.inlineStack.push(expr); - return expr; - }, - - // [pushLiteral] - // - // On stack, before: ... - // On stack, after: value, ... - // - // Pushes a value onto the stack. This operation prevents - // the compiler from creating a temporary variable to hold - // it. - pushLiteral: function(value) { - this.pushStackLiteral(value); - }, - - // [pushProgram] - // - // On stack, before: ... - // On stack, after: program(guid), ... - // - // Push a program expression onto the stack. This takes - // a compile-time guid and converts it into a runtime-accessible - // expression. - pushProgram: function(guid) { - if (guid != null) { - this.pushStackLiteral(this.programExpression(guid)); - } else { - this.pushStackLiteral(null); - } - }, - - // [invokeHelper] - // - // On stack, before: hash, inverse, program, params..., ... - // On stack, after: result of helper invocation - // - // Pops off the helper's parameters, invokes the helper, - // and pushes the helper's return value onto the stack. - // - // If the helper is not found, `helperMissing` is called. - invokeHelper: function(paramSize, name) { - this.context.aliases.helperMissing = 'helpers.helperMissing'; - - var helper = this.lastHelper = this.setupHelper(paramSize, name, true); - - this.push(helper.name); - this.replaceStack(function(name) { - return name + ' ? ' + name + '.call(' + - helper.callParams + ") " + ": helperMissing.call(" + - helper.helperMissingParams + ")"; - }); - }, - - // [invokeKnownHelper] - // - // On stack, before: hash, inverse, program, params..., ... - // On stack, after: result of helper invocation - // - // This operation is used when the helper is known to exist, - // so a `helperMissing` fallback is not required. - invokeKnownHelper: function(paramSize, name) { - var helper = this.setupHelper(paramSize, name); - this.push(helper.name + ".call(" + helper.callParams + ")"); - }, - - // [invokeAmbiguous] - // - // On stack, before: hash, inverse, program, params..., ... - // On stack, after: result of disambiguation - // - // This operation is used when an expression like `{{foo}}` - // is provided, but we don't know at compile-time whether it - // is a helper or a path. - // - // 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, helperCall) { - this.context.aliases.functionType = '"function"'; - - this.pushStackLiteral('{}'); // Hash value - var helper = this.setupHelper(0, name, helperCall); - - var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper'); - - var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context'); - var nextStack = this.nextStack(); - - 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 + '; }'); - }, - - // [invokePartial] - // - // On stack, before: context, ... - // On stack after: result of partial invocation - // - // This operation pops off a context, invokes a partial with that context, - // and pushes the result of the invocation back. - invokePartial: function(name) { - var params = [this.nameLookup('partials', name, 'partial'), "'" + name + "'", this.popStack(), "helpers", "partials"]; - - if (this.options.data) { - params.push("data"); - } - - this.context.aliases.self = "this"; - this.push("self.invokePartial(" + params.join(", ") + ")"); - }, - - // [assignToHash] - // - // On stack, before: value, hash, ... - // On stack, after: hash, ... - // - // 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(), - type; - - if (this.options.stringParams) { - type = this.popStack(); - this.popStack(); - } - - var hash = this.hash; - if (type) { - hash.types.push("'" + key + "': " + type); - } - hash.values.push("'" + key + "': (" + value + ")"); - }, - - // HELPERS - - compiler: JavaScriptCompiler, - - compileChildren: function(environment, options) { - var children = environment.children, child, compiler; - - for(var i=0, l=children.length; i<l; i++) { - child = children[i]; - compiler = new this.compiler(); - - 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; - } - } - }, - - programExpression: function(guid) { - this.context.aliases.self = "this"; - - if(guid == null) { - return "self.noop"; - } - - var child = this.environment.children[guid], - depths = child.depths.list, depth; - - var programParams = [child.index, child.name, "data"]; - - for(var i=0, l = depths.length; i<l; i++) { - depth = depths[i]; - - if(depth === 1) { programParams.push("depth0"); } - else { programParams.push("depth" + (depth - 1)); } - } - - if(depths.length === 0) { - return "self.program(" + programParams.join(", ") + ")"; - } else { - programParams.shift(); - return "self.programWithDepth(" + programParams.join(", ") + ")"; - } - }, - - register: function(name, val) { - this.useRegister(name); - this.source.push(name + " = " + val + ";"); - }, - - useRegister: function(name) { - if(!this.registers[name]) { - this.registers[name] = true; - this.registers.list.push(name); - } - }, - - pushStackLiteral: function(item) { - return this.push(new Literal(item)); - }, - - pushStack: function(item) { - this.flushInline(); - - var stack = this.incrStack(); - if (item) { - this.source.push(stack + " = " + item + ";"); - } - this.compileStack.push(stack); - return stack; - }, - - replaceStack: function(callback) { - var prefix = '', - inline = this.isInline(), - stack; - - // 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(); - } - - 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() { - 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(wrapped) { - var inline = this.isInline(), - item = (inline ? this.inlineStack : this.compileStack).pop(); - - if (!wrapped && (item instanceof Literal)) { - return item.value; - } else { - if (!inline) { - this.stackSlot--; - } - return item; - } - }, - - topStack: function(wrapped) { - var stack = (this.isInline() ? this.inlineStack : this.compileStack), - item = stack[stack.length - 1]; - - if (!wrapped && (item instanceof Literal)) { - return item.value; - } else { - return item; - } - }, - - quotedString: function(str) { - return '"' + str - .replace(/\\/g, '\\\\') - .replace(/"/g, '\\"') - .replace(/\n/g, '\\n') - .replace(/\r/g, '\\r') + '"'; - }, - - setupHelper: function(paramSize, name, missingParams) { - var params = []; - this.setupParams(paramSize, params, missingParams); - var foundHelper = this.nameLookup('helpers', name, 'helper'); - - return { - params: params, - name: foundHelper, - callParams: ["depth0"].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, useRegister) { - var options = [], contexts = [], types = [], param, inverse, program; - - options.push("hash:" + this.popStack()); - - inverse = this.popStack(); - program = this.popStack(); - - // Avoid setting fn and inverse if neither are set. This allows - // helpers to do a check for `if (options.fn)` - if (program || inverse) { - if (!program) { - this.context.aliases.self = "this"; - program = "self.noop"; - } - - if (!inverse) { - this.context.aliases.self = "this"; - inverse = "self.noop"; - } - - options.push("inverse:" + inverse); - options.push("fn:" + program); - } - - for(var i=0; i<paramSize; i++) { - param = this.popStack(); - params.push(param); - - if(this.options.stringParams) { - types.push(this.popStack()); - contexts.push(this.popStack()); - } - } - - if (this.options.stringParams) { - options.push("contexts:[" + contexts.join(",") + "]"); - options.push("types:[" + types.join(",") + "]"); - options.push("hashTypes:hashTypes"); - } - - if(this.options.data) { - options.push("data:data"); - } - - options = "{" + options.join(",") + "}"; - if (useRegister) { - this.register('options', options); - params.push('options'); - } else { - params.push(options); - } - return params.join(", "); - } - }; - - var reservedWords = ( - "break else new var" + - " case finally return void" + - " catch for switch while" + - " continue function this with" + - " default if throw" + - " delete in try" + - " do instanceof typeof" + - " abstract enum int short" + - " boolean export interface static" + - " byte extends long super" + - " char final native synchronized" + - " class float package throws" + - " const goto private transient" + - " debugger implements protected volatile" + - " double import public let yield" - ).split(" "); - - var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {}; - - for(var i=0, l=reservedWords.length; i<l; i++) { - compilerWords[reservedWords[i]] = true; - } - - JavaScriptCompiler.isValidJavaScriptVariableName = function(name) { - if(!JavaScriptCompiler.RESERVED_WORDS[name] && /^[a-zA-Z_$][0-9a-zA-Z_$]+$/.test(name)) { - return true; - } - return false; - }; - -})(Handlebars.Compiler, Handlebars.JavaScriptCompiler); - -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(input); - var environment = new Handlebars.Compiler().compile(ast, options); - return new Handlebars.JavaScriptCompiler().compile(environment, options); -}; - -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 || {}; - if (!('data' in options)) { - options.data = true; - } - var compiled; - function compile() { - 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); - } - - // Template is only compiled on first use and cached after that point. - return function(context, options) { - if (!compiled) { - compiled = compile(); - } - return compiled.call(this, context, options); - }; -}; -; -// lib/handlebars/runtime.js -Handlebars.VM = { - template: function(templateSpec) { - // Just add water - var container = { - escapeExpression: Handlebars.Utils.escapeExpression, - invokePartial: Handlebars.VM.invokePartial, - programs: [], - program: function(i, fn, data) { - var programWrapper = this.programs[i]; - if(data) { - return Handlebars.VM.program(fn, data); - } else if(programWrapper) { - return programWrapper; - } else { - programWrapper = this.programs[i] = Handlebars.VM.program(fn); - return programWrapper; - } - }, - programWithDepth: Handlebars.VM.programWithDepth, - noop: Handlebars.VM.noop, - compilerInfo: null - }; - - return function(context, options) { - options = options || {}; - 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; - }; - }, - - programWithDepth: function(fn, data, $depth) { - var args = Array.prototype.slice.call(arguments, 2); - - return function(context, options) { - options = options || {}; - - return fn.apply(this, [context, options.data || data].concat(args)); - }; - }, - program: function(fn, data) { - return function(context, options) { - options = options || {}; - - return fn(context, options.data || data); - }; - }, - noop: function() { return ""; }, - invokePartial: function(partial, name, context, helpers, partials, data) { - var options = { helpers: helpers, partials: partials, data: data }; - - if(partial === undefined) { - throw new Handlebars.Exception("The partial " + name + " could not be found"); - } else if(partial instanceof Function) { - return partial(context, options); - } else if (!Handlebars.compile) { - throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode"); - } else { - partials[name] = Handlebars.compile(partial, {data: data !== undefined}); - return partials[name](context, options); - } - } -}; - -Handlebars.template = Handlebars.VM.template; -; - -})(); - -(function() { /** @module ember -@submodule ember-handlebars +@submodule ember-handlebars-compiler */ // Eliminate dependency on any Ember to simplify precompilation workflow var objectCreate = Object.create || function(parent) { function F() {} @@ -19830,11 +17905,11 @@ {{repeat text count=3}} ``` ## Example with bound options - Bound hash options are also supported. Example: + Bound hash options are also supported. Example: ```handlebars {{repeat text countBinding="numRepeats"}} ``` @@ -19868,19 +17943,19 @@ Which allows for template syntax such as {{concatenate prop1 prop2}} or {{concatenate prop1 prop2 prop3}}. If any of the properties change, the helpr will re-render. Note that dependency keys cannot be using in conjunction with multi-property helpers, since it is ambiguous - which property the dependent keys would belong to. - + which property the dependent keys would belong to. + ## Use with unbound helper - The {{unbound}} helper can be used with bound helper invocations + The {{unbound}} helper can be used with bound helper invocations to render them in their unbound form, e.g. ```handlebars - {{unbound capitalize name}} + {{unbound capitalize name}} ``` In this example, if the name property changes, the helper will not re-render. @@ -19902,11 +17977,11 @@ data = options.data, hash = options.hash, view = data.view, currentContext = (options.contexts && options.contexts[0]) || this, normalized, - pathRoot, path, + pathRoot, path, loc, hashOption; // Detect bound options (e.g. countBinding="otherCount") hash.boundOptions = {}; for (hashOption in hash) { @@ -20007,11 +18082,11 @@ view.appendChild(bindView); // Assemble liast of watched properties that'll re-render this helper. watchedProperties = []; for (boundOption in boundOptions) { - if (boundOptions.hasOwnProperty(boundOption)) { + if (boundOptions.hasOwnProperty(boundOption)) { watchedProperties.push(normalizePath(context, boundOptions[boundOption], data)); } } watchedProperties = watchedProperties.concat(normalizedProperties); @@ -20136,26 +18211,34 @@ */ var set = Ember.set, get = Ember.get; var Metamorph = requireModule('metamorph'); +function notifyMutationListeners() { + Ember.run.once(Ember.View, 'notifyMutationListeners'); +} + // DOMManager should just abstract dom manipulation between jquery and metamorph var DOMManager = { remove: function(view) { view.morph.remove(); + notifyMutationListeners(); }, prepend: function(view, html) { view.morph.prepend(html); + notifyMutationListeners(); }, after: function(view, html) { view.morph.after(html); + notifyMutationListeners(); }, html: function(view, html) { view.morph.html(html); + notifyMutationListeners(); }, // This is messed up. replace: function(view) { var morph = view.morph; @@ -20174,15 +18257,17 @@ view.triggerRecursively('willInsertElement'); morph.replaceWith(buffer.string()); view.transitionTo('inDOM'); view.triggerRecursively('didInsertElement'); + notifyMutationListeners(); }); }, empty: function(view) { view.morph.html(""); + notifyMutationListeners(); } }; // The `morph` and `outerHTML` properties are internal only // and not observable. @@ -20200,10 +18285,11 @@ instrumentName: 'render.metamorph', init: function() { this._super(); this.morph = Metamorph(); + }, beforeRender: function(buffer) { buffer.push(this.morph.startTag()); buffer.pushOpeningTag(); @@ -20940,18 +19026,18 @@ <img {{bindAttr class="view.someProperty}}> ``` Result in the following rendered output: - ```html + ```html <img class="aValue"> ``` A boolean return value will insert a specified class name if the property returns `true` and remove the class name if the property returns `false`. - A class name is provided via the syntax + A class name is provided via the syntax `somePropertyName:class-name-if-true`. ```javascript AView = Ember.View.extend({ someBool: true @@ -21102,13 +19188,13 @@ Otherwise, it will not add any new class name. @method bindClasses @for Ember.Handlebars @param {Ember.Object} context The context from which to lookup properties - @param {String} classBindings A string, space-separated, of class bindings + @param {String} classBindings A string, space-separated, of class bindings to use - @param {Ember.View} view The view in which observers should look for the + @param {Ember.View} view The view in which observers should look for the element to update @param {Srting} bindAttrId Optional bindAttr id used to lookup elements @return {Array} An array of class names to add */ EmberHandlebars.bindClasses = function(context, classBindings, view, bindAttrId, options) { @@ -21236,10 +19322,15 @@ if (hash.id) { extensions.elementId = hash.id; dup = true; } + if (hash.tag) { + extensions.tagName = hash.tag; + dup = true; + } + if (classes) { classes = classes.split(' '); extensions.classNames = classes; dup = true; } @@ -21262,10 +19353,11 @@ } if (dup) { hash = Ember.$.extend({}, hash); delete hash.id; + delete hash.tag; delete hash['class']; delete hash.classBinding; } // Set the proper context for all bindings passed to the helper. This applies to regular attribute bindings @@ -21793,11 +19885,11 @@ if(arguments.length > 2) { // Unbound helper call. options.data.isUnbound = true; helper = Ember.Handlebars.helpers[arguments[0]] || Ember.Handlebars.helperMissing; - out = helper.apply(this, Array.prototype.slice.call(arguments, 1)); + out = helper.apply(this, Array.prototype.slice.call(arguments, 1)); delete options.data.isUnbound; return out; } context = (fn.contexts && fn.contexts[0]) || this; @@ -22237,10 +20329,45 @@ Ember.TEMPLATES[name](this, { data: options.data }); }); +})(); + + + +(function() { +/** +@module ember +@submodule ember-handlebars +*/ + +/** + `partial` renders a template directly using the current context. + If needed the context can be set using the `{{#with foo}}` helper. + + ```html + <script type="text/x-handlebars" data-template-name="header_bar"> + {{#with currentUser}} + {{partial user_info}} + {{/with}} + </script> + + The `data-template-name` attribute of a partial template + is prefixed with an underscore. + + ```html + <script type="text/x-handlebars" data-template-name="_user_info"> + <span>Hello {{username}}!</span> + </script> + ``` + + @method partial + @for Ember.Handlebars.helpers + @param {String} partialName the name of the template to render minus the leading underscore +*/ + Ember.Handlebars.registerHelper('partial', function(name, options) { var nameParts = name.split("/"), lastPart = nameParts[nameParts.length - 1]; nameParts[nameParts.length - 1] = "_" + lastPart; @@ -24706,24 +22833,32 @@ Ember.controllerFor = function(container, controllerName, context, lookupOptions) { return container.lookup('controller:' + controllerName, lookupOptions) || Ember.generateController(container, controllerName, context); }; - +/** + Generates a controller automatically if none was provided. + The type of generated controller depends on the context. + You can customize your generated controllers by defining + `App.ObjectController` and `App.ArrayController` +*/ Ember.generateController = function(container, controllerName, context) { - var controller; + var controller, DefaultController; if (context && Ember.isArray(context)) { - controller = Ember.ArrayController.extend({ + DefaultController = container.resolve('controller:array'); + controller = DefaultController.extend({ content: context }); } else if (context) { - controller = Ember.ObjectController.extend({ + DefaultController = container.resolve('controller:object'); + controller = DefaultController.extend({ content: context }); } else { - controller = Ember.Controller.extend(); + DefaultController = container.resolve('controller:basic'); + controller = DefaultController.extend(); } controller.toString = function() { return "(generated " + controllerName + " controller)"; }; @@ -24741,11 +22876,11 @@ @module ember @submodule ember-routing */ var Router = requireModule("router"); -var get = Ember.get, set = Ember.set, classify = Ember.String.classify; +var get = Ember.get, set = Ember.set; var DefaultView = Ember._MetamorphView; function setupLocation(router) { var location = get(router, 'location'), rootURL = get(router, 'rootURL'); @@ -24883,11 +23018,12 @@ } } }); function getHandlerFunction(router) { - var seen = {}, container = router.container; + var seen = {}, container = router.container, + DefaultRoute = container.resolve('route:basic'); return function(name) { var handler = container.lookup('route:' + name); if (seen[name]) { return handler; } @@ -24895,11 +23031,11 @@ if (!handler) { if (name === 'loading') { return {}; } if (name === 'failure') { return router.constructor.defaultFailureHandler; } - container.register('route', name, Ember.Route.extend()); + container.register('route', name, DefaultRoute.extend()); handler = container.lookup('route:' + name); } handler.routeName = name; return handler; @@ -25000,12 +23136,11 @@ @module ember @submodule ember-routing */ var get = Ember.get, set = Ember.set, - classify = Ember.String.classify, - decamelize = Ember.String.decamelize; + classify = Ember.String.classify; /** The `Ember.Route` class is used to define individual routes. Refer to the [routing guide](http://emberjs.com/guides/routing/) for documentation. @@ -25554,17 +23689,19 @@ get = Ember.get; function resolveParams(context, params, options) { var resolved = handlebarsResolve(context, params, options); return map.call(resolved, unwrap); - } - function unwrap(object) { - if (Ember.ControllerMixin.detect(object)) { - return unwrap(get(object, 'model')); - } else { - return object; + function unwrap(object, i) { + if (params[i] === 'controller') { return object; } + + if (Ember.ControllerMixin.detect(object)) { + return unwrap(get(object, 'model')); + } else { + return object; + } } } Ember.Router.resolveParams = resolveParams; }); @@ -25703,46 +23840,56 @@ var get = Ember.get, set = Ember.set; Ember.onLoad('Ember.Handlebars', function(Handlebars) { /** @module ember - @submodule ember-handlebars + @submodule ember-routing */ Handlebars.OutletView = Ember.ContainerView.extend(Ember._Metamorph); /** - The `outlet` helper allows you to specify that the current - view's controller will fill in the view for a given area. + The `outlet` helper is a placeholder that the router will fill in with + the appropriate template based on the current state of the application. ``` handlebars {{outlet}} ``` - By default, when the the current controller's `view` property changes, the - outlet will replace its current view with the new view. You can set the - `view` property directly, but it's normally best to use `connectOutlet`. + By default, a template based on Ember's naming conventions will be rendered + into the `outlet` (e.g. `App.PostsRoute` will render the `posts` template). + You can render a different template by using the `render()` method in the + route's `renderTemplate` hook. The following will render the `favoritePost` + template into the `outlet`. + ``` javascript - # Instantiate App.PostsView and assign to `view`, so as to render into outlet. - controller.connectOutlet('posts'); + App.PostsRoute = Ember.Route.extend({ + renderTemplate: function() { + this.render('favoritePost'); + } + }); ``` - You can also specify a particular name other than `view`: + You can create custom named outlets for more control. ``` handlebars - {{outlet masterView}} - {{outlet detailView}} + {{outlet favoritePost}} + {{outlet posts}} ``` - Then, you can control several outlets from a single controller. + Then you can define what template is rendered into each outlet in your + route. + ``` javascript - # Instantiate App.PostsView and assign to controller.masterView. - controller.connectOutlet('masterView', 'posts'); - # Also, instantiate App.PostInfoView and assign to controller.detailView. - controller.connectOutlet('detailView', 'postInfo'); + App.PostsRoute = Ember.Route.extend({ + renderTemplate: function() { + this.render('favoritePost', { outlet: 'favoritePost' }); + this.render('posts', { outlet: 'posts' }); + } + }); ``` @method outlet @for Ember.Handlebars.helpers @param {String} property the property on the controller @@ -25790,14 +23937,14 @@ If a `model` is specified, it becomes the model for that controller. The default target for `{{action}}`s in the rendered template is the named controller. - @method action + @method render @for Ember.Handlebars.helpers - @param {String} actionName - @param {Object?} model + @param {String} name + @param {Object?} contextString @param {Hash} options */ Ember.Handlebars.registerHelper('render', function(name, contextString, options) { var container, router, controller, view, context, lookupOptions; @@ -25884,26 +24031,43 @@ var ActionHelper = EmberHandlebars.ActionHelper = { registeredActions: {} }; - ActionHelper.registerAction = function(actionName, options) { + var keys = ["alt", "shift", "meta", "ctrl"]; + + var isAllowedClick = function(event, allowedKeys) { + if (typeof allowedKeys === "undefined") { + return isSimpleClick(event); + } + + var allowed = true; + + keys.forEach(function(key) { + if (event[key + "Key"] && allowedKeys.indexOf(key) === -1) { + allowed = false; + } + }); + + return allowed; + }; + + ActionHelper.registerAction = function(actionName, options, allowedKeys) { var actionId = (++Ember.uuid).toString(); ActionHelper.registeredActions[actionId] = { eventName: options.eventName, handler: function(event) { - if (!isSimpleClick(event)) { return true; } + if (!isAllowedClick(event, allowedKeys)) { return true; } + event.preventDefault(); if (options.bubbles === false) { event.stopPropagation(); } - var view = options.view, - contexts = options.contexts, - target = options.target; + var target = options.target; if (target.target) { target = handlebarsGet(target.root, target.target, target.options); } else { target = target.root; @@ -26022,10 +24186,25 @@ only function if an `Ember.EventDispatcher` instance is available. An `Ember.EventDispatcher` instance will be created when a new `Ember.Application` is created. Having an instance of `Ember.Application` will satisfy this requirement. + ### Specifying whitelisted modifier keys + + By default the `{{action}}` helper will ignore click event with pressed modifier + keys. You can supply an `allowedKeys` option to specify which keys should not be ignored. + + ```handlebars + <script type="text/x-handlebars" data-template-name='a-template'> + <div {{action anActionName allowedKeys="alt"}}> + click me + </div> + </script> + ``` + + This way the `{{action}}` will fire when clicking with the alt key pressed down. + ### Specifying a Target There are several possible target objects for `{{action}}` helpers: In a typical Ember application, where views are managed through use of the @@ -26103,11 +24282,11 @@ var options = arguments[arguments.length - 1], contexts = a_slice.call(arguments, 1, -1); var hash = options.hash, view = options.data.view, - controller, link; + controller; // create a hash to pass along to registerAction var action = { eventName: hash.on || "click" }; @@ -26130,11 +24309,11 @@ } action.target = { root: root, target: target, options: options }; action.bubbles = hash.bubbles; - var actionId = ActionHelper.registerAction(actionName, action); + var actionId = ActionHelper.registerAction(actionName, action, hash.allowedKeys); return new SafeString('data-ember-action="' + actionId + '"'); }); }); @@ -26527,11 +24706,11 @@ @module ember @submodule ember-routing */ var get = Ember.get, set = Ember.set; -var popstateReady = false; +var popstateFired = false; /** Ember.HistoryLocation implements the location API using the browser's history.pushState API. @@ -26541,10 +24720,11 @@ */ Ember.HistoryLocation = Ember.Object.extend({ init: function() { set(this, 'location', get(this, 'location') || window.location); + this._initialUrl = this.getURL(); this.initState(); }, /** @private @@ -26593,11 +24773,10 @@ */ setURL: function(path) { path = this.formatURL(path); if (this.getState() && this.getState().path !== path) { - popstateReady = true; this.pushState(path); } }, /** @@ -26611,11 +24790,10 @@ */ replaceURL: function(path) { path = this.formatURL(path); if (this.getState() && this.getState().path !== path) { - popstateReady = true; this.replaceState(path); } }, /** @@ -26665,12 +24843,14 @@ onUpdateURL: function(callback) { var guid = Ember.guidFor(this), 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()); }); }, @@ -27066,14 +25246,18 @@ if (!Ember.testing || Ember.testingDeferred) { this.scheduleInitialize(); } + if ( Ember.LOG_VERSION ) { + Ember.LOG_VERSION = false; // we only need to see this once per Application#init + + } }, /** @private @@ -27124,11 +25308,11 @@ @private Automatically initialize the application once the DOM has become ready. - The initialization itself is deferred using Ember.run.once, + The initialization itself is scheduled on the actions queue which ensures that application loading finishes before booting. If you are asynchronously loading code, you should call `deferReadiness()` to defer booting, and then call @@ -27138,12 +25322,12 @@ @method scheduleInitialize */ scheduleInitialize: function() { var self = this; this.$().ready(function() { - if (self.isDestroyed || self.isInitialized) return; - Ember.run(self, 'initialize'); + if (self.isDestroyed || self.isInitialized) { return; } + Ember.run.schedule('actions', self, 'initialize'); }); }, /** Use this to defer readiness until some condition is true. @@ -27248,11 +25432,11 @@ this.isInitialized = true; // At this point, the App.Router must already be assigned - this.__container__.register('router', 'main', this.Router); + this.register('router', 'main', this.Router); this.runInitializers(); Ember.runLoadHooks('application', this); // At this point, any initializers or load hooks that would have wanted @@ -27279,19 +25463,20 @@ runInitializers: function() { var initializers = get(this.constructor, 'initializers'), container = this.__container__, graph = new Ember.DAG(), namespace = this, - properties, i, initializer; + i, initializer; for (i=0; i<initializers.length; i++) { initializer = initializers[i]; graph.addEdges(initializer.name, initializer.initialize, initializer.before, initializer.after); } graph.topsort(function (vertex) { var initializer = vertex.value; + initializer(container, namespace); }); }, /** @@ -27433,10 +25618,16 @@ container.normalize = normalize; container.resolver = resolverFor(namespace); container.optionsForType('view', { singleton: false }); container.optionsForType('template', { instantiate: false }); container.register('application', 'main', namespace, { instantiate: false }); + + container.register('controller:basic', Ember.Controller, { instantiate: false }); + container.register('controller:object', Ember.ObjectController, { instantiate: false }); + container.register('controller:array', Ember.ArrayController, { instantiate: false }); + container.register('route:basic', Ember.Route, { instantiate: false }); + container.injection('router:main', 'namespace', 'application:main'); container.typeInjection('controller', 'target', 'router:main'); container.typeInjection('controller', 'namespace', 'application:main'); @@ -27481,10 +25672,13 @@ } } if (type === 'controller' || type === 'route' || type === 'view') { name = name.replace(/\./g, '_'); + if (name === 'basic') { + name = ''; + } } if (type !== 'template' && name.indexOf('/') !== -1) { var parts = name.split('/'); name = parts[parts.length - 1]; @@ -27508,15 +25702,15 @@ if (type !== 'template') { 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; } else { return fullName; @@ -27536,11 +25730,11 @@ (function() { /** @module ember -@submodule ember-routing +@submodule ember-application */ var get = Ember.get, set = Ember.set; var ControllersProxy = Ember.Object.extend({ controller: null, @@ -27692,11 +25886,11 @@ } this._super.apply(this, arguments); }, init: function() { - var states = get(this, 'states'), foundStates; + var states = get(this, 'states'); set(this, 'childStates', Ember.A()); set(this, 'eventTransitions', get(this, 'eventTransitions') || {}); var name, value, transitionTarget; @@ -27842,10 +26036,10 @@ */ transitionTo: function(target) { var transitionFunction = function(stateManager, contextOrEvent) { - var contexts = [], transitionArgs, + var contexts = [], Event = Ember.$ && Ember.$.Event; if (contextOrEvent && (Event && contextOrEvent instanceof Event)) { if (contextOrEvent.hasOwnProperty('contexts')) { contexts = contextOrEvent.contexts.slice();