mframejs
Version:
simple framework
542 lines (438 loc) • 21.3 kB
text/typescript
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;
}
}
}
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;
}
}
}