UNPKG

mframejs

Version:
542 lines (438 loc) 21.3 kB
import { customAttribute } from '../decorator/exported'; import { IAttribute, IBindingContext } from '../interface/exported'; import { BindingEngine } from '../binding/exported'; import { IListener } from '../interface/exported'; import { Cache, connectBehavior } from '../utils/exported'; // helper class for property changes export class PropertyChangeHandler implements IListener { private firstRun = true; public name = 'valueAttribute'; public caller: any; public value: any; constructor( public node: HTMLInputElement ) { } public call(newValue: any, oldValue: any) { if (oldValue !== newValue || this.firstRun) { this.value = newValue; switch (true) { /** * normal text input logic * */ case this.node.type === 'textarea': case (this.node.type !== 'checkbox' && this.node.type !== 'radio' && this.node.type !== 'select-multiple' && this.node.type !== 'select-one'): this.node.value = newValue || ''; break; /** * checkbox logic * */ case this.node.type === 'checkbox': const modelBind = this.node.hasAttribute('model.bind') ? this.node.getAttributeNode('model.bind') : null; if (modelBind && (modelBind as any).getContent) { const x = (modelBind as any).getContent(); if (x) { if (Array.isArray(newValue)) { if (newValue.indexOf(x.value) !== -1) { this.node.checked = true; } else { this.node.checked = false; } } else { if (newValue === x.value) { this.node.checked = true; } else { this.node.checked = false; } } } } else { if (Array.isArray(newValue)) { console.warn('error: value is array, this should not happen'); } else { this.node.checked = newValue || false; } } break; /** * radio group logic * */ case this.node.type === 'radio': if (this.node) { const modelBind = this.node.hasAttribute('model.bind') ? this.node.getAttributeNode('model.bind') : null; if (modelBind && (modelBind as any).getContent) { const x = (modelBind as any).getContent(); if (x) { if (newValue === x.value) { this.node.checked = true; } } } } break; /** * select with option group *single select * */ case this.node.type === 'select-one': const optionsSingle = (this.node as any).options; let index = null; for (let i = 0; i < optionsSingle.length; i++) { const modelBind = optionsSingle[i].hasAttribute('model.bind') ? optionsSingle[i].getAttributeNode('model.bind') : null; if (modelBind && (modelBind as any).getContent) { const x = (modelBind as any).getContent(); if (x) { if (Array.isArray(newValue)) { if (newValue[0] === x.value) { index = i; } } else { if (newValue === x.value) { index = i; } } } } } (this.node as any).selectedIndex = index; break; /** * select with option group *multi select * */ case this.node.type === 'select-multiple': const optionsMany = (this.node as any).options; for (let i = 0; i < optionsMany.length; i++) { const modelBind = optionsMany[i].hasAttribute('model.bind') ? optionsMany[i].getAttributeNode('model.bind') : null; if (modelBind && (modelBind as any).getContent) { const x = (modelBind as any).getContent(); if (x) { if (Array.isArray(newValue)) { if (newValue.indexOf(x.value) !== -1) { optionsMany[i].selected = true; } else { optionsMany[i].selected = false; } if (newValue.length === 0 && x.value === null) { optionsMany[i].selected = true; } } else { if (newValue === x.value) { optionsMany[i].selected = true; } else { optionsMany[i].selected = false; } } } } } break; } this.firstRun = false; } } } @customAttribute('value.bind') @customAttribute('checked.bind') export class ValueAttribute implements IAttribute { // build in properties added by our template engine public $element: HTMLInputElement; public $attribute: Attr; public $bindingContext: IBindingContext; public name: string; public attributeName: string; public trigger: string[]; public valueConverters: string[] = []; public eventHandlerBinded: any; // private vars private expressionValue: string; private propertyChangeHandler: PropertyChangeHandler; constructor() { // our listener this.eventHandlerBinded = this.eventHandler.bind(this); // nothing atm } public attached() { if (this.$element.type === 'select-multiple') { // model is not ready this.propertyChangeHandler.call(this.propertyChangeHandler.value, 'somethign % its not'); } } /** * created * */ public created() { this.expressionValue = (this.$attribute as any).value; this.attributeName = this.expressionValue.split('|')[0].trim().split('&')[0].trim(); this.trigger = null; // ok, connectBehavior set this this.name = (this.$attribute as any).name; this.propertyChangeHandler = new PropertyChangeHandler(this.$element); // check type is normal input switch (true) { /** * normal text input logic * */ case (this.$element.tagName === 'INPUT' && this.$element.type !== 'checkbox' && this.$element.type !== 'radio' && this.$element.type !== 'select-multiple' && this.$element.type !== 'select-one') || this.$element.tagName === 'TEXTAREA': BindingEngine.subscribeClassProperty(this.$bindingContext, this.expressionValue, this.propertyChangeHandler); connectBehavior(this.expressionValue, this); if (!this.trigger) { (this.$element as any).addEventListener('input', (this.eventHandlerBinded as EventListenerOrEventListenerObject), false); } else { this.trigger.forEach((trigger) => { (this.$element as any).addEventListener(trigger, (this.eventHandlerBinded as EventListenerOrEventListenerObject), false); }); } break; /** * checkbox logic * */ case this.$element.tagName === 'INPUT' && this.$element.type === 'checkbox': BindingEngine.subscribeClassProperty(this.$bindingContext, this.expressionValue, this.propertyChangeHandler); connectBehavior(this.expressionValue, this); if (!this.trigger) { (this.$element as any).addEventListener('click', (this.eventHandlerBinded as EventListenerOrEventListenerObject), false); } else { this.trigger.forEach((trigger) => { (this.$element as any).addEventListener(trigger, (this.eventHandlerBinded as EventListenerOrEventListenerObject), false); }); } break; /** * radio group logic * */ case this.$element.tagName === 'INPUT' && this.$element.type === 'radio': BindingEngine.subscribeClassProperty(this.$bindingContext, this.expressionValue, this.propertyChangeHandler); connectBehavior(this.expressionValue, this); if (!this.trigger) { (this.$element as any).addEventListener('click', (this.eventHandlerBinded as EventListenerOrEventListenerObject), false); } else { this.trigger.forEach((trigger) => { (this.$element as any).addEventListener(trigger, (this.eventHandlerBinded as EventListenerOrEventListenerObject), false); }); } break; /** * select with option group * */ case this.$element.tagName === 'SELECT': BindingEngine.subscribeClassProperty(this.$bindingContext, this.expressionValue, this.propertyChangeHandler); connectBehavior(this.expressionValue, this); if (!this.trigger) { (this.$element as any).addEventListener('change', (this.eventHandlerBinded as EventListenerOrEventListenerObject), false); } else { this.trigger.forEach((trigger) => { (this.$element as any).addEventListener(trigger, (this.eventHandlerBinded as EventListenerOrEventListenerObject), false); }); } break; default: // nothing } } // being able to use my ast would have been best. public runValueConverter(value: any) { const x = Cache.expressionMap.get(this.expressionValue); return BindingEngine.valueConvert((<any>x).ast, this.$bindingContext, value); } /** * detached * */ public detached() { switch (true) { /** * normal text input logic * */ case (this.$element.tagName === 'INPUT' && this.$element.type !== 'checkbox' && this.$element.type !== 'radio' && this.$element.type !== 'select-multiple' && this.$element.type !== 'select-one') || this.$element.tagName === 'TEXTAREA': if (!this.trigger) { this.$element.removeEventListener('input', this.eventHandlerBinded); } else { this.trigger.forEach((trigger) => { this.$element.removeEventListener(trigger, this.eventHandlerBinded); }); } break; /** * checkbox logic * */ case this.$element.tagName === 'INPUT' && this.$element.type === 'checkbox': if (!this.trigger) { this.$element.removeEventListener('click', this.eventHandlerBinded); } else { this.trigger.forEach((trigger) => { this.$element.removeEventListener(trigger, this.eventHandlerBinded); }); } break; /** * radio group logic * */ case this.$element.tagName === 'INPUT' && this.$element.type === 'radio': if (!this.trigger) { this.$element.removeEventListener('click', this.eventHandlerBinded); } else { this.trigger.forEach((trigger) => { this.$element.removeEventListener(trigger, this.eventHandlerBinded); }); } break; /** * select with option group * */ case this.$element.tagName === 'SELECT': if (!this.trigger) { this.$element.removeEventListener('change', this.eventHandlerBinded); } else { this.trigger.forEach((trigger) => { this.$element.removeEventListener(trigger, this.eventHandlerBinded); }); } break; default: // just in case (this.$attribute as any).$bindingContext = null; break; } if (this.propertyChangeHandler.caller) { BindingEngine.unSubscribeClassProperty(this.$bindingContext, this.propertyChangeHandler); } } /** * event handler * */ public eventHandler() { switch (true) { /** * normal text input logic * */ case (this.$element.tagName === 'INPUT' && this.$element.type !== 'checkbox' && this.$element.type !== 'radio' && this.$element.type !== 'select-multiple' && this.$element.type !== 'select-one') || this.$element.tagName === 'TEXTAREA': let value: any = (this.$element as HTMLInputElement).value; if (this.$element.type === 'number') { value = (this.$element as HTMLInputElement).valueAsNumber; } if (this.$element.type === 'date') { value = (this.$element as HTMLInputElement).valueAsDate; } BindingEngine.setValue(this.$bindingContext, this.attributeName, this.runValueConverter(value)); break; /** * checkbox logic * */ case this.$element.tagName === 'INPUT' && this.$element.type === 'checkbox': const modelBindCheckbox = (this.$element as any).hasAttribute('model.bind') ? (this.$element as any).getAttributeNode('model.bind') : null; if (modelBindCheckbox) { // if we have model bind then it is a group const value = modelBindCheckbox.getContent().value; let propertyType = BindingEngine.evaluateExpression(this.$attribute.nodeValue, this.$bindingContext); const isArray = Array.isArray(propertyType); const selected = this.$element.checked; if (isArray) { // if binded variable is array if (selected) { const ii = propertyType.indexOf(value); if (ii === -1) { propertyType = [...propertyType, this.runValueConverter(value)]; BindingEngine.setValue(this.$bindingContext, this.attributeName, propertyType); } } else { const ii = propertyType.indexOf(value); if (ii !== -1) { propertyType.splice(ii, 1); if (!propertyType.__array_observer__class) { propertyType = [...propertyType]; } BindingEngine.setValue(this.$bindingContext, this.attributeName, propertyType); } else { this.$element.checked = false; } } } else { // no array, just set value BindingEngine.setValue(this.$bindingContext, this.attributeName, this.runValueConverter(value) || false); } } else { // not group, just set value BindingEngine.setValue(this.$bindingContext, this.attributeName, (this.$element as HTMLInputElement).checked || false); } break; /** * radio group logic * */ case this.$element.tagName === 'INPUT' && this.$element.type === 'radio': const modelBindRadio = (this.$element as any).hasAttribute('model.bind') ? (this.$element as any).getAttributeNode('model.bind') : null; if (modelBindRadio) { const value = BindingEngine.evaluateExpression(modelBindRadio.nodeValue, modelBindRadio.getContent().$bindingContext); BindingEngine.setValue(this.$bindingContext, this.attributeName, this.runValueConverter(value)); } else { BindingEngine.setValue(this.$bindingContext, this.attributeName, this.runValueConverter((this.$element as HTMLInputElement).checked)); } break; /** * select with option group * */ case this.$element.tagName === 'SELECT': const propertyType = BindingEngine.evaluateExpression(this.$attribute.nodeValue, this.$bindingContext); const isArray = Array.isArray(propertyType); const length = (this.$element as any).selectedOptions.length; let finalValue = null; if (length > 1) { const values = []; for (let i = 0; i < length; i++) { const x = (this.$element as any).selectedOptions[i]; const modelBind = x.hasAttribute('model.bind') ? x.getAttributeNode('model.bind') : null; const value = BindingEngine.evaluateExpression(modelBind.nodeValue, modelBind.getContent().$bindingContext); values.push(this.runValueConverter(value)); } finalValue = values; } else { if (length === 0) { finalValue = null; } else { const x = (this.$element as any).selectedOptions[0]; const modelBind = x.hasAttribute('model.bind') ? x.getAttributeNode('model.bind') : null; const value = BindingEngine.evaluateExpression(modelBind.nodeValue, modelBind.getContent().$bindingContext); finalValue = value; } } if (isArray) { BindingEngine.setValue(this.$bindingContext, this.attributeName, Array.isArray(finalValue) ? finalValue : finalValue === null ? [] : [finalValue]); } else { BindingEngine.setValue(this.$bindingContext, this.attributeName, Array.isArray(finalValue) ? finalValue.map(a => a) : finalValue); } break; } } }