The idea behind Hc
is to allow any HTML element to perform requests on the server for HTML fragments. These requests are specified via the data-request
attribute. We call request elements, elements that bear such an attribute.
In the following example:
<button data-request="POST /clicked" data-effect="element">Replace me</button>
The button is a request element. The request is performed when the event specified via the data-event
attribute occurs. For buttons, if unspecified, this is the click
event.
What happens exactly on a request is fully specified by the connection cycle which we describe next.
Each request element defines a connection cycle in which a few other elements take part. All these other elements are always defined via attributes of the request element. They can all point to the same element. In particular if they are not specified they mostly default to the request element.
data-request
attribute. It is the element that specifies the connection to the server.data-event-src
attribute. They are the elements whose events specified in the data-event
attribute triggers the request.data-query
attribute. These are the element that are looked up to devise the request's query data (in the request URL for GET
and HEAD
, in the body otherwise).data-target
attribute. It specifies the relative location of the request response effect, that is where and how the HTML fragment from the server gets inserted in the page.data-feedback
. These are additional elements on which the connection cycle is feedback using classes.Every request element on the page cycle through the following steps.
hc-request
hc-error
and stop here. Otherwise unclassify hc-request
and proceed.hc-out
for a while before being effectively removed. Elements that are introduced get transiently classified with hc-in
.If the request is:
The following classes can be used to track the connection cycle.
hc-request
This class is applied on the request element and the feedback element(s) whenever a request to the server is ongoing.
This class can be used to trigger a CSS animation if the request is taking too much time. For example assuming you have a .spinner
in your request element, the following shows the spinner if the request is taking longer than 500ms.
.spinner { visibility: hidden; } .hc-request .spinner { animation: spin 1s linear 500ms infinite; } @keyframes spin { from { visibility: visible; } to { transform: rotate(360deg); }}
hc-error
This class is applied on the request element and the feedback element whenever a request failed. This includes if the request returns a 4XX or 5XX result.
hc-in
and hc-in-parent
These classes are applied for a small amount of time (one render frame) when the effect is performed. hc-in
is applied on the root of HTML trees that are inserted in the DOM by the effect. hc-in-parent
is applied on their parent.
These classes are used to trigger CSS animations. For example to fade in an element in 250ms on insertion use:
.my-element.hc-in { opacity: 0; } .my-element { opacity: 1; transition: opacity 250ms; }
hc-out
and hc-out-parent
These classes are applied on elements (if any) that are removed by the effect just before it is performed. hc-out
is applied on elements that will be removed. hc-out-parent
is on their parent.
To determine the amount of time during which the classes are applied before the effect is performed, first the classes are applied on the elements. Then for each of these elements we take the maximum value of either CSS attribute --hc-out-duration
, animation-duration
or transition-duration
. The maximal value of all these durations defines the duration.
If the elements animating are descendent of those that are classified by hc-out
or hc-out-parent
, use the --hc-out-duration
attribute on the .hc-out
elements to set the time needed. For example the following ensures that the hc-out
and hc-out-parent
classes are maintained on the elements to be removed for 500ms before being effectively removed:
.hc-out { --hc-out-duration: 500ms }
The only attribute required for an HTML element to be connected to your server is data-request which specifies the request to initiate the connection.
data-request="[<m>] <url>"
This attribute drives it all. If unspecified the element has no connection to the server and all other data-*
attributes are ignored.
<url>
is the URL to request, use the ws://
or wss://
scheme for a websocket connection. <m>
is the HTTP request method or SSE
to request a server sent events connection; defaults to GET
if unspecified.
data-query="<sel>"
The value of <sel>
determines the elements that are used to specify the query of the request. If unspecified this is the request element itself.
The final query of the request is determined by taking the query part of the requested URL and for each of the elements selected by <sel>
appending:
form
, the form data as gathered by FormData
.<value>
and the element has a name
property value <name>
, a <name>
to <value>
binding. If the element has no name
use "value"
for the <name>
.The query is transmitted in the URL on GET
or HEAD
requests and otherwise in the request body encoded with application/x-www-form-urlencoded
unless there is a file input in which case multipart/form-data
is used.
data-query-rescue="<bool>"
Use this to prevent users from leaving a page without performing the request if the query data changed.
If true
, the values determined by data-query
are snapshot on DOM insertion. If they still exist and they changed when the beforeunload
window event triggers the browser specific "leave page confirmation" modal is triggered.
data-event="<ev> <mod>*"
<ev>
is the name of the JavaScript event of the event source that triggers requests. <mod>
are modifiers for the event:
once
, the event is triggered only once.debounce:<dur>
, a debouncing time, the request only occurs after duration <time>
of the last event occurence.throttle:<dur>
, a throttling time, the request occurs immediatly but won't occur again before duration <dur>
.filter:<jsfun>
, name of a filtering function. The function is given the requesting element and the event, if it returns false
the event did not occur.If the attribute is unspecified this is:
submit
for form
elements.change
for input
, textarea
and select
.click
otherwise.data-event-src="<sel>"
The DOM element(s), as selected by <sel>
, whose events are being listened for to trigger a request. If unspecified this is the request element itself.
data-target="<sel>"
The target DOM element, as selected by the first element of <sel>
, on which the request response performs its effect. If unspecified this is the request element itself.
data-effect="<eff>"
The value <eff>
determines the way the HTML response is used on the target. This can be:
children
to replaces the children of the target (default).element
to replaces the target itself.beforebegin
to insert before the target.afterbegin
to insert before the first child of the target.beforeend
to insert after the last child of the target.afterend
to insert after the target.none
to discard the HTML response.event <ev>
to discard the HTML response but trigger an event named <ev>
on the targetIf unspecified this is children
.
data-feedback="<sel>"
DOM elements in addition to target element on which the connection cycle is feedback with hc-request
and hc-error
.
Attributes of hc
use CSS selectors with querySelector
to gather query data and target response effects. However CSS selectors have no syntax for addressing ancestors. We slightly extend the syntax to allow it because in many cases this eschews the need to use element identifiers which improves modularity – use with care, it can also improve obscurity.
We allow a CSS selector to be prefixed by a sequence of ancestor specifications which select the root on which the CSS selector is then applied. This provides full tree addressing: first move up to find an ancestor and then down by applying the CSS selector to it.
An ancestor specification is made of an optional element, optional classes and the made up :up
pseudo-class. The semantic is to move up from the element by following the up selectors from left-to-right and then apply the CSS selector on ancestor that was found.
Examples:
:up # parent :up :up # parent's parent :up :up :scope > * # parent's parent's children .beet.root:up # ancestor with classes beet and root .beet:up .root:up # move to beet ancestor, then to root ancestor ul.beet.root:up # ul ancestor with classes beet and root div:up .beet # div ancestor, beet classified descendents
The full syntax as an RFC 5234 ABNF grammar is described as follows:
sel = *(up SP) css-selector up = [[el] *class] ":up" el = 1*(ALPHA) class = "." 1*(ALPHA) css-selector = … # See the CSS specification
The server should respond with an HTML fragment to be inserted with respect to the request target. Additionally it can use the headers described next to control the client.
hc-redirect: <url>
Redirects the page to <url>
. The body of the response is ignored by Hc
.
hc-reload: true
Reloads the page. The body of the response is igored by Hc
.
hc-location-push: <url>
Pushes location <url>
on the history stack.
hc-location-replace: <url>
Replaces current location by <url>
(without reloading). Has no effect if hc-location-push
is present.
hc-location-title: <pct-title>
Replaces the current title by percent encoded title <pct-title>
. Mostly used in conjunction with hc-location-push
or hc-location-replace
.
hc: true
Indicates the request is made by Hc
.
referer: <url>
This is the standard HTTP referer
header, it indicates the URL of the page (without the fragment) that performs the request. hc
makes requests with the same-origin
policy.
If your HTML fragment returns relative URLs they will be interpreted relative to <url>
once they are injected in the DOM.
FIXME. We should likely make that or at least the policy, tweakable in some way.