UNPKG

ovuse

Version:

WPF-inspired Web UI framework

291 lines (290 loc) 14.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../utils"); const _1 = require("../."); const _2 = require("."); require("../utils/node-list-extensions"); const xmldom_1 = require("xmldom"); class XamlReader { constructor(namespaceResolver) { this.namespaceResolver = namespaceResolver; this._createdObjectsWithId = {}; this.instanceLoader = new _1.InstanceLoader(window); } Parse(lml) { var parser = new xmldom_1.DOMParser(); var doc = parser.parseFromString(lml, "text/xml").documentElement; return this.Load(doc); } resolveNameSpace(xmlns) { if (xmlns == undefined || xmlns == XamlReader.DefaultNamespace) return "ovuse.controls"; if (this.namespaceResolver != null) return this.namespaceResolver(xmlns); return xmlns; } Load(xamlNode) { //resolve namespace to module/typename var ns = this.resolveNameSpace(xamlNode.namespaceURI); var typeName = ns != null ? ns + "." + xamlNode.localName : xamlNode.localName; if (typeName == null) throw Error("unable to load a type from xaml node"); //load object var containerObject = this.instanceLoader.getInstance(typeName); if (containerObject == null) throw new Error("Unable to create instance of '{0}'".format(typeName)); //load properties objects defined by xml attributes if (xamlNode.attributes != null) { for (var i = 0; i < xamlNode.attributes.length; i++) { let att = xamlNode.attributes[i]; let propertyName = att.localName; if (propertyName == null) continue; if (!this.trySetProperty(containerObject, propertyName, this.resolveNameSpace(att.namespaceURI), att.value)) if (containerObject["addExtentedProperty"] != null) containerObject["addExtentedProperty"](propertyName, att.value); //if no property with right name put it in extented properties collection if (propertyName == "id") this._createdObjectsWithId[att.value] = containerObject; } } // can't use extensions in tests // var childrenProperties = xamlNode.childNodes.where(_ => // _.nodeType == 1 && // _.localName != null && // _.localName.indexOf(".") > -1); var childrenProperties = []; for (var child = xamlNode.firstChild; child !== null; child = child.nextSibling) { if (child.nodeType == 1 && child.localName != null && child.localName.indexOf(".") > -1) childrenProperties.push(child); } ; childrenProperties.forEach(childNode => { if (childNode.localName == null) return; var indexOfDot = childNode.localName.indexOf("."); if (childNode.localName.substr(0, indexOfDot) == xamlNode.localName) { let propertyName = childNode.localName.substr(indexOfDot + 1); let childOfChild = childNode.childNodes.firstOrDefault(_ => _.nodeType == 1, null); let valueToSet = childOfChild == null ? null : this.Load(childOfChild); this.trySetProperty(containerObject, propertyName, this.resolveNameSpace(childNode.namespaceURI), valueToSet); } }); // var children = xamlNode.childNodes.where(_=> // _.nodeType == 1 && // _.localName != null && // _.localName.indexOf(".") == -1); var children = []; for (var child = xamlNode.firstChild; child !== null; child = child.nextSibling) { if (child.nodeType == 1 && child.localName != null && child.localName.indexOf(".") == -1) children.push(child); } ; if (containerObject["setInnerXaml"] != null) { if (children.length > 0) containerObject["setInnerXaml"]((new XMLSerializer()).serializeToString(children[0])); if (containerObject["setXamlLoader"] != null) containerObject["setXamlLoader"](this); return containerObject; } if (children.length == 0) return containerObject; //no children //load children or content or items if (utils_1.hasProperty(containerObject, "content") || utils_1.hasProperty(containerObject, "child")) { //support direct content...try to set content of container object with first child //skip any other children of lml node var contentPropertyName = utils_1.hasProperty(containerObject, "content") ? "content" : "child"; containerObject[contentPropertyName] = this.Load(children[0]); } else { var collectionPropertyName = null; if (utils_1.hasProperty(containerObject, "children")) collectionPropertyName = "children"; if (utils_1.hasProperty(containerObject, "items")) collectionPropertyName = "items"; if (utils_1.hasProperty(containerObject, "templates")) collectionPropertyName = "templates"; if (utils_1.hasProperty(containerObject, "animations")) collectionPropertyName = "animations"; if (collectionPropertyName != null) { //if object has a property called Children or Items //load all children from children nodes and set property with resulting list var listOfChildren = children.map(childNode => this.Load(childNode)); containerObject[collectionPropertyName] = new _1.ObservableCollection(listOfChildren); } } return containerObject; } static compareXml(nodeLeft, nodeRight) { if (nodeLeft == null && nodeRight == null) return true; if (nodeLeft.localName != nodeRight.localName || nodeLeft.namespaceURI != nodeRight.namespaceURI) return false; if (nodeLeft.attributes != null && nodeRight.attributes != null && nodeLeft.attributes.length != nodeRight.attributes.length) return false; for (var i = 0; i < nodeLeft.attributes.length; i++) { var attLeft = nodeLeft.attributes[i]; var attRight = nodeRight.attributes[i]; if (attLeft.name != attRight.name || attLeft.namespaceURI != attRight.namespaceURI || attLeft.value != attRight.value) return false; } var childrenLeft = []; for (var child = nodeLeft.firstChild; child !== null; child = child.nextSibling) { if (child.nodeType == 1) childrenLeft.push(child); } ; //= nodeLeft.childNodes.where(_=> _.nodeType == 1); var childrenRight = []; for (var child = nodeRight.firstChild; child !== null; child = child.nextSibling) { if (child.nodeType == 1) childrenRight.push(child); } ; //= nodeRight.childNodes.where(_=> _.nodeType == 1); if (childrenLeft.length != childrenRight.length) return false; for (var i = 0; i < childrenLeft.length; i++) { var childNodeLeft = childrenLeft[i]; var childNodeRight = childrenRight[i]; if (!XamlReader.compareXml(childNodeLeft, childNodeRight)) return false; } return true; } trySetProperty(obj, propertyName, propertyNameSpace, value) { //walk up in class hierarchy to find a property with right name if (obj == null) return false; if (obj instanceof _1.DependencyObject) { //if obj is a dependency object look for a dependency property var depObject = obj; var typeName = _1.getObjectTypeId(depObject); var depProperty; //if an attached property find the property on publisher object //for example if Grid.Row-> looks for property Grid#Row in Grid type var indexOfDot = propertyName.indexOf("."); if (indexOfDot > -1) { typeName = propertyNameSpace == null ? propertyName.substr(0, indexOfDot) : propertyNameSpace + "." + propertyName.substr(0, indexOfDot); propertyName = propertyName.replace(".", "#"); depProperty = _1.DependencyObject.getProperty(typeName, propertyName); } else depProperty = _1.DependencyObject.lookupProperty(depObject, propertyName); if (depProperty != null) { //ok we have a depProperty and a depObject //test if value is actually a Binding object var bindingDef = utils_1.isString(value) ? XamlReader.tryParseBinding(value) : null; if (bindingDef != null) { //here I should check the source of binding (not yet implemented) //by default if source == DataContext binding just connect to //"DataContext." + original path and source is depObject itself var converter = bindingDef.converter == null ? null : this.instanceLoader.getInstance(bindingDef.converter); if (converter == null && bindingDef.converter != null) throw new Error("Unable to create converter from '{0}'" .format(bindingDef.converter)); //at moment we'll support only 2 modes: //1) default -> connect to DataContext //2) self -> connect to object itself //3) {element} -> source is an element reference var isDataContextProperty = depProperty == _2.FrameworkElement.dataContextProperty; var isElementNameDefined = bindingDef.element != undefined; var bindingPath = bindingDef.source == "self" || isElementNameDefined ? bindingDef.path : isDataContextProperty ? "parentDataContext." + bindingDef.path : bindingDef.path == "." ? "DataContext" : "DataContext." + bindingDef.path; var source = depObject; if (bindingDef.element != undefined) { if (!(bindingDef.element in this._createdObjectsWithId)) console.log("[Bindings] Unable to find element with id '{0}'" .format(bindingDef.element)); else source = this._createdObjectsWithId[bindingDef.element]; } if (bindingPath != undefined) depObject.bind(depProperty, bindingPath, bindingDef.mode != null && bindingDef.mode.toLowerCase() == "twoway", source, converter, bindingDef.converterParameter, bindingDef.format); } else depObject.setValue(depProperty, value); return true; } else if (obj.hasOwnProperty(propertyName)) { //obj[propertyName] = value; utils_1.setPropertyValue(obj, propertyName, value); return true; } else return this.trySetProperty( //obj["__proto__"], utils_1.getFirstAnchestor(obj), propertyName, propertyNameSpace, value); } if (obj.hasOwnProperty(propertyName)) { obj[propertyName] = value; return true; } return false; } static tryCallMethod(obj, methodName, value) { //walk up in class hierarchy to find a property with right name if (obj == null) return false; if (obj[methodName] != null) { obj[methodName](value); return true; } return false; } static tryParseBinding(value) { var bindingValue = value.trim(); if (bindingValue.length >= 3 && //again here maybe better a regex bindingValue[0] == '{' && bindingValue[bindingValue.length - 1] == '}') { try { var bindingDef = new Object(); var tokens = bindingValue.substr(1, bindingValue.length - 2).split(","); tokens.forEach(t => { var keyValue = t.split(":"); if (keyValue.length == 2) { var value = keyValue[1].trim(); if (value.length > 2 && value[0] == '\'' && value[value.length - 1] == '\'') value = value.substr(1, value.length - 2); bindingDef[keyValue[0].trim()] = value; } else if (keyValue.length == 1) bindingDef["path"] = keyValue[0].trim(); else throw Error("syntax error"); }); return bindingDef; } catch (e) { //swallow error here because it could be simply a syntax error //just signal it on console console.log("[Bindings] Unable to parse '{0}' as binding definition".format(bindingValue)); } //var tokens = bindingValue.substr(1, bindingValue.length-2).split(","); //var path = tokens[0]; //ex. '.' or 'Name' //var twoway = tokens.length > 1 ? (tokens[1] == "twoway") : false; //var source = tokens.length > 2 ? tokens[2] : null; //todo convert to source=>self, element etc //var converter = tokens.length > 3 ? tokens[3] : null; //converter (typename) to use when updating the target //var converterParameter = tokens.length > 4 ? tokens[4] : null;//converter parameter to pass to converter as context //return { path: path, twoway: twoway, source: source, converter: converter, converterParameter: converterParameter }; } return null; } } XamlReader.DefaultNamespace = "http://schemas.ovuse.com/"; exports.XamlReader = XamlReader;