diff --git a/example/config.js b/example/config.js index 4aadecf..7ce6d19 100644 --- a/example/config.js +++ b/example/config.js @@ -46,8 +46,7 @@ class Counter extends tApp.Component { for(let i = 0; i < this.children.length; i++) { this.children[i].destroy(); } - return (` -
+ return (`
[[ CounterButton { @@ -66,8 +65,7 @@ class Counter extends tApp.Component { incrementor: 1 } ]] -
-`); +
`); } } @@ -125,6 +123,49 @@ class CounterText extends tApp.Component { } } +class Text extends tApp.Component { + constructor(state, parent) { + super(state, parent); + if(this.state.text == null) { + this.state.text = ""; + } + if(this.state.textInput1 == null) { + this.state.textInput1 = new TextInput({}, this); + } + if(this.state.textInput2 == null) { + this.state.textInput2 = new TextInput({}, this); + } + if(this.state.textDisplay == null) { + this.state.textDisplay = new TextDisplay({}, this); + } + } + render(props) { + if(this.state.text == "hello" || this.state.text == '"hello"') { + return `

HI! hello there

${this.state.textDisplay}

HI! hello there

${this.state.textInput1}

HI! hello there

${this.state.textInput2}

HI! hello there

`; + } else { + return `
${this.state.textDisplay}${this.state.textInput1}${this.state.textInput2}
`; + } + } +} + +class TextInput extends tApp.Component { + constructor(state, parent) { + super(state, parent); + } + render(props) { + return ``; + } +} + +class TextDisplay extends tApp.Component { + constructor(state, parent) { + super(state, parent); + } + render(props) { + return `

{{{ tApp.escape(parent.state.text) }}}

`; + } +} + tApp.route("/", function(request) { tApp.redirect("#/"); }); @@ -181,9 +222,11 @@ tApp.route("#/template", function(request) { }); let counter = new CounterPreserved(); +let textComponent = new Text(); tApp.route("#/components", function(request) { tApp.renderTemplate("./views/components.html", { - counter: counter.toString() + counter: counter.toString(), + text: textComponent.toString() }); }); diff --git a/example/views/components.html b/example/views/components.html index 52acec6..4c4dbd4 100644 --- a/example/views/components.html +++ b/example/views/components.html @@ -2,4 +2,8 @@

Counter (Template-Based/Unpreserved):

[[ Counter {} ]]

Counter (Object-Based/Preserved):

-{{ counter }} \ No newline at end of file +{{ counter }} + +

Updating Text (Object-Based/Preserved):

+

(Try typing "hello" and see tApp handle complex DOM changes)

+{{ text }} \ No newline at end of file diff --git a/tApp.js b/tApp.js index 8deea48..c65dd22 100644 --- a/tApp.js +++ b/tApp.js @@ -8,7 +8,7 @@ class tApp { static database; static currentHash = "/"; static get version() { - return "v0.10.0"; + return "v0.10.1"; } static configure(params) { if(params == null) { @@ -362,6 +362,18 @@ class tApp { }); }); } + static escape(string) { + let entityMap = { + "&": "&", + "<": "<", + ">": ">", + '"': '"', + "'": ''' + }; + return string.replace(/[&<>"']/g, function (s) { + return entityMap[s]; + }); + } static eval(code) { return (function(code) { return eval(code); @@ -413,13 +425,130 @@ class tApp { } } static updateComponent(component) { - let compiled = tApp.compileComponent(component, component.props, component.parent); + function htmlToDOM(html) { + if(html.includes("= beforeChildren.length) { + beforeChildren.splice(pointerBefore, 0, null); + } else if(pointerAfter >= afterChildren.length) { + afterChildren.splice(pointerAfter, 0, null); + } else { + if(beforeChildren[pointerBefore].nodeName != afterChildren[pointerAfter].nodeName) { + if(beforeChildrenPersist.length > afterChildrenPersist.length) { + afterChildren.splice(pointerAfter, 0, null); + } else { + beforeChildren.splice(pointerBefore, 0, null); + } + } + } + pointerBefore++; + pointerAfter++; + } + //console.log("before", beforeChildren, beforeChildren.map(child => {if(child != null){ return child.data }else{ return "null"}})); + //console.log("after", afterChildren, afterChildren.map(child => {if(child != null){ return child.data }else{ return "null"}})); + for(let i = 0; i < beforeChildren.length; i++) { + let nullBefore = beforeChildren.length == beforeChildren.filter(el => el == null || el.nodeName == "#text").length; + if(beforeChildren[i] == null && afterChildren[i] == null) { + } else if(beforeChildren[i] == null) { + if(nullBefore) { + before.appendChild(afterChildren[i]); + } else { + let nextNotNull; + for(let j = i; nextNotNull == null && j < beforeChildren.length; j++) { + if(beforeChildren[j] != null) { + nextNotNull = beforeChildren[j]; + } + } + if(nextNotNull == null) { + let prevNotNull; + for(let j = i; prevNotNull == null && j < beforeChildren.length; j--) { + if(beforeChildren[j] != null) { + prevNotNull = beforeChildren[j]; + } + } + prevNotNull.insertAdjacentElement("afterend", afterChildren[i]); + } else { + nextNotNull.insertAdjacentElement("beforebegin", afterChildren[i]); + } + } + } else if(afterChildren[i] == null) { + beforeChildren[i].remove(); + beforeChildren[i] = null; + } else { + convertNode(beforeChildren[i], afterChildren[i]); + } + } + } + } + } + let compiled = htmlToDOM(tApp.compileComponent(component, component.props, component.parent)); let els = document.querySelectorAll(`[tapp-component="${component.id}"]`); for(let i = 0; i < els.length; i++) { - els[i].outerHTML = compiled; + convertNode(els[i], compiled); } } static compileComponent(component, props = {}, parent = "global") { + function htmlToDOM(html) { + if(html.includes("`, ` tapp-component="${component.id}">`), { + let count = htmlToDOMCount(rendered); + if(count != 1) { + throw "tAppComponentError: Component render output must contain exactly one node/element but can contain subnodes/subelements. To resolve this issue, wrap the entire output of the render in a div or another grouping element. If you only have one node/element, unintentional whitespace at the beginning or end of the render output could be the source of the issue since whitespace can be interpreted as a text node/element."; + } + let domRendered = htmlToDOM(rendered); + domRendered.setAttribute("tapp-component", component.id); + rendered = domRendered.outerHTML; + return tApp.compileTemplate(rendered, { props: props, state: component.state, parent: { @@ -939,7 +1075,7 @@ tApp.GlobalComponent = (function() { super(state, ""); } render(props) { - return ""; + return "
"; } get id() { return "global";