diff --git a/src/htmx.js b/src/htmx.js index 5955d236..5eaa6ce1 100644 --- a/src/htmx.js +++ b/src/htmx.js @@ -472,7 +472,10 @@ return (function () { function removeElement(elt, delay) { elt = resolveTarget(elt); if (delay) { - setTimeout(function(){removeElement(elt);}, delay) + setTimeout(function(){ + removeElement(elt); + elt = null; + }, delay); } else { elt.parentElement.removeChild(elt); } @@ -481,7 +484,10 @@ return (function () { function addClassToElement(elt, clazz, delay) { elt = resolveTarget(elt); if (delay) { - setTimeout(function(){addClassToElement(elt, clazz);}, delay) + setTimeout(function(){ + addClassToElement(elt, clazz); + elt = null; + }, delay); } else { elt.classList && elt.classList.add(clazz); } @@ -490,7 +496,10 @@ return (function () { function removeClassFromElement(elt, clazz, delay) { elt = resolveTarget(elt); if (delay) { - setTimeout(function(){removeClassFromElement(elt, clazz);}, delay) + setTimeout(function(){ + removeClassFromElement(elt, clazz); + elt = null; + }, delay); } else { if (elt.classList) { elt.classList.remove(clazz); @@ -1397,6 +1406,7 @@ return (function () { } else if (triggerSpec.delay) { elementData.delayed = setTimeout(function() { handler(elt, evt) }, triggerSpec.delay); } else { + triggerEvent(elt, 'htmx:trigger') handler(elt, evt); } } @@ -1689,6 +1699,13 @@ return (function () { }); } }); + if (!explicitAction && hasAttribute(elt, 'hx-trigger')) { + explicitAction = true + triggerSpecs.forEach(function(triggerSpec) { + // For "naked" triggers, don't do anything at all + addTriggerHandler(elt, triggerSpec, nodeData, function () { }) + }) + } return explicitAction; } @@ -1773,7 +1790,7 @@ return (function () { if (elt.querySelectorAll) { var boostedElts = hasChanceOfBeingBoosted() ? ", a, form" : ""; var results = elt.querySelectorAll(VERB_SELECTOR + boostedElts + ", [hx-sse], [data-hx-sse], [hx-ws]," + - " [data-hx-ws], [hx-ext], [data-hx-ext]"); + " [data-hx-ws], [hx-ext], [data-hx-ext], [hx-trigger], [data-hx-trigger]"); return results; } else { return []; @@ -3435,6 +3452,7 @@ return (function () { }; setTimeout(function () { triggerEvent(body, 'htmx:load', {}); // give ready handlers a chance to load up before firing this event + body = null; // kill reference for gc }, 0); }) diff --git a/test/attributes/hx-trigger.js b/test/attributes/hx-trigger.js index a181b979..72086a17 100644 --- a/test/attributes/hx-trigger.js +++ b/test/attributes/hx-trigger.js @@ -11,7 +11,6 @@ describe("hx-trigger attribute", function(){ it('non-default value works', function() { this.server.respondWith("GET", "/test", "Clicked!"); - var form = make('
Click Me!
'); form.click(); form.innerHTML.should.equal("Click Me!"); @@ -756,5 +755,33 @@ describe("hx-trigger attribute", function(){ div.innerHTML.should.equal("test 1"); }); + it("fires the htmx:trigger event when an AJAX attribute is specified", function () { + var param = "foo" + var handler = htmx.on("htmx:trigger", function (evt) { + param = "bar" + }); + try { + this.server.respondWith("GET", "/test1", "test 1"); + var div = make(''); + div.click(); + should.equal(param, "bar"); + } finally { + htmx.off("htmx:trigger", handler); + } + }); + + it("fires the htmx:trigger event when no AJAX attribute is specified", function () { + var param = "foo" + var handler = htmx.on("htmx:trigger", function (evt) { + param = "bar" + }); + try { + var div = make(''); + div.click(); + should.equal(param, "bar"); + } finally { + htmx.off("htmx:trigger", handler); + } + }); }) diff --git a/www/attributes/hx-trigger.md b/www/attributes/hx-trigger.md index c50f3886..ce3dadc9 100644 --- a/www/attributes/hx-trigger.md +++ b/www/attributes/hx-trigger.md @@ -64,7 +64,7 @@ is seen again before the delay completes it is ignored, the element will trigger * `closest ` - finds the closest parent matching the given css selector * `find ` - finds the closest child matching the given css selector * `target:` - allows you to filter via a CSS selector on the target of the event. This can be useful when you want to listen for -triggers from elements that might not be in the DOM at the point of initialization, by, for example, listening on the body, +triggers from elements that might not be in the DOM at the point of initialization, by, for example, listening on the body, but with a target filter for a child element * `consume` - if this option is included the event will not trigger any other htmx requests on parents (or on elements listening on parents) @@ -78,7 +78,7 @@ Here is an example of a search box that searches on `keyup`, but only if the sea and the user hasn't typed anything new for 1 second: ```html - ``` @@ -95,10 +95,10 @@ There are some additional non-standard events that htmx supports: * `root:` - a CSS selector of the root element for intersection * `threshold:` - a floating point number between 0.0 and 1.0, indicating what amount of intersection to fire the event on -### Triggering via the `HX-Trigger` header +### Triggering via the `HX-Trigger` header -If you're trying to fire an event from HX-Trigger response header, you will likely want to -use the `from:body` modifier. E.g. if you send a header like this HX-Trigger: my-custom-event +If you're trying to fire an event from HX-Trigger response header, you will likely want to +use the `from:body` modifier. E.g. if you send a header like this HX-Trigger: my-custom-event with a response, an element would likely need to look like this: ```html @@ -108,7 +108,7 @@ with a response, an element would likely need to look like this: ``` in order to fire. - + This is because the header will likely trigger the event in a different DOM hierarchy than the element that you wish to be triggered. For a similar reason, you will often listen for hot keys from the body. @@ -148,3 +148,4 @@ The AJAX request can be triggered via Javascript [`htmx.trigger()`](/api#trigger ### Notes * `hx-trigger` is not inherited +* `hx-trigger` can be used without an AJAX request, in which case it will only fire the `htmx:trigger` event diff --git a/www/events.md b/www/events.md index e027564a..2a4c3ae9 100644 --- a/www/events.md +++ b/www/events.md @@ -413,6 +413,16 @@ Timeout time can be set using `htmx.config.timeout` or per element using [`hx-re * `detail.target` - the target of the request * `detail.requestConfig` - the configuration of the AJAX request +### Event - [`htmx:trigger`](#htmx:trigger) + +This event is triggered whenever an AJAX request would be, even if no AJAX request is specified. It +is primarily intended to allow `hx-trigger` to execute client-side scripts; AJAX requests have more +granular events available, like [`htmx:beforeRequest`](#htmx:beforeRequest) or [`htmx:afterSend`](#htmx:afterSend). + +##### Details + +* `detail.elt` - the element that triggered the request + ### Event - [htmx:validation:validate](#htmx:validation:validate) This event is triggered before an element is validated. It can be used with the `elt.setCustomValidity()` method diff --git a/www/reference.md b/www/reference.md index 9821353e..789ae995 100644 --- a/www/reference.md +++ b/www/reference.md @@ -163,6 +163,7 @@ The table below lists all other attributes available in htmx. | [`htmx:swapError`](/events#htmx:swapError) | triggered when an error occurs during the swap phase | [`htmx:targetError`](/events#htmx:targetError) | triggered when an invalid target is specified | [`htmx:timeout`](/events#htmx:timeout) | triggered when a request timeout occurs +| [`htmx:trigger`](/events#htmx:trigger) | triggered by the event specified in `hx-trigger` | [`htmx:validation:validate`](/events#htmx:validation:validate) | triggered before an element is validated | [`htmx:validation:failed`](/events#htmx:validation:failed) | triggered when an element fails validation | [`htmx:validation:halted`](/events#htmx:validation:halted) | triggered when a request is halted due to validation errors