UNPKG

@danielkalen/simplybind

Version:

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

134 lines (116 loc) 3.97 kB
import {subscriberCollection} from './subscriber-collection'; const checkedArrayContext = 'CheckedObserver:array'; const checkedValueContext = 'CheckedObserver:value'; @subscriberCollection() export class CheckedObserver { constructor(element, handler, observerLocator) { this.element = element; this.handler = handler; this.observerLocator = observerLocator; } getValue() { return this.value; } setValue(newValue) { if (this.value === newValue) { return; } // unsubscribe from old array. if (this.arrayObserver) { this.arrayObserver.unsubscribe(checkedArrayContext, this); this.arrayObserver = null; } // subscribe to new array. if (this.element.type === 'checkbox' && Array.isArray(newValue)) { this.arrayObserver = this.observerLocator.getArrayObserver(newValue); this.arrayObserver.subscribe(checkedArrayContext, this); } // assign and sync element. this.oldValue = this.value; this.value = newValue; this.synchronizeElement(); this.notify(); // queue up an initial sync after the bindings have been evaluated. if (!this.initialSync) { this.initialSync = true; this.observerLocator.taskQueue.queueMicroTask(this); } } call(context, splices) { // called by task queue, array observer, and model/value observer. this.synchronizeElement(); // if the input's model or value property is data-bound, subscribe to it's // changes to enable synchronizing the element's checked status when a change occurs. if (!this.valueObserver) { this.valueObserver = this.element.__observers__.model || this.element.__observers__.value; if (this.valueObserver) { this.valueObserver.subscribe(checkedValueContext, this); } } } synchronizeElement() { let value = this.value; let element = this.element; let elementValue = element.hasOwnProperty('model') ? element.model : element.value; let isRadio = element.type === 'radio'; let matcher = element.matcher || ((a, b) => a === b); element.checked = isRadio && !!matcher(value, elementValue) || !isRadio && value === true || !isRadio && Array.isArray(value) && value.findIndex(item => !!matcher(item, elementValue)) !== -1; } synchronizeValue() { let value = this.value; let element = this.element; let elementValue = element.hasOwnProperty('model') ? element.model : element.value; let index; let matcher = element.matcher || ((a, b) => a === b); if (element.type === 'checkbox') { if (Array.isArray(value)) { index = value.findIndex(item => !!matcher(item, elementValue)); if (element.checked && index === -1) { value.push(elementValue); } else if (!element.checked && index !== -1) { value.splice(index, 1); } // don't invoke callbacks. return; } value = element.checked; } else if (element.checked) { value = elementValue; } else { // don't invoke callbacks. return; } this.oldValue = this.value; this.value = value; this.notify(); } notify() { let oldValue = this.oldValue; let newValue = this.value; this.callSubscribers(newValue, oldValue); } subscribe(context, callable) { if (!this.hasSubscribers()) { this.disposeHandler = this.handler.subscribe(this.element, this.synchronizeValue.bind(this, false)); } this.addSubscriber(context, callable); } unsubscribe(context, callable) { if (this.removeSubscriber(context, callable) && !this.hasSubscribers()) { this.disposeHandler(); this.disposeHandler = null; } } unbind() { if (this.arrayObserver) { this.arrayObserver.unsubscribe(checkedArrayContext, this); this.arrayObserver = null; } if (this.valueObserver) { this.valueObserver.unsubscribe(checkedValueContext, this); } } }