UNPKG

@danielkalen/simplybind

Version:

Magically simple, framework-less one-way/two-way data binding for frontend/backend in ~5kb.

2,071 lines (1,712 loc) 139 kB
var _dec, _dec2, _class, _dec3, _class2, _dec4, _class3, _dec5, _class5, _dec6, _class7, _dec7, _class8, _dec8, _class9, _dec9, _class10, _class11, _temp, _dec10, _class12, _class13, _temp2; import * as LogManager from 'aurelia-logging'; import { PLATFORM, DOM } from 'aurelia-pal'; import { TaskQueue } from 'aurelia-task-queue'; import { metadata } from 'aurelia-metadata'; const map = Object.create(null); export function camelCase(name) { if (name in map) { return map[name]; } const result = name.charAt(0).toLowerCase() + name.slice(1).replace(/[_.-](\w|$)/g, (_, x) => x.toUpperCase()); map[name] = result; return result; } export function createOverrideContext(bindingContext, parentOverrideContext) { return { bindingContext: bindingContext, parentOverrideContext: parentOverrideContext || null }; } export function getContextFor(name, scope, ancestor) { let oc = scope.overrideContext; if (ancestor) { while (ancestor && oc) { ancestor--; oc = oc.parentOverrideContext; } if (ancestor || !oc) { return undefined; } return name in oc ? oc : oc.bindingContext; } while (oc && !(name in oc) && !(oc.bindingContext && name in oc.bindingContext)) { oc = oc.parentOverrideContext; } if (oc) { return name in oc ? oc : oc.bindingContext; } return scope.bindingContext || scope.overrideContext; } export function createScopeForTest(bindingContext, parentBindingContext) { if (parentBindingContext) { return { bindingContext, overrideContext: createOverrideContext(bindingContext, createOverrideContext(parentBindingContext)) }; } return { bindingContext, overrideContext: createOverrideContext(bindingContext) }; } export const sourceContext = 'Binding:source'; const slotNames = []; const versionSlotNames = []; for (let i = 0; i < 100; i++) { slotNames.push(`_observer${ i }`); versionSlotNames.push(`_observerVersion${ i }`); } function addObserver(observer) { let observerSlots = this._observerSlots === undefined ? 0 : this._observerSlots; let i = observerSlots; while (i-- && this[slotNames[i]] !== observer) {} if (i === -1) { i = 0; while (this[slotNames[i]]) { i++; } this[slotNames[i]] = observer; observer.subscribe(sourceContext, this); if (i === observerSlots) { this._observerSlots = i + 1; } } if (this._version === undefined) { this._version = 0; } this[versionSlotNames[i]] = this._version; } function observeProperty(obj, propertyName) { let observer = this.observerLocator.getObserver(obj, propertyName); addObserver.call(this, observer); } function observeArray(array) { let observer = this.observerLocator.getArrayObserver(array); addObserver.call(this, observer); } function unobserve(all) { let i = this._observerSlots; while (i--) { if (all || this[versionSlotNames[i]] !== this._version) { let observer = this[slotNames[i]]; this[slotNames[i]] = null; if (observer) { observer.unsubscribe(sourceContext, this); } } } } export function connectable() { return function (target) { target.prototype.observeProperty = observeProperty; target.prototype.observeArray = observeArray; target.prototype.unobserve = unobserve; target.prototype.addObserver = addObserver; }; } const bindings = new Map(); const minimumImmediate = 100; const frameBudget = 15; let isFlushRequested = false; let immediate = 0; function flush(animationFrameStart) { let i = 0; let keys = bindings.keys(); let item; while (item = keys.next()) { if (item.done) { break; } let binding = item.value; bindings.delete(binding); binding.connect(true); i++; if (i % 100 === 0 && PLATFORM.performance.now() - animationFrameStart > frameBudget) { break; } } if (bindings.size) { PLATFORM.requestAnimationFrame(flush); } else { isFlushRequested = false; immediate = 0; } } export function enqueueBindingConnect(binding) { if (immediate < minimumImmediate) { immediate++; binding.connect(false); } else { bindings.set(binding); } if (!isFlushRequested) { isFlushRequested = true; PLATFORM.requestAnimationFrame(flush); } } function addSubscriber(context, callable) { if (this.hasSubscriber(context, callable)) { return false; } if (!this._context0) { this._context0 = context; this._callable0 = callable; return true; } if (!this._context1) { this._context1 = context; this._callable1 = callable; return true; } if (!this._context2) { this._context2 = context; this._callable2 = callable; return true; } if (!this._contextsRest) { this._contextsRest = [context]; this._callablesRest = [callable]; return true; } this._contextsRest.push(context); this._callablesRest.push(callable); return true; } function removeSubscriber(context, callable) { if (this._context0 === context && this._callable0 === callable) { this._context0 = null; this._callable0 = null; return true; } if (this._context1 === context && this._callable1 === callable) { this._context1 = null; this._callable1 = null; return true; } if (this._context2 === context && this._callable2 === callable) { this._context2 = null; this._callable2 = null; return true; } let rest = this._contextsRest; let index; if (!rest || !rest.length || (index = rest.indexOf(context)) === -1 || this._callablesRest[index] !== callable) { return false; } rest.splice(index, 1); this._callablesRest.splice(index, 1); return true; } let arrayPool1 = []; let arrayPool2 = []; let poolUtilization = []; function callSubscribers(newValue, oldValue) { let context0 = this._context0; let callable0 = this._callable0; let context1 = this._context1; let callable1 = this._callable1; let context2 = this._context2; let callable2 = this._callable2; let length = this._contextsRest ? this._contextsRest.length : 0; let contextsRest; let callablesRest; let poolIndex; let i; if (length) { poolIndex = poolUtilization.length; while (poolIndex-- && poolUtilization[poolIndex]) {} if (poolIndex < 0) { poolIndex = poolUtilization.length; contextsRest = []; callablesRest = []; poolUtilization.push(true); arrayPool1.push(contextsRest); arrayPool2.push(callablesRest); } else { poolUtilization[poolIndex] = true; contextsRest = arrayPool1[poolIndex]; callablesRest = arrayPool2[poolIndex]; } i = length; while (i--) { contextsRest[i] = this._contextsRest[i]; callablesRest[i] = this._callablesRest[i]; } } if (context0) { if (callable0) { callable0.call(context0, newValue, oldValue); } else { context0(newValue, oldValue); } } if (context1) { if (callable1) { callable1.call(context1, newValue, oldValue); } else { context1(newValue, oldValue); } } if (context2) { if (callable2) { callable2.call(context2, newValue, oldValue); } else { context2(newValue, oldValue); } } if (length) { for (i = 0; i < length; i++) { let callable = callablesRest[i]; let context = contextsRest[i]; if (callable) { callable.call(context, newValue, oldValue); } else { context(newValue, oldValue); } contextsRest[i] = null; callablesRest[i] = null; } poolUtilization[poolIndex] = false; } } function hasSubscribers() { return !!(this._context0 || this._context1 || this._context2 || this._contextsRest && this._contextsRest.length); } function hasSubscriber(context, callable) { let has = this._context0 === context && this._callable0 === callable || this._context1 === context && this._callable1 === callable || this._context2 === context && this._callable2 === callable; if (has) { return true; } let index; let contexts = this._contextsRest; if (!contexts || (index = contexts.length) === 0) { return false; } let callables = this._callablesRest; while (index--) { if (contexts[index] === context && callables[index] === callable) { return true; } } return false; } export function subscriberCollection() { return function (target) { target.prototype.addSubscriber = addSubscriber; target.prototype.removeSubscriber = removeSubscriber; target.prototype.callSubscribers = callSubscribers; target.prototype.hasSubscribers = hasSubscribers; target.prototype.hasSubscriber = hasSubscriber; }; } export let ExpressionObserver = (_dec = connectable(), _dec2 = subscriberCollection(), _dec(_class = _dec2(_class = class ExpressionObserver { constructor(scope, expression, observerLocator, lookupFunctions) { this.scope = scope; this.expression = expression; this.observerLocator = observerLocator; this.lookupFunctions = lookupFunctions; } getValue() { return this.expression.evaluate(this.scope, this.lookupFunctions); } setValue(newValue) { this.expression.assign(this.scope, newValue); } subscribe(context, callable) { if (!this.hasSubscribers()) { this.oldValue = this.expression.evaluate(this.scope, this.lookupFunctions); this.expression.connect(this, this.scope); } this.addSubscriber(context, callable); if (arguments.length === 1 && context instanceof Function) { return { dispose: () => { this.unsubscribe(context, callable); } }; } } unsubscribe(context, callable) { if (this.removeSubscriber(context, callable) && !this.hasSubscribers()) { this.unobserve(true); this.oldValue = undefined; } } call() { let newValue = this.expression.evaluate(this.scope, this.lookupFunctions); let oldValue = this.oldValue; if (newValue !== oldValue) { this.oldValue = newValue; this.callSubscribers(newValue, oldValue); } this._version++; this.expression.connect(this, this.scope); this.unobserve(false); } }) || _class) || _class); function isIndex(s) { return +s === s >>> 0; } function toNumber(s) { return +s; } function newSplice(index, removed, addedCount) { return { index: index, removed: removed, addedCount: addedCount }; } const EDIT_LEAVE = 0; const EDIT_UPDATE = 1; const EDIT_ADD = 2; const EDIT_DELETE = 3; function ArraySplice() {} ArraySplice.prototype = { calcEditDistances: function (current, currentStart, currentEnd, old, oldStart, oldEnd) { let rowCount = oldEnd - oldStart + 1; let columnCount = currentEnd - currentStart + 1; let distances = new Array(rowCount); let north; let west; for (let i = 0; i < rowCount; ++i) { distances[i] = new Array(columnCount); distances[i][0] = i; } for (let j = 0; j < columnCount; ++j) { distances[0][j] = j; } for (let i = 1; i < rowCount; ++i) { for (let j = 1; j < columnCount; ++j) { if (this.equals(current[currentStart + j - 1], old[oldStart + i - 1])) { distances[i][j] = distances[i - 1][j - 1]; } else { north = distances[i - 1][j] + 1; west = distances[i][j - 1] + 1; distances[i][j] = north < west ? north : west; } } } return distances; }, spliceOperationsFromEditDistances: function (distances) { let i = distances.length - 1; let j = distances[0].length - 1; let current = distances[i][j]; let edits = []; while (i > 0 || j > 0) { if (i === 0) { edits.push(EDIT_ADD); j--; continue; } if (j === 0) { edits.push(EDIT_DELETE); i--; continue; } let northWest = distances[i - 1][j - 1]; let west = distances[i - 1][j]; let north = distances[i][j - 1]; let min; if (west < north) { min = west < northWest ? west : northWest; } else { min = north < northWest ? north : northWest; } if (min === northWest) { if (northWest === current) { edits.push(EDIT_LEAVE); } else { edits.push(EDIT_UPDATE); current = northWest; } i--; j--; } else if (min === west) { edits.push(EDIT_DELETE); i--; current = west; } else { edits.push(EDIT_ADD); j--; current = north; } } edits.reverse(); return edits; }, calcSplices: function (current, currentStart, currentEnd, old, oldStart, oldEnd) { let prefixCount = 0; let suffixCount = 0; let minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart); if (currentStart === 0 && oldStart === 0) { prefixCount = this.sharedPrefix(current, old, minLength); } if (currentEnd === current.length && oldEnd === old.length) { suffixCount = this.sharedSuffix(current, old, minLength - prefixCount); } currentStart += prefixCount; oldStart += prefixCount; currentEnd -= suffixCount; oldEnd -= suffixCount; if (currentEnd - currentStart === 0 && oldEnd - oldStart === 0) { return []; } if (currentStart === currentEnd) { let splice = newSplice(currentStart, [], 0); while (oldStart < oldEnd) { splice.removed.push(old[oldStart++]); } return [splice]; } else if (oldStart === oldEnd) { return [newSplice(currentStart, [], currentEnd - currentStart)]; } let ops = this.spliceOperationsFromEditDistances(this.calcEditDistances(current, currentStart, currentEnd, old, oldStart, oldEnd)); let splice = undefined; let splices = []; let index = currentStart; let oldIndex = oldStart; for (let i = 0; i < ops.length; ++i) { switch (ops[i]) { case EDIT_LEAVE: if (splice) { splices.push(splice); splice = undefined; } index++; oldIndex++; break; case EDIT_UPDATE: if (!splice) { splice = newSplice(index, [], 0); } splice.addedCount++; index++; splice.removed.push(old[oldIndex]); oldIndex++; break; case EDIT_ADD: if (!splice) { splice = newSplice(index, [], 0); } splice.addedCount++; index++; break; case EDIT_DELETE: if (!splice) { splice = newSplice(index, [], 0); } splice.removed.push(old[oldIndex]); oldIndex++; break; } } if (splice) { splices.push(splice); } return splices; }, sharedPrefix: function (current, old, searchLength) { for (let i = 0; i < searchLength; ++i) { if (!this.equals(current[i], old[i])) { return i; } } return searchLength; }, sharedSuffix: function (current, old, searchLength) { let index1 = current.length; let index2 = old.length; let count = 0; while (count < searchLength && this.equals(current[--index1], old[--index2])) { count++; } return count; }, calculateSplices: function (current, previous) { return this.calcSplices(current, 0, current.length, previous, 0, previous.length); }, equals: function (currentValue, previousValue) { return currentValue === previousValue; } }; let arraySplice = new ArraySplice(); export function calcSplices(current, currentStart, currentEnd, old, oldStart, oldEnd) { return arraySplice.calcSplices(current, currentStart, currentEnd, old, oldStart, oldEnd); } function intersect(start1, end1, start2, end2) { if (end1 < start2 || end2 < start1) { return -1; } if (end1 === start2 || end2 === start1) { return 0; } if (start1 < start2) { if (end1 < end2) { return end1 - start2; } return end2 - start2; } if (end2 < end1) { return end2 - start1; } return end1 - start1; } export function mergeSplice(splices, index, removed, addedCount) { let splice = newSplice(index, removed, addedCount); let inserted = false; let insertionOffset = 0; for (let i = 0; i < splices.length; i++) { let current = splices[i]; current.index += insertionOffset; if (inserted) { continue; } let intersectCount = intersect(splice.index, splice.index + splice.removed.length, current.index, current.index + current.addedCount); if (intersectCount >= 0) { splices.splice(i, 1); i--; insertionOffset -= current.addedCount - current.removed.length; splice.addedCount += current.addedCount - intersectCount; let deleteCount = splice.removed.length + current.removed.length - intersectCount; if (!splice.addedCount && !deleteCount) { inserted = true; } else { let currentRemoved = current.removed; if (splice.index < current.index) { let prepend = splice.removed.slice(0, current.index - splice.index); Array.prototype.push.apply(prepend, currentRemoved); currentRemoved = prepend; } if (splice.index + splice.removed.length > current.index + current.addedCount) { let append = splice.removed.slice(current.index + current.addedCount - splice.index); Array.prototype.push.apply(currentRemoved, append); } splice.removed = currentRemoved; if (current.index < splice.index) { splice.index = current.index; } } } else if (splice.index < current.index) { inserted = true; splices.splice(i, 0, splice); i++; let offset = splice.addedCount - splice.removed.length; current.index += offset; insertionOffset += offset; } } if (!inserted) { splices.push(splice); } } function createInitialSplices(array, changeRecords) { let splices = []; for (let i = 0; i < changeRecords.length; i++) { let record = changeRecords[i]; switch (record.type) { case 'splice': mergeSplice(splices, record.index, record.removed.slice(), record.addedCount); break; case 'add': case 'update': case 'delete': if (!isIndex(record.name)) { continue; } let index = toNumber(record.name); if (index < 0) { continue; } mergeSplice(splices, index, [record.oldValue], record.type === 'delete' ? 0 : 1); break; default: console.error('Unexpected record type: ' + JSON.stringify(record)); break; } } return splices; } export function projectArraySplices(array, changeRecords) { let splices = []; createInitialSplices(array, changeRecords).forEach(function (splice) { if (splice.addedCount === 1 && splice.removed.length === 1) { if (splice.removed[0] !== array[splice.index]) { splices.push(splice); } return; } splices = splices.concat(calcSplices(array, splice.index, splice.index + splice.addedCount, splice.removed, 0, splice.removed.length)); }); return splices; } function newRecord(type, object, key, oldValue) { return { type: type, object: object, key: key, oldValue: oldValue }; } export function getChangeRecords(map) { let entries = new Array(map.size); let keys = map.keys(); let i = 0; let item; while (item = keys.next()) { if (item.done) { break; } entries[i] = newRecord('added', map, item.value); i++; } return entries; } export let ModifyCollectionObserver = (_dec3 = subscriberCollection(), _dec3(_class2 = class ModifyCollectionObserver { constructor(taskQueue, collection) { this.taskQueue = taskQueue; this.queued = false; this.changeRecords = null; this.oldCollection = null; this.collection = collection; this.lengthPropertyName = collection instanceof Map || collection instanceof Set ? 'size' : 'length'; } subscribe(context, callable) { this.addSubscriber(context, callable); } unsubscribe(context, callable) { this.removeSubscriber(context, callable); } addChangeRecord(changeRecord) { if (!this.hasSubscribers() && !this.lengthObserver) { return; } if (changeRecord.type === 'splice') { let index = changeRecord.index; let arrayLength = changeRecord.object.length; if (index > arrayLength) { index = arrayLength - changeRecord.addedCount; } else if (index < 0) { index = arrayLength + changeRecord.removed.length + index - changeRecord.addedCount; } if (index < 0) { index = 0; } changeRecord.index = index; } if (this.changeRecords === null) { this.changeRecords = [changeRecord]; } else { this.changeRecords.push(changeRecord); } if (!this.queued) { this.queued = true; this.taskQueue.queueMicroTask(this); } } flushChangeRecords() { if (this.changeRecords && this.changeRecords.length || this.oldCollection) { this.call(); } } reset(oldCollection) { this.oldCollection = oldCollection; if (this.hasSubscribers() && !this.queued) { this.queued = true; this.taskQueue.queueMicroTask(this); } } getLengthObserver() { return this.lengthObserver || (this.lengthObserver = new CollectionLengthObserver(this.collection)); } call() { let changeRecords = this.changeRecords; let oldCollection = this.oldCollection; let records; this.queued = false; this.changeRecords = []; this.oldCollection = null; if (this.hasSubscribers()) { if (oldCollection) { if (this.collection instanceof Map || this.collection instanceof Set) { records = getChangeRecords(oldCollection); } else { records = calcSplices(this.collection, 0, this.collection.length, oldCollection, 0, oldCollection.length); } } else { if (this.collection instanceof Map || this.collection instanceof Set) { records = changeRecords; } else { records = projectArraySplices(this.collection, changeRecords); } } this.callSubscribers(records); } if (this.lengthObserver) { this.lengthObserver.call(this.collection[this.lengthPropertyName]); } } }) || _class2); export let CollectionLengthObserver = (_dec4 = subscriberCollection(), _dec4(_class3 = class CollectionLengthObserver { constructor(collection) { this.collection = collection; this.lengthPropertyName = collection instanceof Map || collection instanceof Set ? 'size' : 'length'; this.currentValue = collection[this.lengthPropertyName]; } getValue() { return this.collection[this.lengthPropertyName]; } setValue(newValue) { this.collection[this.lengthPropertyName] = newValue; } subscribe(context, callable) { this.addSubscriber(context, callable); } unsubscribe(context, callable) { this.removeSubscriber(context, callable); } call(newValue) { let oldValue = this.currentValue; this.callSubscribers(newValue, oldValue); this.currentValue = newValue; } }) || _class3); let pop = Array.prototype.pop; let push = Array.prototype.push; let reverse = Array.prototype.reverse; let shift = Array.prototype.shift; let sort = Array.prototype.sort; let splice = Array.prototype.splice; let unshift = Array.prototype.unshift; Array.prototype.pop = function () { let notEmpty = this.length > 0; let methodCallResult = pop.apply(this, arguments); if (notEmpty && this.__array_observer__ !== undefined) { this.__array_observer__.addChangeRecord({ type: 'delete', object: this, name: this.length, oldValue: methodCallResult }); } return methodCallResult; }; Array.prototype.push = function () { let methodCallResult = push.apply(this, arguments); if (this.__array_observer__ !== undefined) { this.__array_observer__.addChangeRecord({ type: 'splice', object: this, index: this.length - arguments.length, removed: [], addedCount: arguments.length }); } return methodCallResult; }; Array.prototype.reverse = function () { let oldArray; if (this.__array_observer__ !== undefined) { this.__array_observer__.flushChangeRecords(); oldArray = this.slice(); } let methodCallResult = reverse.apply(this, arguments); if (this.__array_observer__ !== undefined) { this.__array_observer__.reset(oldArray); } return methodCallResult; }; Array.prototype.shift = function () { let notEmpty = this.length > 0; let methodCallResult = shift.apply(this, arguments); if (notEmpty && this.__array_observer__ !== undefined) { this.__array_observer__.addChangeRecord({ type: 'delete', object: this, name: 0, oldValue: methodCallResult }); } return methodCallResult; }; Array.prototype.sort = function () { let oldArray; if (this.__array_observer__ !== undefined) { this.__array_observer__.flushChangeRecords(); oldArray = this.slice(); } let methodCallResult = sort.apply(this, arguments); if (this.__array_observer__ !== undefined) { this.__array_observer__.reset(oldArray); } return methodCallResult; }; Array.prototype.splice = function () { let methodCallResult = splice.apply(this, arguments); if (this.__array_observer__ !== undefined) { this.__array_observer__.addChangeRecord({ type: 'splice', object: this, index: arguments[0], removed: methodCallResult, addedCount: arguments.length > 2 ? arguments.length - 2 : 0 }); } return methodCallResult; }; Array.prototype.unshift = function () { let methodCallResult = unshift.apply(this, arguments); if (this.__array_observer__ !== undefined) { this.__array_observer__.addChangeRecord({ type: 'splice', object: this, index: 0, removed: [], addedCount: arguments.length }); } return methodCallResult; }; export function getArrayObserver(taskQueue, array) { return ModifyArrayObserver.for(taskQueue, array); } let ModifyArrayObserver = class ModifyArrayObserver extends ModifyCollectionObserver { constructor(taskQueue, array) { super(taskQueue, array); } static for(taskQueue, array) { if (!('__array_observer__' in array)) { Reflect.defineProperty(array, '__array_observer__', { value: ModifyArrayObserver.create(taskQueue, array), enumerable: false, configurable: false }); } return array.__array_observer__; } static create(taskQueue, array) { return new ModifyArrayObserver(taskQueue, array); } }; export let Expression = class Expression { constructor() { this.isChain = false; this.isAssignable = false; } evaluate(scope, lookupFunctions, args) { throw new Error(`Binding expression "${ this }" cannot be evaluated.`); } assign(scope, value, lookupFunctions) { throw new Error(`Binding expression "${ this }" cannot be assigned to.`); } toString() { return Unparser.unparse(this); } }; export let Chain = class Chain extends Expression { constructor(expressions) { super(); this.expressions = expressions; this.isChain = true; } evaluate(scope, lookupFunctions) { let result; let expressions = this.expressions; let last; for (let i = 0, length = expressions.length; i < length; ++i) { last = expressions[i].evaluate(scope, lookupFunctions); if (last !== null) { result = last; } } return result; } accept(visitor) { return visitor.visitChain(this); } }; export let BindingBehavior = class BindingBehavior extends Expression { constructor(expression, name, args) { super(); this.expression = expression; this.name = name; this.args = args; } evaluate(scope, lookupFunctions) { return this.expression.evaluate(scope, lookupFunctions); } assign(scope, value, lookupFunctions) { return this.expression.assign(scope, value, lookupFunctions); } accept(visitor) { return visitor.visitBindingBehavior(this); } connect(binding, scope) { this.expression.connect(binding, scope); } bind(binding, scope, lookupFunctions) { if (this.expression.expression && this.expression.bind) { this.expression.bind(binding, scope, lookupFunctions); } let behavior = lookupFunctions.bindingBehaviors(this.name); if (!behavior) { throw new Error(`No BindingBehavior named "${ this.name }" was found!`); } let behaviorKey = `behavior-${ this.name }`; if (binding[behaviorKey]) { throw new Error(`A binding behavior named "${ this.name }" has already been applied to "${ this.expression }"`); } binding[behaviorKey] = behavior; behavior.bind.apply(behavior, [binding, scope].concat(evalList(scope, this.args, binding.lookupFunctions))); } unbind(binding, scope) { let behaviorKey = `behavior-${ this.name }`; binding[behaviorKey].unbind(binding, scope); binding[behaviorKey] = null; if (this.expression.expression && this.expression.unbind) { this.expression.unbind(binding, scope); } } }; export let ValueConverter = class ValueConverter extends Expression { constructor(expression, name, args, allArgs) { super(); this.expression = expression; this.name = name; this.args = args; this.allArgs = allArgs; } evaluate(scope, lookupFunctions) { let converter = lookupFunctions.valueConverters(this.name); if (!converter) { throw new Error(`No ValueConverter named "${ this.name }" was found!`); } if ('toView' in converter) { return converter.toView.apply(converter, evalList(scope, this.allArgs, lookupFunctions)); } return this.allArgs[0].evaluate(scope, lookupFunctions); } assign(scope, value, lookupFunctions) { let converter = lookupFunctions.valueConverters(this.name); if (!converter) { throw new Error(`No ValueConverter named "${ this.name }" was found!`); } if ('fromView' in converter) { value = converter.fromView.apply(converter, [value].concat(evalList(scope, this.args, lookupFunctions))); } return this.allArgs[0].assign(scope, value, lookupFunctions); } accept(visitor) { return visitor.visitValueConverter(this); } connect(binding, scope) { let expressions = this.allArgs; let i = expressions.length; while (i--) { expressions[i].connect(binding, scope); } } }; export let Assign = class Assign extends Expression { constructor(target, value) { super(); this.target = target; this.value = value; } evaluate(scope, lookupFunctions) { return this.target.assign(scope, this.value.evaluate(scope, lookupFunctions)); } accept(vistor) { vistor.visitAssign(this); } connect(binding, scope) {} }; export let Conditional = class Conditional extends Expression { constructor(condition, yes, no) { super(); this.condition = condition; this.yes = yes; this.no = no; } evaluate(scope, lookupFunctions) { return !!this.condition.evaluate(scope) ? this.yes.evaluate(scope) : this.no.evaluate(scope); } accept(visitor) { return visitor.visitConditional(this); } connect(binding, scope) { this.condition.connect(binding, scope); if (this.condition.evaluate(scope)) { this.yes.connect(binding, scope); } else { this.no.connect(binding, scope); } } }; export let AccessThis = class AccessThis extends Expression { constructor(ancestor) { super(); this.ancestor = ancestor; } evaluate(scope, lookupFunctions) { let oc = scope.overrideContext; let i = this.ancestor; while (i-- && oc) { oc = oc.parentOverrideContext; } return i < 1 && oc ? oc.bindingContext : undefined; } accept(visitor) { return visitor.visitAccessThis(this); } connect(binding, scope) {} }; export let AccessScope = class AccessScope extends Expression { constructor(name, ancestor) { super(); this.name = name; this.ancestor = ancestor; this.isAssignable = true; } evaluate(scope, lookupFunctions) { let context = getContextFor(this.name, scope, this.ancestor); return context[this.name]; } assign(scope, value) { let context = getContextFor(this.name, scope, this.ancestor); return context ? context[this.name] = value : undefined; } accept(visitor) { return visitor.visitAccessScope(this); } connect(binding, scope) { let context = getContextFor(this.name, scope, this.ancestor); binding.observeProperty(context, this.name); } }; export let AccessMember = class AccessMember extends Expression { constructor(object, name) { super(); this.object = object; this.name = name; this.isAssignable = true; } evaluate(scope, lookupFunctions) { let instance = this.object.evaluate(scope, lookupFunctions); return instance === null || instance === undefined ? instance : instance[this.name]; } assign(scope, value) { let instance = this.object.evaluate(scope); if (instance === null || instance === undefined) { instance = {}; this.object.assign(scope, instance); } instance[this.name] = value; return value; } accept(visitor) { return visitor.visitAccessMember(this); } connect(binding, scope) { this.object.connect(binding, scope); let obj = this.object.evaluate(scope); if (obj) { binding.observeProperty(obj, this.name); } } }; export let AccessKeyed = class AccessKeyed extends Expression { constructor(object, key) { super(); this.object = object; this.key = key; this.isAssignable = true; } evaluate(scope, lookupFunctions) { let instance = this.object.evaluate(scope, lookupFunctions); let lookup = this.key.evaluate(scope, lookupFunctions); return getKeyed(instance, lookup); } assign(scope, value) { let instance = this.object.evaluate(scope); let lookup = this.key.evaluate(scope); return setKeyed(instance, lookup, value); } accept(visitor) { return visitor.visitAccessKeyed(this); } connect(binding, scope) { this.object.connect(binding, scope); let obj = this.object.evaluate(scope); if (obj instanceof Object) { this.key.connect(binding, scope); let key = this.key.evaluate(scope); if (key !== null && key !== undefined && !(Array.isArray(obj) && typeof key === 'number')) { binding.observeProperty(obj, key); } } } }; export let CallScope = class CallScope extends Expression { constructor(name, args, ancestor) { super(); this.name = name; this.args = args; this.ancestor = ancestor; } evaluate(scope, lookupFunctions, mustEvaluate) { let args = evalList(scope, this.args, lookupFunctions); let context = getContextFor(this.name, scope, this.ancestor); let func = getFunction(context, this.name, mustEvaluate); if (func) { return func.apply(context, args); } return undefined; } accept(visitor) { return visitor.visitCallScope(this); } connect(binding, scope) { let args = this.args; let i = args.length; while (i--) { args[i].connect(binding, scope); } } }; export let CallMember = class CallMember extends Expression { constructor(object, name, args) { super(); this.object = object; this.name = name; this.args = args; } evaluate(scope, lookupFunctions, mustEvaluate) { let instance = this.object.evaluate(scope, lookupFunctions); let args = evalList(scope, this.args, lookupFunctions); let func = getFunction(instance, this.name, mustEvaluate); if (func) { return func.apply(instance, args); } return undefined; } accept(visitor) { return visitor.visitCallMember(this); } connect(binding, scope) { this.object.connect(binding, scope); let obj = this.object.evaluate(scope); if (getFunction(obj, this.name, false)) { let args = this.args; let i = args.length; while (i--) { args[i].connect(binding, scope); } } } }; export let CallFunction = class CallFunction extends Expression { constructor(func, args) { super(); this.func = func; this.args = args; } evaluate(scope, lookupFunctions, mustEvaluate) { let func = this.func.evaluate(scope, lookupFunctions); if (typeof func === 'function') { return func.apply(null, evalList(scope, this.args, lookupFunctions)); } if (!mustEvaluate && (func === null || func === undefined)) { return undefined; } throw new Error(`${ this.func } is not a function`); } accept(visitor) { return visitor.visitCallFunction(this); } connect(binding, scope) { this.func.connect(binding, scope); let func = this.func.evaluate(scope); if (typeof func === 'function') { let args = this.args; let i = args.length; while (i--) { args[i].connect(binding, scope); } } } }; export let Binary = class Binary extends Expression { constructor(operation, left, right) { super(); this.operation = operation; this.left = left; this.right = right; } evaluate(scope, lookupFunctions) { let left = this.left.evaluate(scope); switch (this.operation) { case '&&': return left && this.right.evaluate(scope); case '||': return left || this.right.evaluate(scope); } let right = this.right.evaluate(scope); switch (this.operation) { case '==': return left == right; case '===': return left === right; case '!=': return left != right; case '!==': return left !== right; } if (left === null || right === null || left === undefined || right === undefined) { switch (this.operation) { case '+': if (left !== null && left !== undefined) return left; if (right !== null && right !== undefined) return right; return 0; case '-': if (left !== null && left !== undefined) return left; if (right !== null && right !== undefined) return 0 - right; return 0; } return null; } switch (this.operation) { case '+': return autoConvertAdd(left, right); case '-': return left - right; case '*': return left * right; case '/': return left / right; case '%': return left % right; case '<': return left < right; case '>': return left > right; case '<=': return left <= right; case '>=': return left >= right; case '^': return left ^ right; } throw new Error(`Internal error [${ this.operation }] not handled`); } accept(visitor) { return visitor.visitBinary(this); } connect(binding, scope) { this.left.connect(binding, scope); let left = this.left.evaluate(scope); if (this.operation === '&&' && !left || this.operation === '||' && left) { return; } this.right.connect(binding, scope); } }; export let PrefixNot = class PrefixNot extends Expression { constructor(operation, expression) { super(); this.operation = operation; this.expression = expression; } evaluate(scope, lookupFunctions) { return !this.expression.evaluate(scope); } accept(visitor) { return visitor.visitPrefix(this); } connect(binding, scope) { this.expression.connect(binding, scope); } }; export let LiteralPrimitive = class LiteralPrimitive extends Expression { constructor(value) { super(); this.value = value; } evaluate(scope, lookupFunctions) { return this.value; } accept(visitor) { return visitor.visitLiteralPrimitive(this); } connect(binding, scope) {} }; export let LiteralString = class LiteralString extends Expression { constructor(value) { super(); this.value = value; } evaluate(scope, lookupFunctions) { return this.value; } accept(visitor) { return visitor.visitLiteralString(this); } connect(binding, scope) {} }; export let LiteralArray = class LiteralArray extends Expression { constructor(elements) { super(); this.elements = elements; } evaluate(scope, lookupFunctions) { let elements = this.elements; let result = []; for (let i = 0, length = elements.length; i < length; ++i) { result[i] = elements[i].evaluate(scope, lookupFunctions); } return result; } accept(visitor) { return visitor.visitLiteralArray(this); } connect(binding, scope) { let length = this.elements.length; for (let i = 0; i < length; i++) { this.elements[i].connect(binding, scope); } } }; export let LiteralObject = class LiteralObject extends Expression { constructor(keys, values) { super(); this.keys = keys; this.values = values; } evaluate(scope, lookupFunctions) { let instance = {}; let keys = this.keys; let values = this.values; for (let i = 0, length = keys.length; i < length; ++i) { instance[keys[i]] = values[i].evaluate(scope, lookupFunctions); } return instance; } accept(visitor) { return visitor.visitLiteralObject(this); } connect(binding, scope) { let length = this.keys.length; for (let i = 0; i < length; i++) { this.values[i].connect(binding, scope); } } }; let evalListCache = [[], [0], [0, 0], [0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0, 0]]; function evalList(scope, list, lookupFunctions) { let length = list.length; for (let cacheLength = evalListCache.length; cacheLength <= length; ++cacheLength) { evalListCache.push([]); } let result = evalListCache[length]; for (let i = 0; i < length; ++i) { result[i] = list[i].evaluate(scope, lookupFunctions); } return result; } function autoConvertAdd(a, b) { if (a !== null && b !== null) { if (typeof a === 'string' && typeof b !== 'string') { return a + b.toString(); } if (typeof a !== 'string' && typeof b === 'string') { return a.toString() + b; } return a + b; } if (a !== null) { return a; } if (b !== null) { return b; } return 0; } function getFunction(obj, name, mustExist) { let func = obj === null || obj === undefined ? null : obj[name]; if (typeof func === 'function') { return func; } if (!mustExist && (func === null || func === undefined)) { return null; } throw new Error(`${ name } is not a function`); } function getKeyed(obj, key) { if (Array.isArray(obj)) { return obj[parseInt(key, 10)]; } else if (obj) { return obj[key]; } else if (obj === null || obj === undefined) { return undefined; } return obj[key]; } function setKeyed(obj, key, value) { if (Array.isArray(obj)) { let index = parseInt(key, 10); if (obj.length <= index) { obj.length = index + 1; } obj[index] = value; } else { obj[key] = value; } return value; } export let Unparser = class Unparser { constructor(buffer) { this.buffer = buffer; } static unparse(expression) { let buffer = []; let visitor = new Unparser(buffer); expression.accept(visitor); return buffer.join(''); } write(text) { this.buffer.push(text); } writeArgs(args) { this.write('('); for (let i = 0, length = args.length; i < length; ++i) { if (i !== 0) { this.write(','); } args[i].accept(this); } this.write(')'); } visitChain(chain) { let expressions = chain.expressions; for (let i = 0, length = expression.length; i < length; ++i) { if (i !== 0) { this.write(';'); } expressions[i].accept(this); } } visitBindingBehavior(behavior) { let args = behavior.args; behavior.expression.accept(this); this.write(`&${ behavior.name }`); for (let i = 0, length = args.length; i < length; ++i) { this.write(':'); args[i].accept(this); } } visitValueConverter(converter) { let args = converter.args; converter.expression.accept(this); this.write(`|${ converter.name }`); for (let i = 0, length = args.length; i < length; ++i) { this.write(':'); args[i].accept(this); } } visitAssign(assign) { assign.target.accept(this); this.write('='); assign.value.accept(this); } visitConditional(conditional) { conditional.condition.accept(this); this.write('?'); conditional.yes.accept(this); this.write(':'); conditional.no.accept(this); } visitAccessThis(access) { if (access.ancestor === 0) { this.write('$this'); return; } this.write('$parent'); let i = access.ancestor - 1; while (i--) { this.write('.$parent'); } } visitAccessScope(access) { let i = access.ancestor; while (i--) { this.write('$parent.'); } this.write(access.name); } visitAccessMember(access) { access.object.accept(this); this.write(`.${ access.name }`); } visitAccessKeyed(access) { access.object.accept(this); this.write('['); access.key.accept(this); this.write(']'); } visitCallScope(call) { let i = call.ancestor; while (i--) { this.write('$parent.'); } this.write(call.name); this.writeArgs(call.args); } visitCallFunction(call) { call.func.accept(this); this.writeArgs(call.args); } visitCallMember(call) { call.object.accept(this); this.write(`.${ call.name }`); this.writeArgs(call.args); } visitPrefix(prefix) { this.write(`(${ prefix.operation }`); prefix.expression.accept(this); this.write(')'); } visitBinary(binary) { binary.left.accept(this); this.write(binary.operation); binary.right.accept(this); } visitLiteralPrimitive(literal) { this.write(`${ literal.value }`); } visitLiteralArray(literal) { let elements = literal.elements; this.write('['); for (let i = 0, length = elements.length; i < length; ++i) { if (i !== 0) { this.write(','); } elements[i].accept(this); } this.write(']'); } visitLiteralObject(literal) { let keys = literal.keys; let values = literal.values; this.write('{'); for (let i = 0, length = keys.length; i < length; ++i) { if (i !== 0) { this.write(','); } this.write(`'${ keys[i] }':`); values[i].accept(this); } this.write('}'); } visitLiteralString(literal) { let escaped = literal.value.replace(/'/g, "\'"); this.write(`'${ escaped }'`); } }; export let ExpressionCloner = class ExpressionCloner { cloneExpressionArray(array) { let clonedArray = []; let i = array.length; while (i--) { clonedArray[i] = array[i].accept(this); } return clonedArray; } visitChain(chain) { return new Chain(this.cloneExpressionArray(chain.expressions)); } visitBindingBehavior(behavior) { return new BindingBehavior(behavior.expression.accept(this), behavior.name, this.cloneExpressionArray(behavior.args)); } visitValueConverter(converter) { return new ValueConverter(converter.expression.accept(this), converter.name, this.cloneExpressionArray(converter.args)); } visitAssign(assign) { return new Assign(assign.target.accept(this), assign.value.accept(this)); } visitConditional(conditional) { return new Conditional(conditional.condition.accept(this), conditional.yes.accept(this), conditional.no.accept(this)); } visitAccessThis(access) { return new AccessThis(access.ancestor); } visitAccessScope(access) { return new AccessScope(access.name, access.ancestor); } visitAccessMember(access) { return new AccessMember(access.object.accept(this), access.name); } visitAccessKeyed(access) { return new AccessKeyed(access.object.accept(this), access.key.accept(this)); } visitCallScope(call) { return new CallScope(call.name, this.cloneExpressionArray(call.args), call.ancestor); } visitCallFunction(call) { return new CallFunction(call.func.accept(this), this.cloneExpressionArray(call.args)); } visitCallMember(call) { return new CallMember(call.object.accept(this), call.name, this.cloneExpressionArray(call.args)); } visitPrefix(prefix) { return new PrefixNot(prefix.operation, prefix.expression.accept(this)); } visitBinary(binary) { return new Binary(binary.operation, binary.left.accept(this), binary.right.accept(this)); } visitLiteralPrimitive(literal) { return new LiteralPrimitive(literal); } visitLiteralArray(literal) { return new LiteralArray(this.cloneExpressionArray(literal.elements)); } visitLiteralObject(literal) { return new LiteralObject(literal.keys, this.cloneExpressionArray(literal.values)); } visitLiteralString(literal) { return new LiteralString(literal.value); } }; export function cloneExpression(expression) { let visitor = new ExpressionCloner(); return expression.accept(visitor); } export const bindingMode = { oneTime: 0, oneWay: 1, twoWay: 2 }; export let Token = class Token { constructor(index, text) { this.index = index; this.text = text; } withOp(op) { this.opKey = op; return this; } withGetterSetter(key) { this.key = key; return this; } withValue(value) { this.value = value; return this; } toString() { return `Token(${ this.text })`; } }; export let Lexer = class Lexer { lex(text) { let scanner = new S