OtoReact is a library that brings reactivity straight into your HTML file.
The first application above 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:
That's all you need.
Full framework functionality, offered by a library.
Nothing to install, to configure, to build, and not a single line of required JavaScript code.
Running like a charm.
'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.
This website as a whole is also a reactive web application, written in OtoReact.
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:
Please note that you need at least a basic understanding of HTML and JavaScript to work with a framework like OtoReact.
Let's return to the tiny example above left.
You can modify the source code below, and on every keystroke the modified application will be recompiled and re-executed.
Explanation:
yourName
, that can contain state information of the application.
yourName.V
denotes the value of the variable.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.
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.
Explanation:
maxY
with initial value 10, to persist in sessionStorage
.
y
iterate through the values of range(1,maxY.V)
, which are the numbers 1 to maxY.V.
Here is an example of dynamically building a table based on server data, with a bit of animation as well:
There exist quite a number of alternative libraries and frameworks to make it easier to build reactive web applications. I distinguish two main categories:
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.
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.
<{}script type=module src="path/to/OtoReact.js"><{}/script>
rhtml
:
<{}body rhtml>, then OtoReact will implicitly compile and render the document body at the first event cycle.
rhtml
attribute.<{}body rhtml hidden>
.
That's all!
Compare:
<{}li *ngFor="let item of items"> \{\{ item.message }} <{}/li>
\{#each items as item} <{}li> \{ item.message } <{}/li> \{/each}
<{}li v-for="item in items"> \{\{ item.message }} <{}/li>
items.map( item => ( <{}li> \{ item.message } <{}/li> ))
<{}for let="item" of="items"> <{}li> \{ item.message } <{}/li> <{}/for>
null
and undefined
are not rendered simplifies many embedded expressions.
Some drawbacks are:
reacton
and hash
) that tell OtoReact which RHTML parts should react on which state variables.
Static content in an OtoReact main file is just HTML and can be indexed by any search engine. This is a great advantage over frameworks like Angular and Svelte, where all content is generated by JavaScript.
Dynamic content generated by any JavaScript framework including OtoReact, 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 reasonably good job at executing JavaScript, and may be able to index your dynamic 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 included files can only be loaded through 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:
All code you enter will be saved by your browser in localStorage, and should be available when you return to this page.
{src}
→ Sections and paragraphs marked with a '*' are advanced and may be skipped on first reading.
Notation
means:
Source text "{idemo}" produces output "".
The expressions are evaluated, converted to string, and inserted as text; there is absolutely no risk of code injection.
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:
In all other cases, backslashes stand for themselves:
null
and undefined
are not shown: This is unlike JavaScript template literals, where null
and undefined
are spelled out:
\{ \}
, which comes in handy if you want the parser to not recognize an HTML tag: RHTML defines a number of new constructs, which dynamically build your HTML page:
reacton
does the same thing.
RVAR(name?, initialValue?, store?)
creates a reactive variable.
range(start?, count, step?)
yields an iterable range of count numerical values: start, start+step, …, start+(count-1) * step.
reroute()
and docLocation
are used with URL routing.
RFetch(resource, init?)
is the same as fetch(resource, init?)
, except that it throws an error when an HTTP error status is received.
RCompile(HTMLElement, options?)
is available as an alternative way to initiate RHTML compilation.
All of these are exported by the OtoReact module. TypeScript type declarations are available on the download page.
\{ … \}
of JavaScript code
RHTML adds one other kind of variable: RHTML local variables; these are visible in all local JavaScript code within a block of RHTML code.
These are introduced by the following RHTML constructs:
RHTML local variable names obey strict lexical scoping rules, see
RHTML local variables are not visible within non-local
Global variables are created:
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
.
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:
RVAR(name?, initialValue?, store?, subscriber?, storename?)
from JavaScript,name
name
, the RVAR will be registered in the global environment under that name and will be visible anywhere.
<{}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
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
*subscriber
*x.Subscribe(subscriber, true)
.
storename
*
You may or must inform OtoReact which fragments of RHTML should react on which RVAR's, by using the reacton
attribute, see below.
For RVAR's created by calling RVAR()
, this is necessary.
For RVAR's created by
An RVAR x
is an object, distinct from the value of the variable. It has the following properties and methods:
x.V
x
, one writes x.V
.
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
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()
x.SetDirty()
.
x.Subscribe(subs, bImmediate?, bInit?)
*subs
, when not null, is registered as a subscriber to x
, so subs(x.V)
will be executed 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
.
The return value is x
, so calls to Subscribe
can be chained.
x.Unsubscribe(subs)
*subs
previously registered as a subscriber to x
.
x.Set(value)
*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
*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
*x
, except when x
has just been set dirty in the same event loop.
You can e.g. add an attribute #onsuccess="errMsg.Clear"
to clear any error message when any event handler succeeds without error.
store
parameter to RVAR()
or to 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 windowlocalStorage
, 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 machinesetItem
and getItem
methods of the Storage interface.
The RVAR must have a unique storename
; the default is `RVAR_$\{name\}`
, where the prefix "RVAR_" can be changed using option 'store_Prefix'.
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 can be included anywhere using the
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
With
See <{}STYLE> and <{}RSTYLE> for details.
RCompile
does not, as one might perhaps expect, translate the whole chunk of RHTML into one large string of JavaScript.
eval
function (in global scope, of course).
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.
setTimeout
) all reacting DOM tree parts to be updated by their registered builder routine in its registered environment.
, then the (Chromium) parser will move the`"> … \{x} …
x
being undefined.
When building the DOM tree, the dots will be removed.` "> … \{x} …
…, without closing the` "> …
…
#class:someclass="someBoolean"
), that will only work for class names in lowercase.I imagine a formalism similar to Reactive HTML one day being natively supported by the browser. In that case, these limitations can be lifted.
_RHTML
or // RCompile(…)
reacton
attribute.
bAbortOnError
.
debugger
statement, to get a breakpoint at that point when the browser development tool is opened.