Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Improved CP Syntax #9527

Merged
merged 1 commit into from
Jan 2, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ for a detailed explanation.

* `ember-htmlbars-inline-if-helper`

Enables the use of the if helper in inline form. The truthy
Enables the use of the if helper in inline form. The truthy
and falsy values are passed as params, instead of using the block form.

Added in [#9718](https://github.com/emberjs/ember.js/pull/9718).
Expand Down Expand Up @@ -100,3 +100,12 @@ for a detailed explanation.
- `transitioning-out`: link-to is currently active, but will no longer
be active when the current underway (slow) transition completes.

* `new-computed-syntax`

Enables the new computed property syntax. In this new syntax, instead of passing
a function that acts both as getter and setter for the property, `Ember.computed`
receives an object with `get` and `set` keys, each one containing a function.
If the object does not contain a `set` key, the property will simply be overrided.
Passing just function is still supported, and is equivalent to pass only a getter.

Added in [#9527](https://github.com/emberjs/ember.js/pull/9527).
3 changes: 2 additions & 1 deletion features.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"ember-htmlbars-component-generation": null,
"ember-htmlbars-inline-if-helper": true,
"ember-htmlbars-attribute-syntax": null,
"ember-routing-transitioning-classes": true
"ember-routing-transitioning-classes": true,
"new-computed-syntax": null
},
"debugStatements": [
"Ember.warn",
Expand Down
68 changes: 42 additions & 26 deletions packages/ember-metal/lib/computed.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,28 @@ function UNDEFINED() { }
@extends Ember.Descriptor
@constructor
*/
function ComputedProperty(func, opts) {
func.__ember_arity__ = func.length;
this.func = func;
function ComputedProperty(config, opts) {
if (Ember.FEATURES.isEnabled("new-computed-syntax")) {
if (typeof config === "function") {
config.__ember_arity = config.length;
this._getter = config;
if (config.__ember_arity > 1) {
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;
}
}
} else {
config.__ember_arity = config.length;
this._getter = config;
if (config.__ember_arity > 1) {
this._setter = config;
}
}

this._dependentKeys = undefined;
this._suspended = undefined;
Expand Down Expand Up @@ -336,7 +355,7 @@ ComputedPropertyPrototype.get = function(obj, keyName) {
return result;
}

ret = this.func.call(obj, keyName);
ret = this._getter.call(obj, keyName);
if (ret === undefined) {
cache[keyName] = UNDEFINED;
} else {
Expand All @@ -349,7 +368,7 @@ ComputedPropertyPrototype.get = function(obj, keyName) {
}
addDependentKeys(this, obj, keyName, meta);
} else {
ret = this.func.call(obj, keyName);
ret = this._getter.call(obj, keyName);
}
return ret;
};
Expand Down Expand Up @@ -416,12 +435,12 @@ ComputedPropertyPrototype.set = function computedPropertySetWithSuspend(obj, key

ComputedPropertyPrototype._set = function computedPropertySet(obj, keyName, value) {
var cacheable = this._cacheable;
var func = this.func;
var setter = this._setter;
var meta = metaFor(obj, cacheable);
var cache = meta.cache;
var hadCachedValue = false;

var funcArgLength, cachedValue, ret;
var cachedValue, ret;

if (this._readOnly) {
throw new EmberError('Cannot set read-only property "' + keyName + '" on object: ' + inspect(obj));
Expand All @@ -435,24 +454,16 @@ ComputedPropertyPrototype._set = function computedPropertySet(obj, keyName, valu
hadCachedValue = true;
}

// Check if the CP has been wrapped. If it has, use the
// length from the wrapped function.

funcArgLength = func.wrappedFunction ? func.wrappedFunction.__ember_arity__ : func.__ember_arity__;

// For backwards-compatibility with computed properties
// that check for arguments.length === 2 to determine if
// they are being get or set, only pass the old cached
// value if the computed property opts into a third
// argument.
if (funcArgLength === 3) {
ret = func.call(obj, keyName, value, cachedValue);
} else if (funcArgLength === 2) {
ret = func.call(obj, keyName, value);
} else {
if (!setter) {
defineProperty(obj, keyName, null, cachedValue);
set(obj, keyName, value);
return;
} else if (setter.__ember_arity === 2) {
// Is there any way of deprecate this in a sensitive way?
// Maybe now that getters and setters are the prefered options we can....
ret = setter.call(obj, keyName, value);
} else {
ret = setter.call(obj, keyName, value, cachedValue);
}

if (hadCachedValue && cachedValue === ret) { return; }
Expand Down Expand Up @@ -557,11 +568,16 @@ function computed(func) {
func = args.pop();
}

if (typeof func !== "function") {
throw new EmberError("Computed Property declared without a property function");
}

var cp = new ComputedProperty(func);
// jscs:disable
if (Ember.FEATURES.isEnabled("new-computed-syntax")) {
// Empty block on purpose
} else {
// jscs:enable
if (typeof func !== "function") {
throw new EmberError("Computed Property declared without a property function");
}
}

if (args) {
cp.property.apply(cp, args);
Expand Down
11 changes: 9 additions & 2 deletions packages/ember-metal/lib/mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,14 @@ function giveDescriptorSuper(meta, key, property, values, descs) {
// to clone the computed property so that other mixins do not receive
// the wrapped version.
property = o_create(property);
property.func = wrap(property.func, superProperty.func);
property._getter = wrap(property._getter, superProperty._getter);
if (superProperty._setter) {
if (property._setter) {
property._setter = wrap(property._setter, superProperty._setter);
} else {
property._setter = superProperty._setter;
}
}

return property;
}
Expand Down Expand Up @@ -250,7 +257,7 @@ function addNormalizedProperty(base, key, value, meta, descs, values, concats, m

// Wrap descriptor function to implement
// __nextSuper() if needed
if (value.func) {
if (value._getter) {
value = giveDescriptorSuper(meta, key, value, values, descs);
}

Expand Down
1 change: 0 additions & 1 deletion packages/ember-metal/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,6 @@ export function wrap(func, superFunc) {
}

superWrapper.wrappedFunction = func;
superWrapper.wrappedFunction.__ember_arity__ = func.length;
superWrapper.__ember_observes__ = func.__ember_observes__;
superWrapper.__ember_observesBefore__ = func.__ember_observesBefore__;
superWrapper.__ember_listens__ = func.__ember_listens__;
Expand Down
50 changes: 50 additions & 0 deletions packages/ember-metal/tests/computed_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,56 @@ testBoth('chained dependent keys should evaluate computed properties lazily', fu
equal(count, 0, 'b should not run');
});

// ..........................................................
// improved-cp-syntax
//

if (Ember.FEATURES.isEnabled("new-computed-syntax")) {
QUnit.module('computed - improved cp syntax');

test('setter and getters are passed using an object', function() {
var testObj = Ember.Object.extend({
a: '1',
b: '2',
aInt: computed('a', {
get: function(keyName) {
equal(keyName, 'aInt', 'getter receives the keyName');
return parseInt(this.get('a'));
},
set: function(keyName, value, oldValue) {
equal(keyName, 'aInt', 'setter receives the keyName');
equal(value, 123, 'setter receives the new value');
equal(oldValue, 1, 'setter receives the old value');
this.set('a', ""+value); // side effect
return parseInt(this.get('a'));
}
})
}).create();

ok(testObj.get('aInt') === 1, 'getter works');
testObj.set('aInt', 123);
ok(testObj.get('a') === '123', 'setter works');
ok(testObj.get('aInt') === 123, 'cp has been updated too');
});

test('setter can be omited', function() {
var testObj = Ember.Object.extend({
a: '1',
b: '2',
aInt: computed('a', {
get: function(keyName) {
equal(keyName, 'aInt', 'getter receives the keyName');
return parseInt(this.get('a'));
}
})
}).create();

ok(testObj.get('aInt') === 1, 'getter works');
ok(testObj.get('a') === '1');
testObj.set('aInt', '123');
ok(testObj.get('aInt') === '123', 'cp has been updated too');
});
}

// ..........................................................
// BUGS
Expand Down
4 changes: 2 additions & 2 deletions packages/ember-runtime/lib/computed/array_computed.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ function ArrayComputedProperty() {

ReduceComputedProperty.apply(this, arguments);

this.func = (function(reduceFunc) {
this._getter = (function(reduceFunc) {
return function (propertyName) {
if (!cp._hasInstanceMeta(this, propertyName)) {
// When we recompute an array computed property, we need already
Expand All @@ -29,7 +29,7 @@ function ArrayComputedProperty() {

return reduceFunc.apply(this, arguments);
};
})(this.func);
})(this._getter);

return this;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/ember-runtime/lib/computed/reduce_computed.js
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ function ReduceComputedProperty(options) {
};


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

recompute.call(this, propertyName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ test("The `contentArrayDidChange` method is invoked after `content` is updated."
proxy = ArrayProxy.createWithMixins({
content: Ember.A(),

arrangedContent: computed('content', function(key, value) {
// setup arrangedContent as a different object than content,
// which is the default
arrangedContent: computed('content', function(key) {
return Ember.A(this.get('content').slice());
}),

Expand Down