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

index component
0 foo
1 page
2 doctor

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.

rule what example
/$ 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 value behavior
principal.agent pull out the agent from a principal
principal.authority pull out the authority from a principal
trim trim the string
upper convert the string to upper case
lower convert the string to lower case
is_empty_str returns true/false if the string is empty
is_not_empty_str returns true/false if the string is not empty
jsonify convert the lookup value to a string via JSON
time_now get the current time now
size_bytes convert a number into a size with a suffix of B, KB, MB, GB
vulgar_fraction converts a double into a integer part with the closest unicode vulgar fraction (eighths)
time_ago convert a datetime into a time ago
time convert 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.

syntax what
{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:action behavior requirements
domain:sign-in execute an authorize against the document pointed to by the domain form inputs: username, password
domain:sign-in-reset execute an authorize and password change against the document pointed to by the domain form inputs: username, password, new_password
domain:put execute a @web put against a document pointed to by the domain form element has path attribute
domain:upload-asset upload an asset (and maybe execute a send) form inputs: files
document:sign-in sign in to the document form inputs: username, password, space, key, remember
document:sign-in-reset sign in to the document and reset the password form inputs: username, password, space, key, new_password
document:put execute a @web put against a document form element has attributes: path, space, key
document:upload-asset upload assets to the indicated document form inputs: files, space, key
adama:sign-in sign in as an adama developer form inputs: email, password, remember
adama:sign-up sign up as an adama developer form inputs: email
adama:set-password change your adama developer password form inputs: email, password
send:$channel send a message form inputs should confirm to the channel's message type
copy-from:$formId copy the form with id $formId into the view state -
copy:$path copy the current form into the view state -
custom:$verb run 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

command behavior
toggle:$path toggle a boolean within the viewstate at the given path
inc:$path increase a numeric value within the viewstate at the given path
dec:$path decrease a numeric value within the viewstate at the given path
custom:$verb run a custom verb
set:$path=$value set a string to a value within the viewstate at the given path
raise:$path set a boolean to true within the viewstate at the given path
lower:$path set a boolean to true within the viewstate at the given path
decide:$channel response with a decision on the given channel pulling (see decisions)
goto:$uri redirect to a given uri
decide:$channel respond to a decision for a given (TODO)
choose:$channel add a decision aspect for a given channel (TODO)
finalize if there are multiple things to choose, then finalize will commit to a selection
force-auth:identity=key inject an identity token into the system
fire:$channel send an empty message to the given channel
ot:$path=$val the value at the path is a special order string used by order_dyn
te:$path transfer the event's message into the view state at the given path
tm:$path|$x|$y transfer the mouse coordinate's X and Y into the view state
reset reset the form
submit submit the form
resume when an exit guard is place, this will resume the transition
nuke finds the <nuclear> element containing the element throwing the event and then removes it from the DOM

Standard Events

rx:$event behavior
click the element was clicked
mouseenter the mouse entered
mouseleave the mouse left
change the input field changed
blur the input field lost focus
focus the input field gained focus
check input checkbox is checked
uncheck input checkbox is unchecked

Custom Events

rx:$event behavior
load runs when the DOM element is bound
success the form was success
failure the form was a failure
submit the form was submitted
aftersync the rx:sync just synchronized

Todo

  • customdata
  • wrapping
  • decisions