@@ -8,7 +8,7 @@ class tApp {
8
8
static database ;
9
9
static currentHash = "/" ;
10
10
static get version ( ) {
11
- return "v0.10.0 " ;
11
+ return "v0.10.1 " ;
12
12
}
13
13
static configure ( params ) {
14
14
if ( params == null ) {
@@ -362,6 +362,18 @@ class tApp {
362
362
} ) ;
363
363
} ) ;
364
364
}
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
+ }
365
377
static eval ( code ) {
366
378
return ( function ( code ) {
367
379
return eval ( code ) ;
@@ -413,13 +425,130 @@ class tApp {
413
425
}
414
426
}
415
427
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 ) ) ;
417
532
let els = document . querySelectorAll ( `[tapp-component="${ component . id } "]` ) ;
418
533
for ( let i = 0 ; i < els . length ; i ++ ) {
419
- els [ i ] . outerHTML = compiled ;
534
+ convertNode ( els [ i ] , compiled ) ;
420
535
}
421
536
}
422
537
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
+ }
423
552
if ( component instanceof tApp . Component ) {
424
553
tApp . components [ component . id ] = component ;
425
554
if ( typeof props == "string" ) {
@@ -430,7 +559,14 @@ class tApp {
430
559
if ( component . parent != null ) {
431
560
parentState = component . parent . state ;
432
561
}
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 , {
434
570
props : props ,
435
571
state : component . state ,
436
572
parent : {
@@ -939,7 +1075,7 @@ tApp.GlobalComponent = (function() {
939
1075
super ( state , "" ) ;
940
1076
}
941
1077
render ( props ) {
942
- return "" ;
1078
+ return "<div></div> " ;
943
1079
}
944
1080
get id ( ) {
945
1081
return "global" ;
0 commit comments