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) {
return elt.tagName === "FORM" ||
(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) {
@ -1053,8 +1053,13 @@ return (function () {
}
function processWebSocketSource(elt, wssSource, retryCount) {
if (wssSource.indexOf("ws:") !== 0 && wssSource.indexOf("wss:") !== 0) {
wssSource = "wss:" + wssSource;
if (wssSource.indexOf("/") == 0) { // complete absolute paths only
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);
socket.onerror = function (e) {
@ -1553,22 +1558,21 @@ return (function () {
}
function addRequestIndicatorClasses(elt) {
mutateRequestIndicatorClasses(elt, "add");
}
function removeRequestIndicatorClasses(elt) {
mutateRequestIndicatorClasses(elt, "remove");
}
function mutateRequestIndicatorClasses(elt, action) {
var indicator = getClosestAttributeValue(elt, 'hx-indicator');
if (indicator) {
var indicators = querySelectorAllExt(elt, indicator);
} else {
indicators = [elt];
}
forEach(indicators, function(ic) {
ic.classList[action].call(ic.classList, htmx.config.requestClass);
forEach(indicators, function (ic) {
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));
throw e;
} finally {
removeRequestIndicatorClasses(elt);
var finalElt = getInternalData(elt).replacedWith || elt;
removeRequestIndicatorClasses(indicators);
var finalElt = elt;
if (!bodyContains(elt)) {
finalElt = getInternalData(target).replacedWith || target;
}
triggerEvent(finalElt, 'htmx:afterRequest', responseInfo);
triggerEvent(finalElt, 'htmx:afterOnLoad', responseInfo);
endRequestLock();
}
}
xhr.onerror = function () {
removeRequestIndicatorClasses(elt);
triggerErrorEvent(elt, 'htmx:afterRequest', responseInfo);
triggerErrorEvent(elt, 'htmx:sendError', responseInfo);
removeRequestIndicatorClasses(indicators);
var finalElt = elt;
if (!bodyContains(elt)) {
finalElt = getInternalData(target).replacedWith || target;
}
triggerErrorEvent(finalElt, 'htmx:afterRequest', responseInfo);
triggerErrorEvent(finalElt, 'htmx:sendError', responseInfo);
endRequestLock();
}
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();
}
if(!triggerEvent(elt, 'htmx:beforeRequest', responseInfo)) return endRequestLock();
addRequestIndicatorClasses(elt);
var indicators = addRequestIndicatorClasses(elt);
forEach(['loadstart', 'loadend', 'progress', 'abort'], function(eventName) {
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);
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);
}
});
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;
}
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(){
// 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>
<a href="manual/aborting-request.html">Aborting Request Test</a>
</li>
<li>
</li>
<li>
<a href="manual/preload.html">Preload Extension</a>
</li>
</ul>
</li>
<li>
<a href="manual/manual-perf.html">Manual Perf Test</a>
</li>
</ul>
<h2>Mocha Test Suite</h2>
<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)
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
of the event specified by [`hx-trigger`])