@@ -8,7 +8,7 @@ class tApp {
88 static database ;
99 static currentHash = "/" ;
1010 static get version ( ) {
11- return "v0.10.0 " ;
11+ return "v0.10.1 " ;
1212 }
1313 static configure ( params ) {
1414 if ( params == null ) {
@@ -362,6 +362,18 @@ class tApp {
362362 } ) ;
363363 } ) ;
364364 }
365+ static escape ( string ) {
366+ let entityMap = {
367+ "&" : "&" ,
368+ "<" : "<" ,
369+ ">" : ">" ,
370+ '"' : '"' ,
371+ "'" : '''
372+ } ;
373+ return string . replace ( / [ & < > " ' ] / g, function ( s ) {
374+ return entityMap [ s ] ;
375+ } ) ;
376+ }
365377 static eval ( code ) {
366378 return ( function ( code ) {
367379 return eval ( code ) ;
@@ -413,13 +425,130 @@ class tApp {
413425 }
414426 }
415427 static updateComponent ( component ) {
416- let compiled = tApp . compileComponent ( component , component . props , component . parent ) ;
428+ function htmlToDOM ( html ) {
429+ if ( html . includes ( "<body" ) ) {
430+ return new DOMParser ( ) . parseFromString ( html , "text/html" ) . childNodes [ 0 ] ;
431+ } else {
432+ return new DOMParser ( ) . parseFromString ( html , "text/html" ) . body . childNodes [ 0 ] ;
433+ }
434+ }
435+ function compareChildren ( before , after ) {
436+ if ( before . childNodes . length != after . childNodes . length ) {
437+ return false ;
438+ }
439+ for ( let i = 0 ; i < before . childNodes . length ; i ++ ) {
440+ if ( before . childNodes [ i ] . nodeName != after . childNodes [ i ] . nodeName ) {
441+ return false ;
442+ }
443+ if ( before . childNodes [ i ] . getAttribute ( "tapp-component" ) != after . childNodes [ i ] . getAttribute ( "tApp-component" ) ) {
444+ return false ;
445+ }
446+ }
447+ return true ;
448+ }
449+ function convertNode ( before , after ) {
450+ if ( after . attributes != null ) {
451+ for ( let i = 0 ; i < after . attributes . length ; i ++ ) {
452+ if ( after . attributes . item ( i ) . nodeName == "value" ) {
453+ before . value = after . value ;
454+ } else {
455+ before . setAttribute ( after . attributes . item ( i ) . nodeName , after . attributes . item ( i ) . nodeValue ) ;
456+ }
457+ }
458+ }
459+ if ( before . nodeName == "#text" && after . nodeName == "#text" ) {
460+ before . textContent = after . textContent ;
461+ }
462+
463+ if ( after . childNodes . length == 0 || after . childNodes . length == 1 && after . childNodes [ 0 ] . nodeName == "#text" ) {
464+ before . innerHTML = after . innerHTML ;
465+ } else {
466+ if ( compareChildren ( before , after ) ) {
467+ for ( let i = 0 ; i < after . childNodes . length ; i ++ ) {
468+ convertNode ( before . childNodes [ i ] , after . childNodes [ i ] )
469+ }
470+ } else {
471+ let beforeChildren = [ ...before . childNodes ] ;
472+ let afterChildren = [ ...after . childNodes ] ;
473+ let beforeChildrenPersist = [ ...before . childNodes ] ;
474+ let afterChildrenPersist = [ ...after . childNodes ] ;
475+ let pointerBefore = 0 ;
476+ let pointerAfter = 0 ;
477+ while ( pointerBefore < beforeChildren . length || pointerAfter < afterChildren . length ) {
478+ if ( pointerBefore >= beforeChildren . length ) {
479+ beforeChildren . splice ( pointerBefore , 0 , null ) ;
480+ } else if ( pointerAfter >= afterChildren . length ) {
481+ afterChildren . splice ( pointerAfter , 0 , null ) ;
482+ } else {
483+ if ( beforeChildren [ pointerBefore ] . nodeName != afterChildren [ pointerAfter ] . nodeName ) {
484+ if ( beforeChildrenPersist . length > afterChildrenPersist . length ) {
485+ afterChildren . splice ( pointerAfter , 0 , null ) ;
486+ } else {
487+ beforeChildren . splice ( pointerBefore , 0 , null ) ;
488+ }
489+ }
490+ }
491+ pointerBefore ++ ;
492+ pointerAfter ++ ;
493+ }
494+ //console.log("before", beforeChildren, beforeChildren.map(child => {if(child != null){ return child.data }else{ return "null"}}));
495+ //console.log("after", afterChildren, afterChildren.map(child => {if(child != null){ return child.data }else{ return "null"}}));
496+ for ( let i = 0 ; i < beforeChildren . length ; i ++ ) {
497+ let nullBefore = beforeChildren . length == beforeChildren . filter ( el => el == null || el . nodeName == "#text" ) . length ;
498+ if ( beforeChildren [ i ] == null && afterChildren [ i ] == null ) {
499+ } else if ( beforeChildren [ i ] == null ) {
500+ if ( nullBefore ) {
501+ before . appendChild ( afterChildren [ i ] ) ;
502+ } else {
503+ let nextNotNull ;
504+ for ( let j = i ; nextNotNull == null && j < beforeChildren . length ; j ++ ) {
505+ if ( beforeChildren [ j ] != null ) {
506+ nextNotNull = beforeChildren [ j ] ;
507+ }
508+ }
509+ if ( nextNotNull == null ) {
510+ let prevNotNull ;
511+ for ( let j = i ; prevNotNull == null && j < beforeChildren . length ; j -- ) {
512+ if ( beforeChildren [ j ] != null ) {
513+ prevNotNull = beforeChildren [ j ] ;
514+ }
515+ }
516+ prevNotNull . insertAdjacentElement ( "afterend" , afterChildren [ i ] ) ;
517+ } else {
518+ nextNotNull . insertAdjacentElement ( "beforebegin" , afterChildren [ i ] ) ;
519+ }
520+ }
521+ } else if ( afterChildren [ i ] == null ) {
522+ beforeChildren [ i ] . remove ( ) ;
523+ beforeChildren [ i ] = null ;
524+ } else {
525+ convertNode ( beforeChildren [ i ] , afterChildren [ i ] ) ;
526+ }
527+ }
528+ }
529+ }
530+ }
531+ let compiled = htmlToDOM ( tApp . compileComponent ( component , component . props , component . parent ) ) ;
417532 let els = document . querySelectorAll ( `[tapp-component="${ component . id } "]` ) ;
418533 for ( let i = 0 ; i < els . length ; i ++ ) {
419- els [ i ] . outerHTML = compiled ;
534+ convertNode ( els [ i ] , compiled ) ;
420535 }
421536 }
422537 static compileComponent ( component , props = { } , parent = "global" ) {
538+ function htmlToDOM ( html ) {
539+ if ( html . includes ( "<body" ) ) {
540+ return new DOMParser ( ) . parseFromString ( html , "text/html" ) . childNodes [ 0 ] ;
541+ } else {
542+ return new DOMParser ( ) . parseFromString ( html , "text/html" ) . body . childNodes [ 0 ] ;
543+ }
544+ }
545+ function htmlToDOMCount ( html ) {
546+ if ( html . includes ( "<body" ) ) {
547+ return new DOMParser ( ) . parseFromString ( html , "text/html" ) . childNodes . length ;
548+ } else {
549+ return new DOMParser ( ) . parseFromString ( html , "text/html" ) . body . childNodes . length ;
550+ }
551+ }
423552 if ( component instanceof tApp . Component ) {
424553 tApp . components [ component . id ] = component ;
425554 if ( typeof props == "string" ) {
@@ -430,7 +559,14 @@ class tApp {
430559 if ( component . parent != null ) {
431560 parentState = component . parent . state ;
432561 }
433- return tApp . compileTemplate ( rendered . replace ( `>` , ` tapp-component="${ component . id } ">` ) , {
562+ let count = htmlToDOMCount ( rendered ) ;
563+ if ( count != 1 ) {
564+ 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." ;
565+ }
566+ let domRendered = htmlToDOM ( rendered ) ;
567+ domRendered . setAttribute ( "tapp-component" , component . id ) ;
568+ rendered = domRendered . outerHTML ;
569+ return tApp . compileTemplate ( rendered , {
434570 props : props ,
435571 state : component . state ,
436572 parent : {
@@ -939,7 +1075,7 @@ tApp.GlobalComponent = (function() {
9391075 super ( state , "" ) ;
9401076 }
9411077 render ( props ) {
942- return "" ;
1078+ return "<div></div> " ;
9431079 }
9441080 get id ( ) {
9451081 return "global" ;
0 commit comments