@danielkalen/simplybind
Version:
Magically simple, framework-less one-way/two-way data binding for frontend/backend in ~5kb.
139 lines (124 loc) • 3.87 kB
JavaScript
import {bindingMode} from './binding-mode';
import {connectable, sourceContext} from './connectable-binding';
import {enqueueBindingConnect} from './connect-queue';
export class BindingExpression {
constructor(observerLocator, targetProperty, sourceExpression,
mode, lookupFunctions, attribute) {
this.observerLocator = observerLocator;
this.targetProperty = targetProperty;
this.sourceExpression = sourceExpression;
this.mode = mode;
this.lookupFunctions = lookupFunctions;
this.attribute = attribute;
this.discrete = false;
}
createBinding(target) {
return new Binding(
this.observerLocator,
this.sourceExpression,
target,
this.targetProperty,
this.mode,
this.lookupFunctions
);
}
}
const targetContext = 'Binding:target';
export class Binding {
constructor(observerLocator, sourceExpression, target, targetProperty, mode, lookupFunctions) {
this.observerLocator = observerLocator;
this.sourceExpression = sourceExpression;
this.target = target;
this.targetProperty = targetProperty;
this.mode = mode;
this.lookupFunctions = lookupFunctions;
}
updateTarget(value) {
this.targetObserver.setValue(value, this.target, this.targetProperty);
}
updateSource(value) {
this.sourceExpression.assign(this.source, value, this.lookupFunctions);
}
call(context, newValue, oldValue) {
if (!this.isBound) {
return;
}
if (context === sourceContext) {
oldValue = this.targetObserver.getValue(this.target, this.targetProperty);
newValue = this.sourceExpression.evaluate(this.source, this.lookupFunctions);
if (newValue !== oldValue) {
this.updateTarget(newValue);
}
if (this.mode !== bindingMode.oneTime) {
this._version++;
this.sourceExpression.connect(this, this.source);
this.unobserve(false);
}
return;
}
if (context === targetContext) {
if (newValue !== this.sourceExpression.evaluate(this.source, this.lookupFunctions)) {
this.updateSource(newValue);
}
return;
}
throw new Error(`Unexpected call context ${context}`);
}
bind(source) {
if (this.isBound) {
if (this.source === source) {
return;
}
this.unbind();
}
this.isBound = true;
this.source = source;
if (this.sourceExpression.bind) {
this.sourceExpression.bind(this, source, this.lookupFunctions);
}
let mode = this.mode;
if (!this.targetObserver) {
let method = mode === bindingMode.twoWay ? 'getObserver' : 'getAccessor';
this.targetObserver = this.observerLocator[method](this.target, this.targetProperty);
}
if ('bind' in this.targetObserver) {
this.targetObserver.bind();
}
let value = this.sourceExpression.evaluate(source, this.lookupFunctions);
this.updateTarget(value);
if (mode === bindingMode.oneWay) {
enqueueBindingConnect(this);
} else if (mode === bindingMode.twoWay) {
this.sourceExpression.connect(this, source);
this.targetObserver.subscribe(targetContext, this);
}
}
unbind() {
if (!this.isBound) {
return;
}
this.isBound = false;
if (this.sourceExpression.unbind) {
this.sourceExpression.unbind(this, this.source);
}
this.source = null;
if ('unbind' in this.targetObserver) {
this.targetObserver.unbind();
}
if (this.targetObserver.unsubscribe) {
this.targetObserver.unsubscribe(targetContext, this);
}
this.unobserve(true);
}
connect(evaluate) {
if (!this.isBound) {
return;
}
if (evaluate) {
let value = this.sourceExpression.evaluate(this.source, this.lookupFunctions);
this.updateTarget(value);
}
this.sourceExpression.connect(this, this.source);
}
}