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 that's required.
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 '
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
OtoReact is a small and fast client-side JavaScript 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, and may in many cases be abbreviated to yourName
.store='sessionStorage'
, which makes the value persist when the application is reloaded or recompiled.@value="yourName"
or @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\}
and \{yourName.length\}
, or \{yourName.V\}
and \{yourName.V.length\}
, are embedded expressions. The rendered document will contain the value of the expression between the braces, and will be updated when needed.
Explanation:
maxY
with initial value 10, to persist in sessionStorage
.
y
iterate through the values of range(1,maxY)
, 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
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 compile and render the document body at the first event cycle.
You may mark header elements too.
rhtml
attribute.<{}body rhtml hidden>
or <{}body rhtml hidden="until-found">
.
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.
Drawbacks are:
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
Sections and paragraphs marked with a '*' are advanced and may be skipped on first reading.
Notation
means:
Source text "{idemo}" produces output "All source text is editable.".
The expressions are evaluated, converted to string, and inserted as text; there is absolutely no risk of
Within JavaScript, you can of course use the
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: For details, see Formatting.
Besides this, there are a few RHTML constructs, having a string parameter at compile time that allows embedded expressions too. These expressions are evaluated at compile time, and can only refer to global constants and variables.
This applies to thesrc
parameter of the RHTML defines a number of new constructs, which dynamically build your HTML page:
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.
debug()
is a very simple utility to insert a breakpoint within a JavaScript expression, using the debug(),expr
.
debugger
statementRFetch(resource, init?)
is the same as fetch(resource, init?)
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
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
" or "self
", 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 self
.
RVAR(name)
.
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?, updTo?)
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 undefined
, and when the promise resolves to a value, then the RVAR will be set to that value.
When initialValue
is a Function
, then the RVAR value will be the result of calling the function without arguments.
When this results in a Promise
, then the preceeding rule is applied.
Furthermore, if any other RVAR value was used during the call, then the RVAR value will be re-evaluated whenever one of these other RVARs has changed.
store
*subscriber
*x.Subscribe(subscriber, true)
.
storename
*updTo
*x.Subscribe(() => updTo.SetDirty(), true)
An RVAR x
is an object, distinct from the value of the variable.
It has the following own properties and methods:
x.V
x
, one writes x.V
.
x.V
is invoked while creating or updating a DOM node or OtoReact construct, then that node or construct will be subscribed to the RVAR so it will be updated whenever the RVAR has been marked as changed (i.e. has been "set dirty").
x.V
is set to a different (i.e. not x.V
to just x
:
x.V
, so that whenever x
is used in a context where a literal value is expected, x.V
will be used instead, e.g.:
x+1
, x > y
, Math.max(x, y)
, and also range(x, y)
x == 1
, x != 'hello'
.
x = \{ x \}
, `x = $\{x}`
.V
:
let x = RVAR( '', \{prop: 0} ); let a = x.prop; // Same as: let a = x.V.prop x.prop = 1; // Same as: x.V.prop = 1, except that x will be set dirty if and only if x.V.prop !== 1 delete x.prop; // Same as: delete x.V.prop, except that x will be set dirty if x.V.prop existsExceptions:
.V
when the property name is equal to one of the RVAR property or method names.
x.Set
to access x.V.Set
.x.doSomething()
of x.V
, omitting .V
, then the value of this
during method execution will be the RVAR proxy object x
rather than the target object x.V
.
@value="x"
.
x.V
may not be abbreviated in all other cases, like:
x.V = expr
x.V == expr
if (x.V) ...
, (x.V ? a : b)
f(x.V)
x.U
x.U
gets or sets the value of x
while forcing it to be marked dirty.
E.g., if x.V
is an array, you can write x.U.push(e)
to add an array element, or x.U[i].p = q
to modify a property of 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.
x.U
sets the value of x
and marks it as dirty even when the value is strictly equal to the previous value.
x.$V
x.$V
gets or sets the value of x
without subscribing to it and without marking it dirty.
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.
One should be very careful when using this: there is the possibility that x
has an outdated value, when one is using x.U
to set a property of x
or to call a method that modifies x
, because x.U
sets x
dirty before x
is modified.
When bInit
is truthy, then subs
will initially be called when it is registered.
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
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 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.
eval
function (in global scope, of course).
a+b
is compiled by calling eval("([a,b]) => (a+b)")
.
Each snippet is compiled just once, including event handlers on...
occuring in a repeated element.
OtoReact does not itself parse and analyse the JavaScript, so it is unaware which variables are actually used and which are not.
setTimeout
, 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
statementdebug
function, like: debug(),expr
.
→