HTML properties for reducing the need of writing JavaScript code
In a web application user interface, the application state is usually managed with JavaScript. Variables store the result of input validations, computations, data to send to the server, user preferences, and so on. However, HTML is capable of holding these state values. Native HTML components can store application state as objects, numbers, strings, booleans, and other data types easily. That state can be generated on the back-end and embedded within HTML components or generated on the front-end when needed. New HTML properties can share state among components using send and receive primitives. For example, a sender component may share its state when a DOM event occurs, while a receiver component can, if necessary, transform the state before consuming it or passing it along to another component. State is kept and transformed by the user interface as the user interacts with it.
Moreover, forms and other components can leverage new properties to enhance their behavior and better adapt to modern application needs.
- πͺ Make it easy to query server state, store it and share it among HTML elements
- π Make it possible that forms send JSON data, custom headers, and use different HTTP methods
- π― Create SPA-related behavior (routes, html replacement) without writing any related JavaScript code.
- WORK-IN-PROGRESS
- Partially supported! See
examples/spa.html
and Seeexamples/spa-js.html
- π§ Short learning curve
- π’ Pure HTML properties
- π§© Also works with third-party components
- β‘ Fast, vanilla JavaScript
- π¦ Just 9k unzipped (code), no external dependencies
- π‘οΈ Unit tested
npm i html-signals
π Just include the library in your application and call register()
.
Example using a CDN (you can copy-and-paste to see it working; no installation required):
<body>
<p>This form will send its data as JSON and present any errors in the div element</p>
<form method="POST" action="/foo" send-as="json"
on-send-error="error => msg.innerText = 'Ouch... ' + error.message;"
>
<label for="name" >Name:</label>
<input type="text" name="name" id="name" required />
<button type="submit" >Send</button>
<div id="msg" ></div>
</form>
<script type="module" >
import { register } from "https://unpkg.com/html-signals/dist/index.esm.js";
register(); // Done
</script>
</body>
π The documentation is being improved - please see the tests or examples for now.
Just two functions to use:
/**
* Register new behavior for your HTML elements.
*
* @param root Element in which your HTML elements with extended properties will be declared. By default, it is `document.body`.
* @param options Object with some options, such as:
* - `sanitizer?: ( html: string ) => string` function for sanitizing HTML values.
*/
export function register( root?: HTMLElement, options?: Options ): void;
/**
* Unregister the new behavior for your HTML elements, and cancel all ongoing fetch events eventually started by them.
*/
export function unregister(): void;
π Tip: The property send
can replace all these properties at once: send-prop
, send-element
, send-on
, send-to
, send-as
.
-
send-prop
indicates a property to be sent to another component. Example:<!-- When clicked, this anchor will send its 'href' value to the span --> <a href="/foo" prevent send-prop="href" send-on="click" send-to="span" >Click Me</a> <p>Take me to <span>...</span></p>
-
send-element
indicates an HTML element to be sent to another element. Example:<!-- When clicked, the first div will send the template content (clone) to the div with id "clock" --> <template><p>β±οΈ</p></template> <div send-element="template" send-on="click" send-to="#clock" >Show Clock</div> <div id="clock" receive-as="element" ></div>
-
send-to
indicates a query selector that contain the elements to send a content. Example:<!-- When clicked, the first div will send its 'data-hello' value to the divs with id "one" and "two" --> <div data-hello="Hello!" send-prop="data-hello" send-on="click" send-to="#one,#two" >Say Hello</div> <div id="one" ></div> <div id="two" ></div>
-
send-as
indicates the format of the content to send:"text"
- that is the default value"html"
"number"
"int"
"float"
"json"
- values must contain unquoted or single-quoted properties only (HTML limitation)"fetch-html"
(html, css)"fetch-html-js"
(html, css, javascript)"fetch-json"
"fetch-text"
"element"
"element-clone"
- more options to appear β
-
send-on
indicates in which event the element will send the content.- any DOM event supported by the element, such as
"click"
,"change"
,"blur"
,"focus"
etc. "domcontentloaded"
- that will trigger when the DOM is loaded."receive"
- that will forward a content to other element(s)- more options to appear β
- any DOM event supported by the element, such as
-
on-send-error
to indicate a function to execute when a content is sent and an error occurs - usually forfetch-*
values insend-as
.- The function receives:
- an
Error
object as the first argument; - the target HTML element, that receives the error, as the second argument; and
- a
fetch
'sResponse
object as the third argument, if afetch-*
format is used. Example:
- an
<!-- When the div is clicked, it will fetch the JSON content from the URL, and send it to the paragraph --> <div data-url="https://jsonplaceholder.typicode.com/todos/1" send="data-url|click|#x|fetch-json" on-send-error="(error, target, response) => console.log(error, target, response)" >Click me</a> <p id="x" receive-as="text" ></p>
- The function receives:
-
send
, that can be used as a replacement to all these properties at once:send-prop
,send-element
,send-on
,send-to
andsend-as
.- Syntax 1:
send="property to send|event|target element(s)|format"
, whereformat
is optional (defaulttext
). Example:<!-- When changed, the input will send its value as text to the div element. --> <input send="value|change|div" /> <div receive-as="text" ></div>
- Syntax 2:
send="{query selector for the element to send}|event|target element(s)|format"
, whereformat
is optional (defaulttext
). Example:<!-- When clicked, the div will send the paragraph element to the span with id "bar" --> <div send="{p}|click|#bar" >Click Me</div> <p>Hello</p> <span id="bar" receive-as="element" ></span>
- Syntax 1:
-
receive-as
to indicate the receiving format:"text"
"html"
"number"
"int"
"float"
"json"
"fetch-html"
(html, css)"fetch-html-js"
(html, css, javascript)"fetch-json"
"fetch-text"
"element"
"element-clone"
- more options to appear β
-
on-receive
to indicate a (synchronous) function to execute before a content is received. The function can be used to transform a content. Examples:<!-- When clicked, the div will send its text, "Hello", to the span. Then the span will change the text to "Hello, World!". --> <div send="text|click|span" >Hello</div> <span on-receive="text => `${text}, World!`" ></span> <!-- When clicked, button will send its JSON object in "data-contact" to the span elements. Then first span will extract and show the name (from the object), while the second will extract and show the surname. --> <button data-contact="{name: 'Bob', surname: 'Dylan'}" send="data-contact|click|span|json" >Click Me</button> <span on-receive="obj => obj.name" ></span> <span on-receive="obj => obj.surname" ></span>
-
on-receive-error
to indicate a function to execute when a content is received and an error occurs - usually forfetch-*
values inreceive-as
.- The function receives:
- an
Error
object as the first argument; - the target HTML element, that receives the error, as the second argument; and
- a
fetch
'sResponse
object as the third argument, if afetch-*
format is used. Example:
- an
<!-- When the div is clicked, it will send the URL to the paragraph, that will fetch the JSON content from it --> <div data-url="https://jsonplaceholder.typicode.com/todos/1" send="data-url|click|#x|text" >Click me</a> <p id="x" receive-as="fetch-json" on-receive-error="(error, target, response) => console.log(error, target, response)" ></p>
- The function receives:
-
A form
method
can usePUT
,PATCH
andDELETE
(version0.11.0
+).- Currently, there must be an input element named
id
to store that value that will be dynamically added to the URL inaction
. Thatid
element can be set ashidden
. Example:
<!-- This will send an HTTP `DELETE` to `/foo/10` --> <form method="DELETE" action="/foo" > <input type="hidden" name="id" value="10" /> <button type="submit" >Delete</button> </form>
- When
POST
,PUT
orPATCH
are used, thesend-as
property can use these values:"form"
, that is the default value, will send asapplication/x-www-form-urlencoded
;"json"
will send asapplication/json
;"multipart"
will send asmultipart/form-data
.
- Example:
<!-- This will send an HTTP POST to "/foo" with a JSON object, such as {"name": "Bob"} --> <form method="POST" action="/foo" send-as="json" > <label for="name" >Name:</label> <input type="text" name="name" id="name" required /> <button type="submit" >Send</button> </form>
- Currently, there must be an input element named
-
A form can use the property
headers
with additional HTTP headers with single-quoted JSON (version0.13.0
+). Example:<!-- This will send a `PUT` to `/foo/10` with the additional headers `"X-Foo"` with `"Hello"` and `"X-Bar"` with `"World"` --> <form method="PUT" action="/foo" headers="{'X-Foo':'Hello', 'X-Bar':'World'}" > <input type="hidden" name="id" value="10" /> <label for="name" >Name:</label> <input type="text" name="name" id="name" required /> <button type="submit" >Send</button> </form>
- Additional headers can subscribe default headers, if desired. Example:
headers="{'Content-Type': 'text/plain'}"
.
- Additional headers can subscribe default headers, if desired. Example:
-
prevent
- to call preventDefault() on elements such as<a>
and<form>
. -
headers
- specify headers to include in an HTTP request, using JSON format with unquoted or single-quoted properties.- Example:
headers="{'Accept': 'text/plain'}"
- Headers are considered for
fetch-*
values declared inreceive-as
orsend-as
.
- Example:
$history
can be used bysend-to
orsend
for adding a URL to the browser history - e.g. send-to="$history"
send-prop
andsend-element
must not be used together.- When
"element-clone"
references a<template>
tag, it will clone the template's content. - By default,
fetch
calls will include the option{ credentials: 'include' }
for sending cookies and authentication headers to the server.
- JSON content in element properties must have unquoted or single-quoted properties (HTML limitation)
- ex.:
<span data-todo="{ title: 'Buy coffee', completed: false }" ></span>
- ex.:
<span data-todo="{ 'title': 'Buy coffee', 'completed': false }" ></span>
- ex.:
$history
must be at the beginning or at the end of "send-to"