mirror of
https://github.com/bigskysoftware/htmx.git
synced 2025-09-27 21:13:31 +00:00
Merge branch 'dev' into v2.0v2.0
This commit is contained in:
commit
41e9ce3593
108
src/htmx.js
108
src/htmx.js
@ -120,24 +120,40 @@ return (function () {
|
||||
return "[hx-" + verb + "], [data-hx-" + verb + "]"
|
||||
}).join(", ");
|
||||
|
||||
var HEAD_TAG_REGEX = makeTagRegEx('head'),
|
||||
TITLE_TAG_REGEX = makeTagRegEx('title'),
|
||||
SVG_TAGS_REGEX = makeTagRegEx('svg', true);
|
||||
|
||||
//====================================================================
|
||||
// Utilities
|
||||
//====================================================================
|
||||
|
||||
/**
|
||||
* @param {string} tag
|
||||
* @param {boolean} global
|
||||
* @returns {RegExp}
|
||||
*/
|
||||
function makeTagRegEx(tag, global = false) {
|
||||
return new RegExp(`<${tag}(\\s[^>]*>|>)([\\s\\S]*?)<\\/${tag}>`,
|
||||
global ? 'gim' : 'im');
|
||||
}
|
||||
|
||||
function parseInterval(str) {
|
||||
if (str == undefined) {
|
||||
return undefined
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let interval = NaN;
|
||||
if (str.slice(-2) == "ms") {
|
||||
return parseFloat(str.slice(0,-2)) || undefined
|
||||
interval = parseFloat(str.slice(0, -2));
|
||||
} else if (str.slice(-1) == "s") {
|
||||
interval = parseFloat(str.slice(0, -1)) * 1000;
|
||||
} else if (str.slice(-1) == "m") {
|
||||
interval = parseFloat(str.slice(0, -1)) * 1000 * 60;
|
||||
} else {
|
||||
interval = parseFloat(str);
|
||||
}
|
||||
if (str.slice(-1) == "s") {
|
||||
return (parseFloat(str.slice(0,-1)) * 1000) || undefined
|
||||
}
|
||||
if (str.slice(-1) == "m") {
|
||||
return (parseFloat(str.slice(0,-1)) * 1000 * 60) || undefined
|
||||
}
|
||||
return parseFloat(str) || undefined
|
||||
return isNaN(interval) ? undefined : interval;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -283,38 +299,41 @@ return (function () {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} resp
|
||||
* @param {string} response
|
||||
* @returns {Element}
|
||||
*/
|
||||
function makeFragment(resp) {
|
||||
var partialResponse = !aFullPageResponse(resp);
|
||||
function makeFragment(response) {
|
||||
var partialResponse = !aFullPageResponse(response);
|
||||
var startTag = getStartTag(response);
|
||||
var content = response;
|
||||
if (startTag === 'head') {
|
||||
content = content.replace(HEAD_TAG_REGEX, '');
|
||||
}
|
||||
if (htmx.config.useTemplateFragments && partialResponse) {
|
||||
var documentFragment = parseHTML("<body><template>" + resp + "</template></body>", 0);
|
||||
var documentFragment = parseHTML("<body><template>" + content + "</template></body>", 0);
|
||||
// @ts-ignore type mismatch between DocumentFragment and Element.
|
||||
// TODO: Are these close enough for htmx to use interchangeably?
|
||||
return documentFragment.querySelector('template').content;
|
||||
} else {
|
||||
var startTag = getStartTag(resp);
|
||||
switch (startTag) {
|
||||
case "thead":
|
||||
case "tbody":
|
||||
case "tfoot":
|
||||
case "colgroup":
|
||||
case "caption":
|
||||
return parseHTML("<table>" + resp + "</table>", 1);
|
||||
case "col":
|
||||
return parseHTML("<table><colgroup>" + resp + "</colgroup></table>", 2);
|
||||
case "tr":
|
||||
return parseHTML("<table><tbody>" + resp + "</tbody></table>", 2);
|
||||
case "td":
|
||||
case "th":
|
||||
return parseHTML("<table><tbody><tr>" + resp + "</tr></tbody></table>", 3);
|
||||
case "script":
|
||||
case "style":
|
||||
return parseHTML("<div>" + resp + "</div>", 1);
|
||||
default:
|
||||
return parseHTML(resp, 0);
|
||||
}
|
||||
}
|
||||
switch (startTag) {
|
||||
case "thead":
|
||||
case "tbody":
|
||||
case "tfoot":
|
||||
case "colgroup":
|
||||
case "caption":
|
||||
return parseHTML("<table>" + content + "</table>", 1);
|
||||
case "col":
|
||||
return parseHTML("<table><colgroup>" + content + "</colgroup></table>", 2);
|
||||
case "tr":
|
||||
return parseHTML("<table><tbody>" + content + "</tbody></table>", 2);
|
||||
case "td":
|
||||
case "th":
|
||||
return parseHTML("<table><tbody><tr>" + content + "</tr></tbody></table>", 3);
|
||||
case "script":
|
||||
case "style":
|
||||
return parseHTML("<div>" + content + "</div>", 1);
|
||||
default:
|
||||
return parseHTML(content, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1096,9 +1115,8 @@ return (function () {
|
||||
|
||||
function findTitle(content) {
|
||||
if (content.indexOf('<title') > -1) {
|
||||
var contentWithSvgsRemoved = content.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, '');
|
||||
var result = contentWithSvgsRemoved.match(/<title(\s[^>]*>|>)([\s\S]*?)<\/title>/im);
|
||||
|
||||
var contentWithSvgsRemoved = content.replace(SVG_TAGS_REGEX, '');
|
||||
var result = contentWithSvgsRemoved.match(TITLE_TAG_REGEX);
|
||||
if (result) {
|
||||
return result[2];
|
||||
}
|
||||
@ -1519,14 +1537,14 @@ return (function () {
|
||||
return;
|
||||
}
|
||||
|
||||
if (triggerSpec.throttle) {
|
||||
if (triggerSpec.throttle > 0) {
|
||||
if (!elementData.throttle) {
|
||||
handler(elt, evt);
|
||||
elementData.throttle = setTimeout(function () {
|
||||
elementData.throttle = null;
|
||||
}, triggerSpec.throttle);
|
||||
}
|
||||
} else if (triggerSpec.delay) {
|
||||
} else if (triggerSpec.delay > 0) {
|
||||
elementData.delayed = setTimeout(function() { handler(elt, evt) }, triggerSpec.delay);
|
||||
} else {
|
||||
triggerEvent(elt, 'htmx:trigger')
|
||||
@ -1587,7 +1605,7 @@ return (function () {
|
||||
handler(elt);
|
||||
}
|
||||
}
|
||||
if (delay) {
|
||||
if (delay > 0) {
|
||||
setTimeout(load, delay);
|
||||
} else {
|
||||
load();
|
||||
@ -1644,7 +1662,7 @@ return (function () {
|
||||
if (!maybeFilterEvent(triggerSpec, elt, makeEvent("load", {elt: elt}))) {
|
||||
loadImmediately(elt, handler, nodeData, triggerSpec.delay);
|
||||
}
|
||||
} else if (triggerSpec.pollInterval) {
|
||||
} else if (triggerSpec.pollInterval > 0) {
|
||||
nodeData.polling = true;
|
||||
processPolling(elt, handler, triggerSpec);
|
||||
} else {
|
||||
@ -3222,7 +3240,11 @@ return (function () {
|
||||
}
|
||||
|
||||
if (hasHeader(xhr,/HX-Retarget:/i)) {
|
||||
responseInfo.target = getDocument().querySelector(xhr.getResponseHeader("HX-Retarget"));
|
||||
if (xhr.getResponseHeader("HX-Retarget") === "this") {
|
||||
responseInfo.target = elt;
|
||||
} else {
|
||||
responseInfo.target = querySelectorExt(elt, xhr.getResponseHeader("HX-Retarget"));
|
||||
}
|
||||
}
|
||||
|
||||
var historyUpdate = determineHistoryUpdates(elt, responseInfo);
|
||||
|
@ -197,27 +197,43 @@ describe("hx-swap attribute", function(){
|
||||
swapSpec(make("<div hx-swap='innerHTML'/>")).swapDelay.should.equal(0)
|
||||
swapSpec(make("<div hx-swap='innerHTML'/>")).settleDelay.should.equal(0) // set to 0 in tests
|
||||
swapSpec(make("<div hx-swap='innerHTML swap:10'/>")).swapDelay.should.equal(10)
|
||||
swapSpec(make("<div hx-swap='innerHTML swap:0'/>")).swapDelay.should.equal(0)
|
||||
swapSpec(make("<div hx-swap='innerHTML swap:0ms'/>")).swapDelay.should.equal(0)
|
||||
swapSpec(make("<div hx-swap='innerHTML settle:10'/>")).settleDelay.should.equal(10)
|
||||
swapSpec(make("<div hx-swap='innerHTML settle:0'/>")).settleDelay.should.equal(0)
|
||||
swapSpec(make("<div hx-swap='innerHTML settle:0s'/>")).settleDelay.should.equal(0)
|
||||
swapSpec(make("<div hx-swap='innerHTML swap:10 settle:11'/>")).swapDelay.should.equal(10)
|
||||
swapSpec(make("<div hx-swap='innerHTML swap:10 settle:11'/>")).settleDelay.should.equal(11)
|
||||
swapSpec(make("<div hx-swap='innerHTML settle:11 swap:10'/>")).swapDelay.should.equal(10)
|
||||
swapSpec(make("<div hx-swap='innerHTML settle:11 swap:10'/>")).settleDelay.should.equal(11)
|
||||
swapSpec(make("<div hx-swap='innerHTML settle:0 swap:0'/>")).settleDelay.should.equal(0)
|
||||
swapSpec(make("<div hx-swap='innerHTML settle:0 swap:0'/>")).settleDelay.should.equal(0)
|
||||
swapSpec(make("<div hx-swap='innerHTML settle:0s swap:0ms'/>")).settleDelay.should.equal(0)
|
||||
swapSpec(make("<div hx-swap='innerHTML settle:0s swap:0ms'/>")).settleDelay.should.equal(0)
|
||||
swapSpec(make("<div hx-swap='innerHTML nonsense settle:11 swap:10'/>")).settleDelay.should.equal(11)
|
||||
swapSpec(make("<div hx-swap='innerHTML nonsense settle:11 swap:10 '/>")).settleDelay.should.equal(11)
|
||||
|
||||
|
||||
swapSpec(make("<div hx-swap='swap:10'/>")).swapStyle.should.equal("innerHTML")
|
||||
swapSpec(make("<div hx-swap='swap:10'/>")).swapDelay.should.equal(10)
|
||||
swapSpec(make("<div hx-swap='swap:0'/>")).swapDelay.should.equal(0);
|
||||
swapSpec(make("<div hx-swap='swap:0s'/>")).swapDelay.should.equal(0);
|
||||
|
||||
swapSpec(make("<div hx-swap='settle:10'/>")).swapStyle.should.equal("innerHTML")
|
||||
swapSpec(make("<div hx-swap='settle:10'/>")).settleDelay.should.equal(10)
|
||||
|
||||
swapSpec(make("<div hx-swap='settle:0'/>")).settleDelay.should.equal(0)
|
||||
swapSpec(make("<div hx-swap='settle:0s'/>")).settleDelay.should.equal(0)
|
||||
|
||||
swapSpec(make("<div hx-swap='swap:10 settle:11'/>")).swapStyle.should.equal("innerHTML")
|
||||
swapSpec(make("<div hx-swap='swap:10 settle:11'/>")).swapDelay.should.equal(10)
|
||||
swapSpec(make("<div hx-swap='swap:10 settle:11'/>")).settleDelay.should.equal(11)
|
||||
swapSpec(make("<div hx-swap='swap:0s settle:0'/>")).swapDelay.should.equal(0)
|
||||
swapSpec(make("<div hx-swap='swap:0s settle:0'/>")).settleDelay.should.equal(0)
|
||||
|
||||
swapSpec(make("<div hx-swap='settle:11 swap:10'/>")).swapStyle.should.equal("innerHTML")
|
||||
swapSpec(make("<div hx-swap='settle:11 swap:10'/>")).swapDelay.should.equal(10)
|
||||
swapSpec(make("<div hx-swap='settle:11 swap:10'/>")).settleDelay.should.equal(11)
|
||||
swapSpec(make("<div hx-swap='settle:0s swap:10'/>")).swapDelay.should.equal(10)
|
||||
swapSpec(make("<div hx-swap='settle:0s swap:10'/>")).settleDelay.should.equal(0)
|
||||
|
||||
swapSpec(make("<div hx-swap='customstyle settle:11 swap:10'/>")).swapStyle.should.equal("customstyle")
|
||||
})
|
||||
@ -234,6 +250,17 @@ describe("hx-swap attribute", function(){
|
||||
}, 30);
|
||||
});
|
||||
|
||||
it("works immediately with no swap delay", function (done) {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var div = make(
|
||||
"<div hx-get='/test' hx-swap='innerHTML swap:0ms'></div>"
|
||||
);
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("Clicked!");
|
||||
done();
|
||||
});
|
||||
|
||||
it('works with a settle delay', function(done) {
|
||||
this.server.respondWith("GET", "/test", "<div id='d1' class='foo' hx-get='/test' hx-swap='outerHTML settle:10ms'></div>");
|
||||
var div = make("<div id='d1' hx-get='/test' hx-swap='outerHTML settle:10ms'></div>");
|
||||
@ -246,6 +273,24 @@ describe("hx-swap attribute", function(){
|
||||
}, 30);
|
||||
});
|
||||
|
||||
it("works with no settle delay", function (done) {
|
||||
this.server.respondWith(
|
||||
"GET",
|
||||
"/test",
|
||||
"<div id='d1' class='foo' hx-get='/test' hx-swap='outerHTML settle:0ms'></div>"
|
||||
);
|
||||
var div = make(
|
||||
"<div id='d1' hx-get='/test' hx-swap='outerHTML settle:0ms'></div>"
|
||||
);
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.classList.contains("foo").should.equal(false);
|
||||
setTimeout(function () {
|
||||
byId("d1").classList.contains("foo").should.equal(true);
|
||||
done();
|
||||
}, 30);
|
||||
});
|
||||
|
||||
it('swap outerHTML properly w/ data-* prefix', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", '<a id="a1" data-hx-get="/test2">Click Me</a>');
|
||||
|
@ -211,14 +211,20 @@ describe("hx-trigger attribute", function(){
|
||||
var specExamples = {
|
||||
"": [{trigger: 'click'}],
|
||||
"every 1s": [{trigger: 'every', pollInterval: 1000}],
|
||||
"every 0s": [{trigger: 'every', pollInterval: 0}],
|
||||
"every 0ms": [{trigger: 'every', pollInterval: 0}],
|
||||
"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 throttle:1s": [{trigger: 'event', throttle: 1000}],
|
||||
"event delay:1s, foo": [{trigger: 'event', delay: 1000}, {trigger: 'foo'}],
|
||||
"event throttle:0s": [{trigger: 'event', throttle: 0}],
|
||||
"event throttle:0ms": [{trigger: 'event', throttle: 0}],
|
||||
"event throttle:1s, foo": [{trigger: 'event', throttle: 1000}, {trigger: 'foo'}],
|
||||
"event delay:1s": [{trigger: 'event', delay: 1000}],
|
||||
"event delay:1s, foo": [{trigger: 'event', delay: 1000}, {trigger: 'foo'}],
|
||||
"event delay:0s, foo": [{trigger: 'event', delay: 0}, {trigger: 'foo'}],
|
||||
"event delay:0ms, foo": [{trigger: 'event', delay: 0}, {trigger: 'foo'}],
|
||||
"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'}],
|
||||
@ -678,6 +684,37 @@ describe("hx-trigger attribute", function(){
|
||||
}, 50);
|
||||
});
|
||||
|
||||
it("A throttle of 0 does not multiple requests from happening", function (done) {
|
||||
var requests = 0;
|
||||
var server = this.server;
|
||||
server.respondWith("GET", "/test", function (xhr) {
|
||||
requests++;
|
||||
xhr.respond(200, {}, "Requests: " + requests);
|
||||
});
|
||||
server.respondWith("GET", "/bar", "bar");
|
||||
var div = make(
|
||||
"<div hx-trigger='click throttle:0ms' hx-get='/test'></div>"
|
||||
);
|
||||
|
||||
div.click();
|
||||
server.respond();
|
||||
div.innerText.should.equal("Requests: 1");
|
||||
|
||||
div.click();
|
||||
server.respond();
|
||||
div.innerText.should.equal("Requests: 2");
|
||||
|
||||
div.click();
|
||||
server.respond();
|
||||
div.innerText.should.equal("Requests: 3");
|
||||
|
||||
div.click();
|
||||
server.respond();
|
||||
div.innerText.should.equal("Requests: 4");
|
||||
|
||||
done()
|
||||
});
|
||||
|
||||
it('delay delays the request', function(done)
|
||||
{
|
||||
var requests = 0;
|
||||
@ -714,6 +751,37 @@ describe("hx-trigger attribute", function(){
|
||||
}, 50);
|
||||
});
|
||||
|
||||
it("A 0 delay does not delay the request", function (done) {
|
||||
var requests = 0;
|
||||
this.server.respondWith("GET", "/test", function (xhr) {
|
||||
requests++;
|
||||
xhr.respond(200, {}, "Requests: " + requests);
|
||||
});
|
||||
this.server.respondWith("GET", "/bar", "bar");
|
||||
var div = make(
|
||||
"<div hx-trigger='click delay:0ms' hx-get='/test'></div>"
|
||||
);
|
||||
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("Requests: 1");
|
||||
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("Requests: 2");
|
||||
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("Requests: 3");
|
||||
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("Requests: 4");
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
it('requests are queued with last one winning by default', function()
|
||||
{
|
||||
var requests = 0;
|
||||
|
@ -66,9 +66,14 @@ describe("Core htmx internals Tests", function() {
|
||||
it("handles parseInterval correctly", function() {
|
||||
chai.expect(htmx.parseInterval("1ms")).to.be.equal(1);
|
||||
chai.expect(htmx.parseInterval("300ms")).to.be.equal(300);
|
||||
chai.expect(htmx.parseInterval("1s")).to.be.equal(1000)
|
||||
chai.expect(htmx.parseInterval("1.5s")).to.be.equal(1500)
|
||||
chai.expect(htmx.parseInterval("2s")).to.be.equal(2000)
|
||||
chai.expect(htmx.parseInterval("1s")).to.be.equal(1000);
|
||||
chai.expect(htmx.parseInterval("1.5s")).to.be.equal(1500);
|
||||
chai.expect(htmx.parseInterval("2s")).to.be.equal(2000);
|
||||
chai.expect(htmx.parseInterval("0ms")).to.be.equal(0);
|
||||
chai.expect(htmx.parseInterval("0s")).to.be.equal(0);
|
||||
chai.expect(htmx.parseInterval("0m")).to.be.equal(0);
|
||||
chai.expect(htmx.parseInterval("0")).to.be.equal(0);
|
||||
chai.expect(htmx.parseInterval("5")).to.be.equal(5);
|
||||
|
||||
chai.expect(htmx.parseInterval(null)).to.be.undefined
|
||||
chai.expect(htmx.parseInterval("")).to.be.undefined
|
||||
|
@ -0,0 +1,8 @@
|
||||
<head>
|
||||
<title>Index content</title>
|
||||
</head>
|
||||
<h1># Index</h1>
|
||||
<ul>
|
||||
<li><code><title></code> <b>should not</b> spawn inside <code><main></code></li>
|
||||
<li><code><title></code> <b>should be</b> <em>index content</em></li>
|
||||
</ul>
|
41
test/manual/hxboost_partial_template_parsing/index.html
Normal file
41
test/manual/hxboost_partial_template_parsing/index.html
Normal file
@ -0,0 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" hx-preserve="true">
|
||||
<meta name="htmx-config" content='{"useTemplateFragments": true}' hx-preserve="true">
|
||||
|
||||
<title>Index content</title>
|
||||
|
||||
<style hx-preserve="true">
|
||||
* {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 24px;
|
||||
}
|
||||
code {
|
||||
font-family: monospace;
|
||||
font-size: 20px;
|
||||
}
|
||||
hr { border: 1px solid black; }
|
||||
</style>
|
||||
|
||||
<script src="../../../src/htmx.js" hx-preserve="true"></script>
|
||||
<script src="../../../src/ext/head-support.js" hx-preserve="true"></script>
|
||||
</head>
|
||||
<body hx-ext="head-support" hx-boost="true">
|
||||
<header hx-push-url="false" hx-target="main" hx-swap="innerHTML">
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="./index-partial.html">Index</a></li>
|
||||
<li><a href="./other-content.html">See other content</a></li>
|
||||
</ul>
|
||||
</nav><hr>
|
||||
</header>
|
||||
<main>
|
||||
<h1># Index</h1>
|
||||
<ul>
|
||||
<li><code><title></code> <b>should not</b> spawn inside <code><main></code></li>
|
||||
<li><code><title></code> <b>should be</b> <em>index content</em></li>
|
||||
</ul>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,16 @@
|
||||
<head>
|
||||
<title>Other content</title>
|
||||
<style>
|
||||
body {
|
||||
background: lightgreen;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<h1># Swapped content</h1>
|
||||
<ul>
|
||||
<li><code><title></code> and <style>
|
||||
<b>should not</b> spawn inside <code><main></code></li>
|
||||
<li><code><title></code> <b>should be</b> <em>other content</em></li>
|
||||
<li>Background <b>should be</b> green</li>
|
||||
</ul>
|
||||
|
@ -41,6 +41,7 @@
|
||||
<ul>
|
||||
<li><a href="hxboost_relative_resources">Relative Resources</a></li>
|
||||
<li><a href="hxboost_template_parsing">Template Parsing</a></li>
|
||||
<li><a href="hxboost_partial_template_parsing">Partial Template Parsing</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
Loading…
x
Reference in New Issue
Block a user