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 <script src="retrov.js"></script>
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 <div> 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 <div>.</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><</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 <div> 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 <div> 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><texarea>Hello!</texarea></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('<', '<')
886 .replaceAll('>', '>');
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>