dist/ember-template-compiler.js in ember-source-1.13.0.beta.1 vs dist/ember-template-compiler.js in ember-source-1.13.0.beta.2
- old
+ new
@@ -3,11 +3,11 @@
* @copyright Copyright 2011-2015 Tilde Inc. and contributors
* Portions Copyright 2006-2011 Strobe Inc.
* Portions Copyright 2008-2011 Apple Inc. All rights reserved.
* @license Licensed under MIT license
* See https://raw.github.com/emberjs/ember.js/master/LICENSE
- * @version 1.13.0-beta.1
+ * @version 1.13.0-beta.2
*/
(function() {
var enifed, requireModule, eriuqer, requirejs, Ember;
var mainContext = this;
@@ -114,20 +114,10 @@
'use strict';
exports._warnIfUsingStrippedFeatureFlags = _warnIfUsingStrippedFeatureFlags;
- /**
- Will call `Ember.warn()` if ENABLE_ALL_FEATURES, ENABLE_OPTIONAL_FEATURES, or
- any specific FEATURES flag is truthy.
-
- This method is called automatically in debug canary builds.
-
- @private
- @method _warnIfUsingStrippedFeatureFlags
- @return {void}
- */
function isPlainFunction(test) {
return typeof test === "function" && test.PrototypeMixin === undefined;
}
/**
@@ -307,10 +297,21 @@
@since 1.5.0
*/
Ember['default'].runInDebug = function (func) {
func();
};
+
+ /**
+ Will call `Ember.warn()` if ENABLE_ALL_FEATURES, ENABLE_OPTIONAL_FEATURES, or
+ any specific FEATURES flag is truthy.
+
+ This method is called automatically in debug canary builds.
+
+ @private
+ @method _warnIfUsingStrippedFeatureFlags
+ @return {void}
+ */
function _warnIfUsingStrippedFeatureFlags(FEATURES, featuresWereStripped) {
if (featuresWereStripped) {
Ember['default'].warn("Ember.ENV.ENABLE_ALL_FEATURES is only available in canary builds.", !Ember['default'].ENV.ENABLE_ALL_FEATURES);
Ember['default'].warn("Ember.ENV.ENABLE_OPTIONAL_FEATURES is only available in canary builds.", !Ember['default'].ENV.ENABLE_OPTIONAL_FEATURES);
@@ -809,154 +810,10 @@
exports.bind = bind;
exports.oneWay = oneWay;
exports.Binding = Binding;
- /**
- An `Ember.Binding` connects the properties of two objects so that whenever
- the value of one property changes, the other property will be changed also.
-
- ## Automatic Creation of Bindings with `/^*Binding/`-named Properties
-
- You do not usually create Binding objects directly but instead describe
- bindings in your class or object definition using automatic binding
- detection.
-
- Properties ending in a `Binding` suffix will be converted to `Ember.Binding`
- instances. The value of this property should be a string representing a path
- to another object or a custom binding instance created using Binding helpers
- (see "One Way Bindings"):
-
- ```
- valueBinding: "MyApp.someController.title"
- ```
-
- This will create a binding from `MyApp.someController.title` to the `value`
- property of your object instance automatically. Now the two values will be
- kept in sync.
-
- ## One Way Bindings
-
- One especially useful binding customization you can use is the `oneWay()`
- helper. This helper tells Ember that you are only interested in
- receiving changes on the object you are binding from. For example, if you
- are binding to a preference and you want to be notified if the preference
- has changed, but your object will not be changing the preference itself, you
- could do:
-
- ```
- bigTitlesBinding: Ember.Binding.oneWay("MyApp.preferencesController.bigTitles")
- ```
-
- This way if the value of `MyApp.preferencesController.bigTitles` changes the
- `bigTitles` property of your object will change also. However, if you
- change the value of your `bigTitles` property, it will not update the
- `preferencesController`.
-
- One way bindings are almost twice as fast to setup and twice as fast to
- execute because the binding only has to worry about changes to one side.
-
- You should consider using one way bindings anytime you have an object that
- may be created frequently and you do not intend to change a property; only
- to monitor it for changes (such as in the example above).
-
- ## Adding Bindings Manually
-
- All of the examples above show you how to configure a custom binding, but the
- result of these customizations will be a binding template, not a fully active
- Binding instance. The binding will actually become active only when you
- instantiate the object the binding belongs to. It is useful however, to
- understand what actually happens when the binding is activated.
-
- For a binding to function it must have at least a `from` property and a `to`
- property. The `from` property path points to the object/key that you want to
- bind from while the `to` path points to the object/key you want to bind to.
-
- When you define a custom binding, you are usually describing the property
- you want to bind from (such as `MyApp.someController.value` in the examples
- above). When your object is created, it will automatically assign the value
- you want to bind `to` based on the name of your binding key. In the
- examples above, during init, Ember objects will effectively call
- something like this on your binding:
-
- ```javascript
- binding = Ember.Binding.from("valueBinding").to("value");
- ```
-
- This creates a new binding instance based on the template you provide, and
- sets the to path to the `value` property of the new object. Now that the
- binding is fully configured with a `from` and a `to`, it simply needs to be
- connected to become active. This is done through the `connect()` method:
-
- ```javascript
- binding.connect(this);
- ```
-
- Note that when you connect a binding you pass the object you want it to be
- connected to. This object will be used as the root for both the from and
- to side of the binding when inspecting relative paths. This allows the
- binding to be automatically inherited by subclassed objects as well.
-
- This also allows you to bind between objects using the paths you declare in
- `from` and `to`:
-
- ```javascript
- // Example 1
- binding = Ember.Binding.from("App.someObject.value").to("value");
- binding.connect(this);
-
- // Example 2
- binding = Ember.Binding.from("parentView.value").to("App.someObject.value");
- binding.connect(this);
- ```
-
- Now that the binding is connected, it will observe both the from and to side
- and relay changes.
-
- If you ever needed to do so (you almost never will, but it is useful to
- understand this anyway), you could manually create an active binding by
- using the `Ember.bind()` helper method. (This is the same method used by
- to setup your bindings on objects):
-
- ```javascript
- Ember.bind(MyApp.anotherObject, "value", "MyApp.someController.value");
- ```
-
- Both of these code fragments have the same effect as doing the most friendly
- form of binding creation like so:
-
- ```javascript
- MyApp.anotherObject = Ember.Object.create({
- valueBinding: "MyApp.someController.value",
-
- // OTHER CODE FOR THIS OBJECT...
- });
- ```
-
- Ember's built in binding creation method makes it easy to automatically
- create bindings for you. You should always use the highest-level APIs
- available, even if you understand how it works underneath.
-
- @class Binding
- @namespace Ember
- @since Ember 0.9
- */
- // Ember.Binding = Binding; ES6TODO: where to put this?
-
- /**
- Global helper method to create a new binding. Just pass the root object
- along with a `to` and `from` path to create and connect the binding.
-
- @method bind
- @for Ember
- @param {Object} obj The root object of the transform.
- @param {String} to The path to the 'to' side of the binding.
- Must be relative to obj.
- @param {String} from The path to the 'from' side of the binding.
- Must be relative to obj or a global path.
- @return {Ember.Binding} binding instance
- */
Ember['default'].LOG_BINDINGS = false || !!Ember['default'].ENV.LOG_BINDINGS;
/**
Returns true if the provided path is global (e.g., `MyApp.fooController.bar`)
instead of local (`foo.bar.baz`).
@@ -1240,14 +1097,168 @@
var C = this;
return new C(undefined, from).oneWay(flag);
}
});
+ /**
+ An `Ember.Binding` connects the properties of two objects so that whenever
+ the value of one property changes, the other property will be changed also.
+
+ ## Automatic Creation of Bindings with `/^*Binding/`-named Properties
+
+ You do not usually create Binding objects directly but instead describe
+ bindings in your class or object definition using automatic binding
+ detection.
+
+ Properties ending in a `Binding` suffix will be converted to `Ember.Binding`
+ instances. The value of this property should be a string representing a path
+ to another object or a custom binding instance created using Binding helpers
+ (see "One Way Bindings"):
+
+ ```
+ valueBinding: "MyApp.someController.title"
+ ```
+
+ This will create a binding from `MyApp.someController.title` to the `value`
+ property of your object instance automatically. Now the two values will be
+ kept in sync.
+
+ ## One Way Bindings
+
+ One especially useful binding customization you can use is the `oneWay()`
+ helper. This helper tells Ember that you are only interested in
+ receiving changes on the object you are binding from. For example, if you
+ are binding to a preference and you want to be notified if the preference
+ has changed, but your object will not be changing the preference itself, you
+ could do:
+
+ ```
+ bigTitlesBinding: Ember.Binding.oneWay("MyApp.preferencesController.bigTitles")
+ ```
+
+ This way if the value of `MyApp.preferencesController.bigTitles` changes the
+ `bigTitles` property of your object will change also. However, if you
+ change the value of your `bigTitles` property, it will not update the
+ `preferencesController`.
+
+ One way bindings are almost twice as fast to setup and twice as fast to
+ execute because the binding only has to worry about changes to one side.
+
+ You should consider using one way bindings anytime you have an object that
+ may be created frequently and you do not intend to change a property; only
+ to monitor it for changes (such as in the example above).
+
+ ## Adding Bindings Manually
+
+ All of the examples above show you how to configure a custom binding, but the
+ result of these customizations will be a binding template, not a fully active
+ Binding instance. The binding will actually become active only when you
+ instantiate the object the binding belongs to. It is useful however, to
+ understand what actually happens when the binding is activated.
+
+ For a binding to function it must have at least a `from` property and a `to`
+ property. The `from` property path points to the object/key that you want to
+ bind from while the `to` path points to the object/key you want to bind to.
+
+ When you define a custom binding, you are usually describing the property
+ you want to bind from (such as `MyApp.someController.value` in the examples
+ above). When your object is created, it will automatically assign the value
+ you want to bind `to` based on the name of your binding key. In the
+ examples above, during init, Ember objects will effectively call
+ something like this on your binding:
+
+ ```javascript
+ binding = Ember.Binding.from("valueBinding").to("value");
+ ```
+
+ This creates a new binding instance based on the template you provide, and
+ sets the to path to the `value` property of the new object. Now that the
+ binding is fully configured with a `from` and a `to`, it simply needs to be
+ connected to become active. This is done through the `connect()` method:
+
+ ```javascript
+ binding.connect(this);
+ ```
+
+ Note that when you connect a binding you pass the object you want it to be
+ connected to. This object will be used as the root for both the from and
+ to side of the binding when inspecting relative paths. This allows the
+ binding to be automatically inherited by subclassed objects as well.
+
+ This also allows you to bind between objects using the paths you declare in
+ `from` and `to`:
+
+ ```javascript
+ // Example 1
+ binding = Ember.Binding.from("App.someObject.value").to("value");
+ binding.connect(this);
+
+ // Example 2
+ binding = Ember.Binding.from("parentView.value").to("App.someObject.value");
+ binding.connect(this);
+ ```
+
+ Now that the binding is connected, it will observe both the from and to side
+ and relay changes.
+
+ If you ever needed to do so (you almost never will, but it is useful to
+ understand this anyway), you could manually create an active binding by
+ using the `Ember.bind()` helper method. (This is the same method used by
+ to setup your bindings on objects):
+
+ ```javascript
+ Ember.bind(MyApp.anotherObject, "value", "MyApp.someController.value");
+ ```
+
+ Both of these code fragments have the same effect as doing the most friendly
+ form of binding creation like so:
+
+ ```javascript
+ MyApp.anotherObject = Ember.Object.create({
+ valueBinding: "MyApp.someController.value",
+
+ // OTHER CODE FOR THIS OBJECT...
+ });
+ ```
+
+ Ember's built in binding creation method makes it easy to automatically
+ create bindings for you. You should always use the highest-level APIs
+ available, even if you understand how it works underneath.
+
+ @class Binding
+ @namespace Ember
+ @since Ember 0.9
+ */
+ // Ember.Binding = Binding; ES6TODO: where to put this?
+
+ /**
+ Global helper method to create a new binding. Just pass the root object
+ along with a `to` and `from` path to create and connect the binding.
+
+ @method bind
+ @for Ember
+ @param {Object} obj The root object of the transform.
+ @param {String} to The path to the 'to' side of the binding.
+ Must be relative to obj.
+ @param {String} from The path to the 'from' side of the binding.
+ Must be relative to obj or a global path.
+ @return {Ember.Binding} binding instance
+ */
function bind(obj, to, from) {
return new Binding(to, from).connect(obj);
}
+ /**
+ @method oneWay
+ @for Ember
+ @param {Object} obj The root object of the transform.
+ @param {String} to The path to the 'to' side of the binding.
+ Must be relative to obj.
+ @param {String} from The path to the 'from' side of the binding.
+ Must be relative to obj or a global path.
+ @return {Ember.Binding} binding instance
+ */
function oneWay(obj, to, from) {
return new Binding(to, from).oneWay().connect(obj);
}
exports.isGlobalPath = path_cache.isGlobal;
@@ -1317,13 +1328,10 @@
exports.flushPendingChains = flushPendingChains;
exports.finishChains = finishChains;
exports.removeChainWatcher = removeChainWatcher;
exports.ChainNode = ChainNode;
- // attempts to add the pendingQueue chains again. If some of them end up
- // back in the queue and reschedule is true, schedules a timeout to try
- // again.
var warn = Ember['default'].warn;
var FIRST_KEY = /^([^\.]+)/;
function firstKey(path) {
return path.match(FIRST_KEY)[0];
@@ -1332,10 +1340,15 @@
function isObject(obj) {
return obj && typeof obj === "object";
}
var pendingQueue = [];
+
+ // attempts to add the pendingQueue chains again. If some of them end up
+ // back in the queue and reschedule is true, schedules a timeout to try
+ // again.
+
function flushPendingChains() {
if (pendingQueue.length === 0) {
return;
}
@@ -1813,28 +1826,27 @@
@namespace Ember
@constructor
*/
function ComputedProperty(config, opts) {
this.isDescriptor = true;
-
- if (typeof config === "function") {
- config.__ember_arity = config.length;
- this._getter = config;
- if (config.__ember_arity > 1) {
- Ember.deprecate("Using the same function as getter and setter is deprecated.", false, {
- url: "http://emberjs.com/deprecations/v1.x/#toc_computed-properties-with-a-shared-getter-and-setter"
- });
- this._setter = config;
- }
- } else {
- this._getter = config.get;
- this._setter = config.set;
- if (this._setter && this._setter.__ember_arity === undefined) {
- this._setter.__ember_arity = this._setter.length;
- }
+ if (typeof config === "function") {
+ config.__ember_arity = config.length;
+ this._getter = config;
+ if (config.__ember_arity > 1) {
+ Ember.deprecate("Using the same function as getter and setter is deprecated.", false, {
+ url: "http://emberjs.com/deprecations/v1.x/#toc_deprecate-using-the-same-function-as-getter-and-setter-in-computed-properties"
+ });
+ this._setter = config;
}
-
+ } else {
+ this._getter = config.get;
+ this._setter = config.set;
+ if (this._setter && this._setter.__ember_arity === undefined) {
+ this._setter.__ember_arity = this._setter.length;
+ }
+ }
+
this._dependentKeys = undefined;
this._suspended = undefined;
this._meta = undefined;
Ember.deprecate("Passing opts.cacheable to the CP constructor is deprecated. Invoke `volatile()` on the CP instead.", !opts || !opts.hasOwnProperty("cacheable"));
@@ -2280,12 +2292,11 @@
args = [].slice.call(arguments);
func = args.pop();
}
var cp = new ComputedProperty(func);
- // jscs:disable
-
+
if (args) {
cp.property.apply(cp, args);
}
return cp;
@@ -2354,10 +2365,32 @@
exports.oneWay = oneWay;
exports.readOnly = readOnly;
exports.defaultTo = defaultTo;
exports.deprecatingAlias = deprecatingAlias;
+ function getProperties(self, propertyNames) {
+ var ret = {};
+ for (var i = 0; i < propertyNames.length; i++) {
+ ret[propertyNames[i]] = property_get.get(self, propertyNames[i]);
+ }
+ return ret;
+ }
+
+ function generateComputedWithProperties(macro) {
+ return function () {
+ for (var _len = arguments.length, properties = Array(_len), _key = 0; _key < _len; _key++) {
+ properties[_key] = arguments[_key];
+ }
+
+ var computedFunc = computed.computed(function () {
+ return macro.apply(this, [getProperties(this, properties)]);
+ });
+
+ return computedFunc.property.apply(computedFunc, properties);
+ };
+ }
+
/**
A computed property that returns true if the value of the dependent
property is null, an empty string, empty array, or empty function.
Example
@@ -2381,93 +2414,336 @@
@for Ember.computed
@param {String} dependentKey
@return {Ember.ComputedProperty} computed property which negate
the original value for property
*/
- function getProperties(self, propertyNames) {
- var ret = {};
- for (var i = 0; i < propertyNames.length; i++) {
- ret[propertyNames[i]] = property_get.get(self, propertyNames[i]);
- }
- return ret;
- }
-
- function generateComputedWithProperties(macro) {
- return function () {
- for (var _len = arguments.length, properties = Array(_len), _key = 0; _key < _len; _key++) {
- properties[_key] = arguments[_key];
- }
-
- var computedFunc = computed.computed(function () {
- return macro.apply(this, [getProperties(this, properties)]);
- });
-
- return computedFunc.property.apply(computedFunc, properties);
- };
- }
function empty(dependentKey) {
return computed.computed(dependentKey + ".length", function () {
return isEmpty['default'](property_get.get(this, dependentKey));
});
}
+ /**
+ A computed property that returns true if the value of the dependent
+ property is NOT null, an empty string, empty array, or empty function.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ hasStuff: Ember.computed.notEmpty('backpack')
+ });
+
+ var hamster = Hamster.create({ backpack: ['Food', 'Sleeping Bag', 'Tent'] });
+
+ hamster.get('hasStuff'); // true
+ hamster.get('backpack').clear(); // []
+ hamster.get('hasStuff'); // false
+ ```
+
+ @method notEmpty
+ @for Ember.computed
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which returns true if
+ original value for property is not empty.
+ */
function notEmpty(dependentKey) {
return computed.computed(dependentKey + ".length", function () {
return !isEmpty['default'](property_get.get(this, dependentKey));
});
}
+ /**
+ A computed property that returns true if the value of the dependent
+ property is null or undefined. This avoids errors from JSLint complaining
+ about use of ==, which can be technically confusing.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ isHungry: Ember.computed.none('food')
+ });
+
+ var hamster = Hamster.create();
+
+ hamster.get('isHungry'); // true
+ hamster.set('food', 'Banana');
+ hamster.get('isHungry'); // false
+ hamster.set('food', null);
+ hamster.get('isHungry'); // true
+ ```
+
+ @method none
+ @for Ember.computed
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which
+ returns true if original value for property is null or undefined.
+ */
function none(dependentKey) {
return computed.computed(dependentKey, function () {
return isNone['default'](property_get.get(this, dependentKey));
});
}
+ /**
+ A computed property that returns the inverse boolean value
+ of the original value for the dependent property.
+
+ Example
+
+ ```javascript
+ var User = Ember.Object.extend({
+ isAnonymous: Ember.computed.not('loggedIn')
+ });
+
+ var user = User.create({loggedIn: false});
+
+ user.get('isAnonymous'); // true
+ user.set('loggedIn', true);
+ user.get('isAnonymous'); // false
+ ```
+
+ @method not
+ @for Ember.computed
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which returns
+ inverse of the original value for property
+ */
function not(dependentKey) {
return computed.computed(dependentKey, function () {
return !property_get.get(this, dependentKey);
});
}
+ /**
+ A computed property that converts the provided dependent property
+ into a boolean value.
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ hasBananas: Ember.computed.bool('numBananas')
+ });
+
+ var hamster = Hamster.create();
+
+ hamster.get('hasBananas'); // false
+ hamster.set('numBananas', 0);
+ hamster.get('hasBananas'); // false
+ hamster.set('numBananas', 1);
+ hamster.get('hasBananas'); // true
+ hamster.set('numBananas', null);
+ hamster.get('hasBananas'); // false
+ ```
+
+ @method bool
+ @for Ember.computed
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which converts
+ to boolean the original value for property
+ */
function bool(dependentKey) {
return computed.computed(dependentKey, function () {
return !!property_get.get(this, dependentKey);
});
}
+ /**
+ A computed property which matches the original value for the
+ dependent property against a given RegExp, returning `true`
+ if they values matches the RegExp and `false` if it does not.
+
+ Example
+
+ ```javascript
+ var User = Ember.Object.extend({
+ hasValidEmail: Ember.computed.match('email', /^.+@.+\..+$/)
+ });
+
+ var user = User.create({loggedIn: false});
+
+ user.get('hasValidEmail'); // false
+ user.set('email', '');
+ user.get('hasValidEmail'); // false
+ user.set('email', 'ember_hamster@example.com');
+ user.get('hasValidEmail'); // true
+ ```
+
+ @method match
+ @for Ember.computed
+ @param {String} dependentKey
+ @param {RegExp} regexp
+ @return {Ember.ComputedProperty} computed property which match
+ the original value for property against a given RegExp
+ */
function match(dependentKey, regexp) {
return computed.computed(dependentKey, function () {
var value = property_get.get(this, dependentKey);
return typeof value === "string" ? regexp.test(value) : false;
});
}
+ /**
+ A computed property that returns true if the provided dependent property
+ is equal to the given value.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ napTime: Ember.computed.equal('state', 'sleepy')
+ });
+
+ var hamster = Hamster.create();
+
+ hamster.get('napTime'); // false
+ hamster.set('state', 'sleepy');
+ hamster.get('napTime'); // true
+ hamster.set('state', 'hungry');
+ hamster.get('napTime'); // false
+ ```
+
+ @method equal
+ @for Ember.computed
+ @param {String} dependentKey
+ @param {String|Number|Object} value
+ @return {Ember.ComputedProperty} computed property which returns true if
+ the original value for property is equal to the given value.
+ */
function equal(dependentKey, value) {
return computed.computed(dependentKey, function () {
return property_get.get(this, dependentKey) === value;
});
}
+ /**
+ A computed property that returns true if the provided dependent property
+ is greater than the provided value.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ hasTooManyBananas: Ember.computed.gt('numBananas', 10)
+ });
+
+ var hamster = Hamster.create();
+
+ hamster.get('hasTooManyBananas'); // false
+ hamster.set('numBananas', 3);
+ hamster.get('hasTooManyBananas'); // false
+ hamster.set('numBananas', 11);
+ hamster.get('hasTooManyBananas'); // true
+ ```
+
+ @method gt
+ @for Ember.computed
+ @param {String} dependentKey
+ @param {Number} value
+ @return {Ember.ComputedProperty} computed property which returns true if
+ the original value for property is greater than given value.
+ */
function gt(dependentKey, value) {
return computed.computed(dependentKey, function () {
return property_get.get(this, dependentKey) > value;
});
}
+ /**
+ A computed property that returns true if the provided dependent property
+ is greater than or equal to the provided value.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ hasTooManyBananas: Ember.computed.gte('numBananas', 10)
+ });
+
+ var hamster = Hamster.create();
+
+ hamster.get('hasTooManyBananas'); // false
+ hamster.set('numBananas', 3);
+ hamster.get('hasTooManyBananas'); // false
+ hamster.set('numBananas', 10);
+ hamster.get('hasTooManyBananas'); // true
+ ```
+
+ @method gte
+ @for Ember.computed
+ @param {String} dependentKey
+ @param {Number} value
+ @return {Ember.ComputedProperty} computed property which returns true if
+ the original value for property is greater or equal then given value.
+ */
function gte(dependentKey, value) {
return computed.computed(dependentKey, function () {
return property_get.get(this, dependentKey) >= value;
});
}
+ /**
+ A computed property that returns true if the provided dependent property
+ is less than the provided value.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ needsMoreBananas: Ember.computed.lt('numBananas', 3)
+ });
+
+ var hamster = Hamster.create();
+
+ hamster.get('needsMoreBananas'); // true
+ hamster.set('numBananas', 3);
+ hamster.get('needsMoreBananas'); // false
+ hamster.set('numBananas', 2);
+ hamster.get('needsMoreBananas'); // true
+ ```
+
+ @method lt
+ @for Ember.computed
+ @param {String} dependentKey
+ @param {Number} value
+ @return {Ember.ComputedProperty} computed property which returns true if
+ the original value for property is less then given value.
+ */
function lt(dependentKey, value) {
return computed.computed(dependentKey, function () {
return property_get.get(this, dependentKey) < value;
});
}
+ /**
+ A computed property that returns true if the provided dependent property
+ is less than or equal to the provided value.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ needsMoreBananas: Ember.computed.lte('numBananas', 3)
+ });
+
+ var hamster = Hamster.create();
+
+ hamster.get('needsMoreBananas'); // true
+ hamster.set('numBananas', 5);
+ hamster.get('needsMoreBananas'); // false
+ hamster.set('numBananas', 3);
+ hamster.get('needsMoreBananas'); // true
+ ```
+
+ @method lte
+ @for Ember.computed
+ @param {String} dependentKey
+ @param {Number} value
+ @return {Ember.ComputedProperty} computed property which returns true if
+ the original value for property is less or equal than given value.
+ */
function lte(dependentKey, value) {
return computed.computed(dependentKey, function () {
return property_get.get(this, dependentKey) <= value;
});
}
@@ -2539,18 +2815,92 @@
res.push(properties[key]);
}
}
}
return res;
- });function oneWay(dependentKey) {
+ });
+
+ function oneWay(dependentKey) {
return alias['default'](dependentKey).oneWay();
}
+ /**
+ This is a more semantically meaningful alias of `computed.oneWay`,
+ whose name is somewhat ambiguous as to which direction the data flows.
+
+ @method reads
+ @for Ember.computed
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which creates a
+ one way computed property to the original value for property.
+ */
+
+ /**
+ Where `computed.oneWay` provides oneWay bindings, `computed.readOnly` provides
+ a readOnly one way binding. Very often when using `computed.oneWay` one does
+ not also want changes to propagate back up, as they will replace the value.
+
+ This prevents the reverse flow, and also throws an exception when it occurs.
+
+ Example
+
+ ```javascript
+ var User = Ember.Object.extend({
+ firstName: null,
+ lastName: null,
+ nickName: Ember.computed.readOnly('firstName')
+ });
+
+ var teddy = User.create({
+ firstName: 'Teddy',
+ lastName: 'Zeenny'
+ });
+
+ teddy.get('nickName'); // 'Teddy'
+ teddy.set('nickName', 'TeddyBear'); // throws Exception
+ // throw new Ember.Error('Cannot Set: nickName on: <User:ember27288>' );`
+ teddy.get('firstName'); // 'Teddy'
+ ```
+
+ @method readOnly
+ @for Ember.computed
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which creates a
+ one way computed property to the original value for property.
+ @since 1.5.0
+ */
function readOnly(dependentKey) {
return alias['default'](dependentKey).readOnly();
}
+ /**
+ A computed property that acts like a standard getter and setter,
+ but returns the value at the provided `defaultPath` if the
+ property itself has not been set to a value
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ wishList: Ember.computed.defaultTo('favoriteFood')
+ });
+
+ var hamster = Hamster.create({ favoriteFood: 'Banana' });
+
+ hamster.get('wishList'); // 'Banana'
+ hamster.set('wishList', 'More Unit Tests');
+ hamster.get('wishList'); // 'More Unit Tests'
+ hamster.get('favoriteFood'); // 'Banana'
+ ```
+
+ @method defaultTo
+ @for Ember.computed
+ @param {String} defaultPath
+ @return {Ember.ComputedProperty} computed property which acts like
+ a standard getter and setter, but defaults to the value from `defaultPath`.
+ @deprecated Use `Ember.computed.oneWay` or custom CP with default instead.
+ */
function defaultTo(defaultPath) {
return computed.computed({
get: function (key) {
Ember['default'].deprecate("Usage of Ember.computed.defaultTo is deprecated, use `Ember.computed.oneWay` instead.");
return property_get.get(this, defaultPath);
@@ -2561,10 +2911,23 @@
return newValue != null ? newValue : property_get.get(this, defaultPath);
}
});
}
+ /**
+ Creates a new property that is an alias for another property
+ on an object. Calls to `get` or `set` this property behave as
+ though they were called on the original property, but also
+ print a deprecation warning.
+
+ @method deprecatingAlias
+ @for Ember.computed
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which creates an
+ alias with a deprecation to the original value for property.
+ @since 1.7.0
+ */
function deprecatingAlias(dependentKey) {
return computed.computed(dependentKey, {
get: function (key) {
Ember['default'].deprecate("Usage of `" + key + "` is deprecated, use `" + dependentKey + "` instead.");
return property_get.get(this, dependentKey);
@@ -2595,27 +2958,25 @@
@module ember
@submodule ember-metal
*/
/**
- All Ember methods and functions are defined inside of this namespace. You
- generally should not add new properties to this namespace as it may be
- overwritten by future versions of Ember.
+ This namespace contains all Ember methods and functions. Future versions of
+ Ember may overwrite this namespace and therefore, you should avoid adding any
+ new properties.
You can also use the shorthand `Em` instead of `Ember`.
- Ember-Runtime is a framework that provides core functions for Ember including
- cross-platform functions, support for property observing and objects. Its
- focus is on small size and performance. You can use this in place of or
- along-side other cross-platform libraries such as jQuery.
+ At the heart of Ember is Ember-Runtime, a set of core functions that provide
+ cross-platform compatibility and object property observing. Ember-Runtime is
+ small and performance-focused so you can use it alongside other
+ cross-platform libraries such as jQuery. For more details, see
+ [Ember-Runtime](http://emberjs.com/api/modules/ember-runtime.html).
- The core Runtime framework is based on the jQuery API with a number of
- performance optimizations.
-
@class Ember
@static
- @version 1.13.0-beta.1
+ @version 1.13.0-beta.2
*/
if ('undefined' === typeof Ember) {
// Create core object. Make it act like an instance of Ember.Namespace so that
// objects assigned to it are given a sane string representation.
@@ -2638,24 +2999,26 @@
Ember.toString = function () {
return 'Ember';
};
/**
+ The semantic version.
+
@property VERSION
@type String
- @default '1.13.0-beta.1'
+ @default '1.13.0-beta.2'
@static
*/
- Ember.VERSION = '1.13.0-beta.1';
+ Ember.VERSION = '1.13.0-beta.2';
/**
- Standard environmental variables. You can define these in a global `EmberENV`
- variable before loading Ember to control various configuration settings.
+ The hash of environment variables used to control various configuration
+ settings. To specify your own or override default settings, add the
+ desired properties to a global hash named `EmberENV` (or `ENV` for
+ backwards compatibility with earlier versions of Ember). The `EmberENV`
+ hash must be created before loading Ember.
- For backwards compatibility with earlier versions of Ember the global `ENV`
- variable will be used if `EmberENV` is not defined.
-
@property ENV
@type Hash
*/
if (Ember.ENV) {
@@ -2675,41 +3038,43 @@
if ('undefined' === typeof Ember.ENV.DISABLE_RANGE_API) {
Ember.ENV.DISABLE_RANGE_API = true;
}
/**
- Hash of enabled Canary features. Add to this before creating your application.
+ The hash of enabled Canary features. Add to this, any canary features
+ before creating your application.
- You can also define `EmberENV.FEATURES` if you need to enable features flagged at runtime.
+ Alternatively (and recommended), you can also define `EmberENV.FEATURES`
+ if you need to enable features flagged at runtime.
@class FEATURES
@namespace Ember
@static
@since 1.1.0
*/
- Ember.FEATURES = { 'features-stripped-test': false, 'ember-routing-named-substates': true, 'mandatory-setter': true, 'ember-htmlbars-component-generation': true, 'ember-htmlbars-component-helper': true, 'ember-htmlbars-inline-if-helper': true, 'ember-htmlbars-attribute-syntax': true, 'ember-routing-transitioning-classes': true, 'new-computed-syntax': true, 'ember-testing-checkbox-helpers': false, 'ember-metal-stream': false, 'ember-application-instance-initializers': true, 'ember-application-initializer-context': true, 'ember-router-willtransition': true, 'ember-application-visit': false, 'ember-views-component-block-info': true, 'ember-routing-core-outlet': false, 'ember-libraries-isregistered': false, 'ember-routing-htmlbars-improved-actions': true }; //jshint ignore:line
+ Ember.FEATURES = { 'features-stripped-test': false, 'ember-routing-named-substates': true, 'mandatory-setter': true, 'ember-htmlbars-component-generation': false, 'ember-htmlbars-component-helper': true, 'ember-htmlbars-inline-if-helper': true, 'ember-htmlbars-attribute-syntax': true, 'ember-routing-transitioning-classes': true, 'ember-testing-checkbox-helpers': false, 'ember-metal-stream': false, 'ember-application-instance-initializers': true, 'ember-application-initializer-context': true, 'ember-router-willtransition': true, 'ember-application-visit': false, 'ember-views-component-block-info': true, 'ember-routing-core-outlet': false, 'ember-libraries-isregistered': false, 'ember-routing-htmlbars-improved-actions': true }; //jshint ignore:line
if (Ember.ENV.FEATURES) {
for (var feature in Ember.ENV.FEATURES) {
if (Ember.ENV.FEATURES.hasOwnProperty(feature)) {
Ember.FEATURES[feature] = Ember.ENV.FEATURES[feature];
}
}
}
/**
- Test that a feature is enabled. Parsed by Ember's build tools to leave
- experimental features out of beta/stable builds.
+ Determine whether the specified `feature` is enabled. Used by Ember's
+ build tools to exclude experimental features from beta/stable builds.
You can define the following configuration options:
* `EmberENV.ENABLE_ALL_FEATURES` - force all features to be enabled.
* `EmberENV.ENABLE_OPTIONAL_FEATURES` - enable any features that have not been explicitly
enabled/disabled.
@method isEnabled
- @param {String} feature
+ @param {String} feature The feature to check
@return {Boolean}
@for Ember.FEATURES
@since 1.1.0
*/
@@ -2730,19 +3095,22 @@
// ..........................................................
// BOOTSTRAP
//
/**
- Determines whether Ember should enhance some built-in object prototypes to
- provide a more friendly API. If enabled, a few methods will be added to
- `Function`, `String`, and `Array`. `Object.prototype` will not be enhanced,
- which is the one that causes most trouble for people.
+ Determines whether Ember should add to `Array`, `Function`, and `String`
+ native object prototypes, a few extra methods in order to provide a more
+ friendly API.
- In general we recommend leaving this option set to true since it rarely
- conflicts with other code. If you need to turn it off however, you can
- define an `EmberENV.EXTEND_PROTOTYPES` config to disable it.
+ We generally recommend leaving this option set to true however, if you need
+ to turn it off, you can add the configuration property
+ `EXTEND_PROTOTYPES` to `EmberENV` and set it to `false`.
+ Note, when disabled (the default configuration for Ember Addons), you will
+ instead have to access all methods and functions from the Ember
+ namespace.
+
@property EXTEND_PROTOTYPES
@type Boolean
@default true
@for Ember
*/
@@ -2751,38 +3119,41 @@
if (typeof Ember.EXTEND_PROTOTYPES === 'undefined') {
Ember.EXTEND_PROTOTYPES = true;
}
/**
- Determines whether Ember logs a full stack trace during deprecation warnings
+ The `LOG_STACKTRACE_ON_DEPRECATION` property, when true, tells Ember to log
+ a full stack trace during deprecation warnings.
@property LOG_STACKTRACE_ON_DEPRECATION
@type Boolean
@default true
*/
Ember.LOG_STACKTRACE_ON_DEPRECATION = Ember.ENV.LOG_STACKTRACE_ON_DEPRECATION !== false;
/**
- Determines whether Ember should add ECMAScript 5 Array shims to older browsers.
+ The `SHIM_ES5` property, when true, tells Ember to add ECMAScript 5 Array
+ shims to older browsers.
@property SHIM_ES5
@type Boolean
@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
+ The `LOG_VERSION` property, when true, tells Ember to log versions of all
+ dependent libraries in use.
@property LOG_VERSION
@type Boolean
@default true
*/
Ember.LOG_VERSION = Ember.ENV.LOG_VERSION === false ? false : true;
/**
- Empty function. Useful for some operations. Always returns `this`.
+ An empty function useful for some operations. Always returns `this`.
@method K
@private
@return {Object}
*/
@@ -2909,22 +3280,10 @@
'use strict';
exports.deprecateProperty = deprecateProperty;
- /**
- Used internally to allow changing properties in a backwards compatible way, and print a helpful
- deprecation warning.
-
- @method deprecateProperty
- @param {Object} object The object to add the deprecated property to.
- @param {String} deprecatedKey The property to add (and print deprecation warnings upon accessing).
- @param {String} newKey The property that will be aliased.
- @private
- @since 1.7.0
- */
-
function deprecateProperty(object, deprecatedKey, newKey) {
function deprecate() {
Ember['default'].deprecate("Usage of `" + deprecatedKey + "` is deprecated, use `" + newKey + "` instead.");
}
@@ -2948,16 +3307,10 @@
enifed('ember-metal/dictionary', ['exports', 'ember-metal/platform/create'], function (exports, create) {
'use strict';
-
- // the delete is meant to hint at runtimes that this object should remain in
- // dictionary mode. This is clearly a runtime specific hack, but currently it
- // appears worthwhile in some usecases. Please note, these deletes do increase
- // the cost of creation dramatically over a plain Object.create. And as this
- // only makes sense for long-lived dictionaries that aren't instantiated often.
exports['default'] = makeDictionary;
function makeDictionary(parent) {
var dict = create['default'](parent);
dict['_dict'] = null;
delete dict['_dict'];
@@ -2978,10 +3331,12 @@
exports.removeObject = removeObject;
exports._replace = _replace;
exports.replace = replace;
exports.intersection = intersection;
+ var splice = Array.prototype.splice;
+
/**
* Defines some convenience methods for working with Enumerables.
* `Ember.EnumerableUtils` uses `Ember.ArrayPolyfills` when necessary.
*
* @class EnumerableUtils
@@ -2998,40 +3353,110 @@
* @param {Function} callback The callback to execute
* @param {Object} thisArg Value to use as this when executing *callback*
*
* @return {Array} An array of mapped values.
*/
- var splice = Array.prototype.splice;
function map(obj, callback, thisArg) {
return obj.map ? obj.map(callback, thisArg) : ember_metal__array.map.call(obj, callback, thisArg);
}
+ /**
+ * Calls the forEach function on the passed object with a specified callback. This
+ * uses `Ember.ArrayPolyfill`'s-forEach method when necessary.
+ *
+ * @method forEach
+ * @param {Object} obj The object to call forEach on
+ * @param {Function} callback The callback to execute
+ * @param {Object} thisArg Value to use as this when executing *callback*
+ *
+ */
function forEach(obj, callback, thisArg) {
return obj.forEach ? obj.forEach(callback, thisArg) : ember_metal__array.forEach.call(obj, callback, thisArg);
}
+ /**
+ * Calls the filter function on the passed object with a specified callback. This
+ * uses `Ember.ArrayPolyfill`'s-filter method when necessary.
+ *
+ * @method filter
+ * @param {Object} obj The object to call filter on
+ * @param {Function} callback The callback to execute
+ * @param {Object} thisArg Value to use as this when executing *callback*
+ *
+ * @return {Array} An array containing the filtered values
+ * @since 1.4.0
+ */
function filter(obj, callback, thisArg) {
return obj.filter ? obj.filter(callback, thisArg) : ember_metal__array.filter.call(obj, callback, thisArg);
}
+ /**
+ * Calls the indexOf function on the passed object with a specified callback. This
+ * uses `Ember.ArrayPolyfill`'s-indexOf method when necessary.
+ *
+ * @method indexOf
+ * @param {Object} obj The object to call indexOn on
+ * @param {Function} callback The callback to execute
+ * @param {Object} index The index to start searching from
+ *
+ */
function indexOf(obj, element, index) {
return obj.indexOf ? obj.indexOf(element, index) : ember_metal__array.indexOf.call(obj, element, index);
}
+ /**
+ * Returns an array of indexes of the first occurrences of the passed elements
+ * on the passed object.
+ *
+ * ```javascript
+ * var array = [1, 2, 3, 4, 5];
+ * Ember.EnumerableUtils.indexesOf(array, [2, 5]); // [1, 4]
+ *
+ * var fubar = "Fubarr";
+ * Ember.EnumerableUtils.indexesOf(fubar, ['b', 'r']); // [2, 4]
+ * ```
+ *
+ * @method indexesOf
+ * @param {Object} obj The object to check for element indexes
+ * @param {Array} elements The elements to search for on *obj*
+ *
+ * @return {Array} An array of indexes.
+ *
+ */
function indexesOf(obj, elements) {
return elements === undefined ? [] : map(elements, function (item) {
return indexOf(obj, item);
});
}
+ /**
+ * Adds an object to an array. If the array already includes the object this
+ * method has no effect.
+ *
+ * @method addObject
+ * @param {Array} array The array the passed item should be added to
+ * @param {Object} item The item to add to the passed array
+ *
+ * @return 'undefined'
+ */
function addObject(array, item) {
var index = indexOf(array, item);
if (index === -1) {
array.push(item);
}
}
+ /**
+ * Removes an object from an array. If the array does not contain the passed
+ * object this method has no effect.
+ *
+ * @method removeObject
+ * @param {Array} array The array to remove the item from.
+ * @param {Object} item The item to remove from the passed array.
+ *
+ * @return 'undefined'
+ */
function removeObject(array, item) {
var index = indexOf(array, item);
if (index !== -1) {
array.splice(index, 1);
}
@@ -3061,18 +3486,66 @@
ret = ret.concat(splice.apply(array, chunk));
}
return ret;
}
+ /**
+ * Replaces objects in an array with the passed objects.
+ *
+ * ```javascript
+ * var array = [1,2,3];
+ * Ember.EnumerableUtils.replace(array, 1, 2, [4, 5]); // [1, 4, 5]
+ *
+ * var array = [1,2,3];
+ * Ember.EnumerableUtils.replace(array, 1, 1, [4, 5]); // [1, 4, 5, 3]
+ *
+ * var array = [1,2,3];
+ * Ember.EnumerableUtils.replace(array, 10, 1, [4, 5]); // [1, 2, 3, 4, 5]
+ * ```
+ *
+ * @method replace
+ * @param {Array} array The array the objects should be inserted into.
+ * @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 the array,
+ * starting at *idx*
+ * @param {Array} objects An array of zero or more objects that should be
+ * inserted into the array at *idx*
+ *
+ * @return {Array} The modified array.
+ */
function replace(array, idx, amt, objects) {
if (array.replace) {
return array.replace(idx, amt, objects);
} else {
return _replace(array, idx, amt, objects);
}
}
+ /**
+ * Calculates the intersection of two arrays. This method returns a new array
+ * filled with the records that the two passed arrays share with each other.
+ * If there is no intersection, an empty array will be returned.
+ *
+ * ```javascript
+ * var array1 = [1, 2, 3, 4, 5];
+ * var array2 = [1, 3, 5, 6, 7];
+ *
+ * Ember.EnumerableUtils.intersection(array1, array2); // [1, 3, 5]
+ *
+ * var array1 = [1, 2, 3];
+ * var array2 = [4, 5, 6];
+ *
+ * Ember.EnumerableUtils.intersection(array1, array2); // []
+ * ```
+ *
+ * @method intersection
+ * @param {Array} array1 The first array
+ * @param {Array} array2 The second array
+ *
+ * @return {Array} The intersection of the two passed arrays.
+ */
function intersection(array1, array2) {
var result = [];
forEach(array1, function (element) {
if (indexOf(array2, element) >= 0) {
result.push(element);
@@ -3273,10 +3746,21 @@
}
return newActions;
}
+ /**
+ Add an event listener
+
+ @method addListener
+ @for Ember
+ @param obj
+ @param {String} eventName
+ @param {Object|Function} target A target object or a function
+ @param {Function|String} method A function or the name of a function to be called on `target`
+ @param {Boolean} once A flag whether a function should only be called once
+ */
function addListener(obj, eventName, target, method, once) {
Ember['default'].assert("You must pass at least an object and event name to Ember.addListener", !!obj && !!eventName);
if (!method && "function" === typeof target) {
method = target;
@@ -3350,10 +3834,29 @@
for (var i = actions.length - 3; i >= 0; i -= 3) {
_removeListener(actions[i], actions[i + 1]);
}
}
}
+
+ /**
+ Suspend listener during callback.
+
+ This should only be used by the target of the event listener
+ when it is taking an action that would cause the event, e.g.
+ an object might suspend its property change listener while it is
+ setting that property.
+
+ @method suspendListener
+ @for Ember
+
+ @private
+ @param obj
+ @param {String} eventName
+ @param {Object|Function} target A target object or a function
+ @param {Function|String} method A function or the name of a function to be called on `target`
+ @param {Function} callback
+ */
function suspendListener(obj, eventName, target, method, callback) {
if (!method && "function" === typeof target) {
method = target;
target = null;
}
@@ -3375,10 +3878,23 @@
}
return utils.tryFinally(tryable, finalizer);
}
+ /**
+ Suspends multiple listeners during a callback.
+
+ @method suspendListeners
+ @for Ember
+
+ @private
+ @param obj
+ @param {Array} eventNames Array of event names
+ @param {Object|Function} target A target object or a function
+ @param {Function|String} method A function or the name of a function to be called on `target`
+ @param {Function} callback
+ */
function suspendListeners(obj, eventNames, target, method, callback) {
if (!method && "function" === typeof target) {
method = target;
target = null;
}
@@ -3411,10 +3927,18 @@
}
return utils.tryFinally(tryable, finalizer);
}
+ /**
+ Return a list of currently watched events
+
+ @private
+ @method watchedEvents
+ @for Ember
+ @param obj
+ */
function watchedEvents(obj) {
var listeners = obj["__ember_meta__"].listeners;
var ret = [];
if (listeners) {
@@ -3425,10 +3949,24 @@
}
}
return ret;
}
+ /**
+ Send an event. The execution of suspended listeners
+ is skipped, and once listeners are removed. A listener without
+ a target is executed on the passed object. If an array of actions
+ is not passed, the actions stored on the passed object are invoked.
+
+ @method sendEvent
+ @for Ember
+ @param obj
+ @param {String} eventName
+ @param {Array} params Optional parameters for each listener.
+ @param {Array} actions Optional array of actions (listeners).
+ @return true
+ */
function sendEvent(obj, eventName, params, actions) {
// first give object a chance to handle it
if (obj !== Ember['default'] && "function" === typeof obj.sendEvent) {
obj.sendEvent(eventName, params);
}
@@ -3475,17 +4013,31 @@
}
}
return true;
}
+ /**
+ @private
+ @method hasListeners
+ @for Ember
+ @param obj
+ @param {String} eventName
+ */
function hasListeners(obj, eventName) {
var meta = obj["__ember_meta__"];
var actions = meta && meta.listeners && meta.listeners[eventName];
return !!(actions && actions.length);
}
+ /**
+ @private
+ @method listenersFor
+ @for Ember
+ @param obj
+ @param {String} eventName
+ */
function listenersFor(obj, eventName) {
var ret = [];
var meta = obj["__ember_meta__"];
var actions = meta && meta.listeners && meta.listeners[eventName];
@@ -3500,10 +4052,33 @@
}
return ret;
}
+ /**
+ Define a property as a function that should be executed when
+ a specified event or events are triggered.
+
+
+ ``` javascript
+ var Job = Ember.Object.extend({
+ logCompleted: Ember.on('completed', function() {
+ console.log('Job completed!');
+ })
+ });
+
+ var job = Job.create();
+
+ Ember.sendEvent(job, 'completed'); // Logs 'Job completed!'
+ ```
+
+ @method on
+ @for Ember
+ @param {String} eventNames*
+ @param {Function} func
+ @return func
+ */
function on() {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
@@ -3517,11 +4092,14 @@
enifed('ember-metal/expand_properties', ['exports', 'ember-metal/error', 'ember-metal/array'], function (exports, EmberError, array) {
'use strict';
+ exports['default'] = expandProperties;
+ var SPLIT_REGEX = /\{|\}/;
+
/**
Expands `pattern`, invoking `callback` for each expansion.
The only pattern supported is brace-expansion, anything else will be passed
once to `callback` directly.
@@ -3544,13 +4122,10 @@
@private
@param {String} pattern The property pattern to expand.
@param {Function} callback The callback to invoke. It is invoked once per
expansion, and is passed the expansion.
*/
- exports['default'] = expandProperties;
-
- var SPLIT_REGEX = /\{|\}/;
function expandProperties(pattern, callback) {
if (pattern.indexOf(' ') > -1) {
throw new EmberError['default']('Brace expanded properties cannot contain spaces, e.g. \'user.{firstName, lastName}\' should be \'user.{firstName,lastName}\'');
}
@@ -3590,33 +4165,10 @@
enifed('ember-metal/get_properties', ['exports', 'ember-metal/property_get', 'ember-metal/utils'], function (exports, property_get, utils) {
'use strict';
-
- /**
- To get multiple properties at once, call `Ember.getProperties`
- with an object followed by a list of strings or an array:
-
- ```javascript
- Ember.getProperties(record, 'firstName', 'lastName', 'zipCode');
- // { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
- ```
-
- is equivalent to:
-
- ```javascript
- Ember.getProperties(record, ['firstName', 'lastName', 'zipCode']);
- // { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
- ```
-
- @method getProperties
- @for Ember
- @param {Object} obj
- @param {String...|Array} list of keys to get
- @return {Object}
- */
exports['default'] = getProperties;
function getProperties(obj) {
var ret = {};
var propertyNames = arguments;
var i = 1;
@@ -3677,21 +4229,10 @@
exports._instrumentStart = _instrumentStart;
exports.subscribe = subscribe;
exports.unsubscribe = unsubscribe;
exports.reset = reset;
- /**
- Notifies event's subscribers, calls `before` and `after` hooks.
-
- @method instrument
- @namespace Ember.Instrumentation
-
- @param {String} [name] Namespaced event name.
- @param {Object} payload
- @param {Function} callback Function that you're instrumenting.
- @param {Object} binding Context that instrument function is called with.
- */
var subscribers = [];
var cache = {};
var populateListeners = function (name) {
var listeners = [];
@@ -3714,10 +4255,22 @@
// fn.bind will be available in all the browsers that support the advanced window.performance... ;-)
return fn ? fn.bind(perf) : function () {
return +new Date();
};
})();
+
+ /**
+ Notifies event's subscribers, calls `before` and `after` hooks.
+
+ @method instrument
+ @namespace Ember.Instrumentation
+
+ @param {String} [name] Namespaced event name.
+ @param {Object} payload
+ @param {Function} callback Function that you're instrumenting.
+ @param {Object} binding Context that instrument function is called with.
+ */
function instrument(name, _payload, callback, binding) {
if (arguments.length <= 3 && typeof _payload === "function") {
binding = callback;
callback = _payload;
_payload = undefined;
@@ -3740,10 +4293,12 @@
} else {
return callback.call(binding);
}
}
+ // private for now
+
function _instrumentStart(name, _payload) {
var listeners = cache[name];
if (!listeners) {
listeners = populateListeners(name);
@@ -3783,10 +4338,21 @@
console.timeEnd(timeName);
}
};
}
+ /**
+ Subscribes to a particular event or instrumented block of code.
+
+ @method subscribe
+ @namespace Ember.Instrumentation
+
+ @param {String} [pattern] Namespaced event name.
+ @param {Object} [object] Before and After hooks.
+
+ @return {Subscriber}
+ */
function subscribe(pattern, object) {
var paths = pattern.split(".");
var path;
var regex = [];
@@ -3812,10 +4378,18 @@
cache = {};
return subscriber;
}
+ /**
+ Unsubscribes from a particular event or instrumented block of code.
+
+ @method unsubscribe
+ @namespace Ember.Instrumentation
+
+ @param {Object} [subscriber]
+ */
function unsubscribe(subscriber) {
var index;
for (var i = 0, l = subscribers.length; i < l; i++) {
if (subscribers[i] === subscriber) {
@@ -3825,10 +4399,16 @@
subscribers.splice(index, 1);
cache = {};
}
+ /**
+ Resets `Ember.Instrumentation` by flushing list of subscribers.
+
+ @method reset
+ @namespace Ember.Instrumentation
+ */
function reset() {
subscribers.length = 0;
cache = {};
}
@@ -3838,34 +4418,10 @@
enifed('ember-metal/is_blank', ['exports', 'ember-metal/is_empty'], function (exports, isEmpty) {
'use strict';
-
- /**
- A value is blank if it is empty or a whitespace string.
-
- ```javascript
- Ember.isBlank(); // true
- Ember.isBlank(null); // true
- Ember.isBlank(undefined); // true
- Ember.isBlank(''); // true
- Ember.isBlank([]); // true
- Ember.isBlank('\n\t'); // true
- Ember.isBlank(' '); // true
- Ember.isBlank({}); // false
- Ember.isBlank('\n\t Hello'); // false
- Ember.isBlank('Hello world'); // false
- Ember.isBlank([1,2,3]); // false
- ```
-
- @method isBlank
- @for Ember
- @param {Object} obj Value to test
- @return {Boolean}
- @since 1.5.0
- */
exports['default'] = isBlank;
function isBlank(obj) {
return isEmpty['default'](obj) || typeof obj === 'string' && obj.match(/\S/) === null;
}
@@ -3943,34 +4499,10 @@
enifed('ember-metal/is_present', ['exports', 'ember-metal/is_blank'], function (exports, isBlank) {
'use strict';
-
- /**
- A value is present if it not `isBlank`.
-
- ```javascript
- Ember.isPresent(); // false
- Ember.isPresent(null); // false
- Ember.isPresent(undefined); // false
- Ember.isPresent(''); // false
- Ember.isPresent([]); // false
- Ember.isPresent('\n\t'); // false
- Ember.isPresent(' '); // false
- Ember.isPresent({}); // true
- Ember.isPresent('\n\t Hello'); // true
- Ember.isPresent('Hello world'); // true
- Ember.isPresent([1,2,3]); // true
- ```
-
- @method isPresent
- @for Ember
- @param {Object} obj Value to test
- @return {Boolean}
- @since 1.8.0
- */
exports['default'] = isPresent;
function isPresent(obj) {
return !isBlank['default'](obj);
}
@@ -4736,26 +5268,10 @@
'use strict';
exports.assign = assign;
- /**
- Merge the contents of two objects together into the first object.
-
- ```javascript
- Ember.merge({first: 'Tom'}, {last: 'Dale'}); // {first: 'Tom', last: 'Dale'}
- var a = {first: 'Yehuda'};
- var b = {last: 'Katz'};
- Ember.merge(a, b); // a == {first: 'Yehuda', last: 'Katz'}, b == {last: 'Katz'}
- ```
-
- @method merge
- @for Ember
- @param {Object} original The object to merge into
- @param {Object} updates The object to copy properties from
- @return {Object}
- */
exports['default'] = merge;
function merge(original, updates) {
if (!updates || typeof updates !== 'object') {
return original;
@@ -4804,17 +5320,10 @@
exports.observer = observer;
exports.immediateObserver = immediateObserver;
exports.beforeObserver = beforeObserver;
exports.Mixin = Mixin;
- /**
- @method mixin
- @for Ember
- @param obj
- @param mixins*
- @return obj
- */
"REMOVE_USE_STRICT: true";var REQUIRED;
var a_slice = [].slice;
function superFunction() {
var func = this.__nextSuper;
@@ -5262,10 +5771,18 @@
finishPartial(obj, m);
}
return obj;
}
+
+ /**
+ @method mixin
+ @for Ember
+ @param obj
+ @param mixins*
+ @return obj
+ */
function mixin(obj) {
for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
@@ -5537,10 +6054,17 @@
REQUIRED = new ember_metal__properties.Descriptor();
REQUIRED.toString = function () {
return "(Required Property)";
};
+
+ /**
+ Denotes a required property for a mixin
+
+ @method required
+ @for Ember
+ */
function required() {
Ember['default'].deprecate("Ember.required is deprecated as its behavior is inconsistent and unreliable.", false);
return REQUIRED;
}
@@ -5548,14 +6072,63 @@
this.isDescriptor = true;
this.methodName = methodName;
}
Alias.prototype = new ember_metal__properties.Descriptor();
+
+ /**
+ Makes a method available via an additional name.
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ name: function() {
+ return 'Tomhuda Katzdale';
+ },
+ moniker: Ember.aliasMethod('name')
+ });
+
+ var goodGuy = App.Person.create();
+
+ goodGuy.name(); // 'Tomhuda Katzdale'
+ goodGuy.moniker(); // 'Tomhuda Katzdale'
+ ```
+
+ @method aliasMethod
+ @for Ember
+ @param {String} methodName name of the method to alias
+ */
function aliasMethod(methodName) {
return new Alias(methodName);
}
+ // ..........................................................
+ // OBSERVER HELPER
+ //
+
+ /**
+ Specify a method that observes property changes.
+
+ ```javascript
+ Ember.Object.extend({
+ valueObserver: Ember.observer('value', function() {
+ // Executes whenever the "value" property changes
+ })
+ });
+ ```
+
+ In the future this method may become asynchronous. If you want to ensure
+ synchronous behavior, use `immediateObserver`.
+
+ Also available as `Function.prototype.observes` if prototype extensions are
+ enabled.
+
+ @method observer
+ @for Ember
+ @param {String} propertyNames*
+ @param {Function} func
+ @return func
+ */
function observer() {
for (var _len4 = arguments.length, args = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
args[_key4] = arguments[_key4];
}
@@ -5586,19 +6159,84 @@
func.__ember_observes__ = paths;
return func;
}
+ /**
+ Specify a method that observes property changes.
+
+ ```javascript
+ Ember.Object.extend({
+ valueObserver: Ember.immediateObserver('value', function() {
+ // Executes whenever the "value" property changes
+ })
+ });
+ ```
+
+ In the future, `Ember.observer` may become asynchronous. In this event,
+ `Ember.immediateObserver` will maintain the synchronous behavior.
+
+ Also available as `Function.prototype.observesImmediately` if prototype extensions are
+ enabled.
+
+ @method immediateObserver
+ @for Ember
+ @param {String} propertyNames*
+ @param {Function} func
+ @return func
+ */
function immediateObserver() {
for (var i = 0, l = arguments.length; i < l; i++) {
var arg = arguments[i];
Ember['default'].assert("Immediate observers must observe internal properties only, not properties on other objects.", typeof arg !== "string" || arg.indexOf(".") === -1);
}
return observer.apply(this, arguments);
}
+ /**
+ When observers fire, they are called with the arguments `obj`, `keyName`.
+
+ Note, `@each.property` observer is called per each add or replace of an element
+ and it's not called with a specific enumeration item.
+
+ A `beforeObserver` fires before a property changes.
+
+ A `beforeObserver` is an alternative form of `.observesBefore()`.
+
+ ```javascript
+ App.PersonView = Ember.View.extend({
+ friends: [{ name: 'Tom' }, { name: 'Stefan' }, { name: 'Kris' }],
+
+ valueWillChange: Ember.beforeObserver('content.value', function(obj, keyName) {
+ this.changingFrom = obj.get(keyName);
+ }),
+
+ valueDidChange: Ember.observer('content.value', function(obj, keyName) {
+ // only run if updating a value already in the DOM
+ if (this.get('state') === 'inDOM') {
+ var color = obj.get(keyName) > this.changingFrom ? 'green' : 'red';
+ // logic
+ }
+ }),
+
+ friendsDidChange: Ember.observer('friends.@each.name', function(obj, keyName) {
+ // some logic
+ // obj.get(keyName) returns friends array
+ })
+ });
+ ```
+
+ Also available as `Function.prototype.observesBefore` if prototype extensions are
+ enabled.
+
+ @method beforeObserver
+ @for Ember
+ @param {String} propertyNames*
+ @param {Function} func
+ @return func
+ */
function beforeObserver() {
for (var _len5 = arguments.length, args = Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
args[_key5] = arguments[_key5];
}
@@ -5649,28 +6287,29 @@
exports._suspendBeforeObservers = _suspendBeforeObservers;
exports._suspendObservers = _suspendObservers;
exports.beforeObserversFor = beforeObserversFor;
exports.removeBeforeObserver = removeBeforeObserver;
- /**
- @method addObserver
- @for Ember
- @param obj
- @param {String} path
- @param {Object|Function} targetOrMethod
- @param {Function|String} [method]
- */
var AFTER_OBSERVERS = ":change";
var BEFORE_OBSERVERS = ":before";
function changeEvent(keyName) {
return keyName + AFTER_OBSERVERS;
}
function beforeEvent(keyName) {
return keyName + BEFORE_OBSERVERS;
}
+
+ /**
+ @method addObserver
+ @for Ember
+ @param obj
+ @param {String} path
+ @param {Object|Function} targetOrMethod
+ @param {Function|String} [method]
+ */
function addObserver(obj, _path, target, method) {
ember_metal__events.addListener(obj, changeEvent(_path), target, method);
watching.watch(obj, _path);
return this;
@@ -5678,24 +6317,45 @@
function observersFor(obj, path) {
return ember_metal__events.listenersFor(obj, changeEvent(path));
}
+ /**
+ @method removeObserver
+ @for Ember
+ @param obj
+ @param {String} path
+ @param {Object|Function} target
+ @param {Function|String} [method]
+ */
function removeObserver(obj, path, target, method) {
watching.unwatch(obj, path);
ember_metal__events.removeListener(obj, changeEvent(path), target, method);
return this;
}
+ /**
+ @method addBeforeObserver
+ @for Ember
+ @param obj
+ @param {String} path
+ @param {Object|Function} target
+ @param {Function|String} [method]
+ */
function addBeforeObserver(obj, path, target, method) {
ember_metal__events.addListener(obj, beforeEvent(path), target, method);
watching.watch(obj, path);
return this;
}
+ // Suspend observer during callback.
+ //
+ // This should only be used by the target of the observer
+ // while it is setting the observed path.
+
function _suspendBeforeObserver(obj, path, target, method, callback) {
return ember_metal__events.suspendListener(obj, beforeEvent(path), target, method, callback);
}
function _suspendObserver(obj, path, target, method, callback) {
@@ -5714,10 +6374,18 @@
function beforeObserversFor(obj, path) {
return ember_metal__events.listenersFor(obj, beforeEvent(path));
}
+ /**
+ @method removeBeforeObserver
+ @for Ember
+ @param obj
+ @param {String} path
+ @param {Object|Function} target
+ @param {Function|String} [method]
+ */
function removeBeforeObserver(obj, path, target, method) {
watching.unwatch(obj, path);
ember_metal__events.removeListener(obj, beforeEvent(path), target, method);
return this;
@@ -6132,22 +6800,18 @@
exports.Descriptor = Descriptor;
exports.MANDATORY_SETTER_FUNCTION = MANDATORY_SETTER_FUNCTION;
exports.DEFAULT_GETTER_FUNCTION = DEFAULT_GETTER_FUNCTION;
exports.defineProperty = defineProperty;
- // ..........................................................
- // DESCRIPTOR
- //
-
- /**
- Objects of this type can implement an interface to respond to requests to
- get and set. The default implementation handles simple properties.
- */
function Descriptor() {
this.isDescriptor = true;
}
+ // ..........................................................
+ // DEFINING PROPERTIES API
+ //
+
function MANDATORY_SETTER_FUNCTION(name) {
return function SETTER_FUNCTION(value) {
Ember['default'].assert("You must use Ember.set() to set the `" + name + "` property (of " + this + ") to `" + value + "`.", false);
};
}
@@ -6157,10 +6821,55 @@
var meta = this["__ember_meta__"];
return meta && meta.values[name];
};
}
+ /**
+ NOTE: This is a low-level method used by other parts of the API. You almost
+ never want to call this method directly. Instead you should use
+ `Ember.mixin()` to define new properties.
+
+ Defines a property on an object. This method works much like the ES5
+ `Object.defineProperty()` method except that it can also accept computed
+ properties and other special descriptors.
+
+ Normally this method takes only three parameters. However if you pass an
+ instance of `Descriptor` as the third param then you can pass an
+ optional value as the fourth parameter. This is often more efficient than
+ creating new descriptor hashes for each property.
+
+ ## Examples
+
+ ```javascript
+ // ES5 compatible mode
+ Ember.defineProperty(contact, 'firstName', {
+ writable: true,
+ configurable: false,
+ enumerable: true,
+ value: 'Charles'
+ });
+
+ // define a simple property
+ Ember.defineProperty(contact, 'lastName', undefined, 'Jolley');
+
+ // define a computed property
+ Ember.defineProperty(contact, 'fullName', Ember.computed(function() {
+ return this.firstName+' '+this.lastName;
+ }).property('firstName', 'lastName'));
+ ```
+
+ @private
+ @method defineProperty
+ @for Ember
+ @param {Object} obj the object to define this property on. This may be a prototype.
+ @param {String} keyName the name of the property
+ @param {Descriptor} [desc] an instance of `Descriptor` (typically a
+ computed property) or an ES5 descriptor.
+ You must provide this or `data` but not both.
+ @param {*} [data] something other than a descriptor, that will
+ become the explicit value of this property.
+ */
function defineProperty(obj, keyName, desc, data, meta) {
var possibleDesc, existingDesc, watching, value;
if (!meta) {
meta = utils.meta(obj);
@@ -6544,54 +7253,25 @@
}
exports.PROPERTY_DID_CHANGE = PROPERTY_DID_CHANGE;
});
-enifed('ember-metal/property_get', ['exports', 'ember-metal/core', 'ember-metal/error', 'ember-metal/path_cache', 'ember-metal/platform/define_property', 'ember-metal/utils'], function (exports, Ember, EmberError, path_cache, define_property, utils) {
+enifed('ember-metal/property_get', ['exports', 'ember-metal/core', 'ember-metal/error', 'ember-metal/path_cache', 'ember-metal/platform/define_property', 'ember-metal/utils', 'ember-metal/is_none'], function (exports, Ember, EmberError, path_cache, define_property, utils, isNone) {
'use strict';
exports.get = get;
exports.normalizeTuple = normalizeTuple;
exports._getPath = _getPath;
exports.getWithDefault = getWithDefault;
- // ..........................................................
- // GET AND SET
- //
- // 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 '_'
- 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
- method.
-
- Note that if the object itself is `undefined`, this method will throw
- an error.
-
- @method get
- @for Ember
- @param {Object} obj The object to retrieve from.
- @param {String} keyName The property key to retrieve
- @return {Object} the property value or `null`.
- */
var FIRST_KEY = /^([^\.]+)/;
var INTERCEPT_GET = utils.symbol("INTERCEPT_GET");
- var UNHANDLED_GET = utils.symbol("UNHANDLED_GET");function get(obj, keyName) {
+ var UNHANDLED_GET = utils.symbol("UNHANDLED_GET");
+
+ function get(obj, keyName) {
// Helpers that operate with 'this' within an #each
if (keyName === "") {
return obj;
}
@@ -6601,11 +7281,11 @@
}
Ember['default'].assert("Cannot call get with " + keyName + " key.", !!keyName);
Ember['default'].assert("Cannot call get with '" + keyName + "' on an undefined object.", obj !== undefined);
- if (!obj) {
+ if (isNone['default'](obj)) {
return _getPath(obj, keyName);
}
if (obj && typeof obj[INTERCEPT_GET] === "function") {
var result = obj[INTERCEPT_GET](obj, keyName);
@@ -6639,10 +7319,23 @@
return ret;
}
}
+ /**
+ Normalizes a target/path pair to reflect that actual target/path that should
+ be observed, etc. This takes into account passing in global property
+ paths (i.e. a path beginning with a capital letter not defined on the
+ target).
+
+ @private
+ @method normalizeTuple
+ @for Ember
+ @param {Object} target The current target. May be `null`.
+ @param {String} path A path on the target or a global property path.
+ @return {Array} a temporary array with the normalized target/path pair.
+ */
function normalizeTuple(target, path) {
var hasThis = path_cache.hasThis(path);
var isGlobal = !hasThis && path_cache.isGlobal(path);
var key;
@@ -6719,25 +7412,14 @@
'use strict';
exports.set = set;
exports.trySet = trySet;
- /**
- Sets the value of a property on an object, respecting computed properties
- and notifying observers and other listeners of the change. If the
- property is not defined but the object implements the `setUnknownProperty`
- method then that will be invoked as well.
-
- @method set
- @for Ember
- @param {Object} obj The object to modify.
- @param {String} keyName The property key to set
- @param {Object} value The value to set
- @return {Object} the passed value.
- */
var INTERCEPT_SET = utils.symbol("INTERCEPT_SET");
- var UNHANDLED_SET = utils.symbol("UNHANDLED_SET");function set(obj, keyName, value, tolerant) {
+ var UNHANDLED_SET = utils.symbol("UNHANDLED_SET");
+
+ function set(obj, keyName, value, tolerant) {
if (typeof obj === "string") {
Ember['default'].assert("Path '" + obj + "' must be global if no obj is given.", path_cache.isGlobalPath(obj));
value = keyName;
keyName = obj;
obj = Ember['default'].lookup;
@@ -6850,10 +7532,24 @@
}
}
return set(root, keyName, value);
}
+
+ /**
+ Error-tolerant form of `Ember.set`. Will not blow up if any part of the
+ chain is `undefined`, `null`, or destroyed.
+
+ This is primarily used when syncing bindings, which may try to update after
+ an object has been destroyed.
+
+ @method trySet
+ @for Ember
+ @param {Object} obj The object to modify.
+ @param {String} path The property path to set
+ @param {Object} value The value to set
+ */
function trySet(root, path, value) {
return set(root, path, value, true);
}
exports.INTERCEPT_SET = INTERCEPT_SET;
@@ -7519,31 +8215,10 @@
enifed('ember-metal/set_properties', ['exports', 'ember-metal/property_events', 'ember-metal/property_set', 'ember-metal/keys'], function (exports, property_events, property_set, keys) {
'use strict';
-
- /**
- Set a list of properties on an object. These properties are set inside
- a single `beginPropertyChanges` and `endPropertyChanges` batch, so
- observers will be buffered.
-
- ```javascript
- var anObject = Ember.Object.create();
-
- anObject.setProperties({
- firstName: 'Stanley',
- lastName: 'Stuart',
- age: 21
- });
- ```
-
- @method setProperties
- @param obj
- @param {Object} properties
- @return obj
- */
exports['default'] = setProperties;
function setProperties(obj, properties) {
if (!properties || typeof properties !== "object") {
return obj;
}
@@ -8159,60 +8834,119 @@
exports.zip = zip;
exports.zipHash = zipHash;
exports.chain = chain;
exports.setValue = setValue;
- /*
- Check whether an object is a stream or not
-
- @public
- @for Ember.stream
- @function isStream
- @param {Object|Stream} object object to check whether it is a stream
- @return {Boolean} `true` if the object is a stream, `false` otherwise
- */
function isStream(object) {
return object && object.isStream;
}
+ /*
+ A method of subscribing to a stream which is safe for use with a non-stream
+ object. If a non-stream object is passed, the function does nothing.
+
+ @public
+ @for Ember.stream
+ @function subscribe
+ @param {Object|Stream} object object or stream to potentially subscribe to
+ @param {Function} callback function to run when stream value changes
+ @param {Object} [context] the callback will be executed with this context if it
+ is provided
+ */
function subscribe(object, callback, context) {
if (object && object.isStream) {
return object.subscribe(callback, context);
}
}
+ /*
+ A method of unsubscribing from a stream which is safe for use with a non-stream
+ object. If a non-stream object is passed, the function does nothing.
+
+ @public
+ @for Ember.stream
+ @function unsubscribe
+ @param {Object|Stream} object object or stream to potentially unsubscribe from
+ @param {Function} callback function originally passed to `subscribe()`
+ @param {Object} [context] object originally passed to `subscribe()`
+ */
function unsubscribe(object, callback, context) {
if (object && object.isStream) {
object.unsubscribe(callback, context);
}
}
+ /*
+ Retrieve the value of a stream, or in the case a non-stream object is passed,
+ return the object itself.
+
+ @public
+ @for Ember.stream
+ @function read
+ @param {Object|Stream} object object to return the value of
+ @return the stream's current value, or the non-stream object itself
+ */
function read(object) {
if (object && object.isStream) {
return object.value();
} else {
return object;
}
}
+ /*
+ Map an array, replacing any streams with their values.
+
+ @public
+ @for Ember.stream
+ @function readArray
+ @param {Array} array The array to read values from
+ @return {Array} a new array of the same length with the values of non-stream
+ objects mapped from their original positions untouched, and
+ the values of stream objects retaining their original position
+ and replaced with the stream's current value.
+ */
function readArray(array) {
var length = array.length;
var ret = new Array(length);
for (var i = 0; i < length; i++) {
ret[i] = read(array[i]);
}
return ret;
}
+ /*
+ Map a hash, replacing any stream property values with the current value of that
+ stream.
+
+ @public
+ @for Ember.stream
+ @function readHash
+ @param {Object} object The hash to read keys and values from
+ @return {Object} a new object with the same keys as the passed object. The
+ property values in the new object are the original values in
+ the case of non-stream objects, and the streams' current
+ values in the case of stream objects.
+ */
function readHash(object) {
var ret = {};
for (var key in object) {
ret[key] = read(object[key]);
}
return ret;
}
+ /*
+ Check whether an array contains any stream values
+
+ @public
+ @for Ember.stream
+ @function scanArray
+ @param {Array} array array given to a handlebars helper
+ @return {Boolean} `true` if the array contains a stream/bound value, `false`
+ otherwise
+ */
function scanArray(array) {
var length = array.length;
var containsStream = false;
for (var i = 0; i < length; i++) {
@@ -8223,10 +8957,20 @@
}
return containsStream;
}
+ /*
+ Check whether a hash has any stream property values
+
+ @public
+ @for Ember.stream
+ @function scanHash
+ @param {Object} hash "hash" argument given to a handlebars helper
+ @return {Boolean} `true` if the object contains a stream/bound value, `false`
+ otherwise
+ */
function scanHash(hash) {
var containsStream = false;
for (var prop in hash) {
if (isStream(hash[prop])) {
@@ -8236,10 +8980,23 @@
}
return containsStream;
}
+ /*
+ Join an array, with any streams replaced by their current values
+
+ @public
+ @for Ember.stream
+ @function concat
+ @param {Array} array An array containing zero or more stream objects and
+ zero or more non-stream objects
+ @param {String} separator string to be used to join array elements
+ @return {String} String with array elements concatenated and joined by the
+ provided separator, and any stream array members having been
+ replaced by the current value of the stream
+ */
function concat(array, separator) {
// TODO: Create subclass ConcatStream < Stream. Defer
// subscribing to streams until the value() is called.
var hasStream = scanArray(array);
if (hasStream) {
@@ -8358,10 +9115,42 @@
}
return stream;
}
+ /**
+ Generate a new stream by providing a source stream and a function that can
+ be used to transform the stream's value. In the case of a non-stream object,
+ returns the result of the function.
+
+ The value to transform would typically be available to the function you pass
+ to `chain()` via scope. For example:
+
+ ```javascript
+ var source = ...; // stream returning a number
+ // or a numeric (non-stream) object
+ var result = chain(source, function() {
+ var currentValue = read(source);
+ return currentValue + 1;
+ });
+ ```
+
+ In the example, result is a stream if source is a stream, or a number of
+ source was numeric.
+
+ @public
+ @for Ember.stream
+ @function chain
+ @param {Object|Stream} value A stream or non-stream object
+ @param {Function} fn function to be run when the stream value changes, or to
+ be run once in the case of a non-stream object
+ @return {Object|Stream} In the case of a stream `value` parameter, a new
+ stream that will be updated with the return value of
+ the provided function `fn`. In the case of a
+ non-stream object, the return value of the provided
+ function `fn`.
+ */
function chain(value, fn, label) {
Ember.assert('Must call chain with a label', !!label);
if (isStream(value)) {
var stream = new Stream['default'](fn, function () {
return '' + label + '(' + labelFor(value) + ')';
@@ -8402,19 +9191,10 @@
exports.apply = apply;
exports.applyStr = applyStr;
exports.meta = meta;
exports.canInvoke = canInvoke;
- /**
- Generates a universally unique identifier. This method
- is used internally by Ember for assisting with
- the generation of GUID's and other unique identifiers
- such as `bind-attr` data attributes.
-
- @public
- @return {Number} [description]
- */
"REMOVE_USE_STRICT: true"; /**
@module ember-metal
*/
/**
@@ -8423,10 +9203,20 @@
@private
@return {Number} the uuid
*/
var _uuid = 0;
+
+ /**
+ Generates a universally unique identifier. This method
+ is used internally by Ember for assisting with
+ the generation of GUID's and other unique identifiers
+ such as `bind-attr` data attributes.
+
+ @public
+ @return {Number} [description]
+ */
function uuid() {
return ++_uuid;
}
/**
@@ -8555,11 +9345,13 @@
};
var NEXT_SUPER_PROPERTY = {
name: "__nextSuper",
descriptor: undefinedDescriptor
- };function generateGuid(obj, prefix) {
+ };
+
+ function generateGuid(obj, prefix) {
if (!prefix) {
prefix = GUID_PREFIX;
}
var ret = prefix + uuid();
@@ -8576,10 +9368,24 @@
}
}
return ret;
}
+ /**
+ Returns a unique id for the object. If the object does not yet have a guid,
+ one will be assigned to it. You can call this on any object,
+ `Ember.Object`-based or not, but be aware that it will add a `_guid`
+ property.
+
+ You can also use this method on DOM Element objects.
+
+ @private
+ @method guidFor
+ @for Ember
+ @param {Object} obj any object, string, number, Element, or primitive
+ @return {String} the unique guid for this instance.
+ */
function guidFor(obj) {
// special cases where we don't want to add a key to object
if (obj === undefined) {
return "(undefined)";
@@ -8761,10 +9567,43 @@
var _meta = meta(obj, true);
_meta[property] = value;
return value;
}
+ /**
+ @deprecated
+ @private
+
+ In order to store defaults for a class, a prototype may need to create
+ a default meta object, which will be inherited by any objects instantiated
+ from the class's constructor.
+
+ However, the properties of that meta object are only shallow-cloned,
+ so if a property is a hash (like the event system's `listeners` hash),
+ it will by default be shared across all instances of that class.
+
+ This method allows extensions to deeply clone a series of nested hashes or
+ other complex objects. For instance, the event system might pass
+ `['listeners', 'foo:change', 'ember157']` to `prepareMetaPath`, which will
+ walk down the keys provided.
+
+ For each key, if the key does not exist, it is created. If it already
+ exists and it was inherited from its constructor, the constructor's
+ key is cloned.
+
+ You can also pass false for `writable`, which will simply return
+ undefined if `prepareMetaPath` discovers any part of the path that
+ shared or undefined.
+
+ @method metaPath
+ @for Ember
+ @param {Object} obj The object whose meta we are examining
+ @param {Array} path An array of keys to walk down
+ @param {Boolean} writable whether or not to create a new meta
+ (or meta property) if one does not already exist or if it's
+ shared with its constructor
+ */
function metaPath(obj, path, writable) {
Ember['default'].deprecate("Ember.metaPath is deprecated and will be removed from future releases.");
var _meta = meta(obj, writable);
var keyName, value;
@@ -8789,10 +9628,22 @@
}
return value;
}
+ /**
+ Wraps the passed function so that `this._super` will point to the superFunc
+ when the function is invoked. This is the primitive we use to implement
+ calls to super.
+
+ @private
+ @method wrap
+ @for Ember
+ @param {Function} func The function to call
+ @param {Function} superFunc The super function.
+ @return {Function} wrapped function.
+ */
function wrap(func, superFunc) {
function superWrapper() {
var ret;
var sup = this && this.__nextSuper;
var length = arguments.length;
@@ -8848,10 +9699,30 @@
@return {Boolean}
*/
function canInvoke(obj, methodName) {
return !!(obj && typeof obj[methodName] === "function");
}
+
+ /**
+ Checks to see if the `methodName` exists on the `obj`,
+ and if it does, invokes it with the arguments passed.
+
+ ```javascript
+ var d = new Date('03/15/2013');
+
+ Ember.tryInvoke(d, 'getTime'); // 1363320000000
+ Ember.tryInvoke(d, 'setFullYear', [2014]); // 1394856000000
+ Ember.tryInvoke(d, 'noSuchMethod', [2014]); // undefined
+ ```
+
+ @method tryInvoke
+ @for Ember
+ @param {Object} obj The object to check for the method
+ @param {String} methodName The method name to check for
+ @param {Array} [args] The arguments to pass to the method
+ @return {*} the return value of the invoked method or undefined if it cannot be invoked
+ */
function tryInvoke(obj, methodName, args) {
if (canInvoke(obj, methodName)) {
return args ? applyStr(obj, methodName, args) : applyStr(obj, methodName);
}
}
@@ -9038,17 +9909,53 @@
var toString = Object.prototype.toString;
var isArray = Array.isArray || function (value) {
return value !== null && value !== undefined && typeof value === "object" && typeof value.length === "number" && toString.call(value) === "[object Array]";
};
+
+ /**
+ Forces the passed object to be part of an array. If the object is already
+ an array, it will return the object. Otherwise, it will add the object to
+ an array. If obj is `null` or `undefined`, it will return an empty array.
+
+ ```javascript
+ Ember.makeArray(); // []
+ Ember.makeArray(null); // []
+ Ember.makeArray(undefined); // []
+ Ember.makeArray('lindsay'); // ['lindsay']
+ Ember.makeArray([1, 2, 42]); // [1, 2, 42]
+
+ var controller = Ember.ArrayProxy.create({ content: [] });
+
+ Ember.makeArray(controller) === controller; // true
+ ```
+
+ @method makeArray
+ @for Ember
+ @param {Object} obj the object
+ @return {Array}
+ */
function makeArray(obj) {
if (obj === null || obj === undefined) {
return [];
}
return isArray(obj) ? obj : [obj];
}
+ /**
+ Convenience method to inspect an object. This method will attempt to
+ convert the object into a useful string description.
+
+ It is a pretty simple implementation. If you want something more robust,
+ use something like JSDump: https://github.com/NV/jsDump
+
+ @method inspect
+ @for Ember
+ @param {Object} obj The object you want to inspect.
+ @return {String} A description of the object
+ @since 1.4.0
+ */
function inspect(obj) {
if (obj === null) {
return "null";
}
if (obj === undefined) {
@@ -9087,10 +9994,17 @@
}
}
return "{" + ret.join(", ") + "}";
}
+ // The following functions are intentionally minified to keep the functions
+ // below Chrome's function body size inlining limit of 600 chars.
+ /**
+ @param {Object} target
+ @param {Function} method
+ @param {Array} args
+ */
function apply(t, m, a) {
var l = a && a.length;
if (!a || !l) {
return m.call(t);
}
@@ -9108,10 +10022,15 @@
default:
return m.apply(t, a);
}
}
+ /**
+ @param {Object} target
+ @param {String} method
+ @param {Array} args
+ */
function applyStr(t, m, a) {
var l = a && a.length;
if (!a || !l) {
return t[m]();
}
@@ -9211,11 +10130,12 @@
};
// This is super annoying, but required until
// https://github.com/babel/babel/issues/906 is resolved
- ;
+ ; // jshint ignore:line
+
function unwatchKey(obj, keyName, meta) {
var m = meta || utils.meta(obj);
var watching = m.watching;
if (watching[keyName] === 1) {
@@ -9344,10 +10264,20 @@
watch_path.unwatchPath(obj, _keyPath, m);
}
}
var NODE_STACK = [];
+
+ /**
+ Tears down the meta on an object so that it can be garbage collected.
+ Multiple calls will have no effect.
+
+ @method destroy
+ @for Ember
+ @param {Object} obj the object to destroy
+ @return {void}
+ */
function destroy(obj) {
var meta = obj["__ember_meta__"];
var node, nodes, key, nodeObject;
if (meta) {
@@ -9379,11 +10309,11 @@
}
}
}
});
-enifed('ember-template-compiler', ['exports', 'ember-metal/core', 'ember-template-compiler/system/precompile', 'ember-template-compiler/system/compile', 'ember-template-compiler/system/template', 'ember-template-compiler/plugins', 'ember-template-compiler/plugins/transform-each-in-to-block-params', 'ember-template-compiler/plugins/transform-with-as-to-hash', 'ember-template-compiler/plugins/transform-bind-attr-to-attributes', 'ember-template-compiler/plugins/transform-each-into-collection', 'ember-template-compiler/plugins/transform-single-arg-each', 'ember-template-compiler/plugins/transform-old-binding-syntax', 'ember-template-compiler/plugins/transform-old-class-binding-syntax', 'ember-template-compiler/plugins/transform-item-class', 'ember-template-compiler/plugins/transform-component-attrs-into-mut', 'ember-template-compiler/plugins/transform-component-curly-to-readonly', 'ember-template-compiler/plugins/transform-angle-bracket-components', 'ember-template-compiler/compat'], function (exports, _Ember, precompile, compile, template, plugins, TransformEachInToBlockParams, TransformWithAsToHash, TransformBindAttrToAttributes, TransformEachIntoCollection, TransformSingleArgEach, TransformOldBindingSyntax, TransformOldClassBindingSyntax, TransformItemClass, TransformComponentAttrsIntoMut, TransformComponentCurlyToReadonly, TransformAngleBracketComponents) {
+enifed('ember-template-compiler', ['exports', 'ember-metal/core', 'ember-template-compiler/system/precompile', 'ember-template-compiler/system/compile', 'ember-template-compiler/system/template', 'ember-template-compiler/plugins', 'ember-template-compiler/plugins/transform-each-in-to-block-params', 'ember-template-compiler/plugins/transform-with-as-to-hash', 'ember-template-compiler/plugins/transform-bind-attr-to-attributes', 'ember-template-compiler/plugins/transform-each-into-collection', 'ember-template-compiler/plugins/transform-single-arg-each', 'ember-template-compiler/plugins/transform-old-binding-syntax', 'ember-template-compiler/plugins/transform-old-class-binding-syntax', 'ember-template-compiler/plugins/transform-item-class', 'ember-template-compiler/plugins/transform-component-attrs-into-mut', 'ember-template-compiler/plugins/transform-component-curly-to-readonly', 'ember-template-compiler/plugins/transform-angle-bracket-components', 'ember-template-compiler/plugins/transform-input-on-to-onEvent', 'ember-template-compiler/compat'], function (exports, _Ember, precompile, compile, template, plugins, TransformEachInToBlockParams, TransformWithAsToHash, TransformBindAttrToAttributes, TransformEachIntoCollection, TransformSingleArgEach, TransformOldBindingSyntax, TransformOldClassBindingSyntax, TransformItemClass, TransformComponentAttrsIntoMut, TransformComponentCurlyToReadonly, TransformAngleBracketComponents, TransformInputOnToOnEvent) {
'use strict';
plugins.registerPlugin("ast", TransformWithAsToHash['default']);
plugins.registerPlugin("ast", TransformEachInToBlockParams['default']);
@@ -9394,10 +10324,11 @@
plugins.registerPlugin("ast", TransformOldClassBindingSyntax['default']);
plugins.registerPlugin("ast", TransformItemClass['default']);
plugins.registerPlugin("ast", TransformComponentAttrsIntoMut['default']);
plugins.registerPlugin("ast", TransformComponentCurlyToReadonly['default']);
plugins.registerPlugin("ast", TransformAngleBracketComponents['default']);
+ plugins.registerPlugin("ast", TransformInputOnToOnEvent['default']);
exports._Ember = _Ember['default'];
exports.precompile = precompile['default'];
exports.compile = compile['default'];
exports.template = template['default'];
@@ -9448,19 +10379,20 @@
'use strict';
exports.registerPlugin = registerPlugin;
+ var plugins = {
+ ast: []
+ };
+
/**
Adds an AST plugin to be used by Ember.HTMLBars.compile.
@private
@method registerASTPlugin
*/
- var plugins = {
- ast: []
- };
function registerPlugin(type, Plugin) {
if (!plugins[type]) {
throw new Error('Attempting to register "' + Plugin + '" as "' + type + '" which is not a valid HTMLBars plugin type.');
}
@@ -9512,30 +10444,32 @@
/**
@module ember
@submodule ember-htmlbars
*/
- function TransformBindAttrToAttributes() {
+ function TransformBindAttrToAttributes(options) {
// set later within HTMLBars to the syntax package
this.syntax = null;
+ this.options = options || {};
}
/**
@private
@method transform
@param {AST} The AST to be transformed.
*/
TransformBindAttrToAttributes.prototype.transform = function TransformBindAttrToAttributes_transform(ast) {
var plugin = this;
+ var moduleName = this.options.moduleName;
var walker = new this.syntax.Walker();
walker.visit(ast, function (node) {
if (node.type === "ElementNode") {
for (var i = 0; i < node.modifiers.length; i++) {
var modifier = node.modifiers[i];
- if (isBindAttrModifier(modifier)) {
+ if (isBindAttrModifier(modifier, moduleName)) {
node.modifiers.splice(i--, 1);
plugin.assignAttrs(node, modifier.hash);
}
}
}
@@ -9632,15 +10566,34 @@
default:
Ember['default'].assert("Unsupported bind-attr class syntax: `" + value + "`");
}
};
- function isBindAttrModifier(modifier) {
+ function isBindAttrModifier(modifier, moduleName) {
var name = modifier.path.original;
+ var _ref = modifier.path.loc.start || {};
+
+ var column = _ref.column;
+ var line = _ref.line;
+
+ var moduleInfo = "";
+
+ if (moduleName) {
+ moduleInfo += "'" + moduleName + "' @ ";
+ }
+
+ if (line && column) {
+ moduleInfo += "L" + line + ":C" + column;
+ }
+
+ if (moduleInfo) {
+ moduleInfo = "(" + moduleInfo + ") ";
+ }
+
if (name === "bind-attr" || name === "bindAttr") {
- Ember['default'].deprecate("The `" + name + "` helper is deprecated in favor of " + "HTMLBars-style bound attributes");
+ Ember['default'].deprecate("The `" + name + "` helper " + moduleInfo + "is deprecated in favor of " + "HTMLBars-style bound attributes.");
return true;
} else {
return false;
}
}
@@ -9985,10 +10938,147 @@
return false;
}
});
+enifed('ember-template-compiler/plugins/transform-input-on-to-onEvent', ['exports'], function (exports) {
+
+ 'use strict';
+
+ /**
+ @module ember
+ @submodule ember-htmlbars
+ */
+
+ /**
+ An HTMLBars AST transformation that replaces all instances of
+
+ ```handlebars
+ {{input on="enter" action="doStuff"}}
+ {{input on="key-press" action="doStuff"}}
+ ```
+
+ with
+
+ ```handlebars
+ {{input enter="doStuff"}}
+ {{input key-press="doStuff"}}
+ ```
+
+ @private
+ @class TransformInputOnToOnEvent
+ */
+ function TransformInputOnToOnEvent(options) {
+ // set later within HTMLBars to the syntax package
+ this.syntax = null;
+ this.options = options || {};
+ }
+
+ /**
+ @private
+ @method transform
+ @param {AST} The AST to be transformed.
+ */
+ TransformInputOnToOnEvent.prototype.transform = function TransformInputOnToOnEvent_transform(ast) {
+ var pluginContext = this;
+ var b = pluginContext.syntax.builders;
+ var walker = new pluginContext.syntax.Walker();
+
+ walker.visit(ast, function (node) {
+ if (pluginContext.validate(node)) {
+ var action = hashPairForKey(node.hash, 'action');
+ var on = hashPairForKey(node.hash, 'on');
+ var onEvent = hashPairForKey(node.hash, 'onEvent');
+ var normalizedOn = on || onEvent;
+ var moduleInfo = pluginContext.calculateModuleInfo(node.loc);
+
+ if (normalizedOn && normalizedOn.value.type !== 'StringLiteral') {
+ Ember.deprecate('Using a dynamic value for \'#{normalizedOn.key}=\' with the \'{{input}}\' helper ' + moduleInfo + ' is deprecated.');
+
+ normalizedOn.key = 'onEvent';
+ return; // exit early, as we cannot transform further
+ }
+
+ removeFromHash(node.hash, normalizedOn);
+ removeFromHash(node.hash, action);
+
+ if (!action) {
+ Ember.deprecate('Using \'{{input ' + normalizedOn.key + '="' + normalizedOn.value.value + '" ...}}\' without specifying an action ' + moduleInfo + ' will do nothing.');
+
+ return; // exit early, if no action was available there is nothing to do
+ }
+
+ var specifiedOn = normalizedOn ? '' + normalizedOn.key + '="' + normalizedOn.value.value + '" ' : '';
+ if (normalizedOn && normalizedOn.value.value === 'keyPress') {
+ // using `keyPress` in the root of the component will
+ // clobber the keyPress event handler
+ normalizedOn.value.value = 'key-press';
+ }
+
+ var expected = '' + (normalizedOn ? normalizedOn.value.value : 'enter') + '="' + action.value.original + '"';
+
+ Ember.deprecate('Using \'{{input ' + specifiedOn + 'action="' + action.value.original + '"}} ' + moduleInfo + ' is deprecated. Please use \'{{input ' + expected + '}}\' instead.');
+ if (!normalizedOn) {
+ normalizedOn = b.pair('onEvent', b.string('enter'));
+ }
+
+ node.hash.pairs.push(b.pair(normalizedOn.value.value, action.value));
+ }
+ });
+
+ return ast;
+ };
+
+ TransformInputOnToOnEvent.prototype.validate = function TransformWithAsToHash_validate(node) {
+ return node.type === 'MustacheStatement' && node.path.original === 'input' && (hashPairForKey(node.hash, 'action') || hashPairForKey(node.hash, 'on') || hashPairForKey(node.hash, 'onEvent'));
+ };
+
+ TransformInputOnToOnEvent.prototype.calculateModuleInfo = function TransformInputOnToOnEvent_calculateModuleInfo(loc) {
+ var _ref = loc.start || {};
+
+ var column = _ref.column;
+ var line = _ref.line;
+
+ var moduleInfo = '';
+ if (this.options.moduleName) {
+ moduleInfo += '\'' + this.options.moduleName + '\' ';
+ }
+
+ if (line !== undefined && column !== undefined) {
+ moduleInfo += '@L' + line + ':C' + column;
+ }
+
+ return moduleInfo;
+ };
+
+ function hashPairForKey(hash, key) {
+ for (var i = 0, l = hash.pairs.length; i < l; i++) {
+ var pair = hash.pairs[i];
+ if (pair.key === key) {
+ return pair;
+ }
+ }
+
+ return false;
+ }
+
+ function removeFromHash(hash, pairToRemove) {
+ var newPairs = [];
+ for (var i = 0, l = hash.pairs.length; i < l; i++) {
+ var pair = hash.pairs[i];
+
+ if (pair !== pairToRemove) {
+ newPairs.push(pair);
+ }
+ }
+
+ hash.pairs = newPairs;
+ }
+
+ exports['default'] = TransformInputOnToOnEvent;
+
+});
enifed('ember-template-compiler/plugins/transform-item-class', ['exports'], function (exports) {
'use strict';
exports['default'] = TransformItemClass;
@@ -10416,13 +11506,10 @@
*/
exports['default'] = function (_options) {
var disableComponentGeneration = true;
- disableComponentGeneration = false;
-
-
var options = _options || {};
// When calling `Ember.Handlebars.compile()` a second argument of `true`
// had a special meaning (long since lost), this just gaurds against
// `options` being true, and causing an error during compilation.
if (options === true) {
@@ -10432,11 +11519,11 @@
options.disableComponentGeneration = disableComponentGeneration;
options.plugins = plugins['default'];
options.buildMeta = function buildMeta(program) {
return {
- revision: "Ember@1.13.0-beta.1",
+ revision: "Ember@1.13.0-beta.2",
loc: program.loc,
moduleName: options.moduleName
};
};
@@ -10529,36 +11616,57 @@
exports.compileSpec = compileSpec;
exports.template = template;
exports.compile = compile;
- /*
- * Compile a string into a template spec string. The template spec is a string
- * representation of a template. Usually, you would use compileSpec for
- * pre-compilation of a template on the server.
- *
- * Example usage:
- *
- * var templateSpec = compileSpec("Howdy {{name}}");
- * // This next step is basically what plain compile does
- * var template = new Function("return " + templateSpec)();
- *
- * @method compileSpec
- * @param {String} string An HTMLBars template string
- * @return {TemplateSpec} A template spec string
- */
function compileSpec(string, options) {
var ast = parser.preprocess(string, options);
var compiler = new TemplateCompiler['default'](options);
var program = compiler.compile(ast);
return program;
}
+ /*
+ * @method template
+ * @param {TemplateSpec} templateSpec A precompiled template
+ * @return {Template} A template spec string
+ */
function template(templateSpec) {
return new Function("return " + templateSpec)();
}
+ /*
+ * Compile a string into a template rendering function
+ *
+ * Example usage:
+ *
+ * // Template is the hydration portion of the compiled template
+ * var template = compile("Howdy {{name}}");
+ *
+ * // Template accepts three arguments:
+ * //
+ * // 1. A context object
+ * // 2. An env object
+ * // 3. A contextualElement (optional, document.body is the default)
+ * //
+ * // The env object *must* have at least these two properties:
+ * //
+ * // 1. `hooks` - Basic hooks for rendering a template
+ * // 2. `dom` - An instance of DOMHelper
+ * //
+ * import {hooks} from 'htmlbars-runtime';
+ * import {DOMHelper} from 'morph';
+ * var context = {name: 'whatever'},
+ * env = {hooks: hooks, dom: new DOMHelper()},
+ * contextualElement = document.body;
+ * var domFragment = template(context, env, contextualElement);
+ *
+ * @method compile
+ * @param {String} string An HTMLBars template string
+ * @param {Object} options A set of options to provide to the compiler
+ * @return {Template} A function for rendering the template
+ */
function compile(string, options) {
return hooks.wrap(template(compileSpec(string, options)), render['default']);
}
});
@@ -12064,82 +13172,10 @@
exports.hasHelper = hasHelper;
exports.lookupHelper = lookupHelper;
exports.bindScope = bindScope;
exports.updateScope = updateScope;
- /**
- HTMLBars delegates the runtime behavior of a template to
- hooks provided by the host environment. These hooks explain
- the lexical environment of a Handlebars template, the internal
- representation of references, and the interaction between an
- HTMLBars template and the DOM it is managing.
-
- While HTMLBars host hooks have access to all of this internal
- machinery, templates and helpers have access to the abstraction
- provided by the host hooks.
-
- ## The Lexical Environment
-
- The default lexical environment of an HTMLBars template includes:
-
- * Any local variables, provided by *block arguments*
- * The current value of `self`
-
- ## Simple Nesting
-
- Let's look at a simple template with a nested block:
-
- ```hbs
- <h1>{{title}}</h1>
-
- {{#if author}}
- <p class="byline">{{author}}</p>
- {{/if}}
- ```
-
- In this case, the lexical environment at the top-level of the
- template does not change inside of the `if` block. This is
- achieved via an implementation of `if` that looks like this:
-
- ```js
- registerHelper('if', function(params) {
- if (!!params[0]) {
- return this.yield();
- }
- });
- ```
-
- A call to `this.yield` invokes the child template using the
- current lexical environment.
-
- ## Block Arguments
-
- It is possible for nested blocks to introduce new local
- variables:
-
- ```hbs
- {{#count-calls as |i|}}
- <h1>{{title}}</h1>
- <p>Called {{i}} times</p>
- {{/count}}
- ```
-
- In this example, the child block inherits its surrounding
- lexical environment, but augments it with a single new
- variable binding.
-
- The implementation of `count-calls` supplies the value of
- `i`, but does not otherwise alter the environment:
-
- ```js
- var count = 0;
- registerHelper('count-calls', function() {
- return this.yield([ ++count ]);
- });
- ```
- */
-
function wrap(template) {
if (template === null) {
return null;
}
@@ -12326,10 +13362,34 @@
yield: options.template.yield,
yieldItem: options.template.yieldItem,
yieldIn: options.template.yieldIn
};
}
+
+ /**
+ Host Hook: createScope
+
+ @param {Scope?} parentScope
+ @return Scope
+
+ Corresponds to entering a new HTMLBars block.
+
+ This hook is invoked when a block is entered with
+ a new `self` or additional local variables.
+
+ When invoked for a top-level template, the
+ `parentScope` is `null`, and this hook should return
+ a fresh Scope.
+
+ When invoked for a child template, the `parentScope`
+ is the scope for the parent environment.
+
+ Note that the `Scope` is an opaque value that is
+ passed to other host hooks. For example, the `get`
+ hook uses the scope to retrieve a value for a given
+ scope and variable name.
+ */
function createScope(env, parentScope) {
if (parentScope) {
return env.hooks.createChildScope(parentScope);
} else {
return env.hooks.createFreshScope();
@@ -12341,43 +13401,189 @@
// separate dictionary to track whether a local was bound.
// See `bindLocal` for more information.
return { self: null, blocks: {}, locals: {}, localPresent: {} };
}
+ /**
+ Host Hook: bindShadowScope
+
+ @param {Scope?} parentScope
+ @return Scope
+
+ Corresponds to rendering a new template into an existing
+ render tree, but with a new top-level lexical scope. This
+ template is called the "shadow root".
+
+ If a shadow template invokes `{{yield}}`, it will render
+ the block provided to the shadow root in the original
+ lexical scope.
+
+ ```hbs
+ {{!-- post template --}}
+ <p>{{props.title}}</p>
+ {{yield}}
+
+ {{!-- blog template --}}
+ {{#post title="Hello world"}}
+ <p>by {{byline}}</p>
+ <article>This is my first post</article>
+ {{/post}}
+
+ {{#post title="Goodbye world"}}
+ <p>by {{byline}}</p>
+ <article>This is my last post</article>
+ {{/post}}
+ ```
+
+ ```js
+ helpers.post = function(params, hash, options) {
+ options.template.yieldIn(postTemplate, { props: hash });
+ };
+
+ blog.render({ byline: "Yehuda Katz" });
+ ```
+
+ Produces:
+
+ ```html
+ <p>Hello world</p>
+ <p>by Yehuda Katz</p>
+ <article>This is my first post</article>
+
+ <p>Goodbye world</p>
+ <p>by Yehuda Katz</p>
+ <article>This is my last post</article>
+ ```
+
+ In short, `yieldIn` creates a new top-level scope for the
+ provided template and renders it, making the original block
+ available to `{{yield}}` in that template.
+ */
function bindShadowScope(env /*, parentScope, shadowScope */) {
return env.hooks.createFreshScope();
}
function createChildScope(parent) {
var scope = object_utils.createObject(parent);
scope.locals = object_utils.createObject(parent.locals);
return scope;
}
+ /**
+ Host Hook: bindSelf
+
+ @param {Scope} scope
+ @param {any} self
+
+ Corresponds to entering a template.
+
+ This hook is invoked when the `self` value for a scope is ready to be bound.
+
+ The host must ensure that child scopes reflect the change to the `self` in
+ future calls to the `get` hook.
+ */
function bindSelf(env, scope, self) {
scope.self = self;
}
function updateSelf(env, scope, self) {
env.hooks.bindSelf(env, scope, self);
}
+ /**
+ Host Hook: bindLocal
+
+ @param {Environment} env
+ @param {Scope} scope
+ @param {String} name
+ @param {any} value
+
+ Corresponds to entering a template with block arguments.
+
+ This hook is invoked when a local variable for a scope has been provided.
+
+ The host must ensure that child scopes reflect the change in future calls
+ to the `get` hook.
+ */
function bindLocal(env, scope, name, value) {
scope.localPresent[name] = true;
scope.locals[name] = value;
}
function updateLocal(env, scope, name, value) {
env.hooks.bindLocal(env, scope, name, value);
}
+ /**
+ Host Hook: bindBlock
+
+ @param {Environment} env
+ @param {Scope} scope
+ @param {Function} block
+
+ Corresponds to entering a shadow template that was invoked by a block helper with
+ `yieldIn`.
+
+ This hook is invoked with an opaque block that will be passed along
+ to the shadow template, and inserted into the shadow template when
+ `{{yield}}` is used. Optionally provide a non-default block name
+ that can be targeted by `{{yield to=blockName}}`.
+ */
function bindBlock(env, scope, block) {
var name = arguments[3] === undefined ? "default" : arguments[3];
scope.blocks[name] = block;
}
+ /**
+ Host Hook: block
+
+ @param {RenderNode} renderNode
+ @param {Environment} env
+ @param {Scope} scope
+ @param {String} path
+ @param {Array} params
+ @param {Object} hash
+ @param {Block} block
+ @param {Block} elseBlock
+
+ Corresponds to:
+
+ ```hbs
+ {{#helper param1 param2 key1=val1 key2=val2}}
+ {{!-- child template --}}
+ {{/helper}}
+ ```
+
+ This host hook is a workhorse of the system. It is invoked
+ whenever a block is encountered, and is responsible for
+ resolving the helper to call, and then invoke it.
+
+ The helper should be invoked with:
+
+ - `{Array} params`: the parameters passed to the helper
+ in the template.
+ - `{Object} hash`: an object containing the keys and values passed
+ in the hash position in the template.
+
+ The values in `params` and `hash` will already be resolved
+ through a previous call to the `get` host hook.
+
+ The helper should be invoked with a `this` value that is
+ an object with one field:
+
+ `{Function} yield`: when invoked, this function executes the
+ block with the current scope. It takes an optional array of
+ block parameters. If block parameters are supplied, HTMLBars
+ will invoke the `bindLocal` host hook to bind the supplied
+ values to the block arguments provided by the template.
+
+ In general, the default implementation of `block` should work
+ for most host environments. It delegates to other host hooks
+ where appropriate, and properly invokes the helper with the
+ appropriate arguments.
+ */
function block(morph, env, scope, path, params, hash, template, inverse, visitor) {
if (handleRedirect(morph, env, scope, path, params, hash, template, inverse, visitor)) {
return;
}
@@ -12395,10 +13601,14 @@
var options = optionsFor(template, inverse, env, scope, morph, visitor);
template_utils.renderAndCleanup(morph, env, options, shadowOptions, callback);
}
function handleRedirect(morph, env, scope, path, params, hash, template, inverse, visitor) {
+ if (!path) {
+ return false;
+ }
+
var redirect = env.hooks.classify(env, scope, path);
if (redirect) {
switch (redirect) {
case "component":
env.hooks.component(morph, env, scope, path, params, hash, { "default": template, inverse: inverse }, visitor);break;
@@ -12506,10 +13716,48 @@
}
function linkRenderNode() {
return;
}
+ /**
+ Host Hook: inline
+
+ @param {RenderNode} renderNode
+ @param {Environment} env
+ @param {Scope} scope
+ @param {String} path
+ @param {Array} params
+ @param {Hash} hash
+
+ Corresponds to:
+
+ ```hbs
+ {{helper param1 param2 key1=val1 key2=val2}}
+ ```
+
+ This host hook is similar to the `block` host hook, but it
+ invokes helpers that do not supply an attached block.
+
+ Like the `block` hook, the helper should be invoked with:
+
+ - `{Array} params`: the parameters passed to the helper
+ in the template.
+ - `{Object} hash`: an object containing the keys and values passed
+ in the hash position in the template.
+
+ The values in `params` and `hash` will already be resolved
+ through a previous call to the `get` host hook.
+
+ In general, the default implementation of `inline` should work
+ for most host environments. It delegates to other host hooks
+ where appropriate, and properly invokes the helper with the
+ appropriate arguments.
+
+ The default implementation of `inline` also makes `partial`
+ a keyword. Instead of invoking a helper named `partial`,
+ it invokes the `partial` host hook.
+ */
function inline(morph, env, scope, path, params, hash, visitor) {
if (handleRedirect(morph, env, scope, path, params, hash, null, null, visitor)) {
return;
}
@@ -12586,15 +13834,35 @@
hasBlockParams: function (morph, env, scope, params) {
var name = env.hooks.getValue(params[0]) || "default";
return !!(scope.blocks[name] && scope.blocks[name].arity);
}
- };function partial(renderNode, env, scope, path) {
+ };
+
+ function partial(renderNode, env, scope, path) {
var template = env.partials[path];
return template.render(scope.self, env, {}).fragment;
}
+ /**
+ Host hook: range
+
+ @param {RenderNode} renderNode
+ @param {Environment} env
+ @param {Scope} scope
+ @param {any} value
+
+ Corresponds to:
+
+ ```hbs
+ {{content}}
+ {{{unescaped}}}
+ ```
+
+ This hook is responsible for updating a render node
+ that represents a range of content with a value.
+ */
function range(morph, env, scope, path, value, visitor) {
if (handleRedirect(morph, env, scope, path, [value], {}, null, null, visitor)) {
return;
}
@@ -12605,10 +13873,37 @@
}
morph.lastValue = value;
}
+ /**
+ Host hook: element
+
+ @param {RenderNode} renderNode
+ @param {Environment} env
+ @param {Scope} scope
+ @param {String} path
+ @param {Array} params
+ @param {Hash} hash
+
+ Corresponds to:
+
+ ```hbs
+ <div {{bind-attr foo=bar}}></div>
+ ```
+
+ This hook is responsible for invoking a helper that
+ modifies an element.
+
+ Its purpose is largely legacy support for awkward
+ idioms that became common when using the string-based
+ Handlebars engine.
+
+ Most of the uses of the `element` hook are expected
+ to be superseded by component syntax and the
+ `attribute` hook.
+ */
function element(morph, env, scope, path, params, hash, visitor) {
if (handleRedirect(morph, env, scope, path, params, hash, null, null, visitor)) {
return;
}
@@ -12616,10 +13911,31 @@
if (helper) {
env.hooks.invokeHelper(null, env, scope, null, params, hash, helper, { element: morph.element });
}
}
+ /**
+ Host hook: attribute
+
+ @param {RenderNode} renderNode
+ @param {Environment} env
+ @param {String} name
+ @param {any} value
+
+ Corresponds to:
+
+ ```hbs
+ <div foo={{bar}}></div>
+ ```
+
+ This hook is responsible for updating a render node
+ that represents an element's attribute with a value.
+
+ It receives the name of the attribute as well as an
+ already-resolved value, and should update the render
+ node with the value if appropriate.
+ */
function attribute(morph, env, scope, name, value) {
value = env.hooks.getValue(value);
if (morph.lastValue !== value) {
morph.setContent(value);
@@ -12634,10 +13950,30 @@
if (result && result.value) {
return result.value;
}
}
+ /**
+ Host Hook: get
+
+ @param {Environment} env
+ @param {Scope} scope
+ @param {String} path
+
+ Corresponds to:
+
+ ```hbs
+ {{foo.bar}}
+ ^
+
+ {{helper foo.bar key=value}}
+ ^ ^
+ ```
+
+ This hook is the "leaf" hook of the system. It is used to
+ resolve a path relative to the current scope.
+ */
function get(env, scope, path) {
if (path === "") {
return scope.self;
}
@@ -12816,11 +14152,11 @@
prototype.super$constructor = MorphBase['default'];
exports['default'] = HTMLBarsMorph;
});
-enifed('htmlbars-runtime/render', ['exports', '../htmlbars-util/array-utils', '../htmlbars-util/morph-utils', './expression-visitor', './morph', '../htmlbars-util/template-utils'], function (exports, array_utils, morph_utils, ExpressionVisitor, Morph, template_utils) {
+enifed('htmlbars-runtime/render', ['exports', '../htmlbars-util/array-utils', '../htmlbars-util/morph-utils', './expression-visitor', './morph', '../htmlbars-util/template-utils', '../htmlbars-util/void-tag-names'], function (exports, array_utils, morph_utils, ExpressionVisitor, Morph, template_utils, voidMap) {
'use strict';
exports.manualElement = manualElement;
exports.createChildMorph = createChildMorph;
@@ -12929,13 +14265,17 @@
continue;
}
dom.setAttribute(el1, key, attributes[key]);
}
- var el2 = dom.createComment("");
- dom.appendChild(el1, el2);
+ if (!voidMap['default'][tagName]) {
+ var el2 = dom.createComment("");
+ dom.appendChild(el1, el2);
+ }
+
dom.appendChild(el0, el1);
+
return el0;
},
buildRenderNodes: function buildRenderNodes(dom, fragment) {
var element = dom.childAt(fragment, [0]);
var morphs = [];
@@ -13141,12 +14481,10 @@
exports.buildUndefined = buildUndefined;
exports.buildHash = buildHash;
exports.buildPair = buildPair;
exports.buildProgram = buildProgram;
- // Statements
-
function buildMustache(path, params, hash, raw) {
return {
type: "MustacheStatement",
path: path,
params: params || [],
@@ -13197,10 +14535,12 @@
type: "ConcatStatement",
parts: parts || []
};
}
+ // Nodes
+
function buildElement(tag, attributes, modifiers, children) {
return {
type: "ElementNode",
tag: tag,
attributes: attributes || [],
@@ -13231,10 +14571,12 @@
type: "TextNode",
chars: chars
};
}
+ // Expressions
+
function buildSexpr(path, params, hash) {
return {
type: "SubExpression",
path: path,
params: params || [],
@@ -13288,10 +14630,12 @@
value: undefined,
original: undefined
};
}
+ // Miscellaneous
+
function buildHash(pairs) {
return {
type: "Hash",
pairs: pairs || []
};
@@ -13584,10 +14928,11 @@
function stripComment(comment) {
return comment.replace(/^\{\{~?\!-?-?/, '').replace(/-?-?~?\}\}$/, '');
}
function preparePath(data, parts, locInfo) {
+ /*jshint -W040 */
locInfo = this.locInfo(locInfo);
var original = data ? '@' : '',
dig = [],
depth = 0,
@@ -13615,18 +14960,20 @@
return new this.PathExpression(data, depth, dig, original, locInfo);
}
function prepareMustache(path, params, hash, open, strip, locInfo) {
+ /*jshint -W040 */
// Must use charAt to support IE pre-10
var escapeFlag = open.charAt(3) || open.charAt(2),
escaped = escapeFlag !== '{' && escapeFlag !== '&';
return new this.MustacheStatement(path, params, hash, escaped, strip, this.locInfo(locInfo));
}
function prepareRawBlock(openRawBlock, content, close, locInfo) {
+ /*jshint -W040 */
if (openRawBlock.path.original !== close) {
var errorNode = { loc: openRawBlock.path.loc };
throw new Exception['default'](openRawBlock.path.original + ' doesn\'t match ' + close, errorNode);
}
@@ -13636,10 +14983,11 @@
return new this.BlockStatement(openRawBlock.path, openRawBlock.params, openRawBlock.hash, program, undefined, {}, {}, {}, locInfo);
}
function prepareBlock(openBlock, program, inverseAndProgram, close, inverted, locInfo) {
+ /*jshint -W040 */
// When we are chaining inverse calls, we will not have a close path
if (close && close.path && openBlock.path.original !== close.path.original) {
var errorNode = { loc: openBlock.path.loc };
throw new Exception['default'](openBlock.path.original + ' doesn\'t match ' + close.path.original, errorNode);
@@ -13671,10 +15019,11 @@
});
enifed('htmlbars-syntax/handlebars/compiler/parser', ['exports'], function (exports) {
'use strict';
+ /* jshint ignore:start */
/* istanbul ignore next */
/* Jison generated parser */
var handlebars = (function () {
var parser = { trace: function trace() {},
yy: {},
@@ -14341,10 +15690,11 @@
function Parser() {
this.yy = {};
}Parser.prototype = parser;parser.Parser = Parser;
return new Parser();
})();exports['default'] = handlebars;
+ /* jshint ignore:end */
});
enifed('htmlbars-syntax/handlebars/compiler/visitor', ['exports', '../exception', './ast'], function (exports, Exception, AST) {
'use strict';
@@ -14782,11 +16132,13 @@
};
}
var isFunction;
var isArray = Array.isArray || function (value) {
return value && typeof value === 'object' ? toString.call(value) === '[object Array]' : false;
- };function indexOf(array, value) {
+ };
+
+ function indexOf(array, value) {
for (var i = 0, len = array.length; i < len; i++) {
if (array[i] === value) {
return i;
}
}
@@ -15101,23 +16453,14 @@
return string.join("\n");
};
});
-enifed('htmlbars-syntax/token-handlers', ['exports', '../htmlbars-util/array-utils', './builders', './utils'], function (exports, array_utils, builders, utils) {
+enifed('htmlbars-syntax/token-handlers', ['exports', './builders', './utils', '../htmlbars-util/void-tag-names'], function (exports, builders, utils, voidMap) {
'use strict';
- var voidTagNames = "area base br col command embed hr img input keygen link meta param source track wbr";
- var voidMap = {};
-
- array_utils.forEach(voidTagNames.split(" "), function (tagName) {
- voidMap[tagName] = true;
- });
-
- // Except for `mustache`, all tokens are only allowed outside of
- // a start or end tag.
var tokenHandlers = {
Comment: function (token) {
var current = this.currentElement();
var comment = builders.buildComment(token.chars);
utils.appendChild(current, comment);
@@ -15135,11 +16478,11 @@
start: { line: tag.firstLine, column: tag.firstColumn },
end: { line: null, column: null }
};
this.elementStack.push(element);
- if (voidMap.hasOwnProperty(tag.tagName) || tag.selfClosing) {
+ if (voidMap['default'].hasOwnProperty(tag.tagName) || tag.selfClosing) {
tokenHandlers.EndTag.call(this, tag, true);
}
},
BlockStatement: function () {
@@ -15212,11 +16555,11 @@
};
function validateEndTag(tag, element, selfClosing) {
var error;
- if (voidMap[tag.tagName] && !selfClosing) {
+ if (voidMap['default'][tag.tagName] && !selfClosing) {
// EngTag is also called by StartTag for void and self-closing tags (i.e.
// <input> or <br />, so we need to check for that here. Otherwise, we would
// throw an error for those cases.
error = "Invalid end tag " + formatEndTagInfo(tag) + " (void elements cannot have end tags).";
} else if (element.tag === undefined) {
@@ -15342,15 +16685,16 @@
exports.childrenFor = childrenFor;
exports.appendChild = appendChild;
exports.isHelper = isHelper;
exports.unwrapMustache = unwrapMustache;
+ var ID_INVERSE_PATTERN = /[!"#%-,\.\/;->@\[-\^`\{-~]/;
+
// Checks the component's attributes to see if it uses block params.
// If it does, registers the block params with the program and
// removes the corresponding attributes from the element.
- var ID_INVERSE_PATTERN = /[!"#%-,\.\/;->@\[-\^`\{-~]/;
function parseComponentBlockParams(element, program) {
var l = element.attributes.length;
var attrNames = [];
for (var i = 0; i < l; i++) {
@@ -15635,10 +16979,13 @@
} else {
return el[textProperty];
}
}
+ // IE8 does not have Object.create, so use a polyfill if needed.
+ // Polyfill based on Mozilla's (MDN)
+
function createObject(obj) {
if (typeof Object.create === "function") {
return Object.create(obj);
} else {
var Temp = function () {};
@@ -15786,11 +17133,13 @@
};
}
var isFunction;
var isArray = Array.isArray || function (value) {
return value && typeof value === 'object' ? toString.call(value) === '[object Array]' : false;
- };function indexOf(array, value) {
+ };
+
+ function indexOf(array, value) {
for (var i = 0, len = array.length; i < len; i++) {
if (array[i] === value) {
return i;
}
}
@@ -15851,12 +17200,10 @@
exports.visitChildren = visitChildren;
exports.validateChildMorphs = validateChildMorphs;
exports.linkParams = linkParams;
exports.dump = dump;
- /*globals console*/
-
function visitChildren(nodes, callback) {
if (!nodes || nodes.length === 0) {
return;
}
@@ -15982,10 +17329,13 @@
options[prop] = defaults[prop];
}
return options;
}
+ // IE8 does not have Object.create, so use a polyfill if needed.
+ // Polyfill based on Mozilla's (MDN)
+
function createObject(obj) {
if (typeof Object.create === 'function') {
return Object.create(obj);
} else {
var Temp = function () {};
@@ -16209,10 +17559,24 @@
morph.lastYielded = null;
morph.childNodes = null;
}
});
+enifed('htmlbars-util/void-tag-names', ['exports', './array-utils'], function (exports, array_utils) {
+
+ 'use strict';
+
+ var voidTagNames = "area base br col command embed hr img input keygen link meta param source track wbr";
+ var voidMap = {};
+
+ array_utils.forEach(voidTagNames.split(" "), function (tagName) {
+ voidMap[tagName] = true;
+ });
+
+ exports['default'] = voidMap;
+
+});
enifed('morph-range', ['exports', './morph-range/utils'], function (exports, utils) {
'use strict';
function Morph(domHelper, contextualElement) {
@@ -16592,10 +17956,9 @@
'use strict';
exports.clear = clear;
exports.insertBefore = insertBefore;
- // inclusive of both nodes
function clear(parentNode, firstNode, lastNode) {
if (!parentNode) {
return;
}
\ No newline at end of file