RxHTML

The forest of root elements

With RxHTML, the root element is no longer <html>. Instead, it is a <forest> with multiple pages (via <page> element) and templates (via <template> element). While there are a few custom elements like <connection> (connect the children html to an Adama document) and <lookup> (pull data from the Adama document into a text node), the bulk of RxHTML rests with special attributes like rx:if, rx:ifnot, rx:iterate, rx:switch, rx:template, and more. These attributes reactively bind the HTML document to an Adama tree such that changes in the Adama tree manifest in DOM changes.

The root elements within a <forest>

There are three core elements under a <forest>: pages, templates, and the one true shell.

<page uri="$uri">

A page is a full-page document that is routable via a uri.

<forest>
    <page uri="/">
        <h1>Hello World</h1>
    </page>
</forest>

The uri is broken up into components by splitting via the '/' character and the uri must alway start with '.'. For example, the uri "/foo/page/doctor" breaks down into three components

indexcomponent
0foo
1page
2doctor

These components are all fixed constants, but we can introduce both numeric and string variances by prefixing the component with the '$' character.

Beyond the uri, a page may also require authentication. This is denoted by the valueless attribute authenticate. When there is no default identity available, requests to that URI will forward to the page marked with the valueless attribute default-redirect-source. For example:

<forest>
    <page uri="/product" authenticate>
        The secure product
    </page>
    <page uri="/signin" default-redirect-source>
</forest>

<template name="$name">

A template is fundamentally a way to wrap a shared bit of RxHTML within a name. At core, it's a function with no parameters. Templates are how RxHTML achieve user interface re-use, and the philosophy is akin to duck typing where if the data behaves like a duck, then a template will make the duck pretty.

<forest>
  <template name="template_name">
    <nav>
      It's common to use template for components
      that are repeated heavily like headers, 
      components, widgets, etc...
    </nav>
  </template>
</forest>

However, there are parameters via the <fragment> element and the rx:case attribute. See below for more details.

<forest>
  <template name="template_name_that_switches">
    <nav>
      It's common to use template for components
      that are repeated heavily like headers, 
      components, widgets, etc...
    </nav>
    <fragment case="x" />
    <fragment case="y" />
    <fragment case="x" />
  </template>
</forest>

Templates can be invoked in one of two ways: as a child or inline. The above template can be invoked via the rx:template attribute as a child.

<forest>
  <page uri="/use-template-child">
    <div rx:template="template_name_that_switches" class="clazz">
        <div rx:case="y">
            [Y]
        </div>
        <div rx:case="x">
            [X]
        </div>
    </div>
  </page>
</forest>

This will has the effect of generating DOM like:

<div class="clazz">
  <nav>
    It's common to use template for components
    that are repeated heavily like headers, 
    components, widgets, etc...
  </nav>
  <div>[X]</div>
  <div>[Y]</div>
  <div>[X]</div>
</div>

Now, if the parent div is not desired, then <inline-template> is available as a pseudo-node within RxHTML.

<forest>
  <page uri="/use-template-child">
    <inline-template name="template_name_that_switches">
        <div rx:case="y">
            [Y]
        </div>
        <div rx:case="x">
            [X]
        </div>
    </inline-template>
  </page>
</forest>

This will act as if inline-template doesn't exist and merge the tree into the parent. This requires care as some elements or attributes will inject a div when needed.

The shell

An RxHTML forest can only have one <shell> element which is used to configure the generated application shell.

Data binding with <connection space="$space" key="$key" >

Data can be pulled into HTML via a connection to an Adama document given the document coordinates (space and key).

<forest>
  <page uri="/">
    <connection space="my-space" key="my-key">
      ... use data from the document ...
    </connection>
  </page>
</forest>

Alternatively, connections can be established via the domain for use in a multi-tenant product.

<forest>
  <page uri="/">
    <connection use-domain>
      ... connect to the domain referenced by the domain ...
    </connection>
  </page>
</forest>

Pathing; understanding what $path values

The connection is ultimately providing an object, and various elements and attributes will utilize a value as a path. We see this directly in the <lookup> text element.

  ...
    <lookup path="my_field" />
  ...

This will find the value the value at my_field within the document and convert it to a text node.

The mental model being played out is that a connection is providing a document that is a hierarchical in nature. We use pathing similar to how file systems provide a current working directory. At the start of a connection, we start in the root of the document. Various attributes will manipulate the pathing, but we can also explicitily navigate the directory for lookup.

This path may be a simple field within the current object, or it can be a complex expression to navigate the object structure.

rulewhatexample
/$navigate to the document's root>lookup path="/title" />
../$navigate up to the parent's (if it exists)>lookup path="../name" />
child/$navigate within a child object>lookup path="info/name" />

Viewstate versus Data

At any time, there are two sources of data. There is the data channel which comes from adama, and there is the view channel which is the view state.

The view state is information that is controlled by the view to define the focus of the viewer, and it is sent to Adama asynchronously.

You can prefix a path with "view:" or "data:" to pull either source, and in most situations, the default is "data:".

Using data: pulling in a text node via <lookup path="$path" >

<forest>
    <page uri="/">
        <connection space="my-space" key="my-key">
            <h1><lookup path="title" /></h1>
            <h2><lookup path="byline" /></h2>
            <p>
                <lookup path="intro" /><
            </p>
        </connection>
    </page>
</forest>

Lookup's transforms

The lookup pseudo-element has a transform attribute that runs a function to transform the input into a nicer looking output.

transform valuebehavior
principal.agentpull out the agent from a principal
principal.authoritypull out the authority from a principal
trimtrim the string
upperconvert the string to upper case
lowerconvert the string to lower case
is_empty_strreturns true/false if the string is empty
is_not_empty_strreturns true/false if the string is not empty
jsonifyconvert the lookup value to a string via JSON
time_nowget the current time now
size_bytesconvert a number into a size with a suffix of B, KB, MB, GB
vulgar_fractionconverts a double into a integer part with the closest unicode vulgar fraction (eighths)
time_agoconvert a datetime into a time ago
timeconvert a datetime or time from military time to

Using data: connecting data to attributes

Attributes have a mini-language for building up attribute values using variables pulled from the document or conditions which control the output.

syntaxwhat
{var}embed the text behind the variable into the string
[b]other[/]embed the stuff between the brackets if the evaluation of the variable b is true
[b]true branch[#]false branch[/]embed the stuff between the brackets based of the evaluation of b
[v=$val]other[/]embed the stuff between the brackets if the evaluation of the value v is the given $val
[v=$val]true[#]false[/]embed the stuff between the brackets if the evaluation of the value v being the given $val
<forest>
    <page uri="/">
        <connection space="my-space" key="my-key">
            <a class="[path-to-boolean]active[#]inactive[/]" href="#blah">
            </a>
        </connection>
    </page>
</forest>

Attribute extensions to existing HTML elements

A Guiding philosophy of RxHTML is to minimally extend existing HTML elements with new attributes which bind the HTML tree to a JSON tree

<tag ... rx:iterate="$path" ... >

The rx:iterate will iterate the elements of a list/array and scope into each element. This attribute works best when there is a single HTML child of the tag placed, and it will insert a div if there isn't a single child. In terms of path, the resulting path is two levels deep: first added level is the list and second added level is the item.

<table>
    <tbody rx:iterate="employees">
        <tr>
            <td><lookup path="name" /></td>
            <td><lookup path="level" /></td>
            <td><lookup path="email" /></td>
        </tr>
    </tbody>
</table>

rx:iterate respects rx:expand-view-state. rx:expand-view-state will force the view path to change to mirror the iteration. This allows each element in the iteration to have a unique view state.

<tag ... rx:if="$path" ... >

The rx:if will test a path for true or the presence of an object. If there is an object, then it will scope into it.

<div rx:if="active">
    Show this if active.
</div>

Note: this only renders anything if the value active is present

<tag ... rx:ifnot="$path" ... >

Similar to rx:if, rx:ifnot will test the absense of an object or if the value is false.

<div rx:ifnot="active">
    Show this if not active.
</div>

Note: this only renders anything if the value active is present

<tag ... rx:else ... >

Within a tag that has rx:if or rx:ifnot, the rx:else indicates that this element should be within the child if the condition on the parent is the opposite specified.

<div rx:if="active">
    Show this if active.
    <span rx:else>Show this if not active</span>
</div>

force-hiding

For rx:if and rx:ifnot, the behavior controls the children of the node. This is required because the node must be stable, and we can ameliorate with the valueless attribute force-hiding which will synchronize the result with the node's style.display as this is a common workaround. However, this then means that the associated rx:else will never render.

<div rx:ifnot="active" force-hiding>
    Show this ONLY if active is true
</div>

<tag ... rx:switch="$path" ... >

A wee bit more complicated than rx:if, but instead of testing if a value is true will instead select cases based on a string value. Children the element are selected based on their rx:case attribute.

<div rx:switch="type">
    Your card is a
    <div rx:case="face_card">
         face card named <lookup path="name"/>
    </div>
    <div rx:case="digit">
         numbered card with value of <lookup path="value"/>
    </div>
</div>

Note: this only renders anything if the value is present

<tag ... rx:case="$value" ... >

Part of rx:switch and rx:template, this attribute identifies the case that the element belongs too. See rx::switch for an example.

If a dom element within a child with rx:switch doesn't have an rx:case then it is rendered for every case, and this is true for both rx:switch and templates.

<tag ... rx:template="$name" ... >

The children of the element with rx:template are stored as a fragment and then replaced the children from the <template name=$name>...</template>. The original children be used within the template via <fragment />

<template name="header">
  <header>
  </header>
  <div class="blah-header">
    <h1><fragment /></h1>
  </div>
</template>
<page ur="/">
  <div rx:template="header">
    Home
  </div>
<page>

<fragment>

Fragment is a way for a template to gain access to the children of the invoking element.

Furthermore, fragments support case attribute to filter out children.

<signout>

Once this element is seen, the identities are destroyed

<tag ... rx:scope="$path" ... >

Enter an object assuming it is present.

<form ... rx:action="$action" ... >

Forms that talk to Adama can use a variety of built-in actions like

rx:actionbehaviorrequirements
domain:sign-inexecute an authorize against the document pointed to by the domainform inputs: username, password
domain:sign-in-resetexecute an authorize and password change against the document pointed to by the domainform inputs: username, password, new_password
domain:putexecute a @web put against a document pointed to by the domainform element has path attribute
domain:upload-assetupload an asset (and maybe execute a send)form inputs: files
document:sign-insign in to the documentform inputs: username, password, space, key, remember
document:sign-in-resetsign in to the document and reset the passwordform inputs: username, password, space, key, new_password
document:putexecute a @web put against a documentform element has attributes: path, space, key
document:upload-assetupload assets to the indicated documentform inputs: files, space, key
adama:sign-insign in as an adama developerform inputs: email, password, remember
adama:sign-upsign up as an adama developerform inputs: email
adama:set-passwordchange your adama developer passwordform inputs: email, password
send:$channelsend a messageform inputs should confirm to the channel's message type
copy-from:$formIdcopy the form with id $formId into the view state-
copy:$pathcopy the current form into the view state-
custom:$verbrun custom logic-

<custom- ... rx:link="$value" ... >

TODO

<tag ... rx:wrap="$value" ... >

TODO

<input ... rx:sync="$path" ... >, <textarea ... rx:sync="$path" ... >, <select ... rx:sync="$path" ... >, oh my

Synchronize the input's value to the view state at the given path. This will propagate to the server such that filters, searches, auto completes happen.

<input type="text" rx:sync="search_filter" />

<option> text within rx:iterate

No children DOM elements are allowed within an <option> tag, so to use dynamic text, us label="{$path}" instead of <lookup> or any other DOM element

<select rx:iterate="$path">
    <option value="$path" label="$path" />
</select>

<exit-guard guard="$path" set="$path" >

The <exit-guard> will protect against data loss by preventing transition to a new page if the guard path is set to true. If the guard path is true while a page transition happens, then the set path is raised to true.

<monitor path="$path" delay="10" rise="..." fall="..." />

The <monitor> element will watch a numeric variable and then fire events if the value rises or falls mirroring signal edge transitions.

<todotask>

For the sheer joy of task management, <todotask> formats a TODO item in the HTML and aggregates the task into a file within the devbox.

<title>

Unlike traditional title where the title is placed within the element, the title has a value attribute that can be reactively bound to the document.

  <title value="Messages for {person_name}" />

Events (rx:click, etc...)

Adama supports running a very restrictive command language on various events. The semantics is that a command string evaluates left to right and is delimited by spaces.

  ...
    <input type="text" value="{view:up} / {view:down}">
    <button rx:click="inc:up dec:down">Click Me</button>
  ...

Sinces spaces delineate commands, the single quote character is used to open a string

  <button rx:click="set:title='The Big Thing'">Change Title</button>
  <button rx:click="set:title='The Next Thing'">Change Title Again</button>

Command language

commandbehavior
toggle:$pathtoggle a boolean within the viewstate at the given path
inc:$pathincrease a numeric value within the viewstate at the given path
dec:$pathdecrease a numeric value within the viewstate at the given path
custom:$verbrun a custom verb
set:$path=$valueset a string to a value within the viewstate at the given path
raise:$pathset a boolean to true within the viewstate at the given path
lower:$pathset a boolean to true within the viewstate at the given path
decide:$channelresponse with a decision on the given channel pulling (see decisions)
goto:$uriredirect to a given uri
decide:$channelrespond to a decision for a given (TODO)
choose:$channeladd a decision aspect for a given channel (TODO)
finalizeif there are multiple things to choose, then finalize will commit to a selection
force-auth:identity=keyinject an identity token into the system
fire:$channelsend an empty message to the given channel
ot:$path=$valthe value at the path is a special order string used by order_dyn
te:$pathtransfer the event's message into the view state at the given path
tm:$path|$x|$ytransfer the mouse coordinate's X and Y into the view state
resetreset the form
submitsubmit the form
resumewhen an exit guard is place, this will resume the transition
nukefinds the <nuclear> element containing the element throwing the event and then removes it from the DOM

Standard Events

rx:$eventbehavior
clickthe element was clicked
mouseenterthe mouse entered
mouseleavethe mouse left
changethe input field changed
blurthe input field lost focus
focusthe input field gained focus
checkinput checkbox is checked
uncheckinput checkbox is unchecked

Custom Events

rx:$eventbehavior
loadruns when the DOM element is bound
successthe form was success
failurethe form was a failure
submitthe form was submitted
aftersyncthe rx:sync just synchronized

Todo

  • customdata
  • wrapping
  • decisions