diff --git a/packages/ember-htmlbars/lib/helpers/each.js b/packages/ember-htmlbars/lib/helpers/each.js
index 53eb7418fa3..cb2912dec87 100644
--- a/packages/ember-htmlbars/lib/helpers/each.js
+++ b/packages/ember-htmlbars/lib/helpers/each.js
@@ -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";
@@ -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}}
@@ -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
@@ -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) {
diff --git a/packages/ember-htmlbars/tests/helpers/each_test.js b/packages/ember-htmlbars/tests/helpers/each_test.js
index e108ac32f1c..947b5681f98 100644
--- a/packages/ember-htmlbars/tests/helpers/each_test.js
+++ b/packages/ember-htmlbars/tests/helpers/each_test.js
@@ -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);