on
This feature is responsible for initiating requests to the server.
It supports the following request types:
- XHR requests, including form submissions
- full page transitions
- History API navigation
Its only responsibility is configuring and sending the request. What happens after the request is outside the scope of this feature.
All XHR requests include an X-Requested-With header set to XMLHttpRequest,
allowing the server to distinguish XHR requests from full page navigation when
needed.
KEML sets a permanent cookie named tzo, containing the
browser timezone
after the first load of any page that includes the KEML runtime, all subsequent
requests will include this cookie.
KEML relies heavily on and benefits from the existing web stack, without overriding its semantics or behavior, including the standard browser page caching mechanisms.
Thus, if your server is smart about sending the correct caching response headers at the correct time - that can help speed up KEML even more.
That's right, your application's performance optimizations also live on the server 🤯.
The on attribute is the only required attribute for an element to become
capable of initiating requests to the server.
Even though most examples here show on being used on the same element as
on:*, that is only for brevity and simplicity's sake. It can appear on any
number of elements at once.
on provides a set of optional attributes for customizing its behavior.
These are covered in the sections below.
Endpoint+Method Configuration¶
The default endpoint is an empty string ("").
The default HTTP method is GET.
Given the current URL:
1 | |
| Configuration | Result |
|---|---|
| (empty) | https://www.example.com/some/path/ |
list-todo or ./list-todo |
https://www.example.com/some/path/list-todo/ |
../list-todo |
https://www.example.com/some/list-todo/ |
/list-todo |
https://www.example.com/list-todo/ |
/file.html |
https://www.example.com/file.html |
Automatic normalization:
- paths ending in a file extension → exactly zero trailing slashes
- paths without a file extension → exactly one trailing slash
get, href, action and src¶
These attributes override the default endpoint.
1 2 3 4 5 6 7 8 9 10 11 12 | |
1 2 3 4 5 6 7 8 | |
post, put and delete¶
These attributes override both the default endpoint and the default HTTP method.
The method they set is the same as the attribute name.
1 2 3 4 5 6 7 8 9 10 11 12 | |
1 2 3 4 5 6 7 8 | |
about:blank¶
You can "send a request" to the about:blank endpoint when no work is needed
and an empty response should be produced.
This does not trigger any network request.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
Lorem ipsum dolor sit amet consectetur adipiscing elit.
Sit amet consectetur adipiscing elit quisque faucibus ex.
Adipiscing elit quisque faucibus ex sapien vitae pellentesque.
method¶
This attribute overrides just the default HTTP method. The provided value will be converted into all uppercase.
There are no restrictions on the value — it can be anything as long as your server understands the verb.
It has higher precedence than the attributes shown above.
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
1 2 3 4 5 6 7 8 | |
debounce and throttle¶
These attributes can be used to rate limit the on feature.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
1 2 3 4 5 6 7 8 9 10 | |
The request is only sent when you stop typing for two seconds.
Request Data¶
Forms submit their fields the same way they always did in HTML, including native field validation rules.
Custom elements (e.g. Web Components) can also be made validatable; all they must do is implement the standard checkValidity method.
What's more, any element inside the one sending the request, including the
element itself, can contribute values to the request data by specifying a name
and a value attribute. For non-form fields, both attributes must be provided.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | |
1 2 3 4 5 6 7 8 9 10 | |
Enter your name and submit the form
Form fields can even submit themselves without a form at all.
This example works exactly the same.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | |
1 2 3 4 5 6 7 8 9 10 | |
Enter your name and submit the field
Name:It does not even have to be a form field.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
1 2 3 4 5 6 7 8 9 10 | |
A div huh 🤔
Name:Request Headers¶
Request headers can be set using:
h-<header name>="header value"
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
1 2 3 4 5 6 7 8 | |
credentials¶
RTFM (just kidding)
This is simply the standard mechanism for including credentials (e.g. cookies) when making requests to another domain, where they would normally not be included for security reasons.
Make sure you know what you're doing if you're using this.
This is a boolean attribute, so its value is ignored and only the presence determines whether or not the credentials are enabled.
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
1 2 3 4 5 6 7 8 9 10 11 12 | |
stream¶
This attribute allows the server to stream multiple responses after a single request. Responses do not need to arrive at the same time, and their number does not have to be finite.
Think of this as a middle ground between a normal XHR request and an SSE connection.
It behaves like an XHR request in most respects, but can remain persistent like SSE. It is also simpler on the backend, since you can send as much data as you like, as often as you like, and it will continue streaming until you finalize the response.
Use SSE when you want to send results to all subscribed clients, and use streaming when you want to send the same kind of results in response to a single request from a specific client.
This is made possible by a special delimiting HTML comment convention:
<!-- KEML -->. Each distinct portion of HTML you send must be separated by
this delimiter. You must ensure that the text between two delimiting comments is
a valid, complete, and parsable HTML fragment. The comment is case- and
whitespace-insensitive.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
Since the last message is not delimited by a
<!-- KEML --> comment, it will not be received until
server.end() is called.
Because there is a server.end() call, this stream is finite.
After receiving a single request, this endpoint will return four separate responses: the first immediately, and the next three spaced two seconds apart.
In this example server, the timing is a bit contrived for simplicity. In the real world, these streamed fragments may be delayed by database latency, a microservice response, a chatbot generating messages, or any other timing-dependent behavior.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
redirect¶
This attribute switches the operational mode of on from sending requests to
performing redirects.
The endpoint resolution logic remains the same. Headers and HTTP method are ignored. Form data (excluding file uploads) is applied to the query string.
assign¶
This option performs a full page navigation.
1 2 3 4 5 6 7 8 | |
1 2 3 4 5 6 7 8 | |
1 2 3 | |
replace¶
This option performs a full page navigation, but replaces the current history entry instead of adding a new one.
1 2 3 4 5 6 7 8 | |
1 2 3 4 5 6 7 8 | |
1 2 3 | |
pushState and replaceState¶
These options work exactly the same, but use the History API and do not cause a full page navigation.
Oh, and did I forget to mention that you get SSR for free? It is not some separate thing you have to implement, and it does not impose restrictions on your server infrastructure 🤯.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | |
1 | |
1 | |
1 | |
once¶
This is a self-destruct instruction for the on attribute. It is removed after
the first invocation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
1 | |
You can only add a single entry to the log.
Polling¶
Info
Example only provided for completeness. You are discouraged from doing this. Use SSE if possible.
KEML does not implement polling as a dedicated feature, but it can still be assembled from the basic building blocks shown above.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Changes color every 5 seconds.
Virtualization¶
KEML does not implement virtualization as a dedicated feature, but it can still be assembled from the basic building blocks shown above.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | |
This route cleans up the invisible slices.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
The point is not the grid, but the fact that it is a completely normal
div element, with no grid libraries or any kind of special
rendering needed.
This approach does come with certain limitations, however. Browsers have a
hard limit on total scrollable layout height, beyond which layout calculations
and scroll positioning may start to break down
(e.g. 17895697px in Firefox).
That means this example supports only around 350,000 rows as a practical maximum at typical row heights.
This example shows 300,000 rows (only 😅), so we are comfortably within the safe range.