Skip to content
This repository has been archived by the owner on May 26, 2019. It is now read-only.

Encourage decorator-style Ember.computed/Ember.observer #110

Closed
Closed
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
19 changes: 11 additions & 8 deletions source/concepts/what-is-ember-js.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,11 @@ var president = Ember.Object.create({
firstName: "Barack",
lastName: "Obama",

fullName: function() {
fullName: Ember.computed(function() {
return this.get('firstName') + ' ' + this.get('lastName');

// Call this flag to mark the function as a property
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment isn't accurate now.

}.property()
})
});

president.get('fullName');
Expand All @@ -124,15 +124,18 @@ You can tell Ember about these dependencies like this:

```javascript
var president = Ember.Object.create({
firstName: "Barack",
lastName: "Obama",
init: function() {
this._super.apply(this, arguments);
this.set('firstName', 'Barack');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this kind of initialization encouraged? I thought that the shape deoptimization happened when defining attributes at a class level (using Ember.Object.extend(), but defining attributes with create was fine.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and wouldn't Ember.on('init', function() { be favoured so the user doesn't have to call super?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ember.on('init', function() { has issues:

  • The event system it relies up on is not performant.
  • Using events obscures the order functions are called in during hook execution.
  • It is not as ES2015-safe (native classes have a super, but no events).

Fwiw, Babel make it easy to use super anyway:

  init() {
    this._super(...arguments);
    // some thing
  }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't that be init(...arguments) {? I thought arguments the keyword was "gone" from ES2015.
Thanks for the clarification :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know nothing about arguments being removed. I would be shocked?!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

most runtimes special case method.apply(this, arguments) to a more optimal form of passing arguments through without needing to fully materialize it. Which is what method(...arguments) becomes.

Also, no arguments isn't going away in ES2015.

And, properties set in on('init') emit change events and in init() {..} they do not, which makes them very chatty. When an object is first instantiated its initial state does not need to be broadcast, broadcasting the changes here is wasteful, and easily results in work amplification

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arguments is "deprecated" in ES2015, Babel handles this fine:

function bob(...args) {
  return this.fmt(...args);
}
function bob() {
  return this.fmt.apply(this, arguments);
}

More in depth explanation why I put that in quotes: https://esdiscuss.org/notes/2015-03-25

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the alternative I linked to above now shows the ideal behavior as @thejameskyle describes. Not sure if I read the output incorrectly or if there was a deploy of the repl.

So, using ... at all times seems reasonable to me actually.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thejameskyle Ya, but its not going away in our life times. Except in potentially different modes, or new contexts.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it is actually deprecated. I think those comments may have mislead you.

this.set('lastName', 'Obama');
},

fullName: function() {
fullName: Ember.computed('firstName', 'lastName', function() {
return this.get('firstName') + ' ' + this.get('lastName');

// Tell Ember that this computed property depends on firstName
// and lastName
}.property('firstName', 'lastName')
// Tell Ember that this computed property depends on firstName
// and lastName
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

})
});
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,13 @@ format for the template:

```app/controllers/song.js
export default Ember.ObjectController.extend({
duration: function() {
var duration = this.get('model.duration'),
minutes = Math.floor(duration / 60),
seconds = duration % 60;
duration: Ember.computed('model.duration', function() {
var duration = this.get('model.duration');
var minutes = Math.floor(duration / 60);
var seconds = duration % 60;

return [minutes, seconds].join(':');
}.property('model.duration')
})
});
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ property called `longSongCount` to the controller:

```app/controllers/songs.js
export default Ember.ArrayController.extend({
longSongCount: function() {
longSongCount: Ember.computed('@each.duration', function() {
var longSongs = this.filter(function(song) {
return song.get('duration') > 30;
});
return longSongs.get('length');
}.property('@each.duration')
})
});
```

Expand Down Expand Up @@ -75,11 +75,9 @@ creating an `ObjectController`:

```app/controllers/song.js
export default Ember.ObjectController.extend({
fullName: function() {

fullName: Ember.computed('name', 'artist', function() {
return this.get('name') + ' by ' + this.get('artist');

}.property('name', 'artist')
})
});
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ var Router = Ember.Router.extend({
});

Router.reopen({
notifyGoogleAnalytics: function() {
notifyGoogleAnalytics: Ember.on('didTransition', function() {
return ga('send', 'pageview', {
'page': this.get('url'),
'title': this.get('url')
});
}.on('didTransition')
})
});

export default Router;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ You can also set the class name based on a computed property.

```js
classNameBindings: ['isActive'],
isActive: function() {
isActive: Ember.computed('someAttribute', function() {
return 'active';
}.property('someAttribute')
})
```

Another way would be to bind the class name to a bound property.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ been focused. The component expects to get the validation result as the
export default Ember.Component.extend({
beenFocused: false,
valid: null,
hasError: function() {
hasError: Ember.computed('valid', 'beenFocused', function() {
if (this.get('beenFocused')) {
return !this.get('valid');
}
}.property('valid', 'beenFocused'),
}),

focusOut: function() {
this.set('beenFocused', true);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ Make use of the [computed property's setter][setters] to remove the
display formatting and set the property to the proper value.

```js
formattedAmount: function(key, value) {
formattedAmount: Ember.computed('amount', function(key, value) {
if (arguments.length > 1) {
// setter
var cleanAmount = accounting.unformat(value);
this.set('amount', cleanAmount);
}

return accounting.formatMoney(this.get('amount'));
}.property('amount')
});
```

<!---#### Example
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,14 @@ the same thing as Handlebars helpers defined above.

```app/controllers/application.js
export default Ember.Controller.extend({
format: "YYYYMMDD",
format: 'YYYYMMDD',
date: null,
formattedDate: function() {
var date = this.get('date'),
format = this.get('format');
formattedDate: Ember.computed('date', 'format', function() {
var date = this.get('date');
var format = this.get('format');

return moment(date).format(format);
}.property('date', 'format')
});
});
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ to the text field by accessing the components's jQuery `$` property:

```app/components/focus-input.js
export default Ember.TextField.extend({
becomeFocused: function() {
becomeFocused: Ember.on('didInsertElement', function() {
this.$().focus();
}.on('didInsertElement')
})
});
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,26 @@ of the interval. Since the `tick` method observes the incremented property
another interval is triggered each time the property increases.

```app/services/clock.js
var observer = Ember.observer;
var on = Ember.on;

export default Ember.Object.extend({
pulse: Ember.computed.oneWay('_seconds').readOnly(),
tick: function () {
var clock = this;
Ember.run.later(function () {
var seconds = clock.get('_seconds');
if (typeof seconds === 'number') {
clock.set('_seconds', seconds + (1/4));
}
}, 250);
}.observes('_seconds').on('init'),
_seconds: 0
});
init: function() {
this._super.apply(this, arguments);
this._seconds = 0;
},

pulse: Ember.computed.oneWay('_seconds').readOnly(),
tick: on(observer('_seconds', function () {
var clock = this;
Ember.run.later(function () {
var seconds = clock.get('_seconds');
if (typeof seconds === 'number') {
clock.set('_seconds', seconds + (1/4));
}
}, 250);
}), 'init')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

http://emberjs.com/api/classes/Ember.html#method_on
on (eventNames, func)

isn't this backward? Shouldn't it be
Ember.on('init, Ember.observer('_seconds', function(){...}));

});
```

### Binding to the `pulse` attribute
Expand Down Expand Up @@ -69,20 +76,20 @@ a handful of properties used as conditions in the Handlebars template.

```app/controllers/interval.js
export default Ember.ObjectController.extend({
secondsBinding: 'clock.pulse',
fullSecond: function () {
return (this.get('seconds') % 1 === 0);
}.property('seconds'),
quarterSecond: function () {
return (this.get('seconds') % 1 === 1/4);
}.property('seconds'),
halfSecond: function () {
return (this.get('seconds') % 1 === 1/2);
}.property('seconds'),
threeQuarterSecond: function () {
return (this.get('seconds') % 1 === 3/4);
}.property('seconds')
});
secondsBinding: 'clock.pulse',
fullSecond: Ember.computed('seconds', function () {
return (this.get('seconds') % 1 === 0);
}),
quarterSecond: Ember.computed('seconds', function () {
return (this.get('seconds') % 1 === 1/4);
}),
halfSecond: Ember.computed('seconds', function () {
return (this.get('seconds') % 1 === 1/2);
}),
threeQuarterSecond: Ember.computed('seconds', function () {
return (this.get('seconds') % 1 === 3/4);
})
});
```

A controller for a list of comments, each comment will have a new clock
Expand All @@ -92,26 +99,26 @@ comment was created.

```app/controllers/comment-item.js
export default Ember.ObjectController.extend({
seconds: Ember.computed.oneWay('clock.pulse').readOnly()
seconds: Ember.computed.oneWay('clock.pulse').readOnly()
});
```

```app/controllers/comments.js
import ClockService from '../services/clock';

export default Ember.ArrayController.extend({
itemController: 'commentItem',
comment: null,
actions: {
add: function () {
this.addObject(Em.Object.create({
comment: this.get('comment'),
clock: ClockService.create()
}));
this.set('comment', null);
}
itemController: 'commentItem',
comment: null,
actions: {
add: function () {
this.addObject(Em.Object.create({
comment: this.get('comment'),
clock: ClockService.create()
}));
this.set('comment', null);
}
});
}
});
```

### Handlebars template which displays the `pulse`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ You want to display an Ember content array from an ArrayController in descending
One way to achieve that is to extend `Ember.ArrayController` with a new function called `reverse`.
You will also have to create a computed property:
```javascript
reversedArray: function() {
return this.toArray().reverse();
}.property('myArray.@each')
reversedArray: Ember.computed('myArray.[]', function() {
return this.toArray().reverse();
})
```

Once you do that, you will be able to use `reversedArray` property in your Handlebars template: `{{#each reversedArray}}{{/each}}`.
Expand All @@ -31,4 +31,4 @@ And in your template you will be able to consume a reversed array, like this: `{

### Example

<a class="jsbin-embed" href="http://jsbin.com/opid/3/embed?html,js,output">JS Bin</a><script src="http://static.jsbin.com/js/embed.js"></script>
<a class="jsbin-embed" href="http://jsbin.com/opid/3/embed?html,js,output">JS Bin</a><script src="http://static.jsbin.com/js/embed.js"></script>
4 changes: 2 additions & 2 deletions source/models/defining-models.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ export default DS.Model.extend({
firstName: attr(),
lastName: attr(),

fullName: function() {
fullName: Ember.computed('firstName', 'lastName', function() {
return this.get('firstName') + ' ' + this.get('lastName');
}.property('firstName', 'lastName')
})
});
```

Expand Down
19 changes: 11 additions & 8 deletions source/object-model/computed-properties-and-aggregate-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,19 @@ Here's what that computed property might look like:

```app/controllers/todos.js
export default Ember.Controller.extend({
todos: [
Ember.Object.create({ isDone: true }),
Ember.Object.create({ isDone: false }),
Ember.Object.create({ isDone: true })
],

remaining: function() {
init: function() {
this._super.apply(this, arguments);
this.set('todos', [
{ isDone: true },
{ isDone: false },
{ isDone: true }
]);
},

remaining: Ember.computed('todos.@each.isDone', function() {
var todos = this.get('todos');
return todos.filterBy('isDone', false).get('length');
}.property('todos.@each.isDone')
})
});
```

Expand Down
Loading