Merge branch 'master' of https://github.com/bigskysoftware/htmx into ws-reconnect

This commit is contained in:
Sven R. Kunze 2021-03-01 21:35:53 +01:00
commit fc57045198
7 changed files with 159 additions and 44 deletions

View File

@ -923,7 +923,7 @@ return (function () {
function shouldCancel(elt) { function shouldCancel(elt) {
return elt.tagName === "FORM" || return elt.tagName === "FORM" ||
(matches(elt, 'input[type="submit"], button') && closest(elt, 'form') !== null) || (matches(elt, 'input[type="submit"], button') && closest(elt, 'form') !== null) ||
(elt.tagName === "A" && elt.href && elt.href.indexOf('#') !== 0); (elt.tagName === "A" && elt.href && elt.getAttribute('href').indexOf('#') !== 0);
} }
function ignoreBoostedAnchorCtrlClick(elt, evt) { function ignoreBoostedAnchorCtrlClick(elt, evt) {
@ -1053,8 +1053,13 @@ return (function () {
} }
function processWebSocketSource(elt, wssSource, retryCount) { function processWebSocketSource(elt, wssSource, retryCount) {
if (wssSource.indexOf("ws:") !== 0 && wssSource.indexOf("wss:") !== 0) { if (wssSource.indexOf("/") == 0) { // complete absolute paths only
wssSource = "wss:" + wssSource; var base_part = location.hostname + (location.port ? ':'+location.port: '');
if (location.protocol == 'https:') {
wssSource = "wss://" + base_part + wssSource;
} else if (location.protocol == 'http:') {
wssSource = "ws://" + base_part + wssSource;
}
} }
var socket = htmx.createWebSocket(wssSource); var socket = htmx.createWebSocket(wssSource);
socket.onerror = function (e) { socket.onerror = function (e) {
@ -1553,22 +1558,21 @@ return (function () {
} }
function addRequestIndicatorClasses(elt) { function addRequestIndicatorClasses(elt) {
mutateRequestIndicatorClasses(elt, "add");
}
function removeRequestIndicatorClasses(elt) {
mutateRequestIndicatorClasses(elt, "remove");
}
function mutateRequestIndicatorClasses(elt, action) {
var indicator = getClosestAttributeValue(elt, 'hx-indicator'); var indicator = getClosestAttributeValue(elt, 'hx-indicator');
if (indicator) { if (indicator) {
var indicators = querySelectorAllExt(elt, indicator); var indicators = querySelectorAllExt(elt, indicator);
} else { } else {
indicators = [elt]; indicators = [elt];
} }
forEach(indicators, function(ic) { forEach(indicators, function (ic) {
ic.classList[action].call(ic.classList, htmx.config.requestClass); ic.classList["add"].call(ic.classList, htmx.config.requestClass);
});
return indicators;
}
function removeRequestIndicatorClasses(indicators) {
forEach(indicators, function (ic) {
ic.classList["remove"].call(ic.classList, htmx.config.requestClass);
}); });
} }
@ -2087,25 +2091,38 @@ return (function () {
triggerErrorEvent(elt, 'htmx:onLoadError', mergeObjects({error:e}, responseInfo)); triggerErrorEvent(elt, 'htmx:onLoadError', mergeObjects({error:e}, responseInfo));
throw e; throw e;
} finally { } finally {
removeRequestIndicatorClasses(elt); removeRequestIndicatorClasses(indicators);
var finalElt = getInternalData(elt).replacedWith || elt; var finalElt = elt;
if (!bodyContains(elt)) {
finalElt = getInternalData(target).replacedWith || target;
}
triggerEvent(finalElt, 'htmx:afterRequest', responseInfo); triggerEvent(finalElt, 'htmx:afterRequest', responseInfo);
triggerEvent(finalElt, 'htmx:afterOnLoad', responseInfo); triggerEvent(finalElt, 'htmx:afterOnLoad', responseInfo);
endRequestLock(); endRequestLock();
} }
} }
xhr.onerror = function () { xhr.onerror = function () {
removeRequestIndicatorClasses(elt); removeRequestIndicatorClasses(indicators);
triggerErrorEvent(elt, 'htmx:afterRequest', responseInfo); var finalElt = elt;
triggerErrorEvent(elt, 'htmx:sendError', responseInfo); if (!bodyContains(elt)) {
finalElt = getInternalData(target).replacedWith || target;
}
triggerErrorEvent(finalElt, 'htmx:afterRequest', responseInfo);
triggerErrorEvent(finalElt, 'htmx:sendError', responseInfo);
endRequestLock(); endRequestLock();
} }
xhr.onabort = function() { xhr.onabort = function() {
removeRequestIndicatorClasses(elt); removeRequestIndicatorClasses(indicators);
var finalElt = elt;
if (!bodyContains(elt)) {
finalElt = getInternalData(target).replacedWith || target;
}
triggerErrorEvent(finalElt, 'htmx:afterRequest', responseInfo);
triggerErrorEvent(finalElt, 'htmx:sendAbort', responseInfo);
endRequestLock(); endRequestLock();
} }
if(!triggerEvent(elt, 'htmx:beforeRequest', responseInfo)) return endRequestLock(); if(!triggerEvent(elt, 'htmx:beforeRequest', responseInfo)) return endRequestLock();
addRequestIndicatorClasses(elt); var indicators = addRequestIndicatorClasses(elt);
forEach(['loadstart', 'loadend', 'progress', 'abort'], function(eventName) { forEach(['loadstart', 'loadend', 'progress', 'abort'], function(eventName) {
forEach([xhr, xhr.upload], function (target) { forEach([xhr, xhr.upload], function (target) {

View File

@ -62,4 +62,16 @@ describe("hx-indicator attribute", function(){
btn.classList.contains("htmx-request").should.equal(false); btn.classList.contains("htmx-request").should.equal(false);
div.classList.contains("htmx-request").should.equal(false); div.classList.contains("htmx-request").should.equal(false);
}); });
it('is removed when initiating element is removed from the DOM', function()
{
this.server.respondWith("GET", "/test", "Clicked!");
var indicator = make('<div id="ind1">Indicator</div>')
var div = make('<div id="d1" hx-target="this" hx-indicator="#ind1"><button id="b1" hx-get="/test">Click Me!</button></div>')
var btn = byId("b1");
btn.click();
indicator.classList.contains("htmx-request").should.equal(true);
this.server.respond();
indicator.classList.contains("htmx-request").should.equal(false);
});
}) })

View File

@ -346,5 +346,25 @@ describe("Core htmx Events", function() {
htmx.off("htmx:afterProcessNode", handler); htmx.off("htmx:afterProcessNode", handler);
} }
}); });
it("htmx:afterRequest is called when targeting a parent div", function () {
var called = false;
var handler = htmx.on("htmx:afterRequest", function (evt) {
called = true;
});
try {
this.server.respondWith("POST", "/test", function (xhr) {
xhr.respond(200, {}, "<button>Bar</button>");
});
var div = make("<div hx-target='this'><button id='b1' hx-post='/test' hx-swap='outerHTML'>Foo</button></div>");
var button = byId('b1');
button.click();
this.server.respond();
should.equal(called, true);
} finally {
htmx.off("htmx:afterRequest", handler);
}
});
}); });

View File

@ -29,25 +29,6 @@ describe("Core htmx perf Tests", function() {
return result; return result;
} }
it("DOM processing should be fast", function(){
this.server.respondWith("GET", "/test", "Clicked!");
// create an entry with a large content string (256k) and see how fast we can write and read it
// to local storage as a single entry
var str = stringRepeat("<div>", 30) + stringRepeat("<div><div><span><button hx-get='/test'> Test Get Button </button></span></div></div>\n", 500) + stringRepeat("</div>", 30);
var start = performance.now();
var stuff = make(str);
var end = performance.now();
var timeInMs = end - start;
// make sure the DOM actually processed
var firstBtn = stuff.querySelector("button");
firstBtn.click();
this.server.respond();
firstBtn.innerHTML.should.equal("Clicked!");
chai.assert(timeInMs < 100, "Should take less than 100ms on most platforms, took: " + timeInMs + "ms");
})
it("history implementation should be fast", function(){ it("history implementation should be fast", function(){
// create an entry with a large content string (256k) and see how fast we can write and read it // create an entry with a large content string (256k) and see how fast we can write and read it

View File

@ -53,11 +53,14 @@
</li> </li>
<li> <li>
<a href="manual/aborting-request.html">Aborting Request Test</a> <a href="manual/aborting-request.html">Aborting Request Test</a>
</li> </li>
<li> <li>
<a href="manual/preload.html">Preload Extension</a> <a href="manual/preload.html">Preload Extension</a>
</li> </li>
</ul> <li>
<a href="manual/manual-perf.html">Manual Perf Test</a>
</li>
</ul>
<h2>Mocha Test Suite</h2> <h2>Mocha Test Suite</h2>
<a href="index.html">[ALL]</a> <a href="index.html">[ALL]</a>

View File

@ -0,0 +1,81 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Mocha Tests</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="../../node_modules/mocha/mocha.css" />
</head>
<body style="padding:20px;font-family: sans-serif">
<div id="mocha"></div>
<script src="../../node_modules/chai/chai.js"></script>
<script src="../../node_modules/mocha/mocha.js"></script>
<script src="../../node_modules/sinon/pkg/sinon.js"></script>
<script src="../../src/htmx.js"></script>
<script class="mocha-init">
mocha.setup('bdd');
mocha.checkLeaks();
should = chai.should();
</script>
<script src="../util/util.js"></script>
<script>
describe("Manual Perf Tests", function() {
beforeEach(function () {
this.server = makeServer();
clearWorkArea();
});
afterEach(function () {
this.server.restore();
clearWorkArea();
});
function stringRepeat(str, num) {
num = Number(num);
var result = '';
while (true) {
if (num & 1) { // (1)
result += str;
}
num >>>= 1; // (2)
if (num <= 0) break;
str += str;
}
return result;
}
it("DOM processing should be fast", function(){
this.server.respondWith("GET", "/test", "Clicked!");
// create an entry with a large content string (256k) and see how fast we can write and read it
// to local storage as a single entry
var str = stringRepeat("<div>", 30) + stringRepeat("<div><div><span><button hx-get='/test'> Test Get Button </button></span></div></div>\n", 500) + stringRepeat("</div>", 30);
var start = performance.now();
var stuff = make(str);
var end = performance.now();
var timeInMs = end - start;
// make sure the DOM actually processed
var firstBtn = stuff.querySelector("button");
firstBtn.click();
this.server.respond();
firstBtn.innerHTML.should.equal("Clicked!");
chai.assert(timeInMs < 100, "Should take less than 100ms on most platforms, took: " + timeInMs + "ms");
})
});
</script>
<script class="mocha-exec">
mocha.run();
</script>
<em>Work Area</em>
<hr/>
<div id="work-area" hx-history-elt>
</div>
</body>
</html>

View File

@ -8,7 +8,8 @@ title: </> htmx - hx-ws
The `hx-ws` allows you to work with [Web Sockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications) The `hx-ws` allows you to work with [Web Sockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications)
directly from HTML. The value of the attribute can be one or more of the following, separated by commas: directly from HTML. The value of the attribute can be one or more of the following, separated by commas:
* `connect:<url>` or `connect:<prefix>:<url>` - A URL to establish an `WebSocket` connection against. Prefixes `ws` or `wss` can optionally be specified. If not specified, HTMX defaults to add the `wss` prefix to the url. * `connect:<url>` or `connect:<prefix>:<url>` - A URL to establish an `WebSocket` connection against.
* Prefixes `ws` or `wss` can optionally be specified. If not specified, HTMX defaults to add the location's scheme-type, host and port to have browsers send cookies via websockets.
* `send` - Sends a message to the nearest websocket based on the trigger value for the element (either the natural event * `send` - Sends a message to the nearest websocket based on the trigger value for the element (either the natural event
of the event specified by [`hx-trigger`]) of the event specified by [`hx-trigger`])