Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions src/htmx.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,12 @@ var htmx = (function() {
* @default ['get', 'delete']
*/
methodsThatUseUrlParams: ['get', 'delete'],
/**
* 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
* @type {(String)[]}
* @default []
*/
customVerbs: [],
/**
* If set to true, disables htmx-based requests to non-origin hosts.
* @type boolean
Expand Down Expand Up @@ -354,7 +360,11 @@ var htmx = (function() {
const VERBS = ['get', 'post', 'put', 'delete', 'patch']
const VERB_SELECTOR = VERBS.map(function(verb) {
return '[hx-' + verb + '], [data-hx-' + verb + ']'
}).join(', ')
}).concat(
htmx.config.customVerbs.map(function(verb) {
return '[hx-custom-verb-' + verb + '], [data-hx-custom-verb-' + verb + ']'
})
).join(', ')

//= ===================================================================
// Utilities
Expand Down Expand Up @@ -2669,9 +2679,11 @@ var htmx = (function() {
*/
function processVerbs(elt, nodeData, triggerSpecs) {
let explicitAction = false
forEach(VERBS, function(verb) {
if (hasAttribute(elt, 'hx-' + verb)) {
const path = getAttributeValue(elt, 'hx-' + verb)
const verbsWithAssociatedAttributes = VERBS.map(function(verb) { return [verb, 'hx-' + verb] })
const customVerbsWithAssociatedAttributes = htmx.config.customVerbs.map(function(verb) { return [verb, 'hx-custom-verb-' + verb] })
forEach(verbsWithAssociatedAttributes.concat(customVerbsWithAssociatedAttributes), function([verb, attribute]) {
if (hasAttribute(elt, attribute)) {
const path = getAttributeValue(elt, attribute)
explicitAction = true
nodeData.path = path
nodeData.verb = verb
Expand Down
51 changes: 51 additions & 0 deletions test/attributes/hx-custom-verb-wildcard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
describe('hx-custom-verb attribute', function() {
beforeEach(function() {
this.server = makeServer()
clearWorkArea()
})
afterEach(function() {
this.server.restore()
clearWorkArea()
})

it('issues a custom verb request', function() {
this.server.respondWith('RESET_PASSWORD', '/test', function(xhr) {
xhr.respond(200, {}, 'Password reset!')
})
make('<script lang="js">htmx.config.customVerbs.push(\'reset_password\')</script>')

var btn = make('<button hx-custom-verb-reset_password="/test">Click me!</button>')
btn.click()
this.server.respond()
btn.innerHTML.should.equal('Password reset!')

make('<script lang="js">htmx.config.customVerbs = htmx.config.customVerbs.filter((verb) => verb !== \'reset_password\')</script>')
})

it('issues a custom verb request w/ data-* prefix', function() {
this.server.respondWith('RESET_PASSWORD', '/test', function(xhr) {
xhr.respond(200, {}, 'Password reset!')
})
make('<script lang="js">htmx.config.customVerbs.push(\'reset_password\')</script>')

var btn = make('<button data-hx-custom-verb-reset_password="/test">Click me!</button>')
btn.click()
this.server.respond()
btn.innerHTML.should.equal('Password reset!')

make('<script lang="js">htmx.config.customVerbs = htmx.config.customVerbs.filter((verb) => verb !== \'reset_password\')</script>')
})

it('does not issues a custom verb request if the config is not set', function() {
this.server.respondWith('RESET_PASSWORD', '/test', function(xhr) {
xhr.respond(200, {}, 'Password reset!')
})

// Do not add configuration to prove effectiveness
var btn = make('<button hx-custom-verb-reset_password="/test">Click me!</button>')
btn.click()
this.server.respond()
btn.innerHTML.should.not.equal('Password reset!')
btn.innerHTML.should.equal('Click me!')
})
})
1 change: 1 addition & 0 deletions test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ <h2>Mocha Test Suite</h2>
<!-- attribute tests -->
<script src="attributes/hx-boost.js"></script>
<script src="attributes/hx-confirm.js"></script>
<script src="attributes/hx-custom-verb-wildcard.js"></script>
<script src="attributes/hx-delete.js"></script>
<script src="attributes/hx-ext.js"></script>
<script src="attributes/hx-get.js"></script>
Expand Down
28 changes: 28 additions & 0 deletions www/content/attributes/hx-custom-verb.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
+++
title = "hx-custom-verb"
description = """\
The hx-custom-verb attribute in htmx will cause an element to issue a request with a custom verb \
to the specified URL and swap the returned HTML into the DOM using a swap strategy."""
+++

The `hx-custom-verb-*` attribute in htmx will cause an element to issue a request with a custom verb
to the specified URL and swap the returned HTML into the DOM using a swap strategy:

```html
<script lang="js">htmx.config.customVerbs.push('reset_password')</script>
<button hx-custom-verb-reset_password="/me" hx-target="body">
Reset my password
</button>
```

This example will cause the `button` to issue a `RESET_PASSWORD` to `/me` and swap the returned HTML into
the `innerHTML` of the `body`.

## Notes

* `hx-custom-verb` is not inherited
* You have to allow the custom verb by adding it to the config `customVerbs` as show in the example
* You can control the target of the swap using the [hx-target](@/attributes/hx-target.md) attribute
* You can control the swap strategy by using the [hx-swap](@/attributes/hx-swap.md) attribute
* You can control what event triggers the request with the [hx-trigger](@/attributes/hx-trigger.md) attribute
* You can control the data submitted with the request in various ways, documented here: [Parameters](@/docs.md#parameters)
33 changes: 33 additions & 0 deletions www/static/test/attributes/hx-custom-verb-wildcard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
describe('hx-custom-verb attribute', function() {
beforeEach(function() {
this.server = makeServer()
clearWorkArea()
make('<script lang="js">htmx.config.customVerbs.push(\'reset_password\')</script>')
})
afterEach(function() {
make('<script lang="js">htmx.config.customVerbs = htmx.config.customVerbs.filter((verb) => verb !== \'reset_password\')</script>')
this.server.restore()
clearWorkArea()
})

it('issues a custom verb request', function() {
this.server.respondWith('RESET_PASSWORD', '/test', function(xhr) {
xhr.respond(200, {}, 'Password reset!')
})
var btn = make('<button hx-custom-verb-reset_password="/test">Click me!</button>')
btn.click()
this.server.respond()
btn.innerHTML.should.equal('Password reset!')
})

it('issues a custom verb request w/ data-* prefix', function() {
this.server.respondWith('RESET_PASSWORD', '/test', function(xhr) {
xhr.respond(200, {}, 'Password reset!')
})

var btn = make('<button data-hx-custom-verb-reset_password="/test">Click me!</button>')
btn.click()
this.server.respond()
btn.innerHTML.should.equal('Password reset!')
})
})
1 change: 1 addition & 0 deletions www/static/test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ <h2>Mocha Test Suite</h2>
<!-- attribute tests -->
<script src="attributes/hx-boost.js"></script>
<script src="attributes/hx-confirm.js"></script>
<script src="attributes/hx-custom-verb-wildcard.js"></script>
<script src="attributes/hx-delete.js"></script>
<script src="attributes/hx-ext.js"></script>
<script src="attributes/hx-get.js"></script>
Expand Down