From 01a710336085930a030cbddccf73a3c0918b2cb0 Mon Sep 17 00:00:00 2001 From: Robert Jackson Date: Mon, 15 Jun 2015 01:03:08 -0400 Subject: [PATCH] [BUGFIX release] Default {{each}} key to @guid or @item based on type. --- packages/ember-htmlbars/lib/helpers/each.js | 7 +--- .../lib/utils/decode-each-key.js | 26 ++++++++++-- .../ember-htmlbars/tests/helpers/each_test.js | 42 ++++++++++++++++++- 3 files changed, 64 insertions(+), 11 deletions(-) diff --git a/packages/ember-htmlbars/lib/helpers/each.js b/packages/ember-htmlbars/lib/helpers/each.js index 21d146c7ab6..fcd22884a8b 100644 --- a/packages/ember-htmlbars/lib/helpers/each.js +++ b/packages/ember-htmlbars/lib/helpers/each.js @@ -51,12 +51,7 @@ import decodeEachKey from "ember-htmlbars/utils/decode-each-key"; 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`). + By default the item's own reference is used. ### {{else}} condition diff --git a/packages/ember-htmlbars/lib/utils/decode-each-key.js b/packages/ember-htmlbars/lib/utils/decode-each-key.js index faa09d81674..06f4be4c85e 100644 --- a/packages/ember-htmlbars/lib/utils/decode-each-key.js +++ b/packages/ember-htmlbars/lib/utils/decode-each-key.js @@ -1,26 +1,42 @@ -import Ember from "ember-metal/core"; +import Ember from 'ember-metal/core'; import { get } from "ember-metal/property_get"; import { guidFor } from "ember-metal/utils"; +function identity(item) { + let key; + let type = typeof item; + + if (type === 'string' || type === 'number') { + key = item; + } else { + key = guidFor(item); + } + + return key; +} export default function decodeEachKey(item, keyPath, index) { - var key; + var key, deprecatedSpecialKey; switch (keyPath) { case '@index': key = index; break; case '@guid': + deprecatedSpecialKey = '@guid'; key = guidFor(item); break; case '@item': + deprecatedSpecialKey = '@item'; key = item; break; + case '@identity': + key = identity(item); + break; default: if (keyPath) { key = get(item, keyPath); } else { - Ember.warn('Using `{{each}}` without specifying a key can lead to unusual behavior. Please specify a `key` that identifies a unique value on each item being iterated. E.g. `{{each model key="@guid" as |item|}}`.'); - key = index; + key = identity(item); } } @@ -28,5 +44,7 @@ export default function decodeEachKey(item, keyPath, index) { key = String(key); } + Ember.deprecate(`Using '${deprecatedSpecialKey}' with the {{each}} helper, is deprecated. Switch to '@identity' or remove 'key=' from your template.`, !deprecatedSpecialKey); + return key; } diff --git a/packages/ember-htmlbars/tests/helpers/each_test.js b/packages/ember-htmlbars/tests/helpers/each_test.js index a12713e1fc7..39b273b3d46 100644 --- a/packages/ember-htmlbars/tests/helpers/each_test.js +++ b/packages/ember-htmlbars/tests/helpers/each_test.js @@ -1226,7 +1226,9 @@ QUnit.test('can specify `@index` to represent the items index in the array being equal(view.$().text(), '123'); }); -QUnit.test('can specify `@guid` to represent the items GUID', function() { +QUnit.test('can specify `@guid` to represent the items GUID [DEPRECATED]', function() { + expectDeprecation(`Using '@guid' with the {{each}} helper, is deprecated. Switch to '@identity' or remove 'key=' from your template.`); + runDestroy(view); view = EmberView.create({ items: [ @@ -1243,6 +1245,8 @@ QUnit.test('can specify `@guid` to represent the items GUID', function() { }); QUnit.test('can specify `@item` to represent primitive items', function() { + expectDeprecation(`Using '@item' with the {{each}} helper, is deprecated. Switch to '@identity' or remove 'key=' from your template.`); + runDestroy(view); view = EmberView.create({ items: [1, 2, 3], @@ -1260,5 +1264,41 @@ QUnit.test('can specify `@item` to represent primitive items', function() { equal(view.$().text(), 'foobarbaz'); }); +QUnit.test('can specify `@identity` to represent primitive items', function() { + runDestroy(view); + view = EmberView.create({ + items: [1, 2, 3], + template: compile("{{#each view.items key='@identity' as |item|}}{{item}}{{/each}}") + }); + + runAppend(view); + + equal(view.$().text(), '123'); + + run(function() { + set(view, 'items', ['foo', 'bar', 'baz']); + }); + + equal(view.$().text(), 'foobarbaz'); +}); + +QUnit.test('can specify `@identity` to represent mixed object and primitive items', function() { + runDestroy(view); + view = EmberView.create({ + items: [1, { id: 2 }, 3], + template: compile("{{#each view.items key='@identity' as |item|}}{{#if item.id}}{{item.id}}{{else}}{{item}}{{/if}}{{/each}}") + }); + + runAppend(view); + + equal(view.$().text(), '123'); + + run(function() { + set(view, 'items', ['foo', { id: 'bar' }, 'baz']); + }); + + equal(view.$().text(), 'foobarbaz'); +}); + testEachWithItem("{{#each foo in bar}}", false); testEachWithItem("{{#each bar as |foo|}}", true);