diff --git a/packages/ember-application/lib/system/application.js b/packages/ember-application/lib/system/application.js index c614e253356..8ef1aac6f45 100644 --- a/packages/ember-application/lib/system/application.js +++ b/packages/ember-application/lib/system/application.js @@ -26,7 +26,6 @@ import ApplicationInstance from 'ember-application/system/application-instance'; import TextField from 'ember-views/views/text_field'; import TextArea from 'ember-views/views/text_area'; import Checkbox from 'ember-views/views/checkbox'; -import LegacyEachView from 'ember-views/views/legacy_each_view'; import LinkToComponent from 'ember-routing-views/components/link-to'; import RoutingService from 'ember-routing/services/routing'; import ContainerDebugAdapter from 'ember-extension-support/container_debug_adapter'; @@ -1093,7 +1092,6 @@ Application.reopenClass({ registry.register('component:-text-field', TextField); registry.register('component:-text-area', TextArea); registry.register('component:-checkbox', Checkbox); - registry.register('view:-legacy-each', LegacyEachView); registry.register('component:link-to', LinkToComponent); // Register the routing service... diff --git a/packages/ember-htmlbars/lib/env.js b/packages/ember-htmlbars/lib/env.js index c3343a34e14..9486e1570bd 100644 --- a/packages/ember-htmlbars/lib/env.js +++ b/packages/ember-htmlbars/lib/env.js @@ -79,11 +79,9 @@ import elementComponent from 'ember-htmlbars/keywords/element-component'; import partial from 'ember-htmlbars/keywords/partial'; import input from 'ember-htmlbars/keywords/input'; import textarea from 'ember-htmlbars/keywords/textarea'; -import collection from 'ember-htmlbars/keywords/collection'; import yieldKeyword from 'ember-htmlbars/keywords/yield'; import legacyYield from 'ember-htmlbars/keywords/legacy-yield'; import mut, { privateMut } from 'ember-htmlbars/keywords/mut'; -import each from 'ember-htmlbars/keywords/each'; import readonly from 'ember-htmlbars/keywords/readonly'; import getKeyword from 'ember-htmlbars/keywords/get'; @@ -100,12 +98,10 @@ registerKeyword('yield', yieldKeyword); registerKeyword('legacy-yield', legacyYield); registerKeyword('mut', mut); registerKeyword('@mut', privateMut); -registerKeyword('each', each); registerKeyword('readonly', readonly); registerKeyword('get', getKeyword); if (_Ember.ENV._ENABLE_LEGACY_VIEW_SUPPORT) { - registerKeyword('collection', collection); registerKeyword('view', view); } diff --git a/packages/ember-htmlbars/lib/helpers/-legacy-each-with-controller.js b/packages/ember-htmlbars/lib/helpers/-legacy-each-with-controller.js deleted file mode 100644 index c9210fb6fe0..00000000000 --- a/packages/ember-htmlbars/lib/helpers/-legacy-each-with-controller.js +++ /dev/null @@ -1,38 +0,0 @@ -import { deprecate } from 'ember-metal/debug'; -import { get } from 'ember-metal/property_get'; -import normalizeSelf from 'ember-htmlbars/utils/normalize-self'; -import decodeEachKey from 'ember-htmlbars/utils/decode-each-key'; - -export default function legacyEachWithControllerHelper(params, hash, blocks) { - var list = params[0]; - var keyPath = hash.key; - - // TODO: Correct falsy semantics. - if (!list || get(list, 'length') === 0) { - if (blocks.inverse.yield) { blocks.inverse.yield(); } - return; - } - - list.forEach(function(item, i) { - var self; - - if (blocks.template.arity === 0) { - deprecate(deprecation, false, { id: 'ember-htmlbars.each-with-controller-helper', until: '2.4.0' }); - self = normalizeSelf(item); - self = bindController(self, true); - } - - var key = decodeEachKey(item, keyPath, i); - blocks.template.yieldItem(key, [item, i], self); - }); -} - -function bindController(controller, isSelf) { - return { - controller: controller, - hasBoundController: true, - self: controller ? controller : undefined - }; -} - -export var deprecation = 'Using the context switching form of {{each}} is deprecated. Please use the keyword form (`{{#each items as |item|}}`) instead.'; diff --git a/packages/ember-htmlbars/lib/helpers/-legacy-each-with-keyword.js b/packages/ember-htmlbars/lib/helpers/-legacy-each-with-keyword.js deleted file mode 100644 index f236b56d21a..00000000000 --- a/packages/ember-htmlbars/lib/helpers/-legacy-each-with-keyword.js +++ /dev/null @@ -1,31 +0,0 @@ -import shouldDisplay from 'ember-views/streams/should_display'; -import decodeEachKey from 'ember-htmlbars/utils/decode-each-key'; - -export default function legacyEachWithKeywordHelper(params, hash, blocks) { - var list = params[0]; - var keyPath = hash.key; - var legacyKeyword = hash['-legacy-keyword']; - - if (shouldDisplay(list)) { - list.forEach(function(item, i) { - var self; - if (legacyKeyword) { - self = bindKeyword(self, legacyKeyword, item); - } - - var key = decodeEachKey(item, keyPath, i); - blocks.template.yieldItem(key, [item, i], self); - }); - } else if (blocks.inverse.yield) { - blocks.inverse.yield(); - } -} - -function bindKeyword(self, keyword, item) { - return { - self, - [keyword]: item - }; -} - -export var deprecation = 'Using the context switching form of {{each}} is deprecated. Please use the keyword form (`{{#each items as |item|}}`) instead.'; diff --git a/packages/ember-htmlbars/lib/hooks/create-fresh-scope.js b/packages/ember-htmlbars/lib/hooks/create-fresh-scope.js index d00ba5a3006..82b18a70067 100644 --- a/packages/ember-htmlbars/lib/hooks/create-fresh-scope.js +++ b/packages/ember-htmlbars/lib/hooks/create-fresh-scope.js @@ -25,7 +25,7 @@ import EmptyObject from 'ember-metal/empty_object'; * If `self` is a view, two special locals are created: `view` and `controller`. These locals are legacy semantics. * If self has a `hasBoundController` property, it is coming from - a legacy form of #with or #each + a legacy form of #with (`{{#with something controller=someController}}`). This has the special effect of giving the child scope the supplied `controller` keyword, with an unrelated `self`. This is diff --git a/packages/ember-htmlbars/lib/index.js b/packages/ember-htmlbars/lib/index.js index df4d3c4badd..5238e721082 100644 --- a/packages/ember-htmlbars/lib/index.js +++ b/packages/ember-htmlbars/lib/index.js @@ -120,8 +120,6 @@ import eachInHelper from 'ember-htmlbars/helpers/each-in'; import normalizeClassHelper from 'ember-htmlbars/helpers/-normalize-class'; import concatHelper from 'ember-htmlbars/helpers/concat'; import joinClassesHelper from 'ember-htmlbars/helpers/-join-classes'; -import legacyEachWithControllerHelper from 'ember-htmlbars/helpers/-legacy-each-with-controller'; -import legacyEachWithKeywordHelper from 'ember-htmlbars/helpers/-legacy-each-with-keyword'; import htmlSafeHelper from 'ember-htmlbars/helpers/-html-safe'; import hashHelper from 'ember-htmlbars/helpers/hash'; import DOMHelper from 'ember-htmlbars/system/dom-helper'; @@ -152,11 +150,6 @@ registerHelper('-join-classes', joinClassesHelper); registerHelper('-html-safe', htmlSafeHelper); registerHelper('hash', hashHelper); -if (Ember.ENV._ENABLE_LEGACY_VIEW_SUPPORT) { - registerHelper('-legacy-each-with-controller', legacyEachWithControllerHelper); - registerHelper('-legacy-each-with-keyword', legacyEachWithKeywordHelper); -} - Ember.HTMLBars = { template: template, compile: compile, diff --git a/packages/ember-htmlbars/lib/keywords/collection.js b/packages/ember-htmlbars/lib/keywords/collection.js deleted file mode 100644 index ccb41a3c976..00000000000 --- a/packages/ember-htmlbars/lib/keywords/collection.js +++ /dev/null @@ -1,191 +0,0 @@ -/** -@module ember -@submodule ember-templates -*/ - -import { readViewFactory } from 'ember-views/streams/utils'; -import CollectionView from 'ember-views/views/collection_view'; -import ViewNodeManager from 'ember-htmlbars/node-managers/view-node-manager'; -import assign from 'ember-metal/assign'; - -/** - `{{collection}}` is a template helper for adding instances of - `Ember.CollectionView` to a template. See [Ember.CollectionView](/api/classes/Ember.CollectionView.html) - for additional information on how a `CollectionView` functions. - - `{{collection}}`'s primary use is as a block helper with a `contentBinding` - option pointing towards an `Ember.Array`-compatible object. An `Ember.View` - instance will be created for each item in its `content` property. Each view - will have its own `content` property set to the appropriate item in the - collection. - - The provided block will be applied as the template for each item's view. - - Given an empty `` the following template: - - ```handlebars - {{! application.hbs }} - {{#collection content=model}} - Hi {{view.content.name}} - {{/collection}} - ``` - - And the following application code - - ```javascript - App = Ember.Application.create(); - App.ApplicationRoute = Ember.Route.extend({ - model() { - return [{name: 'Yehuda'},{name: 'Tom'},{name: 'Peter'}]; - } - }); - ``` - - The following HTML will result: - - ```html -
-
Hi Yehuda
-
Hi Tom
-
Hi Peter
-
- ``` - - ### Non-block version of collection - - If you provide an `itemViewClass` option that has its own `template`, - then you may omit the block. - - The following template: - - ```handlebars - {{! application.hbs }} - {{collection content=model itemViewClass="an-item"}} - ``` - - And application code - - ```javascript - App = Ember.Application.create(); - App.ApplicationRoute = Ember.Route.extend({ - model() { - return [{name: 'Yehuda'},{name: 'Tom'},{name: 'Peter'}]; - } - }); - - App.AnItemView = Ember.View.extend({ - template: Ember.Handlebars.compile("Greetings {{view.content.name}}") - }); - ``` - - Will result in the HTML structure below - - ```html -
-
Greetings Yehuda
-
Greetings Tom
-
Greetings Peter
-
- ``` - - ### Specifying a CollectionView subclass - - By default the `{{collection}}` helper will create an instance of - `Ember.CollectionView`. You can supply a `Ember.CollectionView` subclass to - the helper by passing it as the first argument: - - ```handlebars - {{#collection "my-custom-collection" content=model}} - Hi {{view.content.name}} - {{/collection}} - ``` - - This example would look for the class `App.MyCustomCollection`. - - ### Forwarded `item.*`-named Options - - As with the `{{view}}`, helper options passed to the `{{collection}}` will be - set on the resulting `Ember.CollectionView` as properties. Additionally, - options prefixed with `item` will be applied to the views rendered for each - item (note the camelcasing): - - ```handlebars - {{#collection content=model - itemTagName="p" - itemClassNames="greeting"}} - Howdy {{view.content.name}} - {{/collection}} - ``` - - Will result in the following HTML structure: - - ```html -
-

Howdy Yehuda

-

Howdy Tom

-

Howdy Peter

-
- ``` - - @method collection - @for Ember.Templates.helpers - @deprecated Use `{{each}}` helper instead. - @public -*/ -export default { - setupState(state, env, scope, params, hash) { - var read = env.hooks.getValue; - - return assign({}, state, { - parentView: env.view, - viewClassOrInstance: getView(read(params[0]), env.owner) - }); - }, - - rerender(morph, env, scope, params, hash, template, inverse, visitor) { - // If the hash is empty, the component cannot have extracted a part - // of a mutable param and used it in its layout, because there are - // no params at all. - if (Object.keys(hash).length) { - return morph.getState().manager.rerender(env, hash, visitor, true); - } - }, - - render(node, env, scope, params, hash, template, inverse, visitor) { - var state = node.getState(); - var parentView = state.parentView; - - var options = { component: state.viewClassOrInstance, layout: null }; - if (template) { - options.createOptions = { - _itemViewTemplate: template && { raw: template }, - _itemViewInverse: inverse && { raw: inverse } - }; - } - - if (hash.itemView) { - hash.itemViewClass = hash.itemView; - } - - if (hash.emptyView) { - hash.emptyViewClass = hash.emptyView; - } - - var nodeManager = ViewNodeManager.create(node, env, hash, options, parentView, null, scope, template); - state.manager = nodeManager; - - nodeManager.render(env, hash, visitor); - } -}; - -function getView(viewPath, container) { - var viewClassOrInstance; - - if (!viewPath) { - viewClassOrInstance = CollectionView; - } else { - viewClassOrInstance = readViewFactory(viewPath, container); - } - - return viewClassOrInstance; -} diff --git a/packages/ember-htmlbars/lib/keywords/each.js b/packages/ember-htmlbars/lib/keywords/each.js deleted file mode 100644 index 6b5fb2fa357..00000000000 --- a/packages/ember-htmlbars/lib/keywords/each.js +++ /dev/null @@ -1,24 +0,0 @@ -/** -@module ember -@submodule ember-htmlbars -*/ - -export default function each(morph, env, scope, params, hash, template, inverse, visitor) { - let getValue = env.hooks.getValue; - let keyword = hash['-legacy-keyword'] && getValue(hash['-legacy-keyword']); - - /* START: Support of legacy ArrayController. TODO: Remove after 1st 2.0 TLS release */ - let firstParam = params[0] && getValue(params[0]); - if (firstParam && firstParam._isArrayController) { - env.hooks.block(morph, env, scope, '-legacy-each-with-controller', params, hash, template, inverse, visitor); - return true; - } - /* END: Support of legacy ArrayController */ - - if (keyword) { - env.hooks.block(morph, env, scope, '-legacy-each-with-keyword', params, hash, template, inverse, visitor); - return true; - } - - return false; -} diff --git a/packages/ember-htmlbars/lib/templates/legacy-each.hbs b/packages/ember-htmlbars/lib/templates/legacy-each.hbs deleted file mode 100644 index d748541852f..00000000000 --- a/packages/ember-htmlbars/lib/templates/legacy-each.hbs +++ /dev/null @@ -1,21 +0,0 @@ -{{~#each view._arrangedContent -legacy-keyword=view.keyword as |item|~}} - {{~#if view.keyword}} - {{~#if attrs.itemViewClass~}} - {{~#view attrs.itemViewClass _defaultTagName=view._itemTagName~}} - {{~legacy-yield item~}} - {{~/view~}} - {{~else~}} - {{~legacy-yield item~}} - {{~/if~}} - {{~else~}} - {{~#if attrs.itemViewClass~}} - {{~#view attrs.itemViewClass controller=item _defaultTagName=view._itemTagName~}} - {{~legacy-yield item~}} - {{~/view~}} - {{~else~}} - {{~legacy-yield item controller=item~}} - {{~/if~}} - {{~/if~}} -{{~else if view._emptyView~}} - {{~view view._emptyView _defaultTagName=view._itemTagName~}} -{{~/each~}} diff --git a/packages/ember-htmlbars/tests/compat/controller_keyword_test.js b/packages/ember-htmlbars/tests/compat/controller_keyword_test.js index 8bb672faf58..6ddf215b0df 100644 --- a/packages/ember-htmlbars/tests/compat/controller_keyword_test.js +++ b/packages/ember-htmlbars/tests/compat/controller_keyword_test.js @@ -4,7 +4,6 @@ import { runAppend, runDestroy } from 'ember-runtime/tests/utils'; import compile from 'ember-template-compiler/system/compile'; import { registerAstPlugin, removeAstPlugin } from 'ember-htmlbars/tests/utils'; -import TransformEachIntoCollection from 'ember-template-compiler/plugins/transform-each-into-collection'; import AssertNoViewAndControllerPaths from 'ember-template-compiler/plugins/assert-no-view-and-controller-paths'; let component; @@ -38,14 +37,10 @@ QUnit.test('reading the controller keyword fails assertion', function() { QUnit.module('ember-htmlbars: compat - controller keyword (use as a path) [LEGACY]', { setup() { - registerAstPlugin(TransformEachIntoCollection); - component = null; }, teardown() { runDestroy(component); - - removeAstPlugin(TransformEachIntoCollection); } }); diff --git a/packages/ember-htmlbars/tests/helpers/collection_test.js b/packages/ember-htmlbars/tests/helpers/collection_test.js deleted file mode 100644 index be28d3c5b80..00000000000 --- a/packages/ember-htmlbars/tests/helpers/collection_test.js +++ /dev/null @@ -1,649 +0,0 @@ -import Ember from 'ember-metal/core'; -import { get } from 'ember-metal/property_get'; -import { set } from 'ember-metal/property_set'; -import run from 'ember-metal/run_loop'; -import { computed } from 'ember-metal/computed'; -import EmberObject from 'ember-runtime/system/object'; -import ArrayProxy from 'ember-runtime/system/array_proxy'; -import Namespace from 'ember-runtime/system/namespace'; -import { A as emberA } from 'ember-runtime/system/native_array'; -import { runAppend, runDestroy } from 'ember-runtime/tests/utils'; -import CollectionView from 'ember-views/views/collection_view'; -import EmberView from 'ember-views/views/view'; -import jQuery from 'ember-views/system/jquery'; -import compile from 'ember-template-compiler/system/compile'; - -import buildOwner from 'container/tests/test-helpers/build-owner'; -import { OWNER } from 'container/owner'; - -import { registerKeyword, resetKeyword } from 'ember-htmlbars/tests/utils'; -import viewKeyword from 'ember-htmlbars/keywords/view'; - -import { objectAt } from 'ember-runtime/mixins/array'; - -var trim = jQuery.trim; - -var view; - -var originalLookup = Ember.lookup; -var TemplateTests, owner, lookup, originalViewKeyword; - - -function nthChild(view, nth) { - return objectAt(get(view, 'childViews'), nth || 0); -} - -var firstChild = nthChild; - -function firstGrandchild(view) { - return objectAt(get(objectAt(get(view, 'childViews'), 0), 'childViews'), 0); -} - -QUnit.module('collection helper [LEGACY]', { - setup() { - originalViewKeyword = registerKeyword('view', viewKeyword); - - Ember.lookup = lookup = {}; - lookup.TemplateTests = TemplateTests = Namespace.create(); - owner = buildOwner(); - - owner.registerOptionsForType('template', { instantiate: false }); - owner.register('view:toplevel', EmberView.extend()); - }, - - teardown() { - runDestroy(owner); - runDestroy(view); - owner = view = null; - - Ember.lookup = lookup = originalLookup; - TemplateTests = null; - - resetKeyword('view', originalViewKeyword); - } -}); - -QUnit.test('Collection views that specify an example view class have their children be of that class', function() { - var ExampleViewCollection = CollectionView.extend({ - itemViewClass: EmberView.extend({ - isCustom: true - }), - - content: emberA(['foo']) - }); - - view = EmberView.create({ - exampleViewCollection: ExampleViewCollection, - template: compile('{{#collection view.exampleViewCollection}}OHAI{{/collection}}') - }); - - runAppend(view); - - ok(firstGrandchild(view).isCustom, 'uses the example view class'); -}); - -QUnit.test('itemViewClass works in the #collection helper with a property', function() { - var ExampleItemView = EmberView.extend({ - isAlsoCustom: true - }); - - var ExampleCollectionView = CollectionView; - - view = EmberView.create({ - possibleItemView: ExampleItemView, - exampleCollectionView: ExampleCollectionView, - exampleController: ArrayProxy.create({ - content: emberA(['alpha']) - }), - template: compile('{{#collection view.exampleCollectionView content=view.exampleController itemViewClass=view.possibleItemView}}beta{{/collection}}') - }); - - runAppend(view); - - ok(firstGrandchild(view).isAlsoCustom, 'uses the example view class specified in the #collection helper'); -}); - -QUnit.test('itemViewClass works in the #collection via container', function() { - owner.register('view:example-item', EmberView.extend({ - isAlsoCustom: true - })); - - view = EmberView.create({ - [OWNER]: owner, - exampleCollectionView: CollectionView.extend(), - exampleController: ArrayProxy.create({ - content: emberA(['alpha']) - }), - template: compile('{{#collection view.exampleCollectionView content=view.exampleController itemViewClass="example-item"}}beta{{/collection}}') - }); - - runAppend(view); - - ok(firstGrandchild(view).isAlsoCustom, 'uses the example view class specified in the #collection helper'); -}); - - -QUnit.test('passing a block to the collection helper sets it as the template for example views', function() { - var CollectionTestView = CollectionView.extend({ - tagName: 'ul', - content: emberA(['foo', 'bar', 'baz']) - }); - - view = EmberView.create({ - collectionTestView: CollectionTestView, - template: compile('{{#collection view.collectionTestView}} {{/collection}}') - }); - - runAppend(view); - - equal(view.$('label').length, 3, 'one label element is created for each content item'); -}); - -QUnit.test('collection helper should try to use container to resolve view', function() { - var ACollectionView = CollectionView.extend({ - tagName: 'ul', - content: emberA(['foo', 'bar', 'baz']) - }); - - owner.register('view:collectionTest', ACollectionView); - - view = EmberView.create({ - [OWNER]: owner, - template: compile('{{#collection "collectionTest"}} {{/collection}}') - }); - - runAppend(view); - - equal(view.$('label').length, 3, 'one label element is created for each content item'); -}); - -QUnit.test('collection helper should accept relative paths', function() { - view = EmberView.create({ - template: compile('{{#collection view.collection}} {{/collection}}'), - collection: CollectionView.extend({ - tagName: 'ul', - content: emberA(['foo', 'bar', 'baz']) - }) - }); - - runAppend(view); - - equal(view.$('label').length, 3, 'one label element is created for each content item'); -}); - -QUnit.test('empty views should be removed when content is added to the collection (regression, ht: msofaer)', function() { - var EmptyView = EmberView.extend({ - template : compile('No Rows Yet') - }); - - var ListView = CollectionView.extend({ - emptyView: EmptyView - }); - - var listController = ArrayProxy.create({ - content : emberA() - }); - - view = EmberView.create({ - _viewRegistry: {}, - listView: ListView, - listController: listController, - template: compile('{{#collection view.listView content=view.listController tagName="table"}} {{view.content.title}} {{/collection}}') - }); - - runAppend(view); - - equal(view.$('tr').length, 1, 'Make sure the empty view is there (regression)'); - - run(function() { - listController.pushObject({ title : 'Go Away, Placeholder Row!' }); - }); - - equal(view.$('tr').length, 1, 'has one row'); - equal(view.$('tr:nth-child(1) td').text(), 'Go Away, Placeholder Row!', 'The content is the updated data.'); -}); - -QUnit.test('should be able to specify which class should be used for the empty view', function() { - var App; - - run(function() { - lookup.App = App = Namespace.create(); - }); - - var EmptyView = EmberView.extend({ - template: compile('This is an empty view') - }); - - owner.register('view:empty-view', EmptyView); - - view = EmberView.create({ - [OWNER]: owner, - template: compile('{{collection emptyViewClass="empty-view"}}') - }); - - runAppend(view); - - equal(view.$().text(), 'This is an empty view', 'Empty view should be rendered.'); - - runDestroy(App); -}); - -QUnit.test('if no content is passed, and no \'else\' is specified, nothing is rendered', function() { - var CollectionTestView = CollectionView.extend({ - tagName: 'ul', - content: emberA() - }); - - view = EmberView.create({ - collectionTestView: CollectionTestView, - template: compile('{{#collection view.collectionTestView}} {{/collection}}') - }); - - runAppend(view); - - equal(view.$('li').length, 0, 'if no "else" is specified, nothing is rendered'); -}); - -QUnit.test('if no content is passed, and \'else\' is specified, the else block is rendered', function() { - var CollectionTestView = CollectionView.extend({ - tagName: 'ul', - content: emberA() - }); - - view = EmberView.create({ - collectionTestView: CollectionTestView, - template: compile('{{#collection view.collectionTestView}} {{ else }} {{/collection}}') - }); - - runAppend(view); - - equal(view.$('li:has(del)').length, 1, 'the else block is rendered'); -}); - -QUnit.test('a block passed to a collection helper defaults to the content property of the context', function() { - var CollectionTestView = CollectionView.extend({ - tagName: 'ul', - content: emberA(['foo', 'bar', 'baz']) - }); - - view = EmberView.create({ - collectionTestView: CollectionTestView, - template: compile('{{#collection view.collectionTestView}} {{/collection}}') - }); - - runAppend(view); - - equal(view.$('li:nth-child(1) label').length, 1); - equal(view.$('li:nth-child(1) label').text(), 'foo'); - equal(view.$('li:nth-child(2) label').length, 1); - equal(view.$('li:nth-child(2) label').text(), 'bar'); - equal(view.$('li:nth-child(3) label').length, 1); - equal(view.$('li:nth-child(3) label').text(), 'baz'); -}); - -QUnit.test('a block passed to a collection helper defaults to the view', function() { - var CollectionTestView = CollectionView.extend({ - tagName: 'ul', - content: emberA(['foo', 'bar', 'baz']) - }); - - view = EmberView.create({ - collectionTestView: CollectionTestView, - template: compile('{{#collection view.collectionTestView}} {{/collection}}') - }); - - runAppend(view); - - // Preconds - equal(view.$('li:nth-child(1) label').length, 1); - equal(view.$('li:nth-child(1) label').text(), 'foo'); - equal(view.$('li:nth-child(2) label').length, 1); - equal(view.$('li:nth-child(2) label').text(), 'bar'); - equal(view.$('li:nth-child(3) label').length, 1); - equal(view.$('li:nth-child(3) label').text(), 'baz'); - - run(function() { - set(firstChild(view), 'content', emberA()); - }); - equal(view.$('label').length, 0, 'all list item views should be removed from DOM'); -}); - -QUnit.test('should include an id attribute if id is set in the options hash', function() { - var CollectionTestView = CollectionView.extend({ - tagName: 'ul', - content: emberA(['foo', 'bar', 'baz']) - }); - - view = EmberView.create({ - collectionTestView: CollectionTestView, - template: compile('{{#collection view.collectionTestView id="baz"}}foo{{/collection}}') - }); - - runAppend(view); - - equal(view.$('ul#baz').length, 1, 'adds an id attribute'); -}); - -QUnit.test('should give its item views the class specified by itemClass', function() { - var ItemClassTestCollectionView = CollectionView.extend({ - tagName: 'ul', - content: emberA(['foo', 'bar', 'baz']) - }); - view = EmberView.create({ - itemClassTestCollectionView: ItemClassTestCollectionView, - template: compile('{{#collection view.itemClassTestCollectionView itemClass="baz"}}foo{{/collection}}') - }); - - runAppend(view); - - equal(view.$('ul li.baz').length, 3, 'adds class attribute'); -}); - -QUnit.test('should give its item views the class specified by itemClass binding', function() { - var ItemClassBindingTestCollectionView = CollectionView.extend({ - tagName: 'ul', - content: emberA([EmberObject.create({ isBaz: false }), EmberObject.create({ isBaz: true }), EmberObject.create({ isBaz: true })]) - }); - - view = EmberView.create({ - itemClassBindingTestCollectionView: ItemClassBindingTestCollectionView, - isBar: true, - template: compile('{{#collection view.itemClassBindingTestCollectionView itemClass=view.isBar}}foo{{/collection}}') - }); - - runAppend(view); - - equal(view.$('ul li.is-bar').length, 3, 'adds class on initial rendering'); - - // NOTE: in order to bind an item's class to a property of the item itself (e.g. `isBaz` above), it will be necessary - // to introduce a new keyword that could be used from within `itemClassBinding`. For instance, `itemClassBinding="item.isBaz"`. -}); - -QUnit.test('should give its item views the property specified by itemProperty', function() { - var ItemPropertyBindingTestItemView = EmberView.extend({ - tagName: 'li' - }); - - owner.register('view:item-property-binding-test-item-view', ItemPropertyBindingTestItemView); - - // Use preserveContext=false so the itemView handlebars context is the view context - // Set itemView bindings using item* - view = EmberView.create({ - [OWNER]: owner, - baz: 'baz', - content: emberA([EmberObject.create(), EmberObject.create(), EmberObject.create()]), - template: compile('{{#collection content=view.content tagName="ul" itemViewClass="item-property-binding-test-item-view" itemProperty=view.baz preserveContext=false}}{{view.property}}{{/collection}}') - }); - - runAppend(view); - - equal(view.$('ul li').length, 3, 'adds 3 itemView'); - - view.$('ul li').each(function(i, li) { - equal(jQuery(li).text(), 'baz', 'creates the li with the property = baz'); - }); - - run(function() { - set(view, 'baz', 'yobaz'); - }); - - equal(view.$('ul li:first').text(), 'yobaz', 'change property of sub view'); -}); - -QUnit.test('should work inside a bound {{#if}}', function() { - var testData = emberA([EmberObject.create({ isBaz: false }), EmberObject.create({ isBaz: true }), EmberObject.create({ isBaz: true })]); - var IfTestCollectionView = CollectionView.extend({ - tagName: 'ul', - content: testData - }); - - view = EmberView.create({ - ifTestCollectionView: IfTestCollectionView, - template: compile('{{#if view.shouldDisplay}}{{#collection view.ifTestCollectionView}}{{content.isBaz}}{{/collection}}{{/if}}'), - shouldDisplay: true - }); - - runAppend(view); - - equal(view.$('ul li').length, 3, 'renders collection when conditional is true'); - - run(function() { set(view, 'shouldDisplay', false); }); - equal(view.$('ul li').length, 0, 'removes collection when conditional changes to false'); - - run(function() { set(view, 'shouldDisplay', true); }); - equal(view.$('ul li').length, 3, 'collection renders when conditional changes to true'); -}); - -QUnit.test('should pass content as context when using {{#each}} helper [DEPRECATED]', function() { - view = EmberView.create({ - template: compile('{{#each view.releases as |release|}}Mac OS X {{release.version}}: {{release.name}} {{/each}}'), - - releases: emberA([ - { version: '10.7', - name: 'Lion' }, - { version: '10.6', - name: 'Snow Leopard' }, - { version: '10.5', - name: 'Leopard' } - ]) - }); - - runAppend(view); - - equal(view.$().text(), 'Mac OS X 10.7: Lion Mac OS X 10.6: Snow Leopard Mac OS X 10.5: Leopard ', 'prints each item in sequence'); -}); - -QUnit.test('should re-render when the content object changes', function() { - var RerenderTest = CollectionView.extend({ - tagName: 'ul', - content: emberA() - }); - - view = EmberView.create({ - rerenderTestView: RerenderTest, - template: compile('{{#collection view.rerenderTestView}}{{view.content}}{{/collection}}') - }); - - runAppend(view); - - run(function() { - set(firstChild(view), 'content', emberA(['bing', 'bat', 'bang'])); - }); - - run(function() { - set(firstChild(view), 'content', emberA(['ramalamadingdong'])); - }); - - equal(view.$('li').length, 1, 'rerenders with correct number of items'); - equal(trim(view.$('li:eq(0)').text()), 'ramalamadingdong'); -}); - -QUnit.test('select tagName on collection helper automatically sets child tagName to option', function() { - var RerenderTest = CollectionView.extend({ - content: emberA(['foo']) - }); - - view = EmberView.create({ - rerenderTestView: RerenderTest, - template: compile('{{#collection view.rerenderTestView tagName="select"}}{{view.content}}{{/collection}}') - }); - - runAppend(view); - - equal(view.$('option').length, 1, 'renders the correct child tag name'); -}); - -QUnit.test('tagName works in the #collection helper', function() { - var RerenderTest = CollectionView.extend({ - content: emberA(['foo', 'bar']) - }); - - view = EmberView.create({ - rerenderTestView: RerenderTest, - template: compile('{{#collection view.rerenderTestView tagName="ol"}}{{view.content}}{{/collection}}') - }); - - runAppend(view); - - equal(view.$('ol').length, 1, 'renders the correct tag name'); - equal(view.$('li').length, 2, 'rerenders with correct number of items'); - - run(function() { - set(firstChild(view), 'content', emberA(['bing', 'bat', 'bang'])); - }); - - equal(view.$('li').length, 3, 'rerenders with correct number of items'); - equal(trim(view.$('li:eq(0)').text()), 'bing'); -}); - -QUnit.test('itemClassNames adds classes to items', function() { - view = EmberView.create({ - context: { list: emberA(['one', 'two']) }, - template: compile('{{#collection content=list itemClassNames="some-class"}}{{/collection}}') - }); - - runAppend(view); - - equal(view.$('div > .some-class').length, 2, 'should have two items with the class'); -}); - -QUnit.test('should render nested collections', function() { - owner.register('view:inner-list', CollectionView.extend({ - tagName: 'ul', - content: emberA(['one', 'two', 'three']) - })); - - owner.register('view:outer-list', CollectionView.extend({ - tagName: 'ul', - content: emberA(['foo']) - })); - - view = EmberView.create({ - [OWNER]: owner, - template: compile('{{#collection "outer-list" class="outer"}}{{content}}{{#collection "inner-list" class="inner"}}{{content}}{{/collection}}{{/collection}}') - }); - - runAppend(view); - - equal(view.$('ul.outer > li').length, 1, 'renders the outer list with correct number of items'); - equal(view.$('ul.inner').length, 1, 'the inner list exsits'); - equal(view.$('ul.inner > li').length, 3, 'renders the inner list with correct number of items'); -}); - -QUnit.test('should render multiple, bound nested collections (#68)', function() { - var view; - - run(function() { - TemplateTests.contentController = ArrayProxy.create({ - content: emberA(['foo', 'bar']) - }); - - var InnerList = CollectionView.extend({ - tagName: 'ul', - contentBinding: 'parentView.innerListContent' - }); - - var OuterListItem = EmberView.extend({ - innerListView: InnerList, - template: compile('{{#collection view.innerListView class="inner"}}{{content}}{{/collection}}{{content}}'), - innerListContent: computed(function() { - return emberA([1, 2, 3]); - }) - }); - - var OuterList = CollectionView.extend({ - tagName: 'ul', - contentBinding: 'TemplateTests.contentController', - itemViewClass: OuterListItem - }); - - view = EmberView.create({ - outerListView: OuterList, - template: compile('{{collection view.outerListView class="outer"}}') - }); - }); - - runAppend(view); - - equal(view.$('ul.outer > li').length, 2, 'renders the outer list with correct number of items'); - equal(view.$('ul.inner').length, 2, 'renders the correct number of inner lists'); - equal(view.$('ul.inner:first > li').length, 3, 'renders the first inner list with correct number of items'); - equal(view.$('ul.inner:last > li').length, 3, 'renders the second list with correct number of items'); - - runDestroy(view); -}); - -QUnit.test('should allow view objects to be swapped out without throwing an error (#78)', function() { - var view, dataset, secondDataset; - - run(function() { - TemplateTests.datasetController = EmberObject.create(); - - var ExampleCollectionView = CollectionView.extend({ - contentBinding: 'parentView.items', - tagName: 'ul', - _itemViewTemplate: compile('{{view.content}}') - }); - - var ReportingView = EmberView.extend({ - exampleCollectionView: ExampleCollectionView, - datasetBinding: 'TemplateTests.datasetController.dataset', - readyBinding: 'dataset.ready', - itemsBinding: 'dataset.items', - template: compile('{{#if view.ready}}{{collection view.exampleCollectionView}}{{else}}Loading{{/if}}') - }); - - view = ReportingView.create(); - }); - - runAppend(view); - - equal(view.$().text(), 'Loading', 'renders the loading text when the dataset is not ready'); - - run(function() { - dataset = EmberObject.create({ - ready: true, - items: emberA([1, 2, 3]) - }); - TemplateTests.datasetController.set('dataset', dataset); - }); - - equal(view.$('ul > li').length, 3, 'renders the collection with the correct number of items when the dataset is ready'); - - run(function() { - secondDataset = EmberObject.create({ ready: false }); - TemplateTests.datasetController.set('dataset', secondDataset); - }); - - equal(view.$().text(), 'Loading', 'renders the loading text when the second dataset is not ready'); - - runDestroy(view); -}); - -QUnit.test('context should be content', function() { - var view; - - var items = emberA([ - EmberObject.create({ name: 'Dave' }), - EmberObject.create({ name: 'Mary' }), - EmberObject.create({ name: 'Sara' }) - ]); - - owner.register('view:an-item', EmberView.extend({ - template: compile('Greetings {{name}}') - })); - - view = EmberView.create({ - [OWNER]: owner, - controller: { - items: items - }, - template: compile('{{collection content=items itemViewClass="an-item"}}') - }); - - runAppend(view); - - equal(view.$().text(), 'Greetings DaveGreetings MaryGreetings Sara'); - - runDestroy(view); -}); diff --git a/packages/ember-htmlbars/tests/helpers/each_test.js b/packages/ember-htmlbars/tests/helpers/each_test.js index 17990ddc23b..d59635f70eb 100644 --- a/packages/ember-htmlbars/tests/helpers/each_test.js +++ b/packages/ember-htmlbars/tests/helpers/each_test.js @@ -2,7 +2,6 @@ import Ember from 'ember-metal/core'; // Ember.lookup; import EmberObject from 'ember-runtime/system/object'; import run from 'ember-metal/run_loop'; import EmberView from 'ember-views/views/view'; -import LegacyEachView from 'ember-views/views/legacy_each_view'; import { A as emberA } from 'ember-runtime/system/native_array'; import EmberController from 'ember-runtime/controllers/controller'; @@ -11,9 +10,6 @@ import { runAppend, runDestroy } from 'ember-runtime/tests/utils'; import compile from 'ember-template-compiler/system/compile'; -import { registerAstPlugin, removeAstPlugin } from 'ember-htmlbars/tests/utils'; -import TransformEachIntoCollection from 'ember-template-compiler/plugins/transform-each-into-collection'; - import { registerKeyword, resetKeyword } from 'ember-htmlbars/tests/utils'; import viewKeyword from 'ember-htmlbars/keywords/view'; @@ -33,15 +29,12 @@ QUnit.module('the #each helper', { originalViewKeyword = registerKeyword('view', viewKeyword); - registerAstPlugin(TransformEachIntoCollection); - template = compile('{{#each view.people as |person|}}{{person.name}}{{/each}}'); people = emberA([{ name: 'Steve Holt' }, { name: 'Annabelle' }]); owner = buildOwner(); owner.register('view:toplevel', EmberView.extend()); - owner.register('view:-legacy-each', LegacyEachView); view = EmberView.create({ [OWNER]: owner, @@ -69,8 +62,6 @@ QUnit.module('the #each helper', { Ember.lookup = originalLookup; - removeAstPlugin(TransformEachIntoCollection); - resetKeyword('view', originalViewKeyword); } }); @@ -84,10 +75,6 @@ var assertHTML = function(view, expectedHTML) { equal(html, expectedHTML); }; -var assertText = function(view, expectedText) { - equal(view.$().text(), expectedText); -}; - QUnit.test('it renders the template for each item in an array', function() { assertHTML(view, 'Steve HoltAnnabelle'); }); @@ -247,207 +234,6 @@ QUnit.test('it works inside a table element', function() { runDestroy(tableView); }); -QUnit.test('it supports {{itemView=}}', function() { - runDestroy(view); - var itemView = EmberView.extend({ - template: compile('itemView:{{name}}') - }); - - expectDeprecation(() => { - view = EmberView.create({ - [OWNER]: owner, - template: compile('{{each view.people itemView="anItemView"}}'), - people: people - }); - }, /Using 'itemView' with '{{each}}'/); - - owner.register('view:anItemView', itemView); - - runAppend(view); - - assertText(view, 'itemView:Steve HoltitemView:Annabelle'); -}); - -QUnit.test('it defers all normalization of itemView names to the resolver', function() { - runDestroy(view); - var itemView = EmberView.extend({ - template: compile('itemView:{{name}}') - }); - - expectDeprecation(() => { - view = EmberView.create({ - [OWNER]: owner, - template: compile('{{each view.people itemView="an-item-view"}}'), - people: people - }); - }, /Using 'itemView' with '{{each}}'/); - - owner.register('view:an-item-view', itemView); - runAppend(view); - - assertText(view, 'itemView:Steve HoltitemView:Annabelle'); -}); - -QUnit.test('it supports {{itemViewClass=}} via owner', function() { - runDestroy(view); - expectDeprecation(() => { - view = EmberView.create({ - [OWNER]: owner, - template: compile('{{each view.people itemViewClass="my-view"}}'), - people: people - }); - }, /Using 'itemViewClass' with '{{each}}'/); - - runAppend(view); - - assertText(view, 'Steve HoltAnnabelle'); -}); - -QUnit.test('it supports {{itemViewClass=}} with each view tagName (DEPRECATED)', function() { - runDestroy(view); - expectDeprecation(() => { - view = EmberView.create({ - [OWNER]: owner, - template: compile('{{each view.people itemViewClass="my-view" tagName="ul"}}'), - people: people - }); - }, /Using 'itemViewClass' with '{{each}}'/); - - runAppend(view); - equal(view.$('ul').length, 1, 'rendered ul tag'); - equal(view.$('ul li').length, 2, 'rendered 2 li tags'); - equal(view.$('ul li').text(), 'Steve HoltAnnabelle'); -}); - -QUnit.test('it supports {{itemViewClass=}} with tagName in itemViewClass (DEPRECATED)', function() { - runDestroy(view); - owner.register('view:li-view', EmberView.extend({ - tagName: 'li' - })); - - expectDeprecation(() => { - view = EmberView.create({ - [OWNER]: owner, - template: compile(''), - people: people - }); - }, /Using 'itemViewClass' with '{{each}}'/); - - runAppend(view); - - equal(view.$('ul').length, 1, 'rendered ul tag'); - equal(view.$('ul li').length, 2, 'rendered 2 li tags'); - equal(view.$('ul li').text(), 'Steve HoltAnnabelle'); -}); - -QUnit.test('it supports {{itemViewClass=}} with {{else}} block (DEPRECATED)', function() { - runDestroy(view); - expectDeprecation(() => { - view = EmberView.create({ - [OWNER]: owner, - template: compile(` - {{~#each view.people itemViewClass="my-view" as |item|~}} - {{item.name}} - {{~else~}} - No records! - {{~/each}}`), - people: emberA() - }); - }, /Using 'itemViewClass' with '{{each}}'/); - - runAppend(view); - - equal(view.$().text(), 'No records!'); -}); - -QUnit.test('it supports {{emptyView=}}', function() { - runDestroy(view); - var emptyView = EmberView.extend({ - template: compile('emptyView:sad panda') - }); - - expectDeprecation(() => { - view = EmberView.create({ - [OWNER]: owner, - template: compile('{{each view.people emptyView="anEmptyView"}}'), - people: emberA() - }); - }, /Using 'emptyView' with '{{each}}'/); - - owner.register('view:anEmptyView', emptyView); - - runAppend(view); - - assertText(view, 'emptyView:sad panda'); -}); - -QUnit.test('it defers all normalization of emptyView names to the resolver', function() { - runDestroy(view); - var emptyView = EmberView.extend({ - template: compile('emptyView:sad panda') - }); - - expectDeprecation(() => { - view = EmberView.create({ - [OWNER]: owner, - template: compile('{{each view.people emptyView="an-empty-view"}}'), - people: emberA() - }); - }, /Using 'emptyView' with '{{each}}'/); - - owner.register('view:an-empty-view', emptyView); - - runAppend(view); - - assertText(view, 'emptyView:sad panda'); -}); - -QUnit.test('it supports {{emptyViewClass=}} via owner', function() { - runDestroy(view); - expectDeprecation(() => { - view = EmberView.create({ - [OWNER]: owner, - template: compile('{{each view.people emptyViewClass="my-empty-view"}}'), - people: emberA() - }); - }, /Using 'emptyViewClass' with '{{each}}'/); - - runAppend(view); - - assertText(view, 'I\'m empty'); -}); - -QUnit.test('it supports {{emptyViewClass=}} with tagName (DEPRECATED)', function() { - runDestroy(view); - expectDeprecation(() => { - view = EmberView.create({ - [OWNER]: owner, - template: compile('{{each view.people emptyViewClass="my-empty-view" tagName="b"}}'), - people: emberA() - }); - }, /Using 'emptyViewClass' with '{{each}}'/); - - runAppend(view); - - equal(view.$('b').length, 1, 'rendered b tag'); - equal(view.$('b').text(), 'I\'m empty'); -}); - -QUnit.test('it supports {{emptyViewClass=}} with in format', function() { - runDestroy(view); - expectDeprecation(() => { - view = EmberView.create({ - [OWNER]: owner, - template: compile('{{each person in view.people emptyViewClass="my-empty-view"}}'), - people: emberA() - }); - }, /Using 'emptyViewClass' with '{{each}}'/); - - runAppend(view); - - assertText(view, 'I\'m empty'); -}); - QUnit.test('it uses {{else}} when replacing model with an empty array', function() { runDestroy(view); view = EmberView.create({ @@ -525,7 +311,6 @@ QUnit.module('{{each bar as |foo|}}', { setup() { owner = buildOwner(); owner.register('view:toplevel', EmberView.extend()); - owner.register('view:-legacy-each', LegacyEachView); }, teardown() { runDestroy(owner); diff --git a/packages/ember-metal/lib/run_loop.js b/packages/ember-metal/lib/run_loop.js index beac03ca2ee..a802df0492e 100644 --- a/packages/ember-metal/lib/run_loop.js +++ b/packages/ember-metal/lib/run_loop.js @@ -423,17 +423,21 @@ run.scheduleOnce = function(/*queue, target, method*/) { after all DOM element operations have completed within the current run loop, you can make use of the `afterRender` run loop queue (added by the `ember-views` package, along with the preceding `render` queue - where all the DOM element operations happen). Example: + where all the DOM element operations happen). + + Example: ```javascript - App.MyCollectionView = Ember.CollectionView.extend({ - didInsertElement: function() { + export default Ember.Component.extend({ + didInsertElement() { + this._super(...arguments); run.scheduleOnce('afterRender', this, 'processChildElements'); }, - processChildElements: function() { - // ... do something with collectionView's child view + + processChildElements() { + // ... do something with component's child component // elements after they've finished rendering, which - // can't be done within the CollectionView's + // can't be done within this component's // `didInsertElement` hook because that gets run // before the child elements have been added to the DOM. } diff --git a/packages/ember-runtime/lib/system/native_array.js b/packages/ember-runtime/lib/system/native_array.js index 693446bccc5..5c576d9b6f2 100644 --- a/packages/ember-runtime/lib/system/native_array.js +++ b/packages/ember-runtime/lib/system/native_array.js @@ -114,13 +114,16 @@ NativeArray = NativeArray.without.apply(NativeArray, ignore); Example + TODO: Update example to not use CollectionView + ```js - var Pagination = Ember.CollectionView.extend({ + export default Ember.Component.extend({ tagName: 'ul', classNames: ['pagination'], - init: function() { + init() { this._super(...arguments); + if (!this.get('content')) { this.set('content', Ember.A()); } diff --git a/packages/ember-template-compiler/lib/index.js b/packages/ember-template-compiler/lib/index.js index 5ced304236b..10eb52a36d2 100644 --- a/packages/ember-template-compiler/lib/index.js +++ b/packages/ember-template-compiler/lib/index.js @@ -12,7 +12,6 @@ import TransformComponentCurlyToReadonly from 'ember-template-compiler/plugins/t import TransformAngleBracketComponents from 'ember-template-compiler/plugins/transform-angle-bracket-components'; import TransformInputOnToOnEvent from 'ember-template-compiler/plugins/transform-input-on-to-onEvent'; import TransformTopLevelComponents from 'ember-template-compiler/plugins/transform-top-level-components'; -import TransformEachIntoCollection from 'ember-template-compiler/plugins/transform-each-into-collection'; import TransformUnescapedInlineLinkTo from 'ember-template-compiler/plugins/transform-unescaped-inline-link-to'; import AssertNoViewAndControllerPaths from 'ember-template-compiler/plugins/assert-no-view-and-controller-paths'; import AssertNoViewHelper from 'ember-template-compiler/plugins/assert-no-view-helper'; @@ -32,9 +31,7 @@ registerPlugin('ast', TransformTopLevelComponents); registerPlugin('ast', TransformUnescapedInlineLinkTo); registerPlugin('ast', AssertNoEachIn); -if (_Ember.ENV._ENABLE_LEGACY_VIEW_SUPPORT) { - registerPlugin('ast', TransformEachIntoCollection); -} else { +if (!_Ember.ENV._ENABLE_LEGACY_VIEW_SUPPORT) { registerPlugin('ast', AssertNoViewAndControllerPaths); registerPlugin('ast', AssertNoViewHelper); } diff --git a/packages/ember-template-compiler/lib/plugins/transform-each-into-collection.js b/packages/ember-template-compiler/lib/plugins/transform-each-into-collection.js deleted file mode 100644 index 28aaff99e48..00000000000 --- a/packages/ember-template-compiler/lib/plugins/transform-each-into-collection.js +++ /dev/null @@ -1,65 +0,0 @@ -import { deprecate } from 'ember-metal/debug'; -import calculateLocationDisplay from 'ember-template-compiler/system/calculate-location-display'; - -export default function TransformEachIntoCollection(options) { - this.options = options; - this.syntax = null; -} - -TransformEachIntoCollection.prototype.transform = function TransformEachIntoCollection_transform(ast) { - var moduleName = this.options.moduleName; - var b = this.syntax.builders; - var walker = new this.syntax.Walker(); - - walker.visit(ast, function(node) { - let legacyHashKey = validate(node); - if (!legacyHashKey) { return; } - - let moduleInfo = calculateLocationDisplay(moduleName, legacyHashKey.loc); - - deprecate( - `Using '${legacyHashKey.key}' with '{{each}}' ${moduleInfo}is deprecated. Please refactor to a component.`, - false, - { id: 'ember-template-compiler.transform-each-into-collection', until: '2.0.0' } - ); - - let list = node.params.shift(); - node.path = b.path('collection'); - - node.params.unshift(b.string('-legacy-each')); - - let pair = b.pair('content', list); - pair.loc = list.loc; - - node.hash.pairs.push(pair); - - //pair = b.pair('dataSource', list); - //node.hash.pairs.push(pair); - }); - - return ast; -}; - -function validate(node) { - if ((node.type === 'BlockStatement' || node.type === 'MustacheStatement') && node.path.original === 'each') { - return any(node.hash.pairs, pair => { - let key = pair.key; - return key === 'itemController' || - key === 'itemView' || - key === 'itemViewClass' || - key === 'tagName' || - key === 'emptyView' || - key === 'emptyViewClass'; - }); - } - - return false; -} - -function any(list, predicate) { - for (var i = 0, l = list.length; i < l; i++) { - if (predicate(list[i])) { return list[i]; } - } - - return false; -} diff --git a/packages/ember-template-compiler/tests/plugins/transform-each-into-collection-test.js b/packages/ember-template-compiler/tests/plugins/transform-each-into-collection-test.js deleted file mode 100644 index e14ba782700..00000000000 --- a/packages/ember-template-compiler/tests/plugins/transform-each-into-collection-test.js +++ /dev/null @@ -1,46 +0,0 @@ -import { compile } from 'ember-template-compiler'; - -import { registerAstPlugin, removeAstPlugin } from 'ember-htmlbars/tests/utils'; -import TransformEachIntoCollection from 'ember-template-compiler/plugins/transform-each-into-collection'; - -QUnit.module('ember-template-compiler: transform-each-into-collection', { - setup() { - registerAstPlugin(TransformEachIntoCollection); - }, - teardown() { - removeAstPlugin(TransformEachIntoCollection); - } -}); - -let deprecatedAttrs = ['itemController', 'itemView', 'itemViewClass', 'tagName', 'emptyView', 'emptyViewClass']; - -function testBlockForm(attr) { - QUnit.test(`Using the '${attr}' hash argument with a block results in a deprecation`, function() { - expect(1); - - expectDeprecation(function() { - compile(`\n\n {{#each model ${attr}="foo" as |item|}}{{item}}{{/each}}`, { - moduleName: 'lol-wat-app/index/template' - }); - }, `Using '${attr}' with '{{each}}' ('lol-wat-app/index/template' @ L3:C18) is deprecated. Please refactor to a component.`); - }); -} - -function testNonBlockForm(attr) { - QUnit.test(`Using the '${attr}' hash argument in non-block form results in a deprecation`, function() { - expect(1); - - expectDeprecation(function() { - compile(`\n\n {{each model ${attr}="foo"}}`, { - moduleName: 'lol-wat-app/index/template' - }); - }, `Using '${attr}' with '{{each}}' ('lol-wat-app/index/template' @ L3:C17) is deprecated. Please refactor to a component.`); - }); -} - -for (let i = 0, l = deprecatedAttrs.length; i < l; i++) { - let attr = deprecatedAttrs[i]; - - testBlockForm(attr); - testNonBlockForm(attr); -} diff --git a/packages/ember-views/lib/index.js b/packages/ember-views/lib/index.js index ca36ef71f20..693e857b236 100644 --- a/packages/ember-views/lib/index.js +++ b/packages/ember-views/lib/index.js @@ -21,7 +21,6 @@ import { Renderer } from 'ember-metal-views'; import { DeprecatedCoreView } from 'ember-views/views/core_view'; import { DeprecatedView } from 'ember-views/views/view'; import { DeprecatedContainerView } from 'ember-views/views/container_view'; -import CollectionView from 'ember-views/views/collection_view'; import Component from 'ember-views/components/component'; import EventDispatcher from 'ember-views/system/event_dispatcher'; @@ -33,7 +32,6 @@ import TextField from 'ember-views/views/text_field'; import TextArea from 'ember-views/views/text_area'; import _MetamorphView, { _Metamorph } from 'ember-views/compat/metamorph_view'; -import LegacyEachView from 'ember-views/views/legacy_each_view'; // END IMPORTS @@ -62,7 +60,6 @@ if (Ember.ENV._ENABLE_LEGACY_VIEW_SUPPORT) { Ember.View.cloneStates = cloneStates; Ember.View._Renderer = Renderer; Ember.ContainerView = DeprecatedContainerView; - Ember.CollectionView = CollectionView; } Ember._Renderer = Renderer; @@ -80,7 +77,6 @@ Ember.EventDispatcher = EventDispatcher; if (Ember.ENV._ENABLE_LEGACY_VIEW_SUPPORT) { Ember._Metamorph = _Metamorph; Ember._MetamorphView = _MetamorphView; - Ember._LegacyEachView = LegacyEachView; } // END EXPORTS diff --git a/packages/ember-views/lib/mixins/empty_view_support.js b/packages/ember-views/lib/mixins/empty_view_support.js deleted file mode 100644 index ff9bba64f0b..00000000000 --- a/packages/ember-views/lib/mixins/empty_view_support.js +++ /dev/null @@ -1,64 +0,0 @@ -/** - @module ember - @submodule ember-views -*/ - -import { Mixin } from 'ember-metal/mixin'; -import View from 'ember-views/views/view'; -import { get } from 'ember-metal/property_get'; -import { set } from 'ember-metal/property_set'; -import { computed } from 'ember-metal/computed'; - -/** - @class EmptyViewSupport - @namespace Ember - @private -*/ -export default Mixin.create({ - /** - This provides metadata about what kind of empty view class this - collection would like if it is being instantiated from another - system (like Handlebars) - - @private - @property emptyViewClass - */ - emptyViewClass: View, - - /** - An optional view to display if content is set to an empty array. - - @property emptyView - @type Ember.View - @default null - @private - */ - emptyView: null, - - _emptyView: computed('emptyView', 'attrs.emptyViewClass', 'emptyViewClass', function() { - var emptyView = get(this, 'emptyView'); - var attrsEmptyViewClass = this.getAttr('emptyViewClass'); - var emptyViewClass = get(this, 'emptyViewClass'); - var inverse = get(this, '_itemViewInverse'); - var actualEmpty = emptyView || attrsEmptyViewClass; - - // Somehow, our previous semantics differed depending on whether the - // `emptyViewClass` was provided on the JavaScript class or via the - // Handlebars template. - // In Glimmer, we disambiguate between the two by checking first (and - // preferring) the attrs-supplied class. - // If not present, we fall back to the class's `emptyViewClass`, but only - // if an inverse has been provided via an `{{else}}`. - if (inverse && actualEmpty) { - if (actualEmpty.extend) { - return actualEmpty.extend({ template: inverse }); - } else { - set(actualEmpty, 'template', inverse); - } - } else if (inverse && emptyViewClass) { - return emptyViewClass.extend({ template: inverse }); - } - - return actualEmpty; - }) -}); diff --git a/packages/ember-views/lib/views/collection_view.js b/packages/ember-views/lib/views/collection_view.js deleted file mode 100644 index 8dfd67f4597..00000000000 --- a/packages/ember-views/lib/views/collection_view.js +++ /dev/null @@ -1,475 +0,0 @@ -/** -@module ember -@submodule ember-views -*/ - -import Ember from 'ember-metal/core'; -import { assert, deprecate } from 'ember-metal/debug'; -import ContainerView from 'ember-views/views/container_view'; -import View from 'ember-views/views/view'; -import EmberArray, { - addArrayObserver, - removeArrayObserver, - objectAt -} from 'ember-runtime/mixins/array'; -import { get } from 'ember-metal/property_get'; -import { set } from 'ember-metal/property_set'; -import { computed } from 'ember-metal/computed'; -import { observer } from 'ember-metal/mixin'; -import { readViewFactory } from 'ember-views/streams/utils'; -import EmptyViewSupport from 'ember-views/mixins/empty_view_support'; -import { getOwner } from 'container/owner'; - -/** - `Ember.CollectionView` is an `Ember.View` descendent responsible for managing - a collection (an array or array-like object) by maintaining a child view object - and associated DOM representation for each item in the array and ensuring - that child views and their associated rendered HTML are updated when items in - the array are added, removed, or replaced. - - ## Setting content - - The managed collection of objects is referenced as the `Ember.CollectionView` - instance's `content` property. - - ```javascript - someItemsView = Ember.CollectionView.create({ - content: ['A', 'B','C'] - }) - ``` - - The view for each item in the collection will have its `content` property set - to the item. - - ## Specifying `itemViewClass` - - By default the view class for each item in the managed collection will be an - instance of `Ember.View`. You can supply a different class by setting the - `CollectionView`'s `itemViewClass` property. - - Given the following application code: - - ```javascript - var App = Ember.Application.create(); - App.ItemListView = Ember.CollectionView.extend({ - classNames: ['a-collection'], - content: ['A','B','C'], - itemViewClass: Ember.View.extend({ - template: Ember.HTMLBars.compile("the letter: {{view.content}}") - }) - }); - ``` - - And a simple application template: - - ```handlebars - {{view 'item-list'}} - ``` - - The following HTML will result: - - ```html -
-
the letter: A
-
the letter: B
-
the letter: C
-
- ``` - - ## Automatic matching of parent/child tagNames - - Setting the `tagName` property of a `CollectionView` to any of - "ul", "ol", "table", "thead", "tbody", "tfoot", "tr", or "select" will result - in the item views receiving an appropriately matched `tagName` property. - - Given the following application code: - - ```javascript - var App = Ember.Application.create(); - App.UnorderedListView = Ember.CollectionView.create({ - tagName: 'ul', - content: ['A','B','C'], - itemViewClass: Ember.View.extend({ - template: Ember.HTMLBars.compile("the letter: {{view.content}}") - }) - }); - ``` - - And a simple application template: - - ```handlebars - {{view 'unordered-list-view'}} - ``` - - The following HTML will result: - - ```html - - ``` - - Additional `tagName` pairs can be provided by adding to - `Ember.CollectionView.CONTAINER_MAP`. For example: - - ```javascript - Ember.CollectionView.CONTAINER_MAP['article'] = 'section' - ``` - - ## Programmatic creation of child views - - For cases where additional customization beyond the use of a single - `itemViewClass` or `tagName` matching is required CollectionView's - `createChildView` method can be overridden: - - ```javascript - App.CustomCollectionView = Ember.CollectionView.extend({ - createChildView: function(viewClass, attrs) { - if (attrs.content.kind == 'album') { - viewClass = App.AlbumView; - } else { - viewClass = App.SongView; - } - return this._super(viewClass, attrs); - } - }); - ``` - - ## Empty View - - You can provide an `Ember.View` subclass to the `Ember.CollectionView` - instance as its `emptyView` property. If the `content` property of a - `CollectionView` is set to `null` or an empty array, an instance of this view - will be the `CollectionView`s only child. - - ```javascript - var App = Ember.Application.create(); - App.ListWithNothing = Ember.CollectionView.create({ - classNames: ['nothing'], - content: null, - emptyView: Ember.View.extend({ - template: Ember.HTMLBars.compile("The collection is empty") - }) - }); - ``` - - And a simple application template: - - ```handlebars - {{view 'list-with-nothing'}} - ``` - - The following HTML will result: - - ```html -
-
- The collection is empty -
-
- ``` - - ## Adding and Removing items - - The `childViews` property of a `CollectionView` should not be directly - manipulated. Instead, add, remove, replace items from its `content` property. - This will trigger appropriate changes to its rendered HTML. - - - @class CollectionView - @namespace Ember - @extends Ember.ContainerView - @uses Ember.EmptyViewSupport - @since Ember 0.9 - @private -*/ -var CollectionView = ContainerView.extend(EmptyViewSupport, { - - /** - A list of items to be displayed by the `Ember.CollectionView`. - - @property content - @type Ember.Array - @default null - @private - */ - content: null, - - /** - @property itemViewClass - @type Ember.View - @default Ember.View - @private - */ - itemViewClass: View, - - /** - Setup a CollectionView - - @method init - @private - */ - init() { - var ret = this._super(...arguments); - this._contentDidChange(); - return ret; - }, - - /** - Check to make sure that the content has changed, and if so, - update the children directly. This is always scheduled - asynchronously, to allow the element to be created before - bindings have synchronized and vice versa. - - @private - @method _contentDidChange - */ - _contentDidChange: observer('content', function() { - var prevContent = this._prevContent; - if (prevContent) { removeArrayObserver(prevContent, this); } - var len = prevContent ? get(prevContent, 'length') : 0; - this.arrayWillChange(prevContent, 0, len); - - var content = get(this, 'content'); - - if (content) { - this._prevContent = content; - this._assertArrayLike(content); - addArrayObserver(content, this); - } - - len = content ? get(content, 'length') : 0; - this.arrayDidChange(content, 0, null, len); - }), - - /** - Ensure that the content implements Ember.Array - - @private - @method _assertArrayLike - */ - _assertArrayLike(content) { - assert(`an Ember.CollectionView's content must implement Ember.Array. You passed ${content}`, EmberArray.detect(content)); - }, - - /** - Removes the content and content observers. - - @method destroy - @private - */ - destroy() { - if (!this._super(...arguments)) { return; } - - var content = get(this, 'content'); - if (content) { removeArrayObserver(content, this); } - - if (this._createdEmptyView) { - this._createdEmptyView.destroy(); - } - - return this; - }, - - /** - Called when a mutation to the underlying content array will occur. - - This method will remove any views that are no longer in the underlying - content array. - - Invokes whenever the content array itself will change. - - @method arrayWillChange - @param {Array} content the managed collection of objects - @param {Number} start the index at which the changes will occur - @param {Number} removed number of object to be removed from content - @private - */ - arrayWillChange(content, start, removedCount) { - this.replace(start, removedCount, []); - }, - - /** - Called when a mutation to the underlying content array occurs. - - This method will replay that mutation against the views that compose the - `Ember.CollectionView`, ensuring that the view reflects the model. - - This array observer is added in `contentDidChange`. - - @method arrayDidChange - @param {Array} content the managed collection of objects - @param {Number} start the index at which the changes occurred - @param {Number} removed number of object removed from content - @param {Number} added number of object added to content - @private - */ - arrayDidChange(content, start, removed, added) { - var addedViews = []; - var view, item, idx, len, itemViewClass, itemViewProps; - - len = content ? get(content, 'length') : 0; - - if (len) { - itemViewProps = this._itemViewProps || {}; - itemViewClass = this.getAttr('itemViewClass') || get(this, 'itemViewClass'); - - itemViewClass = readViewFactory(itemViewClass, getOwner(this)); - - for (idx = start; idx < start + added; idx++) { - item = objectAt(content, idx); - itemViewProps._context = this.keyword ? this.get('context') : item; - itemViewProps.content = item; - itemViewProps.contentIndex = idx; - - view = this.createChildView(itemViewClass, itemViewProps); - - addedViews.push(view); - } - - this.replace(start, 0, addedViews); - } - }, - - /** - Instantiates a view to be added to the childViews array during view - initialization. You generally will not call this method directly unless - you are overriding `createChildViews()`. Note that this method will - automatically configure the correct settings on the new view instance to - act as a child of the parent. - - The tag name for the view will be set to the tagName of the viewClass - passed in. - - @method createChildView - @param {Class} viewClass - @param {Object} [attrs] Attributes to add - @return {Ember.View} new instance - @private - */ - createChildView(_view, attrs) { - var view = this._super(_view, attrs); - - var itemTagName = get(view, 'tagName'); - - if (itemTagName === null || itemTagName === undefined) { - itemTagName = CollectionView.CONTAINER_MAP[get(this, 'tagName')]; - set(view, 'tagName', itemTagName); - } - - return view; - }, - - _willRender: function() { - var attrs = this.attrs; - var itemProps = buildItemViewProps(this._itemViewTemplate, attrs); - this._itemViewProps = itemProps; - var childViews = get(this, 'childViews'); - - for (var i = 0, l = childViews.length; i < l; i++) { - childViews[i].setProperties(itemProps); - } - - if ('content' in attrs) { - set(this, 'content', this.getAttr('content')); - } - - if ('emptyView' in attrs) { - set(this, 'emptyView', this.getAttr('emptyView')); - } - }, - - _emptyViewTagName: computed('tagName', function() { - var tagName = get(this, 'tagName'); - return CollectionView.CONTAINER_MAP[tagName] || 'div'; - }) -}); - -/** - A map of parent tags to their default child tags. You can add - additional parent tags if you want collection views that use - a particular parent tag to default to a child tag. - - @property CONTAINER_MAP - @type Object - @static - @final - @private -*/ -CollectionView.CONTAINER_MAP = { - ul: 'li', - ol: 'li', - table: 'tr', - thead: 'tr', - tbody: 'tr', - tfoot: 'tr', - tr: 'td', - select: 'option' -}; - -export let CONTAINER_MAP = CollectionView.CONTAINER_MAP; - -function buildItemViewProps(template, attrs) { - var props = {}; - - // Go through options passed to the {{collection}} helper and extract options - // that configure item views instead of the collection itself. - for (var prop in attrs) { - if (prop === 'itemViewClass' || prop === 'itemController' || prop === 'itemClassBinding') { - continue; - } - if (attrs.hasOwnProperty(prop)) { - var match = prop.match(/^item(.)(.*)$/); - if (match) { - var childProp = match[1].toLowerCase() + match[2]; - - if (childProp === 'class' || childProp === 'classNames') { - props.classNames = [attrs[prop]]; - } else { - props[childProp] = attrs[prop]; - } - - delete attrs[prop]; - } - } - } - - if (template) { - props.template = template; - } - - return props; -} - -function viewDeprecationMessage() { - deprecate( - `Ember.CollectionView is deprecated. Consult the Deprecations Guide for a migration strategy.`, - !!Ember.ENV._ENABLE_LEGACY_VIEW_SUPPORT, - { - url: 'http://emberjs.com/deprecations/v1.x/#toc_ember-collectionview', - id: 'ember-views.collection-view-deprecated', - until: '2.4.0' - } - ); -} - -var DeprecatedCollectionView = CollectionView.extend({ - init() { - viewDeprecationMessage(); - this._super(...arguments); - } -}); - -DeprecatedCollectionView.reopen = function() { - viewDeprecationMessage(); - CollectionView.reopen(...arguments); - return this; -}; - -DeprecatedCollectionView.CONTAINER_MAP = CONTAINER_MAP; - -export default CollectionView; - -export { DeprecatedCollectionView }; diff --git a/packages/ember-views/lib/views/legacy_each_view.js b/packages/ember-views/lib/views/legacy_each_view.js deleted file mode 100644 index c28159cc0f6..00000000000 --- a/packages/ember-views/lib/views/legacy_each_view.js +++ /dev/null @@ -1,28 +0,0 @@ -//2.0TODO: Remove this in 2.0 -//This is a fallback path for the `{{#each}}` helper that supports deprecated -//behavior such as itemController. - -import legacyEachTemplate from 'ember-htmlbars/templates/legacy-each'; -import { get } from 'ember-metal/property_get'; -import { computed } from 'ember-metal/computed'; -import View from 'ember-views/views/view'; -import { CONTAINER_MAP } from 'ember-views/views/collection_view'; -import EmptyViewSupport from 'ember-views/mixins/empty_view_support'; - -export default View.extend(EmptyViewSupport, { - template: legacyEachTemplate, - tagName: '', - - /* - Support for ArrayController has been extracted to the ember-legacy-controllers addon. - */ - - _arrangedContent: computed('attrs.content', function() { - return this.getAttr('content'); - }), - - _itemTagName: computed(function() { - var tagName = get(this, 'tagName'); - return CONTAINER_MAP[tagName]; - }) -}); diff --git a/packages/ember-views/tests/views/collection_test.js b/packages/ember-views/tests/views/collection_test.js deleted file mode 100644 index 7fb5c6ed1a3..00000000000 --- a/packages/ember-views/tests/views/collection_test.js +++ /dev/null @@ -1,689 +0,0 @@ -import Ember from 'ember-metal/core'; // Ember.A -import { set } from 'ember-metal/property_set'; -import run from 'ember-metal/run_loop'; -import { runDestroy } from 'ember-runtime/tests/utils'; -import { Mixin } from 'ember-metal/mixin'; -import jQuery from 'ember-views/system/jquery'; -import CollectionView, { DeprecatedCollectionView } from 'ember-views/views/collection_view'; -import View from 'ember-views/views/view'; -import compile from 'ember-template-compiler/system/compile'; -import getElementStyle from 'ember-views/tests/test-helpers/get-element-style'; -import { A as emberA } from 'ember-runtime/system/native_array'; - -import { registerKeyword, resetKeyword } from 'ember-htmlbars/tests/utils'; -import viewKeyword from 'ember-htmlbars/keywords/view'; -import buildOwner from 'container/tests/test-helpers/build-owner'; -import { OWNER } from 'container/owner'; - -var trim = jQuery.trim; -var owner; -var view; - -var originalLookup, originalViewKeyword; - -QUnit.module('CollectionView', { - setup() { - CollectionView.CONTAINER_MAP.del = 'em'; - originalViewKeyword = registerKeyword('view', viewKeyword); - originalLookup = Ember.lookup; - owner = buildOwner(); - }, - teardown() { - delete CollectionView.CONTAINER_MAP.del; - runDestroy(view); - runDestroy(owner); - - Ember.lookup = originalLookup; - resetKeyword('view', originalViewKeyword); - } -}); - -QUnit.test('should render a view for each item in its content array', function() { - view = CollectionView.create({ - content: emberA([1, 2, 3, 4]) - }); - - run(function() { - view.append(); - }); - equal(view.$('div').length, 4); -}); - -QUnit.test('should render the emptyView if content array is empty (view class)', function() { - view = CollectionView.create({ - content: emberA(), - - emptyView: View.extend({ - template: compile('OY SORRY GUVNAH NO NEWS TODAY EH') - }) - }); - - run(function() { - view.append(); - }); - - ok(view.$().find('div:contains("OY SORRY GUVNAH")').length, 'displays empty view'); -}); - -QUnit.test('should render the emptyView if content array is empty (view class with custom tagName)', function() { - view = CollectionView.create({ - tagName: 'del', - content: emberA(), - - emptyView: View.extend({ - tagName: 'kbd', - template: compile('OY SORRY GUVNAH NO NEWS TODAY EH') - }) - }); - - run(function() { - view.append(); - }); - - ok(view.$().find('kbd:contains("OY SORRY GUVNAH")').length, 'displays empty view'); -}); - -QUnit.test('should render the emptyView if content array is empty (view instance)', function() { - view = CollectionView.create({ - tagName: 'del', - content: emberA(), - - emptyView: View.create({ - tagName: 'kbd', - template: compile('OY SORRY GUVNAH NO NEWS TODAY EH') - }) - }); - - run(function() { - view.append(); - }); - - ok(view.$().find('kbd:contains("OY SORRY GUVNAH")').length, 'displays empty view'); -}); - -QUnit.test('should be able to override the tag name of itemViewClass even if tag is in default mapping', function() { - view = CollectionView.create({ - tagName: 'del', - content: emberA(['NEWS GUVNAH']), - - itemViewClass: View.extend({ - tagName: 'kbd', - template: compile('{{view.content}}') - }) - }); - - run(function() { - view.append(); - }); - - ok(view.$().find('kbd:contains("NEWS GUVNAH")').length, 'displays the item view with proper tag name'); -}); - -QUnit.test('should allow custom item views by setting itemViewClass', function() { - var content = emberA(['foo', 'bar', 'baz']); - view = CollectionView.create({ - content: content, - - itemViewClass: View.extend({ - template: compile('{{view.content}}') - }) - }); - - run(function() { - view.append(); - }); - - content.forEach((item) => equal(view.$(':contains("' + item + '")').length, 1)); -}); - -QUnit.test('should insert a new item in DOM when an item is added to the content array', function() { - var content = emberA(['foo', 'bar', 'baz']); - - view = CollectionView.create({ - content: content, - - itemViewClass: View.extend({ - template: compile('{{view.content}}') - }) - }); - - run(function() { - view.append(); - }); - - content.forEach((item) => { - equal(view.$(':contains("' + item + '")').length, 1, 'precond - generates pre-existing items'); - }); - - run(function() { - content.insertAt(1, 'quux'); - }); - - equal(trim(view.$(':nth-child(2)').text()), 'quux'); -}); - -QUnit.test('should remove an item from DOM when an item is removed from the content array', function() { - var content = emberA(['foo', 'bar', 'baz']); - - view = CollectionView.create({ - content: content, - - itemViewClass: View.extend({ - template: compile('{{view.content}}') - }) - }); - - run(() => view.append()); - - content.forEach((item) => { - equal(view.$(':contains("' + item + '")').length, 1, 'precond - generates pre-existing items'); - }); - - run(() => content.removeAt(1)); - - content.forEach((item, idx) => { - equal(view.$(`:nth-child(${idx + 1})`).text(), item); - }); -}); - -QUnit.test('it updates the view if an item is replaced', function() { - var content = emberA(['foo', 'bar', 'baz']); - view = CollectionView.create({ - content: content, - - itemViewClass: View.extend({ - template: compile('{{view.content}}') - }) - }); - - run(function() { - view.append(); - }); - - content.forEach((item) => { - equal(view.$(':contains("' + item + '")').length, 1, 'precond - generates pre-existing items'); - }); - - run(function() { - content.removeAt(1); - content.insertAt(1, 'Kazuki'); - }); - - content.forEach((item, idx) => { - equal(trim(view.$(`:nth-child(${idx + 1})`).text()), item, 'postcond - correct array update'); - }); -}); - -QUnit.test('can add and replace in the same runloop', function() { - var content = emberA(['foo', 'bar', 'baz']); - view = CollectionView.create({ - content: content, - - itemViewClass: View.extend({ - template: compile('{{view.content}}') - }) - }); - - run(() => view.append()); - - content.forEach((item) => { - equal(view.$(':contains("' + item + '")').length, 1, 'precond - generates pre-existing items'); - }); - - run(() => { - content.pushObject('Tom Dale'); - content.removeAt(0); - content.insertAt(0, 'Kazuki'); - }); - - content.forEach((item, idx) => { - equal(trim(view.$(`:nth-child(${idx + 1})`).text()), item, 'postcond - correct array update'); - }); -}); - -QUnit.test('can add and replace the object before the add in the same runloop', function() { - var content = emberA(['foo', 'bar', 'baz']); - view = CollectionView.create({ - content: content, - - itemViewClass: View.extend({ - template: compile('{{view.content}}') - }) - }); - - run(() => view.append()); - - content.forEach((item) => { - equal(view.$(':contains("' + item + '")').length, 1, 'precond - generates pre-existing items'); - }); - - run(() => { - content.pushObject('Tom Dale'); - content.removeAt(1); - content.insertAt(1, 'Kazuki'); - }); - - content.forEach((item, idx) => { - equal(trim(view.$(`:nth-child(${idx + 1})`).text()), item, 'postcond - correct array update'); - }); -}); - -QUnit.test('can add and replace complicatedly', function() { - var content = emberA(['foo', 'bar', 'baz']); - view = CollectionView.create({ - content: content, - - itemViewClass: View.extend({ - template: compile('{{view.content}}') - }) - }); - - run(() => view.append()); - - content.forEach((item) => { - equal(view.$(':contains("' + item + '")').length, 1, 'precond - generates pre-existing items'); - }); - - run(() => { - content.pushObject('Tom Dale'); - content.removeAt(1); - content.insertAt(1, 'Kazuki'); - content.pushObject('Firestone'); - content.pushObject('McMunch'); - }); - - content.forEach((item, idx) => { - equal(trim(view.$(`:nth-child(${idx + 1})`).text()), item, 'postcond - correct array update: ' + item.name + '!=' + view.$(`:nth-child(${idx + 1})`).text()); - }); -}); - -QUnit.test('can add and replace complicatedly harder', function() { - var content = emberA(['foo', 'bar', 'baz']); - view = CollectionView.create({ - content: content, - - itemViewClass: View.extend({ - template: compile('{{view.content}}') - }) - }); - - run(function() { - view.append(); - }); - - content.forEach((item) => { - equal(view.$(':contains("' + item + '")').length, 1, 'precond - generates pre-existing items'); - }); - - run(function() { - content.pushObject('Tom Dale'); - content.removeAt(1); - content.insertAt(1, 'Kazuki'); - content.pushObject('Firestone'); - content.pushObject('McMunch'); - content.removeAt(2); - }); - - content.forEach((item, idx) => { - equal(trim(view.$(`:nth-child(${idx + 1})`).text()), item, 'postcond - correct array update'); - }); -}); - -QUnit.test('should allow changes to content object before layer is created', function() { - view = CollectionView.create({ - content: null - }); - - - run(function() { - set(view, 'content', emberA()); - set(view, 'content', emberA([1, 2, 3])); - set(view, 'content', emberA([1, 2])); - view.append(); - }); - - ok(view.$().children().length); -}); - -QUnit.test('should fire life cycle events when elements are added and removed', function() { - var view; - var didInsertElement = 0; - var willDestroyElement = 0; - var willDestroy = 0; - var destroy = 0; - var content = emberA([1, 2, 3]); - run(function () { - view = CollectionView.create({ - content: content, - itemViewClass: View.extend({ - template: compile('{{view.content}}'), - didInsertElement() { - didInsertElement++; - }, - willDestroyElement() { - willDestroyElement++; - }, - willDestroy() { - willDestroy++; - this._super(...arguments); - }, - destroy() { - destroy++; - this._super(...arguments); - } - }) - }); - view.appendTo('#qunit-fixture'); - }); - - equal(didInsertElement, 3); - equal(willDestroyElement, 0); - equal(willDestroy, 0); - equal(destroy, 0); - equal(view.$().text(), '123'); - - run(function () { - content.pushObject(4); - content.unshiftObject(0); - }); - - - equal(didInsertElement, 5); - equal(willDestroyElement, 0); - equal(willDestroy, 0); - equal(destroy, 0); - // Remove whitespace added by IE 8 - equal(trim(view.$().text()), '01234'); - - run(function () { - content.popObject(); - content.shiftObject(); - }); - - equal(didInsertElement, 5); - equal(willDestroyElement, 2); - equal(willDestroy, 2); - equal(destroy, 2); - // Remove whitspace added by IE 8 - equal(trim(view.$().text()), '123'); - - run(function () { - view.set('content', emberA([7, 8, 9])); - }); - - equal(didInsertElement, 8); - equal(willDestroyElement, 5); - equal(willDestroy, 5); - equal(destroy, 5); - // Remove whitespace added by IE 8 - equal(trim(view.$().text()), '789'); - - run(function () { - view.destroy(); - }); - - equal(didInsertElement, 8); - equal(willDestroyElement, 8); - equal(willDestroy, 8); - equal(destroy, 8); -}); - -QUnit.test('should allow changing content property to be null', function() { - view = CollectionView.create({ - content: emberA([1, 2, 3]), - - emptyView: View.extend({ - template: compile('(empty)') - }) - }); - - run(function() { - view.append(); - }); - - equal(view.$().children().length, 3, 'precond - creates three elements'); - - run(function() { - set(view, 'content', null); - }); - - equal(trim(view.$().children().text()), '(empty)', 'should display empty view'); -}); - -QUnit.test('should allow items to access to the CollectionView\'s current index in the content array', function() { - view = CollectionView.create({ - content: emberA(['zero', 'one', 'two']), - itemViewClass: View.extend({ - template: compile('{{view.contentIndex}}') - }) - }); - - run(function() { - view.append(); - }); - - deepEqual(view.$(':nth-child(1)').text(), '0'); - deepEqual(view.$(':nth-child(2)').text(), '1'); - deepEqual(view.$(':nth-child(3)').text(), '2'); -}); - -QUnit.test('should allow declaration of itemViewClass as a string', function() { - owner.register('view:simple-view', View.extend()); - - view = CollectionView.create({ - [OWNER]: owner, - itemViewClass: 'simple-view' - }); - - view.set('content', emberA([1, 2, 3])); - - run(function() { - view.appendTo('#qunit-fixture'); - }); - - equal(view.$('.ember-view').length, 3); -}); - -QUnit.test('should not render the emptyView if content is emptied and refilled in the same run loop', function() { - view = CollectionView.create({ - tagName: 'div', - content: emberA(['NEWS GUVNAH']), - - emptyView: View.extend({ - tagName: 'kbd', - template: compile('OY SORRY GUVNAH NO NEWS TODAY EH') - }) - }); - - run(function() { - view.append(); - }); - - equal(view.$().find('kbd:contains("OY SORRY GUVNAH")').length, 0); - - run(function() { - view.get('content').popObject(); - view.get('content').pushObject(['NEWS GUVNAH']); - }); - equal(view.$('div').length, 1); - equal(view.$().find('kbd:contains("OY SORRY GUVNAH")').length, 0); -}); - -QUnit.test('when a collection view is emptied, deeply nested views elements are not removed from the DOM and then destroyed again', function() { - var gotDestroyed = []; - - var assertProperDestruction = Mixin.create({ - destroy() { - gotDestroyed.push(this.label); - this._super(...arguments); - } - }); - - var ChildView = View.extend(assertProperDestruction, { - template: compile('{{#view view.assertDestruction}}
{{/view}}'), - label: 'parent', - assertDestruction: View.extend(assertProperDestruction, { - label: 'child' - }) - }); - - var view = CollectionView.create({ - content: emberA([1]), - itemViewClass: ChildView - }); - - run(function() { - view.append(); - }); - equal(jQuery('.inner_element').length, 1, 'precond - generates inner element'); - - run(function() { - view.get('content').clear(); - }); - equal(jQuery('.inner_element').length, 0, 'elements removed'); - - run(function() { - view.destroy(); - }); - - deepEqual(gotDestroyed, ['parent', 'child'], 'The child view was destroyed'); -}); - -QUnit.test('should render the emptyView if content array is empty and emptyView is given as string', function() { - owner.register('view:custom-empty', View.extend({ - tagName: 'kbd', - template: compile('THIS IS AN EMPTY VIEW') - })); - - view = CollectionView.create({ - [OWNER]: owner, - tagName: 'del', - content: emberA(), - emptyView: 'custom-empty' - }); - - run(function() { - view.append(); - }); - - ok(view.$().find('kbd:contains("THIS IS AN EMPTY VIEW")').length, 'displays empty view'); -}); - -QUnit.test('should lookup against the container if itemViewClass is given as a string', function() { - var ItemView = View.extend({ - template: compile('{{view.content}}') - }); - - owner.register('view:item', ItemView); - - view = CollectionView.create({ - [OWNER]: owner, - itemViewClass: 'item' - }); - - view.set('content', emberA([1, 2, 3, 4])); - - run(function() { - view.appendTo('#qunit-fixture'); - }); - - equal(view.$('.ember-view').length, 4); -}); - -QUnit.test('should lookup only global path against the container if itemViewClass is given as a string', function() { - var ItemView = View.extend({ - template: compile('{{view.content}}') - }); - - owner.register('view:top', ItemView); - - view = CollectionView.create({ - [OWNER]: owner, - itemViewClass: 'top' - }); - - view.set('content', emberA(['hi'])); - - run(function() { - view.appendTo('#qunit-fixture'); - }); - - equal(view.$().text(), 'hi'); -}); - -QUnit.test('should lookup against the container and render the emptyView if emptyView is given as string and content array is empty ', function() { - var EmptyView = View.extend({ - tagName: 'kbd', - template: compile('THIS IS AN EMPTY VIEW') - }); - - owner.register('view:empty', EmptyView); - - view = CollectionView.create({ - [OWNER]: owner, - tagName: 'del', - content: emberA(), - emptyView: 'empty' - }); - - run(function() { - view.append(); - }); - - ok(view.$().find('kbd:contains("THIS IS AN EMPTY VIEW")').length, 'displays empty view'); -}); - -QUnit.test('should lookup from only global path against the container if emptyView is given as string and content array is empty ', function() { - var EmptyView = View.extend({ - template: compile('EMPTY') - }); - - owner.register('view:top', EmptyView); - - view = CollectionView.create({ - [OWNER]: owner, - content: emberA(), - emptyView: 'top' - }); - - run(function() { - view.append(); - }); - - equal(view.$().text(), 'EMPTY'); -}); - -QUnit.test('Collection with style attribute supports changing content', function() { - view = CollectionView.create({ - attributeBindings: ['style'], - style: 'width: 100px;', - content: emberA(['foo', 'bar']) - }); - - run(function() { - view.appendTo('#qunit-fixture'); - }); - - var style = getElementStyle(view.element); - - equal(style, 'WIDTH: 100PX;', 'width is applied to the element'); - - run(function() { - view.get('content').pushObject('baz'); - }); -}); - -QUnit.module('DeprecatedCollectionView [LEGACY]'); - -QUnit.test('calling reopen on DeprecatedCollectionView delegates to CollectionView', function() { - expect(2); - var originalReopen = CollectionView.reopen; - var obj = {}; - - CollectionView.reopen = function(arg) { ok(arg === obj); }; - - expectNoDeprecation(); - DeprecatedCollectionView.reopen(obj); - - CollectionView.reopen = originalReopen; -}); diff --git a/packages/ember/tests/controller_test.js b/packages/ember/tests/controller_test.js index 5e66c6e53c8..3a2021bae06 100644 --- a/packages/ember/tests/controller_test.js +++ b/packages/ember/tests/controller_test.js @@ -7,10 +7,6 @@ import Application from 'ember-application/system/application'; import EmberView from 'ember-views/views/view'; import Component from 'ember-views/components/component'; import jQuery from 'ember-views/system/jquery'; -import { A as emberA } from 'ember-runtime/system/native_array'; - -import plugins, { registerPlugin } from 'ember-template-compiler/plugins'; -import TransformEachIntoCollection from 'ember-template-compiler/plugins/transform-each-into-collection'; /* In Ember 1.x, controllers subtly affect things like template scope @@ -22,13 +18,8 @@ import TransformEachIntoCollection from 'ember-template-compiler/plugins/transfo var App, $fixture, templates; -let originalAstPlugins; - QUnit.module('Template scoping examples', { setup() { - originalAstPlugins = plugins['ast'].slice(0); - registerPlugin('ast', TransformEachIntoCollection); - run(function() { templates = Ember.TEMPLATES; App = Application.create({ @@ -55,8 +46,6 @@ QUnit.module('Template scoping examples', { App = null; Ember.TEMPLATES = {}; - - plugins['ast'] = originalAstPlugins; } }); @@ -107,29 +96,6 @@ QUnit.test('the controller property is provided to route driven views', function equal(applicationViewController, applicationController, 'application view should get its controller set properly'); }); -// This test caught a regression where {{#each}}s used directly in a template -// (i.e., not inside a view or component) did not have access to a container and -// would raise an exception. -QUnit.test('{{#each}} inside outlet can have an itemController', function(assert) { - expectDeprecation(function() { - templates.index = compile(` - {{#each model itemController='thing'}} -

hi

- {{/each}} - `); - }, `Using 'itemController' with '{{each}}' (L2:C20) is deprecated. Please refactor to a component.`); - - App.IndexController = Controller.extend({ - model: emberA([1, 2, 3]) - }); - - App.ThingController = Controller.extend(); - - bootApp(); - - assert.equal($fixture.find('p').length, 3, 'the {{#each}} rendered without raising an exception'); -}); - function bootApp() { run(App, 'advanceReadiness'); }