smart-webcomponents-angular
Version:
[](https://jqwidgets.com/license/)
1,847 lines (1,436 loc) • 50.4 kB
JavaScript
/* Smart UI v20.0.2 (2024-09-06)
Copyright (c) 2011-2024 jQWidgets.
License: https://htmlelements.com/license/ */ //
const namespace = 'Smart';
class BindingModule {
static get moduleName() {
return 'BindingModule';
}
/**
* @typedef {Object} bindings
* @property {Array<Node>} children The child nodes.
* @property {Node} node The node.
* @property {BindingData} data The node's binding data.
*/
/**
* @typedef {Object} BindingData
* @property {Boolean} twoWay - Deterimes whether it's one way or two way data binding.
* @property {Boolean} updating - Determines whether the node is in update state.
* @property {Object} value - The bound property's value.
* @property {String} name - The bound property's name.
*/
getBindings(node, ownerMap) {
const that = this;
let index = 0;
let map = {
};
let boundData = (node => {
if (node instanceof HTMLElement) {
return that.parseAttributes(node);
}
else {
let boundProperty = that.parseProperty(node.data ? node.data.trim() : null, 'textContent', node);
if (boundProperty) {
if (that.ownerElement && node.parentNode === that.ownerElement.$.content) {
boundProperty.value = that.ownerElement.$.html !== '' ? that.ownerElement.$.html : undefined;
that.ownerElement.innerHTML = '';
}
return {
'textContent': boundProperty
};
}
}
return undefined;
})(node);
if (boundData) {
map.data = boundData;
}
if (!ownerMap) {
map.mapping = [];
ownerMap = map;
}
if (node.getAttribute) {
map.nodeId = node.getAttribute('smart-id');
if (ownerMap && boundData) {
ownerMap.mapping[map.nodeId] = boundData;
}
}
map.node = node;
if (node.firstChild) {
map.children = {
};
}
for (let child = node.firstChild; child; child = child.nextSibling) {
map.children[index++] = that.getBindings(child, ownerMap);
}
return map;
}
_addRemovePropertyBinding(hostPropertyName, targetPropertyName, targetElement, removeBinding, parentElement) {
if (!hostPropertyName || !targetPropertyName || !targetElement) {
return;
}
const that = this;
const bindings = that.ownerElement.bindings;
const id = targetElement.getAttribute('smart-id');
const twoWayBinding = hostPropertyName.indexOf('{{') >= 0;
hostPropertyName = hostPropertyName.replace('{{', '').replace('}}', '').replace('[[', '').replace(']]', '');
let not = false;
if (hostPropertyName.indexOf('!') >= 0) {
hostPropertyName = hostPropertyName.replace('!', '');
not = true;
}
const hostProperty = that.ownerElement._properties[hostPropertyName];
const boundProperty = {
name: hostPropertyName, reflectToAttribute: hostProperty.reflectToAttribute, twoWay: twoWayBinding, type: hostProperty.type, not: not
};
if (parentElement && !removeBinding) {
const map = {};
const targetBoundProperty = {
name: hostPropertyName, targetPropertyName: targetPropertyName, reflectToAttribute: hostProperty.reflectToAttribute, twoWay: twoWayBinding, type: hostProperty.type, not: not
};
map[hostPropertyName] = targetBoundProperty;
bindings.mapping[id] = map;
}
const setBinding = function (boundChildren) {
for (let childIndex in boundChildren) {
const child = boundChildren[childIndex];
if (child.nodeId === id) {
if (!child.data) {
child.data = {
};
}
if (removeBinding) {
child.data[targetPropertyName] = null;
delete child.data[targetPropertyName];
}
else {
child.data[targetPropertyName] = boundProperty;
}
break;
}
if (child.children) {
setBinding(child.children);
}
else if (child.node && child.node.children && child.node === targetElement.parentElement) {
const node = child.node;
if (node.firstChild) {
child.children = {
};
}
else {
continue;
}
let index = 0;
for (let currentChild = node.firstChild; currentChild; currentChild = currentChild.nextSibling) {
child.children[index++] = that.getBindings(currentChild);
}
setBinding(child.children);
}
}
}
setBinding(bindings.children);
if (!removeBinding) {
that.ownerElement.boundProperties[hostPropertyName] = true;
}
else {
delete that.ownerElement.boundProperties[hostPropertyName];
}
that.updateBoundNodes(hostPropertyName);
}
addPropertyBinding(hostPropertyName, targetPropertyName, targetElement, parentElement) {
const that = this;
that._addRemovePropertyBinding(hostPropertyName, targetPropertyName, targetElement, false, parentElement);
}
removePropertyBinding(hostPropertyName, targetPropertyName, targetElement, parentElement) {
const that = this;
that._addRemovePropertyBinding(hostPropertyName, targetPropertyName, targetElement, true, parentElement);
}
/**
* Parses the element's attributes.
* @param {HTMLElement} - html element.
* @return {Array<BindingData>}
*/
parseAttributes(htmlElement) {
const that = this;
let boundProperties = undefined;
for (let i = 0; i < htmlElement.attributes.length; i++) {
const attribute = htmlElement.attributes[i];
const attributeName = attribute.name;
const attributeValue = attribute.value;
if (!BindingModule.cache['toCamelCase' + attributeName]) {
BindingModule.cache['toCamelCase' + attributeName] = window[namespace].Utilities.Core.toCamelCase(attributeName);
}
const propertyName = BindingModule.cache['toCamelCase' + attributeName];
if (attributeName.indexOf('(') >= 0) {
let eventName = attributeName.substring(1, attributeName.length - 1);
if (that.ownerElement && !that.ownerElement.dataContext) {
that.ownerElement.templateListeners[htmlElement.getAttribute('smart-id') + '.' + eventName] = attributeValue;
htmlElement.removeAttribute(attributeName);
continue;
}
else {
if (!boundProperties) {
boundProperties = {
};
}
const handlerName = attributeValue.substring(0, attributeValue.indexOf('('));
boundProperties[propertyName] = {
isEvent: true, name: eventName, value: handlerName
};
continue;
}
}
let boundProperty = that.parseProperty(attributeValue, attributeName, htmlElement);
if (!boundProperty) {
continue;
}
if (!boundProperties) {
boundProperties = {
};
}
boundProperties[propertyName] = boundProperty;
}
return boundProperties;
}
/**
* Parses a property.
* @param {String} - The string to parse.
* @param {name} - property's name.
* @param {Node} - the node.
* @return {BindingData}
*/
parseProperty(text, elementAttributeName/*, name, node*/) {
if (!text || !text.length)
return;
const that = this;
let boundProperty;
let length = text.length;
let startIndex = 0, lastIndex = 0, endIndex = 0;
let twoWay = true;
while (lastIndex < length) {
startIndex = text.indexOf('{{', lastIndex);
let twoWayStart = text.indexOf('[[', lastIndex);
let terminator = '}}';
if (twoWayStart >= 0 &&
(startIndex < 0 || twoWayStart < startIndex)) {
startIndex = twoWayStart;
twoWay = false;
terminator = ']]';
}
endIndex = startIndex < 0 ? -1 : text.indexOf(terminator, startIndex + 2);
if (endIndex < 0) {
return;
}
boundProperty = boundProperty || {
};
let pathString = text.slice(startIndex + 2, endIndex).trim();
let attributeName = pathString;
/* if (twoWay) {
const updateToken = function (value) {
boundProperty.value = value;
if (node.$ && node.$.isNativeElement) {
if (!BindingModule.cache['toDash' + name]) {
BindingModule.cache['toDash' + name] = Utilities.Core.toDash(name);
}
const attributeName = BindingModule.cache['toDash' + name];
const oldValue = node.$.getAttributeValue(attributeName, boundProperty.type);
if (oldValue !== boundProperty.value) {
node.$.setAttributeValue(attributeName, boundProperty.value, boundProperty.type);
}
}
}
if (pathString.indexOf('::') >= 0) {
const eventIndex = pathString.indexOf('::');
const eventName = pathString.substring(eventIndex + 2);
that.ownerElement['$' + node.getAttribute('smart-id')].listen(eventName, function () {
updateToken(node[name]);
const boundPropertyName = boundProperty.name.substring(0, boundProperty.name.indexOf('::'));
that.updateBoundProperty(boundPropertyName, boundProperty);
});
}
if (node.$ && node.$.isCustomElement) {
if (!BindingModule.cache['toDash' + name]) {
BindingModule.cache['toDash' + name] = Utilities.Core.toDash(name);
}
const attributeName = BindingModule.cache['toDash' + name];
const propertyName = Utilities.Core.toCamelCase(attributeName);
if (node._properties && node._properties[propertyName]) {
node._properties[propertyName].notify = true;
}
that.ownerElement['$' + node.getAttribute('smart-id')].listen(attributeName + '-changed', function (event) {
const detail = event.detail;
updateToken(detail.value);
const context = that.ownerElement.context;
if (event.context !== document) {
that.ownerElement.context = that.ownerElement;
}
that.updateBoundProperty(name, boundProperty);
that.ownerElement.context = context;
});
}
}*/
boundProperty.name = attributeName;
lastIndex = endIndex + 2;
}
const propertyName = boundProperty.name;
const elementProperty = that.ownerElement ? that.ownerElement._properties[propertyName] : null;
boundProperty.twoWay = twoWay;
boundProperty.ready = false;
if (that.ownerElement) {
if (propertyName.indexOf('::') >= 0) {
that.ownerElement.boundProperties[propertyName.substring(0, propertyName.indexOf('::'))] = true;
}
else {
that.ownerElement.boundProperties[propertyName] = true;
}
}
if (elementProperty) {
boundProperty.type = elementProperty.type;
boundProperty.reflectToAttribute = elementProperty.reflectToAttribute;
}
else {
const booleans = ['checked', 'selected', 'async', 'autofocus', 'autoplay', 'controls', 'defer', 'disabled', 'hidden', 'ismap', 'loop', 'multiple', 'open', 'readonly', 'required', 'scoped'];
if (booleans.indexOf(elementAttributeName) >= 0) {
boundProperty.type = 'boolean';
}
else {
boundProperty.type = 'string';
}
boundProperty.reflectToAttribute = true;
}
return boundProperty;
}
/**
* Updates element's data bound nodes.
*/
updateTextNodes() {
const that = this;
that.updateTextNode(that.ownerElement.shadowRoot || that.ownerElement, that.ownerElement.bindings, that.ownerElement);
}
/**
* Updates a data bound node.
* @param {Node} - The bound node.
* @param {Array<BindingData>} - The node's binding data.
* @param {Element} - The element to be updated.
*/
updateTextNode(node, bindings, element) {
const that = this;
if (!bindings) {
return;
}
let index = 0;
for (let child = node.firstChild; child; child = child.nextSibling) {
if (!bindings.children) {
break;
}
that.updateTextNode(child, bindings.children[index++], element);
}
if (!bindings || !bindings.data) {
return;
}
for (let name in bindings.data) {
const boundProperty = bindings.data[name];
const boundPropertyName = boundProperty.name;
if (name !== 'textContent' || !boundProperty.twoWay || boundProperty.updating || boundProperty.value === undefined) {
continue;
}
element[boundPropertyName] = boundProperty.value;
}
}
/**
* Updates a data bound property.
* @param {String} - The propery's name.
* @param {Object} - The property's value.
*/
updateBoundProperty(propertyName, propertyConfig) {
if (propertyConfig.updating) {
return;
}
const that = this;
const element = that.ownerElement;
propertyConfig.updating = true;
element[propertyName] = propertyConfig.value;
propertyConfig.updating = false;
}
/**
* Updates element's data bound nodes.
*/
updateBoundNodes(propertyName) {
const that = this;
that.updateBoundNode(that.ownerElement.shadowRoot || that.ownerElement, that.ownerElement.bindings, that.ownerElement, propertyName);
if (that.ownerElement.detachedChildren.length > 0) {
for (let i = 0; i < that.ownerElement.detachedChildren.length; i++) {
const node = that.ownerElement.detachedChildren[i];
const smartId = node.getAttribute('smart-id');
const getBindings = function (bindings) {
if (bindings.nodeId === smartId) {
return bindings;
}
for (let index in bindings.children) {
const node = bindings.children[index];
const attribute = node.getAttribute ? node.getAttribute('smart-id') : '';
if (attribute === smartId) {
return bindings;
}
if (node.children) {
const result = getBindings(node);
if (result) {
return result;
}
}
}
return null;
}
const bindings = getBindings(that.ownerElement.bindings);
if (bindings) {
that.updateBoundNode(node, bindings, that.ownerElement, propertyName, true);
}
else {
if (node.getAttribute && that.ownerElement.bindings.mapping) {
const element = that.ownerElement;
const bindings = that.ownerElement.bindings;
if (bindings) {
for (let mapping in bindings.mapping) {
const childNode = element.querySelector('[smart-id="' + mapping + '"]');
if (childNode) {
const dataBoundProperties = bindings.mapping[mapping];
that.updateBoundData(childNode, dataBoundProperties, element, propertyName);
}
}
}
}
}
}
}
}
updateBoundMappedNodes() {
const that = this;
//const node = that.ownerElement.shadowRoot || that.ownerElement;
const bindings = that.ownerElement.bindings;
const element = that.ownerElement;
if (!bindings.mapping) {
return;
}
for (let mapping in bindings.mapping) {
let childNode = element.querySelector('[smart-id="' + mapping + '"]');
if (element.shadowRoot) {
childNode = element.querySelector('[id="' + mapping + '"]');
if (!childNode) {
childNode = element.shadowRoot.querySelector('[id="' + mapping + '"]') || element.shadowRoot.querySelector('[smart-id="' + mapping + '"]');
}
}
if (childNode) {
const dataBoundProperties = bindings.mapping[mapping];
that.updateBoundData(childNode, dataBoundProperties, element);
}
else if (element.getAttribute('aria-controls')) {
let detachedChildNode = document.getElementById(element.getAttribute('aria-controls'));
if (!detachedChildNode && element.shadowRoot) {
detachedChildNode = element.shadowRoot.getElementById(element.getAttribute('aria-controls'));
}
childNode = detachedChildNode.querySelector('[smart-id="' + mapping + '"]');
if (childNode) {
const dataBoundProperties = bindings.mapping[mapping];
that.updateBoundData(childNode, dataBoundProperties, element);
}
}
}
}
/**
* Updates a data bound node.
* @param {Node} - The bound node.
* @param {Array<BindingData>} - The node's binding data.
* @param {Element} - The element to be updated.
*/
updateBoundNode(node, bindings, element, propertyName, detached) {
const that = this;
if (!bindings) {
return;
}
/*
if (node.getAttribute && bindings.mapping) {
for (let mapping in bindings.mapping) {
const childNode = element.querySelector('[smart-id="' + mapping + '"]');
if (childNode) {
const dataBoundProperties = bindings.mapping[mapping];
that.updateBoundData(childNode, dataBoundProperties, element, null);
}
}
return;
}
*/
let index = 0;
if (!detached) {
for (let child = node.firstChild; child; child = child.nextSibling) {
if (!bindings.children) {
break;
}
// that.updateBoundNode(child, bindings.children[index++], element, propertyName);
if (child.getAttribute) {
const childId = child.getAttribute('smart-id');
const childBindings = function () {
for (let binding in bindings.children) {
if (bindings.children[binding].nodeId === childId) {
return bindings.children[binding];
}
}
}();
that.updateBoundNode(child, childBindings, element, propertyName);
index++;
}
else {
that.updateBoundNode(child, bindings.children[index++], element, propertyName);
}
}
}
else if (detached && !bindings.data) {
for (let child = node.firstChild; child; child = child.nextSibling) {
if (!bindings.children) {
break;
}
// that.updateBoundNode(child, bindings.children[index++], element, propertyName, detached);
if (child.getAttribute) {
const childId = child.getAttribute('smart-id');
const childBindings = function () {
for (let binding in bindings.children) {
if (bindings.children[binding].nodeId === childId) {
return bindings.children[binding];
}
}
}();
that.updateBoundNode(child, childBindings, element, propertyName);
index++;
}
else {
that.updateBoundNode(child, bindings.children[index++], element, propertyName, detached);
}
}
}
if (!bindings || !bindings.data) {
return;
}
const dataBoundProperties = bindings.data;
that.updateBoundData(node, dataBoundProperties, element, propertyName);
}
updateBoundData(node, dataBoundProperties, element, propertyName) {
const that = this;
for (let name in dataBoundProperties) {
const boundProperty = dataBoundProperties[name];
let boundPropertyName = boundProperty.name;
if (boundProperty.updating) {
continue;
}
if (boundPropertyName.indexOf('::') >= 0) {
boundPropertyName = boundPropertyName.substring(0, boundPropertyName.indexOf('::'));
}
if (propertyName !== undefined && propertyName !== boundPropertyName) {
continue;
}
if (boundPropertyName.indexOf('(') >= 0) {
let args = boundPropertyName.substring(boundPropertyName.indexOf('('));
const methodName = boundPropertyName.substring(0, boundPropertyName.indexOf('('));
args = args.substring(1, args.length - 1);
args = args.replace(/ /ig, '');
args = args.split(',');
if (args.length > 0 && args[0] !== '') {
let values = [];
for (let i = 0; i < args.length; i++) {
values.push(element[args[i]]);
}
boundProperty.value = element[methodName].apply(element, values);
}
else {
boundProperty.value = element[methodName]();
}
boundProperty.type = typeof boundProperty.value;
}
else {
boundProperty.value = element[boundPropertyName];
}
if (boundPropertyName === 'innerHTML') {
if (node[name].toString().trim() !== element[boundPropertyName].toString().trim()) {
if (boundProperty.ready) {
node[name] = boundProperty.value.toString().trim();
}
else if (element._properties[boundPropertyName].defaultValue !== boundProperty.value) {
node[name] = boundProperty.value.toString().trim();
}
}
}
else {
if (boundProperty.not) {
node[name] = !boundProperty.value;
if (boundProperty.targetPropertyName) {
node[boundProperty.targetPropertyName] = !boundProperty.value;
}
}
else {
node[name] = boundProperty.value;
if (boundProperty.targetPropertyName) {
node[boundProperty.targetPropertyName] = boundProperty.value;
}
}
}
if (node.$ && node.$.isNativeElement) {
if (!BindingModule.cache['toDash' + name]) {
BindingModule.cache['toDash' + name] = window[namespace].Utilities.Core.toDash(name);
}
const attributeName = BindingModule.cache['toDash' + name];
const oldValue = node.$.getAttributeValue(attributeName, boundProperty.type);
if (boundProperty.reflectToAttribute && (oldValue !== boundProperty.value || !boundProperty.ready)) {
node.$.setAttributeValue(attributeName, boundProperty.value, boundProperty.type);
}
if (!boundProperty.reflectToAttribute) {
node.$.setAttributeValue(attributeName, null, boundProperty.type);
}
}
if (!boundProperty.ready) {
if (node.$ && node.$.isCustomElement) {
if (!BindingModule.cache['toDash' + name]) {
BindingModule.cache['toDash' + name] = window[namespace].Utilities.Core.toDash(name);
}
const attributeName = BindingModule.cache['toDash' + name];
if (!node._properties) {
node._beforeCreatedProperties = node._properties = node.propertyByAttributeName = [];
}
if (!node._properties[name]) {
node._properties[name] = {
attributeName: attributeName
}
if (node._beforeCreatedProperties) {
node._beforeCreatedProperties[name] = node._properties[name];
}
node.propertyByAttributeName[attributeName] = node._properties[name];
}
const propertyConfig = node._properties[name];
propertyConfig.isUpdating = true;
if (boundProperty.reflectToAttribute) {
if (boundProperty.not) {
node.$.setAttributeValue(propertyConfig.attributeName, !boundProperty.value, boundProperty.type);
}
else {
node.$.setAttributeValue(propertyConfig.attributeName, boundProperty.value, boundProperty.type);
}
}
if (!boundProperty.reflectToAttribute) {
node.$.setAttributeValue(propertyConfig.attributeName, null, boundProperty.type);
}
propertyConfig.isUpdating = false;
}
if (boundProperty.twoWay) {
const updateToken = function (value) {
boundProperty.value = value;
if (node.$ && node.$.isNativeElement) {
if (!BindingModule.cache['toDash' + name]) {
BindingModule.cache['toDash' + name] = window[namespace].Utilities.Core.toDash(name);
}
const attributeName = BindingModule.cache['toDash' + name];
const oldValue = node.$.getAttributeValue(attributeName, boundProperty.type);
if (boundProperty.reflectToAttribute && oldValue !== boundProperty.value) {
node.$.setAttributeValue(attributeName, boundProperty.value, boundProperty.type);
}
if (!boundProperty.reflectToAttribute) {
node.$.setAttributeValue(attributeName, null, boundProperty.type);
}
}
}
if (boundProperty.name.indexOf('::') >= 0) {
const eventIndex = boundProperty.name.indexOf('::');
const eventName = boundProperty.name.substring(eventIndex + 2);
that.ownerElement['$' + node.getAttribute('smart-id')].listen(eventName, function () {
updateToken(node[name]);
const boundPropertyName = boundProperty.name.substring(0, boundProperty.name.indexOf('::'));
that.updateBoundProperty(boundPropertyName, boundProperty);
});
}
if (node.$ && node.$.isCustomElement) {
if (node._properties[name]) {
node._properties[name].notify = true;
}
if (!BindingModule.cache['toDash' + name]) {
BindingModule.cache['toDash' + name] = window[namespace].Utilities.Core.toDash(name);
}
const attributeName = BindingModule.cache['toDash' + name];
that.ownerElement['$' + node.getAttribute('smart-id')].listen(attributeName + '-changed', function (event) {
let detail = event.detail;
updateToken(detail.value);
const context = that.ownerElement.context;
if (event.context !== document) {
that.ownerElement.context = that.ownerElement;
}
that.updateBoundProperty(boundProperty.name, boundProperty);
// that.updateBoundProperty(name, boundProperty);
that.ownerElement.context = context;
});
}
}
}
boundProperty.ready = true;
}
}
static clearCache() {
const that = this;
that.cache = {
};
}
}
BindingModule.cache = {
};
export class App {
constructor(object) {
const that = this;
that._id = object.id;
if (object.id) {
that._appRoot = document.getElementById(that._id);
}
else {
that._id = window[namespace].Utilities.Core.createGUID();
}
if (object.selector) {
that._id = window[namespace].Utilities.Core.createGUID();
that._appRoot = document.querySelector(object.selector);
}
if (!that._appRoot) {
that._appRoot = document.body;
}
that._appRoot.classList.add('smart-visibility-hidden');
const componentsSettings = {
};
if (object.render) {
const template = object.render();
const components = object.components;
const prepareDOM = function () {
const fragment = document.createDocumentFragment();
const templateDoc = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null);
const templateBody = document.createElementNS('http://www.w3.org/1999/xhtml', 'body');
const root = document.createElement('div');
templateDoc.documentElement.appendChild(templateBody);
templateBody.appendChild(root);
root.innerHTML = template;
for (let i = 0; i < components.length; i++) {
const component = components[i];
const name = component.name;
const tagName = window[namespace].Utilities.Core.toDash(name);
const elements = templateDoc.querySelectorAll(name);
component.tagName = tagName;
for (let j = 0; j < elements.length; j++) {
const element = elements[j];
const id = element.getAttribute('id') || window[namespace].Utilities.Core.createGUID();
const hostElement = document.createElement('div');
hostElement.id = id;
root.insertBefore(hostElement, element);
componentsSettings['#' + id] = {
name: name, properties: [], rendered: false
};
element.parentNode.removeChild(element);
}
}
fragment.appendChild(root);
const appRoot = document.querySelector(object.selector);
if (appRoot) {
appRoot.appendChild(fragment);
}
}
prepareDOM();
}
that._template = object.template || {
};
that._data = object.data || {
};
that._components = componentsSettings;
const init = function () {
that._addAttributeBindings();
that._renderForBindings();
that._addModelBindings();
that._observeData();
that.render();
that._appRoot.classList.remove('smart-visibility-hidden');
}
document.readyState === 'complete' ? init() : window.addEventListener('load', init);
}
_addAttributeBindings(ownerElement, ownerPropertyName) {
const that = this;
const bindingModule = new BindingModule();
const bindings = ownerElement ? bindingModule.getBindings(ownerElement) : bindingModule.getBindings(that._appRoot);
if (!ownerPropertyName) {
ownerPropertyName = '';
}
const createId = function () {
return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
}
const traverseBindings = function (bindings) {
const addChildBinding = function (child) {
if (child.data) {
let id = child.node.id;
if (!id) {
child.node.id = id = 'id' + createId();
}
for (let dataItem in child.data) {
const dataItemValue = child.data[dataItem];
if (dataItem === 'textContent') {
if (child.node.parentNode.id) {
id = child.node.parentNode.id;
}
else {
child.node.id = '';
child.node.parentNode.id = id;
}
}
if (dataItemValue.isEvent) {
const listener = {
};
listener[dataItemValue.name] = dataItemValue.value;
that._addListenerBinding(id, listener);
}
else {
const binding = {
};
binding[dataItem] = ownerPropertyName + dataItemValue.name;
that._addTemplateBinding(id, binding);
}
}
}
}
addChildBinding(bindings);
for (let index in bindings.children) {
const child = bindings.children[index];
if (child.node && child.node.getAttribute && child.node.getAttribute('smart-for') && child.node.getAttribute('smart-for-rendered') === null) {
continue;
}
addChildBinding(child);
if (child.children) {
traverseBindings(child);
}
}
}
traverseBindings(bindings);
}
get template() {
return this._template;
}
set template(value) {
this._template = value;
this.render();
}
get id() {
return this._id;
}
set id(value) {
this._id = value;
}
get jsonData() {
const that = this;
return that.toJSON();
}
get formData() {
const that = this;
const formData = new FormData();
const data = that.toJSON();
if (!data) {
return formData;
}
//let path = '';
const traverseTree = function (data, path) {
for (let name in data) {
const value = data[name];
if (window[namespace].Utilities.Types.isFunction(value)) {
continue;
}
else if (typeof value === 'object' && !Array.isArray(value)) {
traverseTree(value, name);
}
if (typeof value !== 'object' || Array.isArray(value)) {
if (path === '') {
formData.append(name, value)
}
else {
formData.append(path + '.' + name, value)
}
}
}
}
traverseTree(data, '');
return formData;
}
toJSON() {
const that = this;
const processData = function (ownerData, targetData/*, path*/) {
for (let dataItem in ownerData) {
if (dataItem.startsWith('_') || dataItem === 'notifyFn' || dataItem === 'canNotify' || dataItem === 'name') {
continue;
}
if (dataItem === 'propertyName' || dataItem === 'toString' || dataItem === 'propertyIsEnumerable' || dataItem === 'valueOf' || dataItem === 'toLocaleString') {
continue;
}
if (dataItem === 'hasOwnProperty' || dataItem === 'isPrototypeOf') {
continue;
}
const item = ownerData[dataItem];
if (item.name === 'observableArray') {
targetData[dataItem] = item.toArray();
continue;
}
if (item.name === 'observable') {
const subItem = {
};
for (let subDataItem in item) {
if (subDataItem.startsWith('_') || subDataItem === 'propertyIsEnumerable' || subDataItem === 'notifyFn' || subDataItem === 'canNotify' || subDataItem === 'name') {
continue;
}
if (subDataItem.startsWith('_') || subDataItem === 'notifyFn' || subDataItem === 'canNotify' || subDataItem === 'name') {
continue;
}
if (subDataItem === 'propertyName' || subDataItem === 'toString' || subDataItem === 'valueOf' || subDataItem === 'toLocaleString') {
continue;
}
if (subDataItem === 'hasOwnProperty' || subDataItem === 'isPrototypeOf') {
continue;
}
if (typeof item === 'object' && typeof item[subDataItem] === 'object' && !window[namespace].Utilities.Types.isFunction(item)) {
const subData = processData(item[subDataItem], {}, dataItem + '.' + subDataItem);
subItem[subDataItem] = subData;
continue;
}
subItem[subDataItem] = item[subDataItem];
}
targetData[dataItem] = subItem;
continue;
}
if (typeof item === 'object' && !window[namespace].Utilities.Types.isFunction(item)) {
const subData = processData(item, {}, dataItem);
targetData[dataItem] = subData;
continue;
}
targetData[dataItem] = item;
}
return targetData;
}
const data = processData(that._data, {
}, '');
return data;
}
get data() {
const that = this;
if (!that._data) {
that._data = {
};
}
return that._data;
}
set data(value) {
const that = this;
that._data = value;
that._observeData();
that.render();
}
_addTemplateBinding(id, bind) {
const that = this;
if (that.template['#' + id]) {
if (!that.template['#' + id].bind) {
that.template['#' + id].bind = bind;
}
else {
Object.assign(that.template['#' + id].bind, bind);
}
}
else {
that.template['#' + id] = {
bind: bind
}
}
}
_addListenerBinding(id, listener) {
const that = this;
if (that.template['#' + id]) {
if (!that.template['#' + id].listeners) {
that.template['#' + id].listeners = listener;
}
else {
Object.assign(that.template['#' + id].listeners, listener);
}
}
else {
that.template['#' + id] = {
listeners: listener
}
}
}
_updateDataFromBooleanElement(element, bind) {
const that = this;
const boundPropertyName = bind['checked'];
const bindItem = that._data[boundPropertyName];
if (element.value && element.value !== 'on') {
if (Array.isArray(bindItem) || bindItem.name === 'observableArray') {
if (element.checked) {
bindItem.push(element.value);
}
else {
const removeIndex = bindItem.indexOf(element.value);
if (removeIndex >= 0) {
bindItem.splice(removeIndex, 1);
}
}
}
else {
if (element.type !== 'radio') {
if (element.checked) {
that._data[boundPropertyName] = element.value;
}
else {
that._data[boundPropertyName] = '';
}
}
else if (element.checked) {
that._data[boundPropertyName] = element.value;
}
}
}
else {
that._data[boundPropertyName] = element.checked;
}
}
_updateDataFromNativeElement(element, bind) {
const that = this;
if (element.tagName === 'SELECT') {
element.addEventListener('change', function () {
const boundPropertyName = bind['value'];
let dataItem = that._data[boundPropertyName];
element.__updating = true;
if (Array.isArray(dataItem) || dataItem.name === 'observableArray') {
dataItem = new window.Smart.ObservableArray();
if (element.selectedOptions) {
for (let i = 0; i < element.selectedOptions.length; i++) {
const option = element.selectedOptions[i];
dataItem.push(option.value);
}
}
that._data[boundPropertyName] = dataItem;
}
else {
that._data[boundPropertyName] = element.value;
}
element.__updating = false;
});
}
if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
element.addEventListener('change', function () {
element.__updating = true;
if (isBoolean) {
that._updateDataFromBooleanElement(element, bind);
}
else {
that._data['value'] = element.value;
}
element.__updating = false;
});
let isBoolean = (element.type === 'checkbox' || element.type === 'radio');
if (!isBoolean) {
element.addEventListener('keyup', function () {
const boundPropertyName = bind['value'];
that._data[boundPropertyName] = element.value;
});
}
}
}
_addModelBindings(ownerElement) {
const that = this;
const elements = ownerElement ? ownerElement.querySelectorAll('[smart-model]') : that._appRoot.querySelectorAll('[smart-model]');
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
let bind = {
value: element.getAttribute('smart-model')
};
if (element.type === 'list') {
bind = {
selectedValues: element.getAttribute('smart-model')
};
}
if (element.type === 'checkbox' || element.type === 'radio') {
bind = {
checked: element.getAttribute('smart-model')
};
}
if (!element.id) {
element.id = 'id' + window[namespace].Utilities.Core.createGUID().replace(/-/ig, '');
}
that._updateDataFromNativeElement(element, bind);
that._addTemplateBinding(element.id, bind);
}
}
_renderForBindings(invokeRender) {
const that = this;
const elements = that._appRoot.querySelectorAll('[smart-for]');
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
const dataName = element.getAttribute('smart-for');
const array = that._data[dataName];
if (!element.id) {
element.id = 'id' + window[namespace].Utilities.Core.createGUID().replace(/-/ig, '');
}
element.setAttribute('smart-for-rendered', '');
const selfRepeat = element.getAttribute('smart-for-self') !== null;
const toRemove = !selfRepeat ? element.querySelectorAll('.smart-for-item-' + element.id) : element.parentNode.querySelectorAll('.smart-for-item-' + element.id);
for (let r = 0; r < toRemove.length; r++) {
if (that.template[toRemove[r].id]) {
delete that.template[toRemove[r].id];
}
toRemove[r].remove();
}
const node = selfRepeat ? element : element.firstElementChild;
const fragment = document.createDocumentFragment();
for (let j = 0; j < array.length; j++) {
const clonedNode = node.cloneNode(true);
clonedNode.style.display = '';
clonedNode.id = element.id + '_' + j;
clonedNode.classList.add('smart-for-item');
clonedNode.classList.add('smart-for-item-' + element.id);
clonedNode.removeAttribute('smart-for');
fragment.appendChild(clonedNode);
}
node.style.display = 'none';
if (!selfRepeat) {
element.appendChild(fragment);
}
else {
element.parentNode.insertBefore(fragment, element.nextSibling);
}
let children = that._appRoot.querySelectorAll('.smart-for-item-' + element.id);
for (let j = 0; j < children.length; j++) {
const childNode = children[j];
that._addAttributeBindings(childNode, dataName + '.' + j + '.');
}
}
if (invokeRender && elements.length > 0) {
that.render();
}
}
notify(notifyFn) {
const that = this;
that.notifyFn = function (changes) {
changes.owner = that;
notifyFn(changes);
}
}
_notify(changes) {
const that = this;
if (changes.propertyName === 'length') {
return;
}
if (that.notifyFn) {
that.notifyFn(changes);
}
let dataPropertyName = changes.propertyName;
if (changes.object) {
dataPropertyName = changes.object.propertyName;
}
for (let selector in that.template) {
const item = that.template[selector];
let elementProperty = null;
for (let property in item.bind) {
if (typeof item.bind[property] === 'function') {
continue;
}
if (item.bind[property].indexOf(dataPropertyName) >= 0) {
elementProperty = property;
break;
}
}
if (!elementProperty) {
continue;
}
that._setPropertyFromData(item, elementProperty);
}
that._renderForBindings(true);
}
_observeData() {
const that = this;
that._data = new window[namespace].Observable(that._data);
that._data.canNotify = false;
that._data.notify(that._notify.bind(that));
const observeSubData = function (data) {
data.canNotify = false;
for (let dataItem in data) {
if (typeof data[dataItem] === 'function') {
data[dataItem] = data[dataItem].bind(data);
continue;
}
if (dataItem.startsWith('_') || dataItem === 'notifyFn' || dataItem === 'canNotify' || dataItem === 'name') {
continue;
}
const dataItemValue = data[dataItem];
if (Array.isArray(dataItemValue)) {
data[dataItem] = new window[namespace].ObservableArray(data[dataItem]);
data[dataItem].canNotify = false;
data[dataItem].notify(that._notify.bind(that));
data[dataItem].canNotify = true;
data[dataItem].propertyName = dataItem;
}
else if (dataItemValue && typeof dataItemValue === 'object' && dataItemValue.constructor === window[namespace].DataAdapter) {
data[dataItem].propertyName = dataItem;
}
else if (dataItemValue && typeof dataItemValue === 'object' && dataItemValue instanceof Date) {
data[dataItem].propertyName = dataItem;
}
else if (dataItemValue && typeof dataItemValue === 'object') {
data[dataItem] = new window[namespace].Observable(data[dataItem]);
data[dataItem].canNotify = false;
data[dataItem].notify(that._notify.bind(that));
data[dataItem].canNotify = true;
data[dataItem].propertyName = dataItem;
observeSubData(data[dataItem]);
}
}
data.canNotify = true;
}
for (let dataItem in that._data) {
if (typeof that._data[dataItem] === 'function') {
that._data[dataItem] = that._data[dataItem].bind(that._data);
continue;
}
if (dataItem.startsWith('_') || dataItem === 'notifyFn' || dataItem === 'canNotify' || dataItem === 'name') {
continue;
}
const dataItemValue = that._data[dataItem];
if (Array.isArray(dataItemValue)) {
that._data[dataItem] = new window[namespace].ObservableArray(that._data[dataItem]);
that._data[dataItem].canNotify = false;
that._data[dataItem].notify(that._notify.bind(that));
that._data[dataItem].canNotify = true;
that._data[dataItem].propertyName = dataItem;
}
else if (typeof dataItemValue === 'object' && dataItemValue.constructor === window[namespace].DataAdapter) {
that._data[dataItem].propertyName = dataItem;
}
else if (typeof dataItemValue === 'object' && dataItemValue instanceof Date) {
that._data[dataItem].propertyName = dataItem;
}
else if (typeof dataItemValue === 'object') {
that._data[dataItem] = new window[namespace].Observable(that._data[dataItem]);
that._data[dataItem].canNotify = false;
that._data[dataItem].notify(that._notify.bind(that));
that._data[dataItem].canNotify = true;
that._data[dataItem].propertyName = dataItem;
observeSubData(that._data[dataItem]);
}
}
that._data.canNotify = true;
}
_setPropertyFromData(item, propertyName) {
const that = this;
if (item.element.__updating || !item.bind) {
return;
}
const boundPropertyName = item.bind[propertyName];
if (typeof boundPropertyName === 'function') {
return;
}
const boundArray = boundPropertyName ? boundPropertyName.split('.') : [];
let dataItem = that._data;
for (let i = 0; i < boundArray.length; i++) {
const name = boundArray[i];
if (typeof dataItem === 'string') {
break;
}
if (undefined === dataItem[name]) {
dataItem = undefined;
break;
}
dataItem = dataItem[name];
}
if (item.bind.computed) {
const computedObject = {
item: item, name: boundPropertyName, value: dataItem
};
item.bind.computed(computedObject);
if (computedObject.value !== dataItem) {
dataItem = computedObject.value;
}
}
item.element.__updatingProperties = true;
if (boundPropertyName && dataItem !== undefined) {
if (dataItem.name === 'observableArray') {
if (item.element.type === 'checkbox') {
item.element[propertyName] = dataItem.indexOf(item.element.value) >= 0;
}
else {
item.element[propertyName] = dataItem.toArray().slice(0);
}
}
else if (dataItem.name === 'observable') {
const findInSubData = function (data) {
let foundData = null;
for (let dataItem in data) {
if (typeof data[dataItem] === 'function') {
continue;
}
if (dataItem.startsWith('_') || dataItem === 'notifyFn' || dataItem === 'canNotify' || dataItem === 'name') {
continue;
}
const dataItemValue = data[dataItem];
if (dataItemValue === undefined) {
continue;
}
if (dataItemValue && Array.isArray(dataItemValue)) {
continue;
}
else if (dataItemValue && typeof dataItemValue === 'object' && dataItemValue.constructor === window[namespace].DataAdapter) {
continue;
}
else if (dataItemValue && typeof dataItemValue === 'object') {
foundData = findInSubData(data[dataItem]);
}
if (dataItem === boundPropertyName) {
foundData = dataItemValue;
break;
}
if (foundData) {
return foundData;
}
}
return foundData;
}
const subData = findInSubData(dataItem);
if (subData) {
item.element[propertyName] = subData;
}
else {
item.element[propertyName] = dataItem;
}
}
else if (item.element.type === 'radio') {
if (item.element.value === dataItem) {
item.element[propertyName] = true;
}
else {
item.element[propertyName] = false;
}
}
else {
item.element[propertyName] = dataItem;
}
if (that._components[item.selector] && !that._components[item.selector].rendered) {
that._components[item.selector].properties[propertyName] = dataItem;
}
}
else if (item.properties) {
item.element[propertyName] = item.properties[propertyName];
if (that._components[item.selector] && !that._components[item.selector].rendered) {
that._components[item.selector].properties[propertyName] = item.properties[propertyName];
}
}
item.element.__updatingProperties = false;
}
render() {
const that = this;
for (let selector in that.template) {
const item = that.template[selector];
const element = item.element ? item.element : document.querySelector(selector);
const properties = item.properties;
const listeners = item.listeners;
const bind = item.bind;
if (!element) {
continue;
}
item.selector = selector;
item.element = element;
if (!element._properties) {
element._properties = [];
}
for (let propertyName in properties) {
const property = element._properties ? element._properties[propertyName] : null;
if (property) {
property.notify = true;
}
that._setPropertyFromData(item, propertyName);
}
for (let propertyName in bind) {
const property = element._properties ? element._properties[propertyName] : null;
if (property) {
property.notify = true;
}
that._setPropertyFromData(item, propertyName);
}
if (that._components && that._components[selector] && that._components[selector].rendered === false) {
const component = that._components[selector];
item.element = component.instance = new window[component.name](selector, component.properties);
component.rendered = true;
}
const handleListeners = function (type) {
const element = item.element;
for (let listenerName in listeners) {
if (!element.$) {
element.$ = window[namespace].Utilities.Extend(element);
}
element.$[type](listenerName, function (event) {
const dataHandlerName = listeners[listenerName]
if (dataHandlerName.indexOf('.') >= 0) {
const path = dataHandlerName.split('.');
let eventHandler = that._data[path[0]];
for (let i = 1; i < path.length; i++) {
eventHandler = eventHandler[path[i]];
}
if (eventHandler !== undefined) {
eventHandler(event);
}
}
else {
if (that._data[dataHandlerName]) {
that._data[dataHandlerName](event);
}
}
});
}
for (let propertyName in bind) {
const property = element._properties ? element._properties[propertyName] : null;
if (property) {
const eventHandler = function (event) {
if (element.__updatingProperties) {
return;
}
element.__updating = true;
const toggleRadioButtonUpdates = function (update) {
if (element.type === 'radio') {
const radioButtons = element.parentNode.querySelectorAll('[type="radio"]');
for (let i = 0; i < radioButtons.length; i++) {
radioButtons[i].__updating = update;
}
}
}
toggleRadioButtonUpdates(true);
const boundPropertyName = bind[propertyName];
if (element.type === 'radio' || element.type === 'toggle' || element.type === 'checkbox') {
that._updateDataFromBooleanElement(element, bind);
}
else {
const setValue = function (obj, path, value) {
var i;
for (i = 0; i < path.length - 1; i++) {
obj = obj[path[i]];
}
obj[path[i]] = value;
}
setValue(that._data, boundPropertyName.split('.'), event.detail.value);
}
element.__updating = false;
toggleRadioButtonUpdates(false);
}
if (element.type === 'textarea') {
element.$[type]('input.keyup', function (event) {
const customEvent = new CustomEvent('keyup', {
detail: {
originalEvent: event, value: element.$.input.value
}
});
eventHandler(customEvent);
});
}
element.$[type](property.attributeName + '-changed', function (event) {
eventHandler(event);
});
}
else {
//
}
}
}
hand