cascade
Version:
A modern library for creating user interfaces.
183 lines (174 loc) • 6.72 kB
text/typescript
import { IVirtualNode, IVirtualElementProps } from './IVirtualNode';
import Ref from './Ref';
export default class VirtualNode<T> implements IVirtualNode<T> {
type: string;
props: T & IVirtualElementProps;
children: any[];
key: string | number;
element: Node;
constructor(type: string, props?: T & IVirtualElementProps, children?: Array<any>) {
this.type = type;
this.props = props || ({} as any);
this.key = this.props.key;
// TODO: Remove key and ref?
// if (this.props.key) {
// delete this.props.key;
// }
this.children = children;
}
toNode(namespace?: string) {
let node: HTMLElement;
namespace = namespace || this.props.xmlns;
if (namespace) {
// Casting potential Element to HtmlElement.
node = document.createElementNS(namespace, this.type) as any;
} else {
node = document.createElement(this.type);
}
for (var index = 0, length = this.children.length; index < length; index++) {
var child = this.children[index];
switch (typeof child) {
case 'string':
node.appendChild(document.createTextNode(child as string));
break;
case 'object':
if (child) {
if ((child as IVirtualNode<any>).toNode) {
var renderedNode = (child as IVirtualNode<any>).toNode(namespace);
if (renderedNode instanceof Node) {
node.appendChild(renderedNode);
}
} else {
node.appendChild(document.createTextNode(child.toString()));
}
}
break;
case 'undefined':
break;
// case 'number':
default:
node.appendChild(document.createTextNode(child.toString()));
break;
}
}
for (var name in this.props) {
if (this.props.hasOwnProperty(name)) {
let value = this.props[name];
if (value !== undefined && value !== null) {
VirtualNode.setAttribute(node, name, this.props[name], namespace);
}
}
}
if (this.props && this.props.ref) {
if (typeof this.props.ref === 'function') {
this.props.ref(node);
} else {
this.props.ref.current = node;
}
}
this.element = node;
return node;
}
toString() {
var container = document.createElement('div') as HTMLElement;
container.appendChild(this.toNode());
return container.innerHTML;
}
static fixChildrenArrays(children: Array<any>, fixedChildren?: any[]) {
fixedChildren = fixedChildren || [];
if (children) {
for (var index = 0, length = children.length; index < length; index++) {
var child = children[index];
// Remove undefined and null elements
if (typeof child !== 'undefined' && child !== null) {
if (child instanceof Array) {
VirtualNode.fixChildrenArrays(child, fixedChildren);
} else {
fixedChildren.push(child);
}
}
}
}
return fixedChildren;
}
static createCssText(style: Partial<CSSStyleDeclaration>) {
let dest = [];
for (let index in style) {
if (style.hasOwnProperty(index)) {
let name = index.replace(/$([a-z])$([A-Z])/, stringReplacer);
let value = style[index];
dest.push(name + ': ' + value);
}
}
return dest.join('; ');
}
static setAttribute(element: HTMLElement, property: string, value: any, namespace?: string) {
if (!namespace) {
if (property === 'style') {
if (typeof value === 'string') {
element.style.cssText = value;
} else {
element.style.cssText = this.createCssText(value);
}
} else if (property.indexOf('-') >= 0) {
element.setAttribute(property, value);
} else if (property === 'class') {
element.setAttribute(property, value);
} else {
try {
element[property] = value;
} catch (e) {
element.setAttribute(property, value);
}
}
} else {
if (property === 'style') {
element.style.cssText = value;
} else if (property.indexOf('on') >= 0) {
element[property] = value;
} else if (property === 'className') {
element[property] = value;
} else if (property === 'xmlns') {
// do nothing
} else {
element.setAttribute(property, value);
}
}
}
// TODO: Should we both set to empty string and delete?
static removeAttribute(element: HTMLElement, property: string, namespace?: string) {
if (!namespace) {
if (property === 'style') {
element.style.cssText = undefined;
} else if (property.indexOf('-') >= 0) {
element.removeAttribute(property);
} else if (property === 'class') {
element.removeAttribute(property);
} else {
try {
element[property] = '';
delete element[property];
} catch (e) {
element.removeAttribute(property);
}
}
} else {
if (property === 'style') {
element.style.cssText = undefined;
} else if (property.indexOf('on') >= 0) {
element[property] = '';
delete element[property];
} else if (property === 'className') {
element[property] = '';
delete element[property];
} else if (property === 'xmlns') {
// do nothing
} else {
element.removeAttribute(property);
}
}
}
}
function stringReplacer(match: string, lowerLetter: string, upperLetter: string) {
return lowerLetter + '-' + upperLetter.toLowerCase();
}