Merge branch 'master' into dev
@ -3,7 +3,7 @@ Thank you for your interest in contributing! Because we're a small team, we have
|
||||
|
||||
## Issues
|
||||
1. Issues are the best place to propose a new feature. Keep in mind that htmx is a small library, so there are lots of great ideas that don't fit in the core; it's always best to check in about an idea before doing a bunch of work on it.
|
||||
1. When proposing a new features, we will often suggest that you implement it as an [extension](https://github.com/bigskysoftware/htmx-extensions), so try that first. Even if we don't end up supporting it officially, you can publish it yourself and we can link to it.
|
||||
1. When proposing a new feature, we will often suggest that you implement it as an [extension](https://github.com/bigskysoftware/htmx-extensions), so try that first. Even if we don't end up supporting it officially, you can publish it yourself and we can link to it.
|
||||
1. Search the issues before proposing a feature to see if it is already under discussion. Referencing existing issues is a good way to increase the priority of your own.
|
||||
1. We don't have an issue template yet, but the more detailed your description of the issue, the more quickly we'll be able to evaluate it.
|
||||
1. See an issue that you also have? Give it a reaction (and comment, if you have something to add). We note that!
|
||||
|
@ -45,6 +45,23 @@ describe('hx-vals attribute', function() {
|
||||
div.innerHTML.should.equal('Clicked!')
|
||||
})
|
||||
|
||||
it('Dynamic hx-vals using spread operator works', function() {
|
||||
this.server.respondWith('POST', '/vars', function(xhr) {
|
||||
var params = getParameters(xhr)
|
||||
params.v1.should.equal('test')
|
||||
params.v2.should.equal('42')
|
||||
xhr.respond(200, {}, 'Clicked!')
|
||||
})
|
||||
window.foo = function() {
|
||||
return { v1: 'test', v2: 42 }
|
||||
}
|
||||
var div = make("<div hx-post='/vars' hx-vals='js:{...foo()}'></div>")
|
||||
div.click()
|
||||
this.server.respond()
|
||||
div.innerHTML.should.equal('Clicked!')
|
||||
delete window.foo
|
||||
})
|
||||
|
||||
it('hx-vals can be on parents', function() {
|
||||
this.server.respondWith('POST', '/vars', function(xhr) {
|
||||
var params = getParameters(xhr)
|
||||
|
@ -161,6 +161,7 @@ Thank you to all our generous <a href="https://github.com/sponsors/bigskysoftwar
|
||||
padding: 16px;
|
||||
min-height: 100px;
|
||||
border-bottom: none;
|
||||
width:33%;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 760px) {
|
||||
@ -169,39 +170,58 @@ Thank you to all our generous <a href="https://github.com/sponsors/bigskysoftwar
|
||||
table, thead, tbody, th, td, tr {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#sponsor-table td {
|
||||
width:90%;
|
||||
}
|
||||
#sponsor-table td * {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
<div style="overflow-x: auto">
|
||||
|
||||
<h1 style="margin-top:32px;text-align:center">Platinum Sponsor</h1>
|
||||
<table id="sponsor-table">
|
||||
<tr>
|
||||
<td colspan="3">
|
||||
<a data-github-account="NotASithLord" href="https://hydrahost.com">
|
||||
<img class="dark-hidden" src="/img/hydra-hosting.svg" alt="The GPU Marketplace" style="width:100%;">
|
||||
<img class="dark-visible" src="/img/hydra-hosting-dark.svg" alt="The GPU Marketplace" style="width:100%;">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3">
|
||||
<a data-github-account="deco-cx" href="https://deco.cx/?utm_source=htmx"><img src="/img/deco.cx-logo-outline.png" alt="Your first and last web editor" style="width:100%;"></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a data-github-account="JetBrainsOfficial" href="https://www.jetbrains.com"><img src="/img/jetbrains.svg" alt="Jetbrains" style="max-width:30%;min-width:100px;"></a>
|
||||
</td>
|
||||
<td>
|
||||
<a data-github-account="commspace" href="https://www.commspace.co.za">
|
||||
<img class="dark-hidden" src="/img/commspace.svg" alt="commspace" style="min-width:200px"/>
|
||||
<img class="dark-visible" src="/img/commspace-dark.svg" alt="commspace" style="min-width:200px"/>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Gold Sponsors
|
||||
|
||||
<table id="sponsor-table">
|
||||
<tr>
|
||||
<td>
|
||||
<a href="https://github.blog/2023-04-12-github-accelerator-our-first-cohort-and-whats-next"><img class="dark-invert" src="/img/Github_Logo.png" alt="GitHub" style="max-width:30%;min-width:100px;"></a>
|
||||
<a data-github-account="NotASithLord" href="https://hydrahost.com">
|
||||
<img class="dark-hidden" src="/img/hydra-hosting.svg" alt="The GPU Marketplace" style="width:100%;">
|
||||
<img class="dark-visible" src="/img/hydra-hosting-dark.svg" alt="The GPU Marketplace" style="width:100%;">
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a data-github-account="deco-cx" href="https://deco.cx/?utm_source=htmx"><img src="/img/deco.cx-logo-outline.png" alt="Your first and last web editor" style="width:100%;"></a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Silver Sponsors
|
||||
|
||||
<table id="sponsor-table">
|
||||
<tr>
|
||||
<td>
|
||||
<a data-github-account="JetBrainsOfficial" href="https://www.jetbrains.com"><img src="/img/jetbrains.svg" alt="Jetbrains" style="max-width:80%;min-width:100px;"></a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="https://github.blog/2023-04-12-github-accelerator-our-first-cohort-and-whats-next"><img class="dark-invert" src="/img/Github_Logo.png" alt="GitHub" style="max-width:80%;min-width:100px;"></a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table id="sponsor-table">
|
||||
<tr>
|
||||
<td>
|
||||
<a data-github-account="craftcms" href="https://craftcms.com">
|
||||
|
@ -126,15 +126,17 @@ Note that using a [meta tag](@/docs.md#config) is the preferred mechanism for se
|
||||
* `wsReconnectDelay:'full-jitter'` - string/function: the default implementation of `getWebSocketReconnectDelay` for reconnecting after unexpected connection loss by the event code `Abnormal Closure`, `Service Restart` or `Try Again Later`
|
||||
* `wsBinaryType:'blob'` - string: the [the type of binary data](https://developer.mozilla.org/docs/Web/API/WebSocket/binaryType) being received over the WebSocket connection
|
||||
* `disableSelector:"[hx-disable], [data-hx-disable]"` - array of strings: htmx will not process elements with this attribute on it or a parent
|
||||
* `scrollBehavior:'smooth'` - string: the behavior for a boosted link on page transitions. The allowed values are `auto` and `smooth`. Smooth will smoothscroll to the top of the page while auto will behave like a vanilla link.
|
||||
* `scrollBehavior:'instant'` - string: the scroll behavior when using the [show](@/attributes/hx-swap.md#scrolling-scroll-show) modifier with `hx-swap`. The allowed values are `instant` (scrolling should happen instantly in a single jump), `smooth` (scrolling should animate smoothly) and `auto` (scroll behavior is determined by the computed value of [scroll-behavior](https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior)).
|
||||
* `defaultFocusScroll:false` - boolean: if the focused element should be scrolled into view, can be overridden using the [focus-scroll](@/attributes/hx-swap.md#focus-scroll) swap modifier
|
||||
* `getCacheBusterParam:false` - boolean: if set to true htmx will append the target element to the `GET` request in the format `org.htmx.cache-buster=targetElementId`
|
||||
* `globalViewTransitions:false` - boolean: if set to `true`, htmx will use the [View Transition](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) API when swapping in new content.
|
||||
* `methodsThatUseUrlParams:["get"]` - array of strings: htmx will format requests with these methods by encoding their parameters in the URL, not the request body
|
||||
* `methodsThatUseUrlParams:["get", "delete"]` - array of strings: htmx will format requests with these methods by encoding their parameters in the URL, not the request body
|
||||
* `selfRequestsOnly:true` - boolean: whether to only allow AJAX requests to the same domain as the current document
|
||||
* `ignoreTitle:false` - boolean: if set to `true` htmx will not update the title of the document when a `title` tag is found in new content
|
||||
* `scrollIntoViewOnBoost:true` - boolean: whether or not the target of a boosted element is scrolled into the viewport. If `hx-target` is omitted on a boosted element, the target defaults to `body`, causing the page to scroll to the top.
|
||||
* `triggerSpecsCache:null` - object: the cache to store evaluated trigger specifications into, improving parsing performance at the cost of more memory usage. You may define a simple object to use a never-clearing cache, or implement your own system using a [proxy object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Proxy) |
|
||||
* `triggerSpecsCache:null` - object: the cache to store evaluated trigger specifications into, improving parsing performance at the cost of more memory usage. You may define a simple object to use a never-clearing cache, or implement your own system using a [proxy object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Proxy)
|
||||
* `htmx.config.responseHandling:[...]` - HtmxResponseHandlingConfig[]: the default [Response Handling](@/docs.md#response-handling) behavior for response status codes can be configured here to either swap or error
|
||||
* `htmx.config.allowNestedOobSwaps:true` - boolean: whether to process OOB swaps on elements that are nested within the main response element. See [Nested OOB Swaps](@/attributes/hx-swap-oob.md#nested-oob-swaps).
|
||||
|
||||
##### Example
|
||||
|
||||
|
@ -6,11 +6,11 @@ The `hx-get` attribute will cause an element to issue a `GET` to the specified U
|
||||
the HTML into the DOM using a swap strategy:
|
||||
|
||||
```html
|
||||
<div hx-get="/example">Get Some HTML</div>
|
||||
<button hx-get="/example">Get Some HTML</button>
|
||||
```
|
||||
|
||||
This example will cause the `div` to issue a `GET` to `/example` and swap the returned HTML into
|
||||
the `innerHTML` of the `div`.
|
||||
This example will cause the `button` to issue a `GET` to `/example` and swap the returned HTML into
|
||||
the `innerHTML` of the `button`.
|
||||
|
||||
### Notes
|
||||
|
||||
|
@ -31,10 +31,10 @@ that will show the spinner:
|
||||
transition: opacity 500ms ease-in;
|
||||
}
|
||||
.htmx-request .htmx-indicator{
|
||||
opacity:1
|
||||
opacity:1;
|
||||
}
|
||||
.htmx-request.htmx-indicator{
|
||||
opacity:1
|
||||
opacity:1;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -9,7 +9,7 @@ Here is an example that selects a subset of the response content:
|
||||
|
||||
```html
|
||||
<div>
|
||||
<button hx-get="/info" hx-select="#info-details" hx-swap="outerHTML">
|
||||
<button hx-get="/info" hx-select="#info-detail" hx-swap="outerHTML">
|
||||
Get Info!
|
||||
</button>
|
||||
</div>
|
||||
|
@ -27,14 +27,50 @@ The value of the `hx-swap-oob` can be:
|
||||
|
||||
If the value is `true` or `outerHTML` (which are equivalent) the element will be swapped inline.
|
||||
|
||||
If a swap value is given, that swap strategy will be used.
|
||||
If a swap value is given, that swap strategy will be used and the encapsulating tag pair will be stripped for all strategies other than `outerHTML`.
|
||||
|
||||
If a selector is given, all elements matched by that selector will be swapped. If not, the element with an ID matching the new content will be swapped.
|
||||
|
||||
### Troublesome Tables
|
||||
### Using alternate swap strategies
|
||||
|
||||
As mentioned previously when using swap strategies other than `true` or `outerHTML` the encapsulating tags are stripped, as such you need to excapsulate the returned data with the correct tags for the context.
|
||||
|
||||
When trying to insert a `<tr>` in a table that uses `<tbody>`:
|
||||
```html
|
||||
<tbody hx-swap-oob="beforeend:#table tbody">
|
||||
<tr>
|
||||
...
|
||||
</tr>
|
||||
</tbody>
|
||||
```
|
||||
|
||||
A "plain" table:
|
||||
```html
|
||||
<table hx-swap-oob="beforeend:#table2">
|
||||
<tr>
|
||||
...
|
||||
</tr>
|
||||
</table>
|
||||
```
|
||||
|
||||
A `<li>` may be encapsulated in `<ul>`, `<ol>`, `<div>` or `<span>`, for example:
|
||||
```html
|
||||
<ul hx-swap-oob="beforeend:#list1">
|
||||
<li>...</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
A `<p>` can be encapsulated in `<div>` or `<span>`:
|
||||
```html
|
||||
<span hx-swap-oob="beforeend:#text">
|
||||
<p>...</p>
|
||||
</span>
|
||||
```
|
||||
|
||||
### Troublesome Tables and lists
|
||||
|
||||
Note that you can use a `template` tag to encapsulate types of elements that, by the HTML spec, can't stand on their own in the
|
||||
DOM (`<tr>`, `<td>`, `<th>`, `<thead>`, `<tbody>`, `<tfoot>`, `<colgroup>`, `<caption>` & `<col>`).
|
||||
DOM (`<tr>`, `<td>`, `<th>`, `<thead>`, `<tbody>`, `<tfoot>`, `<colgroup>`, `<caption>`, `<col>` & `<li>`).
|
||||
|
||||
Here is an example with an out of band swap of a table row being encapsulated in this way:
|
||||
|
||||
@ -51,6 +87,27 @@ Here is an example with an out of band swap of a table row being encapsulated in
|
||||
|
||||
Note that these template tags will be removed from the final content of the page.
|
||||
|
||||
### Slippery SVGs
|
||||
|
||||
Some element types, like SVG, use a specific XML namespace for their child elements. This prevents internal elements from working correctly when swapped in, unless they are encapsulated within a `svg` tag. To modify the internal contents of an existing SVG, you can use both `template` and `svg` tags to encapsulate the elements, allowing them to get the correct namespace applied.
|
||||
|
||||
Here is an example with an out of band swap of svg elements being encapsulated in this way:
|
||||
|
||||
```html
|
||||
<div>
|
||||
...
|
||||
</div>
|
||||
<template><svg>
|
||||
<circle hx-swap-oob="true" id="circle1" r="35" cx="50" cy="50" fill="red" />
|
||||
</svg></template>
|
||||
<template><svg hx-swap-oob="beforebegin:#circle1">
|
||||
<circle id="circle2" r="45" cx="50" cy="50" fill="blue" />
|
||||
</svg></template>
|
||||
```
|
||||
This will replace circle1 inline and then insert circle2 before circle1.
|
||||
|
||||
Note that these `template` and `svg` wrapping tags will be removed from the final content of the page.
|
||||
|
||||
## Nested OOB Swaps
|
||||
|
||||
By default, any element with `hx-swap-oob=` attribute anywhere in the response is processed for oob swap behavior, including when an element is nested within the main response element.
|
||||
|
@ -2,9 +2,9 @@
|
||||
title = "hx-vals"
|
||||
+++
|
||||
|
||||
The `hx-vals` attribute allows you to add to the parameters that will be submitted with an AJAX request.
|
||||
The `hx-vals` attribute allows you to add to the parameters that will be submitted with an AJAX request.
|
||||
|
||||
By default, the value of this attribute is a list of name-expression values in [JSON (JavaScript Object Notation)](https://www.json.org/json-en.html)
|
||||
By default, the value of this attribute is a list of name-expression values in [JSON (JavaScript Object Notation)](https://www.json.org/json-en.html)
|
||||
format.
|
||||
|
||||
If you wish for `hx-vals` to *evaluate* the values given, you can prefix the values with `javascript:` or `js:`.
|
||||
@ -23,12 +23,20 @@ When using evaluated code you can access the `event` object. This example includ
|
||||
</div>
|
||||
```
|
||||
|
||||
You can also use the spread operator to dynamically specify values. This allows you to include all properties from an object returned by a function:
|
||||
|
||||
```html
|
||||
<div hx-get="/example" hx-vals='js:{...foo()}'>Get Some HTML, Including All Values from foo() in the Request</div>
|
||||
```
|
||||
|
||||
In this example, if `foo()` returns an object like `{name: "John", age: 30}`, both `name` and `age` will be included as parameters in the request.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
* By default, the value of `hx-vals` must be valid [JSON](https://developer.mozilla.org/en-US/docs/Glossary/JSON).
|
||||
* By default, the value of `hx-vals` must be valid [JSON](https://developer.mozilla.org/en-US/docs/Glossary/JSON).
|
||||
It is **not** dynamically computed. If you use the `javascript:` prefix, be aware that you are introducing
|
||||
security considerations, especially when dealing with user input such as query strings or user-generated content,
|
||||
which could introduce a [Cross-Site Scripting (XSS)](https://owasp.org/www-community/attacks/xss/) vulnerability.
|
||||
security considerations, especially when dealing with user input such as query strings or user-generated content,
|
||||
which could introduce a [Cross-Site Scripting (XSS)](https://owasp.org/www-community/attacks/xss/) vulnerability.
|
||||
|
||||
## Notes
|
||||
|
||||
|
@ -55,7 +55,7 @@ custom_classes = "wide-content"
|
||||
htmx is a library that allows you to access modern browser features directly from HTML, rather than using
|
||||
javascript.
|
||||
|
||||
To understand htmx, first lets take a look at an anchor tag:
|
||||
To understand htmx, first let's take a look at an anchor tag:
|
||||
|
||||
```html
|
||||
<a href="/blog">Blog</a>
|
||||
@ -117,27 +117,27 @@ tag to your document head. There is no need for a build system to use it.
|
||||
|
||||
### Via A CDN (e.g. unpkg.com)
|
||||
|
||||
The fastest way to get going with htmx is to load it via a CDN. You can simply add this to
|
||||
The fastest way to get going with htmx is to load it via a CDN. You can simply add this to
|
||||
your head tag and get going:
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/htmx.org@2.0.2" integrity="sha384-QWGpdj554B4ETpJJC9z+ZHJcA/i59TyjxEPXiiUgN2WmTyV5OEZWCD6gQhgkdpB/" crossorigin="anonymous"></script>
|
||||
<script src="https://unpkg.com/htmx.org@2.0.2" integrity="sha384-Y7hw+L/jvKeWIRRkqWYfPcvVxHzVzn5REgzbawhxAuQGwX1XWe70vji+VSeHOThJ" crossorigin="anonymous"></script>
|
||||
```
|
||||
|
||||
An unminified version is also available for debugging as well:
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/htmx.org@2.02/dist/htmx.js" integrity="sha384-gpIh5aLQ0qmX8kZdyhsd6jA24uKLkqIr1WAGtantR4KsS97l/NRBvh8/8OYGThAf" crossorigin="anonymous"></script>
|
||||
<script src="https://unpkg.com/htmx.org@2.0.2/dist/htmx.js" integrity="sha384-yZq+5izaUBKcRgFbxgkRYwpHhHHCpp5nseXp0MEQ1A4MTWVMnqkmcuFez8x5qfxr" crossorigin="anonymous"></script>
|
||||
```
|
||||
|
||||
While the CDN approach is extremely simple, you may want to consider
|
||||
While the CDN approach is extremely simple, you may want to consider
|
||||
[not using CDNs in production](https://blog.wesleyac.com/posts/why-not-javascript-cdn).
|
||||
|
||||
### Download a copy
|
||||
|
||||
The next easiest way to install htmx is to simply copy it into your project.
|
||||
|
||||
Download `htmx.min.js` [from unpkg.com](https://unpkg.com/htmx.org@2.0.1/dist/htmx.min.js) and add it to the appropriate directory in your project
|
||||
Download `htmx.min.js` [from unpkg.com](https://unpkg.com/htmx.org@2.0.2/dist/htmx.min.js) and add it to the appropriate directory in your project
|
||||
and include it where necessary with a `<script>` tag:
|
||||
|
||||
```html
|
||||
@ -149,7 +149,7 @@ and include it where necessary with a `<script>` tag:
|
||||
For npm-style build systems, you can install htmx via [npm](https://www.npmjs.com/):
|
||||
|
||||
```sh
|
||||
npm install htmx.org@2.0.1
|
||||
npm install htmx.org@2.0.2
|
||||
```
|
||||
|
||||
After installing, you’ll need to use appropriate tooling to use `node_modules/htmx.org/dist/htmx.js` (or `.min.js`).
|
||||
@ -690,7 +690,7 @@ shows how to use [sweetalert2](https://sweetalert2.github.io/) library for confi
|
||||
|
||||
#### Confirming Requests Using Events
|
||||
|
||||
Another option to do confirmation with is via the [`htmx:confirm` event](@/events.md#htmx:confirm) event. This event
|
||||
Another option to do confirmation with is via the [`htmx:confirm` event](@/events.md#htmx:confirm). This event
|
||||
is fired on *every* trigger for a request (not just on elements that have a `hx-confirm` attribute) and can be used
|
||||
to implement asynchronous confirmation of the request.
|
||||
|
||||
@ -710,7 +710,7 @@ document.body.addEventListener('htmx:confirm', function(evt) {
|
||||
if (confirmed) {
|
||||
evt.detail.issueRequest();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
@ -933,7 +933,7 @@ the response.
|
||||
In the event of an error response from the server (e.g. a 404 or a 501), htmx will trigger the [`htmx:responseError`](@/events.md#htmx:responseError)
|
||||
event, which you can handle.
|
||||
|
||||
In the event of a connection error, the `htmx:sendError` event will be triggered.
|
||||
In the event of a connection error, the [`htmx:sendError`](@/events.md#htmx:sendError) event will be triggered.
|
||||
|
||||
### Configuring Response Handling {#response-handling}
|
||||
|
||||
@ -966,8 +966,8 @@ The fields available for response handling configuration on entries in this arra
|
||||
#### Configuring Response Handling Examples {#response-handling}
|
||||
|
||||
As an example of how to use this configuration, consider a situation when a server-side framework responds with a
|
||||
[`422 - Unprocessable Entity`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422) response when validation errors occur. By default, htmx will ignore the response,
|
||||
since it matches the Regular Expression `[45]..`.
|
||||
[`422 - Unprocessable Entity`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422) response when validation errors occur. By default, htmx will ignore the response,
|
||||
since it matches the Regular Expression `[45]..`.
|
||||
|
||||
Using the [meta config](#configuration-options) mechanism for configuring responseHandling, we could add the following
|
||||
config:
|
||||
@ -996,7 +996,7 @@ config:
|
||||
If you wanted to swap everything, regardless of HTTP response code, you could use this configuration:
|
||||
|
||||
```html
|
||||
<meta name="htmx-config" content='{code:".*", swap: true}, // all responses are swapped'>
|
||||
<meta name="htmx-config" content='{"responseHandling": [{"code":".*", "swap": true}]}' /> <!--all responses are swapped-->
|
||||
```
|
||||
|
||||
Finally, it is worth considering using the [Response Targets](https://github.com/bigskysoftware/htmx-extensions/blob/main/src/response-targets/README.md)
|
||||
@ -1034,7 +1034,7 @@ htmx supports some htmx-specific response headers:
|
||||
|
||||
* [`HX-Location`](@/headers/hx-location.md) - allows you to do a client-side redirect that does not do a full page reload
|
||||
* [`HX-Push-Url`](@/headers/hx-push-url.md) - pushes a new url into the history stack
|
||||
* `HX-Redirect` - can be used to do a client-side redirect to a new location
|
||||
* [`HX-Redirect`](@/headers/hx-redirect.md) - can be used to do a client-side redirect to a new location
|
||||
* `HX-Refresh` - if set to "true" the client-side will do a full refresh of the page
|
||||
* [`HX-Replace-Url`](@/headers/hx-replace-url.md) - replaces the current URL in the location bar
|
||||
* `HX-Reswap` - allows you to specify how the response will be swapped. See [hx-swap](@/attributes/hx-swap.md) for possible values
|
||||
@ -1049,6 +1049,8 @@ For more on the `HX-Trigger` headers, see [`HX-Trigger` Response Headers](@/head
|
||||
Submitting a form via htmx has the benefit of no longer needing the [Post/Redirect/Get Pattern](https://en.wikipedia.org/wiki/Post/Redirect/Get).
|
||||
After successfully processing a POST request on the server, you don't need to return a [HTTP 302 (Redirect)](https://en.wikipedia.org/wiki/HTTP_302). You can directly return the new HTML fragment.
|
||||
|
||||
Also the response headers above are not provided to htmx for processing with 3xx Redirect response codes like [HTTP 302 (Redirect)](https://en.wikipedia.org/wiki/HTTP_302). Instead, the browser will intercept the redirection internally and return the headers and response from the redirected URL. Where possible use alternative response codes like 200 to allow returning of these response headers.
|
||||
|
||||
### Request Order of Operations {#request-operations}
|
||||
|
||||
The order of operations in a htmx request are:
|
||||
@ -1126,7 +1128,9 @@ the [`hx-ext`](@/attributes/hx-ext.md) attribute:
|
||||
</div>
|
||||
```
|
||||
|
||||
If you are interested in adding your own extension to htmx, please [see the extension docs](https://github.com/bigskysoftware/htmx-extensions/tree/main?tab=readme-ov-file#defining-an-extension)
|
||||
For existing extensions, please [see the extensions site](https://extensions.htmx.org).
|
||||
|
||||
If you are interested in adding your own extension to htmx, please [see the extension docs](https://github.com/bigskysoftware/htmx-extensions/tree/main?tab=readme-ov-file#defining-an-extension).
|
||||
|
||||
## Events & Logging {#events}
|
||||
|
||||
@ -1660,16 +1664,17 @@ listed below:
|
||||
| `htmx.config.disableSelector` | defaults to `[hx-disable], [data-hx-disable]`, htmx will not process elements with this attribute on it or a parent |
|
||||
| `htmx.config.withCredentials` | defaults to `false`, allow cross-site Access-Control requests using credentials such as cookies, authorization headers or TLS client certificates |
|
||||
| `htmx.config.timeout` | defaults to 0, the number of milliseconds a request can take before automatically being terminated |
|
||||
| `htmx.config.scrollBehavior` | defaults to 'smooth', the behavior for a boosted link on page transitions. The allowed values are `auto` and `smooth`. Smooth will smoothscroll to the top of the page while auto will behave like a vanilla link. |
|
||||
| `htmx.config.scrollBehavior` | defaults to 'instant', the scroll behavior when using the [show](@/attributes/hx-swap.md#scrolling-scroll-show) modifier with `hx-swap`. The allowed values are `instant` (scrolling should happen instantly in a single jump), `smooth` (scrolling should animate smoothly) and `auto` (scroll behavior is determined by the computed value of [scroll-behavior](https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior)). |
|
||||
| `htmx.config.defaultFocusScroll` | if the focused element should be scrolled into view, defaults to false and can be overridden using the [focus-scroll](@/attributes/hx-swap.md#focus-scroll) swap modifier. |
|
||||
| `htmx.config.getCacheBusterParam` | defaults to false, if set to true htmx will append the target element to the `GET` request in the format `org.htmx.cache-buster=targetElementId` |
|
||||
| `htmx.config.globalViewTransitions` | if set to `true`, htmx will use the [View Transition](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) API when swapping in new content. |
|
||||
| `htmx.config.methodsThatUseUrlParams` | defaults to `["get"]`, htmx will format requests with these methods by encoding their parameters in the URL, not the request body |
|
||||
| `htmx.config.methodsThatUseUrlParams` | defaults to `["get", "delete"]`, htmx will format requests with these methods by encoding their parameters in the URL, not the request body |
|
||||
| `htmx.config.selfRequestsOnly` | defaults to `true`, whether to only allow AJAX requests to the same domain as the current document |
|
||||
| `htmx.config.ignoreTitle` | defaults to `false`, if set to `true` htmx will not update the title of the document when a `title` tag is found in new content |
|
||||
| `htmx.config.disableInheritance` | disables attribute inheritance in htmx, which can then be overridden by the [`hx-inherit`](@/attributes/hx-inherit.md) attribute |
|
||||
| `htmx.config.scrollIntoViewOnBoost` | defaults to `true`, whether or not the target of a boosted element is scrolled into the viewport. If `hx-target` is omitted on a boosted element, the target defaults to `body`, causing the page to scroll to the top. |
|
||||
| `htmx.config.triggerSpecsCache` | defaults to `null`, the cache to store evaluated trigger specifications into, improving parsing performance at the cost of more memory usage. You may define a simple object to use a never-clearing cache, or implement your own system using a [proxy object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Proxy) |
|
||||
| `htmx.config.responseHandling` | the default [Response Handling](@/docs.md#response-handling) behavior for response status codes can be configured here to either swap or error |
|
||||
| `htmx.config.allowNestedOobSwaps` | defaults to `true`, whether to process OOB swaps on elements that are nested within the main response element. See [Nested OOB Swaps](@/attributes/hx-swap-oob.md#nested-oob-swaps). |
|
||||
|
||||
</div>
|
||||
|
@ -16,6 +16,7 @@ page_template = "essay.html"
|
||||
* [Taking HTML Seriously](https://intercoolerjs.org/2020/01/14/taking-html-seriously)
|
||||
* [REST Copypasta](@/essays/rest-copypasta.md)
|
||||
* [The #ViewSource Affordance](@/essays/right-click-view-source.md)
|
||||
* [Hypermedia Controls: Feral to Formal (ACM HT'24)](https://dl.acm.org/doi/pdf/10.1145/3648188.3675127)
|
||||
|
||||
### Why Hypermedia? Why Multi-Page Applications?
|
||||
* [Hypermedia On Whatever you'd Like (HOWL)](@/essays/hypermedia-on-whatever-youd-like.md)
|
||||
@ -39,6 +40,7 @@ page_template = "essay.html"
|
||||
* [Model/View/Controller](@/essays/mvc.md)
|
||||
* [Is Rendering JSON More Efficient Than Rendering HTML?](https://github.com/1cg/html-json-speed-comparison)
|
||||
* [Is JSON Smaller Than HTML?](https://github.com/1cg/html-json-size-comparison)
|
||||
* [You Can't Build Interactive Web Apps Except as Single Page Applications... And Other Myths](@/essays/you-cant.md)
|
||||
|
||||
### Complexity Very Very Bad
|
||||
* [The Grug Brained Developer](https://grugbrain.dev)
|
||||
|
@ -58,7 +58,7 @@ To clarify the discussion around exactly what the uniform interface is, let's co
|
||||
everyone reading this will understand:
|
||||
|
||||
```html
|
||||
<html
|
||||
<html>
|
||||
<body>
|
||||
<section>
|
||||
<p>
|
||||
|
317
www/content/essays/you-cant.md
Normal file
@ -0,0 +1,317 @@
|
||||
+++
|
||||
title = "You Can't Build Interactive Web Apps Except as Single Page Applications... And Other Myths"
|
||||
date = 2024-09-20
|
||||
updated = 2024-09-20
|
||||
[taxonomies]
|
||||
author = ["Tony Alaribe"]
|
||||
tag = ["posts"]
|
||||
+++
|
||||
|
||||
<style>
|
||||
img, video {
|
||||
max-width: 100%;
|
||||
margin: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
### **An Ode to Browser Advancements.**
|
||||
|
||||
I often encounter discussions on Reddit and YCombinator where newer developers seek tech stack advice. Inevitably,
|
||||
someone claims it's impossible to build a high-quality application without using a single-page application (SPA)
|
||||
framework like React or AngularJS. This strikes me as odd because, even before the SPA revolution, many popular
|
||||
multi-page web applications offered excellent user experiences.
|
||||
|
||||
Two years ago, I set out to build an [observability platform](https://apitoolkit.io) and chose to experiment with a
|
||||
multi-page application (MPA) approach using HTMX. I wondered: Would a server-rendered MPA be inadequate for a data-heavy
|
||||
application, considering that most observability platforms are built on ReactJS?
|
||||
|
||||
What I discovered is that you can create outstanding server-rendered applications if you pay attention to certain
|
||||
details.
|
||||
|
||||
**Here are some common MPA myths and what I've learned about them.**
|
||||
|
||||
## Myth 1: MPA Page Transitions are slow because JavaScript and CSS are downloaded on every page navigation
|
||||
|
||||
The perception that MPA page transitions are slow is widespread—and not entirely unfounded—since this is the default
|
||||
behavior of browsers. However, browsers have made significant improvements over the past decade to mitigate this issue.
|
||||
|
||||
To illustrate, in the video below, a full page reload with the cache disabled takes 2.90 seconds until the
|
||||
DOMContentLoaded event fires. I recorded this at a café with poor Wi-Fi, but let's use this as a reference point. Keep
|
||||
that number in mind.
|
||||
|
||||
<video controls>
|
||||
<source src="/img/you-cant/log-exp-cache.mp4">
|
||||
</video>
|
||||
|
||||
It is common to reduce load times in MPAs using libraries such as **PJAX, Turbolinks, and even HTMX Boost**. These
|
||||
libraries hijack the page reload using Javascript and swap out only the HTML body element between transitions. That way,
|
||||
most of the page's head section assets don't need to be reloaded or re-downloaded.
|
||||
|
||||
But there’s a lesser known way of reducing how much assets are re-downloaded or evaluated during page transitions.
|
||||
|
||||
|
||||
|
||||
### Client-side Caching via Service workers
|
||||
|
||||
Frontend developers who have built Progressive Web Applications (PWA) with SPA frameworks might know about service
|
||||
workers.
|
||||
|
||||
For those of us who are not frontend or PWA developers, service workers are a built-in feature of browsers. They let you
|
||||
write Javascript code that sits between your users and the network, intercepting requests and deciding how the browser
|
||||
handles them.
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
Due to its association with the PWA trend, service workers are only ordinary among SPA developers, and developers need
|
||||
to realize that this technology can also be used for regular Multi-Page Applications.
|
||||
|
||||
<video controls>
|
||||
<source src="/img/you-cant/log_exp_with_cache.mp4">
|
||||
</video>
|
||||
|
||||
In the video demonstration, we enable a service worker to cache and refresh the current page. You'll notice that there's
|
||||
no flicker when clicking the link to reload the page, resulting in a smoother user experience.
|
||||
|
||||
Moreover, instead of transmitting over 2 MB of static assets as before, the browser now only fetches 84 KB of HTML
|
||||
content—the actual page data. This optimization reduces the `DOMContentLoaded` event time from 2.9 seconds to under 500
|
||||
milliseconds. Impressively, this improvement is achieved **without** using HTMX Boost, PJAX, or Turbolinks.
|
||||
|
||||
### How to Implement Service workers in Your Multi-Page Application
|
||||
|
||||
You might be wondering how to replicate these performance gains in your own MPA. Here's a simple guide:
|
||||
|
||||
1. **Create a `sw.js` File**: This is your service worker script that will manage caching and network requests.
|
||||
2. **List Files to Cache**: Within the service worker, specify all the assets (HTML, CSS, JavaScript, images) that
|
||||
should be cached.
|
||||
3. **Define Caching Strategies**: Indicate how each type of asset should be cached—for example, whether they should be
|
||||
cached permanently or refreshed periodically.
|
||||
|
||||
By implementing a service worker, you effectively tell the browser how to handle network requests and caching, leading
|
||||
to faster load times and a more seamless user experience.
|
||||
|
||||
### Use Workbox to generate service workers
|
||||
|
||||
While it's possible to write service workers by hand—and there are excellent resources
|
||||
like [this MDN article](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers) to
|
||||
help you—I prefer using Google's [Workbox](https://developer.chrome.com/docs/workbox) library to automate the process.
|
||||
|
||||
### Steps to Use Workbox:
|
||||
|
||||
1. **Install Workbox**: Install Workbox via npm or your preferred package manager:
|
||||
|
||||
```bash
|
||||
npm install workbox-cli --global
|
||||
```
|
||||
|
||||
2. Generate a Workbox Configuration file: Run the following command to create a configuration file:
|
||||
|
||||
```bash
|
||||
workbox wizard
|
||||
```
|
||||
|
||||
3. **Configure Asset Handling**: In the generated `workbox-config.js` file, define how different assets should be
|
||||
cached. Use the `urlPattern` property—a regular expression—to match specific HTTP requests. For each matching
|
||||
request, specify a caching strategy, such as `CacheFirst` or `NetworkFirst`.
|
||||
|
||||

|
||||
|
||||
|
||||
1. **Build the Service Worker**: Run the Workbox build command to generate the `sw.js` file based on your configuration:
|
||||
|
||||
```bash
|
||||
workbox generateSW workbox-config.js
|
||||
```
|
||||
|
||||
2. **Register the Service Worker in Your Application**: Add the following script to your HTML pages to register the
|
||||
service worker:
|
||||
|
||||
```html
|
||||
<script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', function() {
|
||||
navigator.serviceWorker.register('/sw.js').then(function(registration) {
|
||||
console.log('ServiceWorker registration successful with scope: ', registration.scope);
|
||||
}, function(err) {
|
||||
console.log('ServiceWorker registration failed: ', err);
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
By following these steps, you instruct the browser to serve cached assets whenever possible, drastically reducing load
|
||||
times and improving the overall performance of your multi-page application.
|
||||
|
||||

|
||||
|
||||
Image showing the registered service worker from the chrome browser console.
|
||||
|
||||
### [`Speculation Rules API`](https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API): Prerender pages for instant page navigation.
|
||||
|
||||
If you have used **htmx-preload** or **instantpage.js,** you're familiar with prerendering and the problem
|
||||
the [“Speculation Rules API”](https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API) aims to solve. The
|
||||
Speculation Rules API is designed to improve performance for future navigations. It has an expressive syntax for
|
||||
specifying which links should be prefetched or prerendered on the current page.
|
||||
|
||||

|
||||
|
||||
Speculation rules configuration example
|
||||
|
||||
The script above is an example of how speculation rules are configured. It is a Javascript object, and without going
|
||||
into detail, you can see that it uses keywords such as “where,” “and,” “not,” etc. to describe what elements should
|
||||
either be prefetched or prerendered.
|
||||
|
||||
<video controls>
|
||||
<source src="/img/you-cant/prerender-vid.mp4">
|
||||
</video>
|
||||
|
||||
[Example impact of prerendering (Chrome Team)](https://developer.chrome.com/docs/web-platform/prerender-pages)
|
||||
|
||||
|
||||
## Myth 2: MPAs can't operate offline and save updates to retry when there's network
|
||||
|
||||
From the last sections, you know that service workers can cache everything and make our apps operate entirely offline.
|
||||
But what if we want to save offline POST requests and retry them when there is internet?
|
||||
|
||||

|
||||
|
||||
The configuration javascript file above shows how to configure Workbox to support two common offline scenarios. Here,
|
||||
you see background Sync, where we ask the service worker to cache any failed requests due to the internet and retry it
|
||||
for up to 24 hours.
|
||||
|
||||
Below, we define an offline catch Handler, triggered when a request is made offline. We can return a template partial
|
||||
with HTML or a JSON response or dynamically build a response based on the request input. The sky is the limit here.
|
||||
|
||||
## Myth 3: MPAs always flash white during page Transitions
|
||||
|
||||
In the service worker videos, we already saw that this will not happen if we configure caching and prerendering.
|
||||
However, this myth was not generally true until 2019. Since 2019, most browsers withhold painting the next screen until
|
||||
all the required assets for the next page are available or a timeout is reached, resulting in no flash of white while
|
||||
transitioning between both pages. This only works when navigating within the same origin/domain.
|
||||
|
||||
<video controls>
|
||||
<source src="/img/you-cant/paint-holding.mp4">
|
||||
</video>
|
||||
|
||||
[Paint holding documentation on chrome.com](https://developer.chrome.com/blog/paint-holding).
|
||||
|
||||
|
||||
|
||||
## Myth 4: Fancy Cross-document page transitions are not possible with MPAs.
|
||||
|
||||
The advent of single-page application frameworks made custom transitions between pages more popular. The allure of
|
||||
different navigation styles comes from completely taking control of page navigation from the browsers. In practice, such
|
||||
transitions have mostly been popular within the demos at web dev conference talks.
|
||||
|
||||
<video controls>
|
||||
<source src="/img/you-cant/page-transitions.mp4">
|
||||
</video>
|
||||
|
||||
|
||||
[Cross Document Transitions documentation on chrome.com](https://developer.chrome.com/docs/web-platform/view-transitions).
|
||||
|
||||
This remains a common argument for single-page applications, especially on Reddit and Hacker News comment sections.
|
||||
However, browsers have been working towards solving this problem natively for the last couple of years. Chrome 126
|
||||
rolled out cross-document view transitions. This means we can build our MPAs to include those fancy animations and
|
||||
transitions between pages using CSS only or CSS and Javascript.
|
||||
|
||||
My favorite bit is that we might be able to create lovely cross-document transitions with CSS only:
|
||||
|
||||

|
||||
|
||||
You can quickly learn more on
|
||||
the [Google Chrome announcement page](https://developer.chrome.com/docs/web-platform/view-transitions)
|
||||
|
||||
This link hosts a [multi-page application demo](https://view-transitions.netlify.app/stack-navigator/mpa-prerender/),
|
||||
where you can play around with a rudimentary server-rendered application using the cross-document view transitions API
|
||||
to simulate a stack-based animation.
|
||||
|
||||
## Myth 5: With htmx or MPAs, every user action must happen on the server.
|
||||
|
||||
I've heard this a lot when HTMX is discussed. So, there might be some confusion caused by the HTMX positioning. But you
|
||||
don't have to do everything server-side. Many HTMX and regular MPA users continue to use Javascript, Alpine, or
|
||||
Hyperscript where appropriate.
|
||||
|
||||
In situations where robust interactivity is helpful, you can lean into the component islands architecture using
|
||||
WebComponents or any javascript framework (react, angular, etc) of your choice. That way, instead of your entire
|
||||
application being an SPA, you can leverage those frameworks specifically for the bits of your application that need that
|
||||
interactivity.
|
||||
|
||||
The example above shows a very interactive search component in the [APItoolkit](https://apitoolkit.io). It's a web
|
||||
component implemented with lit-element, a zero-compile-step library for writing web components. So, the entire web
|
||||
component event fits in a Javascript file.
|
||||
|
||||
<video controls>
|
||||
<source src="/img/you-cant/webcomponents-filter-element2.mp4">
|
||||
</video>
|
||||
|
||||
|
||||
|
||||
## Myth 6: Operating directly on the DOM is slow. Therefore, it would be best to use React/Virtual DOM.
|
||||
|
||||
The speed of direct DOM operations was a major motivation for building ReactJS on and popularizing the virtual DOM
|
||||
technology. While virtual DOM operations can be faster than direct DOM operations, this is only true for applications
|
||||
that perform many complex operations and refresh in milliseconds, where that performance might be noticeable. But most
|
||||
of us are not building such software.
|
||||
|
||||
The Svelte team wrote an excellent article
|
||||
titled [“Virtual DOM is pure Overhead.”](https://svelte.dev/blog/virtual-dom-is-pure-overhead) I recommend reading it,
|
||||
as it better explains why Virtual DOM doesn't matter for most applications.
|
||||
|
||||
## Myth 7: You still need to write JavaScript for every minor interactivity.
|
||||
|
||||
With the advancements in browser tech, you can avoid writing a lot of client-side Javascript in the first place. For
|
||||
example, a standard action on the web is to show and hide things based on a button click or toggle. These days, you can
|
||||
show and hide elements with only CSS and HTML, for example, by using an HTML input checkbox to track state. We can style
|
||||
an HTML label as a button and give it a `for="checkboxID`" attribute, so clicking the label toggles the checkbox.
|
||||
|
||||
```jsx
|
||||
<input id="published" class="hidden peer" type="checkbox"/>
|
||||
<label for="published" class="btn">toggle content</label>
|
||||
|
||||
<div class="hidden peer-checked:block">
|
||||
Content to be toggled when label/btn is clicked
|
||||
</div>
|
||||
```
|
||||
We can combine such a checkbox with HTMX intersect to fetch content from an endpoint when the button is clicked.
|
||||
|
||||
```html
|
||||
<input id="published" class="peer" type="checkbox" name="status"/>
|
||||
<div
|
||||
class="hidden peer-checked:block"
|
||||
hx-trigger="intersect once"
|
||||
hx-get="/log-item"
|
||||
>Shell/Loading text etc
|
||||
</div>
|
||||
```
|
||||
|
||||
All the classes above are vanilla [Tailwind CSS](https://tailwindcss.com/) classes, but you can also write the CSS by
|
||||
hand. Below is a video of that code being used to hide or reveal log items in the log explorer.
|
||||
|
||||
<video controls>
|
||||
<source src="/img/you-cant/expanding-log-item.mp4">
|
||||
</video>
|
||||
|
||||
## Final Myth: Without a *“Proper”* frontend framework, your Client-side Javascript will be [Spaghetti and Unmaintainable](https://www.reddit.com/r/webdev/comments/bkk0gl/avoiding_the_vanillajs_spaghetticode/).
|
||||
|
||||
This may or may not be true.
|
||||
|
||||
### Who cares? I love Spaghetti.
|
||||
|
||||
I like to argue that some of the most productive days of the web were the PHP and JQuery spaghetti days. A lot of
|
||||
software was built at that time, including many of the popular internet brands we know today. Most of them were built as
|
||||
so-called spaghetti codes, which helped them ship their products early and survive long enough to refactor and not be
|
||||
spaghetti.
|
||||
|
||||
## Conclusion
|
||||
|
||||
The entire point of this talk is to show you that a lot is possible with browsers in 2024. While we were not looking,
|
||||
browsers have closed the gap and borrowed the best ideas from the single-page application revolution. For example,
|
||||
WebComponents exist thanks to the lessons we learned from single-page applications.
|
||||
|
||||
So now, we can build very interactive, even offline web applications using mostly browser tools—HTML, CSS, maybe some
|
||||
Javascript—and still not sacrifice much in terms of user experience.
|
||||
|
||||
<h3>The browser has come a long way. Give it a chance!</h3>
|
@ -179,43 +179,70 @@ than a single value.
|
||||
|
||||
### Event - `htmx:confirm` {#htmx:confirm}
|
||||
|
||||
This event is triggered immediately after a trigger occurs on an element. It allows you to cancel (or delay) issuing
|
||||
the AJAX request. If you call `preventDefault()` on the event, it will not issue the given request. The `detail`
|
||||
object contains a function, `evt.detail.issueRequest()`, that can be used to issue the actual AJAX request at a
|
||||
later point. Combining these two features allows you to create an asynchronous confirmation dialog.
|
||||
This event is fired on every trigger for a request (not just on elements that have a hx-confirm attribute).
|
||||
It allows you to cancel (or delay) issuing the AJAX request.
|
||||
If you call `preventDefault()` on the event, it will not issue the given request.
|
||||
The `detail` object contains a function, `evt.detail.issueRequest(skipConfirmation=false)`, that can be used to issue the actual AJAX request at a later point.
|
||||
Combining these two features allows you to create an asynchronous confirmation dialog.
|
||||
|
||||
Here is an example using [sweet alert](https://sweetalert.js.org/guides/) on any element with a `confirm-with-sweet-alert='true'` attribute on it:
|
||||
Here is a basic example that shows the basic usage of the `htmx:confirm` event without altering the default behavior:
|
||||
|
||||
```javascript
|
||||
document.body.addEventListener('htmx:confirm', function(evt) {
|
||||
if (evt.target.matches("[confirm-with-sweet-alert='true']")) {
|
||||
evt.preventDefault();
|
||||
swal({
|
||||
title: "Are you sure?",
|
||||
text: "Are you sure you are sure?",
|
||||
icon: "warning",
|
||||
buttons: true,
|
||||
dangerMode: true,
|
||||
}).then((confirmed) => {
|
||||
if (confirmed) {
|
||||
evt.detail.issueRequest();
|
||||
}
|
||||
});
|
||||
}
|
||||
// 0. To modify the behavior only for elements with the hx-confirm attribute,
|
||||
// check if evt.detail.target.hasAttribute('hx-confirm')
|
||||
|
||||
// 1. Prevent the default behavior (this will prevent the request from being issued)
|
||||
evt.preventDefault();
|
||||
|
||||
// 2. Do your own logic here
|
||||
console.log(evt.detail)
|
||||
|
||||
// 3. Manually issue the request when you are ready
|
||||
evt.detail.issueRequest(); // or evt.detail.issueRequest(true) to skip the built-in window.confirm()
|
||||
});
|
||||
```
|
||||
|
||||
And here is an example using [sweet alert](https://sweetalert.js.org/guides/) on any element with a `confirm-with-sweet-alert="{question}"` attribute on it:
|
||||
|
||||
```javascript
|
||||
document.body.addEventListener('htmx:confirm', function(evt) {
|
||||
// 1. The requirement to show the sweet alert is that the element has a confirm-with-sweet-alert
|
||||
// attribute on it, if it doesn't we can return early and let the default behavior happen
|
||||
if (!evt.detail.target.hasAttribute('confirm-with-sweet-alert')) return
|
||||
|
||||
// 2. Get the question from the attribute
|
||||
const question = evt.detail.target.getAttribute('confirm-with-sweet-alert');
|
||||
|
||||
// 3. Prevent the default behavior (this will prevent the request from being issued)
|
||||
evt.preventDefault();
|
||||
|
||||
// 4. Show the sweet alert
|
||||
swal({
|
||||
title: "Are you sure?",
|
||||
text: question || "Are you sure you want to continue?",
|
||||
icon: "warning",
|
||||
buttons: true,
|
||||
dangerMode: true,
|
||||
}).then((confirmed) => {
|
||||
if (confirmed) {
|
||||
// 5. If the user confirms, we can manually issue the request
|
||||
evt.detail.issueRequest(true); // true to skip the built-in window.confirm()
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
##### Details
|
||||
|
||||
{target: target, elt: elt, path: path, verb: verb, triggeringEvent: event, etc: etc, issueRequest: issueRequest}
|
||||
|
||||
* `detail.elt` - the element in question
|
||||
* `detail.etc` - additional request information (mostly unused)
|
||||
* `detail.issueRequest` - a no argument function that can be invoked to issue the request (should be paired with `evt.preventDefault()`!)
|
||||
* `detail.issueRequest(skipConfirmation=false)` - a function that can be invoked to issue the request (should be paired with `evt.preventDefault()`!), if skipConfirmation is `true` the original `window.confirm()` is not executed
|
||||
* `detail.path` - the path of the request
|
||||
* `detail.target` - the target of the request
|
||||
* `detail.target` - the element that triggered the request
|
||||
* `detail.triggeringEvent` - the original event that triggered this request
|
||||
* `detail.verb` - the verb of the request (e.g. `GET`)
|
||||
* `detail.question` - the question passed to `hx-confirm` attribute (only available if `hx-confirm` attribute is present)
|
||||
|
||||
### Event - `htmx:historyCacheError` {#htmx:historyCacheError}
|
||||
|
||||
|
@ -36,12 +36,22 @@ which is then picked up by `hx-trigger`.
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
<script>
|
||||
document.addEventListener("htmx:confirm", function(e) {
|
||||
// The event is triggered on every trigger for a request, so we need to check if the element
|
||||
// that triggered the request has a hx-confirm attribute, if not we can return early and let
|
||||
// the default behavior happen
|
||||
if (!evt.detail.target.hasAttribute('hx-confirm')) return
|
||||
|
||||
// This will prevent the request from being issued to later manually issue it
|
||||
e.preventDefault()
|
||||
|
||||
Swal.fire({
|
||||
title: "Proceed?",
|
||||
text: `I ask you... ${e.detail.question}`
|
||||
}).then(function(result) {
|
||||
if(result.isConfirmed) e.detail.issueRequest(true) // use true to skip window.confirm
|
||||
if (result.isConfirmed) {
|
||||
// If the user confirms, we manually issue the request
|
||||
e.detail.issueRequest(true); // true to skip the built-in window.confirm()
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
@ -62,3 +72,5 @@ when the question depends on the element e.g. a django list:
|
||||
<button hx-post="/delete/{{client.pk}}" hx-confirm="Delete {{client.name}}??">Delete</button>
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
Learn more about the [`htmx:confirm`](@/events.md#htmx:confirm) event [here](@/events.md#htmx:confirm).
|
||||
|
@ -8,7 +8,7 @@ state that looks like this:
|
||||
|
||||
```html
|
||||
<div hx-get="/graph" hx-trigger="load">
|
||||
<img alt="Result loading..." class="htmx-indicator" width="150" src="/img/bars.svg"/>
|
||||
<img alt="Result loading..." class="htmx-indicator" width="150" src="/img/bars.svg"/>
|
||||
</div>
|
||||
```
|
||||
|
||||
@ -54,7 +54,7 @@ img {
|
||||
// templates
|
||||
function lazyTemplate(page) {
|
||||
return `<div hx-get="/graph" hx-trigger="load">
|
||||
<img alt="Result loading..." class="htmx-indicator" width="150" src="/img/bars.svg"/>
|
||||
<img alt="Result loading..." class="htmx-indicator" width="150" src="/img/bars.svg"/>
|
||||
</div>`;
|
||||
}
|
||||
</script>
|
||||
|
3
www/content/extensions/_index.md
Normal file
@ -0,0 +1,3 @@
|
||||
+++
|
||||
redirect_to = "https://extensions.htmx.org"
|
||||
+++
|
@ -28,3 +28,7 @@ Path is required and is url to load the response from. The rest of the data mirr
|
||||
* `values` - values to submit with the request
|
||||
* `headers` - headers to submit with the request
|
||||
* `select` - allows you to select the content you want swapped from a response
|
||||
|
||||
## Notes
|
||||
|
||||
Response headers are not processed on 3xx response codes. see [Response Headers](@/docs.md#response-headers)
|
||||
|
@ -13,3 +13,7 @@ The possible values for this header are:
|
||||
1. A URL to be pushed into the location bar.
|
||||
This may be relative or absolute, as per [`history.pushState()`](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState).
|
||||
2. `false`, which prevents the browser’s history from being updated.
|
||||
|
||||
## Notes
|
||||
|
||||
Response headers are not processed on 3xx response codes. see [Response Headers](@/docs.md#response-headers)
|
||||
|
17
www/content/headers/hx-redirect.md
Normal file
@ -0,0 +1,17 @@
|
||||
+++
|
||||
title = "HX-Redirect Response Header"
|
||||
+++
|
||||
|
||||
This response header can be used to trigger a client side redirection to a new url that will do a full reload of the whole page. It uses the browser to redirect to the new location which can be useful when redirecting to non htmx endpoints that may contain different HTML `head` content or scripts. See [`HX-Location`](@/headers/hx-location.md) if you want more control over the redirect or want to use ajax requests instead of full browser reloads.
|
||||
|
||||
A sample response would be:
|
||||
|
||||
```html
|
||||
HX-Redirect: /test
|
||||
```
|
||||
|
||||
Which would push the client to test as if the user had entered this url manually or clicked on a non-boosted link `<a href="/test">`
|
||||
|
||||
## Notes
|
||||
|
||||
Response headers are not processed on 3xx response codes. see [Response Headers](@/docs.md#response-headers)
|
@ -13,3 +13,7 @@ The possible values for this header are:
|
||||
1. A URL to replace the current URL in the location bar.
|
||||
This may be relative or absolute, as per [`history.replaceState()`](https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState), but must have the same origin as the current URL.
|
||||
2. `false`, which prevents the browser’s current URL from being updated.
|
||||
|
||||
## Notes
|
||||
|
||||
Response headers are not processed on 3xx response codes. see [Response Headers](@/docs.md#response-headers)
|
||||
|
@ -78,3 +78,7 @@ You may also trigger multiple events with no additional details by sending event
|
||||
`HX-Trigger: event1, event2`
|
||||
|
||||
Using events gives you a lot of flexibility to add functionality to normal htmx responses.
|
||||
|
||||
## Notes
|
||||
|
||||
Response headers are not processed on 3xx response codes. see [Response Headers](@/docs.md#response-headers)
|
@ -112,7 +112,7 @@ All other attributes available in htmx.
|
||||
|------------------------------------------------------|-------------|
|
||||
| [`HX-Location`](@/headers/hx-location.md) | allows you to do a client-side redirect that does not do a full page reload
|
||||
| [`HX-Push-Url`](@/headers/hx-push-url.md) | pushes a new url into the history stack
|
||||
| `HX-Redirect` | can be used to do a client-side redirect to a new location
|
||||
| [`HX-Redirect`](@/headers/hx-redirect.md) | can be used to do a client-side redirect to a new location
|
||||
| `HX-Refresh` | if set to "true" the client-side will do a full refresh of the page
|
||||
| [`HX-Replace-Url`](@/headers/hx-replace-url.md) | replaces the current URL in the location bar
|
||||
| `HX-Reswap` | allows you to specify how the response will be swapped. See [hx-swap](@/attributes/hx-swap.md) for possible values
|
||||
@ -142,6 +142,7 @@ All other attributes available in htmx.
|
||||
| [`htmx:beforeRequest`](@/events.md#htmx:beforeRequest) | triggered before an AJAX request is made
|
||||
| [`htmx:beforeSwap`](@/events.md#htmx:beforeSwap) | triggered before a swap is done, allows you to configure the swap
|
||||
| [`htmx:beforeSend`](@/events.md#htmx:beforeSend) | triggered just before an ajax request is sent
|
||||
| [`htmx:beforeTransition`](@/events.md#htmx:beforeTransition) | triggered before the [View Transition](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) wrapped swap occurs
|
||||
| [`htmx:configRequest`](@/events.md#htmx:configRequest) | triggered before the request, allows you to customize parameters, headers
|
||||
| [`htmx:confirm`](@/events.md#htmx:confirm) | triggered after a trigger occurs on an element, allows you to cancel (or delay) issuing the AJAX request
|
||||
| [`htmx:historyCacheError`](@/events.md#htmx:historyCacheError) | triggered on an error during cache writing
|
||||
@ -240,15 +241,16 @@ listed below:
|
||||
| `htmx.config.disableSelector` | defaults to `[hx-disable], [data-hx-disable]`, htmx will not process elements with this attribute on it or a parent |
|
||||
| `htmx.config.withCredentials` | defaults to `false`, allow cross-site Access-Control requests using credentials such as cookies, authorization headers or TLS client certificates |
|
||||
| `htmx.config.timeout` | defaults to 0, the number of milliseconds a request can take before automatically being terminated |
|
||||
| `htmx.config.scrollBehavior` | defaults to 'instant', the behavior for a boosted link on page transitions. The allowed values are `auto`, `instant` and `smooth`. Instant will scroll instantly in a single jump, smooth will scroll smoothly, while auto will behave like a vanilla link. |
|
||||
| `htmx.config.scrollBehavior` | defaults to 'instant', the scroll behavior when using the [show](@/attributes/hx-swap.md#scrolling-scroll-show) modifier with `hx-swap`. The allowed values are `instant` (scrolling should happen instantly in a single jump), `smooth` (scrolling should animate smoothly) and `auto` (scroll behavior is determined by the computed value of [scroll-behavior](https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior)). |
|
||||
| `htmx.config.defaultFocusScroll` | if the focused element should be scrolled into view, defaults to false and can be overridden using the [focus-scroll](@/attributes/hx-swap.md#focus-scroll) swap modifier. |
|
||||
| `htmx.config.getCacheBusterParam` | defaults to false, if set to true htmx will append the target element to the `GET` request in the format `org.htmx.cache-buster=targetElementId` |
|
||||
| `htmx.config.globalViewTransitions` | if set to `true`, htmx will use the [View Transition](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) API when swapping in new content. |
|
||||
| `htmx.config.methodsThatUseUrlParams` | defaults to `["get"]`, htmx will format requests with these methods by encoding their parameters in the URL, not the request body |
|
||||
| `htmx.config.methodsThatUseUrlParams` | defaults to `["get", "delete"]`, htmx will format requests with these methods by encoding their parameters in the URL, not the request body |
|
||||
| `htmx.config.selfRequestsOnly` | defaults to `true`, whether to only allow AJAX requests to the same domain as the current document |
|
||||
| `htmx.config.ignoreTitle` | defaults to `false`, if set to `true` htmx will not update the title of the document when a `title` tag is found in new content |
|
||||
| `htmx.config.scrollIntoViewOnBoost` | defaults to `true`, whether or not the target of a boosted element is scrolled into the viewport. If `hx-target` is omitted on a boosted element, the target defaults to `body`, causing the page to scroll to the top. |
|
||||
| `htmx.config.triggerSpecsCache` | defaults to `null`, the cache to store evaluated trigger specifications into, improving parsing performance at the cost of more memory usage. You may define a simple object to use a never-clearing cache, or implement your own system using a [proxy object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Proxy) |
|
||||
| `htmx.config.responseHandling` | the default [Response Handling](@/docs.md#response-handling) behavior for response status codes can be configured here to either swap or error |
|
||||
| `htmx.config.allowNestedOobSwaps` | defaults to `true`, whether to process OOB swaps on elements that are nested within the main response element. See [Nested OOB Swaps](@/attributes/hx-swap-oob.md#nested-oob-swaps). |
|
||||
|
||||
</div>
|
||||
|
@ -126,6 +126,9 @@ These examples may make it a bit easier to get started using htmx with your plat
|
||||
### http4s
|
||||
- <https://github.com/martinprobson/http4s-htmx-demo>
|
||||
|
||||
### zio-http
|
||||
- <https://github.com/rockthejvm/scalatags-htmx-demo>
|
||||
|
||||
## Kotlin
|
||||
|
||||
### Ktor
|
||||
@ -175,6 +178,20 @@ These examples may make it a bit easier to get started using htmx with your plat
|
||||
### Giraffe
|
||||
|
||||
- <https://hamy.xyz/labs/2023-12-fsharp-htmx>
|
||||
- <https://github.com/bit-badger/Giraffe.Htmx>
|
||||
|
||||
### Feliz.ViewEngine.Htmx
|
||||
|
||||
- <https://github.com/Zaid-Ajaj/Feliz.ViewEngine.Htmx>
|
||||
- <https://github.com/jkone27/todo-mvc-feliz-htmx>
|
||||
|
||||
### Falco.Htmx
|
||||
|
||||
- <https://github.com/dpraimeyuu/Falco.Htmx>
|
||||
|
||||
### Suave (with Feliz)
|
||||
|
||||
- <https://jkone27-3876.medium.com/htmx-and-f-c1ffdc18fbb5>
|
||||
|
||||
## Go
|
||||
|
||||
|
BIN
www/static/img/clean-v-dirty.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
www/static/img/you-cant/cross-doc-transitions-css.png
Normal file
After Width: | Height: | Size: 613 KiB |
BIN
www/static/img/you-cant/expanding-log-item.mp4
Normal file
BIN
www/static/img/you-cant/log-exp-cache.mp4
Normal file
BIN
www/static/img/you-cant/log_exp_with_cache.mp4
Normal file
BIN
www/static/img/you-cant/page-transitions.mp4
Normal file
BIN
www/static/img/you-cant/paint-holding.mp4
Normal file
BIN
www/static/img/you-cant/prerender-vid.mp4
Normal file
BIN
www/static/img/you-cant/service-worker-chart.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
www/static/img/you-cant/service-worker.png
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
www/static/img/you-cant/speculation-rules.png
Normal file
After Width: | Height: | Size: 508 KiB |
BIN
www/static/img/you-cant/webcomponents-filter-element2.mp4
Normal file
BIN
www/static/img/you-cant/workbox-cfg.png
Normal file
After Width: | Height: | Size: 569 KiB |
BIN
www/static/img/you-cant/workbox-offline-cfg.png
Normal file
After Width: | Height: | Size: 890 KiB |