UNPKG

@danielkalen/simplybind

Version:

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

175 lines (155 loc) 4.77 kB
import {subscriberCollection} from './subscriber-collection'; import {DOM} from 'aurelia-pal'; const selectArrayContext = 'SelectValueObserver:array'; @subscriberCollection() export class SelectValueObserver { constructor(element, handler, observerLocator) { this.element = element; this.handler = handler; this.observerLocator = observerLocator; } getValue() { return this.value; } setValue(newValue) { if (newValue !== null && newValue !== undefined && this.element.multiple && !Array.isArray(newValue)) { throw new Error('Only null or Array instances can be bound to a multi-select.'); } if (this.value === newValue) { return; } // unsubscribe from old array. if (this.arrayObserver) { this.arrayObserver.unsubscribe(selectArrayContext, this); this.arrayObserver = null; } // subscribe to new array. if (Array.isArray(newValue)) { this.arrayObserver = this.observerLocator.getArrayObserver(newValue); this.arrayObserver.subscribe(selectArrayContext, this); } // assign and sync element. this.oldValue = this.value; this.value = newValue; this.synchronizeOptions(); 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 and array observer. this.synchronizeOptions(); } synchronizeOptions() { let value = this.value; let clear; let isArray; if (value === null || value === undefined) { clear = true; } else if (Array.isArray(value)) { isArray = true; } let options = this.element.options; let i = options.length; let matcher = this.element.matcher || ((a, b) => a === b); while (i--) { let option = options.item(i); if (clear) { option.selected = false; continue; } let optionValue = option.hasOwnProperty('model') ? option.model : option.value; if (isArray) { option.selected = value.findIndex(item => !!matcher(optionValue, item)) !== -1; // eslint-disable-line no-loop-func continue; } option.selected = !!matcher(optionValue, value); } } synchronizeValue() { let options = this.element.options; let count = 0; let value = []; for (let i = 0, ii = options.length; i < ii; i++) { let option = options.item(i); if (!option.selected) { continue; } value.push(option.hasOwnProperty('model') ? option.model : option.value); count++; } if (this.element.multiple) { // multi-select if (Array.isArray(this.value)) { let matcher = this.element.matcher || ((a, b) => a === b); // remove items that are no longer selected. let i = 0; while (i < this.value.length) { let a = this.value[i]; if (value.findIndex(b => matcher(a, b)) === -1) { // eslint-disable-line no-loop-func this.value.splice(i, 1); } else { i++; } } // add items that have been selected. i = 0; while (i < value.length) { let a = value[i]; if (this.value.findIndex(b => matcher(a, b)) === -1) { // eslint-disable-line no-loop-func this.value.push(a); } i++; } return; // don't notify. } } else { // single-select if (count === 0) { value = null; } else { value = value[0]; } } if (value !== this.value) { 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; } } bind() { this.domObserver = DOM.createMutationObserver(() => { this.synchronizeOptions(); this.synchronizeValue(); }); this.domObserver.observe(this.element, { childList: true, subtree: true }); } unbind() { this.domObserver.disconnect(); this.domObserver = null; if (this.arrayObserver) { this.arrayObserver.unsubscribe(selectArrayContext, this); this.arrayObserver = null; } } }