diff --git a/src/repeat.js b/src/repeat.js index 6047646..6c1ec60 100644 --- a/src/repeat.js +++ b/src/repeat.js @@ -1,7 +1,20 @@ /*eslint no-loop-func:0, no-unused-vars:0*/ import {inject} from 'aurelia-dependency-injection'; -import {ObserverLocator, calcSplices, getChangeRecords} from 'aurelia-binding'; -import {BoundViewFactory, ViewSlot, customAttribute, bindable, templateController} from 'aurelia-templating'; +import { + ObserverLocator, + getChangeRecords, + BindingBehavior, + ValueConverter +} from 'aurelia-binding'; +import { + BoundViewFactory, + TargetInstruction, + ViewSlot, + ViewResources, + customAttribute, + bindable, + templateController +} from 'aurelia-templating'; import {CollectionStrategyLocator} from './collection-strategy-locator'; /** @@ -10,12 +23,14 @@ import {CollectionStrategyLocator} from './collection-strategy-locator'; * @class Repeat * @constructor * @param {BoundViewFactory} viewFactory The factory generating the view +* @param {TargetInstruction} instruction The view instruction * @param {ViewSlot} viewSlot The slot the view is injected in to +* @param {ViewResources} viewResources The view resources * @param {ObserverLocator} observerLocator The observer locator instance */ @customAttribute('repeat') @templateController -@inject(BoundViewFactory, ViewSlot, ObserverLocator, CollectionStrategyLocator) +@inject(BoundViewFactory, TargetInstruction, ViewSlot, ViewResources, ObserverLocator, CollectionStrategyLocator) export class Repeat { /** * List of items to bind the repeater to @@ -27,9 +42,11 @@ export class Repeat { @bindable local @bindable key @bindable value - constructor(viewFactory, viewSlot, observerLocator, collectionStrategyLocator) { + constructor(viewFactory, instruction, viewSlot, viewResources, observerLocator, collectionStrategyLocator) { this.viewFactory = viewFactory; + this.instruction = instruction; this.viewSlot = viewSlot; + this.lookupFunctions = viewResources.lookupFunctions; this.observerLocator = observerLocator; this.local = 'item'; this.key = 'key'; @@ -47,12 +64,16 @@ export class Repeat { return; } + this.sourceExpression = getSourceExpression(this.instruction, 'repeat.for'); + this.scope = { bindingContext, overrideContext }; this.collectionStrategy = this.collectionStrategyLocator.getStrategy(this.items); this.collectionStrategy.initialize(this, bindingContext, overrideContext); this.processItems(); } unbind() { + this.sourceExpression = null; + this.scope = null; this.collectionStrategy.dispose(); this.items = null; this.collectionStrategy = null; @@ -94,17 +115,74 @@ export class Repeat { } } - processItemsByStrategy() { + getInnerCollection() { + let expression = unwrapExpression(this.sourceExpression); + if (!expression) { + return null; + } + return expression.evaluate(this.scope, null); + } + + observeInnerCollection() { + let items = this.getInnerCollection(); + if (items instanceof Array) { + this.collectionObserver = this.observerLocator.getArrayObserver(items); + } else if (items instanceof Map) { + this.collectionObserver = this.observerLocator.getMapObserver(items); + } else { + return false; + } + this.callContext = 'handleInnerCollectionChanges'; + this.collectionObserver.subscribe(this.callContext, this); + return true; + } + + observeCollection() { let items = this.items; this.collectionObserver = this.collectionStrategy.getCollectionObserver(items); - this.collectionStrategy.processItems(items); if (this.collectionObserver) { - this.callContext = 'handleChanges'; + this.callContext = 'handleCollectionChanges'; this.collectionObserver.subscribe(this.callContext, this); } } - handleChanges(collection, changes) { + processItemsByStrategy() { + if (!this.observeInnerCollection()) { + this.observeCollection(); + } + this.collectionStrategy.processItems(this.items); + } + + handleCollectionChanges(collection, changes) { this.collectionStrategy.handleChanges(collection, changes); } + + handleInnerCollectionChanges(collection, changes) { + let newItems = this.sourceExpression.evaluate(this.scope, this.lookupFunctions); + if (newItems === this.items) { + return; + } + this.items = newItems; + this.itemsChanged(); + } +} + +function getSourceExpression(instruction, attrName) { + return instruction.behaviorInstructions + .filter(bi => bi.originalAttrName === attrName)[0] + .attributes + .items + .sourceExpression; +} + +function unwrapExpression(expression) { + let unwrapped = false; + while (expression instanceof BindingBehavior) { + expression = expression.expression; + } + while (expression instanceof ValueConverter) { + expression = expression.expression; + unwrapped = true; + } + return unwrapped ? expression : null; } diff --git a/test/array-collection-strategy.spec.js b/test/array-collection-strategy.spec.js index ad0097b..a44bb89 100644 --- a/test/array-collection-strategy.spec.js +++ b/test/array-collection-strategy.spec.js @@ -1,11 +1,11 @@ import {ObserverLocator} from 'aurelia-binding'; -import {BoundViewFactory, TemplatingEngine, ViewSlot, ViewFactory, ModuleAnalyzer} from 'aurelia-templating'; +import {BoundViewFactory, TemplatingEngine, ViewSlot, ViewFactory, ModuleAnalyzer, TargetInstruction, ViewResources} from 'aurelia-templating'; import {Container} from 'aurelia-dependency-injection'; import {initialize} from 'aurelia-pal-browser'; import {Repeat} from '../src/repeat'; import {CollectionStrategyLocator} from '../src/collection-strategy-locator'; import {ArrayCollectionStrategy} from '../src/array-collection-strategy'; -import {ViewSlotMock, BoundViewFactoryMock, CollectionStrategyMock, ViewMock, ArrayObserverMock, ViewFactoryMock} from './mocks'; +import {ViewSlotMock, BoundViewFactoryMock, CollectionStrategyMock, ViewMock, ArrayObserverMock, ViewFactoryMock, instructionMock, viewResourcesMock} from './mocks'; describe('ArrayCollectionStrategy', () => { let repeat, strategy, viewSlot, viewFactory, observerLocator, collectionStrategyLocator, collectionStrategyMock; @@ -22,6 +22,8 @@ describe('ArrayCollectionStrategy', () => { collectionStrategyLocator = new CollectionStrategyLocator(); collectionStrategyMock = new CollectionStrategyMock(); strategy = new ArrayCollectionStrategy(); + container.registerInstance(TargetInstruction, instructionMock); + container.registerInstance(ViewResources, viewResourcesMock); container.registerInstance(ViewSlot, viewSlot); container.registerInstance(BoundViewFactory, viewFactory); container.registerInstance(ObserverLocator, observerLocator); @@ -95,7 +97,7 @@ describe('ArrayCollectionStrategy', () => { }); it('should correctly handle adding item (i.e Array.prototype.push())', () => { - repeat = new Repeat(new ViewFactoryMock(), viewSlot, new ObserverLocator()); + repeat = new Repeat(new ViewFactoryMock(), instructionMock, viewSlot, viewResourcesMock, new ObserverLocator()); strategy.initialize(repeat, {}); splices = [{ addedCount: 1, @@ -118,7 +120,7 @@ describe('ArrayCollectionStrategy', () => { view4.overrideContext = { item: 'norf' }; let viewSlotMock = new ViewSlotMock(); viewSlotMock.children = [view1, view2, view3, view4]; - repeat = new Repeat(new ViewFactoryMock(), viewSlotMock, new ObserverLocator()); + repeat = new Repeat(new ViewFactoryMock(), instructionMock, viewSlotMock, viewResourcesMock, new ObserverLocator()); strategy.initialize(repeat, {}); splices = [{ addedCount: 1, @@ -138,7 +140,7 @@ describe('ArrayCollectionStrategy', () => { view4.overrideContext = { item: 'norf' }; let viewSlotMock = new ViewSlotMock(); viewSlotMock.children = [view1, view2, view3, view4]; - repeat = new Repeat(new ViewFactoryMock(), viewSlotMock, new ObserverLocator()); + repeat = new Repeat(new ViewFactoryMock(), instructionMock, viewSlotMock, viewResourcesMock, new ObserverLocator()); strategy.initialize(repeat, {}); splices = [{ @@ -168,7 +170,7 @@ describe('ArrayCollectionStrategy', () => { it('moving animated item', done => { let viewSlotMock = new ViewSlotMock(); viewSlotMock.children = [view1, view2, view3]; - repeat = new Repeat(new ViewFactoryMock(), viewSlotMock, new ObserverLocator()); + repeat = new Repeat(new ViewFactoryMock(), instructionMock, viewSlotMock, viewResourcesMock, new ObserverLocator()); strategy.initialize(repeat, {}); let removeAction = () => { viewSlot.children.splice(2, 1); diff --git a/test/mocks.js b/test/mocks.js index 238760a..831a08d 100644 --- a/test/mocks.js +++ b/test/mocks.js @@ -50,3 +50,25 @@ export class CollectionStrategyMock { getCollectionObserver() {} dispose() {} } + +export const instructionMock = { + behaviorInstructions: [ + { + originalAttrName: 'repeat.for', + attributes: { + items: { + sourceExpression: { + evaluate: (scope, lookupFunctions) => null + } + } + } + } + ] +}; + +export const viewResourcesMock = { + lookupFunctions: { + valueConverters: name => null, + bindingBehaviors: name => null + } +}; diff --git a/test/number-strategy.spec.js b/test/number-strategy.spec.js index e5becff..f744b75 100644 --- a/test/number-strategy.spec.js +++ b/test/number-strategy.spec.js @@ -1,11 +1,11 @@ import {ObserverLocator} from 'aurelia-binding'; -import {BoundViewFactory, TemplatingEngine, ViewSlot, ViewFactory, ModuleAnalyzer} from 'aurelia-templating'; +import {BoundViewFactory, TemplatingEngine, ViewSlot, ViewFactory, ModuleAnalyzer, TargetInstruction, ViewResources} from 'aurelia-templating'; import {Container} from 'aurelia-dependency-injection'; import {initialize} from 'aurelia-pal-browser'; import {Repeat} from '../src/repeat'; import {CollectionStrategyLocator} from '../src/collection-strategy-locator'; import {NumberStrategy} from '../src/number-strategy'; -import {ViewSlotMock, BoundViewFactoryMock, CollectionStrategyMock, ViewMock, ArrayObserverMock, ViewFactoryMock} from './mocks'; +import {ViewSlotMock, BoundViewFactoryMock, CollectionStrategyMock, ViewMock, ArrayObserverMock, ViewFactoryMock, instructionMock, viewResourcesMock} from './mocks'; describe('NumberStrategy', () => { let repeat, strategy, viewSlot, viewFactory, observerLocator, collectionStrategyLocator, collectionStrategyMock; @@ -22,6 +22,8 @@ describe('NumberStrategy', () => { collectionStrategyLocator = new CollectionStrategyLocator(); collectionStrategyMock = new CollectionStrategyMock(); strategy = new NumberStrategy(); + container.registerInstance(TargetInstruction, instructionMock); + container.registerInstance(ViewResources, viewResourcesMock); container.registerInstance(ViewSlot, viewSlot); container.registerInstance(BoundViewFactory, viewFactory); container.registerInstance(ObserverLocator, observerLocator); @@ -32,7 +34,7 @@ describe('NumberStrategy', () => { describe('processItems', () => { beforeEach(() => { - repeat = new Repeat(new ViewFactoryMock(), viewSlot, new ObserverLocator()); + repeat = new Repeat(new ViewFactoryMock(), instructionMock, viewSlot, viewResourcesMock, new ObserverLocator()); strategy.initialize(repeat, {}); viewSlot.children = []; }); diff --git a/test/repeat.spec.js b/test/repeat.spec.js index 6ca7ecc..0ed5154 100644 --- a/test/repeat.spec.js +++ b/test/repeat.spec.js @@ -1,10 +1,10 @@ import {ObserverLocator} from 'aurelia-binding'; -import {BoundViewFactory, TemplatingEngine, ViewSlot, ViewFactory, ModuleAnalyzer} from 'aurelia-templating'; +import {BoundViewFactory, TemplatingEngine, ViewSlot, ViewFactory, ModuleAnalyzer, TargetInstruction, ViewResources} from 'aurelia-templating'; import {Container} from 'aurelia-dependency-injection'; import {initialize} from 'aurelia-pal-browser'; import {Repeat} from '../src/repeat'; import {CollectionStrategyLocator} from '../src/collection-strategy-locator'; -import {ViewSlotMock, BoundViewFactoryMock, CollectionStrategyMock, ViewMock, ArrayObserverMock} from './mocks'; +import {ViewSlotMock, BoundViewFactoryMock, CollectionStrategyMock, ViewMock, ArrayObserverMock, instructionMock, viewResourcesMock} from './mocks'; describe('repeat', () => { let repeat, viewSlot, viewFactory, observerLocator, collectionStrategyLocator, collectionStrategyMock; @@ -20,6 +20,8 @@ describe('repeat', () => { observerLocator = new ObserverLocator(); collectionStrategyLocator = new CollectionStrategyLocator(); collectionStrategyMock = new CollectionStrategyMock(); + container.registerInstance(TargetInstruction, instructionMock); + container.registerInstance(ViewResources, viewResourcesMock); container.registerInstance(ViewSlot, viewSlot); container.registerInstance(BoundViewFactory, viewFactory); container.registerInstance(ObserverLocator, observerLocator);