Skip to content

Commit

Permalink
Merge pull request #3696 from twokul/composable-cp
Browse files Browse the repository at this point in the history
[FEATURE beta] Composable Computed Properties
  • Loading branch information
rwjblue committed Jan 7, 2014
2 parents 51d1ebd + f34661c commit 54262c7
Show file tree
Hide file tree
Showing 10 changed files with 453 additions and 27 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ assets/bpm_libs.js
assets/bpm_styles.css
coverage
dist
distold
docs/build
docs/node_modules
lib/*/tests/all.js
Expand Down
3 changes: 2 additions & 1 deletion features.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"ember-testing-simple-setup": null,
"ember-testing-routing-helpers": null,
"ember-testing-triggerEvent-helper": null,
"computed-read-only": null
"computed-read-only": null,
"composable-computed-properties": true
},
"debugStatements": ["Ember.warn", "Ember.assert", "Ember.deprecate", "Ember.debug", "Ember.Logger.info"]
}
54 changes: 43 additions & 11 deletions packages/ember-metal/lib/array.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,49 @@ var arrayIndexOf = isNativeFunc(Array.prototype.indexOf) ? Array.prototype.index
return -1;
};

/**
Array polyfills to support ES5 features in older browsers.
@namespace Ember
@property ArrayPolyfills
*/
Ember.ArrayPolyfills = {
map: arrayMap,
forEach: arrayForEach,
indexOf: arrayIndexOf
};
if (Ember.FEATURES.isEnabled('array-filter')) {
var arrayFilter = isNativeFunc(Array.prototype.filter) ? Array.prototype.filter : function (fn, context) {
var i,
value,
result = [],
length = this.length;

for (i = 0; i < length; i++) {
if (this.hasOwnProperty(i)) {
value = this[i];
if (fn.call(context, value, i, this)) {
result.push(value);
}
}
}
return result;
};

/**
Array polyfills to support ES5 features in older browsers.
@namespace Ember
@property ArrayPolyfills
*/
Ember.ArrayPolyfills = {
map: arrayMap,
forEach: arrayForEach,
filter: arrayFilter,
indexOf: arrayIndexOf
};
} else {
/**
Array polyfills to support ES5 features in older browsers.
@namespace Ember
@property ArrayPolyfills
*/
Ember.ArrayPolyfills = {
map: arrayMap,
forEach: arrayForEach,
indexOf: arrayIndexOf
};
}

if (Ember.SHIM_ES5) {
if (!Array.prototype.map) {
Expand Down
6 changes: 6 additions & 0 deletions packages/ember-metal/lib/composable_computed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
require('ember-metal/core');
require('ember-metal/platform');
require('ember-metal/utils');

if (Ember.FEATURES.isEnabled('composable-computed-properties')) {
}
133 changes: 119 additions & 14 deletions packages/ember-metal/lib/computed.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ var get = Ember.get,
watch = Ember.watch,
unwatch = Ember.unwatch;


if (Ember.FEATURES.isEnabled('composable-computed-properties')) {
}

if (Ember.FEATURES.isEnabled('propertyBraceExpansion')) {
var expandProperties = Ember.expandProperties;
}
Expand Down Expand Up @@ -186,9 +190,13 @@ function removeDependentKeys(desc, obj, keyName, meta) {
*/
function ComputedProperty(func, opts) {
this.func = func;
if (Ember.FEATURES.isEnabled('composable-computed-properties')) {
setDependentKeys(this, opts && opts.dependentKeys);
} else {
this._dependentKeys = opts && opts.dependentKeys;
}

this._cacheable = (opts && opts.cacheable !== undefined) ? opts.cacheable : true;
this._dependentKeys = opts && opts.dependentKeys;
this._readOnly = opts && (opts.readOnly !== undefined || !!opts.readOnly);
}

Expand All @@ -197,6 +205,15 @@ ComputedProperty.prototype = new Ember.Descriptor();

var ComputedPropertyPrototype = ComputedProperty.prototype;

if (Ember.FEATURES.isEnabled('composable-computed-properties')) {
ComputedPropertyPrototype.toString = function() {
if (this.implicitCPKey) {
return this.implicitCPKey;
}
return Ember.Descriptor.prototype.toString.apply(this, arguments);
};
}

/**
Properties are cacheable by default. Computed property will automatically
cache the return value of your function until one of the dependent keys changes.
Expand Down Expand Up @@ -304,7 +321,12 @@ ComputedPropertyPrototype.property = function() {
args = a_slice.call(arguments);
}

this._dependentKeys = args;
if (Ember.FEATURES.isEnabled('composable-computed-properties')) {
setDependentKeys(this, args);
} else {
this._dependentKeys = args;
}

return this;
};

Expand Down Expand Up @@ -562,24 +584,107 @@ function getProperties(self, propertyNames) {
return ret;
}

function registerComputed(name, macro) {
Ember.computed[name] = function(dependentKey) {
var args = a_slice.call(arguments);
return Ember.computed(dependentKey, function() {
return macro.apply(this, args);
var registerComputed, registerComputedWithProperties;

if (Ember.FEATURES.isEnabled('composable-computed-properties')) {
var guidFor = Ember.guidFor,
map = Ember.EnumerableUtils.map,
filter = Ember.EnumerableUtils.filter,
typeOf = Ember.typeOf;

var implicitKey = function (cp) {
return [guidFor(cp)].concat(cp._dependentKeys).join('_');
};

var normalizeDependentKey = function (key) {
if (key instanceof Ember.ComputedProperty) {
return implicitKey(key);
} else if (typeof key === 'string' || key instanceof String || typeof key === 'object' || typeof key === 'number') {
return key;
} else {
Ember.assert('Unexpected dependent key ' + key + ' of type ' + typeof(key), false);
}
};

var normalizeDependentKeys = function (keys) {
return map(keys, function (key) {
return normalizeDependentKey(key);
});
};

var selectDependentCPs = function (keys) {
return filter(keys, function (key) {
return key instanceof Ember.ComputedProperty;
});
};

var setDependentKeys = function(cp, dependentKeys) {
if (dependentKeys) {
cp._dependentKeys = normalizeDependentKeys(dependentKeys);
cp._dependentCPs = selectDependentCPs(dependentKeys);
cp.implicitCPKey = implicitKey(cp);
} else {
cp._dependentKeys = cp._dependentCPs = [];
delete cp.implicitCPKey;
}
};
// expose `normalizeDependentKey[s]` so user CP macros can easily support
// composition
Ember.computed.normalizeDependentKey = normalizeDependentKey;
Ember.computed.normalizeDependentKeys = normalizeDependentKeys;

registerComputed = function (name, macro) {
Ember.computed[name] = function(dependentKey) {
var args = normalizeDependentKeys(a_slice.call(arguments));
return Ember.computed(dependentKey, function() {
return macro.apply(this, args);
});
};
};
}

function registerComputedWithProperties(name, macro) {
Ember.computed[name] = function() {
var properties = a_slice.call(arguments);
if (Ember.FEATURES.isEnabled('composable-computed-properties')) {
registerComputedWithProperties = function(name, macro) {
Ember.computed[name] = function() {
var args = a_slice.call(arguments);
var properties = normalizeDependentKeys(args);

var computed = Ember.computed(function() {
return macro.apply(this, [getProperties(this, properties)]);
});
var computed = Ember.computed(function() {
return macro.apply(this, [getProperties(this, properties)]);
});

return computed.property.apply(computed, args);
};
};
} else {
registerComputed = function (name, macro) {
Ember.computed[name] = function(dependentKey) {
var args = a_slice.call(arguments);
return Ember.computed(dependentKey, function() {
return macro.apply(this, args);
});
};
};

registerComputedWithProperties = function(name, macro) {
Ember.computed[name] = function() {
var properties = a_slice.call(arguments);

return computed.property.apply(computed, properties);
var computed = Ember.computed(function() {
return macro.apply(this, [getProperties(this, properties)]);
});

return computed.property.apply(computed, properties);
};
};
}


if (Ember.FEATURES.isEnabled('composable-computed-properties')) {
Ember.computed.literal = function (value) {
return Ember.computed(function () {
return value;
});
};
}

Expand Down
7 changes: 6 additions & 1 deletion packages/ember-metal/lib/enumerable_utils.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
var map, forEach, indexOf, splice;
var map, forEach, indexOf, splice, filter;
require('ember-metal/array');

map = Array.prototype.map || Ember.ArrayPolyfills.map;
forEach = Array.prototype.forEach || Ember.ArrayPolyfills.forEach;
indexOf = Array.prototype.indexOf || Ember.ArrayPolyfills.indexOf;
filter = Array.prototype.filter || Ember.ArrayPolyfills.filter;
splice = Array.prototype.splice;

var utils = Ember.EnumerableUtils = {
Expand All @@ -15,6 +16,10 @@ var utils = Ember.EnumerableUtils = {
return obj.forEach ? obj.forEach.call(obj, callback, thisArg) : forEach.call(obj, callback, thisArg);
},

filter: function(obj, callback, thisArg) {
return obj.filter ? obj.filter.call(obj, callback, thisArg) : filter.call(obj, callback, thisArg);
},

indexOf: function(obj, element, index) {
return obj.indexOf ? obj.indexOf.call(obj, element, index) : indexOf.call(obj, element, index);
},
Expand Down
22 changes: 22 additions & 0 deletions packages/ember-metal/lib/properties.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ Ember.defineProperty = function(obj, keyName, desc, data, meta) {
} else {
obj[keyName] = undefined; // make enumerable
}

if (Ember.FEATURES.isEnabled('composable-computed-properties')) {
if (desc.func && desc._dependentCPs) {
addImplicitCPs(obj, desc._dependentCPs, meta);
}
}
} else {
descs[keyName] = undefined; // shadow descriptor in proto
if (desc == null) {
Expand Down Expand Up @@ -151,3 +157,19 @@ Ember.defineProperty = function(obj, keyName, desc, data, meta) {
return this;
};

if (Ember.FEATURES.isEnabled('composable-computed-properties')) {
var addImplicitCPs = function defineImplicitCPs(obj, implicitCPs, meta) {
var cp, key, length = implicitCPs.length;

for (var i=0; i<length; ++i) {
cp = implicitCPs[i];
key = cp.implicitCPKey;

Ember.defineProperty(obj, key, cp, undefined, meta);

if (cp._dependentCPs) {
addImplicitCPs(obj, cp._dependentCPs, meta);
}
}
};
}
1 change: 1 addition & 0 deletions packages/ember-runtime/lib/computed/reduce_computed.js
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,7 @@ function ReduceComputedProperty(options) {
}, this);
};


this.func = function (propertyName) {
Ember.assert("Computed reduce values require at least one dependent key", cp._dependentKeys);

Expand Down
Loading

0 comments on commit 54262c7

Please sign in to comment.