From ab9f9c91519b6175b255cecde4eabe6bf198f735 Mon Sep 17 00:00:00 2001 From: Rob Eisenberg Date: Tue, 10 Nov 2015 10:26:01 -0500 Subject: [PATCH] chore(all): prepare release 0.17.0 --- bower.json | 2 +- dist/amd/array-collection-strategy.js | 96 +++++ dist/amd/aurelia-templating-resources.js | 13 +- dist/amd/binding-mode-behaviors.js | 71 ++++ dist/amd/binding-signaler.js | 30 ++ dist/amd/collection-strategy-locator.js | 50 +++ dist/amd/collection-strategy.js | 90 +++++ dist/amd/compose.js | 10 +- dist/amd/debounce-binding-behavior.js | 61 +++ dist/amd/global-behavior.js | 102 ----- dist/amd/if.js | 12 +- dist/amd/map-collection-strategy.js | 121 ++++++ dist/amd/number-strategy.js | 62 +++ dist/amd/repeat.js | 360 ++++------------- dist/amd/replaceable.js | 16 +- dist/amd/signal-binding-behavior.js | 42 ++ dist/amd/throttle-binding-behavior.js | 69 ++++ dist/amd/update-trigger-binding-behavior.js | 53 +++ dist/amd/with.js | 24 +- dist/commonjs/array-collection-strategy.js | 96 +++++ dist/commonjs/aurelia-templating-resources.js | 27 +- dist/commonjs/binding-mode-behaviors.js | 71 ++++ dist/commonjs/binding-signaler.js | 30 ++ dist/commonjs/collection-strategy-locator.js | 56 +++ dist/commonjs/collection-strategy.js | 92 +++++ dist/commonjs/compose.js | 10 +- dist/commonjs/debounce-binding-behavior.js | 61 +++ dist/commonjs/global-behavior.js | 112 ------ dist/commonjs/if.js | 12 +- dist/commonjs/map-collection-strategy.js | 121 ++++++ dist/commonjs/number-strategy.js | 62 +++ dist/commonjs/repeat.js | 362 ++++------------- dist/commonjs/replaceable.js | 16 +- dist/commonjs/signal-binding-behavior.js | 44 +++ dist/commonjs/throttle-binding-behavior.js | 69 ++++ .../update-trigger-binding-behavior.js | 53 +++ dist/commonjs/with.js | 28 +- dist/es6/array-collection-strategy.js | 74 ++++ dist/es6/aurelia-templating-resources.js | 26 +- dist/es6/binding-mode-behaviors.js | 35 ++ dist/es6/binding-signaler.js | 16 + dist/es6/collection-strategy-locator.js | 34 ++ dist/es6/collection-strategy.js | 78 ++++ dist/es6/compose.js | 10 +- dist/es6/debounce-binding-behavior.js | 51 +++ dist/es6/global-behavior.js | 93 ----- dist/es6/if.js | 12 +- dist/es6/map-collection-strategy.js | 95 +++++ dist/es6/number-strategy.js | 42 ++ dist/es6/repeat.js | 366 ++++------------- dist/es6/replaceable.js | 16 +- dist/es6/signal-binding-behavior.js | 30 ++ dist/es6/throttle-binding-behavior.js | 60 +++ dist/es6/update-trigger-binding-behavior.js | 34 ++ dist/es6/with.js | 24 +- dist/system/array-collection-strategy.js | 103 +++++ dist/system/aurelia-templating-resources.js | 40 +- dist/system/binding-mode-behaviors.js | 78 ++++ dist/system/binding-signaler.js | 37 ++ dist/system/collection-strategy-locator.js | 64 +++ dist/system/collection-strategy.js | 101 +++++ dist/system/compose.js | 10 +- dist/system/debounce-binding-behavior.js | 68 ++++ dist/system/global-behavior.js | 118 ------ dist/system/if.js | 12 +- dist/system/map-collection-strategy.js | 128 ++++++ dist/system/number-strategy.js | 69 ++++ dist/system/repeat.js | 368 ++++-------------- dist/system/replaceable.js | 16 +- dist/system/signal-binding-behavior.js | 51 +++ dist/system/throttle-binding-behavior.js | 76 ++++ .../system/update-trigger-binding-behavior.js | 61 +++ dist/system/with.js | 30 +- doc/CHANGELOG.md | 31 ++ package.json | 2 +- 75 files changed, 3490 insertions(+), 1675 deletions(-) create mode 100644 dist/amd/array-collection-strategy.js create mode 100644 dist/amd/binding-mode-behaviors.js create mode 100644 dist/amd/binding-signaler.js create mode 100644 dist/amd/collection-strategy-locator.js create mode 100644 dist/amd/collection-strategy.js create mode 100644 dist/amd/debounce-binding-behavior.js delete mode 100644 dist/amd/global-behavior.js create mode 100644 dist/amd/map-collection-strategy.js create mode 100644 dist/amd/number-strategy.js create mode 100644 dist/amd/signal-binding-behavior.js create mode 100644 dist/amd/throttle-binding-behavior.js create mode 100644 dist/amd/update-trigger-binding-behavior.js create mode 100644 dist/commonjs/array-collection-strategy.js create mode 100644 dist/commonjs/binding-mode-behaviors.js create mode 100644 dist/commonjs/binding-signaler.js create mode 100644 dist/commonjs/collection-strategy-locator.js create mode 100644 dist/commonjs/collection-strategy.js create mode 100644 dist/commonjs/debounce-binding-behavior.js delete mode 100644 dist/commonjs/global-behavior.js create mode 100644 dist/commonjs/map-collection-strategy.js create mode 100644 dist/commonjs/number-strategy.js create mode 100644 dist/commonjs/signal-binding-behavior.js create mode 100644 dist/commonjs/throttle-binding-behavior.js create mode 100644 dist/commonjs/update-trigger-binding-behavior.js create mode 100644 dist/es6/array-collection-strategy.js create mode 100644 dist/es6/binding-mode-behaviors.js create mode 100644 dist/es6/binding-signaler.js create mode 100644 dist/es6/collection-strategy-locator.js create mode 100644 dist/es6/collection-strategy.js create mode 100644 dist/es6/debounce-binding-behavior.js delete mode 100644 dist/es6/global-behavior.js create mode 100644 dist/es6/map-collection-strategy.js create mode 100644 dist/es6/number-strategy.js create mode 100644 dist/es6/signal-binding-behavior.js create mode 100644 dist/es6/throttle-binding-behavior.js create mode 100644 dist/es6/update-trigger-binding-behavior.js create mode 100644 dist/system/array-collection-strategy.js create mode 100644 dist/system/binding-mode-behaviors.js create mode 100644 dist/system/binding-signaler.js create mode 100644 dist/system/collection-strategy-locator.js create mode 100644 dist/system/collection-strategy.js create mode 100644 dist/system/debounce-binding-behavior.js delete mode 100644 dist/system/global-behavior.js create mode 100644 dist/system/map-collection-strategy.js create mode 100644 dist/system/number-strategy.js create mode 100644 dist/system/signal-binding-behavior.js create mode 100644 dist/system/throttle-binding-behavior.js create mode 100644 dist/system/update-trigger-binding-behavior.js diff --git a/bower.json b/bower.json index 53b82b0..a83e7a7 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "aurelia-templating-resources", - "version": "0.16.1", + "version": "0.17.0", "description": "A standard set of behaviors, converters and other resources for use with the Aurelia templating library.", "keywords": [ "aurelia", diff --git a/dist/amd/array-collection-strategy.js b/dist/amd/array-collection-strategy.js new file mode 100644 index 0000000..9e602ba --- /dev/null +++ b/dist/amd/array-collection-strategy.js @@ -0,0 +1,96 @@ +define(['exports', './collection-strategy'], function (exports, _collectionStrategy) { + 'use strict'; + + exports.__esModule = true; + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var ArrayCollectionStrategy = (function (_CollectionStrategy) { + _inherits(ArrayCollectionStrategy, _CollectionStrategy); + + function ArrayCollectionStrategy() { + _classCallCheck(this, ArrayCollectionStrategy); + + _CollectionStrategy.apply(this, arguments); + } + + ArrayCollectionStrategy.prototype.processItems = function processItems(items) { + var i = undefined; + var ii = undefined; + var overrideContext = undefined; + var view = undefined; + this.items = items; + for (i = 0, ii = items.length; i < ii; ++i) { + overrideContext = _CollectionStrategy.prototype.createFullOverrideContext.call(this, items[i], i, ii); + view = this.viewFactory.create(); + view.bind(undefined, overrideContext); + this.viewSlot.add(view); + } + }; + + ArrayCollectionStrategy.prototype.getCollectionObserver = function getCollectionObserver(items) { + return this.observerLocator.getArrayObserver(items); + }; + + ArrayCollectionStrategy.prototype.handleChanges = function handleChanges(array, splices) { + var _this = this; + + var removeDelta = 0; + var viewSlot = this.viewSlot; + var rmPromises = []; + + for (var i = 0, ii = splices.length; i < ii; ++i) { + var splice = splices[i]; + var removed = splice.removed; + + for (var j = 0, jj = removed.length; j < jj; ++j) { + var viewOrPromise = viewSlot.removeAt(splice.index + removeDelta + rmPromises.length, true); + if (viewOrPromise instanceof Promise) { + rmPromises.push(viewOrPromise); + } + } + removeDelta -= splice.addedCount; + } + + if (rmPromises.length > 0) { + Promise.all(rmPromises).then(function () { + var spliceIndexLow = _this._handleAddedSplices(array, splices); + _this.updateOverrideContexts(spliceIndexLow); + }); + } else { + var spliceIndexLow = this._handleAddedSplices(array, splices); + _CollectionStrategy.prototype.updateOverrideContexts.call(this, spliceIndexLow); + } + }; + + ArrayCollectionStrategy.prototype._handleAddedSplices = function _handleAddedSplices(array, splices) { + var spliceIndex = undefined; + var spliceIndexLow = undefined; + var arrayLength = array.length; + for (var i = 0, ii = splices.length; i < ii; ++i) { + var splice = splices[i]; + var addIndex = spliceIndex = splice.index; + var end = splice.index + splice.addedCount; + + if (typeof spliceIndexLow === 'undefined' || spliceIndexLow === null || spliceIndexLow > splice.index) { + spliceIndexLow = spliceIndex; + } + + for (; addIndex < end; ++addIndex) { + var overrideContext = this.createFullOverrideContext(array[addIndex], addIndex, arrayLength); + var view = this.viewFactory.create(); + view.bind(undefined, overrideContext); + this.viewSlot.insert(addIndex, view); + } + } + + return spliceIndexLow; + }; + + return ArrayCollectionStrategy; + })(_collectionStrategy.CollectionStrategy); + + exports.ArrayCollectionStrategy = ArrayCollectionStrategy; +}); \ No newline at end of file diff --git a/dist/amd/aurelia-templating-resources.js b/dist/amd/aurelia-templating-resources.js index d03f0a4..2470fb0 100644 --- a/dist/amd/aurelia-templating-resources.js +++ b/dist/amd/aurelia-templating-resources.js @@ -1,4 +1,4 @@ -define(['exports', './compose', './if', './with', './repeat', './show', './global-behavior', './sanitize-html', './replaceable', './focus', './compile-spy', './view-spy', 'aurelia-templating', './dynamic-element', './css-resource', 'aurelia-pal', './html-sanitizer'], function (exports, _compose, _if, _with, _repeat, _show, _globalBehavior, _sanitizeHtml, _replaceable, _focus, _compileSpy, _viewSpy, _aureliaTemplating, _dynamicElement, _cssResource, _aureliaPal, _htmlSanitizer) { +define(['exports', './compose', './if', './with', './repeat', './show', './sanitize-html', './replaceable', './focus', './compile-spy', './view-spy', 'aurelia-templating', './dynamic-element', './css-resource', 'aurelia-pal', './html-sanitizer', './binding-mode-behaviors', './throttle-binding-behavior', './debounce-binding-behavior', './signal-binding-behavior', './binding-signaler', './update-trigger-binding-behavior'], function (exports, _compose, _if, _with, _repeat, _show, _sanitizeHtml, _replaceable, _focus, _compileSpy, _viewSpy, _aureliaTemplating, _dynamicElement, _cssResource, _aureliaPal, _htmlSanitizer, _bindingModeBehaviors, _throttleBindingBehavior, _debounceBindingBehavior, _signalBindingBehavior, _bindingSignaler, _updateTriggerBindingBehavior) { 'use strict'; exports.__esModule = true; @@ -10,7 +10,7 @@ define(['exports', './compose', './if', './with', './repeat', './show', './globa _aureliaPal.DOM.injectStyles('.aurelia-hide { display:none !important; }'); } - config.globalResources('./compose', './if', './with', './repeat', './show', './replaceable', './global-behavior', './sanitize-html', './focus', './compile-spy', './view-spy'); + config.globalResources('./compose', './if', './with', './repeat', './show', './replaceable', './sanitize-html', './focus', './compile-spy', './view-spy', './binding-mode-behaviors', './throttle-binding-behavior', './debounce-binding-behavior', './signal-binding-behavior', './update-trigger-binding-behavior'); var viewEngine = config.container.get(_aureliaTemplating.ViewEngine); var loader = config.aurelia.loader; @@ -58,10 +58,17 @@ define(['exports', './compose', './if', './with', './repeat', './show', './globa exports.Show = _show.Show; exports.HTMLSanitizer = _htmlSanitizer.HTMLSanitizer; exports.SanitizeHTMLValueConverter = _sanitizeHtml.SanitizeHTMLValueConverter; - exports.GlobalBehavior = _globalBehavior.GlobalBehavior; exports.Replaceable = _replaceable.Replaceable; exports.Focus = _focus.Focus; exports.CompileSpy = _compileSpy.CompileSpy; exports.ViewSpy = _viewSpy.ViewSpy; exports.configure = configure; + exports.OneTimeBindingBehavior = _bindingModeBehaviors.OneTimeBindingBehavior; + exports.OneWayBindingBehavior = _bindingModeBehaviors.OneWayBindingBehavior; + exports.TwoWayBindingBehavior = _bindingModeBehaviors.TwoWayBindingBehavior; + exports.ThrottleBindingBehavior = _throttleBindingBehavior.ThrottleBindingBehavior; + exports.DebounceBindingBehavior = _debounceBindingBehavior.DebounceBindingBehavior; + exports.SignalBindingBehavior = _signalBindingBehavior.SignalBindingBehavior; + exports.BindingSignaler = _bindingSignaler.BindingSignaler; + exports.UpdateTriggerBindingBehavior = _updateTriggerBindingBehavior.UpdateTriggerBindingBehavior; }); \ No newline at end of file diff --git a/dist/amd/binding-mode-behaviors.js b/dist/amd/binding-mode-behaviors.js new file mode 100644 index 0000000..26a81c1 --- /dev/null +++ b/dist/amd/binding-mode-behaviors.js @@ -0,0 +1,71 @@ +define(['exports', 'aurelia-binding'], function (exports, _aureliaBinding) { + 'use strict'; + + exports.__esModule = true; + + function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + var ModeBindingBehavior = (function () { + function ModeBindingBehavior(mode) { + _classCallCheck(this, ModeBindingBehavior); + + this.mode = mode; + } + + ModeBindingBehavior.prototype.bind = function bind(binding, source, lookupFunctions) { + binding.originalMode = binding.mode; + binding.mode = this.mode; + }; + + ModeBindingBehavior.prototype.unbind = function unbind(binding, source) { + binding.mode = binding.originalMode; + binding.originalMode = null; + }; + + return ModeBindingBehavior; + })(); + + var OneTimeBindingBehavior = (function (_ModeBindingBehavior) { + _inherits(OneTimeBindingBehavior, _ModeBindingBehavior); + + function OneTimeBindingBehavior() { + _classCallCheck(this, OneTimeBindingBehavior); + + _ModeBindingBehavior.call(this, _aureliaBinding.bindingMode.oneTime); + } + + return OneTimeBindingBehavior; + })(ModeBindingBehavior); + + exports.OneTimeBindingBehavior = OneTimeBindingBehavior; + + var OneWayBindingBehavior = (function (_ModeBindingBehavior2) { + _inherits(OneWayBindingBehavior, _ModeBindingBehavior2); + + function OneWayBindingBehavior() { + _classCallCheck(this, OneWayBindingBehavior); + + _ModeBindingBehavior2.call(this, _aureliaBinding.bindingMode.oneWay); + } + + return OneWayBindingBehavior; + })(ModeBindingBehavior); + + exports.OneWayBindingBehavior = OneWayBindingBehavior; + + var TwoWayBindingBehavior = (function (_ModeBindingBehavior3) { + _inherits(TwoWayBindingBehavior, _ModeBindingBehavior3); + + function TwoWayBindingBehavior() { + _classCallCheck(this, TwoWayBindingBehavior); + + _ModeBindingBehavior3.call(this, _aureliaBinding.bindingMode.twoWay); + } + + return TwoWayBindingBehavior; + })(ModeBindingBehavior); + + exports.TwoWayBindingBehavior = TwoWayBindingBehavior; +}); \ No newline at end of file diff --git a/dist/amd/binding-signaler.js b/dist/amd/binding-signaler.js new file mode 100644 index 0000000..d3a1db7 --- /dev/null +++ b/dist/amd/binding-signaler.js @@ -0,0 +1,30 @@ +define(['exports', 'aurelia-binding'], function (exports, _aureliaBinding) { + 'use strict'; + + exports.__esModule = true; + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + var BindingSignaler = (function () { + function BindingSignaler() { + _classCallCheck(this, BindingSignaler); + + this.signals = {}; + } + + BindingSignaler.prototype.signal = function signal(name) { + var bindings = this.signals[name]; + if (!bindings) { + return; + } + var i = bindings.length; + while (i--) { + bindings[i].call(_aureliaBinding.sourceContext); + } + }; + + return BindingSignaler; + })(); + + exports.BindingSignaler = BindingSignaler; +}); \ No newline at end of file diff --git a/dist/amd/collection-strategy-locator.js b/dist/amd/collection-strategy-locator.js new file mode 100644 index 0000000..c5bb86c --- /dev/null +++ b/dist/amd/collection-strategy-locator.js @@ -0,0 +1,50 @@ +define(['exports', 'aurelia-dependency-injection', './array-collection-strategy', './map-collection-strategy', './number-strategy'], function (exports, _aureliaDependencyInjection, _arrayCollectionStrategy, _mapCollectionStrategy, _numberStrategy) { + 'use strict'; + + exports.__esModule = true; + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + var CollectionStrategyLocator = (function () { + function CollectionStrategyLocator(container) { + _classCallCheck(this, _CollectionStrategyLocator); + + this.container = container; + this.strategies = []; + this.matchers = []; + + this.addStrategy(_arrayCollectionStrategy.ArrayCollectionStrategy, function (items) { + return items instanceof Array; + }); + this.addStrategy(_mapCollectionStrategy.MapCollectionStrategy, function (items) { + return items instanceof Map; + }); + this.addStrategy(_numberStrategy.NumberStrategy, function (items) { + return typeof items === 'number'; + }); + } + + CollectionStrategyLocator.prototype.addStrategy = function addStrategy(collectionStrategy, matcher) { + this.strategies.push(collectionStrategy); + this.matchers.push(matcher); + }; + + CollectionStrategyLocator.prototype.getStrategy = function getStrategy(items) { + var matchers = this.matchers; + + for (var i = 0, ii = matchers.length; i < ii; ++i) { + if (matchers[i](items)) { + return this.container.get(this.strategies[i]); + } + } + + throw new Error('Object in "repeat" must have a valid collection strategy.'); + }; + + var _CollectionStrategyLocator = CollectionStrategyLocator; + CollectionStrategyLocator = _aureliaDependencyInjection.inject(_aureliaDependencyInjection.Container)(CollectionStrategyLocator) || CollectionStrategyLocator; + return CollectionStrategyLocator; + })(); + + exports.CollectionStrategyLocator = CollectionStrategyLocator; +}); \ No newline at end of file diff --git a/dist/amd/collection-strategy.js b/dist/amd/collection-strategy.js new file mode 100644 index 0000000..f11c39a --- /dev/null +++ b/dist/amd/collection-strategy.js @@ -0,0 +1,90 @@ +define(['exports', 'aurelia-dependency-injection', 'aurelia-binding'], function (exports, _aureliaDependencyInjection, _aureliaBinding) { + 'use strict'; + + exports.__esModule = true; + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + var CollectionStrategy = (function () { + function CollectionStrategy(observerLocator) { + _classCallCheck(this, _CollectionStrategy); + + this.observerLocator = observerLocator; + } + + CollectionStrategy.prototype.initialize = function initialize(repeat, bindingContext, overrideContext) { + this.viewFactory = repeat.viewFactory; + this.viewSlot = repeat.viewSlot; + this.items = repeat.items; + this.local = repeat.local; + this.key = repeat.key; + this.value = repeat.value; + this.bindingContext = bindingContext; + this.overrideContext = overrideContext; + }; + + CollectionStrategy.prototype.dispose = function dispose() { + this.viewFactory = null; + this.viewSlot = null; + this.items = null; + this.local = null; + this.key = null; + this.value = null; + this.bindingContext = null; + this.overrideContext = null; + }; + + CollectionStrategy.prototype.updateOverrideContexts = function updateOverrideContexts(startIndex) { + var children = this.viewSlot.children; + var length = children.length; + + if (startIndex > 0) { + startIndex = startIndex - 1; + } + + for (; startIndex < length; ++startIndex) { + this.updateOverrideContext(children[startIndex].overrideContext, startIndex, length); + } + }; + + CollectionStrategy.prototype.createFullOverrideContext = function createFullOverrideContext(data, index, length, key) { + var context = this.createBaseOverrideContext(data, key); + return this.updateOverrideContext(context, index, length); + }; + + CollectionStrategy.prototype.createBaseOverrideContext = function createBaseOverrideContext(data, key) { + var context = _aureliaBinding.createOverrideContext(undefined, this.overrideContext); + + if (typeof key !== 'undefined') { + context[this.key] = key; + context[this.value] = data; + } else { + context[this.local] = data; + } + + return context; + }; + + CollectionStrategy.prototype.updateOverrideContext = function updateOverrideContext(context, index, length) { + var first = index === 0; + var last = index === length - 1; + var even = index % 2 === 0; + + context.$index = index; + context.$first = first; + context.$last = last; + context.$middle = !(first || last); + context.$odd = !even; + context.$even = even; + + return context; + }; + + var _CollectionStrategy = CollectionStrategy; + CollectionStrategy = _aureliaDependencyInjection.transient()(CollectionStrategy) || CollectionStrategy; + CollectionStrategy = _aureliaDependencyInjection.inject(_aureliaBinding.ObserverLocator)(CollectionStrategy) || CollectionStrategy; + return CollectionStrategy; + })(); + + exports.CollectionStrategy = CollectionStrategy; +}); \ No newline at end of file diff --git a/dist/amd/compose.js b/dist/amd/compose.js index a4ff376..a8e4110 100644 --- a/dist/amd/compose.js +++ b/dist/amd/compose.js @@ -44,6 +44,8 @@ define(['exports', 'aurelia-dependency-injection', 'aurelia-task-queue', 'aureli this.viewSlot = viewSlot; this.viewResources = viewResources; this.taskQueue = taskQueue; + this.currentController = null; + this.currentViewModel = null; } Compose.prototype.bind = function bind(bindingContext) { @@ -132,16 +134,16 @@ define(['exports', 'aurelia-dependency-injection', 'aurelia-task-queue', 'aureli container: composer.container, viewSlot: composer.viewSlot, viewResources: composer.viewResources, - currentBehavior: composer.currentBehavior, + currentController: composer.currentController, host: composer.element }); } function processInstruction(composer, instruction) { composer.currentInstruction = null; - composer.compositionEngine.compose(instruction).then(function (next) { - composer.currentBehavior = next; - composer.currentViewModel = next ? next.bindingContext : null; + composer.compositionEngine.compose(instruction).then(function (controller) { + composer.currentController = controller; + composer.currentViewModel = controller ? controller.viewModel : null; }); } }); \ No newline at end of file diff --git a/dist/amd/debounce-binding-behavior.js b/dist/amd/debounce-binding-behavior.js new file mode 100644 index 0000000..74a5899 --- /dev/null +++ b/dist/amd/debounce-binding-behavior.js @@ -0,0 +1,61 @@ +define(['exports', 'aurelia-binding'], function (exports, _aureliaBinding) { + 'use strict'; + + exports.__esModule = true; + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + function debounce(newValue) { + var _this = this; + + var state = this.debounceState; + if (state.immediate) { + state.immediate = false; + this.debouncedMethod(newValue); + return; + } + clearTimeout(state.timeoutId); + state.timeoutId = setTimeout(function () { + return _this.debouncedMethod(newValue); + }, state.delay); + } + + var DebounceBindingBehavior = (function () { + function DebounceBindingBehavior() { + _classCallCheck(this, DebounceBindingBehavior); + } + + DebounceBindingBehavior.prototype.bind = function bind(binding, source) { + var delay = arguments.length <= 2 || arguments[2] === undefined ? 200 : arguments[2]; + + var methodToDebounce = 'updateTarget'; + if (binding.callSource) { + methodToDebounce = 'callSource'; + } else if (binding.updateSource && binding.mode === _aureliaBinding.bindingMode.twoWay) { + methodToDebounce = 'updateSource'; + } + + binding.debouncedMethod = binding[methodToDebounce]; + binding.debouncedMethod.originalName = methodToDebounce; + + binding[methodToDebounce] = debounce; + + binding.debounceState = { + delay: delay, + timeoutId: null, + immediate: methodToDebounce === 'updateTarget' }; + }; + + DebounceBindingBehavior.prototype.unbind = function unbind(binding, source) { + var methodToRestore = binding.debouncedMethod.originalName; + binding[methodToRestore] = binding.debouncedMethod; + binding.debouncedMethod = null; + clearTimeout(binding.debounceState.timeoutId); + binding.debounceState = null; + }; + + return DebounceBindingBehavior; + })(); + + exports.DebounceBindingBehavior = DebounceBindingBehavior; +}); \ No newline at end of file diff --git a/dist/amd/global-behavior.js b/dist/amd/global-behavior.js deleted file mode 100644 index a3bb3c8..0000000 --- a/dist/amd/global-behavior.js +++ /dev/null @@ -1,102 +0,0 @@ -define(['exports', 'aurelia-dependency-injection', 'aurelia-templating', 'aurelia-logging', 'aurelia-pal'], function (exports, _aureliaDependencyInjection, _aureliaTemplating, _aureliaLogging, _aureliaPal) { - 'use strict'; - - exports.__esModule = true; - - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - - var GlobalBehavior = (function () { - function GlobalBehavior(element) { - _classCallCheck(this, _GlobalBehavior); - - this.element = element; - _aureliaLogging.getLogger('templating-resources').warn('The "GlobalBehavior" behavior will be removed in the next release.'); - } - - GlobalBehavior.prototype.bind = function bind() { - var handler = GlobalBehavior.handlers[this.aureliaAttrName]; - - if (!handler) { - throw new Error('Binding handler not found for \'' + this.aureliaAttrName + '.' + this.aureliaCommand + '\'. Element:\n' + this.element.outerHTML + '\n'); - } - - try { - this.handler = handler.bind(this, this.element, this.aureliaCommand) || handler; - } catch (error) { - throw new _aureliaPal.AggregateError('Conventional binding handler failed.', error); - } - }; - - GlobalBehavior.prototype.attached = function attached() { - if (this.handler && 'attached' in this.handler) { - this.handler.attached(this, this.element); - } - }; - - GlobalBehavior.prototype.detached = function detached() { - if (this.handler && 'detached' in this.handler) { - this.handler.detached(this, this.element); - } - }; - - GlobalBehavior.prototype.unbind = function unbind() { - if (this.handler && 'unbind' in this.handler) { - this.handler.unbind(this, this.element); - } - - this.handler = null; - }; - - var _GlobalBehavior = GlobalBehavior; - GlobalBehavior = _aureliaDependencyInjection.inject(_aureliaPal.DOM.Element)(GlobalBehavior) || GlobalBehavior; - GlobalBehavior = _aureliaTemplating.dynamicOptions(GlobalBehavior) || GlobalBehavior; - GlobalBehavior = _aureliaTemplating.customAttribute('global-behavior')(GlobalBehavior) || GlobalBehavior; - return GlobalBehavior; - })(); - - exports.GlobalBehavior = GlobalBehavior; - - GlobalBehavior.createSettingsFromBehavior = function (behavior) { - var settings = {}; - - for (var key in behavior) { - if (key === 'aureliaAttrName' || key === 'aureliaCommand' || !behavior.hasOwnProperty(key)) { - continue; - } - - settings[key] = behavior[key]; - } - - return settings; - }; - - GlobalBehavior.jQueryPlugins = {}; - - GlobalBehavior.handlers = { - jquery: { - bind: function bind(behavior, element, command) { - var settings = GlobalBehavior.createSettingsFromBehavior(behavior); - var pluginName = GlobalBehavior.jQueryPlugins[command] || command; - var jqueryElement = _aureliaPal.PLATFORM.global.jQuery(element); - - if (!jqueryElement[pluginName]) { - _aureliaLogging.getLogger('templating-resources').warn('Could not find the jQuery plugin ' + pluginName + ', possibly due to case mismatch. Trying to enumerate jQuery methods in lowercase. Add the correctly cased plugin name to the GlobalBehavior to avoid this performance hit.'); - - for (var prop in jqueryElement) { - if (prop.toLowerCase() === pluginName) { - pluginName = prop; - } - } - } - - behavior.plugin = jqueryElement[pluginName](settings); - }, - unbind: function unbind(behavior, element) { - if (typeof behavior.plugin.destroy === 'function') { - behavior.plugin.destroy(); - behavior.plugin = null; - } - } - } - }; -}); \ No newline at end of file diff --git a/dist/amd/if.js b/dist/amd/if.js index accf9c2..e5aadbd 100644 --- a/dist/amd/if.js +++ b/dist/amd/if.js @@ -14,11 +14,13 @@ define(['exports', 'aurelia-templating', 'aurelia-dependency-injection', 'aureli this.showing = false; this.taskQueue = taskQueue; this.view = null; - this.$parent = null; + this.bindingContext = null; + this.overrideContext = null; } - If.prototype.bind = function bind(bindingContext) { - this.$parent = bindingContext; + If.prototype.bind = function bind(bindingContext, overrideContext) { + this.bindingContext = bindingContext; + this.overrideContext = overrideContext; this.valueChanged(this.value); }; @@ -44,14 +46,14 @@ define(['exports', 'aurelia-templating', 'aurelia-dependency-injection', 'aureli } if (this.view === null) { - this.view = this.viewFactory.create(this.$parent); + this.view = this.viewFactory.create(); } if (!this.showing) { this.showing = true; if (!this.view.isBound) { - this.view.bind(this.$parent); + this.view.bind(this.bindingContext, this.overrideContext); } this.viewSlot.add(this.view); diff --git a/dist/amd/map-collection-strategy.js b/dist/amd/map-collection-strategy.js new file mode 100644 index 0000000..68276cb --- /dev/null +++ b/dist/amd/map-collection-strategy.js @@ -0,0 +1,121 @@ +define(['exports', './collection-strategy'], function (exports, _collectionStrategy) { + 'use strict'; + + exports.__esModule = true; + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var MapCollectionStrategy = (function (_CollectionStrategy) { + _inherits(MapCollectionStrategy, _CollectionStrategy); + + function MapCollectionStrategy() { + _classCallCheck(this, MapCollectionStrategy); + + _CollectionStrategy.apply(this, arguments); + } + + MapCollectionStrategy.prototype.getCollectionObserver = function getCollectionObserver(items) { + return this.observerLocator.getMapObserver(items); + }; + + MapCollectionStrategy.prototype.processItems = function processItems(items) { + var _this = this; + + var viewFactory = this.viewFactory; + var viewSlot = this.viewSlot; + var index = 0; + var overrideContext = undefined; + var view = undefined; + + items.forEach(function (value, key) { + overrideContext = _this.createFullOverrideContext(value, index, items.size, key); + view = viewFactory.create(); + view.bind(undefined, overrideContext); + viewSlot.add(view); + ++index; + }); + }; + + MapCollectionStrategy.prototype.handleChanges = function handleChanges(map, records) { + var _this2 = this; + + var viewSlot = this.viewSlot; + var key = undefined; + var i = undefined; + var ii = undefined; + var view = undefined; + var overrideContext = undefined; + var removeIndex = undefined; + var record = undefined; + var rmPromises = []; + var viewOrPromise = undefined; + + for (i = 0, ii = records.length; i < ii; ++i) { + record = records[i]; + key = record.key; + switch (record.type) { + case 'update': + removeIndex = this._getViewIndexByKey(key); + viewOrPromise = viewSlot.removeAt(removeIndex, true); + if (viewOrPromise instanceof Promise) { + rmPromises.push(viewOrPromise); + } + overrideContext = this.createFullOverrideContext(map.get(key), removeIndex, map.size, key); + view = this.viewFactory.create(); + view.bind(undefined, overrideContext); + viewSlot.insert(removeIndex, view); + break; + case 'add': + overrideContext = this.createFullOverrideContext(map.get(key), map.size - 1, map.size, key); + view = this.viewFactory.create(); + view.bind(undefined, overrideContext); + viewSlot.insert(map.size - 1, view); + break; + case 'delete': + if (record.oldValue === undefined) { + return; + } + removeIndex = this._getViewIndexByKey(key); + viewOrPromise = viewSlot.removeAt(removeIndex, true); + if (viewOrPromise instanceof Promise) { + rmPromises.push(viewOrPromise); + } + break; + case 'clear': + viewSlot.removeAll(true); + break; + default: + continue; + } + } + + if (rmPromises.length > 0) { + Promise.all(rmPromises).then(function () { + _this2.updateOverrideContexts(0); + }); + } else { + this.updateOverrideContexts(0); + } + }; + + MapCollectionStrategy.prototype._getViewIndexByKey = function _getViewIndexByKey(key) { + var viewSlot = this.viewSlot; + var i = undefined; + var ii = undefined; + var child = undefined; + + for (i = 0, ii = viewSlot.children.length; i < ii; ++i) { + child = viewSlot.children[i]; + if (child.overrideContext[this.key] === key) { + return i; + } + } + }; + + return MapCollectionStrategy; + })(_collectionStrategy.CollectionStrategy); + + exports.MapCollectionStrategy = MapCollectionStrategy; +}); \ No newline at end of file diff --git a/dist/amd/number-strategy.js b/dist/amd/number-strategy.js new file mode 100644 index 0000000..b126c32 --- /dev/null +++ b/dist/amd/number-strategy.js @@ -0,0 +1,62 @@ +define(['exports', './collection-strategy'], function (exports, _collectionStrategy) { + 'use strict'; + + exports.__esModule = true; + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var NumberStrategy = (function (_CollectionStrategy) { + _inherits(NumberStrategy, _CollectionStrategy); + + function NumberStrategy() { + _classCallCheck(this, NumberStrategy); + + _CollectionStrategy.apply(this, arguments); + } + + NumberStrategy.prototype.getCollectionObserver = function getCollectionObserver() { + return; + }; + + NumberStrategy.prototype.processItems = function processItems(value) { + var viewFactory = this.viewFactory; + var viewSlot = this.viewSlot; + var childrenLength = viewSlot.children.length; + var i = undefined; + var ii = undefined; + var overrideContext = undefined; + var view = undefined; + var viewsToRemove = undefined; + + value = Math.floor(value); + viewsToRemove = childrenLength - value; + + if (viewsToRemove > 0) { + if (viewsToRemove > childrenLength) { + viewsToRemove = childrenLength; + } + + for (i = 0, ii = viewsToRemove; i < ii; ++i) { + viewSlot.removeAt(childrenLength - (i + 1), true); + } + + return; + } + + for (i = childrenLength, ii = value; i < ii; ++i) { + overrideContext = this.createFullOverrideContext(i, i, ii); + view = viewFactory.create(); + view.bind(undefined, overrideContext); + viewSlot.add(view); + } + + this.updateOverrideContexts(0); + }; + + return NumberStrategy; + })(_collectionStrategy.CollectionStrategy); + + exports.NumberStrategy = NumberStrategy; +}); \ No newline at end of file diff --git a/dist/amd/repeat.js b/dist/amd/repeat.js index 91aa45a..b059476 100644 --- a/dist/amd/repeat.js +++ b/dist/amd/repeat.js @@ -1,4 +1,4 @@ -define(['exports', 'aurelia-dependency-injection', 'aurelia-binding', 'aurelia-templating'], function (exports, _aureliaDependencyInjection, _aureliaBinding, _aureliaTemplating) { +define(['exports', 'aurelia-dependency-injection', 'aurelia-binding', 'aurelia-templating', './collection-strategy-locator'], function (exports, _aureliaDependencyInjection, _aureliaBinding, _aureliaTemplating, _collectionStrategyLocator) { 'use strict'; exports.__esModule = true; @@ -34,7 +34,7 @@ define(['exports', 'aurelia-dependency-injection', 'aurelia-binding', 'aurelia-t enumerable: true }], null, _instanceInitializers); - function Repeat(viewFactory, viewSlot, observerLocator) { + function Repeat(viewFactory, instruction, viewSlot, viewResources, observerLocator, collectionStrategyLocator) { _classCallCheck(this, _Repeat); _defineDecoratedPropertyDescriptor(this, 'items', _instanceInitializers); @@ -46,66 +46,40 @@ define(['exports', 'aurelia-dependency-injection', 'aurelia-binding', 'aurelia-t _defineDecoratedPropertyDescriptor(this, 'value', _instanceInitializers); this.viewFactory = viewFactory; + this.instruction = instruction; this.viewSlot = viewSlot; + this.lookupFunctions = viewResources.lookupFunctions; this.observerLocator = observerLocator; this.local = 'item'; this.key = 'key'; this.value = 'value'; + this.collectionStrategyLocator = collectionStrategyLocator; } Repeat.prototype.call = function call(context, changes) { this[context](this.items, changes); }; - Repeat.prototype.bind = function bind(bindingContext) { + Repeat.prototype.bind = function bind(bindingContext, overrideContext) { var items = this.items; - var viewSlot = this.viewSlot; - var observer = undefined; - - this.bindingContext = bindingContext; - - if (!items) { - if (this.oldItems) { - viewSlot.removeAll(true); - } - - return; - } - - if (this.oldItems === items) { - if (items instanceof Map) { - var records = _aureliaBinding.getChangeRecords(items); - this.collectionObserver = this.observerLocator.getMapObserver(items); - - this.handleMapChangeRecords(items, records); - - this.callContext = 'handleMapChangeRecords'; - this.collectionObserver.subscribe(this.callContext, this); - } else if (items instanceof Array) { - var splices = _aureliaBinding.calcSplices(items, 0, items.length, this.lastBoundItems, 0, this.lastBoundItems.length); - this.collectionObserver = this.observerLocator.getArrayObserver(items); - - this.handleSplices(items, splices); - this.lastBoundItems = this.oldItems = null; - - this.callContext = 'handleSplices'; - this.collectionObserver.subscribe(this.callContext, this); - } + if (items === undefined || items === null) { return; - } else if (this.oldItems) { - viewSlot.removeAll(true); } + this.sourceExpression = getSourceExpression(this.instruction, 'repeat.for'); + this.scope = { bindingContext: bindingContext, overrideContext: overrideContext }; + this.collectionStrategy = this.collectionStrategyLocator.getStrategy(this.items); + this.collectionStrategy.initialize(this, bindingContext, overrideContext); this.processItems(); }; Repeat.prototype.unbind = function unbind() { - this.oldItems = this.items; - - if (this.items instanceof Array) { - this.lastBoundItems = this.items.slice(0); - } - + this.sourceExpression = null; + this.scope = null; + this.collectionStrategy.dispose(); + this.items = null; + this.collectionStrategy = null; + this.viewSlot.removeAll(true); this.unsubscribeCollection(); }; @@ -138,280 +112,88 @@ define(['exports', 'aurelia-dependency-injection', 'aurelia-binding', 'aurelia-t if (rmPromise instanceof Promise) { rmPromise.then(function () { - _this.processItemsByType(); + _this.processItemsByStrategy(); }); } else { - this.processItemsByType(); + this.processItemsByStrategy(); } }; - Repeat.prototype.processItemsByType = function processItemsByType() { - var items = this.items; - if (items instanceof Array) { - this.processArrayItems(items); - } else if (items instanceof Map) { - this.processMapEntries(items); - } else if (typeof items === 'number') { - this.processNumber(items); - } else { - throw new Error('Object in "repeat" must be of type Array, Map or Number'); + Repeat.prototype.getInnerCollection = function getInnerCollection() { + var expression = unwrapExpression(this.sourceExpression); + if (!expression) { + return null; } + return expression.evaluate(this.scope, null); }; - Repeat.prototype.processArrayItems = function processArrayItems(items) { - var viewFactory = this.viewFactory; - var viewSlot = this.viewSlot; - var i = undefined; - var ii = undefined; - var row = undefined; - var view = undefined; - var observer = undefined; - - this.collectionObserver = this.observerLocator.getArrayObserver(items); - - for (i = 0, ii = items.length; i < ii; ++i) { - row = this.createFullBindingContext(items[i], i, ii); - view = viewFactory.create(row); - viewSlot.add(view); + Repeat.prototype.observeInnerCollection = function observeInnerCollection() { + var 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 = 'handleSplices'; - this.collectionObserver.subscribe(this.callContext, this); - }; - - Repeat.prototype.processMapEntries = function processMapEntries(items) { - var _this2 = this; - - var viewFactory = this.viewFactory; - var viewSlot = this.viewSlot; - var index = 0; - var row = undefined; - var view = undefined; - var observer = undefined; - - this.collectionObserver = this.observerLocator.getMapObserver(items); - - items.forEach(function (value, key) { - row = _this2.createFullExecutionKvpContext(key, value, index, items.size); - view = viewFactory.create(row); - viewSlot.add(view); - ++index; - }); - - this.callContext = 'handleMapChangeRecords'; + this.callContext = 'handleInnerCollectionChanges'; this.collectionObserver.subscribe(this.callContext, this); + return true; }; - Repeat.prototype.processNumber = function processNumber(value) { - var viewFactory = this.viewFactory; - var viewSlot = this.viewSlot; - var childrenLength = viewSlot.children.length; - var i = undefined; - var ii = undefined; - var row = undefined; - var view = undefined; - var viewsToRemove = undefined; - - value = Math.floor(value); - viewsToRemove = childrenLength - value; - - if (viewsToRemove > 0) { - if (viewsToRemove > childrenLength) { - viewsToRemove = childrenLength; - } - - for (i = 0, ii = viewsToRemove; i < ii; ++i) { - viewSlot.removeAt(childrenLength - (i + 1), true); - } - - return; - } - - for (i = childrenLength, ii = value; i < ii; ++i) { - row = this.createFullBindingContext(i, i, ii); - view = viewFactory.create(row); - viewSlot.add(view); - } - }; - - Repeat.prototype.createBaseBindingContext = function createBaseBindingContext(data) { - var context = {}; - context[this.local] = data; - context.$parent = this.bindingContext; - return context; - }; - - Repeat.prototype.createBaseExecutionKvpContext = function createBaseExecutionKvpContext(key, value) { - var context = {}; - context[this.key] = key; - context[this.value] = value; - context.$parent = this.bindingContext; - return context; - }; - - Repeat.prototype.createFullBindingContext = function createFullBindingContext(data, index, length) { - var context = this.createBaseBindingContext(data); - return this.updateBindingContext(context, index, length); - }; - - Repeat.prototype.createFullExecutionKvpContext = function createFullExecutionKvpContext(key, value, index, length) { - var context = this.createBaseExecutionKvpContext(key, value); - return this.updateBindingContext(context, index, length); - }; - - Repeat.prototype.updateBindingContext = function updateBindingContext(context, index, length) { - var first = index === 0; - var last = index === length - 1; - var even = index % 2 === 0; - - context.$index = index; - context.$first = first; - context.$last = last; - context.$middle = !(first || last); - context.$odd = !even; - context.$even = even; - - return context; - }; - - Repeat.prototype.updateBindingContexts = function updateBindingContexts(startIndex) { - var children = this.viewSlot.children; - var length = children.length; - - if (startIndex > 0) { - startIndex = startIndex - 1; - } - - for (; startIndex < length; ++startIndex) { - this.updateBindingContext(children[startIndex].bindingContext, startIndex, length); - } - }; - - Repeat.prototype.handleSplices = function handleSplices(array, splices) { - var _this3 = this; - - var removeDelta = 0; - var viewSlot = this.viewSlot; - var rmPromises = []; - - for (var i = 0, ii = splices.length; i < ii; ++i) { - var splice = splices[i]; - var removed = splice.removed; - - for (var j = 0, jj = removed.length; j < jj; ++j) { - var viewOrPromise = viewSlot.removeAt(splice.index + removeDelta + rmPromises.length, true); - if (viewOrPromise instanceof Promise) { - rmPromises.push(viewOrPromise); - } - } - removeDelta -= splice.addedCount; - } - - if (rmPromises.length > 0) { - Promise.all(rmPromises).then(function () { - var spliceIndexLow = _this3.handleAddedSplices(array, splices); - _this3.updateBindingContexts(spliceIndexLow); - }); - } else { - var spliceIndexLow = this.handleAddedSplices(array, splices); - this.updateBindingContexts(spliceIndexLow); + Repeat.prototype.observeCollection = function observeCollection() { + var items = this.items; + this.collectionObserver = this.collectionStrategy.getCollectionObserver(items); + if (this.collectionObserver) { + this.callContext = 'handleCollectionChanges'; + this.collectionObserver.subscribe(this.callContext, this); } }; - Repeat.prototype.handleAddedSplices = function handleAddedSplices(array, splices) { - var spliceIndex = undefined; - var spliceIndexLow = undefined; - var arrayLength = array.length; - for (var i = 0, ii = splices.length; i < ii; ++i) { - var splice = splices[i]; - var addIndex = spliceIndex = splice.index; - var end = splice.index + splice.addedCount; - - if (typeof spliceIndexLow === 'undefined' || spliceIndexLow === null || spliceIndexLow > splice.index) { - spliceIndexLow = spliceIndex; - } - - for (; addIndex < end; ++addIndex) { - var row = this.createFullBindingContext(array[addIndex], addIndex, arrayLength); - var view = this.viewFactory.create(row); - this.viewSlot.insert(addIndex, view); - } + Repeat.prototype.processItemsByStrategy = function processItemsByStrategy() { + if (!this.observeInnerCollection()) { + this.observeCollection(); } - - return spliceIndexLow; + this.collectionStrategy.processItems(this.items); }; - Repeat.prototype.handleMapChangeRecords = function handleMapChangeRecords(map, records) { - var viewSlot = this.viewSlot; - var key = undefined; - var i = undefined; - var ii = undefined; - var view = undefined; - var children = undefined; - var length = undefined; - var row = undefined; - var removeIndex = undefined; - var record = undefined; - - for (i = 0, ii = records.length; i < ii; ++i) { - record = records[i]; - key = record.key; - switch (record.type) { - case 'update': - removeIndex = this.getViewIndexByKey(key); - viewSlot.removeAt(removeIndex, true); - row = this.createBaseExecutionKvpContext(key, map.get(key)); - view = this.viewFactory.create(row); - viewSlot.insert(removeIndex, view); - break; - case 'add': - row = this.createBaseExecutionKvpContext(key, map.get(key)); - view = this.viewFactory.create(row); - viewSlot.insert(map.size, view); - break; - case 'delete': - if (!record.oldValue) { - return; - } - removeIndex = this.getViewIndexByKey(key); - viewSlot.removeAt(removeIndex, true); - break; - case 'clear': - viewSlot.removeAll(true); - break; - default: - continue; - } - } - - children = viewSlot.children; - length = children.length; - - for (i = 0; i < length; i++) { - this.updateBindingContext(children[i].bindingContext, i, length); - } + Repeat.prototype.handleCollectionChanges = function handleCollectionChanges(collection, changes) { + this.collectionStrategy.handleChanges(collection, changes); }; - Repeat.prototype.getViewIndexByKey = function getViewIndexByKey(key) { - var viewSlot = this.viewSlot; - var i = undefined; - var ii = undefined; - var child = undefined; - - for (i = 0, ii = viewSlot.children.length; i < ii; ++i) { - child = viewSlot.children[i]; - if (child.bindings[0].source[this.key] === key) { - return i; - } + Repeat.prototype.handleInnerCollectionChanges = function handleInnerCollectionChanges(collection, changes) { + var newItems = this.sourceExpression.evaluate(this.scope, this.lookupFunctions); + if (newItems === this.items) { + return; } + this.items = newItems; + this.itemsChanged(); }; var _Repeat = Repeat; - Repeat = _aureliaDependencyInjection.inject(_aureliaTemplating.BoundViewFactory, _aureliaTemplating.ViewSlot, _aureliaBinding.ObserverLocator)(Repeat) || Repeat; + Repeat = _aureliaDependencyInjection.inject(_aureliaTemplating.BoundViewFactory, _aureliaTemplating.TargetInstruction, _aureliaTemplating.ViewSlot, _aureliaTemplating.ViewResources, _aureliaBinding.ObserverLocator, _collectionStrategyLocator.CollectionStrategyLocator)(Repeat) || Repeat; Repeat = _aureliaTemplating.templateController(Repeat) || Repeat; Repeat = _aureliaTemplating.customAttribute('repeat')(Repeat) || Repeat; return Repeat; })(); exports.Repeat = Repeat; + + function getSourceExpression(instruction, attrName) { + return instruction.behaviorInstructions.filter(function (bi) { + return bi.originalAttrName === attrName; + })[0].attributes.items.sourceExpression; + } + + function unwrapExpression(expression) { + var unwrapped = false; + while (expression instanceof _aureliaBinding.BindingBehavior) { + expression = expression.expression; + } + while (expression instanceof _aureliaBinding.ValueConverter) { + expression = expression.expression; + unwrapped = true; + } + return unwrapped ? expression : null; + } }); \ No newline at end of file diff --git a/dist/amd/replaceable.js b/dist/amd/replaceable.js index 476259b..9f3cbdc 100644 --- a/dist/amd/replaceable.js +++ b/dist/amd/replaceable.js @@ -11,14 +11,20 @@ define(['exports', 'aurelia-dependency-injection', 'aurelia-templating'], functi this.viewFactory = viewFactory; this.viewSlot = viewSlot; - this.needsReplacement = true; + this.view = null; } - Replaceable.prototype.bind = function bind() { - if (this.needsReplacement) { - this.needsReplacement = false; - this.viewSlot.add(this.viewFactory.create()); + Replaceable.prototype.bind = function bind(bindingContext, overrideContext) { + if (this.view === null) { + this.view = this.viewFactory.create(); + this.viewSlot.add(this.view); } + + this.view.bind(bindingContext, overrideContext); + }; + + Replaceable.prototype.unbind = function unbind() { + this.view.unbind(); }; var _Replaceable = Replaceable; diff --git a/dist/amd/signal-binding-behavior.js b/dist/amd/signal-binding-behavior.js new file mode 100644 index 0000000..0200b08 --- /dev/null +++ b/dist/amd/signal-binding-behavior.js @@ -0,0 +1,42 @@ +define(['exports', 'aurelia-binding', './binding-signaler'], function (exports, _aureliaBinding, _bindingSignaler) { + 'use strict'; + + exports.__esModule = true; + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + var SignalBindingBehavior = (function () { + SignalBindingBehavior.inject = function inject() { + return [_bindingSignaler.BindingSignaler]; + }; + + function SignalBindingBehavior(bindingSignaler) { + _classCallCheck(this, SignalBindingBehavior); + + this.signals = bindingSignaler.signals; + } + + SignalBindingBehavior.prototype.bind = function bind(binding, source, name) { + if (!binding.updateTarget) { + throw new Error('Only property bindings and string interpolation bindings can be signaled. Trigger, delegate and call bindings cannot be signaled.'); + } + if (binding.mode === _aureliaBinding.bindingMode.oneTime) { + throw new Error('One-time bindings cannot be signaled.'); + } + var bindings = this.signals[name] || (this.signals[name] = []); + bindings.push(binding); + binding.signalName = name; + }; + + SignalBindingBehavior.prototype.unbind = function unbind(binding, source) { + var name = binding.signalName; + binding.signalName = null; + var bindings = signals[name]; + bindings.splice(bindings.indexOf(binding), 1); + }; + + return SignalBindingBehavior; + })(); + + exports.SignalBindingBehavior = SignalBindingBehavior; +}); \ No newline at end of file diff --git a/dist/amd/throttle-binding-behavior.js b/dist/amd/throttle-binding-behavior.js new file mode 100644 index 0000000..8e5c300 --- /dev/null +++ b/dist/amd/throttle-binding-behavior.js @@ -0,0 +1,69 @@ +define(['exports', 'aurelia-binding'], function (exports, _aureliaBinding) { + 'use strict'; + + exports.__esModule = true; + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + function throttle(newValue) { + var _this = this; + + var state = this.throttleState; + var elapsed = +new Date() - state.last; + if (elapsed >= state.delay) { + clearTimeout(state.timeoutId); + state.timeoutId = null; + state.last = +new Date(); + this.throttledMethod(newValue); + return; + } + state.newValue = newValue; + if (state.timeoutId === null) { + state.timeoutId = setTimeout(function () { + state.timeoutId = null; + state.last = +new Date(); + _this.throttledMethod(state.newValue); + }, state.delay - elapsed); + } + } + + var ThrottleBindingBehavior = (function () { + function ThrottleBindingBehavior() { + _classCallCheck(this, ThrottleBindingBehavior); + } + + ThrottleBindingBehavior.prototype.bind = function bind(binding, source) { + var delay = arguments.length <= 2 || arguments[2] === undefined ? 200 : arguments[2]; + + var methodToThrottle = 'updateTarget'; + if (binding.callSource) { + methodToThrottle = 'callSource'; + } else if (binding.updateSource && binding.mode === _aureliaBinding.bindingMode.twoWay) { + methodToThrottle = 'updateSource'; + } + + binding.throttledMethod = binding[methodToThrottle]; + binding.throttledMethod.originalName = methodToThrottle; + + binding[methodToThrottle] = throttle; + + binding.throttleState = { + delay: delay, + last: 0, + timeoutId: null + }; + }; + + ThrottleBindingBehavior.prototype.unbind = function unbind(binding, source) { + var methodToRestore = binding.throttledMethod.originalName; + binding[methodToRestore] = binding.throttledMethod; + binding.throttledMethod = null; + clearTimeout(binding.throttleState.timeoutId); + binding.throttleState = null; + }; + + return ThrottleBindingBehavior; + })(); + + exports.ThrottleBindingBehavior = ThrottleBindingBehavior; +}); \ No newline at end of file diff --git a/dist/amd/update-trigger-binding-behavior.js b/dist/amd/update-trigger-binding-behavior.js new file mode 100644 index 0000000..7e57c24 --- /dev/null +++ b/dist/amd/update-trigger-binding-behavior.js @@ -0,0 +1,53 @@ +define(['exports', 'aurelia-binding'], function (exports, _aureliaBinding) { + 'use strict'; + + exports.__esModule = true; + + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + var eventNamesRequired = 'The updateTrigger binding behavior requires at least one event name argument: eg '; + var notApplicableMessage = 'The updateTrigger binding behavior can only be applied to two-way bindings on input/select elements.'; + + var UpdateTriggerBindingBehavior = (function () { + _createClass(UpdateTriggerBindingBehavior, null, [{ + key: 'inject', + value: [_aureliaBinding.EventManager], + enumerable: true + }]); + + function UpdateTriggerBindingBehavior(eventManager) { + _classCallCheck(this, UpdateTriggerBindingBehavior); + + this.eventManager = eventManager; + } + + UpdateTriggerBindingBehavior.prototype.bind = function bind(binding, source) { + for (var _len = arguments.length, events = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { + events[_key - 2] = arguments[_key]; + } + + if (events.length === 0) { + throw new Error(eventNamesRequired); + } + if (binding.mode !== _aureliaBinding.bindingMode.twoWay || !binding.targetProperty.handler) { + throw new Error(notApplicableMessage); + } + + binding.targetProperty.originalHandler = binding.targetProperty.handler; + + var handler = this.eventManager.createElementHandler(events); + binding.targetProperty.handler = handler; + }; + + UpdateTriggerBindingBehavior.prototype.unbind = function unbind(binding, source) { + binding.targetProperty.handler = binding.targetProperty.originalHandler; + binding.targetProperty.originalHandler = null; + }; + + return UpdateTriggerBindingBehavior; + })(); + + exports.UpdateTriggerBindingBehavior = UpdateTriggerBindingBehavior; +}); \ No newline at end of file diff --git a/dist/amd/with.js b/dist/amd/with.js index e56ac96..01e7a3a 100644 --- a/dist/amd/with.js +++ b/dist/amd/with.js @@ -1,4 +1,4 @@ -define(['exports', 'aurelia-dependency-injection', 'aurelia-templating', 'aurelia-logging'], function (exports, _aureliaDependencyInjection, _aureliaTemplating, _aureliaLogging) { +define(['exports', 'aurelia-dependency-injection', 'aurelia-templating', 'aurelia-binding'], function (exports, _aureliaDependencyInjection, _aureliaTemplating, _aureliaBinding) { 'use strict'; exports.__esModule = true; @@ -11,15 +11,31 @@ define(['exports', 'aurelia-dependency-injection', 'aurelia-templating', 'aureli this.viewFactory = viewFactory; this.viewSlot = viewSlot; - _aureliaLogging.getLogger('templating-resources').warn('The "with" behavior will be removed in the next release.'); + this.parentOverrideContext = null; + this.view = null; } + With.prototype.bind = function bind(bindingContext, overrideContext) { + this.parentOverrideContext = overrideContext; + this.valueChanged(this.value); + }; + With.prototype.valueChanged = function valueChanged(newValue) { + var overrideContext = _aureliaBinding.createOverrideContext(newValue, this.parentOverrideContext); if (!this.view) { - this.view = this.viewFactory.create(newValue); + this.view = this.viewFactory.create(); + this.view.bind(newValue, overrideContext); this.viewSlot.add(this.view); } else { - this.view.bind(newValue); + this.view.bind(newValue, overrideContext); + } + }; + + With.prototype.unbind = function unbind() { + this.parentOverrideContext = null; + + if (this.view) { + this.view.unbind(); } }; diff --git a/dist/commonjs/array-collection-strategy.js b/dist/commonjs/array-collection-strategy.js new file mode 100644 index 0000000..705d478 --- /dev/null +++ b/dist/commonjs/array-collection-strategy.js @@ -0,0 +1,96 @@ +'use strict'; + +exports.__esModule = true; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _collectionStrategy = require('./collection-strategy'); + +var ArrayCollectionStrategy = (function (_CollectionStrategy) { + _inherits(ArrayCollectionStrategy, _CollectionStrategy); + + function ArrayCollectionStrategy() { + _classCallCheck(this, ArrayCollectionStrategy); + + _CollectionStrategy.apply(this, arguments); + } + + ArrayCollectionStrategy.prototype.processItems = function processItems(items) { + var i = undefined; + var ii = undefined; + var overrideContext = undefined; + var view = undefined; + this.items = items; + for (i = 0, ii = items.length; i < ii; ++i) { + overrideContext = _CollectionStrategy.prototype.createFullOverrideContext.call(this, items[i], i, ii); + view = this.viewFactory.create(); + view.bind(undefined, overrideContext); + this.viewSlot.add(view); + } + }; + + ArrayCollectionStrategy.prototype.getCollectionObserver = function getCollectionObserver(items) { + return this.observerLocator.getArrayObserver(items); + }; + + ArrayCollectionStrategy.prototype.handleChanges = function handleChanges(array, splices) { + var _this = this; + + var removeDelta = 0; + var viewSlot = this.viewSlot; + var rmPromises = []; + + for (var i = 0, ii = splices.length; i < ii; ++i) { + var splice = splices[i]; + var removed = splice.removed; + + for (var j = 0, jj = removed.length; j < jj; ++j) { + var viewOrPromise = viewSlot.removeAt(splice.index + removeDelta + rmPromises.length, true); + if (viewOrPromise instanceof Promise) { + rmPromises.push(viewOrPromise); + } + } + removeDelta -= splice.addedCount; + } + + if (rmPromises.length > 0) { + Promise.all(rmPromises).then(function () { + var spliceIndexLow = _this._handleAddedSplices(array, splices); + _this.updateOverrideContexts(spliceIndexLow); + }); + } else { + var spliceIndexLow = this._handleAddedSplices(array, splices); + _CollectionStrategy.prototype.updateOverrideContexts.call(this, spliceIndexLow); + } + }; + + ArrayCollectionStrategy.prototype._handleAddedSplices = function _handleAddedSplices(array, splices) { + var spliceIndex = undefined; + var spliceIndexLow = undefined; + var arrayLength = array.length; + for (var i = 0, ii = splices.length; i < ii; ++i) { + var splice = splices[i]; + var addIndex = spliceIndex = splice.index; + var end = splice.index + splice.addedCount; + + if (typeof spliceIndexLow === 'undefined' || spliceIndexLow === null || spliceIndexLow > splice.index) { + spliceIndexLow = spliceIndex; + } + + for (; addIndex < end; ++addIndex) { + var overrideContext = this.createFullOverrideContext(array[addIndex], addIndex, arrayLength); + var view = this.viewFactory.create(); + view.bind(undefined, overrideContext); + this.viewSlot.insert(addIndex, view); + } + } + + return spliceIndexLow; + }; + + return ArrayCollectionStrategy; +})(_collectionStrategy.CollectionStrategy); + +exports.ArrayCollectionStrategy = ArrayCollectionStrategy; \ No newline at end of file diff --git a/dist/commonjs/aurelia-templating-resources.js b/dist/commonjs/aurelia-templating-resources.js index 29680fa..23bd140 100644 --- a/dist/commonjs/aurelia-templating-resources.js +++ b/dist/commonjs/aurelia-templating-resources.js @@ -12,8 +12,6 @@ var _repeat = require('./repeat'); var _show = require('./show'); -var _globalBehavior = require('./global-behavior'); - var _sanitizeHtml = require('./sanitize-html'); var _replaceable = require('./replaceable'); @@ -34,6 +32,18 @@ var _aureliaPal = require('aurelia-pal'); var _htmlSanitizer = require('./html-sanitizer'); +var _bindingModeBehaviors = require('./binding-mode-behaviors'); + +var _throttleBindingBehavior = require('./throttle-binding-behavior'); + +var _debounceBindingBehavior = require('./debounce-binding-behavior'); + +var _signalBindingBehavior = require('./signal-binding-behavior'); + +var _bindingSignaler = require('./binding-signaler'); + +var _updateTriggerBindingBehavior = require('./update-trigger-binding-behavior'); + function configure(config) { if (_aureliaPal.FEATURE.shadowDOM) { _aureliaPal.DOM.injectStyles('body /deep/ .aurelia-hide { display:none !important; }'); @@ -41,7 +51,7 @@ function configure(config) { _aureliaPal.DOM.injectStyles('.aurelia-hide { display:none !important; }'); } - config.globalResources('./compose', './if', './with', './repeat', './show', './replaceable', './global-behavior', './sanitize-html', './focus', './compile-spy', './view-spy'); + config.globalResources('./compose', './if', './with', './repeat', './show', './replaceable', './sanitize-html', './focus', './compile-spy', './view-spy', './binding-mode-behaviors', './throttle-binding-behavior', './debounce-binding-behavior', './signal-binding-behavior', './update-trigger-binding-behavior'); var viewEngine = config.container.get(_aureliaTemplating.ViewEngine); var loader = config.aurelia.loader; @@ -89,9 +99,16 @@ exports.Repeat = _repeat.Repeat; exports.Show = _show.Show; exports.HTMLSanitizer = _htmlSanitizer.HTMLSanitizer; exports.SanitizeHTMLValueConverter = _sanitizeHtml.SanitizeHTMLValueConverter; -exports.GlobalBehavior = _globalBehavior.GlobalBehavior; exports.Replaceable = _replaceable.Replaceable; exports.Focus = _focus.Focus; exports.CompileSpy = _compileSpy.CompileSpy; exports.ViewSpy = _viewSpy.ViewSpy; -exports.configure = configure; \ No newline at end of file +exports.configure = configure; +exports.OneTimeBindingBehavior = _bindingModeBehaviors.OneTimeBindingBehavior; +exports.OneWayBindingBehavior = _bindingModeBehaviors.OneWayBindingBehavior; +exports.TwoWayBindingBehavior = _bindingModeBehaviors.TwoWayBindingBehavior; +exports.ThrottleBindingBehavior = _throttleBindingBehavior.ThrottleBindingBehavior; +exports.DebounceBindingBehavior = _debounceBindingBehavior.DebounceBindingBehavior; +exports.SignalBindingBehavior = _signalBindingBehavior.SignalBindingBehavior; +exports.BindingSignaler = _bindingSignaler.BindingSignaler; +exports.UpdateTriggerBindingBehavior = _updateTriggerBindingBehavior.UpdateTriggerBindingBehavior; \ No newline at end of file diff --git a/dist/commonjs/binding-mode-behaviors.js b/dist/commonjs/binding-mode-behaviors.js new file mode 100644 index 0000000..1824908 --- /dev/null +++ b/dist/commonjs/binding-mode-behaviors.js @@ -0,0 +1,71 @@ +'use strict'; + +exports.__esModule = true; + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +var _aureliaBinding = require('aurelia-binding'); + +var ModeBindingBehavior = (function () { + function ModeBindingBehavior(mode) { + _classCallCheck(this, ModeBindingBehavior); + + this.mode = mode; + } + + ModeBindingBehavior.prototype.bind = function bind(binding, source, lookupFunctions) { + binding.originalMode = binding.mode; + binding.mode = this.mode; + }; + + ModeBindingBehavior.prototype.unbind = function unbind(binding, source) { + binding.mode = binding.originalMode; + binding.originalMode = null; + }; + + return ModeBindingBehavior; +})(); + +var OneTimeBindingBehavior = (function (_ModeBindingBehavior) { + _inherits(OneTimeBindingBehavior, _ModeBindingBehavior); + + function OneTimeBindingBehavior() { + _classCallCheck(this, OneTimeBindingBehavior); + + _ModeBindingBehavior.call(this, _aureliaBinding.bindingMode.oneTime); + } + + return OneTimeBindingBehavior; +})(ModeBindingBehavior); + +exports.OneTimeBindingBehavior = OneTimeBindingBehavior; + +var OneWayBindingBehavior = (function (_ModeBindingBehavior2) { + _inherits(OneWayBindingBehavior, _ModeBindingBehavior2); + + function OneWayBindingBehavior() { + _classCallCheck(this, OneWayBindingBehavior); + + _ModeBindingBehavior2.call(this, _aureliaBinding.bindingMode.oneWay); + } + + return OneWayBindingBehavior; +})(ModeBindingBehavior); + +exports.OneWayBindingBehavior = OneWayBindingBehavior; + +var TwoWayBindingBehavior = (function (_ModeBindingBehavior3) { + _inherits(TwoWayBindingBehavior, _ModeBindingBehavior3); + + function TwoWayBindingBehavior() { + _classCallCheck(this, TwoWayBindingBehavior); + + _ModeBindingBehavior3.call(this, _aureliaBinding.bindingMode.twoWay); + } + + return TwoWayBindingBehavior; +})(ModeBindingBehavior); + +exports.TwoWayBindingBehavior = TwoWayBindingBehavior; \ No newline at end of file diff --git a/dist/commonjs/binding-signaler.js b/dist/commonjs/binding-signaler.js new file mode 100644 index 0000000..d54c5f6 --- /dev/null +++ b/dist/commonjs/binding-signaler.js @@ -0,0 +1,30 @@ +'use strict'; + +exports.__esModule = true; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +var _aureliaBinding = require('aurelia-binding'); + +var BindingSignaler = (function () { + function BindingSignaler() { + _classCallCheck(this, BindingSignaler); + + this.signals = {}; + } + + BindingSignaler.prototype.signal = function signal(name) { + var bindings = this.signals[name]; + if (!bindings) { + return; + } + var i = bindings.length; + while (i--) { + bindings[i].call(_aureliaBinding.sourceContext); + } + }; + + return BindingSignaler; +})(); + +exports.BindingSignaler = BindingSignaler; \ No newline at end of file diff --git a/dist/commonjs/collection-strategy-locator.js b/dist/commonjs/collection-strategy-locator.js new file mode 100644 index 0000000..3419e2a --- /dev/null +++ b/dist/commonjs/collection-strategy-locator.js @@ -0,0 +1,56 @@ +'use strict'; + +exports.__esModule = true; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +var _aureliaDependencyInjection = require('aurelia-dependency-injection'); + +var _arrayCollectionStrategy = require('./array-collection-strategy'); + +var _mapCollectionStrategy = require('./map-collection-strategy'); + +var _numberStrategy = require('./number-strategy'); + +var CollectionStrategyLocator = (function () { + function CollectionStrategyLocator(container) { + _classCallCheck(this, _CollectionStrategyLocator); + + this.container = container; + this.strategies = []; + this.matchers = []; + + this.addStrategy(_arrayCollectionStrategy.ArrayCollectionStrategy, function (items) { + return items instanceof Array; + }); + this.addStrategy(_mapCollectionStrategy.MapCollectionStrategy, function (items) { + return items instanceof Map; + }); + this.addStrategy(_numberStrategy.NumberStrategy, function (items) { + return typeof items === 'number'; + }); + } + + CollectionStrategyLocator.prototype.addStrategy = function addStrategy(collectionStrategy, matcher) { + this.strategies.push(collectionStrategy); + this.matchers.push(matcher); + }; + + CollectionStrategyLocator.prototype.getStrategy = function getStrategy(items) { + var matchers = this.matchers; + + for (var i = 0, ii = matchers.length; i < ii; ++i) { + if (matchers[i](items)) { + return this.container.get(this.strategies[i]); + } + } + + throw new Error('Object in "repeat" must have a valid collection strategy.'); + }; + + var _CollectionStrategyLocator = CollectionStrategyLocator; + CollectionStrategyLocator = _aureliaDependencyInjection.inject(_aureliaDependencyInjection.Container)(CollectionStrategyLocator) || CollectionStrategyLocator; + return CollectionStrategyLocator; +})(); + +exports.CollectionStrategyLocator = CollectionStrategyLocator; \ No newline at end of file diff --git a/dist/commonjs/collection-strategy.js b/dist/commonjs/collection-strategy.js new file mode 100644 index 0000000..c7992cc --- /dev/null +++ b/dist/commonjs/collection-strategy.js @@ -0,0 +1,92 @@ +'use strict'; + +exports.__esModule = true; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +var _aureliaDependencyInjection = require('aurelia-dependency-injection'); + +var _aureliaBinding = require('aurelia-binding'); + +var CollectionStrategy = (function () { + function CollectionStrategy(observerLocator) { + _classCallCheck(this, _CollectionStrategy); + + this.observerLocator = observerLocator; + } + + CollectionStrategy.prototype.initialize = function initialize(repeat, bindingContext, overrideContext) { + this.viewFactory = repeat.viewFactory; + this.viewSlot = repeat.viewSlot; + this.items = repeat.items; + this.local = repeat.local; + this.key = repeat.key; + this.value = repeat.value; + this.bindingContext = bindingContext; + this.overrideContext = overrideContext; + }; + + CollectionStrategy.prototype.dispose = function dispose() { + this.viewFactory = null; + this.viewSlot = null; + this.items = null; + this.local = null; + this.key = null; + this.value = null; + this.bindingContext = null; + this.overrideContext = null; + }; + + CollectionStrategy.prototype.updateOverrideContexts = function updateOverrideContexts(startIndex) { + var children = this.viewSlot.children; + var length = children.length; + + if (startIndex > 0) { + startIndex = startIndex - 1; + } + + for (; startIndex < length; ++startIndex) { + this.updateOverrideContext(children[startIndex].overrideContext, startIndex, length); + } + }; + + CollectionStrategy.prototype.createFullOverrideContext = function createFullOverrideContext(data, index, length, key) { + var context = this.createBaseOverrideContext(data, key); + return this.updateOverrideContext(context, index, length); + }; + + CollectionStrategy.prototype.createBaseOverrideContext = function createBaseOverrideContext(data, key) { + var context = _aureliaBinding.createOverrideContext(undefined, this.overrideContext); + + if (typeof key !== 'undefined') { + context[this.key] = key; + context[this.value] = data; + } else { + context[this.local] = data; + } + + return context; + }; + + CollectionStrategy.prototype.updateOverrideContext = function updateOverrideContext(context, index, length) { + var first = index === 0; + var last = index === length - 1; + var even = index % 2 === 0; + + context.$index = index; + context.$first = first; + context.$last = last; + context.$middle = !(first || last); + context.$odd = !even; + context.$even = even; + + return context; + }; + + var _CollectionStrategy = CollectionStrategy; + CollectionStrategy = _aureliaDependencyInjection.transient()(CollectionStrategy) || CollectionStrategy; + CollectionStrategy = _aureliaDependencyInjection.inject(_aureliaBinding.ObserverLocator)(CollectionStrategy) || CollectionStrategy; + return CollectionStrategy; +})(); + +exports.CollectionStrategy = CollectionStrategy; \ No newline at end of file diff --git a/dist/commonjs/compose.js b/dist/commonjs/compose.js index d8e6f4b..1c6a5b0 100644 --- a/dist/commonjs/compose.js +++ b/dist/commonjs/compose.js @@ -51,6 +51,8 @@ var Compose = (function () { this.viewSlot = viewSlot; this.viewResources = viewResources; this.taskQueue = taskQueue; + this.currentController = null; + this.currentViewModel = null; } Compose.prototype.bind = function bind(bindingContext) { @@ -139,15 +141,15 @@ function createInstruction(composer, instruction) { container: composer.container, viewSlot: composer.viewSlot, viewResources: composer.viewResources, - currentBehavior: composer.currentBehavior, + currentController: composer.currentController, host: composer.element }); } function processInstruction(composer, instruction) { composer.currentInstruction = null; - composer.compositionEngine.compose(instruction).then(function (next) { - composer.currentBehavior = next; - composer.currentViewModel = next ? next.bindingContext : null; + composer.compositionEngine.compose(instruction).then(function (controller) { + composer.currentController = controller; + composer.currentViewModel = controller ? controller.viewModel : null; }); } \ No newline at end of file diff --git a/dist/commonjs/debounce-binding-behavior.js b/dist/commonjs/debounce-binding-behavior.js new file mode 100644 index 0000000..97cb5c5 --- /dev/null +++ b/dist/commonjs/debounce-binding-behavior.js @@ -0,0 +1,61 @@ +'use strict'; + +exports.__esModule = true; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +var _aureliaBinding = require('aurelia-binding'); + +function debounce(newValue) { + var _this = this; + + var state = this.debounceState; + if (state.immediate) { + state.immediate = false; + this.debouncedMethod(newValue); + return; + } + clearTimeout(state.timeoutId); + state.timeoutId = setTimeout(function () { + return _this.debouncedMethod(newValue); + }, state.delay); +} + +var DebounceBindingBehavior = (function () { + function DebounceBindingBehavior() { + _classCallCheck(this, DebounceBindingBehavior); + } + + DebounceBindingBehavior.prototype.bind = function bind(binding, source) { + var delay = arguments.length <= 2 || arguments[2] === undefined ? 200 : arguments[2]; + + var methodToDebounce = 'updateTarget'; + if (binding.callSource) { + methodToDebounce = 'callSource'; + } else if (binding.updateSource && binding.mode === _aureliaBinding.bindingMode.twoWay) { + methodToDebounce = 'updateSource'; + } + + binding.debouncedMethod = binding[methodToDebounce]; + binding.debouncedMethod.originalName = methodToDebounce; + + binding[methodToDebounce] = debounce; + + binding.debounceState = { + delay: delay, + timeoutId: null, + immediate: methodToDebounce === 'updateTarget' }; + }; + + DebounceBindingBehavior.prototype.unbind = function unbind(binding, source) { + var methodToRestore = binding.debouncedMethod.originalName; + binding[methodToRestore] = binding.debouncedMethod; + binding.debouncedMethod = null; + clearTimeout(binding.debounceState.timeoutId); + binding.debounceState = null; + }; + + return DebounceBindingBehavior; +})(); + +exports.DebounceBindingBehavior = DebounceBindingBehavior; \ No newline at end of file diff --git a/dist/commonjs/global-behavior.js b/dist/commonjs/global-behavior.js deleted file mode 100644 index 1884c27..0000000 --- a/dist/commonjs/global-behavior.js +++ /dev/null @@ -1,112 +0,0 @@ -'use strict'; - -exports.__esModule = true; - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - -var _aureliaDependencyInjection = require('aurelia-dependency-injection'); - -var _aureliaTemplating = require('aurelia-templating'); - -var _aureliaLogging = require('aurelia-logging'); - -var LogManager = _interopRequireWildcard(_aureliaLogging); - -var _aureliaPal = require('aurelia-pal'); - -var GlobalBehavior = (function () { - function GlobalBehavior(element) { - _classCallCheck(this, _GlobalBehavior); - - this.element = element; - LogManager.getLogger('templating-resources').warn('The "GlobalBehavior" behavior will be removed in the next release.'); - } - - GlobalBehavior.prototype.bind = function bind() { - var handler = GlobalBehavior.handlers[this.aureliaAttrName]; - - if (!handler) { - throw new Error('Binding handler not found for \'' + this.aureliaAttrName + '.' + this.aureliaCommand + '\'. Element:\n' + this.element.outerHTML + '\n'); - } - - try { - this.handler = handler.bind(this, this.element, this.aureliaCommand) || handler; - } catch (error) { - throw new _aureliaPal.AggregateError('Conventional binding handler failed.', error); - } - }; - - GlobalBehavior.prototype.attached = function attached() { - if (this.handler && 'attached' in this.handler) { - this.handler.attached(this, this.element); - } - }; - - GlobalBehavior.prototype.detached = function detached() { - if (this.handler && 'detached' in this.handler) { - this.handler.detached(this, this.element); - } - }; - - GlobalBehavior.prototype.unbind = function unbind() { - if (this.handler && 'unbind' in this.handler) { - this.handler.unbind(this, this.element); - } - - this.handler = null; - }; - - var _GlobalBehavior = GlobalBehavior; - GlobalBehavior = _aureliaDependencyInjection.inject(_aureliaPal.DOM.Element)(GlobalBehavior) || GlobalBehavior; - GlobalBehavior = _aureliaTemplating.dynamicOptions(GlobalBehavior) || GlobalBehavior; - GlobalBehavior = _aureliaTemplating.customAttribute('global-behavior')(GlobalBehavior) || GlobalBehavior; - return GlobalBehavior; -})(); - -exports.GlobalBehavior = GlobalBehavior; - -GlobalBehavior.createSettingsFromBehavior = function (behavior) { - var settings = {}; - - for (var key in behavior) { - if (key === 'aureliaAttrName' || key === 'aureliaCommand' || !behavior.hasOwnProperty(key)) { - continue; - } - - settings[key] = behavior[key]; - } - - return settings; -}; - -GlobalBehavior.jQueryPlugins = {}; - -GlobalBehavior.handlers = { - jquery: { - bind: function bind(behavior, element, command) { - var settings = GlobalBehavior.createSettingsFromBehavior(behavior); - var pluginName = GlobalBehavior.jQueryPlugins[command] || command; - var jqueryElement = _aureliaPal.PLATFORM.global.jQuery(element); - - if (!jqueryElement[pluginName]) { - LogManager.getLogger('templating-resources').warn('Could not find the jQuery plugin ' + pluginName + ', possibly due to case mismatch. Trying to enumerate jQuery methods in lowercase. Add the correctly cased plugin name to the GlobalBehavior to avoid this performance hit.'); - - for (var prop in jqueryElement) { - if (prop.toLowerCase() === pluginName) { - pluginName = prop; - } - } - } - - behavior.plugin = jqueryElement[pluginName](settings); - }, - unbind: function unbind(behavior, element) { - if (typeof behavior.plugin.destroy === 'function') { - behavior.plugin.destroy(); - behavior.plugin = null; - } - } - } -}; \ No newline at end of file diff --git a/dist/commonjs/if.js b/dist/commonjs/if.js index b813c57..966b1ef 100644 --- a/dist/commonjs/if.js +++ b/dist/commonjs/if.js @@ -19,11 +19,13 @@ var If = (function () { this.showing = false; this.taskQueue = taskQueue; this.view = null; - this.$parent = null; + this.bindingContext = null; + this.overrideContext = null; } - If.prototype.bind = function bind(bindingContext) { - this.$parent = bindingContext; + If.prototype.bind = function bind(bindingContext, overrideContext) { + this.bindingContext = bindingContext; + this.overrideContext = overrideContext; this.valueChanged(this.value); }; @@ -49,14 +51,14 @@ var If = (function () { } if (this.view === null) { - this.view = this.viewFactory.create(this.$parent); + this.view = this.viewFactory.create(); } if (!this.showing) { this.showing = true; if (!this.view.isBound) { - this.view.bind(this.$parent); + this.view.bind(this.bindingContext, this.overrideContext); } this.viewSlot.add(this.view); diff --git a/dist/commonjs/map-collection-strategy.js b/dist/commonjs/map-collection-strategy.js new file mode 100644 index 0000000..6bc73d9 --- /dev/null +++ b/dist/commonjs/map-collection-strategy.js @@ -0,0 +1,121 @@ +'use strict'; + +exports.__esModule = true; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _collectionStrategy = require('./collection-strategy'); + +var MapCollectionStrategy = (function (_CollectionStrategy) { + _inherits(MapCollectionStrategy, _CollectionStrategy); + + function MapCollectionStrategy() { + _classCallCheck(this, MapCollectionStrategy); + + _CollectionStrategy.apply(this, arguments); + } + + MapCollectionStrategy.prototype.getCollectionObserver = function getCollectionObserver(items) { + return this.observerLocator.getMapObserver(items); + }; + + MapCollectionStrategy.prototype.processItems = function processItems(items) { + var _this = this; + + var viewFactory = this.viewFactory; + var viewSlot = this.viewSlot; + var index = 0; + var overrideContext = undefined; + var view = undefined; + + items.forEach(function (value, key) { + overrideContext = _this.createFullOverrideContext(value, index, items.size, key); + view = viewFactory.create(); + view.bind(undefined, overrideContext); + viewSlot.add(view); + ++index; + }); + }; + + MapCollectionStrategy.prototype.handleChanges = function handleChanges(map, records) { + var _this2 = this; + + var viewSlot = this.viewSlot; + var key = undefined; + var i = undefined; + var ii = undefined; + var view = undefined; + var overrideContext = undefined; + var removeIndex = undefined; + var record = undefined; + var rmPromises = []; + var viewOrPromise = undefined; + + for (i = 0, ii = records.length; i < ii; ++i) { + record = records[i]; + key = record.key; + switch (record.type) { + case 'update': + removeIndex = this._getViewIndexByKey(key); + viewOrPromise = viewSlot.removeAt(removeIndex, true); + if (viewOrPromise instanceof Promise) { + rmPromises.push(viewOrPromise); + } + overrideContext = this.createFullOverrideContext(map.get(key), removeIndex, map.size, key); + view = this.viewFactory.create(); + view.bind(undefined, overrideContext); + viewSlot.insert(removeIndex, view); + break; + case 'add': + overrideContext = this.createFullOverrideContext(map.get(key), map.size - 1, map.size, key); + view = this.viewFactory.create(); + view.bind(undefined, overrideContext); + viewSlot.insert(map.size - 1, view); + break; + case 'delete': + if (record.oldValue === undefined) { + return; + } + removeIndex = this._getViewIndexByKey(key); + viewOrPromise = viewSlot.removeAt(removeIndex, true); + if (viewOrPromise instanceof Promise) { + rmPromises.push(viewOrPromise); + } + break; + case 'clear': + viewSlot.removeAll(true); + break; + default: + continue; + } + } + + if (rmPromises.length > 0) { + Promise.all(rmPromises).then(function () { + _this2.updateOverrideContexts(0); + }); + } else { + this.updateOverrideContexts(0); + } + }; + + MapCollectionStrategy.prototype._getViewIndexByKey = function _getViewIndexByKey(key) { + var viewSlot = this.viewSlot; + var i = undefined; + var ii = undefined; + var child = undefined; + + for (i = 0, ii = viewSlot.children.length; i < ii; ++i) { + child = viewSlot.children[i]; + if (child.overrideContext[this.key] === key) { + return i; + } + } + }; + + return MapCollectionStrategy; +})(_collectionStrategy.CollectionStrategy); + +exports.MapCollectionStrategy = MapCollectionStrategy; \ No newline at end of file diff --git a/dist/commonjs/number-strategy.js b/dist/commonjs/number-strategy.js new file mode 100644 index 0000000..cfb4c78 --- /dev/null +++ b/dist/commonjs/number-strategy.js @@ -0,0 +1,62 @@ +'use strict'; + +exports.__esModule = true; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _collectionStrategy = require('./collection-strategy'); + +var NumberStrategy = (function (_CollectionStrategy) { + _inherits(NumberStrategy, _CollectionStrategy); + + function NumberStrategy() { + _classCallCheck(this, NumberStrategy); + + _CollectionStrategy.apply(this, arguments); + } + + NumberStrategy.prototype.getCollectionObserver = function getCollectionObserver() { + return; + }; + + NumberStrategy.prototype.processItems = function processItems(value) { + var viewFactory = this.viewFactory; + var viewSlot = this.viewSlot; + var childrenLength = viewSlot.children.length; + var i = undefined; + var ii = undefined; + var overrideContext = undefined; + var view = undefined; + var viewsToRemove = undefined; + + value = Math.floor(value); + viewsToRemove = childrenLength - value; + + if (viewsToRemove > 0) { + if (viewsToRemove > childrenLength) { + viewsToRemove = childrenLength; + } + + for (i = 0, ii = viewsToRemove; i < ii; ++i) { + viewSlot.removeAt(childrenLength - (i + 1), true); + } + + return; + } + + for (i = childrenLength, ii = value; i < ii; ++i) { + overrideContext = this.createFullOverrideContext(i, i, ii); + view = viewFactory.create(); + view.bind(undefined, overrideContext); + viewSlot.add(view); + } + + this.updateOverrideContexts(0); + }; + + return NumberStrategy; +})(_collectionStrategy.CollectionStrategy); + +exports.NumberStrategy = NumberStrategy; \ No newline at end of file diff --git a/dist/commonjs/repeat.js b/dist/commonjs/repeat.js index 37ffa9b..76c0d9b 100644 --- a/dist/commonjs/repeat.js +++ b/dist/commonjs/repeat.js @@ -14,6 +14,8 @@ var _aureliaBinding = require('aurelia-binding'); var _aureliaTemplating = require('aurelia-templating'); +var _collectionStrategyLocator = require('./collection-strategy-locator'); + var Repeat = (function () { var _instanceInitializers = {}; @@ -39,7 +41,7 @@ var Repeat = (function () { enumerable: true }], null, _instanceInitializers); - function Repeat(viewFactory, viewSlot, observerLocator) { + function Repeat(viewFactory, instruction, viewSlot, viewResources, observerLocator, collectionStrategyLocator) { _classCallCheck(this, _Repeat); _defineDecoratedPropertyDescriptor(this, 'items', _instanceInitializers); @@ -51,66 +53,40 @@ var Repeat = (function () { _defineDecoratedPropertyDescriptor(this, 'value', _instanceInitializers); this.viewFactory = viewFactory; + this.instruction = instruction; this.viewSlot = viewSlot; + this.lookupFunctions = viewResources.lookupFunctions; this.observerLocator = observerLocator; this.local = 'item'; this.key = 'key'; this.value = 'value'; + this.collectionStrategyLocator = collectionStrategyLocator; } Repeat.prototype.call = function call(context, changes) { this[context](this.items, changes); }; - Repeat.prototype.bind = function bind(bindingContext) { + Repeat.prototype.bind = function bind(bindingContext, overrideContext) { var items = this.items; - var viewSlot = this.viewSlot; - var observer = undefined; - - this.bindingContext = bindingContext; - - if (!items) { - if (this.oldItems) { - viewSlot.removeAll(true); - } - + if (items === undefined || items === null) { return; } - if (this.oldItems === items) { - if (items instanceof Map) { - var records = _aureliaBinding.getChangeRecords(items); - this.collectionObserver = this.observerLocator.getMapObserver(items); - - this.handleMapChangeRecords(items, records); - - this.callContext = 'handleMapChangeRecords'; - this.collectionObserver.subscribe(this.callContext, this); - } else if (items instanceof Array) { - var splices = _aureliaBinding.calcSplices(items, 0, items.length, this.lastBoundItems, 0, this.lastBoundItems.length); - this.collectionObserver = this.observerLocator.getArrayObserver(items); - - this.handleSplices(items, splices); - this.lastBoundItems = this.oldItems = null; - - this.callContext = 'handleSplices'; - this.collectionObserver.subscribe(this.callContext, this); - } - return; - } else if (this.oldItems) { - viewSlot.removeAll(true); - } - + this.sourceExpression = getSourceExpression(this.instruction, 'repeat.for'); + this.scope = { bindingContext: bindingContext, overrideContext: overrideContext }; + this.collectionStrategy = this.collectionStrategyLocator.getStrategy(this.items); + this.collectionStrategy.initialize(this, bindingContext, overrideContext); this.processItems(); }; Repeat.prototype.unbind = function unbind() { - this.oldItems = this.items; - - if (this.items instanceof Array) { - this.lastBoundItems = this.items.slice(0); - } - + this.sourceExpression = null; + this.scope = null; + this.collectionStrategy.dispose(); + this.items = null; + this.collectionStrategy = null; + this.viewSlot.removeAll(true); this.unsubscribeCollection(); }; @@ -143,279 +119,87 @@ var Repeat = (function () { if (rmPromise instanceof Promise) { rmPromise.then(function () { - _this.processItemsByType(); + _this.processItemsByStrategy(); }); } else { - this.processItemsByType(); + this.processItemsByStrategy(); } }; - Repeat.prototype.processItemsByType = function processItemsByType() { - var items = this.items; - if (items instanceof Array) { - this.processArrayItems(items); - } else if (items instanceof Map) { - this.processMapEntries(items); - } else if (typeof items === 'number') { - this.processNumber(items); - } else { - throw new Error('Object in "repeat" must be of type Array, Map or Number'); + Repeat.prototype.getInnerCollection = function getInnerCollection() { + var expression = unwrapExpression(this.sourceExpression); + if (!expression) { + return null; } + return expression.evaluate(this.scope, null); }; - Repeat.prototype.processArrayItems = function processArrayItems(items) { - var viewFactory = this.viewFactory; - var viewSlot = this.viewSlot; - var i = undefined; - var ii = undefined; - var row = undefined; - var view = undefined; - var observer = undefined; - - this.collectionObserver = this.observerLocator.getArrayObserver(items); - - for (i = 0, ii = items.length; i < ii; ++i) { - row = this.createFullBindingContext(items[i], i, ii); - view = viewFactory.create(row); - viewSlot.add(view); + Repeat.prototype.observeInnerCollection = function observeInnerCollection() { + var 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 = 'handleSplices'; - this.collectionObserver.subscribe(this.callContext, this); - }; - - Repeat.prototype.processMapEntries = function processMapEntries(items) { - var _this2 = this; - - var viewFactory = this.viewFactory; - var viewSlot = this.viewSlot; - var index = 0; - var row = undefined; - var view = undefined; - var observer = undefined; - - this.collectionObserver = this.observerLocator.getMapObserver(items); - - items.forEach(function (value, key) { - row = _this2.createFullExecutionKvpContext(key, value, index, items.size); - view = viewFactory.create(row); - viewSlot.add(view); - ++index; - }); - - this.callContext = 'handleMapChangeRecords'; + this.callContext = 'handleInnerCollectionChanges'; this.collectionObserver.subscribe(this.callContext, this); + return true; }; - Repeat.prototype.processNumber = function processNumber(value) { - var viewFactory = this.viewFactory; - var viewSlot = this.viewSlot; - var childrenLength = viewSlot.children.length; - var i = undefined; - var ii = undefined; - var row = undefined; - var view = undefined; - var viewsToRemove = undefined; - - value = Math.floor(value); - viewsToRemove = childrenLength - value; - - if (viewsToRemove > 0) { - if (viewsToRemove > childrenLength) { - viewsToRemove = childrenLength; - } - - for (i = 0, ii = viewsToRemove; i < ii; ++i) { - viewSlot.removeAt(childrenLength - (i + 1), true); - } - - return; - } - - for (i = childrenLength, ii = value; i < ii; ++i) { - row = this.createFullBindingContext(i, i, ii); - view = viewFactory.create(row); - viewSlot.add(view); - } - }; - - Repeat.prototype.createBaseBindingContext = function createBaseBindingContext(data) { - var context = {}; - context[this.local] = data; - context.$parent = this.bindingContext; - return context; - }; - - Repeat.prototype.createBaseExecutionKvpContext = function createBaseExecutionKvpContext(key, value) { - var context = {}; - context[this.key] = key; - context[this.value] = value; - context.$parent = this.bindingContext; - return context; - }; - - Repeat.prototype.createFullBindingContext = function createFullBindingContext(data, index, length) { - var context = this.createBaseBindingContext(data); - return this.updateBindingContext(context, index, length); - }; - - Repeat.prototype.createFullExecutionKvpContext = function createFullExecutionKvpContext(key, value, index, length) { - var context = this.createBaseExecutionKvpContext(key, value); - return this.updateBindingContext(context, index, length); - }; - - Repeat.prototype.updateBindingContext = function updateBindingContext(context, index, length) { - var first = index === 0; - var last = index === length - 1; - var even = index % 2 === 0; - - context.$index = index; - context.$first = first; - context.$last = last; - context.$middle = !(first || last); - context.$odd = !even; - context.$even = even; - - return context; - }; - - Repeat.prototype.updateBindingContexts = function updateBindingContexts(startIndex) { - var children = this.viewSlot.children; - var length = children.length; - - if (startIndex > 0) { - startIndex = startIndex - 1; - } - - for (; startIndex < length; ++startIndex) { - this.updateBindingContext(children[startIndex].bindingContext, startIndex, length); - } - }; - - Repeat.prototype.handleSplices = function handleSplices(array, splices) { - var _this3 = this; - - var removeDelta = 0; - var viewSlot = this.viewSlot; - var rmPromises = []; - - for (var i = 0, ii = splices.length; i < ii; ++i) { - var splice = splices[i]; - var removed = splice.removed; - - for (var j = 0, jj = removed.length; j < jj; ++j) { - var viewOrPromise = viewSlot.removeAt(splice.index + removeDelta + rmPromises.length, true); - if (viewOrPromise instanceof Promise) { - rmPromises.push(viewOrPromise); - } - } - removeDelta -= splice.addedCount; - } - - if (rmPromises.length > 0) { - Promise.all(rmPromises).then(function () { - var spliceIndexLow = _this3.handleAddedSplices(array, splices); - _this3.updateBindingContexts(spliceIndexLow); - }); - } else { - var spliceIndexLow = this.handleAddedSplices(array, splices); - this.updateBindingContexts(spliceIndexLow); + Repeat.prototype.observeCollection = function observeCollection() { + var items = this.items; + this.collectionObserver = this.collectionStrategy.getCollectionObserver(items); + if (this.collectionObserver) { + this.callContext = 'handleCollectionChanges'; + this.collectionObserver.subscribe(this.callContext, this); } }; - Repeat.prototype.handleAddedSplices = function handleAddedSplices(array, splices) { - var spliceIndex = undefined; - var spliceIndexLow = undefined; - var arrayLength = array.length; - for (var i = 0, ii = splices.length; i < ii; ++i) { - var splice = splices[i]; - var addIndex = spliceIndex = splice.index; - var end = splice.index + splice.addedCount; - - if (typeof spliceIndexLow === 'undefined' || spliceIndexLow === null || spliceIndexLow > splice.index) { - spliceIndexLow = spliceIndex; - } - - for (; addIndex < end; ++addIndex) { - var row = this.createFullBindingContext(array[addIndex], addIndex, arrayLength); - var view = this.viewFactory.create(row); - this.viewSlot.insert(addIndex, view); - } + Repeat.prototype.processItemsByStrategy = function processItemsByStrategy() { + if (!this.observeInnerCollection()) { + this.observeCollection(); } - - return spliceIndexLow; + this.collectionStrategy.processItems(this.items); }; - Repeat.prototype.handleMapChangeRecords = function handleMapChangeRecords(map, records) { - var viewSlot = this.viewSlot; - var key = undefined; - var i = undefined; - var ii = undefined; - var view = undefined; - var children = undefined; - var length = undefined; - var row = undefined; - var removeIndex = undefined; - var record = undefined; - - for (i = 0, ii = records.length; i < ii; ++i) { - record = records[i]; - key = record.key; - switch (record.type) { - case 'update': - removeIndex = this.getViewIndexByKey(key); - viewSlot.removeAt(removeIndex, true); - row = this.createBaseExecutionKvpContext(key, map.get(key)); - view = this.viewFactory.create(row); - viewSlot.insert(removeIndex, view); - break; - case 'add': - row = this.createBaseExecutionKvpContext(key, map.get(key)); - view = this.viewFactory.create(row); - viewSlot.insert(map.size, view); - break; - case 'delete': - if (!record.oldValue) { - return; - } - removeIndex = this.getViewIndexByKey(key); - viewSlot.removeAt(removeIndex, true); - break; - case 'clear': - viewSlot.removeAll(true); - break; - default: - continue; - } - } - - children = viewSlot.children; - length = children.length; - - for (i = 0; i < length; i++) { - this.updateBindingContext(children[i].bindingContext, i, length); - } + Repeat.prototype.handleCollectionChanges = function handleCollectionChanges(collection, changes) { + this.collectionStrategy.handleChanges(collection, changes); }; - Repeat.prototype.getViewIndexByKey = function getViewIndexByKey(key) { - var viewSlot = this.viewSlot; - var i = undefined; - var ii = undefined; - var child = undefined; - - for (i = 0, ii = viewSlot.children.length; i < ii; ++i) { - child = viewSlot.children[i]; - if (child.bindings[0].source[this.key] === key) { - return i; - } + Repeat.prototype.handleInnerCollectionChanges = function handleInnerCollectionChanges(collection, changes) { + var newItems = this.sourceExpression.evaluate(this.scope, this.lookupFunctions); + if (newItems === this.items) { + return; } + this.items = newItems; + this.itemsChanged(); }; var _Repeat = Repeat; - Repeat = _aureliaDependencyInjection.inject(_aureliaTemplating.BoundViewFactory, _aureliaTemplating.ViewSlot, _aureliaBinding.ObserverLocator)(Repeat) || Repeat; + Repeat = _aureliaDependencyInjection.inject(_aureliaTemplating.BoundViewFactory, _aureliaTemplating.TargetInstruction, _aureliaTemplating.ViewSlot, _aureliaTemplating.ViewResources, _aureliaBinding.ObserverLocator, _collectionStrategyLocator.CollectionStrategyLocator)(Repeat) || Repeat; Repeat = _aureliaTemplating.templateController(Repeat) || Repeat; Repeat = _aureliaTemplating.customAttribute('repeat')(Repeat) || Repeat; return Repeat; })(); -exports.Repeat = Repeat; \ No newline at end of file +exports.Repeat = Repeat; + +function getSourceExpression(instruction, attrName) { + return instruction.behaviorInstructions.filter(function (bi) { + return bi.originalAttrName === attrName; + })[0].attributes.items.sourceExpression; +} + +function unwrapExpression(expression) { + var unwrapped = false; + while (expression instanceof _aureliaBinding.BindingBehavior) { + expression = expression.expression; + } + while (expression instanceof _aureliaBinding.ValueConverter) { + expression = expression.expression; + unwrapped = true; + } + return unwrapped ? expression : null; +} \ No newline at end of file diff --git a/dist/commonjs/replaceable.js b/dist/commonjs/replaceable.js index 7cccf26..dac8a01 100644 --- a/dist/commonjs/replaceable.js +++ b/dist/commonjs/replaceable.js @@ -14,14 +14,20 @@ var Replaceable = (function () { this.viewFactory = viewFactory; this.viewSlot = viewSlot; - this.needsReplacement = true; + this.view = null; } - Replaceable.prototype.bind = function bind() { - if (this.needsReplacement) { - this.needsReplacement = false; - this.viewSlot.add(this.viewFactory.create()); + Replaceable.prototype.bind = function bind(bindingContext, overrideContext) { + if (this.view === null) { + this.view = this.viewFactory.create(); + this.viewSlot.add(this.view); } + + this.view.bind(bindingContext, overrideContext); + }; + + Replaceable.prototype.unbind = function unbind() { + this.view.unbind(); }; var _Replaceable = Replaceable; diff --git a/dist/commonjs/signal-binding-behavior.js b/dist/commonjs/signal-binding-behavior.js new file mode 100644 index 0000000..3780618 --- /dev/null +++ b/dist/commonjs/signal-binding-behavior.js @@ -0,0 +1,44 @@ +'use strict'; + +exports.__esModule = true; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +var _aureliaBinding = require('aurelia-binding'); + +var _bindingSignaler = require('./binding-signaler'); + +var SignalBindingBehavior = (function () { + SignalBindingBehavior.inject = function inject() { + return [_bindingSignaler.BindingSignaler]; + }; + + function SignalBindingBehavior(bindingSignaler) { + _classCallCheck(this, SignalBindingBehavior); + + this.signals = bindingSignaler.signals; + } + + SignalBindingBehavior.prototype.bind = function bind(binding, source, name) { + if (!binding.updateTarget) { + throw new Error('Only property bindings and string interpolation bindings can be signaled. Trigger, delegate and call bindings cannot be signaled.'); + } + if (binding.mode === _aureliaBinding.bindingMode.oneTime) { + throw new Error('One-time bindings cannot be signaled.'); + } + var bindings = this.signals[name] || (this.signals[name] = []); + bindings.push(binding); + binding.signalName = name; + }; + + SignalBindingBehavior.prototype.unbind = function unbind(binding, source) { + var name = binding.signalName; + binding.signalName = null; + var bindings = signals[name]; + bindings.splice(bindings.indexOf(binding), 1); + }; + + return SignalBindingBehavior; +})(); + +exports.SignalBindingBehavior = SignalBindingBehavior; \ No newline at end of file diff --git a/dist/commonjs/throttle-binding-behavior.js b/dist/commonjs/throttle-binding-behavior.js new file mode 100644 index 0000000..8e6d20e --- /dev/null +++ b/dist/commonjs/throttle-binding-behavior.js @@ -0,0 +1,69 @@ +'use strict'; + +exports.__esModule = true; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +var _aureliaBinding = require('aurelia-binding'); + +function throttle(newValue) { + var _this = this; + + var state = this.throttleState; + var elapsed = +new Date() - state.last; + if (elapsed >= state.delay) { + clearTimeout(state.timeoutId); + state.timeoutId = null; + state.last = +new Date(); + this.throttledMethod(newValue); + return; + } + state.newValue = newValue; + if (state.timeoutId === null) { + state.timeoutId = setTimeout(function () { + state.timeoutId = null; + state.last = +new Date(); + _this.throttledMethod(state.newValue); + }, state.delay - elapsed); + } +} + +var ThrottleBindingBehavior = (function () { + function ThrottleBindingBehavior() { + _classCallCheck(this, ThrottleBindingBehavior); + } + + ThrottleBindingBehavior.prototype.bind = function bind(binding, source) { + var delay = arguments.length <= 2 || arguments[2] === undefined ? 200 : arguments[2]; + + var methodToThrottle = 'updateTarget'; + if (binding.callSource) { + methodToThrottle = 'callSource'; + } else if (binding.updateSource && binding.mode === _aureliaBinding.bindingMode.twoWay) { + methodToThrottle = 'updateSource'; + } + + binding.throttledMethod = binding[methodToThrottle]; + binding.throttledMethod.originalName = methodToThrottle; + + binding[methodToThrottle] = throttle; + + binding.throttleState = { + delay: delay, + last: 0, + timeoutId: null + }; + }; + + ThrottleBindingBehavior.prototype.unbind = function unbind(binding, source) { + var methodToRestore = binding.throttledMethod.originalName; + binding[methodToRestore] = binding.throttledMethod; + binding.throttledMethod = null; + clearTimeout(binding.throttleState.timeoutId); + binding.throttleState = null; + }; + + return ThrottleBindingBehavior; +})(); + +exports.ThrottleBindingBehavior = ThrottleBindingBehavior; \ No newline at end of file diff --git a/dist/commonjs/update-trigger-binding-behavior.js b/dist/commonjs/update-trigger-binding-behavior.js new file mode 100644 index 0000000..fcd875e --- /dev/null +++ b/dist/commonjs/update-trigger-binding-behavior.js @@ -0,0 +1,53 @@ +'use strict'; + +exports.__esModule = true; + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +var _aureliaBinding = require('aurelia-binding'); + +var eventNamesRequired = 'The updateTrigger binding behavior requires at least one event name argument: eg '; +var notApplicableMessage = 'The updateTrigger binding behavior can only be applied to two-way bindings on input/select elements.'; + +var UpdateTriggerBindingBehavior = (function () { + _createClass(UpdateTriggerBindingBehavior, null, [{ + key: 'inject', + value: [_aureliaBinding.EventManager], + enumerable: true + }]); + + function UpdateTriggerBindingBehavior(eventManager) { + _classCallCheck(this, UpdateTriggerBindingBehavior); + + this.eventManager = eventManager; + } + + UpdateTriggerBindingBehavior.prototype.bind = function bind(binding, source) { + for (var _len = arguments.length, events = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { + events[_key - 2] = arguments[_key]; + } + + if (events.length === 0) { + throw new Error(eventNamesRequired); + } + if (binding.mode !== _aureliaBinding.bindingMode.twoWay || !binding.targetProperty.handler) { + throw new Error(notApplicableMessage); + } + + binding.targetProperty.originalHandler = binding.targetProperty.handler; + + var handler = this.eventManager.createElementHandler(events); + binding.targetProperty.handler = handler; + }; + + UpdateTriggerBindingBehavior.prototype.unbind = function unbind(binding, source) { + binding.targetProperty.handler = binding.targetProperty.originalHandler; + binding.targetProperty.originalHandler = null; + }; + + return UpdateTriggerBindingBehavior; +})(); + +exports.UpdateTriggerBindingBehavior = UpdateTriggerBindingBehavior; \ No newline at end of file diff --git a/dist/commonjs/with.js b/dist/commonjs/with.js index 6268a17..f1fc789 100644 --- a/dist/commonjs/with.js +++ b/dist/commonjs/with.js @@ -2,17 +2,13 @@ exports.__esModule = true; -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } var _aureliaDependencyInjection = require('aurelia-dependency-injection'); var _aureliaTemplating = require('aurelia-templating'); -var _aureliaLogging = require('aurelia-logging'); - -var LogManager = _interopRequireWildcard(_aureliaLogging); +var _aureliaBinding = require('aurelia-binding'); var With = (function () { function With(viewFactory, viewSlot) { @@ -20,15 +16,31 @@ var With = (function () { this.viewFactory = viewFactory; this.viewSlot = viewSlot; - LogManager.getLogger('templating-resources').warn('The "with" behavior will be removed in the next release.'); + this.parentOverrideContext = null; + this.view = null; } + With.prototype.bind = function bind(bindingContext, overrideContext) { + this.parentOverrideContext = overrideContext; + this.valueChanged(this.value); + }; + With.prototype.valueChanged = function valueChanged(newValue) { + var overrideContext = _aureliaBinding.createOverrideContext(newValue, this.parentOverrideContext); if (!this.view) { - this.view = this.viewFactory.create(newValue); + this.view = this.viewFactory.create(); + this.view.bind(newValue, overrideContext); this.viewSlot.add(this.view); } else { - this.view.bind(newValue); + this.view.bind(newValue, overrideContext); + } + }; + + With.prototype.unbind = function unbind() { + this.parentOverrideContext = null; + + if (this.view) { + this.view.unbind(); } }; diff --git a/dist/es6/array-collection-strategy.js b/dist/es6/array-collection-strategy.js new file mode 100644 index 0000000..033484c --- /dev/null +++ b/dist/es6/array-collection-strategy.js @@ -0,0 +1,74 @@ +import {CollectionStrategy} from './collection-strategy'; + +export class ArrayCollectionStrategy extends CollectionStrategy { + processItems(items) { + let i; + let ii; + let overrideContext; + let view; + this.items = items; + for (i = 0, ii = items.length; i < ii; ++i) { + overrideContext = super.createFullOverrideContext(items[i], i, ii); + view = this.viewFactory.create(); + view.bind(undefined, overrideContext); + this.viewSlot.add(view); + } + } + + getCollectionObserver(items) { + return this.observerLocator.getArrayObserver(items); + } + + handleChanges(array, splices) { + let removeDelta = 0; + let viewSlot = this.viewSlot; + let rmPromises = []; + + for (let i = 0, ii = splices.length; i < ii; ++i) { + let splice = splices[i]; + let removed = splice.removed; + + for (let j = 0, jj = removed.length; j < jj; ++j) { + let viewOrPromise = viewSlot.removeAt(splice.index + removeDelta + rmPromises.length, true); + if (viewOrPromise instanceof Promise) { + rmPromises.push(viewOrPromise); + } + } + removeDelta -= splice.addedCount; + } + + if (rmPromises.length > 0) { + Promise.all(rmPromises).then(() => { + let spliceIndexLow = this._handleAddedSplices(array, splices); + this.updateOverrideContexts(spliceIndexLow); + }); + } else { + let spliceIndexLow = this._handleAddedSplices(array, splices); + super.updateOverrideContexts(spliceIndexLow); + } + } + + _handleAddedSplices(array, splices) { + let spliceIndex; + let spliceIndexLow; + let arrayLength = array.length; + for (let i = 0, ii = splices.length; i < ii; ++i) { + let splice = splices[i]; + let addIndex = spliceIndex = splice.index; + let end = splice.index + splice.addedCount; + + if (typeof spliceIndexLow === 'undefined' || spliceIndexLow === null || spliceIndexLow > splice.index) { + spliceIndexLow = spliceIndex; + } + + for (; addIndex < end; ++addIndex) { + let overrideContext = this.createFullOverrideContext(array[addIndex], addIndex, arrayLength); + let view = this.viewFactory.create(); + view.bind(undefined, overrideContext); + this.viewSlot.insert(addIndex, view); + } + } + + return spliceIndexLow; + } +} diff --git a/dist/es6/aurelia-templating-resources.js b/dist/es6/aurelia-templating-resources.js index d68dd4a..7ba914e 100644 --- a/dist/es6/aurelia-templating-resources.js +++ b/dist/es6/aurelia-templating-resources.js @@ -3,7 +3,6 @@ import {If} from './if'; import {With} from './with'; import {Repeat} from './repeat'; import {Show} from './show'; -import {GlobalBehavior} from './global-behavior'; import {SanitizeHTMLValueConverter} from './sanitize-html'; import {Replaceable} from './replaceable'; import {Focus} from './focus'; @@ -14,6 +13,12 @@ import {_createDynamicElement} from './dynamic-element'; import {_createCSSResource} from './css-resource'; import {FEATURE, DOM} from 'aurelia-pal'; import {HTMLSanitizer} from './html-sanitizer'; +import {OneTimeBindingBehavior, OneWayBindingBehavior, TwoWayBindingBehavior} from './binding-mode-behaviors'; +import {ThrottleBindingBehavior} from './throttle-binding-behavior'; +import {DebounceBindingBehavior} from './debounce-binding-behavior'; +import {SignalBindingBehavior} from './signal-binding-behavior'; +import {BindingSignaler} from './binding-signaler'; +import {UpdateTriggerBindingBehavior} from './update-trigger-binding-behavior'; function configure(config) { if (FEATURE.shadowDOM) { @@ -29,11 +34,15 @@ function configure(config) { './repeat', './show', './replaceable', - './global-behavior', './sanitize-html', './focus', './compile-spy', - './view-spy' + './view-spy', + './binding-mode-behaviors', + './throttle-binding-behavior', + './debounce-binding-behavior', + './signal-binding-behavior', + './update-trigger-binding-behavior' ); let viewEngine = config.container.get(ViewEngine); @@ -77,10 +86,17 @@ export { Show, HTMLSanitizer, SanitizeHTMLValueConverter, - GlobalBehavior, Replaceable, Focus, CompileSpy, ViewSpy, - configure + configure, + OneTimeBindingBehavior, + OneWayBindingBehavior, + TwoWayBindingBehavior, + ThrottleBindingBehavior, + DebounceBindingBehavior, + SignalBindingBehavior, + BindingSignaler, + UpdateTriggerBindingBehavior }; diff --git a/dist/es6/binding-mode-behaviors.js b/dist/es6/binding-mode-behaviors.js new file mode 100644 index 0000000..50ace9e --- /dev/null +++ b/dist/es6/binding-mode-behaviors.js @@ -0,0 +1,35 @@ +import {bindingMode} from 'aurelia-binding'; + +class ModeBindingBehavior { + constructor(mode) { + this.mode = mode; + } + + bind(binding, source, lookupFunctions) { + binding.originalMode = binding.mode; + binding.mode = this.mode; + } + + unbind(binding, source) { + binding.mode = binding.originalMode; + binding.originalMode = null; + } +} + +export class OneTimeBindingBehavior extends ModeBindingBehavior { + constructor() { + super(bindingMode.oneTime); + } +} + +export class OneWayBindingBehavior extends ModeBindingBehavior { + constructor() { + super(bindingMode.oneWay); + } +} + +export class TwoWayBindingBehavior extends ModeBindingBehavior { + constructor() { + super(bindingMode.twoWay); + } +} diff --git a/dist/es6/binding-signaler.js b/dist/es6/binding-signaler.js new file mode 100644 index 0000000..21a0b30 --- /dev/null +++ b/dist/es6/binding-signaler.js @@ -0,0 +1,16 @@ +import {sourceContext} from 'aurelia-binding'; + +export class BindingSignaler { + signals = {}; + + signal(name: string): void { + let bindings = this.signals[name]; + if (!bindings) { + return; + } + let i = bindings.length; + while (i--) { + bindings[i].call(sourceContext); + } + } +} diff --git a/dist/es6/collection-strategy-locator.js b/dist/es6/collection-strategy-locator.js new file mode 100644 index 0000000..2dc4892 --- /dev/null +++ b/dist/es6/collection-strategy-locator.js @@ -0,0 +1,34 @@ +import {Container, inject} from 'aurelia-dependency-injection'; +import {ArrayCollectionStrategy} from './array-collection-strategy'; +import {MapCollectionStrategy} from './map-collection-strategy'; +import {NumberStrategy} from './number-strategy'; + +@inject(Container) +export class CollectionStrategyLocator { + constructor(container) { + this.container = container; + this.strategies = []; + this.matchers = []; + + this.addStrategy(ArrayCollectionStrategy, items => items instanceof Array); + this.addStrategy(MapCollectionStrategy, items => items instanceof Map); + this.addStrategy(NumberStrategy, items => typeof items === 'number'); + } + + addStrategy(collectionStrategy: Function, matcher: (items: any) => boolean) { + this.strategies.push(collectionStrategy); + this.matchers.push(matcher); + } + + getStrategy(items: any): CollectionStrategy { + let matchers = this.matchers; + + for (let i = 0, ii = matchers.length; i < ii; ++i) { + if (matchers[i](items)) { + return this.container.get(this.strategies[i]); + } + } + + throw new Error('Object in "repeat" must have a valid collection strategy.'); + } +} diff --git a/dist/es6/collection-strategy.js b/dist/es6/collection-strategy.js new file mode 100644 index 0000000..fa3b60b --- /dev/null +++ b/dist/es6/collection-strategy.js @@ -0,0 +1,78 @@ +import {inject, transient} from 'aurelia-dependency-injection'; +import {ObserverLocator, createOverrideContext} from 'aurelia-binding'; + +@inject(ObserverLocator) +@transient() +export class CollectionStrategy { + constructor(observerLocator) { + this.observerLocator = observerLocator; + } + + initialize(repeat, bindingContext, overrideContext) { + this.viewFactory = repeat.viewFactory; + this.viewSlot = repeat.viewSlot; + this.items = repeat.items; + this.local = repeat.local; + this.key = repeat.key; + this.value = repeat.value; + this.bindingContext = bindingContext; + this.overrideContext = overrideContext; + } + + dispose() { + this.viewFactory = null; + this.viewSlot = null; + this.items = null; + this.local = null; + this.key = null; + this.value = null; + this.bindingContext = null; + this.overrideContext = null; + } + + updateOverrideContexts(startIndex) { + let children = this.viewSlot.children; + let length = children.length; + + if (startIndex > 0) { + startIndex = startIndex - 1; + } + + for (; startIndex < length; ++startIndex) { + this.updateOverrideContext(children[startIndex].overrideContext, startIndex, length); + } + } + + createFullOverrideContext(data, index, length, key) { + let context = this.createBaseOverrideContext(data, key); + return this.updateOverrideContext(context, index, length); + } + + createBaseOverrideContext(data, key) { + let context = createOverrideContext(undefined, this.overrideContext); + // is key/value pair (Map) + if (typeof key !== 'undefined') { + context[this.key] = key; + context[this.value] = data; + } else { + context[this.local] = data; + } + + return context; + } + + updateOverrideContext(context, index, length) { + let first = (index === 0); + let last = (index === length - 1); + let even = index % 2 === 0; + + context.$index = index; + context.$first = first; + context.$last = last; + context.$middle = !(first || last); + context.$odd = !even; + context.$even = even; + + return context; + } +} diff --git a/dist/es6/compose.js b/dist/es6/compose.js index a5e6485..c135cc8 100644 --- a/dist/es6/compose.js +++ b/dist/es6/compose.js @@ -49,6 +49,8 @@ export class Compose { this.viewSlot = viewSlot; this.viewResources = viewResources; this.taskQueue = taskQueue; + this.currentController = null; + this.currentViewModel = null; } /** @@ -125,15 +127,15 @@ function createInstruction(composer, instruction) { container: composer.container, viewSlot: composer.viewSlot, viewResources: composer.viewResources, - currentBehavior: composer.currentBehavior, + currentController: composer.currentController, host: composer.element }); } function processInstruction(composer, instruction) { composer.currentInstruction = null; - composer.compositionEngine.compose(instruction).then(next => { - composer.currentBehavior = next; - composer.currentViewModel = next ? next.bindingContext : null; + composer.compositionEngine.compose(instruction).then(controller => { + composer.currentController = controller; + composer.currentViewModel = controller ? controller.viewModel : null; }); } diff --git a/dist/es6/debounce-binding-behavior.js b/dist/es6/debounce-binding-behavior.js new file mode 100644 index 0000000..e226fc6 --- /dev/null +++ b/dist/es6/debounce-binding-behavior.js @@ -0,0 +1,51 @@ +import {bindingMode} from 'aurelia-binding'; + +function debounce(newValue) { + let state = this.debounceState; + if (state.immediate) { + state.immediate = false; + this.debouncedMethod(newValue); + return; + } + clearTimeout(state.timeoutId); + state.timeoutId = setTimeout( + () => this.debouncedMethod(newValue), + state.delay); +} + +export class DebounceBindingBehavior { + bind(binding, source, delay = 200) { + // determine which method to debounce. + let methodToDebounce = 'updateTarget'; // one-way bindings or interpolation bindings + if (binding.callSource) { + methodToDebounce = 'callSource'; // listener and call bindings + } else if (binding.updateSource && binding.mode === bindingMode.twoWay) { + methodToDebounce = 'updateSource'; // two-way bindings + } + + // stash the original method and it's name. + // note: a generic name like "originalMethod" is not used to avoid collisions + // with other binding behavior types. + binding.debouncedMethod = binding[methodToDebounce]; + binding.debouncedMethod.originalName = methodToDebounce; + + // replace the original method with the debouncing version. + binding[methodToDebounce] = debounce; + + // create the debounce state. + binding.debounceState = { + delay: delay, + timeoutId: null, + immediate: methodToDebounce === 'updateTarget' // should not delay initial target update that occurs during bind. + }; + } + + unbind(binding, source) { + // restore the state of the binding. + let methodToRestore = binding.debouncedMethod.originalName; + binding[methodToRestore] = binding.debouncedMethod; + binding.debouncedMethod = null; + clearTimeout(binding.debounceState.timeoutId); + binding.debounceState = null; + } +} diff --git a/dist/es6/global-behavior.js b/dist/es6/global-behavior.js deleted file mode 100644 index 4729f36..0000000 --- a/dist/es6/global-behavior.js +++ /dev/null @@ -1,93 +0,0 @@ -import {inject} from 'aurelia-dependency-injection'; -import {customAttribute, dynamicOptions} from 'aurelia-templating'; -import * as LogManager from 'aurelia-logging'; -import {PLATFORM, DOM, AggregateError} from 'aurelia-pal'; - -@customAttribute('global-behavior') -@dynamicOptions -@inject(DOM.Element) -export class GlobalBehavior { - constructor(element) { - this.element = element; - LogManager.getLogger('templating-resources').warn('The "GlobalBehavior" behavior will be removed in the next release.'); - } - - bind() { - let handler = GlobalBehavior.handlers[this.aureliaAttrName]; - - if (!handler) { - throw new Error(`Binding handler not found for '${this.aureliaAttrName}.${this.aureliaCommand}'. Element:\n${this.element.outerHTML}\n`); - } - - try { - this.handler = handler.bind(this, this.element, this.aureliaCommand) || handler; - } catch (error) { - throw new AggregateError('Conventional binding handler failed.', error); - } - } - - attached() { - if (this.handler && 'attached' in this.handler) { - this.handler.attached(this, this.element); - } - } - - detached() { - if (this.handler && 'detached' in this.handler) { - this.handler.detached(this, this.element); - } - } - - unbind() { - if (this.handler && 'unbind' in this.handler) { - this.handler.unbind(this, this.element); - } - - this.handler = null; - } -} - -GlobalBehavior.createSettingsFromBehavior = function(behavior) { - let settings = {}; - - for (let key in behavior) { - if (key === 'aureliaAttrName' || key === 'aureliaCommand' || !behavior.hasOwnProperty(key)) { - continue; - } - - settings[key] = behavior[key]; - } - - return settings; -}; - -GlobalBehavior.jQueryPlugins = {}; - -GlobalBehavior.handlers = { - jquery: { - bind(behavior, element, command) { - let settings = GlobalBehavior.createSettingsFromBehavior(behavior); - let pluginName = GlobalBehavior.jQueryPlugins[command] || command; - let jqueryElement = PLATFORM.global.jQuery(element); - - if (!jqueryElement[pluginName]) { - LogManager.getLogger('templating-resources') - .warn(`Could not find the jQuery plugin ${pluginName}, possibly due to case mismatch. Trying to enumerate jQuery methods in lowercase. Add the correctly cased plugin name to the GlobalBehavior to avoid this performance hit.`); - - for (let prop in jqueryElement) { - if (prop.toLowerCase() === pluginName) { - pluginName = prop; - } - } - } - - behavior.plugin = jqueryElement[pluginName](settings); - }, - unbind(behavior, element) { - if (typeof behavior.plugin.destroy === 'function') { - behavior.plugin.destroy(); - behavior.plugin = null; - } - } - } -}; diff --git a/dist/es6/if.js b/dist/es6/if.js index e9e0932..e30b564 100644 --- a/dist/es6/if.js +++ b/dist/es6/if.js @@ -16,12 +16,14 @@ export class If { this.showing = false; this.taskQueue = taskQueue; this.view = null; - this.$parent = null; + this.bindingContext = null; + this.overrideContext = null; } - bind(bindingContext) { + bind(bindingContext, overrideContext) { // Store parent bindingContext, so we can pass it down - this.$parent = bindingContext; + this.bindingContext = bindingContext; + this.overrideContext = overrideContext; this.valueChanged(this.value); } @@ -43,14 +45,14 @@ export class If { } if (this.view === null) { - this.view = this.viewFactory.create(this.$parent); + this.view = this.viewFactory.create(); } if (!this.showing) { this.showing = true; if (!this.view.isBound) { - this.view.bind(this.$parent); + this.view.bind(this.bindingContext, this.overrideContext); } this.viewSlot.add(this.view); diff --git a/dist/es6/map-collection-strategy.js b/dist/es6/map-collection-strategy.js new file mode 100644 index 0000000..9dac605 --- /dev/null +++ b/dist/es6/map-collection-strategy.js @@ -0,0 +1,95 @@ +import {CollectionStrategy} from './collection-strategy'; + +export class MapCollectionStrategy extends CollectionStrategy { + getCollectionObserver(items) { + return this.observerLocator.getMapObserver(items); + } + + processItems(items) { + let viewFactory = this.viewFactory; + let viewSlot = this.viewSlot; + let index = 0; + let overrideContext; + let view; + + items.forEach((value, key) => { + overrideContext = this.createFullOverrideContext(value, index, items.size, key); + view = viewFactory.create(); + view.bind(undefined, overrideContext); + viewSlot.add(view); + ++index; + }); + } + + handleChanges(map, records) { + let viewSlot = this.viewSlot; + let key; + let i; + let ii; + let view; + let overrideContext; + let removeIndex; + let record; + let rmPromises = []; + let viewOrPromise; + + for (i = 0, ii = records.length; i < ii; ++i) { + record = records[i]; + key = record.key; + switch (record.type) { + case 'update': + removeIndex = this._getViewIndexByKey(key); + viewOrPromise = viewSlot.removeAt(removeIndex, true); + if (viewOrPromise instanceof Promise) { + rmPromises.push(viewOrPromise); + } + overrideContext = this.createFullOverrideContext(map.get(key), removeIndex, map.size, key); + view = this.viewFactory.create(); + view.bind(undefined, overrideContext); + viewSlot.insert(removeIndex, view); + break; + case 'add': + overrideContext = this.createFullOverrideContext(map.get(key), map.size - 1, map.size, key); + view = this.viewFactory.create(); + view.bind(undefined, overrideContext); + viewSlot.insert(map.size - 1, view); + break; + case 'delete': + if (record.oldValue === undefined) { return; } + removeIndex = this._getViewIndexByKey(key); + viewOrPromise = viewSlot.removeAt(removeIndex, true); + if (viewOrPromise instanceof Promise) { + rmPromises.push(viewOrPromise); + } + break; + case 'clear': + viewSlot.removeAll(true); + break; + default: + continue; + } + } + + if (rmPromises.length > 0) { + Promise.all(rmPromises).then(() => { + this.updateOverrideContexts(0); + }); + } else { + this.updateOverrideContexts(0); + } + } + + _getViewIndexByKey(key) { + let viewSlot = this.viewSlot; + let i; + let ii; + let child; + + for (i = 0, ii = viewSlot.children.length; i < ii; ++i) { + child = viewSlot.children[i]; + if (child.overrideContext[this.key] === key) { + return i; + } + } + } +} diff --git a/dist/es6/number-strategy.js b/dist/es6/number-strategy.js new file mode 100644 index 0000000..e3ffa71 --- /dev/null +++ b/dist/es6/number-strategy.js @@ -0,0 +1,42 @@ +import {CollectionStrategy} from './collection-strategy'; + +export class NumberStrategy extends CollectionStrategy { + getCollectionObserver() { + return; + } + + processItems(value) { + let viewFactory = this.viewFactory; + let viewSlot = this.viewSlot; + let childrenLength = viewSlot.children.length; + let i; + let ii; + let overrideContext; + let view; + let viewsToRemove; + + value = Math.floor(value); + viewsToRemove = childrenLength - value; + + if (viewsToRemove > 0) { + if (viewsToRemove > childrenLength) { + viewsToRemove = childrenLength; + } + + for (i = 0, ii = viewsToRemove; i < ii; ++i) { + viewSlot.removeAt(childrenLength - (i + 1), true); + } + + return; + } + + for (i = childrenLength, ii = value; i < ii; ++i) { + overrideContext = this.createFullOverrideContext(i, i, ii); + view = viewFactory.create(); + view.bind(undefined, overrideContext); + viewSlot.add(view); + } + + this.updateOverrideContexts(0); + } +} diff --git a/dist/es6/repeat.js b/dist/es6/repeat.js index 5703e4f..6c1ec60 100644 --- a/dist/es6/repeat.js +++ b/dist/es6/repeat.js @@ -1,7 +1,21 @@ /*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'; /** * Binding to iterate over an array and repeat a template @@ -9,12 +23,14 @@ import {BoundViewFactory, ViewSlot, customAttribute, bindable, templateControlle * @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) +@inject(BoundViewFactory, TargetInstruction, ViewSlot, ViewResources, ObserverLocator, CollectionStrategyLocator) export class Repeat { /** * List of items to bind the repeater to @@ -26,68 +42,42 @@ export class Repeat { @bindable local @bindable key @bindable value - constructor(viewFactory, viewSlot, observerLocator) { + 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'; this.value = 'value'; + this.collectionStrategyLocator = collectionStrategyLocator; } call(context, changes) { this[context](this.items, changes); } - bind(bindingContext) { + bind(bindingContext, overrideContext) { let items = this.items; - let viewSlot = this.viewSlot; - let observer; - - this.bindingContext = bindingContext; - - if (!items) { - if (this.oldItems) { - viewSlot.removeAll(true); - } - - return; - } - - if (this.oldItems === items) { - if (items instanceof Map) { - let records = getChangeRecords(items); - this.collectionObserver = this.observerLocator.getMapObserver(items); - - this.handleMapChangeRecords(items, records); - - this.callContext = 'handleMapChangeRecords'; - this.collectionObserver.subscribe(this.callContext, this); - } else if (items instanceof Array) { - let splices = calcSplices(items, 0, items.length, this.lastBoundItems, 0, this.lastBoundItems.length); - this.collectionObserver = this.observerLocator.getArrayObserver(items); - - this.handleSplices(items, splices); - this.lastBoundItems = this.oldItems = null; - - this.callContext = 'handleSplices'; - this.collectionObserver.subscribe(this.callContext, this); - } + if (items === undefined || items === null) { return; - } else if (this.oldItems) { - viewSlot.removeAll(true); } + 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.oldItems = this.items; - - if (this.items instanceof Array) { - this.lastBoundItems = this.items.slice(0); - } - + this.sourceExpression = null; + this.scope = null; + this.collectionStrategy.dispose(); + this.items = null; + this.collectionStrategy = null; + this.viewSlot.removeAll(true); this.unsubscribeCollection(); } @@ -118,265 +108,81 @@ export class Repeat { if (rmPromise instanceof Promise) { rmPromise.then(() => { - this.processItemsByType(); + this.processItemsByStrategy(); }); } else { - this.processItemsByType(); + this.processItemsByStrategy(); } } - processItemsByType() { - let items = this.items; - if (items instanceof Array) { - this.processArrayItems(items); - } else if (items instanceof Map) { - this.processMapEntries(items); - } else if ((typeof items === 'number')) { - this.processNumber(items); - } else { - throw new Error('Object in "repeat" must be of type Array, Map or Number'); + getInnerCollection() { + let expression = unwrapExpression(this.sourceExpression); + if (!expression) { + return null; } + return expression.evaluate(this.scope, null); } - processArrayItems(items) { - let viewFactory = this.viewFactory; - let viewSlot = this.viewSlot; - let i; - let ii; - let row; - let view; - let observer; - - this.collectionObserver = this.observerLocator.getArrayObserver(items); - - for (i = 0, ii = items.length; i < ii; ++i) { - row = this.createFullBindingContext(items[i], i, ii); - view = viewFactory.create(row); - viewSlot.add(view); + 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 = 'handleSplices'; - this.collectionObserver.subscribe(this.callContext, this); - } - - processMapEntries(items) { - let viewFactory = this.viewFactory; - let viewSlot = this.viewSlot; - let index = 0; - let row; - let view; - let observer; - - this.collectionObserver = this.observerLocator.getMapObserver(items); - - items.forEach((value, key) => { - row = this.createFullExecutionKvpContext(key, value, index, items.size); - view = viewFactory.create(row); - viewSlot.add(view); - ++index; - }); - - this.callContext = 'handleMapChangeRecords'; + this.callContext = 'handleInnerCollectionChanges'; this.collectionObserver.subscribe(this.callContext, this); + return true; } - processNumber(value) { - let viewFactory = this.viewFactory; - let viewSlot = this.viewSlot; - let childrenLength = viewSlot.children.length; - let i; - let ii; - let row; - let view; - let viewsToRemove; - - value = Math.floor(value); - viewsToRemove = childrenLength - value; - - if (viewsToRemove > 0) { - if (viewsToRemove > childrenLength) { - viewsToRemove = childrenLength; - } - - for (i = 0, ii = viewsToRemove; i < ii; ++i) { - viewSlot.removeAt(childrenLength - (i + 1), true); - } - - return; - } - - for (i = childrenLength, ii = value; i < ii; ++i) { - row = this.createFullBindingContext(i, i, ii); - view = viewFactory.create(row); - viewSlot.add(view); + observeCollection() { + let items = this.items; + this.collectionObserver = this.collectionStrategy.getCollectionObserver(items); + if (this.collectionObserver) { + this.callContext = 'handleCollectionChanges'; + this.collectionObserver.subscribe(this.callContext, this); } } - createBaseBindingContext(data) { - let context = {}; - context[this.local] = data; - context.$parent = this.bindingContext; - return context; - } - - createBaseExecutionKvpContext(key, value) { - let context = {}; - context[this.key] = key; - context[this.value] = value; - context.$parent = this.bindingContext; - return context; - } - - createFullBindingContext(data, index, length) { - let context = this.createBaseBindingContext(data); - return this.updateBindingContext(context, index, length); - } - - createFullExecutionKvpContext(key, value, index, length) { - let context = this.createBaseExecutionKvpContext(key, value); - return this.updateBindingContext(context, index, length); - } - - updateBindingContext(context, index, length) { - let first = (index === 0); - let last = (index === length - 1); - let even = index % 2 === 0; - - context.$index = index; - context.$first = first; - context.$last = last; - context.$middle = !(first || last); - context.$odd = !even; - context.$even = even; - - return context; - } - - updateBindingContexts(startIndex) { - let children = this.viewSlot.children; - let length = children.length; - - if (startIndex > 0) { - startIndex = startIndex - 1; - } - - for (; startIndex < length; ++startIndex) { - this.updateBindingContext(children[startIndex].bindingContext, startIndex, length); + processItemsByStrategy() { + if (!this.observeInnerCollection()) { + this.observeCollection(); } + this.collectionStrategy.processItems(this.items); } - handleSplices(array, splices) { - let removeDelta = 0; - let viewSlot = this.viewSlot; - let rmPromises = []; - - for (let i = 0, ii = splices.length; i < ii; ++i) { - let splice = splices[i]; - let removed = splice.removed; - - for (let j = 0, jj = removed.length; j < jj; ++j) { - let viewOrPromise = viewSlot.removeAt(splice.index + removeDelta + rmPromises.length, true); - if (viewOrPromise instanceof Promise) { - rmPromises.push(viewOrPromise); - } - } - removeDelta -= splice.addedCount; - } - - if (rmPromises.length > 0) { - Promise.all(rmPromises).then(() => { - let spliceIndexLow = this.handleAddedSplices(array, splices); - this.updateBindingContexts(spliceIndexLow); - }); - } else { - let spliceIndexLow = this.handleAddedSplices(array, splices); - this.updateBindingContexts(spliceIndexLow); - } + handleCollectionChanges(collection, changes) { + this.collectionStrategy.handleChanges(collection, changes); } - handleAddedSplices(array, splices) { - let spliceIndex; - let spliceIndexLow; - let arrayLength = array.length; - for (let i = 0, ii = splices.length; i < ii; ++i) { - let splice = splices[i]; - let addIndex = spliceIndex = splice.index; - let end = splice.index + splice.addedCount; - - if (typeof spliceIndexLow === 'undefined' || spliceIndexLow === null || spliceIndexLow > splice.index) { - spliceIndexLow = spliceIndex; - } - - for (; addIndex < end; ++addIndex) { - let row = this.createFullBindingContext(array[addIndex], addIndex, arrayLength); - let view = this.viewFactory.create(row); - this.viewSlot.insert(addIndex, view); - } + handleInnerCollectionChanges(collection, changes) { + let newItems = this.sourceExpression.evaluate(this.scope, this.lookupFunctions); + if (newItems === this.items) { + return; } - - return spliceIndexLow; + this.items = newItems; + this.itemsChanged(); } +} - handleMapChangeRecords(map, records) { - let viewSlot = this.viewSlot; - let key; - let i; - let ii; - let view; - let children; - let length; - let row; - let removeIndex; - let record; - - for (i = 0, ii = records.length; i < ii; ++i) { - record = records[i]; - key = record.key; - switch (record.type) { - case 'update': - removeIndex = this.getViewIndexByKey(key); - viewSlot.removeAt(removeIndex, true); - row = this.createBaseExecutionKvpContext(key, map.get(key)); - view = this.viewFactory.create(row); - viewSlot.insert(removeIndex, view); - break; - case 'add': - row = this.createBaseExecutionKvpContext(key, map.get(key)); - view = this.viewFactory.create(row); - viewSlot.insert(map.size, view); - break; - case 'delete': - if (!record.oldValue) { return; } - removeIndex = this.getViewIndexByKey(key); - viewSlot.removeAt(removeIndex, true); - break; - case 'clear': - viewSlot.removeAll(true); - break; - default: - continue; - } - } - - children = viewSlot.children; - length = children.length; +function getSourceExpression(instruction, attrName) { + return instruction.behaviorInstructions + .filter(bi => bi.originalAttrName === attrName)[0] + .attributes + .items + .sourceExpression; +} - for (i = 0; i < length; i++) { - this.updateBindingContext(children[i].bindingContext, i, length); - } +function unwrapExpression(expression) { + let unwrapped = false; + while (expression instanceof BindingBehavior) { + expression = expression.expression; } - - getViewIndexByKey(key) { - let viewSlot = this.viewSlot; - let i; - let ii; - let child; - - for (i = 0, ii = viewSlot.children.length; i < ii; ++i) { // TODO (martingust) better way to get index? - child = viewSlot.children[i]; - if (child.bindings[0].source[this.key] === key) { - return i; - } - } + while (expression instanceof ValueConverter) { + expression = expression.expression; + unwrapped = true; } + return unwrapped ? expression : null; } diff --git a/dist/es6/replaceable.js b/dist/es6/replaceable.js index bcaf7fd..5fb8037 100644 --- a/dist/es6/replaceable.js +++ b/dist/es6/replaceable.js @@ -8,13 +8,19 @@ export class Replaceable { constructor(viewFactory, viewSlot) { this.viewFactory = viewFactory; this.viewSlot = viewSlot; - this.needsReplacement = true; + this.view = null; } - bind() { - if (this.needsReplacement) { - this.needsReplacement = false; - this.viewSlot.add(this.viewFactory.create()); + bind(bindingContext, overrideContext) { + if (this.view === null) { + this.view = this.viewFactory.create(); + this.viewSlot.add(this.view); } + + this.view.bind(bindingContext, overrideContext); + } + + unbind() { + this.view.unbind(); } } diff --git a/dist/es6/signal-binding-behavior.js b/dist/es6/signal-binding-behavior.js new file mode 100644 index 0000000..c708f41 --- /dev/null +++ b/dist/es6/signal-binding-behavior.js @@ -0,0 +1,30 @@ +import {bindingMode} from 'aurelia-binding'; +import {BindingSignaler} from './binding-signaler'; + +export class SignalBindingBehavior { + static inject() { return [BindingSignaler]; } + signals; + + constructor(bindingSignaler) { + this.signals = bindingSignaler.signals; + } + + bind(binding, source, name) { + if (!binding.updateTarget) { + throw new Error('Only property bindings and string interpolation bindings can be signaled. Trigger, delegate and call bindings cannot be signaled.'); + } + if (binding.mode === bindingMode.oneTime) { + throw new Error('One-time bindings cannot be signaled.'); + } + let bindings = this.signals[name] || (this.signals[name] = []); + bindings.push(binding); + binding.signalName = name; + } + + unbind(binding, source) { + let name = binding.signalName; + binding.signalName = null; + let bindings = signals[name]; + bindings.splice(bindings.indexOf(binding), 1); + } +} diff --git a/dist/es6/throttle-binding-behavior.js b/dist/es6/throttle-binding-behavior.js new file mode 100644 index 0000000..41e64cb --- /dev/null +++ b/dist/es6/throttle-binding-behavior.js @@ -0,0 +1,60 @@ +import {bindingMode} from 'aurelia-binding'; + +function throttle(newValue) { + let state = this.throttleState; + let elapsed = +new Date() - state.last; + if (elapsed >= state.delay) { + clearTimeout(state.timeoutId); + state.timeoutId = null; + state.last = +new Date(); + this.throttledMethod(newValue); + return; + } + state.newValue = newValue; + if (state.timeoutId === null) { + state.timeoutId = setTimeout( + () => { + state.timeoutId = null; + state.last = +new Date(); + this.throttledMethod(state.newValue); + }, + state.delay - elapsed); + } +} + +export class ThrottleBindingBehavior { + bind(binding, source, delay = 200) { + // determine which method to throttle. + let methodToThrottle = 'updateTarget'; // one-way bindings or interpolation bindings + if (binding.callSource) { + methodToThrottle = 'callSource'; // listener and call bindings + } else if (binding.updateSource && binding.mode === bindingMode.twoWay) { + methodToThrottle = 'updateSource'; // two-way bindings + } + + // stash the original method and it's name. + // note: a generic name like "originalMethod" is not used to avoid collisions + // with other binding behavior types. + binding.throttledMethod = binding[methodToThrottle]; + binding.throttledMethod.originalName = methodToThrottle; + + // replace the original method with the throttling version. + binding[methodToThrottle] = throttle; + + // create the throttle state. + binding.throttleState = { + delay: delay, + last: 0, + timeoutId: null + }; + } + + unbind(binding, source) { + // restore the state of the binding. + let methodToRestore = binding.throttledMethod.originalName; + binding[methodToRestore] = binding.throttledMethod; + binding.throttledMethod = null; + clearTimeout(binding.throttleState.timeoutId); + binding.throttleState = null; + } +} diff --git a/dist/es6/update-trigger-binding-behavior.js b/dist/es6/update-trigger-binding-behavior.js new file mode 100644 index 0000000..334e231 --- /dev/null +++ b/dist/es6/update-trigger-binding-behavior.js @@ -0,0 +1,34 @@ +import {bindingMode, EventManager} from 'aurelia-binding'; + +const eventNamesRequired = `The updateTrigger binding behavior requires at least one event name argument: eg `; +const notApplicableMessage = `The updateTrigger binding behavior can only be applied to two-way bindings on input/select elements.`; + +export class UpdateTriggerBindingBehavior { + static inject = [EventManager]; + + constructor(eventManager) { + this.eventManager = eventManager; + } + + bind(binding, source, ...events) { + if (events.length === 0) { + throw new Error(eventNamesRequired); + } + if (binding.mode !== bindingMode.twoWay || !binding.targetProperty.handler) { + throw new Error(notApplicableMessage); + } + + // stash the original element subscribe function. + binding.targetProperty.originalHandler = binding.targetProperty.handler; + + // replace the element subscribe function with one that uses the correct events. + let handler = this.eventManager.createElementHandler(events); + binding.targetProperty.handler = handler; + } + + unbind(binding, source) { + // restore the state of the binding. + binding.targetProperty.handler = binding.targetProperty.originalHandler; + binding.targetProperty.originalHandler = null; + } +} diff --git a/dist/es6/with.js b/dist/es6/with.js index 6503629..68a02ec 100644 --- a/dist/es6/with.js +++ b/dist/es6/with.js @@ -1,6 +1,6 @@ import {inject} from 'aurelia-dependency-injection'; import {BoundViewFactory, ViewSlot, customAttribute, templateController} from 'aurelia-templating'; -import * as LogManager from 'aurelia-logging'; +import {createOverrideContext} from 'aurelia-binding'; @customAttribute('with') @templateController @@ -9,15 +9,31 @@ export class With { constructor(viewFactory, viewSlot) { this.viewFactory = viewFactory; this.viewSlot = viewSlot; - LogManager.getLogger('templating-resources').warn('The "with" behavior will be removed in the next release.'); + this.parentOverrideContext = null; + this.view = null; + } + + bind(bindingContext, overrideContext) { + this.parentOverrideContext = overrideContext; + this.valueChanged(this.value); } valueChanged(newValue) { + let overrideContext = createOverrideContext(newValue, this.parentOverrideContext); if (!this.view) { - this.view = this.viewFactory.create(newValue); + this.view = this.viewFactory.create(); + this.view.bind(newValue, overrideContext); this.viewSlot.add(this.view); } else { - this.view.bind(newValue); + this.view.bind(newValue, overrideContext); + } + } + + unbind() { + this.parentOverrideContext = null; + + if (this.view) { + this.view.unbind(); } } } diff --git a/dist/system/array-collection-strategy.js b/dist/system/array-collection-strategy.js new file mode 100644 index 0000000..d3347f5 --- /dev/null +++ b/dist/system/array-collection-strategy.js @@ -0,0 +1,103 @@ +System.register(['./collection-strategy'], function (_export) { + 'use strict'; + + var CollectionStrategy, ArrayCollectionStrategy; + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + return { + setters: [function (_collectionStrategy) { + CollectionStrategy = _collectionStrategy.CollectionStrategy; + }], + execute: function () { + ArrayCollectionStrategy = (function (_CollectionStrategy) { + _inherits(ArrayCollectionStrategy, _CollectionStrategy); + + function ArrayCollectionStrategy() { + _classCallCheck(this, ArrayCollectionStrategy); + + _CollectionStrategy.apply(this, arguments); + } + + ArrayCollectionStrategy.prototype.processItems = function processItems(items) { + var i = undefined; + var ii = undefined; + var overrideContext = undefined; + var view = undefined; + this.items = items; + for (i = 0, ii = items.length; i < ii; ++i) { + overrideContext = _CollectionStrategy.prototype.createFullOverrideContext.call(this, items[i], i, ii); + view = this.viewFactory.create(); + view.bind(undefined, overrideContext); + this.viewSlot.add(view); + } + }; + + ArrayCollectionStrategy.prototype.getCollectionObserver = function getCollectionObserver(items) { + return this.observerLocator.getArrayObserver(items); + }; + + ArrayCollectionStrategy.prototype.handleChanges = function handleChanges(array, splices) { + var _this = this; + + var removeDelta = 0; + var viewSlot = this.viewSlot; + var rmPromises = []; + + for (var i = 0, ii = splices.length; i < ii; ++i) { + var splice = splices[i]; + var removed = splice.removed; + + for (var j = 0, jj = removed.length; j < jj; ++j) { + var viewOrPromise = viewSlot.removeAt(splice.index + removeDelta + rmPromises.length, true); + if (viewOrPromise instanceof Promise) { + rmPromises.push(viewOrPromise); + } + } + removeDelta -= splice.addedCount; + } + + if (rmPromises.length > 0) { + Promise.all(rmPromises).then(function () { + var spliceIndexLow = _this._handleAddedSplices(array, splices); + _this.updateOverrideContexts(spliceIndexLow); + }); + } else { + var spliceIndexLow = this._handleAddedSplices(array, splices); + _CollectionStrategy.prototype.updateOverrideContexts.call(this, spliceIndexLow); + } + }; + + ArrayCollectionStrategy.prototype._handleAddedSplices = function _handleAddedSplices(array, splices) { + var spliceIndex = undefined; + var spliceIndexLow = undefined; + var arrayLength = array.length; + for (var i = 0, ii = splices.length; i < ii; ++i) { + var splice = splices[i]; + var addIndex = spliceIndex = splice.index; + var end = splice.index + splice.addedCount; + + if (typeof spliceIndexLow === 'undefined' || spliceIndexLow === null || spliceIndexLow > splice.index) { + spliceIndexLow = spliceIndex; + } + + for (; addIndex < end; ++addIndex) { + var overrideContext = this.createFullOverrideContext(array[addIndex], addIndex, arrayLength); + var view = this.viewFactory.create(); + view.bind(undefined, overrideContext); + this.viewSlot.insert(addIndex, view); + } + } + + return spliceIndexLow; + }; + + return ArrayCollectionStrategy; + })(CollectionStrategy); + + _export('ArrayCollectionStrategy', ArrayCollectionStrategy); + } + }; +}); \ No newline at end of file diff --git a/dist/system/aurelia-templating-resources.js b/dist/system/aurelia-templating-resources.js index 5e6ab5f..2c217c4 100644 --- a/dist/system/aurelia-templating-resources.js +++ b/dist/system/aurelia-templating-resources.js @@ -1,7 +1,7 @@ -System.register(['./compose', './if', './with', './repeat', './show', './global-behavior', './sanitize-html', './replaceable', './focus', './compile-spy', './view-spy', 'aurelia-templating', './dynamic-element', './css-resource', 'aurelia-pal', './html-sanitizer'], function (_export) { +System.register(['./compose', './if', './with', './repeat', './show', './sanitize-html', './replaceable', './focus', './compile-spy', './view-spy', 'aurelia-templating', './dynamic-element', './css-resource', 'aurelia-pal', './html-sanitizer', './binding-mode-behaviors', './throttle-binding-behavior', './debounce-binding-behavior', './signal-binding-behavior', './binding-signaler', './update-trigger-binding-behavior'], function (_export) { 'use strict'; - var Compose, If, With, Repeat, Show, GlobalBehavior, SanitizeHTMLValueConverter, Replaceable, Focus, CompileSpy, ViewSpy, ViewEngine, _createDynamicElement, _createCSSResource, FEATURE, DOM, HTMLSanitizer; + var Compose, If, With, Repeat, Show, SanitizeHTMLValueConverter, Replaceable, Focus, CompileSpy, ViewSpy, ViewEngine, _createDynamicElement, _createCSSResource, FEATURE, DOM, HTMLSanitizer, OneTimeBindingBehavior, OneWayBindingBehavior, TwoWayBindingBehavior, ThrottleBindingBehavior, DebounceBindingBehavior, SignalBindingBehavior, BindingSignaler, UpdateTriggerBindingBehavior; function configure(config) { if (FEATURE.shadowDOM) { @@ -10,7 +10,7 @@ System.register(['./compose', './if', './with', './repeat', './show', './global- DOM.injectStyles('.aurelia-hide { display:none !important; }'); } - config.globalResources('./compose', './if', './with', './repeat', './show', './replaceable', './global-behavior', './sanitize-html', './focus', './compile-spy', './view-spy'); + config.globalResources('./compose', './if', './with', './repeat', './show', './replaceable', './sanitize-html', './focus', './compile-spy', './view-spy', './binding-mode-behaviors', './throttle-binding-behavior', './debounce-binding-behavior', './signal-binding-behavior', './update-trigger-binding-behavior'); var viewEngine = config.container.get(ViewEngine); var loader = config.aurelia.loader; @@ -62,8 +62,6 @@ System.register(['./compose', './if', './with', './repeat', './show', './global- Repeat = _repeat.Repeat; }, function (_show) { Show = _show.Show; - }, function (_globalBehavior) { - GlobalBehavior = _globalBehavior.GlobalBehavior; }, function (_sanitizeHtml) { SanitizeHTMLValueConverter = _sanitizeHtml.SanitizeHTMLValueConverter; }, function (_replaceable) { @@ -85,6 +83,20 @@ System.register(['./compose', './if', './with', './repeat', './show', './global- DOM = _aureliaPal.DOM; }, function (_htmlSanitizer) { HTMLSanitizer = _htmlSanitizer.HTMLSanitizer; + }, function (_bindingModeBehaviors) { + OneTimeBindingBehavior = _bindingModeBehaviors.OneTimeBindingBehavior; + OneWayBindingBehavior = _bindingModeBehaviors.OneWayBindingBehavior; + TwoWayBindingBehavior = _bindingModeBehaviors.TwoWayBindingBehavior; + }, function (_throttleBindingBehavior) { + ThrottleBindingBehavior = _throttleBindingBehavior.ThrottleBindingBehavior; + }, function (_debounceBindingBehavior) { + DebounceBindingBehavior = _debounceBindingBehavior.DebounceBindingBehavior; + }, function (_signalBindingBehavior) { + SignalBindingBehavior = _signalBindingBehavior.SignalBindingBehavior; + }, function (_bindingSignaler) { + BindingSignaler = _bindingSignaler.BindingSignaler; + }, function (_updateTriggerBindingBehavior) { + UpdateTriggerBindingBehavior = _updateTriggerBindingBehavior.UpdateTriggerBindingBehavior; }], execute: function () { _export('Compose', Compose); @@ -101,8 +113,6 @@ System.register(['./compose', './if', './with', './repeat', './show', './global- _export('SanitizeHTMLValueConverter', SanitizeHTMLValueConverter); - _export('GlobalBehavior', GlobalBehavior); - _export('Replaceable', Replaceable); _export('Focus', Focus); @@ -112,6 +122,22 @@ System.register(['./compose', './if', './with', './repeat', './show', './global- _export('ViewSpy', ViewSpy); _export('configure', configure); + + _export('OneTimeBindingBehavior', OneTimeBindingBehavior); + + _export('OneWayBindingBehavior', OneWayBindingBehavior); + + _export('TwoWayBindingBehavior', TwoWayBindingBehavior); + + _export('ThrottleBindingBehavior', ThrottleBindingBehavior); + + _export('DebounceBindingBehavior', DebounceBindingBehavior); + + _export('SignalBindingBehavior', SignalBindingBehavior); + + _export('BindingSignaler', BindingSignaler); + + _export('UpdateTriggerBindingBehavior', UpdateTriggerBindingBehavior); } }; }); \ No newline at end of file diff --git a/dist/system/binding-mode-behaviors.js b/dist/system/binding-mode-behaviors.js new file mode 100644 index 0000000..33cadf0 --- /dev/null +++ b/dist/system/binding-mode-behaviors.js @@ -0,0 +1,78 @@ +System.register(['aurelia-binding'], function (_export) { + 'use strict'; + + var bindingMode, ModeBindingBehavior, OneTimeBindingBehavior, OneWayBindingBehavior, TwoWayBindingBehavior; + + function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + return { + setters: [function (_aureliaBinding) { + bindingMode = _aureliaBinding.bindingMode; + }], + execute: function () { + ModeBindingBehavior = (function () { + function ModeBindingBehavior(mode) { + _classCallCheck(this, ModeBindingBehavior); + + this.mode = mode; + } + + ModeBindingBehavior.prototype.bind = function bind(binding, source, lookupFunctions) { + binding.originalMode = binding.mode; + binding.mode = this.mode; + }; + + ModeBindingBehavior.prototype.unbind = function unbind(binding, source) { + binding.mode = binding.originalMode; + binding.originalMode = null; + }; + + return ModeBindingBehavior; + })(); + + OneTimeBindingBehavior = (function (_ModeBindingBehavior) { + _inherits(OneTimeBindingBehavior, _ModeBindingBehavior); + + function OneTimeBindingBehavior() { + _classCallCheck(this, OneTimeBindingBehavior); + + _ModeBindingBehavior.call(this, bindingMode.oneTime); + } + + return OneTimeBindingBehavior; + })(ModeBindingBehavior); + + _export('OneTimeBindingBehavior', OneTimeBindingBehavior); + + OneWayBindingBehavior = (function (_ModeBindingBehavior2) { + _inherits(OneWayBindingBehavior, _ModeBindingBehavior2); + + function OneWayBindingBehavior() { + _classCallCheck(this, OneWayBindingBehavior); + + _ModeBindingBehavior2.call(this, bindingMode.oneWay); + } + + return OneWayBindingBehavior; + })(ModeBindingBehavior); + + _export('OneWayBindingBehavior', OneWayBindingBehavior); + + TwoWayBindingBehavior = (function (_ModeBindingBehavior3) { + _inherits(TwoWayBindingBehavior, _ModeBindingBehavior3); + + function TwoWayBindingBehavior() { + _classCallCheck(this, TwoWayBindingBehavior); + + _ModeBindingBehavior3.call(this, bindingMode.twoWay); + } + + return TwoWayBindingBehavior; + })(ModeBindingBehavior); + + _export('TwoWayBindingBehavior', TwoWayBindingBehavior); + } + }; +}); \ No newline at end of file diff --git a/dist/system/binding-signaler.js b/dist/system/binding-signaler.js new file mode 100644 index 0000000..a614f5b --- /dev/null +++ b/dist/system/binding-signaler.js @@ -0,0 +1,37 @@ +System.register(['aurelia-binding'], function (_export) { + 'use strict'; + + var sourceContext, BindingSignaler; + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + return { + setters: [function (_aureliaBinding) { + sourceContext = _aureliaBinding.sourceContext; + }], + execute: function () { + BindingSignaler = (function () { + function BindingSignaler() { + _classCallCheck(this, BindingSignaler); + + this.signals = {}; + } + + BindingSignaler.prototype.signal = function signal(name) { + var bindings = this.signals[name]; + if (!bindings) { + return; + } + var i = bindings.length; + while (i--) { + bindings[i].call(sourceContext); + } + }; + + return BindingSignaler; + })(); + + _export('BindingSignaler', BindingSignaler); + } + }; +}); \ No newline at end of file diff --git a/dist/system/collection-strategy-locator.js b/dist/system/collection-strategy-locator.js new file mode 100644 index 0000000..0d38010 --- /dev/null +++ b/dist/system/collection-strategy-locator.js @@ -0,0 +1,64 @@ +System.register(['aurelia-dependency-injection', './array-collection-strategy', './map-collection-strategy', './number-strategy'], function (_export) { + 'use strict'; + + var Container, inject, ArrayCollectionStrategy, MapCollectionStrategy, NumberStrategy, CollectionStrategyLocator; + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + return { + setters: [function (_aureliaDependencyInjection) { + Container = _aureliaDependencyInjection.Container; + inject = _aureliaDependencyInjection.inject; + }, function (_arrayCollectionStrategy) { + ArrayCollectionStrategy = _arrayCollectionStrategy.ArrayCollectionStrategy; + }, function (_mapCollectionStrategy) { + MapCollectionStrategy = _mapCollectionStrategy.MapCollectionStrategy; + }, function (_numberStrategy) { + NumberStrategy = _numberStrategy.NumberStrategy; + }], + execute: function () { + CollectionStrategyLocator = (function () { + function CollectionStrategyLocator(container) { + _classCallCheck(this, _CollectionStrategyLocator); + + this.container = container; + this.strategies = []; + this.matchers = []; + + this.addStrategy(ArrayCollectionStrategy, function (items) { + return items instanceof Array; + }); + this.addStrategy(MapCollectionStrategy, function (items) { + return items instanceof Map; + }); + this.addStrategy(NumberStrategy, function (items) { + return typeof items === 'number'; + }); + } + + CollectionStrategyLocator.prototype.addStrategy = function addStrategy(collectionStrategy, matcher) { + this.strategies.push(collectionStrategy); + this.matchers.push(matcher); + }; + + CollectionStrategyLocator.prototype.getStrategy = function getStrategy(items) { + var matchers = this.matchers; + + for (var i = 0, ii = matchers.length; i < ii; ++i) { + if (matchers[i](items)) { + return this.container.get(this.strategies[i]); + } + } + + throw new Error('Object in "repeat" must have a valid collection strategy.'); + }; + + var _CollectionStrategyLocator = CollectionStrategyLocator; + CollectionStrategyLocator = inject(Container)(CollectionStrategyLocator) || CollectionStrategyLocator; + return CollectionStrategyLocator; + })(); + + _export('CollectionStrategyLocator', CollectionStrategyLocator); + } + }; +}); \ No newline at end of file diff --git a/dist/system/collection-strategy.js b/dist/system/collection-strategy.js new file mode 100644 index 0000000..fe9e77f --- /dev/null +++ b/dist/system/collection-strategy.js @@ -0,0 +1,101 @@ +System.register(['aurelia-dependency-injection', 'aurelia-binding'], function (_export) { + 'use strict'; + + var inject, transient, ObserverLocator, createOverrideContext, CollectionStrategy; + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + return { + setters: [function (_aureliaDependencyInjection) { + inject = _aureliaDependencyInjection.inject; + transient = _aureliaDependencyInjection.transient; + }, function (_aureliaBinding) { + ObserverLocator = _aureliaBinding.ObserverLocator; + createOverrideContext = _aureliaBinding.createOverrideContext; + }], + execute: function () { + CollectionStrategy = (function () { + function CollectionStrategy(observerLocator) { + _classCallCheck(this, _CollectionStrategy); + + this.observerLocator = observerLocator; + } + + CollectionStrategy.prototype.initialize = function initialize(repeat, bindingContext, overrideContext) { + this.viewFactory = repeat.viewFactory; + this.viewSlot = repeat.viewSlot; + this.items = repeat.items; + this.local = repeat.local; + this.key = repeat.key; + this.value = repeat.value; + this.bindingContext = bindingContext; + this.overrideContext = overrideContext; + }; + + CollectionStrategy.prototype.dispose = function dispose() { + this.viewFactory = null; + this.viewSlot = null; + this.items = null; + this.local = null; + this.key = null; + this.value = null; + this.bindingContext = null; + this.overrideContext = null; + }; + + CollectionStrategy.prototype.updateOverrideContexts = function updateOverrideContexts(startIndex) { + var children = this.viewSlot.children; + var length = children.length; + + if (startIndex > 0) { + startIndex = startIndex - 1; + } + + for (; startIndex < length; ++startIndex) { + this.updateOverrideContext(children[startIndex].overrideContext, startIndex, length); + } + }; + + CollectionStrategy.prototype.createFullOverrideContext = function createFullOverrideContext(data, index, length, key) { + var context = this.createBaseOverrideContext(data, key); + return this.updateOverrideContext(context, index, length); + }; + + CollectionStrategy.prototype.createBaseOverrideContext = function createBaseOverrideContext(data, key) { + var context = createOverrideContext(undefined, this.overrideContext); + + if (typeof key !== 'undefined') { + context[this.key] = key; + context[this.value] = data; + } else { + context[this.local] = data; + } + + return context; + }; + + CollectionStrategy.prototype.updateOverrideContext = function updateOverrideContext(context, index, length) { + var first = index === 0; + var last = index === length - 1; + var even = index % 2 === 0; + + context.$index = index; + context.$first = first; + context.$last = last; + context.$middle = !(first || last); + context.$odd = !even; + context.$even = even; + + return context; + }; + + var _CollectionStrategy = CollectionStrategy; + CollectionStrategy = transient()(CollectionStrategy) || CollectionStrategy; + CollectionStrategy = inject(ObserverLocator)(CollectionStrategy) || CollectionStrategy; + return CollectionStrategy; + })(); + + _export('CollectionStrategy', CollectionStrategy); + } + }; +}); \ No newline at end of file diff --git a/dist/system/compose.js b/dist/system/compose.js index 36b6930..c09fc01 100644 --- a/dist/system/compose.js +++ b/dist/system/compose.js @@ -15,16 +15,16 @@ System.register(['aurelia-dependency-injection', 'aurelia-task-queue', 'aurelia- container: composer.container, viewSlot: composer.viewSlot, viewResources: composer.viewResources, - currentBehavior: composer.currentBehavior, + currentController: composer.currentController, host: composer.element }); } function processInstruction(composer, instruction) { composer.currentInstruction = null; - composer.compositionEngine.compose(instruction).then(function (next) { - composer.currentBehavior = next; - composer.currentViewModel = next ? next.bindingContext : null; + composer.compositionEngine.compose(instruction).then(function (controller) { + composer.currentController = controller; + composer.currentViewModel = controller ? controller.viewModel : null; }); } return { @@ -79,6 +79,8 @@ System.register(['aurelia-dependency-injection', 'aurelia-task-queue', 'aurelia- this.viewSlot = viewSlot; this.viewResources = viewResources; this.taskQueue = taskQueue; + this.currentController = null; + this.currentViewModel = null; } Compose.prototype.bind = function bind(bindingContext) { diff --git a/dist/system/debounce-binding-behavior.js b/dist/system/debounce-binding-behavior.js new file mode 100644 index 0000000..20ed754 --- /dev/null +++ b/dist/system/debounce-binding-behavior.js @@ -0,0 +1,68 @@ +System.register(['aurelia-binding'], function (_export) { + 'use strict'; + + var bindingMode, DebounceBindingBehavior; + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + function debounce(newValue) { + var _this = this; + + var state = this.debounceState; + if (state.immediate) { + state.immediate = false; + this.debouncedMethod(newValue); + return; + } + clearTimeout(state.timeoutId); + state.timeoutId = setTimeout(function () { + return _this.debouncedMethod(newValue); + }, state.delay); + } + + return { + setters: [function (_aureliaBinding) { + bindingMode = _aureliaBinding.bindingMode; + }], + execute: function () { + DebounceBindingBehavior = (function () { + function DebounceBindingBehavior() { + _classCallCheck(this, DebounceBindingBehavior); + } + + DebounceBindingBehavior.prototype.bind = function bind(binding, source) { + var delay = arguments.length <= 2 || arguments[2] === undefined ? 200 : arguments[2]; + + var methodToDebounce = 'updateTarget'; + if (binding.callSource) { + methodToDebounce = 'callSource'; + } else if (binding.updateSource && binding.mode === bindingMode.twoWay) { + methodToDebounce = 'updateSource'; + } + + binding.debouncedMethod = binding[methodToDebounce]; + binding.debouncedMethod.originalName = methodToDebounce; + + binding[methodToDebounce] = debounce; + + binding.debounceState = { + delay: delay, + timeoutId: null, + immediate: methodToDebounce === 'updateTarget' }; + }; + + DebounceBindingBehavior.prototype.unbind = function unbind(binding, source) { + var methodToRestore = binding.debouncedMethod.originalName; + binding[methodToRestore] = binding.debouncedMethod; + binding.debouncedMethod = null; + clearTimeout(binding.debounceState.timeoutId); + binding.debounceState = null; + }; + + return DebounceBindingBehavior; + })(); + + _export('DebounceBindingBehavior', DebounceBindingBehavior); + } + }; +}); \ No newline at end of file diff --git a/dist/system/global-behavior.js b/dist/system/global-behavior.js deleted file mode 100644 index cc5b8f2..0000000 --- a/dist/system/global-behavior.js +++ /dev/null @@ -1,118 +0,0 @@ -System.register(['aurelia-dependency-injection', 'aurelia-templating', 'aurelia-logging', 'aurelia-pal'], function (_export) { - 'use strict'; - - var inject, customAttribute, dynamicOptions, LogManager, PLATFORM, DOM, AggregateError, GlobalBehavior; - - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - - return { - setters: [function (_aureliaDependencyInjection) { - inject = _aureliaDependencyInjection.inject; - }, function (_aureliaTemplating) { - customAttribute = _aureliaTemplating.customAttribute; - dynamicOptions = _aureliaTemplating.dynamicOptions; - }, function (_aureliaLogging) { - LogManager = _aureliaLogging; - }, function (_aureliaPal) { - PLATFORM = _aureliaPal.PLATFORM; - DOM = _aureliaPal.DOM; - AggregateError = _aureliaPal.AggregateError; - }], - execute: function () { - GlobalBehavior = (function () { - function GlobalBehavior(element) { - _classCallCheck(this, _GlobalBehavior); - - this.element = element; - LogManager.getLogger('templating-resources').warn('The "GlobalBehavior" behavior will be removed in the next release.'); - } - - GlobalBehavior.prototype.bind = function bind() { - var handler = GlobalBehavior.handlers[this.aureliaAttrName]; - - if (!handler) { - throw new Error('Binding handler not found for \'' + this.aureliaAttrName + '.' + this.aureliaCommand + '\'. Element:\n' + this.element.outerHTML + '\n'); - } - - try { - this.handler = handler.bind(this, this.element, this.aureliaCommand) || handler; - } catch (error) { - throw new AggregateError('Conventional binding handler failed.', error); - } - }; - - GlobalBehavior.prototype.attached = function attached() { - if (this.handler && 'attached' in this.handler) { - this.handler.attached(this, this.element); - } - }; - - GlobalBehavior.prototype.detached = function detached() { - if (this.handler && 'detached' in this.handler) { - this.handler.detached(this, this.element); - } - }; - - GlobalBehavior.prototype.unbind = function unbind() { - if (this.handler && 'unbind' in this.handler) { - this.handler.unbind(this, this.element); - } - - this.handler = null; - }; - - var _GlobalBehavior = GlobalBehavior; - GlobalBehavior = inject(DOM.Element)(GlobalBehavior) || GlobalBehavior; - GlobalBehavior = dynamicOptions(GlobalBehavior) || GlobalBehavior; - GlobalBehavior = customAttribute('global-behavior')(GlobalBehavior) || GlobalBehavior; - return GlobalBehavior; - })(); - - _export('GlobalBehavior', GlobalBehavior); - - GlobalBehavior.createSettingsFromBehavior = function (behavior) { - var settings = {}; - - for (var key in behavior) { - if (key === 'aureliaAttrName' || key === 'aureliaCommand' || !behavior.hasOwnProperty(key)) { - continue; - } - - settings[key] = behavior[key]; - } - - return settings; - }; - - GlobalBehavior.jQueryPlugins = {}; - - GlobalBehavior.handlers = { - jquery: { - bind: function bind(behavior, element, command) { - var settings = GlobalBehavior.createSettingsFromBehavior(behavior); - var pluginName = GlobalBehavior.jQueryPlugins[command] || command; - var jqueryElement = PLATFORM.global.jQuery(element); - - if (!jqueryElement[pluginName]) { - LogManager.getLogger('templating-resources').warn('Could not find the jQuery plugin ' + pluginName + ', possibly due to case mismatch. Trying to enumerate jQuery methods in lowercase. Add the correctly cased plugin name to the GlobalBehavior to avoid this performance hit.'); - - for (var prop in jqueryElement) { - if (prop.toLowerCase() === pluginName) { - pluginName = prop; - } - } - } - - behavior.plugin = jqueryElement[pluginName](settings); - }, - unbind: function unbind(behavior, element) { - if (typeof behavior.plugin.destroy === 'function') { - behavior.plugin.destroy(); - behavior.plugin = null; - } - } - } - }; - } - }; -}); \ No newline at end of file diff --git a/dist/system/if.js b/dist/system/if.js index f42a8a9..f6441d0 100644 --- a/dist/system/if.js +++ b/dist/system/if.js @@ -26,11 +26,13 @@ System.register(['aurelia-templating', 'aurelia-dependency-injection', 'aurelia- this.showing = false; this.taskQueue = taskQueue; this.view = null; - this.$parent = null; + this.bindingContext = null; + this.overrideContext = null; } - If.prototype.bind = function bind(bindingContext) { - this.$parent = bindingContext; + If.prototype.bind = function bind(bindingContext, overrideContext) { + this.bindingContext = bindingContext; + this.overrideContext = overrideContext; this.valueChanged(this.value); }; @@ -56,14 +58,14 @@ System.register(['aurelia-templating', 'aurelia-dependency-injection', 'aurelia- } if (this.view === null) { - this.view = this.viewFactory.create(this.$parent); + this.view = this.viewFactory.create(); } if (!this.showing) { this.showing = true; if (!this.view.isBound) { - this.view.bind(this.$parent); + this.view.bind(this.bindingContext, this.overrideContext); } this.viewSlot.add(this.view); diff --git a/dist/system/map-collection-strategy.js b/dist/system/map-collection-strategy.js new file mode 100644 index 0000000..a88dee9 --- /dev/null +++ b/dist/system/map-collection-strategy.js @@ -0,0 +1,128 @@ +System.register(['./collection-strategy'], function (_export) { + 'use strict'; + + var CollectionStrategy, MapCollectionStrategy; + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + return { + setters: [function (_collectionStrategy) { + CollectionStrategy = _collectionStrategy.CollectionStrategy; + }], + execute: function () { + MapCollectionStrategy = (function (_CollectionStrategy) { + _inherits(MapCollectionStrategy, _CollectionStrategy); + + function MapCollectionStrategy() { + _classCallCheck(this, MapCollectionStrategy); + + _CollectionStrategy.apply(this, arguments); + } + + MapCollectionStrategy.prototype.getCollectionObserver = function getCollectionObserver(items) { + return this.observerLocator.getMapObserver(items); + }; + + MapCollectionStrategy.prototype.processItems = function processItems(items) { + var _this = this; + + var viewFactory = this.viewFactory; + var viewSlot = this.viewSlot; + var index = 0; + var overrideContext = undefined; + var view = undefined; + + items.forEach(function (value, key) { + overrideContext = _this.createFullOverrideContext(value, index, items.size, key); + view = viewFactory.create(); + view.bind(undefined, overrideContext); + viewSlot.add(view); + ++index; + }); + }; + + MapCollectionStrategy.prototype.handleChanges = function handleChanges(map, records) { + var _this2 = this; + + var viewSlot = this.viewSlot; + var key = undefined; + var i = undefined; + var ii = undefined; + var view = undefined; + var overrideContext = undefined; + var removeIndex = undefined; + var record = undefined; + var rmPromises = []; + var viewOrPromise = undefined; + + for (i = 0, ii = records.length; i < ii; ++i) { + record = records[i]; + key = record.key; + switch (record.type) { + case 'update': + removeIndex = this._getViewIndexByKey(key); + viewOrPromise = viewSlot.removeAt(removeIndex, true); + if (viewOrPromise instanceof Promise) { + rmPromises.push(viewOrPromise); + } + overrideContext = this.createFullOverrideContext(map.get(key), removeIndex, map.size, key); + view = this.viewFactory.create(); + view.bind(undefined, overrideContext); + viewSlot.insert(removeIndex, view); + break; + case 'add': + overrideContext = this.createFullOverrideContext(map.get(key), map.size - 1, map.size, key); + view = this.viewFactory.create(); + view.bind(undefined, overrideContext); + viewSlot.insert(map.size - 1, view); + break; + case 'delete': + if (record.oldValue === undefined) { + return; + } + removeIndex = this._getViewIndexByKey(key); + viewOrPromise = viewSlot.removeAt(removeIndex, true); + if (viewOrPromise instanceof Promise) { + rmPromises.push(viewOrPromise); + } + break; + case 'clear': + viewSlot.removeAll(true); + break; + default: + continue; + } + } + + if (rmPromises.length > 0) { + Promise.all(rmPromises).then(function () { + _this2.updateOverrideContexts(0); + }); + } else { + this.updateOverrideContexts(0); + } + }; + + MapCollectionStrategy.prototype._getViewIndexByKey = function _getViewIndexByKey(key) { + var viewSlot = this.viewSlot; + var i = undefined; + var ii = undefined; + var child = undefined; + + for (i = 0, ii = viewSlot.children.length; i < ii; ++i) { + child = viewSlot.children[i]; + if (child.overrideContext[this.key] === key) { + return i; + } + } + }; + + return MapCollectionStrategy; + })(CollectionStrategy); + + _export('MapCollectionStrategy', MapCollectionStrategy); + } + }; +}); \ No newline at end of file diff --git a/dist/system/number-strategy.js b/dist/system/number-strategy.js new file mode 100644 index 0000000..def9df9 --- /dev/null +++ b/dist/system/number-strategy.js @@ -0,0 +1,69 @@ +System.register(['./collection-strategy'], function (_export) { + 'use strict'; + + var CollectionStrategy, NumberStrategy; + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + return { + setters: [function (_collectionStrategy) { + CollectionStrategy = _collectionStrategy.CollectionStrategy; + }], + execute: function () { + NumberStrategy = (function (_CollectionStrategy) { + _inherits(NumberStrategy, _CollectionStrategy); + + function NumberStrategy() { + _classCallCheck(this, NumberStrategy); + + _CollectionStrategy.apply(this, arguments); + } + + NumberStrategy.prototype.getCollectionObserver = function getCollectionObserver() { + return; + }; + + NumberStrategy.prototype.processItems = function processItems(value) { + var viewFactory = this.viewFactory; + var viewSlot = this.viewSlot; + var childrenLength = viewSlot.children.length; + var i = undefined; + var ii = undefined; + var overrideContext = undefined; + var view = undefined; + var viewsToRemove = undefined; + + value = Math.floor(value); + viewsToRemove = childrenLength - value; + + if (viewsToRemove > 0) { + if (viewsToRemove > childrenLength) { + viewsToRemove = childrenLength; + } + + for (i = 0, ii = viewsToRemove; i < ii; ++i) { + viewSlot.removeAt(childrenLength - (i + 1), true); + } + + return; + } + + for (i = childrenLength, ii = value; i < ii; ++i) { + overrideContext = this.createFullOverrideContext(i, i, ii); + view = viewFactory.create(); + view.bind(undefined, overrideContext); + viewSlot.add(view); + } + + this.updateOverrideContexts(0); + }; + + return NumberStrategy; + })(CollectionStrategy); + + _export('NumberStrategy', NumberStrategy); + } + }; +}); \ No newline at end of file diff --git a/dist/system/repeat.js b/dist/system/repeat.js index 4076113..50c3619 100644 --- a/dist/system/repeat.js +++ b/dist/system/repeat.js @@ -1,7 +1,7 @@ -System.register(['aurelia-dependency-injection', 'aurelia-binding', 'aurelia-templating'], function (_export) { +System.register(['aurelia-dependency-injection', 'aurelia-binding', 'aurelia-templating', './collection-strategy-locator'], function (_export) { 'use strict'; - var inject, ObserverLocator, calcSplices, getChangeRecords, BoundViewFactory, ViewSlot, customAttribute, bindable, templateController, Repeat; + var inject, ObserverLocator, getChangeRecords, BindingBehavior, ValueConverter, BoundViewFactory, TargetInstruction, ViewSlot, ViewResources, customAttribute, bindable, templateController, CollectionStrategyLocator, Repeat; var _createDecoratedClass = (function () { function defineProperties(target, descriptors, initializers) { for (var i = 0; i < descriptors.length; i++) { var descriptor = descriptors[i]; var decorators = descriptor.decorators; var key = descriptor.key; delete descriptor.key; delete descriptor.decorators; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor || descriptor.initializer) descriptor.writable = true; if (decorators) { for (var f = 0; f < decorators.length; f++) { var decorator = decorators[f]; if (typeof decorator === 'function') { descriptor = decorator(target, key, descriptor) || descriptor; } else { throw new TypeError('The decorator for method ' + descriptor.key + ' is of the invalid type ' + typeof decorator); } } if (descriptor.initializer !== undefined) { initializers[key] = descriptor; continue; } } Object.defineProperty(target, key, descriptor); } } return function (Constructor, protoProps, staticProps, protoInitializers, staticInitializers) { if (protoProps) defineProperties(Constructor.prototype, protoProps, protoInitializers); if (staticProps) defineProperties(Constructor, staticProps, staticInitializers); return Constructor; }; })(); @@ -9,19 +9,41 @@ System.register(['aurelia-dependency-injection', 'aurelia-binding', 'aurelia-tem function _defineDecoratedPropertyDescriptor(target, key, descriptors) { var _descriptor = descriptors[key]; if (!_descriptor) return; var descriptor = {}; for (var _key in _descriptor) descriptor[_key] = _descriptor[_key]; descriptor.value = descriptor.initializer ? descriptor.initializer.call(target) : undefined; Object.defineProperty(target, key, descriptor); } + function getSourceExpression(instruction, attrName) { + return instruction.behaviorInstructions.filter(function (bi) { + return bi.originalAttrName === attrName; + })[0].attributes.items.sourceExpression; + } + + function unwrapExpression(expression) { + var unwrapped = false; + while (expression instanceof BindingBehavior) { + expression = expression.expression; + } + while (expression instanceof ValueConverter) { + expression = expression.expression; + unwrapped = true; + } + return unwrapped ? expression : null; + } return { setters: [function (_aureliaDependencyInjection) { inject = _aureliaDependencyInjection.inject; }, function (_aureliaBinding) { ObserverLocator = _aureliaBinding.ObserverLocator; - calcSplices = _aureliaBinding.calcSplices; getChangeRecords = _aureliaBinding.getChangeRecords; + BindingBehavior = _aureliaBinding.BindingBehavior; + ValueConverter = _aureliaBinding.ValueConverter; }, function (_aureliaTemplating) { BoundViewFactory = _aureliaTemplating.BoundViewFactory; + TargetInstruction = _aureliaTemplating.TargetInstruction; ViewSlot = _aureliaTemplating.ViewSlot; + ViewResources = _aureliaTemplating.ViewResources; customAttribute = _aureliaTemplating.customAttribute; bindable = _aureliaTemplating.bindable; templateController = _aureliaTemplating.templateController; + }, function (_collectionStrategyLocator) { + CollectionStrategyLocator = _collectionStrategyLocator.CollectionStrategyLocator; }], execute: function () { Repeat = (function () { @@ -49,7 +71,7 @@ System.register(['aurelia-dependency-injection', 'aurelia-binding', 'aurelia-tem enumerable: true }], null, _instanceInitializers); - function Repeat(viewFactory, viewSlot, observerLocator) { + function Repeat(viewFactory, instruction, viewSlot, viewResources, observerLocator, collectionStrategyLocator) { _classCallCheck(this, _Repeat); _defineDecoratedPropertyDescriptor(this, 'items', _instanceInitializers); @@ -61,66 +83,40 @@ System.register(['aurelia-dependency-injection', 'aurelia-binding', 'aurelia-tem _defineDecoratedPropertyDescriptor(this, 'value', _instanceInitializers); this.viewFactory = viewFactory; + this.instruction = instruction; this.viewSlot = viewSlot; + this.lookupFunctions = viewResources.lookupFunctions; this.observerLocator = observerLocator; this.local = 'item'; this.key = 'key'; this.value = 'value'; + this.collectionStrategyLocator = collectionStrategyLocator; } Repeat.prototype.call = function call(context, changes) { this[context](this.items, changes); }; - Repeat.prototype.bind = function bind(bindingContext) { + Repeat.prototype.bind = function bind(bindingContext, overrideContext) { var items = this.items; - var viewSlot = this.viewSlot; - var observer = undefined; - - this.bindingContext = bindingContext; - - if (!items) { - if (this.oldItems) { - viewSlot.removeAll(true); - } - + if (items === undefined || items === null) { return; } - if (this.oldItems === items) { - if (items instanceof Map) { - var records = getChangeRecords(items); - this.collectionObserver = this.observerLocator.getMapObserver(items); - - this.handleMapChangeRecords(items, records); - - this.callContext = 'handleMapChangeRecords'; - this.collectionObserver.subscribe(this.callContext, this); - } else if (items instanceof Array) { - var splices = calcSplices(items, 0, items.length, this.lastBoundItems, 0, this.lastBoundItems.length); - this.collectionObserver = this.observerLocator.getArrayObserver(items); - - this.handleSplices(items, splices); - this.lastBoundItems = this.oldItems = null; - - this.callContext = 'handleSplices'; - this.collectionObserver.subscribe(this.callContext, this); - } - return; - } else if (this.oldItems) { - viewSlot.removeAll(true); - } - + this.sourceExpression = getSourceExpression(this.instruction, 'repeat.for'); + this.scope = { bindingContext: bindingContext, overrideContext: overrideContext }; + this.collectionStrategy = this.collectionStrategyLocator.getStrategy(this.items); + this.collectionStrategy.initialize(this, bindingContext, overrideContext); this.processItems(); }; Repeat.prototype.unbind = function unbind() { - this.oldItems = this.items; - - if (this.items instanceof Array) { - this.lastBoundItems = this.items.slice(0); - } - + this.sourceExpression = null; + this.scope = null; + this.collectionStrategy.dispose(); + this.items = null; + this.collectionStrategy = null; + this.viewSlot.removeAll(true); this.unsubscribeCollection(); }; @@ -153,276 +149,66 @@ System.register(['aurelia-dependency-injection', 'aurelia-binding', 'aurelia-tem if (rmPromise instanceof Promise) { rmPromise.then(function () { - _this.processItemsByType(); + _this.processItemsByStrategy(); }); } else { - this.processItemsByType(); + this.processItemsByStrategy(); } }; - Repeat.prototype.processItemsByType = function processItemsByType() { - var items = this.items; - if (items instanceof Array) { - this.processArrayItems(items); - } else if (items instanceof Map) { - this.processMapEntries(items); - } else if (typeof items === 'number') { - this.processNumber(items); - } else { - throw new Error('Object in "repeat" must be of type Array, Map or Number'); + Repeat.prototype.getInnerCollection = function getInnerCollection() { + var expression = unwrapExpression(this.sourceExpression); + if (!expression) { + return null; } + return expression.evaluate(this.scope, null); }; - Repeat.prototype.processArrayItems = function processArrayItems(items) { - var viewFactory = this.viewFactory; - var viewSlot = this.viewSlot; - var i = undefined; - var ii = undefined; - var row = undefined; - var view = undefined; - var observer = undefined; - - this.collectionObserver = this.observerLocator.getArrayObserver(items); - - for (i = 0, ii = items.length; i < ii; ++i) { - row = this.createFullBindingContext(items[i], i, ii); - view = viewFactory.create(row); - viewSlot.add(view); + Repeat.prototype.observeInnerCollection = function observeInnerCollection() { + var 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 = 'handleSplices'; - this.collectionObserver.subscribe(this.callContext, this); - }; - - Repeat.prototype.processMapEntries = function processMapEntries(items) { - var _this2 = this; - - var viewFactory = this.viewFactory; - var viewSlot = this.viewSlot; - var index = 0; - var row = undefined; - var view = undefined; - var observer = undefined; - - this.collectionObserver = this.observerLocator.getMapObserver(items); - - items.forEach(function (value, key) { - row = _this2.createFullExecutionKvpContext(key, value, index, items.size); - view = viewFactory.create(row); - viewSlot.add(view); - ++index; - }); - - this.callContext = 'handleMapChangeRecords'; + this.callContext = 'handleInnerCollectionChanges'; this.collectionObserver.subscribe(this.callContext, this); + return true; }; - Repeat.prototype.processNumber = function processNumber(value) { - var viewFactory = this.viewFactory; - var viewSlot = this.viewSlot; - var childrenLength = viewSlot.children.length; - var i = undefined; - var ii = undefined; - var row = undefined; - var view = undefined; - var viewsToRemove = undefined; - - value = Math.floor(value); - viewsToRemove = childrenLength - value; - - if (viewsToRemove > 0) { - if (viewsToRemove > childrenLength) { - viewsToRemove = childrenLength; - } - - for (i = 0, ii = viewsToRemove; i < ii; ++i) { - viewSlot.removeAt(childrenLength - (i + 1), true); - } - - return; - } - - for (i = childrenLength, ii = value; i < ii; ++i) { - row = this.createFullBindingContext(i, i, ii); - view = viewFactory.create(row); - viewSlot.add(view); - } - }; - - Repeat.prototype.createBaseBindingContext = function createBaseBindingContext(data) { - var context = {}; - context[this.local] = data; - context.$parent = this.bindingContext; - return context; - }; - - Repeat.prototype.createBaseExecutionKvpContext = function createBaseExecutionKvpContext(key, value) { - var context = {}; - context[this.key] = key; - context[this.value] = value; - context.$parent = this.bindingContext; - return context; - }; - - Repeat.prototype.createFullBindingContext = function createFullBindingContext(data, index, length) { - var context = this.createBaseBindingContext(data); - return this.updateBindingContext(context, index, length); - }; - - Repeat.prototype.createFullExecutionKvpContext = function createFullExecutionKvpContext(key, value, index, length) { - var context = this.createBaseExecutionKvpContext(key, value); - return this.updateBindingContext(context, index, length); - }; - - Repeat.prototype.updateBindingContext = function updateBindingContext(context, index, length) { - var first = index === 0; - var last = index === length - 1; - var even = index % 2 === 0; - - context.$index = index; - context.$first = first; - context.$last = last; - context.$middle = !(first || last); - context.$odd = !even; - context.$even = even; - - return context; - }; - - Repeat.prototype.updateBindingContexts = function updateBindingContexts(startIndex) { - var children = this.viewSlot.children; - var length = children.length; - - if (startIndex > 0) { - startIndex = startIndex - 1; - } - - for (; startIndex < length; ++startIndex) { - this.updateBindingContext(children[startIndex].bindingContext, startIndex, length); - } - }; - - Repeat.prototype.handleSplices = function handleSplices(array, splices) { - var _this3 = this; - - var removeDelta = 0; - var viewSlot = this.viewSlot; - var rmPromises = []; - - for (var i = 0, ii = splices.length; i < ii; ++i) { - var splice = splices[i]; - var removed = splice.removed; - - for (var j = 0, jj = removed.length; j < jj; ++j) { - var viewOrPromise = viewSlot.removeAt(splice.index + removeDelta + rmPromises.length, true); - if (viewOrPromise instanceof Promise) { - rmPromises.push(viewOrPromise); - } - } - removeDelta -= splice.addedCount; - } - - if (rmPromises.length > 0) { - Promise.all(rmPromises).then(function () { - var spliceIndexLow = _this3.handleAddedSplices(array, splices); - _this3.updateBindingContexts(spliceIndexLow); - }); - } else { - var spliceIndexLow = this.handleAddedSplices(array, splices); - this.updateBindingContexts(spliceIndexLow); + Repeat.prototype.observeCollection = function observeCollection() { + var items = this.items; + this.collectionObserver = this.collectionStrategy.getCollectionObserver(items); + if (this.collectionObserver) { + this.callContext = 'handleCollectionChanges'; + this.collectionObserver.subscribe(this.callContext, this); } }; - Repeat.prototype.handleAddedSplices = function handleAddedSplices(array, splices) { - var spliceIndex = undefined; - var spliceIndexLow = undefined; - var arrayLength = array.length; - for (var i = 0, ii = splices.length; i < ii; ++i) { - var splice = splices[i]; - var addIndex = spliceIndex = splice.index; - var end = splice.index + splice.addedCount; - - if (typeof spliceIndexLow === 'undefined' || spliceIndexLow === null || spliceIndexLow > splice.index) { - spliceIndexLow = spliceIndex; - } - - for (; addIndex < end; ++addIndex) { - var row = this.createFullBindingContext(array[addIndex], addIndex, arrayLength); - var view = this.viewFactory.create(row); - this.viewSlot.insert(addIndex, view); - } + Repeat.prototype.processItemsByStrategy = function processItemsByStrategy() { + if (!this.observeInnerCollection()) { + this.observeCollection(); } - - return spliceIndexLow; + this.collectionStrategy.processItems(this.items); }; - Repeat.prototype.handleMapChangeRecords = function handleMapChangeRecords(map, records) { - var viewSlot = this.viewSlot; - var key = undefined; - var i = undefined; - var ii = undefined; - var view = undefined; - var children = undefined; - var length = undefined; - var row = undefined; - var removeIndex = undefined; - var record = undefined; - - for (i = 0, ii = records.length; i < ii; ++i) { - record = records[i]; - key = record.key; - switch (record.type) { - case 'update': - removeIndex = this.getViewIndexByKey(key); - viewSlot.removeAt(removeIndex, true); - row = this.createBaseExecutionKvpContext(key, map.get(key)); - view = this.viewFactory.create(row); - viewSlot.insert(removeIndex, view); - break; - case 'add': - row = this.createBaseExecutionKvpContext(key, map.get(key)); - view = this.viewFactory.create(row); - viewSlot.insert(map.size, view); - break; - case 'delete': - if (!record.oldValue) { - return; - } - removeIndex = this.getViewIndexByKey(key); - viewSlot.removeAt(removeIndex, true); - break; - case 'clear': - viewSlot.removeAll(true); - break; - default: - continue; - } - } - - children = viewSlot.children; - length = children.length; - - for (i = 0; i < length; i++) { - this.updateBindingContext(children[i].bindingContext, i, length); - } + Repeat.prototype.handleCollectionChanges = function handleCollectionChanges(collection, changes) { + this.collectionStrategy.handleChanges(collection, changes); }; - Repeat.prototype.getViewIndexByKey = function getViewIndexByKey(key) { - var viewSlot = this.viewSlot; - var i = undefined; - var ii = undefined; - var child = undefined; - - for (i = 0, ii = viewSlot.children.length; i < ii; ++i) { - child = viewSlot.children[i]; - if (child.bindings[0].source[this.key] === key) { - return i; - } + Repeat.prototype.handleInnerCollectionChanges = function handleInnerCollectionChanges(collection, changes) { + var newItems = this.sourceExpression.evaluate(this.scope, this.lookupFunctions); + if (newItems === this.items) { + return; } + this.items = newItems; + this.itemsChanged(); }; var _Repeat = Repeat; - Repeat = inject(BoundViewFactory, ViewSlot, ObserverLocator)(Repeat) || Repeat; + Repeat = inject(BoundViewFactory, TargetInstruction, ViewSlot, ViewResources, ObserverLocator, CollectionStrategyLocator)(Repeat) || Repeat; Repeat = templateController(Repeat) || Repeat; Repeat = customAttribute('repeat')(Repeat) || Repeat; return Repeat; diff --git a/dist/system/replaceable.js b/dist/system/replaceable.js index 971dd6e..21a098d 100644 --- a/dist/system/replaceable.js +++ b/dist/system/replaceable.js @@ -21,14 +21,20 @@ System.register(['aurelia-dependency-injection', 'aurelia-templating'], function this.viewFactory = viewFactory; this.viewSlot = viewSlot; - this.needsReplacement = true; + this.view = null; } - Replaceable.prototype.bind = function bind() { - if (this.needsReplacement) { - this.needsReplacement = false; - this.viewSlot.add(this.viewFactory.create()); + Replaceable.prototype.bind = function bind(bindingContext, overrideContext) { + if (this.view === null) { + this.view = this.viewFactory.create(); + this.viewSlot.add(this.view); } + + this.view.bind(bindingContext, overrideContext); + }; + + Replaceable.prototype.unbind = function unbind() { + this.view.unbind(); }; var _Replaceable = Replaceable; diff --git a/dist/system/signal-binding-behavior.js b/dist/system/signal-binding-behavior.js new file mode 100644 index 0000000..8e25a70 --- /dev/null +++ b/dist/system/signal-binding-behavior.js @@ -0,0 +1,51 @@ +System.register(['aurelia-binding', './binding-signaler'], function (_export) { + 'use strict'; + + var bindingMode, BindingSignaler, SignalBindingBehavior; + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + return { + setters: [function (_aureliaBinding) { + bindingMode = _aureliaBinding.bindingMode; + }, function (_bindingSignaler) { + BindingSignaler = _bindingSignaler.BindingSignaler; + }], + execute: function () { + SignalBindingBehavior = (function () { + SignalBindingBehavior.inject = function inject() { + return [BindingSignaler]; + }; + + function SignalBindingBehavior(bindingSignaler) { + _classCallCheck(this, SignalBindingBehavior); + + this.signals = bindingSignaler.signals; + } + + SignalBindingBehavior.prototype.bind = function bind(binding, source, name) { + if (!binding.updateTarget) { + throw new Error('Only property bindings and string interpolation bindings can be signaled. Trigger, delegate and call bindings cannot be signaled.'); + } + if (binding.mode === bindingMode.oneTime) { + throw new Error('One-time bindings cannot be signaled.'); + } + var bindings = this.signals[name] || (this.signals[name] = []); + bindings.push(binding); + binding.signalName = name; + }; + + SignalBindingBehavior.prototype.unbind = function unbind(binding, source) { + var name = binding.signalName; + binding.signalName = null; + var bindings = signals[name]; + bindings.splice(bindings.indexOf(binding), 1); + }; + + return SignalBindingBehavior; + })(); + + _export('SignalBindingBehavior', SignalBindingBehavior); + } + }; +}); \ No newline at end of file diff --git a/dist/system/throttle-binding-behavior.js b/dist/system/throttle-binding-behavior.js new file mode 100644 index 0000000..2d6e738 --- /dev/null +++ b/dist/system/throttle-binding-behavior.js @@ -0,0 +1,76 @@ +System.register(['aurelia-binding'], function (_export) { + 'use strict'; + + var bindingMode, ThrottleBindingBehavior; + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + function throttle(newValue) { + var _this = this; + + var state = this.throttleState; + var elapsed = +new Date() - state.last; + if (elapsed >= state.delay) { + clearTimeout(state.timeoutId); + state.timeoutId = null; + state.last = +new Date(); + this.throttledMethod(newValue); + return; + } + state.newValue = newValue; + if (state.timeoutId === null) { + state.timeoutId = setTimeout(function () { + state.timeoutId = null; + state.last = +new Date(); + _this.throttledMethod(state.newValue); + }, state.delay - elapsed); + } + } + + return { + setters: [function (_aureliaBinding) { + bindingMode = _aureliaBinding.bindingMode; + }], + execute: function () { + ThrottleBindingBehavior = (function () { + function ThrottleBindingBehavior() { + _classCallCheck(this, ThrottleBindingBehavior); + } + + ThrottleBindingBehavior.prototype.bind = function bind(binding, source) { + var delay = arguments.length <= 2 || arguments[2] === undefined ? 200 : arguments[2]; + + var methodToThrottle = 'updateTarget'; + if (binding.callSource) { + methodToThrottle = 'callSource'; + } else if (binding.updateSource && binding.mode === bindingMode.twoWay) { + methodToThrottle = 'updateSource'; + } + + binding.throttledMethod = binding[methodToThrottle]; + binding.throttledMethod.originalName = methodToThrottle; + + binding[methodToThrottle] = throttle; + + binding.throttleState = { + delay: delay, + last: 0, + timeoutId: null + }; + }; + + ThrottleBindingBehavior.prototype.unbind = function unbind(binding, source) { + var methodToRestore = binding.throttledMethod.originalName; + binding[methodToRestore] = binding.throttledMethod; + binding.throttledMethod = null; + clearTimeout(binding.throttleState.timeoutId); + binding.throttleState = null; + }; + + return ThrottleBindingBehavior; + })(); + + _export('ThrottleBindingBehavior', ThrottleBindingBehavior); + } + }; +}); \ No newline at end of file diff --git a/dist/system/update-trigger-binding-behavior.js b/dist/system/update-trigger-binding-behavior.js new file mode 100644 index 0000000..76fcb87 --- /dev/null +++ b/dist/system/update-trigger-binding-behavior.js @@ -0,0 +1,61 @@ +System.register(['aurelia-binding'], function (_export) { + 'use strict'; + + var bindingMode, EventManager, eventNamesRequired, notApplicableMessage, UpdateTriggerBindingBehavior; + + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + return { + setters: [function (_aureliaBinding) { + bindingMode = _aureliaBinding.bindingMode; + EventManager = _aureliaBinding.EventManager; + }], + execute: function () { + eventNamesRequired = 'The updateTrigger binding behavior requires at least one event name argument: eg '; + notApplicableMessage = 'The updateTrigger binding behavior can only be applied to two-way bindings on input/select elements.'; + + UpdateTriggerBindingBehavior = (function () { + _createClass(UpdateTriggerBindingBehavior, null, [{ + key: 'inject', + value: [EventManager], + enumerable: true + }]); + + function UpdateTriggerBindingBehavior(eventManager) { + _classCallCheck(this, UpdateTriggerBindingBehavior); + + this.eventManager = eventManager; + } + + UpdateTriggerBindingBehavior.prototype.bind = function bind(binding, source) { + for (var _len = arguments.length, events = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { + events[_key - 2] = arguments[_key]; + } + + if (events.length === 0) { + throw new Error(eventNamesRequired); + } + if (binding.mode !== bindingMode.twoWay || !binding.targetProperty.handler) { + throw new Error(notApplicableMessage); + } + + binding.targetProperty.originalHandler = binding.targetProperty.handler; + + var handler = this.eventManager.createElementHandler(events); + binding.targetProperty.handler = handler; + }; + + UpdateTriggerBindingBehavior.prototype.unbind = function unbind(binding, source) { + binding.targetProperty.handler = binding.targetProperty.originalHandler; + binding.targetProperty.originalHandler = null; + }; + + return UpdateTriggerBindingBehavior; + })(); + + _export('UpdateTriggerBindingBehavior', UpdateTriggerBindingBehavior); + } + }; +}); \ No newline at end of file diff --git a/dist/system/with.js b/dist/system/with.js index 9657552..c309382 100644 --- a/dist/system/with.js +++ b/dist/system/with.js @@ -1,7 +1,7 @@ -System.register(['aurelia-dependency-injection', 'aurelia-templating', 'aurelia-logging'], function (_export) { +System.register(['aurelia-dependency-injection', 'aurelia-templating', 'aurelia-binding'], function (_export) { 'use strict'; - var inject, BoundViewFactory, ViewSlot, customAttribute, templateController, LogManager, With; + var inject, BoundViewFactory, ViewSlot, customAttribute, templateController, createOverrideContext, With; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } @@ -13,8 +13,8 @@ System.register(['aurelia-dependency-injection', 'aurelia-templating', 'aurelia- ViewSlot = _aureliaTemplating.ViewSlot; customAttribute = _aureliaTemplating.customAttribute; templateController = _aureliaTemplating.templateController; - }, function (_aureliaLogging) { - LogManager = _aureliaLogging; + }, function (_aureliaBinding) { + createOverrideContext = _aureliaBinding.createOverrideContext; }], execute: function () { With = (function () { @@ -23,15 +23,31 @@ System.register(['aurelia-dependency-injection', 'aurelia-templating', 'aurelia- this.viewFactory = viewFactory; this.viewSlot = viewSlot; - LogManager.getLogger('templating-resources').warn('The "with" behavior will be removed in the next release.'); + this.parentOverrideContext = null; + this.view = null; } + With.prototype.bind = function bind(bindingContext, overrideContext) { + this.parentOverrideContext = overrideContext; + this.valueChanged(this.value); + }; + With.prototype.valueChanged = function valueChanged(newValue) { + var overrideContext = createOverrideContext(newValue, this.parentOverrideContext); if (!this.view) { - this.view = this.viewFactory.create(newValue); + this.view = this.viewFactory.create(); + this.view.bind(newValue, overrideContext); this.viewSlot.add(this.view); } else { - this.view.bind(newValue); + this.view.bind(newValue, overrideContext); + } + }; + + With.prototype.unbind = function unbind() { + this.parentOverrideContext = null; + + if (this.view) { + this.view.unbind(); } }; diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md index e7268f0..4ced738 100644 --- a/doc/CHANGELOG.md +++ b/doc/CHANGELOG.md @@ -1,3 +1,34 @@ +## 0.17.0 (2015-11-10) + + +#### Bug Fixes + +* **all:** remove the global behavior ([00e63909](http://github.com/aurelia/templating-resources/commit/00e6390978217e0bd304bb423498607675c7a767)) +* **collection-strategy-locator:** correctly check for number ([265509ec](http://github.com/aurelia/templating-resources/commit/265509ec3cf593e67ea00075ef6dc7753b053050)) +* **compose:** update to latest composition engine ([90ce0d4c](http://github.com/aurelia/templating-resources/commit/90ce0d4cfd22212e5c37f689c1d0f93e655cee8f)) +* **map-collection-strategy:** + * fix animation interaction ([d97d5771](http://github.com/aurelia/templating-resources/commit/d97d5771509b9929a7b3178e32150abdd80fed34)) + * correctly locate view by index ([2a16daca](http://github.com/aurelia/templating-resources/commit/2a16daca9ec7f5a2498c6232fa886d8ea83217f7)) +* **number-strategy:** update binding context after processing items ([dc50b100](http://github.com/aurelia/templating-resources/commit/dc50b1002360326bcd18fd8f028aa0fae452874a)) +* **repeat:** + * various fixes ([c5c75329](http://github.com/aurelia/templating-resources/commit/c5c75329bc6d03863034b24c99e2eff9c03f21a5)) + * overrideContext should not be assigned to bindingContext ([164c361a](http://github.com/aurelia/templating-resources/commit/164c361ade46dbb6fff4939696b70ec3066e0c98)) + * return from bind if items is undefined ([4d89c0b4](http://github.com/aurelia/templating-resources/commit/4d89c0b4af529f3c2ad96680636cabc17011d6c3)) + * remove children from the view slot on unbind ([7a2bc54f](http://github.com/aurelia/templating-resources/commit/7a2bc54f37b97bd67e59a5bb5f74a48c085071ac)) +* **with:** use value as bindingContext ([1436ad45](http://github.com/aurelia/templating-resources/commit/1436ad451be73bb8bb19bac56bbe3cfb03784397)) + + +#### Features + +* **UpdateTriggerBindingBehavior:** add behavior ([3784c4f2](http://github.com/aurelia/templating-resources/commit/3784c4f2cf0e9e2f9185c9b1d6e92a74c8bf14ce), closes [#137](http://github.com/aurelia/templating-resources/issues/137)) +* **all:** + * update to use override context ([494f1f59](http://github.com/aurelia/templating-resources/commit/494f1f596df1c6c7e1f1671ee7f324589407e501)) + * update to separated create/bind view pattern ([42f3ff99](http://github.com/aurelia/templating-resources/commit/42f3ff99e0418eb4dde0f146e411d321340d7040)) +* **binding-behaviors:** add throttle, debounce, signal, oneTime, oneWay and twoWay behaviors ([dac5679c](http://github.com/aurelia/templating-resources/commit/dac5679cedc72fac8bdf81dbba2122606042c378)) +* **collection-strategy-locator:** add strategies ([60b8067c](http://github.com/aurelia/templating-resources/commit/60b8067cbf814d416344859225945786e0014de1), closes [#95](http://github.com/aurelia/templating-resources/issues/95)) +* **repeat:** handle value converters ([7c689a03](http://github.com/aurelia/templating-resources/commit/7c689a03f5e70fd1170d12103b1d4a0cfb3b4d34)) + + ### 0.16.1 (2015-10-15) diff --git a/package.json b/package.json index f9691b4..e2506ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aurelia-templating-resources", - "version": "0.16.1", + "version": "0.17.0", "description": "A standard set of behaviors, converters and other resources for use with the Aurelia templating library.", "keywords": [ "aurelia",