If you are viewing this as a README.md, visit https://anywhichway.github.io/trui/ for a more interactive experience.
The closest comparable libraries are VanJS and htmx.
You can think of trui
as what might happen if VanJS and htmx had a baby:
rjs.js
(reactive JavaScript) is API compatible with VanJS but slightly largertrui
provides rhtml.js
, reactive HTML templating without the need for developer JavaScriptLearning and using trui
rjs.js
, rhtml.js
, or the htmx like element-fetch.js
and xon.js
separately or together. They know about and can leverage each other, but do not require each other.npm install trui
Then use the trui.js
or trui.min.js
file in the root directory.
<p style="border:1px solid black;padding:5px">
<button onclick="this.state.counter||=0;this.state.counter++">Click Count: ${counter||0}</button>
</p>
Set age to greater than or equal 21 to see the Access Granted message.
<p id="person" style="border:1px solid black;padding:5px" data-name="Joe" data-age="21" title="21 and older are eligible">
Name: ${name} Age: ${age}
Age: <input name="age" type="number" value=${age} oninput="person.dataset.age=event.target.value">
<span style="display:${age >=21 ? '' : 'none'}">Access is granted</div>
</p>
By loading rhtmx.js
(804 bytes) you can get utility functions like $data
, $state
and $attribute
to facilitate data updates and reactive rendering.
<p style="border:1px solid black;padding:5px" data-name="Joe" data-age="21" title="21 and older are eligible" oninput="$data(event,false)">
Name: ${name} Age: ${age}
<input name="age" type="number" value="${age}">
<span style="display:${age >=21 ? '' : 'none'}">Access is granted</span>
</p>
Whereas states cannot be private with rhtml.js
, they are a property of each element, with rjs.js
they can be private.
var rjsCounter = () => {
const state = rjs.state(0);
return button({
onclick() {
state.value++;
}
}, () => {
return `Click Count: ${state.value}`
});
}
document.currentScript.insertAdjacentElement("afterend", rjsCounter());
However, you can use the public state if you wish:
<script>
var rhtmlCounter = () => {
return button({
onclick(event) {
this.state.count ||= 0;
this.state.count++;
// state would also be available as event.target.state
}
}, (state) => {
return `Click Count: ${state.count||=0}`
});
}
document.currentScript.insertAdjacentElement("afterend", rhtmlCounter());
</script>
Below we return to the eligibility example using private state instead of data attributes:
var rjsForm = () => {
const state = rjs.state({name:"Joe",age:21});
return p({
oninput(event) {
state[event.target.name] = event.target.value;
},
style: "border:1px solid black; padding:5px"
}, () => {
return `Name: ${state.name} Age: ${state.age}`;
},() => {
return input({
name: "age",
type: "number",
value: state.age
});
}, () => {
return span({
style: `display:${state.age >=21 ? '' : 'none'}`
},() => {
return " Access is granted";
});
});
}
document.currentScript.insertAdjacentElement("afterend", rjsForm());
<script src="./src/element-fetch.js"></script>
<p style="border:1px solid black;padding:5px" target=">" x-on='every:1000' >${new Date()}</p>
The target
attribute is used to specify the target of the xon
operation. The >
means inner. Additional options include:
<|
before begin|>
after begin>|
before end|>
after end<
outer, i.e. replacerhtml.js
)With reactive html, you can use the JavaScript ${}
syntax directly in your HTML.
dataset
values are automatically reactive if they are referenced within ${}
.state
property is added to all HTMLElement
objects. Unlike the dataset
property, state
can contain values of any type including nested objects and functions.Note: rhtml.js
should be loaded in the head
of your HTML document.
<p style="border:1px solid black;padding:5px">
<button name="Greetings!!"
onclick="this.getAttribute('name')=='Greetings!!' ? this.setAttribute('name','Goodbye...') : this.setAttribute('name','Greetings!!')">
${name}
</button> Attribute Update<br>
<button data-counter="0" onclick="this.dataset.counter++">Click Count: ${counter}</button> Dataset Update<br>
<button onclick="this.state.counter||=0;this.state.counter++">Click Count: ${counter||0}</button> State Update<br>
<button style="color:blue" onclick="this.style.color==='blue' ? this.style.color='red' : this.style.color='blue'">Click To Color</button> Style Update<br>
<button data-counter="0" style="color:${counter%2 ? 'red' : 'blue'}" onclick="this.dataset.counter++">Click Count: ${counter}</button> Dataset Update With Computed Style
</p>
state
of their parent. And, state
properties can be shadowed in child elements.The best way to resolve reactive HTML is to call document.resolve()
in a script immediately after the body
tag of a document:
document.resolve()
will hide the body, resolve the document, and then un-hide the body. If you do not want the body
to be hidden, you can call document.resolve({hidden:false})
; however, this will result in screen flicker.
document.resolve()
can be called with a state
and dataset
argument for use in resolving reactive values in the document.
Updating properties of document.body.state
or document.body.dataset
will then trigger a re-render of the document.
<body>
<script>
document.resolve({state:{greeting:"Hello World!",counter:0}});
document.currentScript.remove();
</script>
${greeting}
</body>
IMPORTANT: You MUST make the call to remove the current script, or the resolve in this script will override any state or dataset values set elsewhere in the document.
rhtmlx.js
)$attribute
is a utility function that can be used to update the attributes of an element. It is capable of accomplishing 3 things:
$attribute
is polymorphic. These are the signatures:
$attribute(event:Event,selector:string|boolean,{property:string,value:any,stop:boolean=true})
$attribute(element:HTMLElement,selector:string|boolean,{property:string,value:any,stop:boolean=true})
$attribute(selector:string,{property:string,value:any,stop:boolean})
event
is the event that triggered the update
property
will be set to the value of the name
attribute of the element that produced the event.value
will be set to the value
attribute or property of the element that produced the event.currentTarget
of the event is the element for which the attribute should be set.element
is the element on from which the closest matching parent should be found, unless selector
is false
, in
which case it will be the element on which the attribute value will be set. Values for property
(the attribute name)
and value
must be provided.
selector
is used to find the element to which the attribute should be applied.
false
the target of the event is the target.true
(only used for elements hosting a shadowDOM), the shadow host, i.e. the hosting element, is returnedSee $data
below for an example.
rhtmlx.js
)$data
accomplishes the same three things as $attribute
but for the dataset
.
Here is the same content as used in the simple examples, but with a selector to find the p
element with data
attribute:
<p style="border:1px solid black;padding:5px" data-name="Joe" data-age="21" title="21 and older are eligible">
Name: ${name} Age: ${age}
<input name="age" type="number" value=${age} oninput="$data(event,'p:has(> input)')">
<span style="display:${age >=21 ? '' : 'none'}">Access is granted</span>
</p>
If you have a complex structure, you can create dynamic selectors with string templates, e.g. p[data-${event.target.name}]:has(> input)
.
rhtmlx.js
)$state
accomplishes the same three things as $attribute
but for the state
.
rjs.js
)Can be used standalone or with any other trui
files.
The functionality of rjs.js
is similar to VanJS.
xfetch.js
)Can be used standalone or with any other trui
files.
When xfetch.js
is loaded:
fetch
method that can be used to load content into or replace the element.href
and target
attribute, or alternatively x-href
and x-target
for compliance with the HTML standard.Clicking on Privacy Policy
below will load the content of the privacy-policy.html
file into the p
element.
<p x-href="./examples/privacy-policy.html#content" onclick="xfetch(event)">Privacy Policy</p>
If you are not trying to build a single page application, you can dispense with SSR by making a relatively minor change to your HTML authoring approach.
Since href
is not standard on all elements and x-href
is not standard at all, web crawlers and indexing services may not
find all your content if you author like this:
<p x-href="./examples/privacy-policy.html#content" onclick="xfetch(event)">Privacy Policy</p>
However, by adding an anchor with the class xfetch
you can make the privacy policy visible to all web
crawlers and indexing services even without the user clicking on the link, and will also make the content available to
users with JavaScript disabled and ensure proper behavior of the browser back button:
<p title="Privacy Policy" onclick="xfetch(event)" x-target=">">
<a class="xfetch" href="./examples/privacy-policy.html#content" target="_top">Privacy Policy</a>
</p>
Note the targets above. A target
or x-target
on the p
element will be used as the target for the fetch
operation.
Whereas the target
on the a
element will be used when JavaScript is not enabled. If you do not provide a target
on the container element, it will fall through to the target on the a
element. If neither is provided, the default
target is for the container is >
, i.e. inner content.
Using a hash in the href
allows the loaded content to be an entire webpage with header, footer, and other content,
but only the content with the id
specified in the hash will be loaded into the target element. But, when JavaScript is
disabled, the entire page will be loaded and simply scrolled to the target.
xon.js
)Can be used standalone or with any other trui
files.
THe most relevant libraries to compare trui
to are VanJS
and htmx
. Neither VanJS
nor htmx
provide reactive
HTML templating like rhtml.js
and rhtmlx.js
. You could use either one of these libraries in conjunction with
just rhtml.js
and rhtmlx.js
.
rjs.js
is 1,548 bytes minified and gzipped, while VanJS
official number is 1,055rjs
has a slightly smaller API surface than VanJS
, it does not support add
by default, but it can be added by loading xrjs.js
xrjs
supports a higher level component model than VanJS
rjs
has the additional aliases observe
, value
, and peek
for the VanJS
derive
, rawValue
, and val
respectivelyrjs
does not require accessing the value
property, VanJS does, e.g. console.log(
Counter: ${counter}))
vs console.log(
Counter: ${counter.val}))
rjs
is rhtml
aware and will resolve reactive values in generated HTMLrjs
provides an oncreate
handlerrjs
does not have a TypeScript definition filerjs
does not currently have an SSR package, although with rhtml.js
, rhtmlx.js
, and xfetch.js
you may not need SSRVanJS
is more mature, and has way more supporters, documentation, and add-onsxon.js
and xfetch.js
combined are less than half the size of htmx
htmx
capabilities that are not in trui, e.g. form validity checking, animationshtmx
is more mature, and has way more supporters, documentation, and add-onsPost ideas at https://github.com/anywhichway/trui/issues
rjs-ssr.js
packagexform.js
package to support form validationMIT
v0.0.19a 2024-04-17
Fixed issue with xfetch trying to resolve body when rhtml.js is not loaded.
v0.0.18a 2024-04-12
Fix for undefined function dp
(Object.defineProperty) in xhtml.js
v0.0.17a 2024-04-11
Added document.resolve()
to hide body, resolve document, and unhide body instead of just automatically resolving body.
v0.0.16a 2024-04-11
more handling of nested ${ with HTML as its result value
v0.0.15a 2024-04-11
more handling of nested ${ with HTML as its result value
v0.0.14a 2024-04-11
more handling of nested ${ with HTML as its result value
v0.0.13a 2024-04-11
more handling of nested ${ with HTML as its result value
v0.0.12a 2024-04-11
more handling of nested ${ with HTML as its result value
v0.0.11a 2024-04-11
more handling of nested ${ with HTML as its result value
v0.0.10a 2024-04-11
Addressed issue with properly handling nested ${ with HTML as its result value
v0.0.8a 2024-04-01
element-fetch.js
to xfetch.js
rhtmlx.js
to xrhtml.js
xrhtml.js
into rhtmlx.js
and xrjs.js
v0.0.7a 2024-04-01
v0.0.6a 2024-04-31
v0.0.5a 2024-04-31
rhtml.js
to provide reactive HTML templatingrhtmlx.js
to provide utility functions for rhtml.js
trui.js
to rjs.js
v0.0.4a 2024-04-28
xon
to emulate some htmx capabilityadd
as a separate module to emulate VanJSv0.0.3a 2024-04-27
v0.0.2a 2024-04-27
v0.0.1a 2024-04-27 Initial release
load
in the examples