UNPKG

pawajs-dom-rerender

Version:

Pawajs dom diffing for server html with the current dom

427 lines (391 loc) 16.2 kB
import {PawaElement} from 'pawajs/pawaElement.js'; import Pawa, {setStateContext,render, keepContext}from 'pawajs/index.js'; import {sanitizeTemplate} from 'pawajs/utils.js'; import { pawaCompare} from './compare.js'; import { componentSettings, fetchDomRender } from './smallRender.js'; // next is to reArrange the element accordingly to the updated style const scheduled = new Set(); let rafScheduled = false; function scheduleRender(fn) { scheduled.add(fn); if (!rafScheduled) { rafScheduled = true; requestAnimationFrame(() => { scheduled.forEach(f => f()); scheduled.clear(); rafScheduled = false; }); } } const pluginMap=new Map() /** * @param {{ * afterMatch:{ * name:string, * plugin:(app:PawaElement | HTMLElement,app1:PawaElement | HTMLElement)=>void * } * }} objects */ export const registerPlugin=(objects)=>{ if (objects.afterMatch && typeof objects.afterMatch.plugin === 'function' && objects.afterMatch.name) { pluginMap.set(objects.afterMatch.name,objects.afterMatch.plugin) } } export const pawaRender=(app,app1) => { //compare current element tree with the fetched element tree if (!pawaCompare(app,fetchDomRender(app1))) { // scheduleRender(()=>{ // const parent=app.parentElement // app._remove(()=>{ // parent.appendChild(app1) // render(app1,parent._context,parent._tree) // }) // }) console.warn(`miss Matched at`,app._tree,app1._tree,app1) return } pluginMap.forEach((plugin, name)=>{ plugin(app, app1) }) if (app._tree.textContextAvoid !== true && app1.textContextAvoid !== true) { if (!app.firstElementChild ) { const text=app1.textContent app.textContent=text } } const promisedArray=[] //// Attributes Array.from(app1.attributes).forEach((attr) => { if (!app._pawaAttribute[attr.name]) { if(app._preRenderAvoid.includes(attr.name))return //attribute to avoid because client is controlling updates like animation etc app.setAttribute(attr.name,attr.value) } }) if (app._tree.thisSame) { //this category is for element that has pawajs condition directives //they are this same means the directives original children are the clone nodes of the same element app1.removeAttribute(app._tree.primaryAttribute) // console.log(app,app1) app._tree.children.forEach((appTree) => { scheduleRender(() => { if(app._tree.primaryAttribute === 'for'){ app1.setAttribute('for-unique',appTree.pawaAttributes['for-unique']) if(appTree.pawaAttributes['for-key']){ app1.setAttribute('for-key',appTree.pawaAttributes['for-key']) } } pawaRender(appTree.el,app1) }) }) app.innerHTML=app1.innerHTML return } else { if (app._componentName && !app._isElementComponent && app._tree.isComponent) { const stateContext=app._tree.stateContext setStateContext(stateContext) const app2= componentSettings(app,app1) const div=document.createElement('div') const compo=stateContext.component(app2) // console.log(app._tree.children[1]) div.innerHTML=sanitizeTemplate(compo) Array.from(div.children).forEach((child,index) => { // console.log(child) scheduleRender(() => { pawaRender(app._tree.children[index].el,child) }) }) } else { const newAppTree=[] const removeElement=[] const parent=app._tree.children[0]?.parent.el || app const parentTree=app._tree if (app._tree.element !== 'TEMPLATE') { Array.from(app1.children).forEach((child,index) => { newAppTree.push(fetchDomRender(child)) }) } if (app._tree.element === 'TEMPLATE') { // console.log(app1.content.children) Array.from(app1.content.children).forEach((child)=>{ newAppTree.push(fetchDomRender(child)) }) } // console.log(newAppTree,'n ewTree') // remove child element that doesn't exit with the element children Array.from(app._tree.children).forEach((child) =>{ let matched=false newAppTree.forEach((newChild) => { if (matched || newChild._tree.matched) { return } if (pawaCompare(child.el,newChild)) { newChild._tree.matched=true matched=true newChild._tree.isMatched=true // later empty it child.matchedNode=newChild } }) if (matched) { // the element matched together tell the diff engine //later make it false child.preRender=true } if (!matched) { // removeElement.push(child) const promised=new Promise((resolve)=>{ child.el._deCompositionElement=true child.el._isKill=true child.el._remove(()=>resolve(true)) }) promisedArray.push(promised) } }) newAppTree.forEach((child) => { child._tree.matched=false }) Promise.all(promisedArray).then(()=>{//let animation run before adding if (app._tree.element === 'TEMPLATE') { forTemplateElement({app,app1,newAppTree,parent,parentTree}) } // update the current dom element from the fetch element if ( app1.children.length > app._tree.children.length && app._tree.element !== 'TEMPLATE') { if (app._tree.children.length === 0) { Array.from(app1.children).forEach((child) => { if (app._scriptFetching && app._scriptDone === false) { app.innerHTML=app1.innerHTML }else{ app.appendChild(child) } requestAnimationFrame(() => { if(app._tree.stateContext._hasRun){ app._tree.stateContext._hasRun=false keepContext(app._tree.stateContext) } if (app._scriptFetching && app._scriptDone === false) { app.innerHTML=app1.innerHTML }else{ render(child,app._context,app._tree) app._tree.stateContext._hasRun=true } }) }) } else { let lastMatched let siblings const insertNewOne=[] // console.log(app._tree) app._tree.children.forEach(child =>{ // let matched=false newAppTree.forEach((newChild,index) => { if (child.alreadyMatched) { return } if (pawaCompare(child.el,newChild)) { newAppTree.splice(index,1) // matched=true lastMatched=child child.alreadyMatched=true // console.log(child,newChild); } else { // next step is to make sure that the element does not enter double if (!newChild._tree.inserted) { newChild._tree.inserted=true newChild._tree.beforeElement=child.el insertNewOne.push({before:child.el, newElement:newChild,parent:parent,remove:()=>newAppTree.splice(index,1)}) } } }) }) app._tree.children.forEach(child =>{ child.alreadyMatched=false }) //insert the new element if (insertNewOne.length > 0) { // console.log(insertNewOne) insertNewOne.forEach(obj=>{ if(obj.newElement._tree.isMatched){ return } app.insertBefore(obj.newElement,obj.before) obj.remove() requestAnimationFrame(() => { if(app._tree.stateContext._hasRun){ app._tree.stateContext._hasRun=false keepContext(app._tree.stateContext) } if (app._scriptFetching && app._scriptDone === false) { app.innerHTML=app1.innerHTML }else{ render(obj.newElement,obj.parent._context,obj.parent._tree) } app._tree.stateContext._hasRun=true }) }) } //insert the other element if (newAppTree.length > 0) { // console.log(lastMatched,app); siblings=lastMatched?.el?.nextElementSibling newAppTree.forEach((child) => { if(child._tree.isMatched){ return } if (siblings) { app.insertBefore(child,siblings) requestAnimationFrame(() => { if(app._tree.stateContext._hasRun){ app._tree.stateContext._hasRun=false keepContext(app._tree.stateContext) } if (app._scriptFetching && !app._scriptDone) { app.innerHTML=app1.innerHTML }else{ render(child,parent._context,parent._tree) } app._tree.stateContext._hasRun=true }) } else { // console.log(child._tree,app1._tree); if(child._tree.isMatched){ return } parent.appendChild(child) requestAnimationFrame(() => { if(app._tree.stateContext._hasRun){ app._tree.stateContext._hasRun=false keepContext(app._tree.stateContext) } if (app._scriptFetching && app._scriptDone === false) { app.innerHTML=app1.innerHTML }else{ render(child,parent._context,parent._tree) } app._tree.stateContext._hasRun=true }) } }) } } } const preRenderTree=app._tree.children.filter(tree => tree.preRender=== true) preRenderTree.forEach((child) => { scheduleRender(() => { pawaRender(child.el,child.matchedNode) }) }) }) //later add for template too } } } const getTemplateElementParent=(el,callback)=>{ if (el._tree.parent.element === 'TEMPLATE' ) { getTemplateElementParent(el._tree.parent.el) }else{ callback(el._tree.parent.el) } } const forTemplateElement=({app1,app,newAppTree,parent,parentTree})=>{ if ( app1.content.children.length > app._tree.children.length ) { if (app._tree.children.length === 0) { Array.from(app1.content.children).forEach((child) => { getTemplateElementParent(app,(parent)=>parent.appendChild(child)) requestAnimationFrame(() => { if(app._tree.stateContext._hasRun){ app._tree.stateContext._hasRun=false keepContext(app._tree.stateContext) } if (app._scriptFetching && app._scriptDone === false) { app.innerHTML=app1.innerHTML }else{ render(child,parent._context,parent._tree) } app._tree.stateContext._hasRun=true }) }) } else { let lastMatched let siblings const insertNewOne=[] app._tree.children.forEach(child =>{ // let matched=false newAppTree.forEach((newChild,index) => { if (child.alreadyMatched) { return } if (pawaCompare(child.el,newChild)) { newAppTree.splice(index,1) // matched=true child.alreadyMatched=true // console.log(child,newChild); lastMatched=child } else { // next step is to make sure that the element does not enter double insertNewOne.push({before:child.el, newElement:newChild,parent:app,remove:()=>newAppTree.splice(index,1)}) } }) }) //clear matched element from main dom element app._tree.children.forEach(child =>{ child.alreadyMatched=false }) //insert the new element if (insertNewOne.length > 0) { insertNewOne.forEach(obj=>{ consol.log(obj.before) getTemplateElementParent(app,(parent)=>parent.insertBefore(obj.newElement,obj.before)) obj.remove() requestAnimationFrame(() => { if(app._tree.stateContext._hasRun){ app._tree.stateContext._hasRun=false keepContext(app._tree.stateContext) } if (app._scriptFetching && app._scriptDone === false) { app.innerHTML=app1.innerHTML }else{ render(obj.newElement,app._context,app._tree) } app._tree.stateContext._hasRun=true }) }) } //insert the other element if (newAppTree.length > 0) { siblings=lastMatched.el.nextElementSibling newAppTree.forEach((child) => { if (siblings) { getTemplateElementParent(app,(parent)=>parent.insertBefore(child,siblings)) requestAnimationFrame(() => { if(app._tree.stateContext._hasRun){ app._tree.stateContext._hasRun=false keepContext(app._tree.stateContext) } if (app._scriptFetching && app._scriptDone === false) { app.innerHTML=app1.innerHTML }else{ render(child,app._context,app._tree) } app._tree.stateContext=true }) } else { getTemplateElementParent(app,(parent)=> parent.appendChild(child)) requestAnimationFrame(() => { if(app._tree.stateContext._hasRun){ app._tree.stateContext._hasRun=false keepContext(app._tree.stateContext) } if (app._scriptFetching && app._scriptDone === false) { app.innerHTML=app1.innerHTML }else{ render(child,app._context,app._tree) } app._tree.stateContext=true }) } }) } } } }