mframejs
Version:
simple framework
261 lines (205 loc) • 11 kB
text/typescript
import { subscribeClassProperty } from './property/subscribeClassProperty';
import { PropertyObserverHandler } from './property/propertyObserverHandler';
import { BindingEngine } from './bindingEngine';
import {
IAttribute,
CONSTANTS,
IElement,
IListener
} from '../interface/exported';
import { createBindingContext } from './createBindingContext';
/**
* callbacks changes in class if chnage method is added
*
*/
const subscribeChangeBaseClass = class implements IListener {
private key: string;
private _class: IElement | IAttribute;
private meta: any;
public caller: PropertyObserverHandler;
constructor(_class: IElement | IAttribute, key: string, meta: any) {
this._class = _class;
this.key = key;
this.meta = meta;
}
/**
* called on changes
*
*/
public call(newValue: any, oldValue: any) {
if (oldValue !== newValue) {
const key = this.key;
const _class = this._class;
const META = this.meta;
if (_class.$bindingContext && key in _class.$bindingContext.$context) { // I should call this before?
if (_class.$bindingContext.$context[key] !== newValue) {
_class.$bindingContext.$context[key] = newValue;
}
}
if (_class[`${key}Changed`]) {
_class[`${key}Changed`](newValue, oldValue);
}
if (_class[`attributesChanged`]) { // usefull so we dont need a handler for each
_class[`attributesChanged`](key, newValue, oldValue);
}
if (META[key].options.changeHandler) {
_class[META[key].options.changeHandler](key, newValue, oldValue);
}
}
}
};
/**
* creates a observer if bindable meta data is added
*
*/
export function subscribeClassMetaBinding(_class: IElement | IAttribute) {
const META = (_class as any).__proto__[CONSTANTS.META_BINDABLE];
if (META) {
const keys = Object.keys(META);
for (const key of keys) {
if (!(_class as any).__metaBinding) {
(_class as any).__metaBinding = {};
}
if (!(_class as any).__metaBinding[key]) {
(_class as any).__metaBinding[key] = {};
(_class as any).__metaBinding[key].key = key;
(_class as any).__metaBinding[key].options = {};
}
const CLASSMETA = (_class as any).__metaBinding[key];
const subscribeInternal = new subscribeChangeBaseClass(_class, key, META);
subscribeClassProperty(createBindingContext(_class), key, subscribeInternal);
// save it for later so we can unsubscribe
CLASSMETA.options.subscribeInternal = subscribeInternal;
if (META[key].observableOnly === true) {
// nothing atm
} else {
if ((_class as IElement).$attributes && !(_class as IAttribute).$attribute) {
// custom element
// we now check for if attribute have bind, else we just give the value
if (_class.$element && _class.$bindingContext) {
// need to make sure there is parent and element
const el = _class.$element as Element;
let att = `${META[key].options.attribute}.bind`;
let attrValue = el.getAttribute(att);
if (attrValue) {
const subscribeExternal: IListener = Object.create({
call: (newValue: any, oldValue: any) => {
if (oldValue !== newValue) {
_class[key] = newValue;
}
}
});
subscribeClassProperty(_class.$bindingContext, attrValue, subscribeExternal);
// save it for later so we can unsubscribe
CLASSMETA.options.subscribeExternal = subscribeExternal;
} else {
// just set value, no bind
att = `${META[key].options.attribute}`;
attrValue = el.getAttribute(att);
if (attrValue) {
if (attrValue.indexOf('${') !== -1 || attrValue.indexOf('@{') !== -1) {
const val = BindingEngine.tokenizeParseAndTraverseAST(attrValue, _class.$bindingContext);
if (val) {
_class[key] = val;
}
} else {
_class[key] = attrValue;
}
}
}
}
} else {
// custom attribute
// - > need to extract values, they can be many in 1
if (_class.$element && _class.$bindingContext) {
// many or 1 check, if just value we only want that
if (keys.length === 1 && key === 'value') {
// only 1 key and its value, then we only use this
const el = _class.$element as Element;
const att = (_class as IAttribute).$attribute.name;
const haveBind = att.indexOf('.bind');
let attrValue = el.getAttribute(att);
if (haveBind !== -1) {
// binding
const subscribeExternal: IListener = Object.create({
call: (newValue: any, oldValue: any) => {
if (oldValue !== newValue) {
_class[key] = newValue;
}
}
});
subscribeClassProperty(_class.$bindingContext, attrValue, subscribeExternal);
// save it for later so we can unsubscribe
CLASSMETA.options.subscribeExternal = subscribeExternal;
} else {
// no bind, just get value
attrValue = el.getAttribute(att);
if (attrValue) {
if (attrValue.indexOf('${') !== -1 || attrValue.indexOf('@{') !== -1) {
const val = BindingEngine.tokenizeParseAndTraverseAST(attrValue, _class.$bindingContext);
if (val) {
_class[key] = val;
}
} else {
_class[key] = attrValue;
}
}
}
} else {
// not just value and 1 @bindable
const el = _class.$element as Element;
const attributeName = (_class as IAttribute).$attribute.name;
let att = `${META[key].options.attribute}.bind`;
const attrValues: any[] = el.getAttribute(attributeName) ? el.getAttribute(attributeName).split(';') : null;
// do we have many values inside?
if (attrValues) {
// yes, lets get them and check for key/att
// atm checking for .bind
let attrValue: string;
attrValues.forEach((value: any) => {
const test = value.split(':');
if (test && test[0].trim() === att) {
attrValue = test[1];
}
});
if (attrValue) {
const subscribeExternal: IListener = Object.create({
call: (newValue: any, oldValue: any) => {
if (oldValue !== newValue) {
_class[key] = newValue;
}
}
});
subscribeClassProperty(_class.$bindingContext, attrValue, subscribeExternal);
// save it for later so we can unsubscribe
CLASSMETA.options.subscribeExternal = subscribeExternal;
} else {
// no .bind
// just set value if it is there
att = `${META[key].options.attribute}`;
let attrValue: string;
attrValues.forEach((value: any) => {
const test = value.split(':');
if (test && test[0].trim() === att) {
attrValue = test[1];
}
});
if (attrValue) {
if (attrValue.indexOf('${') !== -1 || attrValue.indexOf('@{') !== -1) {
const val = BindingEngine.tokenizeParseAndTraverseAST(attrValue, _class.$bindingContext);
if (val) {
_class[key] = val;
}
} else {
_class[key] = attrValue;
}
}
}
}
}
}
}
}
}
}
}