You can add the RetroV library to a page with nothing more than a script tag like so:
<script src="retrov.js"></script>
This exposes a global RV
object with a render()
method. The first render parameter is a target DOM element to render into. The
second parameter is an element or tree of "virtual node" data to be
rendered.
RV.render(document.body, 'Hello world."); RV.render(document.getElementById('my_panel'), ['div', ...]);
RetroV can be used to render HTML from JavaScript once as a template engine. Or it can be used to create interactive UIs with functional "components".
The rest of this page is a series of increasingly interesting examples that run live in the browser.
This page makes heavy use of two utility functions. Both are part of this page and have nothing to do with RetroV itself:
make_container()
creates a <div> and returns a
reference to it.toggle_state()
creates a Toggle button
which renders "before" and "after" VNodes.With that out of the way, let's see some examples!
Strings and numbers are rendered as text nodes.
In RetroV, an HTML tag is represented by an array with the tag name as the first element of the array. Here is a paragraph tag with a text node child:
You can supply more than one element in an array. (Note: you cannot have an array of strings because that would be indistinguishable from an HTML tag.)
HTML tags can have a properties object as the second item. Let's make this
paragraph tag more interesting by assigning a class via the
class
property.
After this object, any children follow as usual.
Note: Sharp-eyed JavaScript developers will recognize that 'class' is a reserved
word and is not used as the actual property in the JS interface to the DOM.
RetroV automatically converts
class
to className
(and for
to htmlFor
)
for you. You are welcome to use the second form directly.
Because it's so common to apply a class name (or two) to a tag, you can also use the "CSS selector" style shorthand to apply one or more classes.
In addition, just a class name without a tag creates an implicit <div>.
Though excessive use of inline styles can get messy real fast, sometimes they're unavoidable.
RetroV accepts a style
property object containing the styles
you wish to set using standard JavaScript names (in which CSS properties
with hyphens are replaced with camelCase).
It is inevitable that you will eventually need to include some raw
markup in your interface. RetroV supports this. If it detects that you have an
element name that starts with "<
", it assumes the whole
string is raw verbatim HTML.
There are lots of ways to use this feature, including cloning another
element (and its children) by stealing its innerHTML
content.
And let's face it, sometimes it's just easier to produce an HTML string than create a nested array data structure and that's okay!
Note: Only one top-level item will be created, but it can have as many children as you like. (This may mean you need to make a container span or div. Sorry. It has to be this way to keep the child element counts in sync across changes.)
So far, we've seen tags with just a single text node child. But arbitrary HTML structures can be nested as children.
When you render more than once to the same DOM element, RetroV will check for changes in the "virtual DOM" VNodes from the previous rendering and apply any differences to the real DOM.
Here, the class
(class) property is being updated.
Click the Toggle button to see the change applied.
Click it again to revert to the original state.
Change class property via shorthand and implicit div tags.
In the example below, a third <div> element is added to the list. Clicking Toggle a second time removes the element and so on.
Note how the new element "jumps" when it is added. This is done with a CSS animation when the element is added to the DOM. By watching which elements jump, you can see which nodes RetroV is adding/replacing and which ones are being left alone. (Property changes and text node changes won't make an element jump, though. Only elements being added to the DOM.)
Since the middle item is being changed from an (implicit) div to a span tag, it will be replaced completely. It will "jump" as the new element replaces the existing one. Notice how the two elements on either side remain untouched since they have not changed.
Completely removing an element from a list will cause all elements after it to be re-evaluated. Since the tags alternate between divs and spans, the new list won't line up with the old list and all the following tags will end up being replaced entirely.
On the other hand, replacing an element with the value null
will render an HTML comment placeholder, which keeps subsequent items lined up
in their original position.
Compare the "jumping" between this and the previous example. This one is much more efficient since only the affected item is redrawn.
You can use null
anywhere you might otherwise have an element
(not just in an array, like above). Here, it is taking the place of several
child nodes. Notice how the last element does not jump since it is still in the
same child position.
false
means "no change"This looks just like the null
example above, except
with false
in place of two of the items. The visual difference
is that the items remain. This is because as long as false
is given for a position, it will be left alone.
Note how the flowers "jump" when you click the Toggle button a second time (as a non-false value is toggled back in).
See the Cookbook section below for an example of using false
to control rendering.
oncreate
It should be fairly rare, but sometimes you cannot avoid
directly manipulating DOM elements. RetroV has a special pseudo-event
called oncreate
which takes a function. When the actual
DOM element for that virtual element is created and attached to the
DOM, the function is called and passed a reference to the element.
In this example, we want to animate a div by manipulating the
element directly. We could do this by re-rendering the
entire scene through RV.render()
, but that would be
wasteful.
rvid
RetroV
recognizes that you'll occasionally need to store a
reference to an element it has created.
You could easily do this yourself with the oncreate
callback property above, but the result would be annoying boilerplate code.
An rvid
property lets you pick a name
for an element and RetroV will store a reference to it
in its id
namespace.
In the example below, rvid: 'neighbor'
creates a reference to a <div> element as
RV.id.neighbor
, which can be used as soon
as the element has been rendered.
Ideas for solutions to common problems.
Here you can see that I've broken down the task of drawing lists of numbers into drawing the list and drawing the numbers. It's a silly example, of course, but the principle applies nicely to a larger and more complex interface.
It's worth pointing out that in this case, RetroV doesn't know anything about these functions. It's just seeing the returned data they generate.
Creating interfaces by generating data also plays extremely well with
functional programming concepts, such as using map()
to
render an array with a function.
(Map is a higher-order function because it takes another function as input.)
RetroV is built with the philosophy that storing and updating state should be separate from rendering the result of that state.
Thus, "components" which track a lot of state are antithetical to the intention of RetroV. Having said that, it is nice to be able to keep track of simple things locally sometimes.
Note that the feed()
function in this example doesn't
just update the counter, it also re-renders everything.
The whole point of using a VDOM is to let the library detect changes
and efficiently perform only the updates that are needed.
false
You may wish to have a section of a page only render (or stop rendering) when some condition has been met.
This example has a "component" that renders exactly twice. It does
this by returning false
after the second render.
Noticec how the area's render counter will continue to go up, but the "component" will stop incrementing at 2.
This isn't a special technique, but just an example of an extremely common interaction that deserves an example somewhere.
There are countless ways to add abstraction to handle the tedious redundancy of form elements. This example does not demonstrate any.
Keep in mind that RetroV is a rendering library. It has absolutely no opinion about how you save/load/update data.
This is another note that isn't actually specific to RetroV, but deserves an example because it's a confusing topic.
The HTML textarea element doesn't have a
value
attribute. Instead, we write a
textarea's value as content in the the tag like so:
<texarea>Hello!</texarea>
.
However, when interacting with the element, its
value
property contains the current
value of the textarea.
Play with this:
At first, both examples update as you hit the Update
button. However, once you type into the textareas, only the
one that updates the value
property will
update. That's because whatever you type becomes the
value
and supersedes the text content
in the tag!
The oncreate
pseudo-event is one of the few ways to
make sure certain dynamic properties such as input focus are handled
correctly on a page in certain circumstances.
This particular example is silly, but it's the sort of real problem that crops up in interfaces all the time.