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

[BUGFIX beta] Add special values to {{each}}'s keyPath. #11339

Merged
merged 1 commit into from
Jun 4, 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
49 changes: 46 additions & 3 deletions packages/ember-htmlbars/lib/helpers/each.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { get } from "ember-metal/property_get";
import { forEach } from "ember-metal/enumerable_utils";
import { guidFor } from "ember-metal/utils";
import normalizeSelf from "ember-htmlbars/utils/normalize-self";
import shouldDisplay from "ember-views/streams/should_display";

Expand All @@ -15,7 +16,7 @@ import shouldDisplay from "ember-views/streams/should_display";
```

```handlebars
{{#each developers as |person|}}
{{#each developers key="name" as |person|}}
{{person.name}}
{{! `this` is whatever it was outside the #each }}
{{/each}}
Expand All @@ -28,11 +29,35 @@ import shouldDisplay from "ember-views/streams/should_display";
```

```handlebars
{{#each developerNames as |name|}}
{{#each developerNames key="@index" as |name|}}
{{name}}
{{/each}}
```

### `key` param

The `key` hash parameter provides much needed insight into how the rendering
engine should determine if a given iteration of the loop matches a previous one.
This is mostly apparent during re-rendering when the array being iterated may
have changed (via sort, removal, addition, etc).

For example, using the following:

```handlebars
{{#each model key="id" as |item|}}
{{/each}}
```

Upon re-render, the rendering engine will match up the previously rendered items
(and reorder the generated DOM elements) based on each item's `id` property.

There are a few special values for `key`:

* `@index` - The index of the item in the array.
* `@item` - The item in the array itself. This can only be used for arrays of strings
or numbers.
* `@guid` - Generate a unique identifier for each object (uses `Ember.guidFor`).

### {{else}} condition

`{{#each}}` can have a matching `{{else}}`. The contents of this block will render
Expand Down Expand Up @@ -67,7 +92,25 @@ export default function eachHelper(params, hash, blocks) {
self = normalizeSelf(item);
}

var key = keyPath ? get(item, keyPath) : String(i);
var key;
switch (keyPath) {
case '@index':
key = i;
break;
case '@guid':
key = guidFor(item);
break;
case '@item':
key = item;
break;
default:
key = keyPath ? get(item, keyPath) : i;
}

if (typeof key === 'number') {
key = String(key);
}

blocks.template.yieldItem(key, [item, i], self);
});
} else if (blocks.inverse.yield) {
Expand Down
82 changes: 82 additions & 0 deletions packages/ember-htmlbars/tests/helpers/each_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1144,5 +1144,87 @@ QUnit.test("context switching deprecation is printed when no items are present",
assertHTML(view, "Nothing");
});

QUnit.test('a string key can be used with {{each}}', function() {
runDestroy(view);
view = EmberView.create({
items: [
{ id: 'foo' },
{ id: 'bar' },
{ id: 'baz' }
],
template: compile("{{#each view.items key='id' as |item|}}{{item.id}}{{/each}}")
});

runAppend(view);

equal(view.$().text(), 'foobarbaz');
});

QUnit.test('a numeric key can be used with {{each}}', function() {
runDestroy(view);
view = EmberView.create({
items: [
{ id: 1 },
{ id: 2 },
{ id: 3 }
],
template: compile("{{#each view.items key='id' as |item|}}{{item.id}}{{/each}}")
});

runAppend(view);

equal(view.$().text(), '123');
});

QUnit.test('can specify `@index` to represent the items index in the array being iterated', function() {
runDestroy(view);
view = EmberView.create({
items: [
{ id: 1 },
{ id: 2 },
{ id: 3 }
],
template: compile("{{#each view.items key='@index' as |item|}}{{item.id}}{{/each}}")
});

runAppend(view);

equal(view.$().text(), '123');
});

QUnit.test('can specify `@guid` to represent the items GUID', function() {
runDestroy(view);
view = EmberView.create({
items: [
{ id: 1 },
{ id: 2 },
{ id: 3 }
],
template: compile("{{#each view.items key='@guid' as |item|}}{{item.id}}{{/each}}")
});

runAppend(view);

equal(view.$().text(), '123');
});

QUnit.test('can specify `@item` to represent primitive items', function() {
runDestroy(view);
view = EmberView.create({
items: [1, 2, 3],
template: compile("{{#each view.items key='@item' as |item|}}{{item}}{{/each}}")
});

runAppend(view);

equal(view.$().text(), '123');

run(function() {
set(view, 'items', ['foo', 'bar', 'baz']);
});

equal(view.$().text(), 'foobarbaz');
});

testEachWithItem("{{#each foo in bar}}", false);
testEachWithItem("{{#each bar as |foo|}}", true);