{isMobile.V ? 'Mobile' : ''}
Reactive HTML through the OtoReact framework

Introduction

Summary

OtoReact is a library that brings reactivity straight into your HTML file.
The tiny application above left is the result of this piece of Reactive HTML (RHTML):


    

The Reactive HTML is placed in an HTML document that loads the OtoReact library, e.g. like this:


        

This code is served to the browser and that's all.

Full framework functionality, offered by a library.

Nothing to install, nothing to compile, nothing to configure.

Runs like a charm.

What is it?

'Reactive web applications', or 'Single Page Applications', are web applications that, when possible, react immediately on user input, timer events etc, or the receival of server data requested by the application, instead of having the web server generate and send a new HTML page.
This results in both a much better user experience and much less server load.
Two tiny examples you see above; please enter some data.

Reactivity is attained by means of JavaScript programming code running inside the web browser. The JavaScript code has to manipulate the so-called "DOM" ("Document Object Model"), which is the internal object model of a web page ("document").
Writing such JavaScript by hand can get quite complicated and might result in very cluttered programming code. A framework makes it much easier to create reactive web applications.

OtoReact is a small and fast framework library to attain reactivity by loading so-called Reactive HTML, or RHTML, straight into the browser:

  • Reactive HTML is based on HTML; JavaScript is needed only for data manipulation and event handling. Not for DOM manipulation.
    Even components are defined in HTML without a single line of JavaScript.
  • Reactive HTML is parsed by the browser itself, and compiled and executed by OtoReact within milliseconds.
  • Reactive HTML is easy to learn, and one doesn't need to install a compiler, a bunch of libraries, node.js, or anything.
    Just a text editor (like Visual Studio Code) suffices.
  • Reactive HTML makes it easy to build dynamic pages based on data, even if no reactivity is needed.
  • Reactive HTML makes it easy to follow the Model-view-controller design pattern, separating your data model and program logic from the presentation.
  • Reactive HTML can build dynamic CSS style sheets; you may not need a separate CSS framework.
  • Reactive HTML has a powerful component system, URL routing, global error handling, some persistence, et cetera.
    • Components are defined right within the HTML, without needing any JavaScript statements
    • Components are lightweight and have local scope.
    • Components can be (mutually) recursive
    • Component definitions can be nested: a component definition can contain its own local component definitions
  • OtoReact can be combined with other tooling: it can be used for fragments of a larger application, and does not modify any native objects (prototypes) which might cause conflicts. It defines a few global variables but does not depend on them.

Samples

What's your name

Let's return to the tiny example above left. You can modify the source code below and the modified application will be reloaded immediately.

Explanation:

  • DEFINE rvar='yourName' introduces a "reactive variable" yourName, that can contain state information of the application.
    • yourName.V denotes the value of the variable.
    • We have now added store='sessionStorage', which makes the value persist when the application is reloaded.
  • @value="yourName.V" binds the value of the reactive variable to the value of the input element. Whenever the input element receives input, OtoReact will update the reactive variable and all document content that reacts on it.
  • IF cond=… is an RHTML conditional. Whenever the JavaScript condition is 'truthy', in this case when yourName.V is non-empty, then the body of the conditional is rendered.
  • \{yourName.V\} and \{yourName.V.length\} are embedded expressions. The rendered document will contain the value of the expression between the braces.

Multiplication tables

Feel free to increment the numbers!

Explanation:

  • DEF rvar=maxY … is an abbreviation of DEFINE …. It declares a reactive variable maxY with initial value 10, to persist in sessionStorage.
  • Attributing INPUT type=number @valueAsNumber=maxY.V declares that, at any input event, the numeric value of the input element shall be assigned to maxY.V. The rest of the application shall react on the new value.
  • FOR let=y of="range(1,maxY.V)" lets local variable y iterate through the values of range(1,maxY.V), which are the numbers 1 to maxY.V.

Tic-Tac-Toe

Study the source code after you have read more of this documentation.

Working with server data

Here is an example of dynamically building a table based on server data, with a bit of animation as well:

Remarks

Other frameworks

There exist quite a number of alternative libraries and frameworks to make it easier to build reactive web applications. I distinguish two main categories:

  1. Some, like JQuery and React, offer tools to make it easier to manipulate the DOM.
    All manipulation is still done from within the JavaScript you write, and you need a good understanding of DOM manipulation and JavaScript in general.
    The respective libraries can in some cases be loaded straight into the browser without compilation.
  2. Others, like Angular and Svelte, make a clean separation between the programming code and presentation. The JavaScript you write contains the data model and data manipulation you need; the actual web page is described by a template file containing HTML enriched with directives and other stuff.
    You don't need to write code to manipulate the DOM at all anymore; this is done by the framework.
    The source code files have to be compiled on you development machine. You need to install the compiler, and quite some modules for each application (which for Angular may take more than 1GB per application).

Reactive HTML combines features of both approaches, giving you the advantages of describing the desired reactive layout by a HTML template, separate from your programming code, but without the hassle of installing and using a compiler and managing quite a number of configuration files, and you need just a basic understanding of JavaScript.

Reactive !== Responsive

Reactive is not the same thing as responsive, though there is some overlap.
Resonsive web design is a phrase used to indicate web pages that adapt themselves to the device they are viewed on.

This document for example changes its layout when being printed or viewed on a narrow screen: the table of contents moves to the top, and on a mobile device font sizes are adapted.

Responsiveness is usually attained by using CSS media queries, which is perfectly possible in Reactive HTML.
When you get stuck with media queries, then reactivity may come to the rescue, to make responsive adaptations to your document or style definitions not possible with media queries.

No server-side functionality

OtoReact does not provide server-side functionality. You need other tooling for that, like Node.js (+ Express), Python, PHP, .Net, or perhaps you can use existing web API's.

Use file extension .html, not .rhtml

By using file extension ".html" for RHTML files, file editors can provide HTML syntax features and servers can serve the files just like HTML files without needing additional configuration.

Using ".rhtml" might also cause confusion with Ruby on Rails HTML files, that are to be processed by the server-side Ruby on Rails engine.

Document setup

  1. Download OtoReact.js from the download page and place it somewhere on your server.
    It's less than 50 kilobyte, and has no dependencies at all.
  2. Include the OtoReact compiler into your application:
    <{}script type=module src="OtoReact.js"><{}/script>
  3. Mark the body element of your document with type="rhtml":
    <{}body type="rhtml">
    , then OtoReact will implicitly compile and render the document body at the first event cycle.
  4. You may also want to hide all or part of your application until it has been built, e.g. by using: <{}body type="rhtml" hidden>.
    OtoReact will unhide its target after it has been built.
Or, instead of step 2 and 3, one can explicitly call the OtoReact compiler using the RCompile function:
<{}script type=module>
    import \{RCompile, RVAR} from './OtoReact.js';
    RCompile(document.body);
<{}/pre>
This allows one to compile one or more parts of your document, instead of the whole document body, and it allows one to specify additional options

That's all!

RHTML Concepts

This chapter introduces a number of basic RHTML concepts, but it also contains full documentation of all features.
Some sections you can skip on first reading are marked with a '*'.

String interpolation: text with embedded expressions

All text content and all HTML attributes inside RHTML code, except scripts, expressions and event handlers that are already JavaScript, may contained embedded JavaScript expressions between braces, like 1 + 1 = \{1 + 1}, which results in the text "1 + 1 = {1 + 1}".
This is called string interpolation or, in JavaScript terminology, 'template literals'.

The expressions are evaluated, converted to string, and inserted as text; there is absolutely no risk of code injection.

To include a normal pair of braces in RHTML, at least one of them must be escaped with a backslash.

Within JavaScript, you can of course use the JavaScript syntax for template literals, using backquotes and dollar signs:

let x = `Some text ${expression} et cetera`;

If you prefer, you may add a dollar sign in RHTML as well, like 1 + 1 = $\{1 + 1}.

* If you set the option 'bDollarRequired', then the dollar sign becomes compulsory, and you can write normal braces without a backslash.

Expression values null and undefined are not shown. This is unlike JavaScript template literals, where `$\{null}` yields "null".

You may omit the expression, like \{}, which comes in handy if you want the parser to not recognize an HTML tag:

RHTML constructs overview

RHTML defines a number of new constructs, which dynamically build your HTML page:

  • DEFINE introduces a local variable or reactive variable
  • IF specifies a conditional block of RHTML
  • CASE specifies a series of alternative conditional blocks of RHTML
  • FOR specifies a repeating block of RHTML, with a number of additional features
  • COMPONENT defines a user-defined construct.
    Components may be recursive and may have slots, which are themselves full-fledged constructs.
  • IMPORT asynchronously imports components defined in a separate file (a module) into your application
  • INCLUDE asynchronously includes a separate RHTML file into your application
  • RHTML dynamically compiles a string as RHTML.
    This is used in the demo component you see on this page.
  • REACT on optionally allows you to specify which blocks of RHTML should react on which variables, optimizing your application.
    The RHTML attribute reacton does the same thing.
  • DOCUMENT name defines a separate reactive child document that can be opened in separate (popup) windows, or can be printed without being opened.
  • ELEMENT inserts a variably named element.
  • ATTRIBUTE adds a variably named attribute to its parent HTML element.
Note: We write construct names in uppercase and attribute names in lowercase here, but as in HTML, both are case independent.

RHTML functions

The OtoReact module makes the following functions available for import. They are added to the global environment as well.
The question marks indicate optional arguments.
  • RCompile(HTMLElement, options?) compiles and builds the given HTMLElement as RHTML, using the given options.
  • RVAR(name?, initialValue?, store?) creates a reactive variable.
  • reroute() and docLocation are used with URL routing.
  • range(start?, count, step?) yields an iterable range of count numerical values: start, start+step, …, start+(count-1) * step.
    You can use this with FOR.
  • * RFetch(resource, init?) is the same as fetch(resource, init?), except that it throws an error when an HTTP error status is received.

All of these are exported by the OtoReact module. TypeScript type declarations are available on the download page.

Local and global variables, 'globalThis'

Ordinary JavaScript in a browser environment distinguishes between local variables, module variables and global variables:
  • Local variables are visible within a single block \{ … \} of JavaScript code
  • Module variables are visible within a single module, and may be exported and imported into other modules
  • Global variables are visible in all JavaScript code running in the context of a single browser window:
    • Embedded scripts
    • External scripts
    • HTML inline event handlers
Browser-based JavaScript also has a distinction between classic JavaScript and module JavaScript, more about that in our section on SCRIPT.

RHTML local variables

RHTML adds another kind of variable: RHTML local variables; these are visible in all inline JavaScript code within a block of RHTML code.

These are introduced by the following RHTML constructs:

  • Constructs DEFINE and SCRIPT type="otoreact/local" introduce local variable(s) visible in the surrounding block, following the construct closing tag.
  • Constructs FOR, CASE value with capturing, and COMPONENT templates with parameters introduce local variables visible within the construct block.

RHTML local variable names obey strict lexical scoping rules, see DEFINE for a demonstration.

RHTML local variables are not visible within other SCRIPT embedded or external scripts; these are executed just once in global scope.

Global variables in RHTML

Global variables are visible in RHTML inline scripts, of course.

Global variables are created:

  • Either as properties of the JavaScript global object, which is preferably refered to as globalThis:
    globalThis.varName = value;
    In web browsers, the global object is commonly named "window", but in other JavaScript environments it is named otherwise, and using "window" causes confusion with real window properties. Hence ECMAScript 2020 introduced the new cross-platform name globalThis to refer to the global object, and we suggest you use this rather than window.
  • Or by using the defines attribute of SCRIPT type="otoreact".

RVAR's: Reactive VARiable's

Reactive variables (RVAR's) are objects containing variable data on which RHTML elements may react.
Anytime the value of an RVAR changes, the reacting elements will be updated.

RVAR's are created:

  • either by calling RVAR(name?, initialValue?, store?, subscriber?, storename = `RVAR_$\{name\}`) from JavaScript,
  • or by using the define rvar construct.

RVAR arguments

name
If you supply a name, the RVAR will be registered in the global environment under that name and will be visible anywhere.
So if you write:
    <{}script type=module>
        const x = RVAR('X');
        …
, then it will be available as x just inside this module, and also as X anywhere (though normally one shouldn't use different local and global names).
initialValue
Provides the initial value of the RVAR.
When initialValue is a Promise, then the value of the RVAR will initially be undefined, and when the promise resolves to a value, then the RVAR will be set to that value.
store*
Provides simple persistence functionality, see Persistence.
subscriber*
A routine which is subscribed to the RVAR, as by x.Subscribe(subscriber).
storename*
See Persistence.

You may or must inform OtoReact which fragments of RHTML should react on which RVAR's, by using the REACT element or the reacton attribute, see below.
For RVAR's created by calling RVAR(), this is necessary.
For RVAR's created by DEFINE rvar, if there are no explicit react's and no other subscribers, then all RHTML code following the DEFINE rvar, i.e. the range where the RVAR is visible, will implicitly react on the RVAR.

Properties and methods

An RVAR x is an object, distinct from the value of the variable. It has the following properties and methods:

x.V
To get or set the value of an RVAR x, one writes x.V.
When x.V is set to a different value than it had before, then the RVAR is marked as dirty, and all RHTML code that reacts on it will be updated.
x.U
When you modify not the value of a RVAR, but properties of the value, you may want the RVAR to be marked as dirty as well.
You can do this by writing x.U to get the value of the RVAR.

E.g., if x.V is an array, you can write x.U.push(e) or x.U[i] = e to add an or modify an array element, and the DOM will react on the modified array. So you don't have to assign to x.V to trigger a reaction.

Exception: within an HTML attribute or property definition, accessing x.U does not mark the RVAR as dirty. This is so that one can use x.U within RHTML two-way bindings, and the RVAR will only be marked dirty when the property is modified by an event, not when it is being set by the RHTML engine.

Setting x.U sets the value of x and marks it as dirty even when the value is equal to the previous value.

x.SetDirty()
The RVAR can be explicitly set dirty by calling x.SetDirty().
x.Subscribe(subs, bImmediate?, bInit = bImmediate) *
Routine subs is registered as a subscriber to x, so it will be called whenever x has been set dirty.
When bImmediate is truthy, subs will be called immediately every time x is being set dirty; otherwise it will be called once at the next event cycle.
When bInit is truthy, then subs will initially be called when it is registered. The default value for bInit is the value of bImmediate.
x.UnSubscribe(subs) *
Unregisters a routine subs previously registered as a subscriber to x.
x.Set(value) *
Sets x.V either synchronously, or asynchronously when value is a Promise.

When it is a Promise, then x.V will initially be undefined, and when the promise resolves to a value, then x.V will be set to that value.

x.Set *
Provides a routine that sets the value of x, i.e. v => x.Set(v).

This is handy to create an errorhandler. E.g., if errMsg is an RVAR that should receive error messages, then you can write doSomething().catch(errMsg.Set) to catch the errors of some asynchronous routine, or you can add an attribute #onerror="errMsg.Set" to catch all errors within a block of RHTML.

x.Clear *
Provides a routine that clears the value of x.

You can e.g. add an attribute #onsuccess="errMsg.Clear" to clear any error message when any event handler succeeds without error.

Persistence*

When you provide a store parameter to RVAR() or to define rvar, then the variable value will be retrieved from that store when the variable is created, and stored at every update.

store can be:

  • sessionStorage, meaning that the value will be restored when the user reloads the page, or leaves it and returns again while staying in the same browser window
  • localStorage, meaning that the value will be preserved in local browser storage and restored when the user returns to the same site in the same browser on the same machine
  • Any other object that supports the setItem and getItem methods of the Storage interface.
    It could be an object that saves values to a database.

The RVAR must have a unique storename; the default is `RVAR_$\{name\}`.

An example using sessionStorage can be found in the Multiplication demo: modify the numbers; then either reload the page or modify the source code, which triggers a reload too, and see that the modified numbers have persisted.

Scripts in RHTML

Scripts can be included anywhere using the SCRIPT type? element just as in HTML.
Depending on the script type, they are either executed just once, or every time the surrounding element is being instantiated (built).

Scripts in OtoReact can export variables, so that these variables are either globally defined or locally visible in RHTML code.

See <{}SCRIPT> for details about SCRIPT in RHTML.

Style sheets

Style sheets can be included anywhere using the STYLE element as in HTML. OtoReact will move them to the document head so they don't get repeated or lost.

Styles sheets have global effect, except inside an 'isolated' component or inside RHTML text.
There is no provision yet for style sheets with local scope. To use local styling, just add local class names to each styling rule.

Style sheets inserted with STYLE cannot contain interpolated strings, because the HTML parser does not allow that.
To build up dynamic style sheets using string interpolation and other OtoReact features, you can use RSTYLE.

How it works

OtoReact's RCompile does not, as one might perhaps expect, translate the whole chunk of RHTML into one large string of JavaScript.
Rather:
  1. Each snippet of JavaScript is compiled separately into a routine, by offering it to JavaScripts eval function (in global scope, of course).
    E.g., an RHTML property expression a+b is compiled by calling eval("([a,b]) => (a+b)").

    OtoReact does not itself parse and analyse the JavaScript, so it is unaware which variables are actually used and which are not.

  2. While traversing the RHTML document tree, all these tiny routines are combined by JavaScript functions into "builder" routines, which can build ór update the DOM tree according to the RHTML. Each RHTML source node corresponds to a separate builder routine.
  3. The root builder routine is called to perform the initial build.
  4. Each time a builder routine is called to build a DOM node, or a range of DOM nodes, it creates a so-called 'Range' object that refers to the created range of nodes and contains other meta-information as needed. This information is used when the builder routine is called again to update the same range of nodes.
  5. While building, any part of the resulting DOM tree which is marked to react on some RVAR, is registered with the RVAR together with its local 'environment' containing the value of all local variables at that point, and a local builder routine which can update just that part of the DOM tree.
  6. After the DOM tree has been built, the browser engine will compute the graphical document layout and graphically render its contents.
    It will do so again anytime the DOM tree changes.
  7. Whenever some RVAR is marked dirty, it will schedule (by setTimeout) all reacting DOM tree parts to get updated by their registered builder routine in its registered environment.
    The updating takes place at the next 'event cycle'.
    While updating, all present HTML elements will remain in place as much as possible, so that any elements that had received focus or input data will keep that.

Limitations inflicted by the parser

Using the browser HTML parser inflicts us some limitations, as it isn't tailored for RHTML. Such as:
  • Element arrangement
    The HTML parser forces elements to be arranged according to HTML rules, and will silently rearrange them if they are not.
    Notably:
    • Table-related elements TABLE, THEAD, TBODY, TFOOT, TR may only contain appropriate children.
      The same holds for elements SELECT, DATALIST, OPTION, and perhaps others.
      If, e.g., a table would contain a for loop containing a tr:
      
                      , then the (Chromium) parser will move the for before the table, while leaving the tr inside.
                      
      Thus OtoReact sees:
      
                      , and will give a misleading error about x being undefined.
                      
      To get the loop right, OtoReact allows you to put one (or more) dots after any tag name, so the parser won't recognize this as a table and won't interfere:
      
                      When building the DOM tree, the dots will be removed.
                  
    • The paragraph-element p does not allow block-level elements inside, but allows unknown elements.
      Thus, if you write
      
                      , without closing the p, perhaps because you know the parser will automatically close it when needed, or because you didn't know block-level element ul is not allowed inside p, then the parser will silently close the p before the ul but after the if, because if is unknown to the parser.
                      
      So the ul ends up outside the if, without you or OtoReact being informed:
      
                      To avoid this, make a habit of always closing all elements.
                  
  • Attribute names are always lowercase
    As attribute names in HTML are case-insensitive, the parser makes them all lowercase.
    • As RHTML sometimes uses attribute names as bound variables, such variables will be lowercase, even if you wrote them with uppercase, and OtoReact can't warn you about this.
    • There is also a feature for conditional classes (#class:someclass="someBoolean"), which will only work for class names in lowercase.
    • RHTML allows you to use attributes to set DOM and Style properties, but these property names are case sensitive. So OtoReact receives the property name in lowercase and has to restore proper casing.
  • Missing error messages
    There are more cases where the browser doesn't inform you of errors, which may result in strange behaviour.

I imagine Reactive HTML one day being natively supported by the browser. In that case, these limitations can be lifted.

Problem solving

  • When you get weird errors, you may have forgotten to add dots where needed.
    Otherwise, you may want to check how the browser has parsed your document:
    1. Outcomment or remove your compiler invocation: // RCompile(…)
    2. Reload your application
    3. Inspect the parsed DOM-tree using your browsers development tools. Either:
      • Right click in your document and select 'Inspect'
      • Press F12 or Ctrl-Shift-I and select tab 'Elements' (Chrome) or 'Inspector' (Firefox)
  • When your application doesn't react on events, you may have forgotten some reacton attribute.
  • Errors while building the DOM are by default inserted into the DOM output, but may sometimes be invisible or get overlooked. Look in the development console to be sure you haven't missed any error messages, or enable option bAbortOnError.
  • When an event handler or some other piece of JavaScript doesn't work as expected, you might insert the debugger statement, to get a breakpoint at that point when the browser development tool is opened.

HTML elements

All source elements that are not RHTML or user-defined constructs, build HTML elements.

All trailing dots in the tag name are removed.

Source attributes are compiled as described below.

HTML attributes

All source attributes that do not fall in any of the categories below, are compiled as HTML attributes.
They may use string interpolation, and trailing dots are removed.

Attributes starting withan underscore _ are ignored. You can use this to insert comments within HTML tags, or to outcomment any attribute.

Trailing dots are removed, so you can use error.="..." to set an HTML onerror handler, whereas onerror="..." would set the RHTML onerror handler.

DOM properties: #

The browser translates attributes specified in HTML elements to properties of the corresponding DOM objects.
Rather than setting attributes, RHTML allows you to directly set the DOM properties, by prepending the property name with a hash mark and specifying a JavaScript expression: #propertyName="expression"

Note that while attributes always have string values, even when the content of the string is numeric, properties can have any type.
So by setting a property rather than an attribute, you avoid unnecessary type conversions, and you spare an attribute node in the created DOM tree.
Furthermore, there are some DOM properties that are not available as HTML attributes at all.

Documentation of all properties can be found at MDN or partly at W3schools.

When the expression yields value null or undefined and the property had a string value, then the property is set to the empty string, because otherwise the DOM would turn the value into string "null" or "undefined".

* OtoReact will discover the proper casing of propertyName the first time it is being set.

* There are two special cases:

  • HTML attribute class corresponds not to DOM property class (because that's a reserved word in JavaScript), but rather to property className.
    RHTML however allows you to simply set #class, which translates to #classname.
  • DOM property valueAsNumber returns the numeric value of an input type=number element, but cannot be set in some browsers, which would make it unusable for two-way binding.
    RHTML however allows you to set #valueasnumber, which translates to setting #value for this element, so one can use two-way binding on this property.
Notes:
  • For properties with string values, setting #name="expr" has usually the same effect as name="\{expr\}".
  • For events, setting onname="script" is completely the same as onname="(event) => \{ script }".

HTML DOM events

In HTML, attributes whose name starts with "on" are not normal attributes but event handlers: pieces of JavaScript that will be executed when some event happens.
They may contain the name event which represents an object containing more information about the event.

In RHTML, this is exactly the same, see onclick="x.V += 1" in the demo above.

  • RHTML event handler attributes do not have string interpolation; they are already JavaScript and braces have their normal JavaScript meaning.
  • RHTML event handlers may contain RHTML local variables; these will be bound to their values. Assignments to these variables (but not to their properties) are ignored.
  • As a bonus, the keyword this in all RHTML event handlers is bound to the current HTMLElement object.
    (So you don't have to write event.target or event.currentTarget.)
  • When the handler initiates an asynchronous operation, it may return the resulting Promise. If such a Promise fails, the error will be caught by an RHTML onerror handler.

DOM event properties

For each DOM event, say onclick, there is a corresponding DOM property that can be set with #onclick:
  • Setting onclick="Statements" is the same as #onclick="(event) => \{ Statements \}"
  • Setting #onclick="handlerFunction" is the same as onclick="return (handlerFunction)(event);"

RHTML global attributes: reacton, thisreactson, #if

RHTML recognizes the following global attributes on every element and construct.
reacton="RVAR-list" or reactson="RVAR-list"
This tells OtoReact that the whole DOM-tree built by this node should react on the listed comma-separated RVAR's.

For RVAR's created by calling RVAR(), this is compulsory.
For RVAR's created by DEFINE rvar, this is optional.

The items in RVAR-list may be expressions rather than just names.

Samples of using reacton are in the Tic-Tac-Toe demo.

thisreactson="RVAR-list"
This tells OtoReact that only the attributes of the current node itself should react on the listed RVAR's. Not its contents, unless specified otherwise.
#if="condition" or if="condition"
The element or construct with this attribute will be built only when the given condition evaluates to a truthy value.
So this is a shorter version of the IF-construct with no elsepart.
When you want to apply these attributes to a range of nodes at once, you can enclose the range in REACT ….

RHTML pseudo-events: oncreate, onupdate, ondestroy*

RHTML implements some additional event-like properties that are not triggered by browser engine but by the OtoReact engine:
oncreate, aftercreate
Executed after an element (and its children) has been created (built).
onupdate, afterupdate
Executed after an element (and its children) has been updated.
ondestroy, afterdestroy
Executed after an element has been destroyed, i.e. removed from the DOM tree.
Combinations of these, notably oncreateupdate
Executed after an element (and its children) has been created (built) or updated.
beforecreate, beforeupdate, beforedestroy, and combinations of these
Executed before an element has been created, updated, destroyed.
The name event is not available in handlers for these pseudo-events, but this is available and will be bound to the HTMLElement the handler is attached to.

These pseudo-events may be attached to RHTML constructs too. In that case this will be bound to the nearest parent HTMLElement.

As with DOM events, one can write on…="…" to specify a function to be called, instead of a block of statements to be executed.

RHTML onerror, onsuccess*

Normally, errors occuring in HTML event handlers are logged to the console and not handled otherwise, unless you explicitly add e.g. a try-catch construction to each and every error-prone event handler.

RHTML allows you to set an error handler for all error cases at once, as follows.

onerror
Executed whenever an (uncaught) error occurs in the following situations:
  1. During any synchronous DOM event handler attached to the current element or any of its HTML descendants
  2. During the asynchronous execution of a Promise returned by a DOM event handler, i.e. when the Promise is rejected.
    So if your handler includes a call to some asynchronous function doSomething, then it must return the resulting promise, in order for asynchronous errors to be caught, e.g.: onclick="return doSomething()" or #onclick="doSomething"
  3. While creating or updating the current element or its children
  4. While resolving an asynchronously defined local variable value, see DEFINE.
The name event is bound to the error value (usually a string).
onerror-
Executed in situations 1 and 2 listed for onerror, so not while creating or updating an element.
onsuccess
  1. Executed when any (synchronous) DOM event handler terminates without throwing an uncaught error.
  2. If the handler returns a Promise, then onsuccess is executed only after the promise succeeds ("is fulfilled").
If you use onerror to assign errormessages to an RVAR to be shown in your document, then you can use onsuccess to reset that message.
Alternatively, you can use the window.onerror global event handler of the DOM API.

If you want to handle the DOM event onerror of e.g. an img or audio element, then you must add a dot: img src="…" onerror.="…"

Class names

"Class names" are used in HTML to select CSS styling rules from a stylesheet, and can of course be set with the class attribute or the #className DOM property.

Besides these, RHTML recognizes the following attributes:

#class="stringExpression"
This is an alias to set the #className property. The expression may yield a list of class names, separated by spaces.
#class:name="booleanExpression"
or
#class.name="booleanExpression"
Class name name will be added in lowercase to the element if and only if the expression evaluates to true (or another truthy value).
Note that the HTML parser transforms name into lowercase, so you cannot use this for class names containing uppercase characters.
+class="expression"
When the value of the expression is a string, then it is added to the list of class names.
When it is an array, then every element of the array is added to the list of class names
When it is an object, then for every property name in the object, name will be included in the list of class names if and only if the property value is truthy.
Just as other DOM properties, class names are recomputed at every update of the element.

Inline styles

HTML, and hence RHTML, allow you set style properties of individual elements with the style global attribute. These are called "inline styles".
The corresponding style DOM property yields the inline style as an object, but can't be set.
One can, however, set its properties. RHTML recognizes the following attributes:
style.name="string"
Style property name will be set to string, applying string interpolation.
#style.name="expression"
Style property name will be set to the value of expression.

When the value is null, empty, or undefined, then the style property will be reset.

When the value is false, then the style property will also be reset.
This allows you to abbreviate a conditional style setting like #style.name="cond ? expr : null" to #style.name="cond && expr"

+style="objectExpression"
The expression should evaluate to an object.
Every property name in the object will be set or reset in the inline style object.

Output and two-way bindings: @*!+

DOM properties are not static but can change. RHTML allows four variants of output binding for properties to expressions that are valid 'assignment targets' (or 'left-hand side' expressions): *, +, ! and !!.
Valid assignment targets are expressions that can be assigned to: global variables, array elements, and object properties, including RVAR values.
*propertyName="assignmentTarget"
The assignment target receives the value of the property immediately after the element and its children have been created.
This is the same as setting a handler:
oncreate="assignmentTarget = this.propertyName"
+propertyName="assignmentTarget"
The assignment target receives the value of the property immediately after the element and its children have been updated.
This is the same as setting a handler:
onupdate="assignmentTarget = this.propertyName"
!propertyName="assignmentTarget"
The assignment target receives the value of the property at every input event.
This is the same as setting a handler:
oninput="assignmentTarget = this.propertyName"

For elements which allow text input, an input event happens at every keystroke. You see the effect in several samples on this page.

!!propertyName="assignmentTarget"
The assignment target receives the value of the property at every change event.

Note that only input, select, and textarea produce change events.
For elements which allow text input, a change event happens when the user presses ‹Enter› or when the element looses focus, so less often than input events.

These may be combined with each other and with #propertyName to get two-way bindings, for example:
#!propertyName="assignmentTarget"
is the same as #propertyName="assignmentTarget" !propertyName="assignmentTarget".
So the property receives the value of the assignment target at element creation and update, and the assignment target receives the value of the property at every input event.

Last but not least, there are two abbreviations:

@propertyName="assignmentTarget"
This is the same as #!propertyName="assignmentTarget".
So the property receives the value of the assignment target at element creation and update, and the assignment target receives the value of the property at every input event.
@@propertyName="assignmentTarget"
This is the same as #!!propertyName="assignmentTarget".
So the property receives the value of the assignment target at element creation and update, and the assignment target receives the value of the property at every change event.

These bindings are especially useful for the value or checked property of input elements, or the textContent, innerText, or innerHTML properties of elements with contentEditable=true.
See the example in the Persistence paragraph.

Or you can use them to capture the innerHTML or outerHTML of an element:

RHTML Constructs

N.B. All construct attributes which must be JavaScript expressions, like cond and of, may optionally be prefixed with a hash mark: cond, of.
Their meaning remains the same.

Any attribute starting with an underscore "_" is ignored. So one can use this to insert a comment right between attributes (_="Some comment") or to comment out some attribute.

Otherwise, it is an error when a construct instance has unknown attributes.

<{}DEFINE var #value>, Local Variables

DEFINE var=name #value="expression"/DEFINE

introduces a local immutable variable name, with a value computed by expression.
Alternatively, one can give an interpolated string: value="string". So for an expression, the '#' is required.
The variable is visible in all local expressions from the DEFINE till the end of the enclosing element, except when obscured by nested definitions.
It cannot be assigned to, but when its value is an RVAR or some other object, then one can of course modify its properties.

The expression is, by default, evaluated only once, when the respective DOM-trunk is built, not when it is updated.

When you add an attribute updating or reacting (without value), then the expression is re-evaluated at every update.

DEFINE rvar=name #value?="expression" store?=storage async?/DEFINE

introduces a local reactive variable name, with a value computed by expression.
This is equivalent to DEFINE var=name #value="RVAR(null, expression, storage)"/DEFINE.
When store is specified, then the rvar value is stored in the given storage, see Persistence.

When async is specified, then expression must yield a Promise. The value of the rvar wil initially be undefined, and when the promise resolves to a value, then the rvar will be set to that value.

DEFINE may not have any child nodes, other than comments or white space.

DEFINE may be abbreviated to DEF.

Local variable names (including loop variables and component parameters) obey strict lexical scoping rules:

<{}CASE> <{}WHEN cond> <{}ELSE>

<{}CASE hiding?>
    <{}WHEN cond="expr"> WhenPart <{}/WHEN>
    …
    [<{}ELSE> ElsePart <{}/ELSE>]
<{}/CASE>
is a conditional piece of RHTML.
  • There may be multiple WHEN parts.
  • The first WhenPart for which the value of expr is true (or truthy), is included in the document.
  • If none is truthy, and there is an ELSE part, then ElsePart is included.
  • When hiding is specified, then all alternatives will be included in the DOM tree, but the unwanted ones will be hidden.
    This may result in slower initial building, but faster switching between alternatives (especially when using thisreactson).

Pattern Matching

Furthermore, CASE can select an alternative based on pattern matching against a given string value.

Substrings of the value can be captured into new local variables.

There are three variants of patterns:

<{}CASE #value="expr">
    <{}WHEN match="pattern"> … <{}/WHEN>
    <{}WHEN urlmatch="pattern"> … <{}/WHEN>
    <{}WHEN regmatch="regExp" captures?="nameList"> … <{}/WHEN>
    …
    [<{}ELSE> ElsePart <{}/ELSE>]
<{}/CASE>
All three variants are case-insensitive, and all can be combined with a simple condition cond.
match="pattern"
The value is matched against the given 'simple pattern', which may contain the following wildcards:
  • ? matches any single character
  • * matches any string of characters
  • [charString] matches any character in charString, where all rules for JavaScript character classes apply
  • \{ varName \} matches non-greedily any string of characters, and
  • \ followed by any character matches just that character
The whole value has to match, not just a substring.
urlmatch="pattern"
This form is meant to be matched against (parts of) an URL, and is useful for routing.
The recognized patterns are the same simple patterns as above, but any substring captured in a local variable will be URL-decoded.
regmatch="regExp" captures?="nameList"
regExp can be any JavaScript regular expression pattern.

The match succeeds when a substring of the string value matches against regExp.

When captures="nameList" is specified, then the values captured by any capturing groups in the pattern will be stored in local variables with the specified names, in order of appearance. Use empty names to skip unneeded capturing groups.

Patterns with captures cannot be combined with hiding, as no DOM-tree can be built when the variable values are unknown.

<{}IF cond> <{}THEN> <{}ELSE>

<{}IF cond="expr">
    ThenPart
    [<{}ELSE> ElsePart <{}/ELSE>]
<{}/IF>
or
<{}IF cond="expr">
    <{}THEN> ThenPart <{}/THEN>
    [<{}ELSE> ElsePart <{}/ELSE>]
<{}/IF>
is the same as:
<{}CASE>
    <{}WHEN cond="expr"> ThenPart <{}/WHEN>
    [<{}ELSE> ElsePart <{}/ELSE>]
<{}/CASE>

All hiding and pattern-matching features of CASEWHEN apply to IF as well.

<{}FOR let of index?>

<{}FOR let=name of="iterableExpression">
    BodyPart
<{}/FOR>

The normal FOR-construct repeats its BodyPart for each item yielded by the value of iterableExpression.

The value of iterableExpression must be an iterable object, e.g. an Array, or the result of the RHTML range function, if you want a numeric repetition.

Attribute let=name (or var=name) is compulsory, but name may be missing or empty. When nonempty, name becomes a local (immutable) variable bound to the respective item.

Further on, FOR can have the following attributes:
index=indexName
When present, indexName becomes a local variable bound to the zero-based index number: 0 for the first item, 1 for the second, and so on.
When index is present but indexName omitted, then index='index' is assumed.
previous=prevName
When present, prevName becomes a local variable bound to the preceeding item in the iteration, or undefined for the first item.
This allows one to easily check for changed properties within the iteration.
When prevName is omitted, then 'previous' is assumed.
next=nextName
In the same way, nextName becomes bound to the next item in the iteration.
key="keyExpression"
When a FOR-construct has to be updated, items in the iteration may have been removed, added, reordered, or replaced.
OtoReact does not make any assumption about these items; it will by default completely rebuild the FOR DOM-tree on every update.

You may however specify a keyExpression, which may refer to name and indexName and should compute a unique "key" value for each item in the iteration.
In that case OtoReact will use this to identify old and new items. When an item in a new iteration has the same key value as an item in the previous iteration, then OtoReact won't rebuild its DOM-tree, but will move its previous DOM-tree to its (possibly) new position and will update it in-place.
This has the following effects:

  • It is faster
  • If any input element in the tree had received focus or inputdata, then it will keep that focus or data
  • Any oncreate handlers won be executed again
  • If the RHTML in BodyPart created any RVAR's or other objects, then these won't be renewed but will keep their previous value

When items in the iteration are always distinct, then the key may be the item itself: key=name.
When items in the iteration always remain in place, then the key may be the index number: key=indexName

hash="hashExpressionList"
Processing can be further speeded up by specifying one or more comma-separated "hash expressions".
OtoReact cannot quickly detect whether some property (deep) inside an object has changed, so it will by default update all subtrees when updating the FOR-construct, meaning it will recompute all properties, conditions, et cetera.

If, however, you specify a hashExpressionList, then OtoReact will assume an item (identified by its key) has not changed when the values of the expressions in hashExpressionList haven't changed, and it won't spend time on updating its DOM subtree.

The hash expression need not be a real hash function; it could be a possibly long string.
If, e.g., items are read from an external source and parsed using JSON.parse, then one could preserve the unparsed string and use it as the hash value.

reacting (or reactive)
The loop variable name is immutable, but events may modify its properties. If you need the DOM subtree to react on property changes, then you can add attribute reacting (or reactive) to make name reactive.

When reacting is present, then name gets a property name.U, which yields the value of name and marks it as dirty, just as with reactive variables.
Name does not become a full RVAR: one cannot get or set name.V. One could call it an RVAR-light.

Note that just name is marked dirty, not the source (array) of the iteration. If you need elements of your application outside the local subtree to react on changes, then you can use the updates attribute described below.

updates="rvarList"
If you want changes to properties of name to have reactions outside the local subtree, then you can use updates="rvarList".
rvarList must be a comma-separated list of zero or more RVAR's.

When present, then name becomes reacting as above, and whenever name is marked dirty (by a reference to name.U), then all RVAR's in rvarList> will be marked dirty too.

You can find an example of this in the todo-list below: whenever an item is (un)marked as done, then both lists that depend on TODO are updated.

<{}SCRIPT type? src? defines?>

Scripts can be included almost anywhere in your application, either embedded or through an external src reference.

External scripts may be written in TypeScript, of course.

You have to be aware that scripts included in the main document, except those tagged with nomodule, are automatically executed by the browser before OtoReact has had any opportunity to intervene.

You have also to be aware that scripts in browser-based JavaScript come in two flavours: classic JavaScript and JavaScript modules, see MDN, and that OtoReact is imported as a JavaScript module.

Outside the main document, both classic and module scripts will be executed by OtoReact, and OtoReact add a number of extra options.
Together, you have the following alternatives:

SCRIPT src? or SCRIPT type="text/javascript" src?
  • When these elements occur in the main document file, the browser will execute the internal or external script as classic JavaScript while parsing the file.
    That is before OtoReact has been loaded, so it cannot immediately execute OtoReact functions like RVAR.
    Any names defined in the script are automatically put in the global scope and are available in RHTML code.
    One can use strict mode by placing the statement "use strict"; or 'use strict'; (including the quotes) at the beginning of the script.
    One can add async or defer as described on MDN.
  • Outside the main file, in included or imported RHTML files, OtoReact will execute the internal or external script as classic JavaScript while compiling the file.
    OtoReact functions are available.
    OtoReact tries to mimick browser behaviour, but it cannot detect which names have been defined, and does not automatically put them in global scope. You need to explicitly define them as properties of the global object, as with type="module", or switch to SCRIPT type="otoreact" defines?="nameList".
    One can use strict mode as above.
    With attribute async, fetching and executing is done asynchronously, and awaited for when the respective DOM tree is being built.
    With attribute defer, execution is postponed till the first time the DOM tree is being built.
SCRIPT type="module" src?
  • When this element occurs in the main document file, the browser will asynchronously execute the internal or external script as a JavaScript module. Multiple module scripts are executed in order of appearance.
    That means that it will be executed after the whole document has been parsed and, assuming that you import and call OtoReact at the top of your document, after OtoReact has been loaded and has compiled your document, but before the document has been built.
    To execute OtoReact functions, one can either import the functions from OtoReact.js, or invoke them from global scope.
    Names defined in a JavaScript module are not automatically put in the global scope. You have to explicitly define them as properties of the global object: window.name = … or better: globalThis.name = … before they are available in RHTML code.
    Modules use strict mode automatically.
  • Outside the main file, OtoReact will asynchronously execute the script as a JavaScript module, and await for it when building the DOM tree.
SCRIPT type="otoreact" src? defines?="nameList",
SCRIPT type="otoreact/global" src? defines?="nameList"
OtoReact asynchronously fetches the internal or external script, and executes it once as classic JavaScript the first time the DOM tree is being built.
Names are not automatically defined in global scope, but one can provide a comma-separated defines="nameList" telling which names should be put in global scope.
OtoReact invokes strict mode automatically.
SCRIPT type="otoreact/static" src? defines="nameList"
OtoReact asynchronously fetches the internal or external script, and executes it once as classic JavaScript the first time the DOM tree is being built.
Names listed in nameList are not put in global scope, but are defined as OtoReact local variables.

So when this type of script is used inside, e.g., a component, then the script is executed just once, the values of the listed names are internally remembered, and visible as local variables just inside the component.

SCRIPT type="otoreact/local" src? defines?="nameList" updating?
This type of script is executed every time the surrounding RHTML is being built.
With attribute updating, the script is executed every time the surrounding RHTML is being built or updated.
Local variables from the surrounding RHTML are visible inside the script, and names listed in nameList are defined as new OtoReact local variables.

So when this type of script is used inside a component template, then each instance of the component will have its own set of data.

SCRIPT type="otoreact; type=module" src? defines?="nameList"
SCRIPT type="otoreact/global; type=module" src? defines?="nameList"
OtoReact asynchronously executes the internal or external script as a JavaScript module.
Fetching and executing the script is initiated when compiling the containing file, and awaited for when first building the DOM tree.
When a nameList is given, then the script is required to export the listed names, and OtoReact will define them in global scope.
SCRIPT type="otoreact/static; type=module" src? defines="nameList"
OtoReact asynchronously executes the internal or external script as a JavaScript module.
Fetching and executing the script is initiated when compiling the containing file, and awaited for when first building the DOM tree.
The script is required to export the names listed in nameList, and OtoReact will define them as OtoReact local variables.

<{}REACT on? hash? renew?>

REACT on=RVARlist
    Content
/REACT
specifies that the given content should react on changes to any of the listed RVAR's. So whenever one or more of the RVAR's have changed, the content will be updated.

With attribute hash="expressionlist", the given content will only be updated when an update is requested and the value of some expression(s) in the list has changed. This can improve performance.

With attribute renew, the given content will never be updated, but will be removed and rebuilt on every update request.

A REACT element without content will not produce any DOM elements, and can be useful to insert oncreate or onupdate handlers in between elements.

Introduction to Components

COMPONENT defines a new user-defined construct, or custom element.

We start with a gentle introduction into components, which is followed by a full descripion of all concepts and features, and some more examples.

Signatures, templates, and instances

Suppose one wishes to insert a single multiplication table (or 'times table') on multiple places within one application.
Then one could want to define a construct (1):


    that is to be replaced by the output of (2):

where R and F are JavaScript variables representing the values of the construct properties #rows and #factor.

HTML expression (1) is called the component 'signature', RHTML code (2) is called its 'template', and #rows and #factor are called its 'parameters'.

The construct can be defined by writing:



The new construct can subsequently be used like:


This is called an 'instance' of the construct.

Instances are fully reactive:

Parameters

There are several kinds of parameters, like interpolated strings, JavaScript expressions, event handlers, two-way parameters. These are further described described in the section on Signatures.

In all cases, parameters represent data.

Slots

Besides parameters, components can have slots.

Slots represent not data, but a small or large document part , including document markup, or even a reactive sub-application.
A component slot is like a placeholder, to be filled by the user of the component. So an instance of the component must provide definitions of the actual values of the component slots. These definitions are actually templates just like component templates.

Slots can have parameters and slots themselves ('subslots'). So a slot is itself characterized by a signature, just as a component.

Inside a component, slots can be instantiated just like used-defined constructs. So in OtoReact, we have three types of constructs:

  • Those defined by OtoReact itself, like DEFINE, CASE, and COMPONENT
  • Constructs defined by the user by means of COMPONENT
  • Inside a (component or slot) template: constructs corresponding to the slots of the construct that's being defined by the template

Now note:

  • A construct signature contains a signature of each contruct slot
  • A construct template can contain instances of each construct slot
  • A construct instance can or must contain templates for each construct slot

Content slots

A component instance may contain content (text and elements) that does not correspond to any slot name. The component definition can use this spurious content as well by including a content slot, which is a slot named "CONTENT" or starting with "CONTENT". All instance content not corresponding to any slot slot name forms a template for this content slot.

For example, to define a construct ablank that just creates an a element with an extra target="_blank" attribute, one writes:



Notes:
  • There may be at most one content slot
  • Naturally, slots (other than content slots) can have a content slot as well
  • Content slots can have (non-content) slots as well, see the Radiogroup example

<{}COMPONENT recursive? encapsulate?>

A component definition consists of a component signature, optionally one or more style sheets, scripts, local definitions etc., and finally a component template:

<{}COMPONENT recursive? encapsulate?>

  <{}SIGNATURE> Signature <{}/SIGNATURE>
  or just:
  Signature

  <{}STYLE …> … <{}/STYLE>…
  <{}SCRIPT …> … <{}/SCRIPT>…
  <{}DEFINE …> … <{}/DEFINE>…

  <{}TEMPLATE …> Template <{}/TEMPLATE>
  
<{}/COMPONENT>
  • The signature describes how invocations of the component (or construct), called 'instances', look like. The signature may specify parameters and slots.
  • The template describes what content should be generated for each component instance. Inside the template, parameters are available as JavaScript variables, and slots are available as RHTML constructs. So the template may contain instances of the slots.
  • Instances may or must specify values for each parameter, and contain blocks of RHTML code (or actually templates) for each slot.
  • With attribute recursive, the component can be recursive: the template may contain instances of the component itself. Also see Mutual recursion.
  • With attribute encapsulate, the generated content will be encapsulated, meaning mainly that the component can contain its own styling rules, independent of the main document. See encapsulate for details.
  • Component templates can contain nested component definitions.
  • Component templates can use local variables defined outside of the component.
    So a nested component definition can use the parameter values of the outer component.

Note: RHTML component templates and slots are not the same as HTML web component templates and slots. It's the same idea but a different syntax and implementation, both simpler and more powerful.

Signatures

The first element inside COMPONENT is its signature.
It is optionally surrounded by SIGNATURE.
E.g.:


    
  • The tag name of the signature element ('Repeat' in this example) defines the (case-insensitive) name of the component.
  • Attributes like name, #name, or @name of the signature element define the parameters of the component.
    Instances of the component must have the same attributes, and these correspond to local variables inside the component template.

    There are special cases:

    • Attributes with a question mark name? or with a nonempty default value name="string" or #name="expression" define optional parameters.
      When an instance doesn't have this attribute, then the specified default string or expression is used instead, or undefined when there is no default.

      An empty string name="" is not recognized as a default value, because the HTML parser does not distinguish between a missing value and the empty string.
      To specify the empty string as default value, one must use an empty string expression, like: #name=" '' ".

      The default default value is undefined.

    • Attributes name where name starts with "on" represent event handlers.
      Values are compiled as JavaScript, and the default default value is the empty handler.
    • Attributes @name define two-way parameters.
      Their values must be valid assignment targets.
    • An attribute prefixed with three dots ...name is a rest parameter. It must be the last parameter.
      All instance attributes that do not match a normal parameter, nor an RHTML global attribute or pseudo-event, are collected into this rest parameter, which can be passed on to an HTML element.
  • Each child element of the signature element defines a slot of the component.
    These child elements are themselves signatures: slot signatures.

    For each slot, component instances may have one or more child elements that should match the slot signature.
    The slots are available as constructs within the component template.

    When building a component instance, the content of any instance child element that matches a slot name becomes the (slot) template for the slot instances within the component template

    As slots have signatures, they can have parameters and slots themselves.

  • Slot names starting with "CONTENT" are special. When a component signature contains such a CONTENT* slot, then all content of a component instance that does not match any other slot, forms a template for this special slot.
  • There may be at most one such content slot

Templates

The component template is tagged TEMPLATE, and must be the last element child of \COMPONENT. E.g.:

  • Parameters of the component (like count) are available as local variables inside the template.
  • The value of the local variable name representing a two-way parameter @name will be a new RVAR for each created instance of the component.
    The RVAR value name.V will be the value of the assignment target provided in the instance, and whenever a new value is assigned to the RVAR, that value will be assigned to the assignment target as well.
  • Parameter names may be repeated in the template tag, like TEMPLATE #count or TEMPLATE count.
    Or they may specify a local variable name, like: TEMPLATE #count=Count. This is useful when you want to have uppercase characters in the local variable name, because the parameter name itself, being an attribute name, is always seen as lowercase by the parser.
  • Slots of the component (content in this case) are available as constructs inside the template
  • The template of a component with slots may contain a special kind of FOR-loop which iterates over the slot templates

Instances

A component is instantiated by a tag containing the component name:


Parameter values

A component instance may/must have attributes that provide values for all optional/compulsory parameters of the template. There are four forms:
  • By default, the value is specified as an interpolated string: name="string"
  • When the name is prefixed with a hash mark, the value is computed as a JavaScript expression: #name="expression"
  • When name starts with "on", then the value is specified as a block of JavaScript statements, just like an event handler: name="statements"
    The value of the parameter will be the routine function(event) \{ statements \}.
  • The value provided for a two-way parameter @name must be a valid assignment target.
  • As with standard constructs, attribute names starting with an underscore "_" are ignored.

When the component has a rest parameter, then the values specified by any remaining attributes are collected into that rest parameter. Otherwise it is an error when there are remaining attributes.

Slot templates

The component instance may contain templates for each of the slots of the component. So just as the component definition contains a component template that can be instantiated by a client, so the component instances may contain slot templates that can be instantiated within the component template.
  • Slot templates are tagged by the slot name (not by template)
  • Slot templates must be direct children of the component instance. They are to be recognized at compile time, so they cannot be inside a CASE or some other construct.
  • When the slot signature contains parameters, then these names must be repeated in the slot template tag.
  • There may be multiple templates for the same slot. By default these are concatenated.
  • When the component signature contains the special slot name content, then all content of the component instance that is not some (other) slot template, forms a template for the content slot.
Now in this case, the slot is named content indeed, so we can shorten the component instance to:


Note how the instance tag repeat carries both  the component parameter value #count=7 and the content slot parameter name #num.

Putting it all together, we get:

Mutual recursion

With the attribute recursive, the component template may contain instances of the component itself. There is an example below.

Mutual recursion between components means a series of multiple component definitions that may contain instances of each other.

Mutual recursion is possible by combining multiple signatures and multiple templates within a single COMPONENT definition, like this:


Notes:
  • Each template is tagged by its component name.
  • There must be exactly one template for each signature.
  • This form of defining multiple components at once is possible for non-recursive components as well.

Encapsulate*

The COMPONENT tag may have an attribute encapsulate.
  • Without encapsulate, instances of the component are replaced by the output of the component template.

    Any styling rules of the surrounding document apply to the component output as well, and vice versa.
    To make styling rules apply only to the component content, use CSS class names.

  • With encapsulate, COMPONENT defines a new custom element.
    The output of the component template defines the appearance of the element, but is encapsulated inside, using the so-called Shadow DOM.

    Any styling rules of the surrounding document do not apply to the encapsulated content, and vice versa. The component can have its own style sheet(s), independent of the main document.
    Styling rules and inline styles specifically targeted at the custom element, however, are inherited by the encapsulated content.

    The internally generated custom element name is required to contain a hyphen. If the component name does not already contain a hyphen, then a prefix 'rhtml-' is prepended.
    This concerns the generated output only, component instances use just the component name, but styling rules targeted at the custom element must use the prefix.

More examples

To-do application

Here is a to-do application, which splits the collection of items into those done and not done. We use a component itemlist to show such a filtered list.

A recursive component

Components may be recursive, i.e. refer to themselve, when the attribute recursive is specified.
Here is a recursive component, which images a JavaScript nested list of lists:

Redefining HTML elements

Components may redefine HTML elements. To refer to the original element, add a dot to the tag name.
Here is a component that redefines a to ensures that all links with no target and an external href, get a target='_blank' attribute:

To instantiate the original a HTML element, write a..

Slots within slots*

As there are no restrictions on slot signatures, slots can have slots themselves (subslots). So when a component template instantiates a slot, it may provide templates for its subslots, and these subslots can be instantiated within the slot templates of a component instance.
This is particularly useful for components with a CONTENT slot. The component may provide new or redefined constructs, that are available only within the contents of component instances, and that may provide interaction with the component.

This documentation website uses such subslots to redefine H1, H2 within the context of a particular page, so that the given section titles are automatically entered into the table of contents of that page.

A radio group component*

HTML provides radio buttons input type=radio bearing a name and a fixed value, and buttons with the same name constitute a group.
When they are within a form, and the form is submitted to the web server, then the value of the currently selected button within each group is sent to the server.

For a reactive application, using form works a bit clumsily.
The following custom component does a much better job; it allows one to bind an RVAR, or any other object property, directly to the selected radio button value, without needing a form and without repeating any code.

Let me explain the somewhat complicated radiogroup signature.
  • radiogroup has a parameter 'name' which will be the common name of all radio buttons in the group, and a two-way parameter '@value', that will provide and receive the value of the selected radio button.
  • radiogroup has a slot content so that the component output can include the content of any radiogroup instance.
  • Slot content has itself a slot radiobutton, that will be available as a new construct anywhere within the radiogroup instance contents.
  • radiobutton has parameter '#value' that defines the value of the current button, and an optional 'onclick' handler so that component instances can specify this handler without overwriting the 'onclick' handler set by the component template, and a rest parameter '...rest' that will be passed on to the input type=radio radio button element.
  • radiobutton has a slot content, that defines the label content for the current button.

<{}FOR of> over component slots

When a custom construct (a component or a slot) has a slot slot, then instances of the construct may provide multiple templates for that slot.
Now, inside the construct template, one can write:
<{}FOR of=slot>
    Body
<{}/FOR>
When this template is instantiated, Body will be repeated for each provided slot template, and slot instances inside Body will be replaced by the output of just that template, rather than by the concatenation of all slot templates.

Here is a component which allows one to define tables column by column:

<{}INCLUDE src>

INCLUDE src="URL"/INCLUDE allows you to include another RHTML file as if it were inserted into the main file.
  • If the INCLUDE element has any content other than comments or whitespace, then that content is processed and URL is ignored.
    (This allows a preprocessor to combine all files into one file.)
  • Otherwise, the file is fetched from URL and compiled asynchronously: compiling the main file will continue while the included file is being fetched.
  • URL may not use string interpolation, because the URL is needed at compile time.

  • URL may be a relative URL. In that case it is interpreted relative to the location of the current HTML file.

To speed things up, you can insert a preload link in the main document header:

<{}link rel=preload href="URL" as=fetch crossorigin>
("crossorigin" seems incorrect, but Google Chrome requires it.)

Using Server Side Includes

A drawback of using INCLUDE may be that not all search engines will see the content of the included file.
Some engines, including Google, do execute JavaScript and will see the full document as it is built by OtoReact, while others won't.
So if you want all search engines to see the content of your document, then do not use INCLUDE.
An alternative might be to use server-side technology like Server Side Includes (SSI) to include your file, if your server supports it.

It is possible to use both types of inclusion at once, if you have e.g. a production server that supports SSI and a development environment that does not:


  • In an environment that does not support SSI, the second line is ignored as a comment, and OtoReact will fetch the file and include its content.
  • In an environment that supports SSI, the server will include the file in place of the second line, and:
    1. All search engines will see its content
    2. OtoReact will process the included content rather than fetching the file separately.

<{}MODULE id?>

MODULE encapsulates a series of components, definitions, and other content, that can be imported at other places using IMPORT.

A MODULE can either be saved in a separate file, or be included anywhere in the main file.
It may even be concatenated to the main file; the HTML parser will insert it inside the document body.

<{}IMPORT src defines? include?>

<{}IMPORT async? src="src" defines?="nameList" include?>
    Signatures
<{}/IMPORT>
imports a number of components defined in a module.

The module may reside either:

  • Anywhere in the main file with an id being exactly equal to src
  • Or in a separate file loaded from URL src.
There are two variants:
  • With the async option, the IMPORT element must contain full signatures of the imported components.
    Compiling the document will continue with the provided signatures while the external file is being asynchronously fetched and compiled.
    Only when one of the components has to be instantiated, will OtoReact wait for the completion of the import.

    The listed signature need not be identical to the external signature, but must be 'compatible'. E.g., the order of parameters and slots may differ, and the external signature may have optional parameters and slots missing in the listed signature.
    Default values may be different too; Otoreact will use the values listed in the IMPORT element.

  • Without the async option, the IMPORT element needs only specify the names of the imported components.
    Compiling will still continue asynchronously, but as soon as the compiler encounters an invocation of one of these components, it will wait for the external file and get the signatures from there.
    So one can import from multiple modules one after another, and these will still be fetched in parallel.
Furthermore:
  • Components from the same module may be imported at multiple places and will be fetched and compiled only once, provided the URL is spelled exactly the same.
  • Local variables from the main file are not visible within the module.
  • When there is an attribute defines="nameList", then the names in the list, which must be defined as local variables in the module, are made available as local variables in the importing RHTML code.
  • Any style definitions in the source module are added to the main document (even those within non-imported components, currently).
  • Any non-declarative content of the module file is normally ignored.
    If, however, the attribute include is specified, then all content is instantiated at the place of the IMPORT element.

    This is mainly useful for invisible content, like the definition of pop-up windows and datalists.

Again, to speed things up, you can insert a preload link in the main document header.

<{}RSTYLE> dynamic style sheets

While STYLE sheets are always static, RSTYLE allows you to build style sheets using string interpolation and RHTML constructs, including loops and conditionals.

Braces \{ \} inside RSTYLE are part of the style sheet; to use string interpolation you must add a dollar sign \$\{ \}.

Here is a sample style sheet template that, upon pressing the button, gives all h2 elements an identical random hue, without needing inline styles:

Some points of attention:

  • RSTYLE style sheet templates have global effect just like STYLE, but may depend on local variables.
    When they are placed inside a FOR loop or component template, then multiple identical or different instances of the same style sheet template may arise.
    For efficiency reasons you should avoid this, unless you really mean to have multiple distinct instances.
  • RSTYLE sheets react on updates, but at every update, the sheet has to be rebuilt as a string, and parsed again by the browser.
    So you may want to avoid unnecessary style sheet updates, by proper use of reacton.
  • Sheets imported with @import cannot use string interpolation.
    If you want to use RHTML functionality in an external style sheet, then combine RSTYLE with INCLUDE:
    <{}RSTYLE>
        <{}INCLUDE src="yourDynamicStyleSheet.css">
    <{}/RSTYLE>
  • Note that STYLE. has exactly the same result as RSTYLE, except that braces inside STYLE. must be escaped like \\{ \\}.

<{}ELEMENT #tagname>*

ELEMENT #tagname="expression" builds an HTML element whose tagname is the string value of expression.
All other attributes and child contents are passed on to the new element.

The tagname may not be empty, and updates to the tagname of a created element are ignored.

<{}ATTRIBUTE #name value>*

ATTRIBUTE #name="expression" value adds an attribute, whose name is the string value of expression, to its (nearest) parent HTML element.
There may not be any child contents.

When the value is null or empty, no attribute is added.

Updates to the attribute name are handled accordingly.

<{}RHTML #srctext>*

RHTML #srctext="sourceExpr"/RHTML allows you to interpret dynamically generated text as Reactive HTML.

After each build or update, when the text value of sourceExpr has changed, then this text is compiled as RHTML, built, encapsulated using Shadow DOM, and shown.

The demo component on this page uses RHTML, of course.

Here you have an RHTML within the demo component, i.e. an RHTML within an RHTML:

Caution

When you use RHTML, you should be cautious of client-side (DOM-based) cross-site scripting (XSS, or code injection) attacks, when the text content can be entered by (or influenced by) the user, as on this site.
Client-side XSS is much less dangerous than server-side XSS, but still: don't ever use RHTML on sites with any security impact.

Demo HTML vs RHTML

In the following demo you can enter a piece of (R)HTML and compare how it is parsed and rendered by the browser without and with OtoReact processing.
Note how OtoReact removes irrelevant white space.

<{}RHEAD>*

Any child elements of RHEAD are inserted into the current document header, and are updated or removed as needed.

You can use this e.g. to set a dynamic document title or to add dynamic links, or to specify header content for documents created by DOCUMENT.

<{}DOCUMENT>*

    <{}DOCUMENT name=docName params?="nameList" window?=windowVar encapsulate?>
        content
    <{}/DOCUMENT>
allows you to define a separate document or form, that remains part of the same RHTML application.
content can be any RHTML code, which is not rendered immediately, but docName is defined as a local variable, bound to an object with the methods listed below, that will create a window either to be shown or printed.
Any scripts within content are executed within the same browsing context as the main document, and local RHTML variables from the main document are visible in local scripts within content.

When params is specified, then the given parameter names are available within content as local variables.

When window is specified, then the given windowVar is available within content as a local variable, and will be bound to the created window.
Note that global variable window refers to the main application window, but may be hidden by the local variable by specifying window="window".

The available methods are:

docName.open(target?, windowFeatures?, ...args)
The construct content is rendered in a separate browser window, using the optionally specified target and windowFeatures as in Window.open(), and parameter names bound to the given args.
The window gets an onkeydown handler that closes the window when the escape key is pressed.
The method returns a reference to the opened window.
All windows opened through this method are automatically closed when the user leaves the current RHTML application (through the pagehide event).
docName.print(...args)
The construct content is rendered in an invisible browser window with parameter names bound to the given args, and its browser print dialog is opened.
After the user has closed the print dialog, the invisible window is closed as well.
docName.closeAll()
This closes all child windows opened through docName.open.

You could for example use this in an ondestroy handler, to close all child windows when the document definition is removed from the DOM-tree:


        

By default, all styling rules attached to the parent document are copied to the new document, but with the option encapsulate no rules are copied.
Any style sheets within the DOCUMENT content are excluded from the parent document, and attached to each new child window.
Or you can use RHEADLINK …/RHEAD to link an external style sheet.

Other Subjects

URL routing

"URL routing" in a single-page application is about routing different URLs to seemingly different pages without requesting them from the server.
This routed page tells you all about it.

Here a summary of the OtoReact routing features:

docLocation
Predefined RVAR, containing the current URL.
So its value docLocation.V is always equal to location.href (currently "{docLocation.V}").

It allows applications to react on changes in the URL, and you can set its value to navigate to a different URL while staying within the application.

It has four additional properties and methods:

docLocation.basepath
This property holds the base path of the application.
All relative URL's within an OtoReact aplication are considered relative to this base path, and it is used to determine the current subpath.
The current base path is "{docLocation.basepath}".
docLocation.subpath
Part of the current pathname after the base path.
For this page it is "{docLocation.subpath}" (empty); for the routing page "{new URL('routing', location.origin+docLocation.basepath).href}", it is "routing".

An application can use pattern matching on this value to decode encoded special characters, capture parameters into variables, and show the correct page.

docLocation.searchParams
An URLSearchParams object containing the current URL search parameters.
It is equivalent to new URLSearchParams(location.search).
docLocation.search(key, value)
Returns a URL, equal to the current URL with search parameter key set to value, or with search parameter key removed when value is null.
reroute
Handler for the click-event for internal links, like a href="href" #onclick=reroute.
It causes clicks on the link to be intercepted, so that the application can change its state instead of sending a page request to the server.

Control-clicks are not intercepted and will open in a new browser tab.

reroute(href)
Command to navigate the application to href, which may be an absolute or relative URL.
For absolute URLs, it is the same as docLocation.V = href.
This is useful, e.g., for BUTTON onclick handlers.
\{basePattern: …\}
Compiler option, specifying a RegExp to identify the part of the compile-time URL that constitutes the base path.
This is only needed for applications that want to use routing on subpathes containing slashes.
The initial part of the compile-time pathname up to and including the last match to this RegExp, constitutes the base path.
If there is no match, then the base path is empty.

The default basePattern is "/".
E.g., if an OtoReact application is loaded from URL "https://mydomain.org/myapplication/myroutedpage?query" and basePattern is not explicitly set, then BasePath is set equal to the initial part of pathname "/myapplication/myroutedpage" up to and including the last slash, which is "/myapplication/".

Formatting

OtoReact does not (yet) have extra formatting functionality for numbers and dates, etc. You can use standard JavaScript features like the Internationalization API, or a library like Day.js.

Options

When calling RCompile(HTMLElement, options?), the options object may contain the following settings.
NameDefaultDescription
bTimingfalse When true, OtoReact will write timing information to the JavaScript console for each compile, build or update action.
bAbortOnErrorfalse When true, building or updating the DOM will be aborted when any error occurs.
An error message will be written to the console and shown in an alert box.

Compile time errors will always cause an abort, except within RHTML text.

bShowErrorstrue When true, errors while building or updating the DOM will be included in the DOM output, besides being written to the console.
bDollarRequiredfalse When true, a dollar sign is required before string interpolation braces, and no backslash is needed before other braces within interpolated text.
bSetPointertrue When true, OtoReact will add style 'cursor: pointer' to any element that has a non-null onclick handler and is not disabled.
basePattern"/" Regular expression to identify the part of the URL that constitutes the base path, see basePattern.
Relevant only for applications that use URL routing.
preformatted[ ] By default, OtoReact may skip white space when it reckons it is irrelevant for the visual appearance of the page. Only within the preformatted pre element white space is always preserved.
With the 'preformatted' option, you can set an array of other (case insensitive) element names that should have all white space preserved in the DOM.

You may need to use this when you have set the white-space CSS property so that white space is not collapsed for other elements.

bKeepWhiteSpacefalse When true, all white space will be preserved in the DOM output.
bKeepCommentsfalse When true, comments in the RHTML code will be included in the DOM output.
Example: RCompile(document.body, \{bTiming: true, preformatted: ['quote'], } );

Search engine compatibility

Dynamic content generated by a JavaScript framework, or just any client-side JavaScript code, can only be indexed by a search engine if that engine is able to execute the JavaScript.
Now Google does a very good job at executing JavaScript, and will probably be able to index your OtoReact site, provided each page has its own URL and is reachable through normal links.
Most other engines, including Bing I think, won't do so.

Static content in an OtoReact main file, on the other hand, is just HTML and can be indexed by any search engine. This is an advantage over frameworks like Angular and Svelte, where all content is generated by JavaScript.

Static content in included files can only be loaded throught JavaScript, and can only be indexed by search engines executing JavaScript.
If you want your static content be split over multiple files ánd want it to be indexable by any search engine, you could think of the following solutions:

  • Use server-side technology, like Server Side Includes (SSI) or PHP, to include files in your main file.
  • Use a tool to bundle multiple OtoReact files into a single file before deployment.
    Such a tool is not available yet, but it's not difficult to develop one.

Playground(s)

Here you have a playground to enter your own Reactive HTML.

All code you enter will be saved by your browser in localStorage, and should be available when you return to this page.

Page '{docLocation.subpath}' not found
This site uses cookies from Google Analytics and from PayPal.
You can find more about me on my LinkedIn profile,
and on my personal homepage in Dutch