Skip to content

Commit f7501ac

Browse files
Allow to use custom HTTP verbs
1 parent 503fd33 commit f7501ac

File tree

6 files changed

+130
-4
lines changed

6 files changed

+130
-4
lines changed

src/htmx.js

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,12 @@ var htmx = (function() {
233233
* @default ['get', 'delete']
234234
*/
235235
methodsThatUseUrlParams: ['get', 'delete'],
236+
/**
237+
* Custom HTTP verbs allowed to be parsed as attribute in form hx-custom-verb-XXX or data-hx-custom-verb-XXX where XXX is the custom verb
238+
* @type {(String)[]}
239+
* @default []
240+
*/
241+
customVerbs: [],
236242
/**
237243
* If set to true, disables htmx-based requests to non-origin hosts.
238244
* @type boolean
@@ -354,7 +360,11 @@ var htmx = (function() {
354360
const VERBS = ['get', 'post', 'put', 'delete', 'patch']
355361
const VERB_SELECTOR = VERBS.map(function(verb) {
356362
return '[hx-' + verb + '], [data-hx-' + verb + ']'
357-
}).join(', ')
363+
}).concat(
364+
htmx.config.customVerbs.map(function(verb) {
365+
return '[hx-custom-verb-' + verb + '], [data-hx-custom-verb-' + verb + ']'
366+
})
367+
).join(', ')
358368

359369
//= ===================================================================
360370
// Utilities
@@ -2669,9 +2679,11 @@ var htmx = (function() {
26692679
*/
26702680
function processVerbs(elt, nodeData, triggerSpecs) {
26712681
let explicitAction = false
2672-
forEach(VERBS, function(verb) {
2673-
if (hasAttribute(elt, 'hx-' + verb)) {
2674-
const path = getAttributeValue(elt, 'hx-' + verb)
2682+
const verbsWithAssociatedAttributes = VERBS.map(function(verb) { return [verb, 'hx-' + verb] })
2683+
const customVerbsWithAssociatedAttributes = htmx.config.customVerbs.map(function(verb) { return [verb, 'hx-custom-verb-' + verb] })
2684+
forEach(verbsWithAssociatedAttributes.concat(customVerbsWithAssociatedAttributes), function([verb, attribute]) {
2685+
if (hasAttribute(elt, attribute)) {
2686+
const path = getAttributeValue(elt, attribute)
26752687
explicitAction = true
26762688
nodeData.path = path
26772689
nodeData.verb = verb
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
describe('hx-custom-verb attribute', function() {
2+
beforeEach(function() {
3+
this.server = makeServer()
4+
clearWorkArea()
5+
})
6+
afterEach(function() {
7+
this.server.restore()
8+
clearWorkArea()
9+
})
10+
11+
it('issues a custom verb request', function() {
12+
this.server.respondWith('RESET_PASSWORD', '/test', function(xhr) {
13+
xhr.respond(200, {}, 'Password reset!')
14+
})
15+
make('<script lang="js">htmx.config.customVerbs.push(\'reset_password\')</script>')
16+
17+
var btn = make('<button hx-custom-verb-reset_password="/test">Click me!</button>')
18+
btn.click()
19+
this.server.respond()
20+
btn.innerHTML.should.equal('Password reset!')
21+
22+
make('<script lang="js">htmx.config.customVerbs = htmx.config.customVerbs.filter((verb) => verb !== \'reset_password\')</script>')
23+
})
24+
25+
it('issues a custom verb request w/ data-* prefix', function() {
26+
this.server.respondWith('RESET_PASSWORD', '/test', function(xhr) {
27+
xhr.respond(200, {}, 'Password reset!')
28+
})
29+
make('<script lang="js">htmx.config.customVerbs.push(\'reset_password\')</script>')
30+
31+
var btn = make('<button data-hx-custom-verb-reset_password="/test">Click me!</button>')
32+
btn.click()
33+
this.server.respond()
34+
btn.innerHTML.should.equal('Password reset!')
35+
36+
make('<script lang="js">htmx.config.customVerbs = htmx.config.customVerbs.filter((verb) => verb !== \'reset_password\')</script>')
37+
})
38+
39+
it('does not issues a custom verb request if the config is not set', function() {
40+
this.server.respondWith('RESET_PASSWORD', '/test', function(xhr) {
41+
xhr.respond(200, {}, 'Password reset!')
42+
})
43+
44+
// Do not add configuration to prove effectiveness
45+
var btn = make('<button hx-custom-verb-reset_password="/test">Click me!</button>')
46+
btn.click()
47+
this.server.respond()
48+
btn.innerHTML.should.not.equal('Password reset!')
49+
btn.innerHTML.should.equal('Click me!')
50+
})
51+
})

test/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ <h2>Mocha Test Suite</h2>
8181
<!-- attribute tests -->
8282
<script src="attributes/hx-boost.js"></script>
8383
<script src="attributes/hx-confirm.js"></script>
84+
<script src="attributes/hx-custom-verb-wildcard.js"></script>
8485
<script src="attributes/hx-delete.js"></script>
8586
<script src="attributes/hx-ext.js"></script>
8687
<script src="attributes/hx-get.js"></script>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
+++
2+
title = "hx-custom-verb"
3+
description = """\
4+
The hx-custom-verb attribute in htmx will cause an element to issue a request with a custom verb \
5+
to the specified URL and swap the returned HTML into the DOM using a swap strategy."""
6+
+++
7+
8+
The `hx-custom-verb-*` attribute in htmx will cause an element to issue a request with a custom verb
9+
to the specified URL and swap the returned HTML into the DOM using a swap strategy:
10+
11+
```html
12+
<script lang="js">htmx.config.customVerbs.push('reset_password')</script>
13+
<button hx-custom-verb-reset_password="/me" hx-target="body">
14+
Reset my password
15+
</button>
16+
```
17+
18+
This example will cause the `button` to issue a `RESET_PASSWORD` to `/me` and swap the returned HTML into
19+
the `innerHTML` of the `body`.
20+
21+
## Notes
22+
23+
* `hx-custom-verb` is not inherited
24+
* You have to allow the custom verb by adding it to the config `customVerbs` as show in the example
25+
* You can control the target of the swap using the [hx-target](@/attributes/hx-target.md) attribute
26+
* You can control the swap strategy by using the [hx-swap](@/attributes/hx-swap.md) attribute
27+
* You can control what event triggers the request with the [hx-trigger](@/attributes/hx-trigger.md) attribute
28+
* You can control the data submitted with the request in various ways, documented here: [Parameters](@/docs.md#parameters)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
describe('hx-custom-verb attribute', function() {
2+
beforeEach(function() {
3+
this.server = makeServer()
4+
clearWorkArea()
5+
make('<script lang="js">htmx.config.customVerbs.push(\'reset_password\')</script>')
6+
})
7+
afterEach(function() {
8+
make('<script lang="js">htmx.config.customVerbs = htmx.config.customVerbs.filter((verb) => verb !== \'reset_password\')</script>')
9+
this.server.restore()
10+
clearWorkArea()
11+
})
12+
13+
it('issues a custom verb request', function() {
14+
this.server.respondWith('RESET_PASSWORD', '/test', function(xhr) {
15+
xhr.respond(200, {}, 'Password reset!')
16+
})
17+
var btn = make('<button hx-custom-verb-reset_password="/test">Click me!</button>')
18+
btn.click()
19+
this.server.respond()
20+
btn.innerHTML.should.equal('Password reset!')
21+
})
22+
23+
it('issues a custom verb request w/ data-* prefix', function() {
24+
this.server.respondWith('RESET_PASSWORD', '/test', function(xhr) {
25+
xhr.respond(200, {}, 'Password reset!')
26+
})
27+
28+
var btn = make('<button data-hx-custom-verb-reset_password="/test">Click me!</button>')
29+
btn.click()
30+
this.server.respond()
31+
btn.innerHTML.should.equal('Password reset!')
32+
})
33+
})

www/static/test/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ <h2>Mocha Test Suite</h2>
8181
<!-- attribute tests -->
8282
<script src="attributes/hx-boost.js"></script>
8383
<script src="attributes/hx-confirm.js"></script>
84+
<script src="attributes/hx-custom-verb-wildcard.js"></script>
8485
<script src="attributes/hx-delete.js"></script>
8586
<script src="attributes/hx-ext.js"></script>
8687
<script src="attributes/hx-get.js"></script>

0 commit comments

Comments
 (0)