Initial rendering of a Todos-MVC app. (events are called but don't trigger a re-rendering yet, renderin-function does not yet support updating DOM yet)

This commit is contained in:
2019-10-23 01:13:27 +02:00
parent 31cfda50f5
commit 5169c5018d
13 changed files with 361 additions and 47 deletions

View File

@@ -1,8 +1,22 @@
import {render} from "../vdom";
/**
* This CustomElement class is to avoid having to do an ugly workaround in every custom-element:
* Which would be replacing 'HTMLElement' with '(class extends HTMLElement{})'
*
* Also, it is a good starting point for implementing render() functionality, listening to props, state changes, events and whatnot (use decorators)
*/
export class CustomElement extends HTMLElement {}
export class CustomElement extends HTMLElement {
connectedCallback() {
if(this.render){
let newVNode = this.render();
render(newVNode, {
host: this
});
}
}
disconnectedCallback(){
}
}

View File

@@ -15,4 +15,7 @@ export const VNODEPROP_EXCLUDE_DIRECT = {
export const VNODEPROP_IGNORE = {
['key']: true,
};
};
export const Host = Symbol('host');
export const ShadowDOM = Symbol('shadow-dom');

View File

@@ -1,2 +1,3 @@
export * from "./vnode";
export * from "./render";
export * from "./render";
export {Host, ShadowDOM} from "./constants";

View File

@@ -1,5 +1,6 @@
import './vnode';
import {
Host, ShadowDOM,
VNODE_EXCLUDE,
VNODEPROP_DIRECT, VNODEPROP_EXCLUDE_DIRECT,
VNODEPROP_IGNORE,
@@ -21,7 +22,11 @@ import {
* @return {Element}
*/
export function render(vnode, opts = {}) {
// Replace how this works into a queue instead of a recursive call, also consider changing JSX to support (changed)="..." notation
// TODO figure out how to handle events (its not that easy to create (click)={this.onClick} or something, that is not supported by the @babel/parser and we'd have to fork it..
// ---> We've got a basic onClick (react-style) system set up now
// TODO innerHTML, innerText and other tags/props that are trickyer then just mapping value to attribute
// TODO: Replace how this works into a queue instead of a recursive call, also consider changing JSX to support (changed)="..." notation
// TODO ref-prop (should it only return once all child els are created and appended to the child?!)
let {
/**
* @type {Element}
@@ -30,61 +35,84 @@ export function render(vnode, opts = {}) {
} = opts;
if(VNODE_EXCLUDE[vnode]) return undefined;
console.log(vnode);
if(vnode instanceof Object){
// Type
let tagName = vnode.type instanceof Object? vnode.type.tagName : vnode.type;
if(!host) host = document.createElement(tagName);
// Props
if (vnode.props) {
if (vnode.props.style && typeof (vnode.props.style) === 'object') {
for (let styleKey in vnode.props.style) {
host.style[ styleKey ] = vnode.props.style[ styleKey ];
}
if(!host){
if(!['object', 'function', 'symbol'].includes(typeof(vnode))){
host = document.createTextNode(vnode);
}else if(typeof(vnode?.type) === 'string'){
host = document.createElement(vnode.type);
}else if(vnode?.type?.tagName){
host = document.createElement(vnode.type.tagName);
}else{
throw new Error("Unrecognized vnode type", vnode);
}
}
// Props
if (vnode?.props) {
if (vnode.props.style && typeof (vnode.props.style) === 'object') {
for (let styleKey in vnode.props.style) {
host.style[ styleKey ] = vnode.props.style[ styleKey ];
}
for (let key in vnode.props) {
let val = vnode.props[key];
if(VNODEPROP_IGNORE[key]){
// NO-OP
}else if(VNODEPROP_DIRECT[key]){
}
for (let key in vnode.props) {
let val = vnode.props[key];
if(VNODEPROP_IGNORE[key]){
// NO-OP
}else if(VNODEPROP_DIRECT[key]){
host[key] = val;
}else{
if(!VNODEPROP_EXCLUDE_DIRECT[key] && !key.indexOf('-')){
host[key] = val;
}else{
if(!VNODEPROP_EXCLUDE_DIRECT[key] && !key.indexOf('-')){
host[key] = val;
}
if(key.slice(0,2)==='on' && key[2]>='A' && key[2]<='Z'){
if(val instanceof Function){
host.addEventListener(
// Convert camelCase to dash-case
key[2].toLowerCase()+key.slice(3).replace(/[A-Z]/g, function(c){return('-'+c.toLowerCase())}),
val
);
}else{
new Error("Unsupported event-handler");
}
if (val === false) {
}else {
if (val === false || val===null || val==='') {
host.removeAttribute(key);
} else if (val === true) {
host.setAttribute(key, "");
} else{
} else {
host.setAttribute(key, val);
}
}
}
}
}
// Children
if (vnode.children) {
let children = vnode.children instanceof Array? vnode.children : [vnode.children];
for(let child of children){
let el = child instanceof Element? child : render(child, {
...opts,
host: undefined
});
if(el!==undefined){
host.appendChild(el);
// Children
if (vnode?.children) {
let queue = vnode.children instanceof Array? vnode.children.slice() : [vnode.children];
while(queue.length){
let child = queue.splice(0,1)[0];
if(child instanceof Array){
queue.splice(0,0,...child);
}else{
if(child?.type === ShadowDOM){
let shadow = host.attachShadow({mode: 'open'});
render({children: child.children}, {
...opts,
host: shadow
});
}else{
let el = child instanceof Element? child : render(child, {
...opts,
host: undefined
});
if(el!==undefined) host.appendChild(el);
}
}
}
// TODO figure out how to handle events (its not that easy to create (click)={this.onClick} or something, that is not supporter by the @babel/parser and we'd have to fork it..
// TODO ref-prop (should it only return once all child els are created and appended to the child?!)
// TODO innerHTML, innerText and other tags/props that are trickyer then just mapping value to attribute
}else{
if(!host) host = document.createTextNode(vnode);
}
return host;