mirror of
https://github.com/bigskysoftware/htmx.git
synced 2025-10-03 15:55:39 +00:00
docs and tests for trigger filters
This commit is contained in:
parent
6c8c124c42
commit
165586b777
35
src/htmx.js
35
src/htmx.js
@ -688,7 +688,7 @@ return (function () {
|
|||||||
if (tokens[0] === '[') {
|
if (tokens[0] === '[') {
|
||||||
tokens.shift();
|
tokens.shift();
|
||||||
var bracketCount = 1;
|
var bracketCount = 1;
|
||||||
var conditional = "(function(" + paramName + "){ return (";
|
var conditionalSource = "(function(" + paramName + "){ return (";
|
||||||
var last = null;
|
var last = null;
|
||||||
while (tokens.length > 0) {
|
while (tokens.length > 0) {
|
||||||
var token = tokens[0];
|
var token = tokens[0];
|
||||||
@ -696,23 +696,26 @@ return (function () {
|
|||||||
bracketCount--;
|
bracketCount--;
|
||||||
if (bracketCount === 0) {
|
if (bracketCount === 0) {
|
||||||
if (last === null) {
|
if (last === null) {
|
||||||
conditional = conditional + "true";
|
conditionalSource = conditionalSource + "true";
|
||||||
}
|
}
|
||||||
tokens.shift();
|
tokens.shift();
|
||||||
conditional += ")})";
|
conditionalSource += ")})";
|
||||||
try {
|
try {
|
||||||
return eval(conditional);
|
var conditionFunction = eval(conditionalSource);
|
||||||
|
conditionFunction.source = conditionalSource;
|
||||||
|
return conditionFunction;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
triggerErrorEvent(getDocument(), "htmx:syntax:error", {error:e})
|
triggerErrorEvent(getDocument().body, "htmx:syntax:error", {error:e, source:conditionalSource})
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (token === "[") {
|
} else if (token === "[") {
|
||||||
bracketCount++;
|
bracketCount++;
|
||||||
}
|
}
|
||||||
if (isPossibleRelativeReference(token, last, paramName)) {
|
if (isPossibleRelativeReference(token, last, paramName)) {
|
||||||
conditional += "((" + paramName + "." + token + ") ? (" + paramName + "." + token + ") : (window." + token + "))";
|
conditionalSource += "((" + paramName + "." + token + ") ? (" + paramName + "." + token + ") : (window." + token + "))";
|
||||||
} else {
|
} else {
|
||||||
conditional = conditional + token;
|
conditionalSource = conditionalSource + token;
|
||||||
}
|
}
|
||||||
last = tokens.shift();
|
last = tokens.shift();
|
||||||
}
|
}
|
||||||
@ -746,7 +749,7 @@ return (function () {
|
|||||||
triggerSpecs.push({trigger: 'sse', sseEvent: trigger.substr(4)});
|
triggerSpecs.push({trigger: 'sse', sseEvent: trigger.substr(4)});
|
||||||
} else {
|
} else {
|
||||||
var triggerSpec = {trigger: trigger};
|
var triggerSpec = {trigger: trigger};
|
||||||
var eventFilter = maybeGenerateConditional(tokens, "evt");
|
var eventFilter = maybeGenerateConditional(tokens, "event");
|
||||||
if (eventFilter) {
|
if (eventFilter) {
|
||||||
triggerSpec.eventFilter = eventFilter;
|
triggerSpec.eventFilter = eventFilter;
|
||||||
}
|
}
|
||||||
@ -836,10 +839,22 @@ return (function () {
|
|||||||
return getInternalData(elt).boosted && elt.tagName === "A" && evt.type === "click" && evt.ctrlKey;
|
return getInternalData(elt).boosted && elt.tagName === "A" && evt.type === "click" && evt.ctrlKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function maybeFilterEvent(triggerSpec, evt) {
|
||||||
|
var eventFilter = triggerSpec.eventFilter;
|
||||||
|
if(eventFilter){
|
||||||
|
try {
|
||||||
|
return eventFilter(evt) !== true;
|
||||||
|
} catch(e) {
|
||||||
|
triggerErrorEvent(getDocument().body, "htmx:eventFilter:error", {error: e, source:eventFilter.source});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function addEventListener(elt, verb, path, nodeData, triggerSpec, explicitCancel) {
|
function addEventListener(elt, verb, path, nodeData, triggerSpec, explicitCancel) {
|
||||||
var eventListener = function (evt) {
|
var eventListener = function (evt) {
|
||||||
if (triggerSpec.eventFilter &&
|
if (maybeFilterEvent(triggerSpec, evt)) {
|
||||||
triggerSpec.eventFilter(evt) !== true) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (ignoreBoostedAnchorCtrlClick(elt, evt)) {
|
if (ignoreBoostedAnchorCtrlClick(elt, evt)) {
|
||||||
|
@ -37,7 +37,6 @@ describe("hx-push-url attribute", function() {
|
|||||||
this.server.respond();
|
this.server.respond();
|
||||||
getWorkArea().textContent.should.equal("second")
|
getWorkArea().textContent.should.equal("second")
|
||||||
var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME));
|
var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME));
|
||||||
console.log(cache);
|
|
||||||
cache.length.should.equal(2);
|
cache.length.should.equal(2);
|
||||||
cache[1].url.should.equal("/abc123");
|
cache[1].url.should.equal("/abc123");
|
||||||
});
|
});
|
||||||
@ -193,7 +192,6 @@ describe("hx-push-url attribute", function() {
|
|||||||
for (var i = 0; i < 20; i++) {
|
for (var i = 0; i < 20; i++) {
|
||||||
bigContent += bigContent;
|
bigContent += bigContent;
|
||||||
}
|
}
|
||||||
console.log(bigContent.length);
|
|
||||||
try {
|
try {
|
||||||
localStorage.removeItem("htmx-history-cache");
|
localStorage.removeItem("htmx-history-cache");
|
||||||
htmx._("saveToHistoryCache")("/dummy", bigContent, "Foo", 0);
|
htmx._("saveToHistoryCache")("/dummy", bigContent, "Foo", 0);
|
||||||
|
@ -152,7 +152,7 @@ describe("hx-trigger attribute", function(){
|
|||||||
spec.should.deep.equal([{trigger: 'change'}]);
|
spec.should.deep.equal([{trigger: 'change'}]);
|
||||||
})
|
})
|
||||||
|
|
||||||
it('filters properly with filter spec', function(){
|
it('filters properly with false filter spec', function(){
|
||||||
this.server.respondWith("GET", "/test", "Called!");
|
this.server.respondWith("GET", "/test", "Called!");
|
||||||
var form = make('<form hx-get="/test" hx-trigger="evt[foo]">Not Called</form>');
|
var form = make('<form hx-get="/test" hx-trigger="evt[foo]">Not Called</form>');
|
||||||
form.click();
|
form.click();
|
||||||
@ -161,10 +161,135 @@ describe("hx-trigger attribute", function(){
|
|||||||
form.dispatchEvent(event);
|
form.dispatchEvent(event);
|
||||||
this.server.respond();
|
this.server.respond();
|
||||||
form.innerHTML.should.equal("Not Called");
|
form.innerHTML.should.equal("Not Called");
|
||||||
|
})
|
||||||
|
|
||||||
|
it('filters properly with true filter spec', function(){
|
||||||
|
this.server.respondWith("GET", "/test", "Called!");
|
||||||
|
var form = make('<form hx-get="/test" hx-trigger="evt[foo]">Not Called</form>');
|
||||||
|
form.click();
|
||||||
|
form.innerHTML.should.equal("Not Called");
|
||||||
|
var event = htmx._("makeEvent")('evt');
|
||||||
event.foo = true;
|
event.foo = true;
|
||||||
form.dispatchEvent(event);
|
form.dispatchEvent(event);
|
||||||
this.server.respond();
|
this.server.respond();
|
||||||
form.innerHTML.should.equal("Called!");
|
form.innerHTML.should.equal("Called!");
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('filters properly compound filter spec', function(){
|
||||||
|
this.server.respondWith("GET", "/test", "Called!");
|
||||||
|
var div = make('<div hx-get="/test" hx-trigger="evt[foo&&bar]">Not Called</div>');
|
||||||
|
var event = htmx._("makeEvent")('evt');
|
||||||
|
event.foo = true;
|
||||||
|
div.dispatchEvent(event);
|
||||||
|
this.server.respond();
|
||||||
|
div.innerHTML.should.equal("Not Called");
|
||||||
|
event.bar = true;
|
||||||
|
div.dispatchEvent(event);
|
||||||
|
this.server.respond();
|
||||||
|
div.innerHTML.should.equal("Called!");
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can refer to target element in condition', function(){
|
||||||
|
this.server.respondWith("GET", "/test", "Called!");
|
||||||
|
var div = make('<div hx-get="/test" hx-trigger="evt[target.classList.contains(\'doIt\')]">Not Called</div>');
|
||||||
|
var event = htmx._("makeEvent")('evt');
|
||||||
|
div.dispatchEvent(event);
|
||||||
|
this.server.respond();
|
||||||
|
div.innerHTML.should.equal("Not Called");
|
||||||
|
div.classList.add("doIt");
|
||||||
|
div.dispatchEvent(event);
|
||||||
|
this.server.respond();
|
||||||
|
div.innerHTML.should.equal("Called!");
|
||||||
|
})
|
||||||
|
|
||||||
|
it('negative condition', function(){
|
||||||
|
this.server.respondWith("GET", "/test", "Called!");
|
||||||
|
var div = make('<div hx-get="/test" hx-trigger="evt[!target.classList.contains(\'disabled\')]">Not Called</div>');
|
||||||
|
div.classList.add("disabled");
|
||||||
|
var event = htmx._("makeEvent")('evt');
|
||||||
|
div.dispatchEvent(event);
|
||||||
|
this.server.respond();
|
||||||
|
div.innerHTML.should.equal("Not Called");
|
||||||
|
div.classList.remove("disabled");
|
||||||
|
div.dispatchEvent(event);
|
||||||
|
this.server.respond();
|
||||||
|
div.innerHTML.should.equal("Called!");
|
||||||
|
})
|
||||||
|
|
||||||
|
it('global function call works', function(){
|
||||||
|
window.globalFun = function(evt) {
|
||||||
|
return evt.bar;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.server.respondWith("GET", "/test", "Called!");
|
||||||
|
var div = make('<div hx-get="/test" hx-trigger="evt[globalFun(event)]">Not Called</div>');
|
||||||
|
var event = htmx._("makeEvent")('evt');
|
||||||
|
event.bar = false;
|
||||||
|
div.dispatchEvent(event);
|
||||||
|
this.server.respond();
|
||||||
|
div.innerHTML.should.equal("Not Called");
|
||||||
|
event.bar = true;
|
||||||
|
div.dispatchEvent(event);
|
||||||
|
this.server.respond();
|
||||||
|
div.innerHTML.should.equal("Called!");
|
||||||
|
} finally {
|
||||||
|
delete window.globalFun;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('global property event filter works', function(){
|
||||||
|
window.foo = {
|
||||||
|
bar:false
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.server.respondWith("GET", "/test", "Called!");
|
||||||
|
var div = make('<div hx-get="/test" hx-trigger="evt[foo.bar]">Not Called</div>');
|
||||||
|
var event = htmx._("makeEvent")('evt');
|
||||||
|
div.dispatchEvent(event);
|
||||||
|
this.server.respond();
|
||||||
|
div.innerHTML.should.equal("Not Called");
|
||||||
|
foo.bar = true;
|
||||||
|
div.dispatchEvent(event);
|
||||||
|
this.server.respond();
|
||||||
|
div.innerHTML.should.equal("Called!");
|
||||||
|
} finally {
|
||||||
|
delete window.foo;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('global variable filter works', function(){
|
||||||
|
try {
|
||||||
|
this.server.respondWith("GET", "/test", "Called!");
|
||||||
|
var div = make('<div hx-get="/test" hx-trigger="evt[foo]">Not Called</div>');
|
||||||
|
var event = htmx._("makeEvent")('evt');
|
||||||
|
div.dispatchEvent(event);
|
||||||
|
this.server.respond();
|
||||||
|
div.innerHTML.should.equal("Not Called");
|
||||||
|
foo = true;
|
||||||
|
div.dispatchEvent(event);
|
||||||
|
this.server.respond();
|
||||||
|
div.innerHTML.should.equal("Called!");
|
||||||
|
} finally {
|
||||||
|
delete window.foo;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('bad condition issues error', function(){
|
||||||
|
this.server.respondWith("GET", "/test", "Called!");
|
||||||
|
var div = make('<div hx-get="/test" hx-trigger="evt[a.b]">Not Called</div>');
|
||||||
|
var errorEvent = null;
|
||||||
|
var handler = htmx.on("htmx:eventFilter:error", function (event) {
|
||||||
|
errorEvent = event;
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
var event = htmx._("makeEvent")('evt');
|
||||||
|
div.dispatchEvent(event);
|
||||||
|
should.not.equal(null, errorEvent);
|
||||||
|
should.not.equal(null, errorEvent.detail.source);
|
||||||
|
console.log(errorEvent.detail.source);
|
||||||
|
} finally {
|
||||||
|
htmx.off("htmx:eventFilter:error", handler);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
@ -35,11 +35,11 @@ describe("Core htmx tokenizer tests", function(){
|
|||||||
{
|
{
|
||||||
var tokens = tokenize("[code==4||(code==5&&foo==true)]");
|
var tokens = tokenize("[code==4||(code==5&&foo==true)]");
|
||||||
var conditional = htmx._("maybeGenerateConditional")(tokens);
|
var conditional = htmx._("maybeGenerateConditional")(tokens);
|
||||||
console.log(conditional);
|
|
||||||
var func = eval(conditional);
|
var func = eval(conditional);
|
||||||
console.log(func({code: 5, foo: true}));
|
func({code: 5, foo: true}).should.equal(true);
|
||||||
console.log(func({code: 4, foo: false}));
|
func({code: 5, foo: false}).should.equal(false);
|
||||||
console.log(func({code: 3, foo: false}));
|
func({code: 4, foo: false}).should.equal(true);
|
||||||
|
func({code: 3, foo: true}).should.equal(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ title: </> htmx - hx-trigger
|
|||||||
The `hx-trigger` attribute allows you to specify what triggers an AJAX request. A trigger
|
The `hx-trigger` attribute allows you to specify what triggers an AJAX request. A trigger
|
||||||
value can be one of the following:
|
value can be one of the following:
|
||||||
|
|
||||||
* An event name (e.g. "click" or "my-custom-event") followed by a set of event modifiers
|
* An event name (e.g. "click" or "my-custom-event") followed by an event filter and a set of event modifiers
|
||||||
* A polling definition of the form `every <timing declaration>`
|
* A polling definition of the form `every <timing declaration>`
|
||||||
* A comma-separated list of such events
|
* A comma-separated list of such events
|
||||||
|
|
||||||
@ -20,6 +20,35 @@ A standard event, such as `click` can be specified as the trigger like so:
|
|||||||
<div hx-get="/clicked" hx-trigger="click">Click Me</div>
|
<div hx-get="/clicked" hx-trigger="click">Click Me</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Standard Event Filters
|
||||||
|
|
||||||
|
Events can be filtered by enclosing a boolean javascript expression in square brackets after the event name. If
|
||||||
|
this expression evaluates to `true` the event will be triggered, otherwise it will be ignored.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div hx-get="/clicked" hx-trigger="click[ctrlKey]">Control Click Me</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
This event will trigger if a click event is triggered with the `event.ctrlKey` property set to true.
|
||||||
|
|
||||||
|
Conditions can also refer to global functions or state
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div hx-get="/clicked" hx-trigger="click[checkGlobalState()]">Control Click Me</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
And can also be combined using the standard javascript syntax
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div hx-get="/clicked" hx-trigger="click[ctrlKey&&shfitKey]">Control-Shift Click Me</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that all symbols used in the expression will be resolved first against the triggering event, and then next
|
||||||
|
against the global namespace, so `myEvent[foo]` will first look for a property named `foo` on the event, then look
|
||||||
|
for a global symbol with the name `foo`
|
||||||
|
|
||||||
|
#### Standard Event Modifiers
|
||||||
|
|
||||||
Standard events can also have modifiers that change how they behave. The modifiers are:
|
Standard events can also have modifiers that change how they behave. The modifiers are:
|
||||||
|
|
||||||
* `once` - the event will only trigger once (e.g. the first click)
|
* `once` - the event will only trigger once (e.g. the first click)
|
||||||
|
22
www/docs.md
22
www/docs.md
@ -13,6 +13,8 @@ title: </> htmx - high power tools for html
|
|||||||
* [installing](#installing)
|
* [installing](#installing)
|
||||||
* [ajax](#ajax)
|
* [ajax](#ajax)
|
||||||
* [triggers](#triggers)
|
* [triggers](#triggers)
|
||||||
|
* [trigger filters](#trigger-filters)
|
||||||
|
* [trigger modifiers](#trigger-modifiers)
|
||||||
* [special events](#special-events)
|
* [special events](#special-events)
|
||||||
* [polling](#polling)
|
* [polling](#polling)
|
||||||
* [load polling](#load_polling)
|
* [load polling](#load_polling)
|
||||||
@ -143,7 +145,23 @@ Here is a `div` that posts to `/mouse_entered` when a mouse enters it:
|
|||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want a request to only happen once, you can use the `once` modifier for the trigger:
|
#### <a name="trigger-filters"></a> [Trigger Filters](#trigger-filters)
|
||||||
|
|
||||||
|
You may also apply trigger filters by using square brackets after the event name, enclosing a javascript expression that
|
||||||
|
will be evaluated. If the expression evaluates to `true` the event will trigger, otherwise it will not.
|
||||||
|
|
||||||
|
Here is an example that triggers only on a Control-Click of the element
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div hx-get="/clicked" hx-trigger="click[ctrlKey]">Control Click Me</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Properties like `ctrlKey` will be resolved against the triggering event first, then the global scope.
|
||||||
|
|
||||||
|
#### <a name="trigger-modifiers"></a> [Trigger Modifiers](#trigger-modifiers)
|
||||||
|
|
||||||
|
A trigger can also have a few additional modifiers that change its behavior. For example, if you want a request to only
|
||||||
|
happen once, you can use the `once` modifier for the trigger:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<div hx-post="/mouse_entered" hx-trigger="mouseenter once">
|
<div hx-post="/mouse_entered" hx-trigger="mouseenter once">
|
||||||
@ -151,7 +169,7 @@ If you want a request to only happen once, you can use the `once` modifier for t
|
|||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
There are few other modifiers you can use for trigger:
|
Other modifiers you can use for triggers are:
|
||||||
|
|
||||||
* `changed` - only issue a request if the value of the element has changed
|
* `changed` - only issue a request if the value of the element has changed
|
||||||
* `delay:<time interval>` - wait the given amount of time (e.g. `1s`) before
|
* `delay:<time interval>` - wait the given amount of time (e.g. `1s`) before
|
||||||
|
Loading…
x
Reference in New Issue
Block a user