colorful rat Ratfactor.com > Dave's Repos

retrov

A tiny browser-native Virtual DOM rendering library.
git clone http://ratfactor.com/repos/retrov/retrov.git

retrov/demo.html

Download raw file: demo.html

1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <title>RetroV Demo and Tutorial</title> 5 <meta charset="utf-8"> 6 <meta name="viewport" content="width=device-width, initial-scale=1"> 7 <style> 8 body { 9 background: #000; color: #FFF; 10 font-size: 18px; 11 max-width: 700px; 12 margin: auto; 13 } 14 a, a:hover, a:visited { color: #F0A; } 15 pre { padding: 1em; font-size: 0.8em; margin-left: 2em; overflow: auto; } 16 pre,code { color: #F4F; } 17 h2 { color: #acff47; margin-top: 2em; border-bottom: 2px solid #acff47; } 18 h3 { color: #47ffdd; margin-top: 3em; } 19 strong { color: #47ffdd; } 20 .container { 21 padding: 1em; 22 border: 1px solid #0cf; 23 margin-top: 10px; 24 } 25 .aqua { 26 background: #0CF; color: black; padding: 3px; 27 display: inline-block; margin: 5px; 28 animation: 0.5s attention; 29 position: relative; /* for attention anim */ 30 border: 1px solid #000; 31 } 32 .orange { 33 background: orange; color: black; padding: 3px; 34 display: inline-block; margin: 5px; 35 animation: 0.5s attention; 36 position: relative; /* for attention anim */ 37 border: 1px solid #000; 38 } 39 @keyframes attention { from { top: -5px; } to { top: 0; } } 40 .bold { font-weight: bold; } 41 button.toggle_state { 42 display: block; 43 background: #db01db; 44 margin: 10px; 45 color: white; 46 font-weight: bold; 47 border-style: none; 48 border-radius: 8px; 49 padding: 5px 8px; 50 float: right; 51 } 52 button.toggle_state:hover { background: #ff4aff; } 53 </style> 54 <script src="retrov.js"></script> 55 </head> 56 <body> 57 58 <center> <!-- A center tag! Now that's retro! --> 59 <h1>RetroV demo and tutorial</h1> 60 <img src="./retrov.svg" alt="RetroV 1970s colors svg logo"> 61 </center> 62 63 <script> 64 function make_container(){ 65 var script = document.currentScript; 66 var output = document.createElement('div'); 67 output.className = "container"; 68 script.after(output); 69 // script.parentNode.insertBefore(output, script); 70 window.test_container = output; 71 return output; 72 } 73 74 function toggle_state(c){ 75 var after = false; 76 var states = c.states; 77 78 var btn = document.createElement('button'); 79 btn.innerHTML = 'Toggle'; // "2" meaning "second" 80 btn.className = 'toggle_state'; 81 btn.addEventListener('click', function(){ 82 var s = after ? 'before' : 'after'; 83 after = !after; //toggle 84 RV.render(c, states[s]); 85 }); 86 c.before(btn); 87 // c.append(btn); 88 } 89 90 var c; // re-used a bunch to hold example containers 91 </script> 92 93 <h2>Contents</h2> 94 <ul> 95 <li><a href="#start">Getting started</a> 96 <ul> 97 <li><a href="#startnote">Note</a></li> 98 </ul> 99 </li> 100 <li><a href="#simplerender">Simple rendering 101 <ul> 102 <li><a href="#text">Text</a></li> 103 <li><a href="#html">HTML nodes</a></li> 104 <li><a href="#siblings">Arrays of siblings</a></li> 105 <li><a href="#props">Element properties</a></li> 106 <li><a href="#classes">Class shorthand</a></li> 107 <li><a href="#styleprop">The style property</a></li> 108 <li><a href="#htmllit">HTML literals</a></li> 109 <li><a href="#nestednodes">Nested HTML nodes</a></li> 110 </ul> 111 </li> 112 <li><a href="#dynamic">Dynamic updates 113 <ul> 114 <li><a href="#changingnode">Changing an HTML node property</a></li> 115 <li><a href="#changingcls">Same, but with class shorthand</a></li> 116 <li><a href="#addingitem">Adding an item to a list</a></li> 117 <li><a href="#changingsib">Changing a sibling element in a list</a></li> 118 <li><a href="#removingsib">Removing a sibling element from a list</a></li> 119 <li><a href="#nullingsib">"Nulling" a sibling element from a list</a></li> 120 <li><a href="#nullingchild">"Nulling" a child</a></li> 121 <li><a href="#false">A <code>false</code> means "no change"</a></li> 122 <li><a href="#oncreate">Special event: <code>oncreate</code></a></li> 123 <li><a href="#rvid">Special property: <code>rvid</code></a></li> 124 </ul> 125 </li> 126 <li><a href="#cookbook">Cookbook 127 <ul> 128 <li><a href="#renderingfn">Rendering with simple nested functions</a></li> 129 <li><a href="#renderingfn2">Rendering with higher-order functions</a></li> 130 <li><a href="#stateclosures">Storing state in function closures</a></li> 131 <li><a href="#renderfalse">Controlling rendering with <code>false</code></a></li> 132 <li><a href="#input">Text input</a></li> 133 <li><a href="#textarea">Textarea updates</a></li> 134 <li><a href="#focus">Element focus</a></li> 135 </ul> 136 </li> 137 </ul> 138 139 140 <h2 id="start">Getting started</h2> 141 142 <p>You can add the RetroV library to a page 143 with nothing more than a script tag like so: 144 145 <pre> 146 &lt;script src="retrov.js"&gt;&lt;/script&gt; 147 </pre> 148 149 <p>This exposes a global <code>RV</code> object with a <code>render()</code> 150 method. The first render parameter is a target DOM element to render into. The 151 second parameter is an element or tree of "virtual node" data to be 152 rendered.<p> 153 154 <pre> 155 RV.render(document.body, 'Hello world."); 156 157 RV.render(document.getElementById('my_panel'), ['div', ...]); 158 </pre> 159 160 <p>RetroV can be used to render HTML from JavaScript once as a 161 template engine. Or it can be used to create interactive UIs with 162 functional "components".</p> 163 164 <p>The rest of this page is a series of increasingly interesting examples 165 that run live in the browser.</p> 166 167 <h3 id="startnote">Note</h3> 168 <p>This page makes heavy use of two utility functions. Both are part of this 169 page and have <em>nothing</em> to do with RetroV itself: 170 <ul> 171 <li><code>make_container()</code> creates a &lt;div&gt; and returns a 172 reference to it.</li> 173 <li><code>toggle_state()</code> creates a <strong>Toggle</strong> button 174 which renders "before" and "after" VNodes.</li> 175 </ul> 176 </p> 177 178 <p>With that out of the way, let's see some examples!</p> 179 180 <h2 id="simplerender">Simple rendering</h2> 181 182 183 <h3 id="text">Text</h3> 184 <p>Strings and numbers are rendered as text nodes.</p> 185 <script data-mirror> 186 c = make_container(); 187 188 RV.render(c, 'Hello world.'); 189 </script> 190 191 <h3 id="html">HTML nodes</h3> 192 <p>In RetroV, an HTML tag is represented by an array with the tag name 193 as the first element of the array. Here is a paragraph tag with a 194 text node child:</p> 195 <script data-mirror> 196 c = make_container(); 197 198 RV.render(c, ['p', 'Hello paragraph.']); 199 </script> 200 201 <h3 id="siblings">Arrays of siblings</h3> 202 <p>You can supply more than one element in an array. (<strong>Note:</strong> 203 you cannot have an array of strings because that would be indistinguishable 204 from an HTML tag.)</p> 205 <script data-mirror> 206 c = make_container(); 207 208 RV.render(c, [ 209 ['p', 'Paragraph one.'], 210 ['p', 'Paragraph two.'], 211 ]); 212 </script> 213 214 <h3 id="props">Element properties</h3> 215 <p>HTML tags can have a properties object as the second item. Let's make this 216 paragraph tag more interesting by assigning a class via the 217 <code>class</code> property. 218 After this object, any children follow as usual.</p> 219 <script data-mirror> 220 c = make_container(); 221 222 RV.render(c, ['p', {'class': 'aqua'}, 'Classy paragraph.']); 223 </script> 224 225 <p>Note: Sharp-eyed JavaScript developers will recognize that 'class' is a reserved 226 word and is not used as the actual property in the JS interface to the DOM. 227 RetroV automatically converts 228 <code>class</code> to <code>className</code> (and <code>for</code> to <code>htmlFor</code>) 229 for you. You are welcome to use the second form directly.</p> 230 231 232 <h3 id="classes">Class shorthand</h3> 233 <p>Because it's so common to apply a class name (or two) to 234 a tag, you can also use the "CSS selector" style shorthand 235 to apply one or more classes.</p> 236 <p>In addition, just a class name without a tag creates an 237 implicit &lt;div&gt;.</p> 238 <script data-mirror> 239 c = make_container(); 240 241 RV.render(c, [ 242 ['span.aqua', 'aqua'], 243 ['span.orange', 'orange'], 244 ['span.orange.bold', 'orange bold'], 245 ['.aqua', 'implicit div tag'], 246 ]); 247 </script> 248 249 <h3 id="styleprop">The style property</h3> 250 251 <p>Though <em>excessive</em> use of inline styles can get messy real fast, 252 sometimes they're unavoidable.</p> 253 254 <p>RetroV accepts a <code>style</code> property object containing the styles 255 you wish to set using standard JavaScript names (in which CSS properties 256 with hyphens are replaced with camelCase).</p> 257 258 <script data-mirror> 259 c = make_container(); 260 261 RV.render(c, 262 ['div', { 263 style: { 264 color: 'orange', 265 textShadow: '1px 1px #000, 3px 3px #0cf', 266 fontSize: '2em', 267 }}, 268 "Fancy!" 269 ] 270 ); 271 </script> 272 273 <h3 id="htmllit">HTML literals</h3> 274 275 <p>It is inevitable that you will eventually need to include some raw 276 markup in your interface. RetroV supports this. If it detects that you have an 277 element name that starts with "<code>&lt;</code>", it assumes the whole 278 string is raw verbatim HTML.</p> 279 280 <p>There are lots of ways to use this feature, including cloning another 281 element (and its children) by stealing its <code>innerHTML</code> content.</p> 282 283 <p>And let's face it, sometimes it's just easier to produce an HTML string 284 than create a nested array data structure and that's okay!</p> 285 286 <p><strong>Note:</strong> Only <em>one</em> top-level item will be created, 287 but it can have as many children as you like. (This may mean you need to 288 make a container span or div. Sorry. It has to be this way to keep the 289 child element counts in sync across changes.)</p> 290 291 <script data-mirror> 292 c = make_container(); 293 294 var svg_smiley = '<svg xmlns="http://www.w3.org/2000/svg" width="215.123" height="215.123" viewBox="0 0 56.918 56.918"><circle cx="28.459" cy="28.459" r="28.084" fill="#faf100" stroke="#070707" stroke-width=".75"/><ellipse cx="18.074" cy="20.509" rx="4.972" ry="7.382"/><ellipse cx="38.712" cy="20.509" rx="4.972" ry="7.382"/><path d="M11.739 38.311c7.173 11.435 26.554 11.585 33.419 0" fill="none" stroke="#070707" stroke-width="1.55"/></svg>'; 295 296 RV.render(c, ['.aqua', 297 ['<div style="font-size: 1.7em">Have a nice day!</div>'], 298 [svg_smiley], 299 ]); 300 </script> 301 302 303 304 <h3 id="nestednodes">Nested HTML nodes</h3> 305 <p>So far, we've seen tags with just a single text node child. 306 But arbitrary HTML structures can be nested as children.</p> 307 <script data-mirror> 308 c = make_container(); 309 310 RV.render(c, 311 ['.aqua', 312 'Enjoy', 313 ['.orange', 314 'those', 315 ['.aqua', 'antique'], 316 ], 317 ['.orange', 318 ['.aqua.bold', 'spicy'], 319 ['.aqua', 'rats'], 320 ], 321 ] 322 ); 323 </script> 324 325 326 <h2 id="dynamic">Dynamic updates</h2> 327 328 <p>When you render more than once to the same DOM element, RetroV will check 329 for changes in the "virtual DOM" VNodes from the previous rendering and apply 330 any differences to the real DOM.</p> 331 332 <h3 id="changingnode">Changing an HTML node property</h3> 333 334 <p>Here, the <code>class</code> (class) property is being updated. 335 Click the <strong>Toggle</strong> button to see the change applied. 336 Click it again to revert to the original state.</p> 337 <script data-mirror> 338 c = make_container(); 339 340 c.states = { 341 before: ['div', {'class':'aqua'}, 'Hello world'], 342 after: ['div', {'class':'orange'}, 'Hello world'], 343 }; 344 345 RV.render(c, c.states.before); 346 347 toggle_state(c); 348 </script> 349 350 <h3 id="changingcls">Same, but with class shorthand</h3> 351 352 <p>Change class property via shorthand and implicit div tags.</p> 353 <script data-mirror> 354 c = make_container(); 355 356 c.states = { 357 before: ['.aqua','Hello again'], 358 after: ['.orange','Hello again'], 359 }; 360 361 RV.render(c, c.states.before); 362 363 toggle_state(c); 364 </script> 365 366 367 <h3 id="addingitem">Adding an item to a list</h3> 368 369 <p>In the example below, a third &lt;div&gt; element is added to the 370 list. Clicking <strong>Toggle</strong> a <em>second</em> time removes the 371 element and so on.</p> 372 <p>Note how the new element "jumps" when it is added. This is done with 373 a CSS animation when the element is added to the DOM. 374 By watching which elements jump, you can see which nodes RetroV is 375 adding/replacing and which ones are being left alone. 376 (Property changes and text node changes won't make an element 377 jump, though. Only elements being added to the DOM.)</p> 378 379 <script data-mirror> 380 c = make_container(); 381 382 c.states = { 383 before: [['.aqua','A'],['.orange','B']], 384 after: [['.aqua','A'],['.orange','B'],['.aqua','C']], 385 }; 386 387 RV.render(c, c.states.before); 388 389 toggle_state(c); 390 </script> 391 392 393 <h3 id="changingsib">Changing a sibling element in a list</h3> 394 395 <p>Since the middle item is being changed from an (implicit) div to 396 a span tag, it will be replaced completely. It will "jump" as 397 the new element replaces the existing one. Notice how the two 398 elements on either side remain untouched since they have not changed.</p> 399 <script data-mirror> 400 c = make_container(); 401 402 c.states = { 403 before: [['.aqua','A'],['.aqua','B'],['.aqua','C']], 404 after: [['.aqua','A'],['span.orange','Q'],['.aqua','C']], 405 }; 406 407 RV.render(c, c.states.before); 408 409 toggle_state(c); 410 </script> 411 412 413 <h3 id="removingsib">Removing a sibling element from a list</h3> 414 415 <p>Completely removing an element from a list will cause all elements 416 after it to be re-evaluated. Since the tags alternate between divs and 417 spans, the new list won't line up with the old list and all the 418 following tags will end up being replaced entirely.</p> 419 420 <script data-mirror> 421 c = make_container(); 422 423 c.states = { 424 before: [['.aqua','A'],['span.orange','B'],['.aqua','C'],['span.aqua','D']], 425 after: [['.aqua','A'],['.aqua','C'],['span.aqua','D']], 426 }; 427 428 RV.render(c, c.states.before); 429 430 toggle_state(c); 431 </script> 432 433 434 <h3 id="nullingsib">"Nulling" a sibling element from a list</h3> 435 436 <p>On the other hand, replacing an element with the value <code>null</code> 437 will render an HTML comment placeholder, which keeps subsequent items lined up 438 in their original position.</p> 439 <p>Compare the "jumping" between this and the previous example. This one 440 is much more efficient since only the affected item is redrawn.</p> 441 442 <script data-mirror> 443 c = make_container(); 444 445 c.states = { 446 before: [['.aqua','A'],['span.orange','B'],['.aqua','C'],['span.aqua','D']], 447 after: [['.aqua','A'],null,['.aqua','C'],['span.aqua','D']], 448 }; 449 450 RV.render(c, c.states.before); 451 452 toggle_state(c); 453 </script> 454 455 <h3 id="nullingchild">"Nulling" a child</h3> 456 457 <p>You can use <code>null</code> anywhere you might otherwise have an element 458 (not just in an array, like above). Here, it is taking the place of several 459 child nodes. Notice how the last element does not jump since it is still in the 460 same child position.</p> 461 462 <script data-mirror> 463 c = make_container(); 464 465 c.states = { 466 before: ['.aqua','Flowers', 467 ['.orange','Roses'], 468 ['.orange','Sunflowers'], 469 ['.orange','Lavender'], 470 ], 471 after: ['.aqua','Flowers', 472 null, 473 null, 474 ['.orange','Lavender'], 475 ] 476 }; 477 478 RV.render(c, c.states.before); 479 480 toggle_state(c); 481 </script> 482 483 <h3 id="false">A <code>false</code> means "no change"</h3> 484 485 <p>This looks just like the <code>null</code> example above, except 486 with <code>false</code> in place of two of the items. The visual difference 487 is that the items remain. This is because as long as <code>false</code> 488 is given for a position, it will be <em>left alone</em>.</p> 489 490 <p>Note how the flowers "jump" when you click the <strong>Toggle</strong> 491 button a <em>second</em> time (as a non-false value is toggled back in).</p> 492 493 <p>See the Cookbook section below for an example of using <code>false</code> 494 to control rendering.</p> 495 496 <script data-mirror> 497 c = make_container(); 498 499 c.states = { 500 before: ['.aqua','Flowers', 501 ['.orange','Roses'], 502 ['.orange','Sunflowers'], 503 ['.orange','Lavender'], 504 ], 505 after: ['.aqua','Flowers', 506 false, 507 false, 508 ['.orange','Lavender'], 509 ] 510 }; 511 512 RV.render(c, c.states.before); 513 514 toggle_state(c); 515 </script> 516 517 <h3 id="oncreate">Special event: <code>oncreate</code></h3> 518 519 <p>It <em>should</em> be fairly rare, but sometimes you cannot avoid 520 directly manipulating DOM elements. RetroV has a special pseudo-event 521 called <code>oncreate</code> which takes a function. When the actual 522 DOM element for that virtual element is created and attached to the 523 DOM, the function is called and passed a reference to the element. 524 </p> 525 526 <p>In this example, we want to animate a div by manipulating the 527 element directly. We <em>could</em> do this by re-rendering the 528 entire scene through <code>RV.render()</code>, but that would be 529 wasteful.</p> 530 531 <script data-mirror> 532 c = make_container(); 533 534 function twitchy_start(elem){ 535 var twitch=false; 536 setInterval( 537 function(){ 538 elem.style.marginLeft = (twitch?'10px':'0'); 539 twitch=!twitch; 540 }, 541 800, // fraction of a second 542 ); 543 } 544 545 RV.render(c, ['.twitchy', 546 { 547 oncreate: twitchy_start, 548 }, 549 'Twitchy Div!', 550 ]); 551 </script> 552 553 <h3 id="rvid">Special property: <code>rvid</code></h3> 554 555 <p>RetroV 556 recognizes that you'll occasionally need to store a 557 reference to an element it has created. 558 You could easily do this yourself with the <code>oncreate</code> callback property above, but the result would be annoying boilerplate code.</p> 559 560 <p>An <code>rvid</code> property lets you pick a name 561 for an element and RetroV will store a reference to it 562 in its <code>id</code> namespace. 563 564 <p>In the example below, <code>rvid: 'neighbor'</code> 565 creates a reference to a &lt;div&gt; element as 566 <code>RV.id.neighbor</code>, which can be used as soon 567 as the element has been rendered.</p> 568 569 <script data-mirror> 570 c = make_container(); 571 572 function rvid_click(){ 573 RV.id.neighbor.classList.toggle('orange'); 574 } 575 576 RV.render(c, [ 577 ['.aqua', {rvid:'neighbor'}, 'Neighbor'], 578 ['button', { onclick: rvid_click }, 'Click me!'], 579 ]); 580 </script> 581 582 <h2 id="cookbook">Cookbook</h2> 583 584 <p>Ideas for solutions to common problems.</p> 585 586 <h3 id="renderingfn">Rendering with simple nested functions</h3> 587 588 <p>Here you can see that I've broken down the task of drawing lists of 589 numbers into drawing the list and drawing the numbers. It's a silly 590 example, of course, but the principle applies nicely to a larger and 591 more complex interface.</p> 592 593 <p>It's worth pointing out that in this case, RetroV doesn't know anything 594 about these functions. It's just seeing the returned data they generate.</p> 595 596 <script data-mirror> 597 c = make_container(); 598 599 function draw_number(num){ 600 return ['.orange', num]; 601 } 602 603 function draw_number_list(num1, num2){ 604 return ['.aqua', 605 draw_number(num1), 606 draw_number(num2), 607 ]; 608 } 609 610 RV.render(c, 611 ['.orange', 'My number lists:', 612 draw_number_list(42, 13), 613 draw_number_list(11, 999), 614 ] 615 ); 616 </script> 617 618 619 <h3 id="renderingfn2">Rendering with higher-order functions</h3> 620 621 <p>Creating interfaces by generating data also plays extremely well with 622 <em>functional programming</em> concepts, such as using <code>map()</code> to 623 render an array with a function.</p> 624 625 <p>(Map is a higher-order function because it takes another function 626 as input.)</p> 627 628 <script data-mirror> 629 c = make_container(); 630 631 var fruits = [ 632 'Apple', 633 'Pear', 634 'Lime', 635 'Strawberry', 636 ]; 637 638 function draw_fruit(num){ 639 return ['.aqua', num]; 640 } 641 642 RV.render(c, ['.orange', 'My fruit:', fruits.map(draw_fruit) ]); 643 </script> 644 645 <h3 id="stateclosures">Storing state in function closures</h3> 646 647 <p>RetroV is built with the philosophy that storing and updating state 648 should be separate from rendering the result of that state.</p> 649 650 <p>Thus, "components" which track a <em>lot</em> of state are antithetical to 651 the intention of RetroV. Having said that, it is nice to be able to 652 keep track of simple things locally sometimes.</p> 653 654 <p>Note that the <code>feed()</code> function in this example doesn't 655 just update the counter, it also re-renders <em>everything</em>. 656 The whole point of using a VDOM is to let the library detect changes 657 and efficiently perform only the updates that are needed.</p> 658 659 <script data-mirror> 660 // This example needs a unique container variable. 661 var c1 = make_container(); 662 663 function Animal(name, color){ 664 var fed = 0; 665 666 function feed(){ 667 fed++; 668 render_animals(); 669 } 670 671 return function draw_animal(){ 672 return ['div', {'class':color}, 673 name + ' has been fed: ' + fed + 674 (fed === 1 ? ' time.' : ' times.'), 675 ['button', {onclick:feed}, 'Feed'], 676 ]; 677 }; 678 } 679 680 var tiger = Animal('Tiger', 'orange'); 681 var fish = Animal('Fish', 'aqua'); 682 683 function render_animals(){ 684 RV.render(c1, [tiger(), fish()]); 685 } 686 687 render_animals(); 688 </script> 689 690 <h3 id="renderfalse">Controlling rendering with <code>false</code></h3> 691 692 <p>You may wish to have a section of a page only render 693 (or <em>stop</em> rendering) when some condition has been met.</p> 694 695 <p>This example has a "component" that renders exactly twice. It does 696 this by returning <code>false</code> after the second render.</p> 697 698 <p>Noticec how the area's render counter will continue to go up, but the 699 "component" will stop incrementing at 2.</p> 700 701 <script data-mirror> 702 // This example needs a unique container variable. 703 var c2 = make_container(); 704 705 function RendersTwice(){ 706 var render_count = 0; 707 708 return function draw(){ 709 render_count++; 710 711 if(render_count > 2){ 712 return false; 713 } 714 715 return ['.aqua', 'Renders Twice: ' + render_count]; 716 }; 717 } 718 719 var area_render_count = 0; 720 var renders_twice = RendersTwice(); 721 722 function render_area(){ 723 area_render_count++; 724 725 RV.render(c2, ['.orange', 'Area rendered: ' + area_render_count, 726 renders_twice(), 727 ['button', {onclick:render_area}, 'Re-Render'], 728 ]); 729 } 730 731 render_area(); 732 </script> 733 734 <h3 id="input">Text input</h3> 735 736 <p>This isn't a special technique, but just an example of an extremely 737 common interaction that deserves an example somewhere.</p> 738 739 <p>There are countless ways to add abstraction to handle the tedious 740 redundancy of form elements. This example does not demonstrate any.</p> 741 742 <p>Keep in mind that RetroV is a rendering library. It has absolutely no 743 opinion about how you save/load/update data.</p> 744 745 <script data-mirror> 746 // This example needs a unique container variable. 747 var c3 = make_container(); 748 749 var my_data = { 750 name: "Nothing", 751 age: 0, 752 }; 753 754 function update_name(e){ 755 my_data.name = e.target.value; 756 render_form(); 757 } 758 759 function update_age(e){ 760 my_data.age = e.target.value; 761 render_form(); 762 } 763 764 function render_form(){ 765 RV.render(c3, ['form.aqua', 766 ['label', 'Name:', 767 ['input', { 768 type: 'text', 769 value: my_data.name, 770 oninput: update_name, 771 }], 772 ], 773 ['label', 'Age:', 774 ['input', { 775 type: 'text', 776 value: my_data.age, 777 oninput: update_age, 778 }], 779 ], 780 ['p', '"I am ' + my_data.name + ', ' + my_data.age + ' years old."'], 781 ]); // end of form 782 } 783 784 render_form(); 785 </script> 786 787 788 <h3 id="textarea">Textarea updates</h3> 789 790 <p>This is another note that isn't actually specific to 791 RetroV, but deserves an example because it's a confusing 792 topic.</p> 793 794 <p>The HTML textarea element doesn't have a 795 <code>value</code> <em>attribute</em>. Instead, we write a 796 textarea's value as content in the the tag like so: 797 <code>&lt;texarea&gt;Hello!&lt;/texarea&gt;</code>. 798 However, when interacting with the element, its 799 <code>value</code> <em>property</em> contains the current 800 value of the textarea.</p> 801 802 <p>Play with this:</p> 803 804 <script data-mirror> 805 // This example needs a unique container variable. 806 var c4 = make_container(); 807 808 var foo_count = 0; 809 function draw_textarea(){ 810 foo_count++; // change with each draw 811 var txt = "Foo " + foo_count; 812 813 RV.render(c4, ['.aqua', 814 ['textarea', txt], 815 ['textarea', {value: txt}], 816 ['button', {onclick: draw_textarea}, "Update!"], 817 ]); 818 } 819 820 draw_textarea(); 821 </script> 822 823 <p>At first, both examples update as you hit the Update 824 button. However, once you type into the textareas, only the 825 one that updates the <code>value</code> property will 826 update. That's because whatever you type becomes the 827 <code>value</code> and supersedes the <em>text content</em> 828 in the tag!</p> 829 830 831 <h3 id="focus">Element focus</h3> 832 833 <p>The <code>oncreate</code> pseudo-event is one of the few ways to 834 make sure certain dynamic properties such as input focus are handled 835 correctly on a page in certain circumstances.</p> 836 837 <p>This particular example is silly, but it's the sort of real problem 838 that crops up in interfaces all the time.</p> 839 840 <script data-mirror> 841 c = make_container(); 842 843 var my_input = null; 844 845 function input_created(elem){ 846 my_input = elem; 847 } 848 function focus_input(){ 849 my_input.focus(); 850 } 851 852 RV.render(c, ['.orange', 853 ['input', {oncreate:input_created}], 854 ['button', {onclick:focus_input}, 'Focus the input'], 855 ]); 856 </script> 857 858 <br><br> 859 860 <hr> 861 862 Read more about this library at 863 <a href="http://ratfactor.com/retrov/">http://ratfactor.com/retrov/</a>. 864 865 866 867 <!-- End of Demo/Tutorial content! --> 868 869 <script> 870 // This helper displays the source of script tags: 871 872 /*/ The Mirror of Galadriel 2 873 * Copyright 2023 Dave Gauer (ratfactor.com) 874 * Released under the MIT License. 875 /*/ 876 document.addEventListener("DOMContentLoaded", function(e) { 877 var scripts = document.querySelectorAll('script[data-mirror]'); 878 if(scripts.length<1){ 879 console.log("Galadriel's Mirror 2: No scripts with data-mirror found."); 880 } 881 scripts.forEach(function(script){ 882 // remove initial blank line from script (if any) 883 var text = script.innerHTML 884 .replace(/^\r?\n/, '') 885 .replaceAll('<', '&lt;') 886 .replaceAll('>', '&gt;'); 887 // create <pre> to mirror text contents 888 var mirror = document.createElement('pre'); 889 mirror.innerHTML = text; 890 script.parentNode.insertBefore(mirror, script); 891 }); 892 }); 893 </script> 894 </body> 895 </html>