2019-11-26 17:15:12 +00:00
|
|
|
// @ts-check
|
|
|
|
|
2019-11-26 20:05:41 +00:00
|
|
|
import { Observable, Computed } from "./observable.js"
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Class supports data-binding operations
|
|
|
|
*/
|
2019-11-26 17:15:12 +00:00
|
|
|
export class DataBinding {
|
2019-11-26 20:05:41 +00:00
|
|
|
|
2019-11-26 17:15:12 +00:00
|
|
|
/**
|
|
|
|
* Simple evaluation
|
|
|
|
* @param {string} js The JavaScript to evaluate
|
|
|
|
*/
|
|
|
|
execute(js) {
|
|
|
|
return eval(js);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Evaluates JavaScript with a constrained context (scope)
|
|
|
|
* @param {string} src The JavaScript to evaluate
|
|
|
|
* @param {object} context The context (data) to evaluate with
|
|
|
|
* @returns {object} The result of the evaluation
|
|
|
|
*/
|
2019-11-26 20:05:41 +00:00
|
|
|
executeInContext(src, context, attachBindingHelpers = false) {
|
|
|
|
if (attachBindingHelpers) {
|
|
|
|
context.observable = this.observable;
|
|
|
|
context.computed = this.computed;
|
|
|
|
context.bindValue = this.bindValue;
|
|
|
|
}
|
2019-11-26 17:15:12 +00:00
|
|
|
return this.execute.call(context, src);
|
|
|
|
}
|
|
|
|
|
2019-11-26 20:05:41 +00:00
|
|
|
/**
|
|
|
|
* A simple observable implementation
|
|
|
|
* @param {object} value Any value to observe
|
|
|
|
* @returns {Observable} The observable instance to use
|
|
|
|
*/
|
|
|
|
observable(value) {
|
|
|
|
return new Observable(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates an observed computed property
|
|
|
|
* @param {function} calculation Calculated value
|
|
|
|
* @param {Observable[]} deps The list of dependent observables
|
|
|
|
* @returns {Computed} The observable computed value
|
|
|
|
*/
|
|
|
|
computed(calculation, deps) {
|
|
|
|
return new Computed(calculation, deps);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Binds an input element to an observable value
|
|
|
|
* @param {HTMLInputElement} input The element to bind to
|
|
|
|
* @param {Observable} observable The observable instance to bind to
|
|
|
|
*/
|
|
|
|
bindValue(input, observable) {
|
|
|
|
let initialValue = observable.value;
|
|
|
|
input.value = initialValue;
|
|
|
|
observable.subscribe(() => input.value = observable.value);
|
|
|
|
/**
|
|
|
|
* Converts the values
|
|
|
|
* @param {object} value
|
|
|
|
*/
|
|
|
|
let converter = value => value;
|
|
|
|
if (typeof initialValue === "number") {
|
|
|
|
converter = num => isNaN(num = parseFloat(num)) ? 0 : num;
|
|
|
|
}
|
|
|
|
input.onkeyup = () => {
|
|
|
|
observable.value = converter(input.value);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-11-26 17:15:12 +00:00
|
|
|
/**
|
|
|
|
* Searches for "repeat" attribute to data-bind lists
|
|
|
|
* @param {HTMLElement} elem The parent element to search
|
|
|
|
* @param {object} context The context to use for binding
|
|
|
|
*/
|
|
|
|
bindLists(elem, context) {
|
|
|
|
const listBinding = elem.querySelectorAll("[repeat]");
|
|
|
|
listBinding.forEach(elem => {
|
|
|
|
const parent = elem.parentElement;
|
|
|
|
const expression = elem.getAttribute("repeat");
|
|
|
|
elem.removeAttribute("repeat");
|
|
|
|
const template = elem.outerHTML;
|
|
|
|
parent.removeChild(elem);
|
|
|
|
context[expression].forEach(item => {
|
|
|
|
let newTemplate = `${template}`;
|
|
|
|
const matches = newTemplate.match(/\{\{([^\}]*?)\}\}/g);
|
|
|
|
if (matches) {
|
|
|
|
matches.forEach(match => {
|
|
|
|
match = match.replace("{{", "").replace("}}", "");
|
2019-11-26 20:05:41 +00:00
|
|
|
const value = this.executeInContext(`this.${match}`, { item });
|
|
|
|
newTemplate = newTemplate.replace(`{{${match}}}`, value);
|
2019-11-26 17:15:12 +00:00
|
|
|
});
|
|
|
|
parent.innerHTML += newTemplate;
|
2019-11-26 20:05:41 +00:00
|
|
|
}
|
2019-11-26 17:15:12 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2019-11-26 20:05:41 +00:00
|
|
|
}
|
|
|
|
|