UNPKG

nova-frontend

Version:

Nova is an alternative to all those gigantic front-end frameworks, that often do more than is necessary when it comes to building simple UIs. Pure Vanilla Javascript is performance-wise the best way to build your front-end in a SPA, but it can be hard to

1 lines 10.1 kB
const root=document.getElementById("root");class Component{#e;#t;#r;constructor(e,t){this.#e=e,this.#t=t||this.#e[0].parent,this.#r=[]}get elements(){return this.#e}setProps(e){for(const t in e){const r=`{{${t}}}`;this.#e.forEach((n=>{for(const s in n.node)n.node[s]===r&&(n.node[s]=e[t],n.props[s]=e[t])}))}}setState(e){const t=e.getState();for(const r in t){const t=`{{${r.replace(/\..+/gi,"")}}}`;this.#e.forEach((n=>{for(const s in n.node)if(String(n.node[s]).replace(/\.[a-z]+/gi,"")===t){let t=n.node[s].match(/(?<=\.)[a-z]+/gi)[0]||null;const i=()=>{if(t){const i=e.getState()[r];n.node[s]=i[t]}else n.node[s]=e.getState()[r]};i(),this.#r.push(i)}}))}}updateState(){this.#r.forEach((e=>{e()}))}retrieve(e){let t=[];return"#"===e[0]?t=this.#e.find((t=>t.node.id===e.replace("#",""))):"."===e[0]?this.#e.forEach((r=>{r.node.className===e.replace(".","")&&t.push(r)})):this.#e.forEach((r=>{r.type===e&&t.push(r)})),t}changeParent(e){this.#t=e,this.#e[0].changeParent(e.node)}sortInDom(e="id",t="ascending"){}render(){this.#e.forEach((e=>{e.addNode()}))}unrender(){this.#e.forEach((e=>{e.removed||e.removeNode()}))}deleteByIndex(e){const t=this.#e[e];t.removed||t.removeNode(),this.#e.splice(e,1)}deleteById(e){const t=this.#e.findIndex((t=>t.id===e)),r=this.#e[t];r.removed||r.removeNode(),this.#e.splice(t,1)}}class Element{#n;#t;#s;#i;#o;#a;constructor(e,t,r,n,s){this.#n=e,this.#t=t.node?t.node:t,this.#s=s||null,this.#i=r,this.#o=!s,this.#a=n,!s&&this.#h(),!s&&this.#d()}static getElementFromId(e){const t=document.getElementById(e);return new Element(t.tagName.toLowerCase(),t.parentNode,{},!1,t)}get node(){return this.#s}get type(){return this.#n}get parent(){return this.#t}get value(){return this.#s.value}get id(){return this.#s.id}get className(){return this.#s.className}get text(){return this.#s.textContent}get html(){return this.#s.innerHTML}get siblings(){return this.parent.children}get children(){return this.node.children}get removed(){return this.#o}get props(){return this.#i}#d(){this.#a&&this.addNode()}#l(e){const t=this.#c();for(const r in e)if(!t.includes(r))throw new Error(`Supplied property in updateNode call doesn't exist on type ${this.#n}`)}#h(){const e=document.createElement(this.#n);this.#s=e,this.#l(this.#i);for(const e in this.#i)this.#s[e]=this.#i[e]}#c(){const e=[];for(const t in this.#s)e.push(t);return e}updateNode(e){this.#l(e),this.#i={...this.#i,...e};for(const e in this.#i)this.#s[e]!==this.#i[e]&&(this.#s[e]=this.#i[e])}toggleNode(){!0===this.#o?this.addNode():this.removeNode()}addNode(){this.parent.appendChild(this.node),this.#o=!1}removeNode(){this.parent.removeChild(this.node),this.#o=!0}changeParent(e){e.node&&(e=e.node),this.#t!==e&&(this.#t=e,this.#t.appendChild(this.node))}addEventListener(e,t){this.node.addEventListener(e,t)}addStyle(e,t){this.#s.style[e]=t}createComponent(){return new Component([this],this.parent)}beforeSibling(){let e=this.node.parentNode.children,t=null;for(let r=0;r<e.length;r++)if(e[r]===this.node){t=r;break}if(0!==t){const r=e[t],n=e[t-1];this.#t.insertBefore(r,n)}}afterSibling(){let e=this.node.parentNode.children,t=null;for(let r=0;r<e.length;r++)if(e[r]===this.node){t=r;break}if(t<e.length-1){const r=e[t],n=e[t+1];this.#t.insertBefore(n,r)}}after(e){const t=e.node.nextSibling;t?this.parent.insertBefore(this.node,t):this.parent.appendChild(this.node)}before(e){e&&this.parent.insertBefore(this.node,e.node)}}class Generator{constructor(){this.tokens=[],this.treeObjectArray=[],this.indentationRule=2,this.endToken="end",this.rules={typeExpected:!0,valueExpected:!1,checkNextTypeIsValue:!1}}get elements(){return this.elementsArray}get tags(){return["a","abbr","acronym","address","applet","area","article","aside","audio","b","base","basefont","bb","bdo","big","blockquote","body","br /","button","canvas","caption","center","cite","code","col","colgroup","command","datagrid","datalist","dd","del","details","dfn","dialog","dir","div","dl","dt","em","embed","eventsource","fieldset","figcaption","figure","font","footer","form","frame","frameset","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr /","html","i","iframe","img","input","ins","isindex","kbd","keygen","label","legend","li","link","map","mark","menu","meta","meter","nav","noframes","noscript","object","ol","optgroup","option","output","p","param","pre","progress","q","rp","rt","ruby","s","samp","script","section","select","small","source","span","strike","strong","style","sub","sup","table","tbody","td","textarea","tfoot","th","thead","time","title","tr","track","tt","u","ul","var","video","wbr"]}_generateElementsFromTree(){const e=document.getElementById("root"),t=[];let r=[],n=0,s=null;this.treeObjectArray.forEach(((e,t)=>{(0===t||e.priority<n)&&(n=e.priority)}));let i=0,o=!1;this.treeObjectArray.forEach((a=>{let h;if(a.priority===n){if(o)throw new Error("Only one grandparent allowed!");o=!0,h=new Element(a.tag,e,a.propertyObject),r=r.filter((e=>e.priority<=a.priority-2))}else if(a.priority===s+2){const e=r.find((e=>e.priority===a.priority-2));h=new Element(a.tag,e.element,a.propertyObject)}else if(a.priority<=s-2){r=r.filter((e=>e.priority<=a.priority-2));let e=r.find((e=>e.priority===a.priority-2));h=new Element(a.tag,e.element,a.propertyObject)}else if(a.priority===s){r.splice(i-1,1),i--;const e=r.find((e=>e.priority===a.priority-2));h=new Element(a.tag,e.element,a.propertyObject)}i++,t.push(h),r.push({element:h,priority:a.priority}),s=a.priority})),this.elementsArray=t}_createTreeObjectFromTokens(){this.tokens.forEach(((e,t)=>{const r=e[0].indentation,n={},s={};for(let t=0;t<e.length;t++)"token_tag"!==e[t].type?"token_property"===e[t].type&&(s[e[t].token]=e[t+1].token,t++):(n.tag=e[t].token,n.priority=r);n.propertyObject=s,this.treeObjectArray.push(n)}))}_checkGrammar(e,t,r){const n=this.tags;let s="";if(this.rules.typeExpected){if(!n.includes(e)){if(e===this.endToken)return 0;throw console.table(e),new Error(`Invalid type, check if applied HTML tag on line ${t+1} is valid. To see available tags, console.log(generator.tags)`)}s="token_tag"}if(this.rules.valueExpected){const t=r[0].token,n=document.createElement(t);for(const t in n)t==e&&(s="token_property");if("token_property"!==s)throw new Error(`Invalid property value ${e} for ${t}`);return"token_property"}return this.rules.checkNextTypeIsValue&&"'"===e[0]&&"'"===e[e.length-1]&&(e=e.replace(/'/g,""),s="token_value"),s}_generateTokens(e){e.filter((e=>0!==e.length)).forEach(((e,t)=>{const r=[];this.rules.typeExpected=!0;let n=0;for(let t=0;" "==e[t];t++)n++;let s="",i="";for(let o=n;o<=e.length;o++)if(" "!==e[o]&&o!==e.length&&e[o]){if(this.rules.valueExpected&&"'"!==e[o])throw new Error("Missing ' after :");if(":"===e[o]&&!this.rules.checkNextTypeIsValue){this.rules.valueExpected=!0;continue}s+=e[o],i=e[o]}else if(this.rules.checkNextTypeIsValue&&"'"!==i)s+=e[o],i=e[o];else{const e=this._checkGrammar(s,t,r);if(this.rules.typeExpected&&"token_tag"!==e&&s!==this.endToken)throw new Error("type expected as first token! ex. div");if(this.rules.typeExpected=!1,this.rules.checkNextTypeIsValue&&"token_value"!==e)throw new Error("'value' expected after after token_property");this.rules.checkNextTypeIsValue=!1,this.rules.valueExpected&&(this.rules.checkNextTypeIsValue=!0,this.rules.valueExpected=!1),"token_value"===e&&(s=s.replace(/'/g,"")),r.push({token:s,type:e}),s=""}r.unshift({indentation:n}),this.tokens.push(r)}))}createTree(e,t=2){this.indentationRule=t;const r=e.split("\n");this._generateTokens(r),this._createTreeObjectFromTokens(),this._generateElementsFromTree(),this.elementsArray.pop();const n=[...this.elementsArray];return this._defaultGenerator(),new Component(n)}_defaultGenerator(){this.tokens=[],this.treeObjectArray=[],this.indentationRule=2,this.endToken="end",this.rules={typeExpected:!0,valueExpected:!1,checkNextTypeIsValue:!1},this.elementsArray=[]}}class Group{#p;#t;constructor(e,t){this.#p=e||[],this.#t=t,this.#m(),this.#u()}get components(){return this.#p}#m(){this.#p=this.#p.map((e=>e.node?e.createComponent():e))}#u(){this.#t&&this.#p.forEach((e=>{e.elements[0].changeParent(this.#t.node)}))}sortInDom(e="id",t="ascending"){}render(){this.#p.forEach((e=>{e.render()}))}add(e){this.#p=[...this.#p,e],e.elements[0].changeParent(this.#t.node),this.render()}update(e){this.arrayOfComponents=e,this.#u(),this.render()}unrender(){this.#p.forEach((e=>{e.unrender()}))}retrieve(e){return this.#p.find((t=>t.retrieve(`#${e}`))).elements}deleteById(e){let t;this.#p.forEach(((r,n)=>{if(r.elements[0].id===e)for(t=n;r.elements.length>0;)r.deleteByIndex(0)})),this.#p.splice(t,1)}}class Router{#f;#y;#g;constructor(e,t){this.#f=e,this.#y=t,this.#g=!1,this.#E()}get path(){return this.#f}set path(e){this.#f=e}#E(){this.#b(),this.path===window.location.pathname&&this.#O()}#O(){this.#y.forEach((e=>e.render())),this.#g=!0}#k(){this.#y.forEach((e=>e.unrender())),this.#g=!1}static getPath(){return window.location.pathname}static changePath(e){window.history.pushState({},"",e),window.dispatchEvent(new Event("locationChange"))}#b(){["locationChange","popstate"].forEach((e=>{window.addEventListener(e,(()=>{window.location.pathname!==this.#f||this.#g?this.#g&&this.#k():this.#O()}))}))}}class State{#v;#w;#x;#C;constructor(e,t){this.#w=e,this.#v=t||{},this.#x=[],this.#C=[]}get listeners(){return this.#x}get actions(){return this.#C}getState(){return this.#v}static mergeWorkers(e){return(t,r)=>{const n={};for(const s in e){const i=e[s](t[s],r);n[s]=i}return n}}createAction(e,t){const r={name:e,deps:t};this.#C.forEach((e=>{if(e.name===r.name)throw new Error(`Action name "${e.name}" already exists... Please choose a different one!`)})),this.#C.push(r)}getAction(e,t){const r=this.#C.findIndex((t=>t.name===e));return t&&(this.#C[r].deps={...this.#C[r].deps,...t}),this.#C[r].deps}subscribe(e){return this.#x.includes(e)||this.#x.push(e),()=>{const t=this.#x.indexOf(e);this.#x.splice(t,1)}}dispatch(e){this.#v=this.#w(this.getState(),e),this.#x.forEach((t=>{"object"==typeof t?t.type===e.type?t.func():t.updateState&&t.updateState():t()}))}}