mirror of
https://github.com/bigskysoftware/htmx.git
synced 2026-04-25 13:22:11 +00:00
hx-on attribute
This commit is contained in:
@@ -1066,7 +1066,7 @@ return (function () {
|
||||
var WHITESPACE = /\s/;
|
||||
var WHITESPACE_OR_COMMA = /[\s,]/;
|
||||
var SYMBOL_START = /[_$a-zA-Z]/;
|
||||
var SYMBOL_CONT = /[_$a-zA-Z0-9:-]/;
|
||||
var SYMBOL_CONT = /[_$a-zA-Z0-9]/;
|
||||
var STRINGISH_START = ['"', "'", "/"];
|
||||
var NOT_WHITESPACE = /[^\s]/;
|
||||
function tokenizeString(str) {
|
||||
@@ -1842,7 +1842,7 @@ return (function () {
|
||||
nodeData.onHandlers ||= {};
|
||||
var func = new Function("event", code + "; return;");
|
||||
var listener = elt.addEventListener(eventName, function (e) {
|
||||
return func(e);
|
||||
return func.call(elt, e);
|
||||
});
|
||||
nodeData.onHandlers[eventName] = listener;
|
||||
return {nodeData, code, func, listener};
|
||||
|
||||
96
test/attributes/hx-on.js
Normal file
96
test/attributes/hx-on.js
Normal file
@@ -0,0 +1,96 @@
|
||||
describe("hx-on attribute", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it("can handle basic events w/ no other attributes", function () {
|
||||
var btn = make("<button hx-on='click: window.foo = true'>Foo</button>");
|
||||
btn.click();
|
||||
window.foo.should.equal(true);
|
||||
delete window.foo;
|
||||
});
|
||||
|
||||
it("can modify a parameter via htmx:configRequest", function () {
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
var params = parseParams(xhr.requestBody);
|
||||
xhr.respond(200, {}, params.foo);
|
||||
});
|
||||
var btn = make("<button hx-on='htmx:configRequest: event.detail.parameters.foo = \"bar\"' hx-post='/test'>Foo</button>");
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerText.should.equal("bar");
|
||||
});
|
||||
|
||||
it("can cancel an event via preventDefault for htmx:configRequest", function () {
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
xhr.respond(200, {}, "<button>Bar</button>");
|
||||
});
|
||||
var btn = make("<button hx-on='htmx:configRequest: event.preventDefault()' hx-post='/test' hx-swap='outerHTML'>Foo</button>");
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerText.should.equal("Foo");
|
||||
});
|
||||
|
||||
it("can respond to kebab-case events", function () {
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
var params = parseParams(xhr.requestBody);
|
||||
xhr.respond(200, {}, params.foo);
|
||||
});
|
||||
var btn = make("<button hx-on='htmx:config-request: event.detail.parameters.foo = \"bar\"' hx-post='/test'>Foo</button>");
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerText.should.equal("bar");
|
||||
});
|
||||
|
||||
it("has the this symbol set to the element", function () {
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
xhr.respond(200, {}, "foo");
|
||||
});
|
||||
var btn = make("<button hx-on='htmx:config-request: window.elt = this' hx-post='/test'>Foo</button>");
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerText.should.equal("foo");
|
||||
btn.should.equal(window.elt);
|
||||
delete window.elt;
|
||||
});
|
||||
|
||||
it("can handle multi-line JSON", function () {
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
xhr.respond(200, {}, "foo");
|
||||
});
|
||||
var btn = make("<button hx-on='htmx:config-request: window.elt = {foo: true,\n" +
|
||||
" bar: false}' hx-post='/test'>Foo</button>");
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerText.should.equal("foo");
|
||||
var obj = {foo: true, bar: false};
|
||||
obj.should.deep.equal(window.elt);
|
||||
delete window.elt;
|
||||
});
|
||||
|
||||
it("can handle multiple event handlers in the presence of multi-line JSON", function () {
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
xhr.respond(200, {}, "foo");
|
||||
});
|
||||
var btn = make("<button hx-on='htmx:config-request: window.elt = {foo: true,\n" +
|
||||
" bar: false}\n" +
|
||||
" htmx:afterRequest: window.foo = true'" +
|
||||
" hx-post='/test'>Foo</button>");
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerText.should.equal("foo");
|
||||
|
||||
var obj = {foo: true, bar: false};
|
||||
obj.should.deep.equal(window.elt);
|
||||
delete window.elt;
|
||||
|
||||
window.foo.should.equal(true);
|
||||
delete window.foo;
|
||||
});
|
||||
|
||||
});
|
||||
@@ -506,7 +506,7 @@ describe("Core htmx Events", function() {
|
||||
it("preventDefault() in htmx:configRequest stops the request", function () {
|
||||
try {
|
||||
var handler = htmx.on("htmx:configRequest", function (evt) {
|
||||
evt.detail.errors.push("An error");
|
||||
evt.preventDefault();
|
||||
});
|
||||
var request = false;
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
<script src="attributes/hx-include.js"></script>
|
||||
<script src="attributes/hx-indicator.js"></script>
|
||||
<script src="attributes/hx-disinherit.js"></script>
|
||||
<script src="attributes/hx-on.js"></script>
|
||||
<script src="attributes/hx-params.js"></script>
|
||||
<script src="attributes/hx-patch.js"></script>
|
||||
<script src="attributes/hx-post.js"></script>
|
||||
|
||||
@@ -92,6 +92,8 @@ Autorespond: <input id="autorespond" type="checkbox" onclick="toggleAutoRespond(
|
||||
<a id="a1" hx-boost="true" href="#" target="">Asdf</a>
|
||||
</div>
|
||||
|
||||
<button onclick="console.log(this, event)">Log It...</button>
|
||||
|
||||
<script>
|
||||
let requestCount = 0;
|
||||
this.server.respondWith("GET", "/demo", function(xhr){
|
||||
|
||||
53
www/attributes/hx-on.md
Normal file
53
www/attributes/hx-on.md
Normal file
@@ -0,0 +1,53 @@
|
||||
---
|
||||
layout: layout.njk
|
||||
title: </> htmx - hx-on
|
||||
---
|
||||
|
||||
## `hx-on`
|
||||
|
||||
The `hx-on` attribute allows you embed JavaScript scripts to respond to events directly on an element. It is
|
||||
very similar to the [`onevent` properties](https://developer.mozilla.org/en-US/docs/Web/Events/Event_handlers#using_onevent_properties)
|
||||
found in HTML, such as `onClick`.
|
||||
|
||||
`hx-on` improves on the `onevent` handlers in that it can handle any events, not just a fixed number of specific
|
||||
DOM events. This allows you to respond to, for example, the many htmx-emitted events in a nice, embedded manner
|
||||
that gives good [Locality of Behavior (LoB)](/essays/locality-of-behavior).
|
||||
|
||||
The `hx-on` attribute's value is an event name, followed by a colon, followed by the event handler code:
|
||||
|
||||
```html
|
||||
<div>
|
||||
<button hx-get="/info" hx-on="htmx:beforeRequest: alert('Making a request!')">
|
||||
Get Info!
|
||||
</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
Here the event `hmtx:beforeRequest` is captured and shows an alert. Note that it is not possible to respond to this
|
||||
event using the `onevent` properties in normal HTML.
|
||||
|
||||
### Symbols
|
||||
|
||||
Following the conventions of the `onevent` properties, two symbols are available in the body of the event handler code:
|
||||
|
||||
* `this` - Set to the element on which the `hx-on` attribute is defined
|
||||
* `event` - Set to the event that triggered the handler
|
||||
|
||||
### Multiple Handlers
|
||||
|
||||
Multiple handlers can be defined by putting them on new lines:
|
||||
|
||||
```html
|
||||
<div>
|
||||
<button hx-get="/info" hx-on="htmx:beforeRequest: alert('Making a request!')
|
||||
htmx:afterRequest: alert('Done making a request!')">
|
||||
Get Info!
|
||||
</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Notes
|
||||
|
||||
* `hx-on` is _not_ inherited, however due to
|
||||
[event bubbling](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_bubbling_and_capture),
|
||||
`hx-on` attributes on parent elements will typically be triggered by events on child elements
|
||||
78
www/docs.md
78
www/docs.md
@@ -37,7 +37,8 @@ customClasses: wide-content
|
||||
* [extensions](#extensions)
|
||||
* [events & logging](#events)
|
||||
* [debugging](#debugging)
|
||||
* [hyperscript](#hyperscript)
|
||||
* [scripting](#scripting)
|
||||
* [hyperscript](#hyperscript)
|
||||
* [3rd party integration](#3rd-party)
|
||||
* [caching](#caching)
|
||||
* [security](#security)
|
||||
@@ -1166,7 +1167,80 @@ Here is an example of the code in action:
|
||||
|
||||
```
|
||||
|
||||
## <a name="hyperscript"></a>[hyperscript](#hyperscript)
|
||||
## <a name="scripting"></a>[Scripting](#scripting)
|
||||
|
||||
While htmx encourages a hypermedia-based approach to building web applications, it does not preclude scripting and
|
||||
offers a few mechanisms for integrating scripting into your web application. Scripting was explicitly included in
|
||||
the REST-ful description of the web architecture in the [Code-On-Demand](https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#sec_5_1_7)
|
||||
section. As much as is feasible, we recommend a [hypermedia-friendly](/essays/hypermedia-friendly-scripting) approach
|
||||
to scripting in your htmx-based web application:
|
||||
|
||||
* [Respect HATEOAS](/essays/hypermedia-friendly-scripting#prime_directive)
|
||||
* [Use events to communicate between components](/essays/hypermedia-friendly-scripting#events)
|
||||
* [Use islands to isolate non-hypermedia components from the rest of your application](/essays/hypermedia-friendly-scripting#islands)
|
||||
* [Consider inline scripting](/essays/hypermedia-friendly-scripting#inline)
|
||||
|
||||
The primary integration point between htmx and scripting solutions is the [events](#events) that htmx sends and can
|
||||
respond to. See the SortableJS example in the [3rd Party Javascript](3rd-party) section for a good template for
|
||||
integrating a JavaScript library with htmx via events.
|
||||
|
||||
Scripting solutions that pair well with htmx include:
|
||||
|
||||
* [VanillaJS](http://vanilla-js.com/) - Simply using the built-in abilities of JavaScript to hook in event handlers to
|
||||
respond to the events htmx emits can work very well for scripting. This is an extremely lightweight and increasingly
|
||||
popular approach.
|
||||
* [AlpineJS](https://alpinejs.dev/) - Alpine.js provides a rich set of tools for creating sophisticated front end scripts,
|
||||
including reactive programming support, while still remaining extremely lightweight. Alpine encourages the "inline scripting"
|
||||
approach that we feel pairs well with htmx.
|
||||
* [jQuery](https://jquery.com/) - Despite its age and reputation in some circles, jQuery pairs very well with htmx, particularly
|
||||
in older code-bases that already have a lot of jQuery in them.
|
||||
* [hyperscript](https://hyperscript.org) - Hyperscript is an experimental front-end scripting language created by the same
|
||||
team that created htmx. It is designed to embed well in HTML and both respond to and create events, and pairs very well
|
||||
with htmx.
|
||||
|
||||
### <a name="hx-on"></a>[The `hx-on` Attribute](#hyperscript)
|
||||
|
||||
HTML allows the embedding of inline scripts via the [`onevent` properties](https://developer.mozilla.org/en-US/docs/Web/Events/Event_handlers#using_onevent_properties),
|
||||
such as `onClick`:
|
||||
|
||||
```html
|
||||
<button onclick="alert('You clicked me!')">
|
||||
Click Me!
|
||||
</button>
|
||||
```
|
||||
|
||||
This feature allows scripting logic to be co-located with the HTML elements the logic applies to, giving good
|
||||
[Locality of Behavior (LoB)](/essays/locality-of-behavior). Unfortunately, HTML only allows `on*` attributes for a fixed
|
||||
number of specific DOM events (e.g. `onclick`) and doesn't offer a way to respond generally to events in this embedded
|
||||
manner.
|
||||
|
||||
In order to address this shortcoming, htmx offers the [`hx-on`](/attributes/hx-on) attribute. This attribute allows
|
||||
you to respond to any event in a manner that preserves the LoB of the `on*` properties:
|
||||
|
||||
```html
|
||||
<button hx-on="click: alert('You clicked me!')">
|
||||
Click Me!
|
||||
</button>
|
||||
```
|
||||
|
||||
For a `click` event, we would recommend sticking with the standard `onclick` attribute. However, consider an htmx-powered
|
||||
button that wishes to add an attribute to a request using the `htmx:configRequest` event. This would not be possible
|
||||
with an `on*` property, but can be done using the `hx-on` attribute:
|
||||
|
||||
```html
|
||||
<button hx-post="/example"
|
||||
hx-on="htmx:beforeRequest: event.detail.parameters.example = 'Hello Scripting!'">
|
||||
Post Me!
|
||||
</button>
|
||||
```
|
||||
|
||||
Here the `example` parameter is added to the `POST` request before it is issued, with the value 'Hello Scripting!'.
|
||||
|
||||
The `hx-on` attribute is a very simple mechanism for generalized embedded scripting. It is _not_ a replacement for more
|
||||
fully developed front-end scripting solutions such as AlpineJS or hyperscript. It can, however, augment a VanillaJS-based
|
||||
approach to scripting in your htmx-powered application.
|
||||
|
||||
### <a name="hyperscript"></a>[hyperscript](#hyperscript)
|
||||
|
||||
Hyperscript is an experimental front end scripting language designed to be expressive and easily embeddable directly in HTML
|
||||
for handling custom events, etc. The language is inspired by [HyperTalk](http://hypercard.org/HyperTalk%20Reference%202.4.pdf),
|
||||
|
||||
@@ -56,6 +56,7 @@ The table below lists all other attributes available in htmx.
|
||||
| [`hx-history-elt`](/attributes/hx-history-elt) | the element to snapshot and restore during history navigation
|
||||
| [`hx-include`](/attributes/hx-include) | include additional data in requests
|
||||
| [`hx-indicator`](/attributes/hx-indicator) | the element to put the `htmx-request` class on during the request
|
||||
| [`hx-on`](/attributes/hx-on) | defines event handlers inline on an element
|
||||
| [`hx-params`](/attributes/hx-params) | filters the parameters that will be submitted with a request
|
||||
| [`hx-patch`](/attributes/hx-patch) | issues a `PATCH` to the specified URL
|
||||
| [`hx-preserve`](/attributes/hx-preserve) | specifies elements to keep unchanged between requests
|
||||
|
||||
Reference in New Issue
Block a user