Merge pull request #39 from rschroll/multi-trigger

Allow multiple triggers
This commit is contained in:
chg20 2020-05-23 18:54:44 -07:00 committed by GitHub
commit 3555ef1130
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 128 additions and 53 deletions

View File

@ -471,44 +471,46 @@ var htmx = htmx || (function () {
}
}
function getTriggerSpec(elt) {
function getTriggerSpecs(elt) {
var triggerSpec = {
"trigger" : "click"
}
var explicitTrigger = getAttributeValue(elt, 'hx-trigger');
if (explicitTrigger) {
var tokens = splitOnWhitespace(explicitTrigger);
if (tokens.length > 0) {
var trigger = tokens[0];
if (trigger === "every") {
triggerSpec.pollInterval = parseInterval(tokens[1]);
} else if (trigger.indexOf("sse:") === 0) {
triggerSpec.sseEvent = trigger.substr(4);
} else {
triggerSpec['trigger'] = trigger;
for (var i = 1; i < tokens.length; i++) {
var token = tokens[i].trim();
if (token === "changed") {
triggerSpec.changed = true;
}
if (token === "once") {
triggerSpec.once = true;
}
if (token.indexOf("delay:") === 0) {
triggerSpec.delay = parseInterval(token.substr(6));
}
var triggerSpecs = explicitTrigger.split(',').map(function(triggerString) {
var tokens = splitOnWhitespace(triggerString.trim());
var trigger = tokens[0]; // splitOnWhitespace returns at least one element
if (!trigger)
return null;
if (trigger === "every")
return {trigger: 'every', pollInterval: parseInterval(tokens[1])};
if (trigger.indexOf("sse:") === 0)
return {trigger: 'sse', sseEvent: trigger.substr(4)};
var triggerSpec = {trigger: trigger};
for (var i = 1; i < tokens.length; i++) {
var token = tokens[i].trim();
if (token === "changed") {
triggerSpec.changed = true;
}
if (token === "once") {
triggerSpec.once = true;
}
if (token.indexOf("delay:") === 0) {
triggerSpec.delay = parseInterval(token.substr(6));
}
}
}
} else {
if (matches(elt, 'form')) {
triggerSpec['trigger'] = 'submit';
} else if (matches(elt, 'input, textarea, select')) {
triggerSpec['trigger'] = 'change';
}
return triggerSpec;
}).filter(x => x !== null);
if (triggerSpecs.length)
return triggerSpecs;
}
return triggerSpec;
if (matches(elt, 'form'))
return [{trigger: 'submit'}];
if (matches(elt, 'input, textarea, select'))
return [{trigger: 'change'}];
return [{trigger: 'click'}];
}
function parseClassOperation(trimmedValue) {
@ -581,7 +583,7 @@ var htmx = htmx || (function () {
getRawAttribute(elt,'href').indexOf("#") !== 0;
}
function boostElement(elt, nodeData, triggerSpec) {
function boostElement(elt, nodeData, triggerSpecs) {
if ((elt.tagName === "A" && isLocalLink(elt)) || elt.tagName === "FORM") {
nodeData.boosted = true;
var verb, path;
@ -593,7 +595,9 @@ var htmx = htmx || (function () {
verb = rawAttribute ? rawAttribute.toLowerCase() : "get";
path = getRawAttribute(elt, 'action');
}
addEventListener(elt, verb, path, nodeData, triggerSpec, true);
triggerSpecs.forEach(function(triggerSpec) {
addEventListener(elt, verb, path, nodeData, triggerSpec, true);
});
}
}
@ -723,7 +727,7 @@ var htmx = htmx || (function () {
}
}
function processVerbs(elt, nodeData, triggerSpec) {
function processVerbs(elt, nodeData, triggerSpecs) {
var explicitAction = false;
forEach(VERBS, function (verb) {
var path = getAttributeValue(elt, 'hx-' + verb);
@ -731,19 +735,21 @@ var htmx = htmx || (function () {
explicitAction = true;
nodeData.path = path;
nodeData.verb = verb;
if (triggerSpec.sseEvent) {
processSSETrigger(elt, verb, path, triggerSpec.sseEvent);
} else if (triggerSpec.trigger === "revealed") {
initScrollHandler();
maybeReveal(elt);
} else if (triggerSpec.trigger === "load") {
loadImmediately(elt, verb, path, nodeData, triggerSpec.delay);
} else if (triggerSpec.pollInterval) {
nodeData.polling = true;
processPolling(elt, verb, path, triggerSpec.pollInterval);
} else {
addEventListener(elt, verb, path, nodeData, triggerSpec);
}
triggerSpecs.forEach(function(triggerSpec) {
if (triggerSpec.sseEvent) {
processSSETrigger(elt, verb, path, triggerSpec.sseEvent);
} else if (triggerSpec.trigger === "revealed") {
initScrollHandler();
maybeReveal(elt);
} else if (triggerSpec.trigger === "load") {
loadImmediately(elt, verb, path, nodeData, triggerSpec.delay);
} else if (triggerSpec.pollInterval) {
nodeData.polling = true;
processPolling(elt, verb, path, triggerSpec.pollInterval);
} else {
addEventListener(elt, verb, path, nodeData, triggerSpec);
}
});
}
});
return explicitAction;
@ -754,11 +760,11 @@ var htmx = htmx || (function () {
if (!nodeData.processed) {
nodeData.processed = true;
var triggerSpec = getTriggerSpec(elt);
var explicitAction = processVerbs(elt, nodeData, triggerSpec);
var triggerSpecs = getTriggerSpecs(elt);
var explicitAction = processVerbs(elt, nodeData, triggerSpecs);
if (!explicitAction && getClosestAttributeValue(elt, "hx-boost") === "true") {
boostElement(elt, nodeData, triggerSpec);
boostElement(elt, nodeData, triggerSpecs);
}
var sseSrc = getAttributeValue(elt, 'hx-sse-source');
if (sseSrc) {

View File

@ -97,4 +97,62 @@ describe("hx-trigger attribute", function(){
form.innerHTML.should.equal("Clicked!");
});
it('works with multiple events', function()
{
var requests = 0;
this.server.respondWith("GET", "/test", function (xhr) {
requests++;
xhr.respond(200, {}, "Requests: " + requests);
});
var div = make('<div hx-trigger="load,click" hx-get="/test">Requests: 0</div>');
div.innerHTML.should.equal("Requests: 0");
this.server.respond();
div.innerHTML.should.equal("Requests: 1");
div.click();
this.server.respond();
div.innerHTML.should.equal("Requests: 2");
});
var specExamples = {
"": [{trigger: 'click'}],
"every 1s": [{trigger: 'every', pollInterval: 1000}],
"sse:/foo": [{trigger: 'sse', sseEvent: '/foo'}],
"click": [{trigger: 'click'}],
"customEvent": [{trigger: 'customEvent'}],
"event changed": [{trigger: 'event', changed: true}],
"event once": [{trigger: 'event', once: true}],
"event delay:1s": [{trigger: 'event', delay: 1000}],
"event changed once delay:1s": [{trigger: 'event', changed: true, once: true, delay: 1000}],
"event1,event2": [{trigger: 'event1'}, {trigger: 'event2'}],
"event1, event2": [{trigger: 'event1'}, {trigger: 'event2'}],
"event1 once, event2 changed": [{trigger: 'event1', once: true}, {trigger: 'event2', changed: true}],
"event1,": [{trigger: 'event1'}],
",event1": [{trigger: 'event1'}],
" ": [{trigger: 'click'}],
",": [{trigger: 'click'}]
}
for (const specString in specExamples) {
it(`parses "${specString}"`, function()
{
var div = make(`<div hx-trigger="${specString}"></div>`);
var spec = htmx._('getTriggerSpecs')(div);
spec.should.deep.equal(specExamples[specString]);
});
}
it('sets default trigger for forms', function()
{
var form = make('<form></form>');
var spec = htmx._('getTriggerSpecs')(form);
spec.should.deep.equal([{trigger: 'submit'}]);
})
it('sets default trigger for form elements', function()
{
var form = make('<input></input>');
var spec = htmx._('getTriggerSpecs')(form);
spec.should.deep.equal([{trigger: 'change'}]);
})
})

View File

@ -8,9 +8,10 @@ title: </> htmx - hx-trigger
The `hx-trigger` attribute allows you to specify what triggers an AJAX request. A trigger
value can be one of the following:
* An event name (e.g. "click") followed by a set of event modifiers
* An event name (e.g. "click" or "my-custom-event") followed by a set of event modifiers
* A polling definition of the form `every <timing declaration>`
* An SSE event declaration of the form `sse:<event name>`
* A comma-separated list of such events
### Standard Events
@ -74,6 +75,14 @@ Here is an example:
This example establishes an SSE connection to the `event_stream` end point which then triggers
a `GET` to the `/chatroom` url whenever the `chatter` event is seen.
### Multiple Triggers
Multiple triggers can be provided, seprarated by commas. Each trigger gets its own options.
```html
<div hx-get="/news" hx-trigger="load, click delay:1s"></div>
```
This example will load `/news` immediate on the page load, and then again with a delay of one second after each click.
### Notes
* `hx-trigger` is not inherited

View File

@ -171,6 +171,8 @@ You can use these two attributes to implement a common UX pattern, [Active Searc
This input will issue a request 500 milliseconds after a key up event if the input has been changed and inserts the results
into the `div` with the id `search-results`.
Multiple triggers can be specified in the [hx-trigger](/attributes/hx-trigger) attribute, separated by commas.
#### <a name="special-events"></a> [Special Events](#special-events)
htmx provides a few special events for use in [hx-trigger](/attributes/hx-trigger):