···11-//==========================================================
22-// head-support.js
33-//
44-// An extension to htmx 1.0 to add head tag merging.
55-//==========================================================
66-(function(){
77-88- var api = null;
99-1010- function log() {
1111- //console.log(arguments);
1212- }
1313-1414- function mergeHead(newContent, defaultMergeStrategy) {
1515-1616- if (newContent && newContent.indexOf('<head') > -1) {
1717- const htmlDoc = document.createElement("html");
1818- // remove svgs to avoid conflicts
1919- var contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, '');
2020- // extract head tag
2121- var headTag = contentWithSvgsRemoved.match(/(<head(\s[^>]*>|>)([\s\S]*?)<\/head>)/im);
2222-2323- // if the head tag exists...
2424- if (headTag) {
2525-2626- var added = []
2727- var removed = []
2828- var preserved = []
2929- var nodesToAppend = []
3030-3131- htmlDoc.innerHTML = headTag;
3232- var newHeadTag = htmlDoc.querySelector("head");
3333- var currentHead = document.head;
3434-3535- if (newHeadTag == null) {
3636- return;
3737- } else {
3838- // put all new head elements into a Map, by their outerHTML
3939- var srcToNewHeadNodes = new Map();
4040- for (const newHeadChild of newHeadTag.children) {
4141- srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild);
4242- }
4343- }
4444-4545-4646-4747- // determine merge strategy
4848- var mergeStrategy = api.getAttributeValue(newHeadTag, "hx-head") || defaultMergeStrategy;
4949-5050- // get the current head
5151- for (const currentHeadElt of currentHead.children) {
5252-5353- // If the current head element is in the map
5454- var inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML);
5555- var isReAppended = currentHeadElt.getAttribute("hx-head") === "re-eval";
5656- var isPreserved = api.getAttributeValue(currentHeadElt, "hx-preserve") === "true";
5757- if (inNewContent || isPreserved) {
5858- if (isReAppended) {
5959- // remove the current version and let the new version replace it and re-execute
6060- removed.push(currentHeadElt);
6161- } else {
6262- // this element already exists and should not be re-appended, so remove it from
6363- // the new content map, preserving it in the DOM
6464- srcToNewHeadNodes.delete(currentHeadElt.outerHTML);
6565- preserved.push(currentHeadElt);
6666- }
6767- } else {
6868- if (mergeStrategy === "append") {
6969- // we are appending and this existing element is not new content
7070- // so if and only if it is marked for re-append do we do anything
7171- if (isReAppended) {
7272- removed.push(currentHeadElt);
7373- nodesToAppend.push(currentHeadElt);
7474- }
7575- } else {
7676- // if this is a merge, we remove this content since it is not in the new head
7777- if (api.triggerEvent(document.body, "htmx:removingHeadElement", {headElement: currentHeadElt}) !== false) {
7878- removed.push(currentHeadElt);
7979- }
8080- }
8181- }
8282- }
8383-8484- // Push the tremaining new head elements in the Map into the
8585- // nodes to append to the head tag
8686- nodesToAppend.push(...srcToNewHeadNodes.values());
8787- log("to append: ", nodesToAppend);
8888-8989- for (const newNode of nodesToAppend) {
9090- log("adding: ", newNode);
9191- var newElt = document.createRange().createContextualFragment(newNode.outerHTML);
9292- log(newElt);
9393- if (api.triggerEvent(document.body, "htmx:addingHeadElement", {headElement: newElt}) !== false) {
9494- currentHead.appendChild(newElt);
9595- added.push(newElt);
9696- }
9797- }
9898-9999- // remove all removed elements, after we have appended the new elements to avoid
100100- // additional network requests for things like style sheets
101101- for (const removedElement of removed) {
102102- if (api.triggerEvent(document.body, "htmx:removingHeadElement", {headElement: removedElement}) !== false) {
103103- currentHead.removeChild(removedElement);
104104- }
105105- }
106106-107107- api.triggerEvent(document.body, "htmx:afterHeadMerge", {added: added, kept: preserved, removed: removed});
108108- }
109109- }
110110- }
111111-112112- htmx.defineExtension("head-support", {
113113- init: function(apiRef) {
114114- // store a reference to the internal API.
115115- api = apiRef;
116116-117117- htmx.on('htmx:afterSwap', function(evt){
118118- var serverResponse = evt.detail.xhr.response;
119119- if (api.triggerEvent(document.body, "htmx:beforeHeadMerge", evt.detail)) {
120120- mergeHead(serverResponse, evt.detail.boosted ? "merge" : "append");
121121- }
122122- })
123123-124124- htmx.on('htmx:historyRestore', function(evt){
125125- if (api.triggerEvent(document.body, "htmx:beforeHeadMerge", evt.detail)) {
126126- if (evt.detail.cacheMiss) {
127127- mergeHead(evt.detail.serverResponse, "merge");
128128- } else {
129129- mergeHead(evt.detail.item.head, "merge");
130130- }
131131- }
132132- })
133133-134134- htmx.on('htmx:historyItemCreated', function(evt){
135135- var historyItem = evt.detail.item;
136136- historyItem.head = document.head.outerHTML;
137137- })
138138- }
139139- });
140140-141141-})()
-147
public/script/lib/htmx-preload.js
···11-// This adds the "preload" extension to htmx. By default, this will
22-// preload the targets of any tags with `href` or `hx-get` attributes
33-// if they also have a `preload` attribute as well. See documentation
44-// for more details
55-htmx.defineExtension("preload", {
66-77- onEvent: function(name, event) {
88-99- // Only take actions on "htmx:afterProcessNode"
1010- if (name !== "htmx:afterProcessNode") {
1111- return;
1212- }
1313-1414- // SOME HELPER FUNCTIONS WE'LL NEED ALONG THE WAY
1515-1616- // attr gets the closest non-empty value from the attribute.
1717- var attr = function(node, property) {
1818- if (node == undefined) {return undefined;}
1919- return node.getAttribute(property) || node.getAttribute("data-" + property) || attr(node.parentElement, property)
2020- }
2121-2222- // load handles the actual HTTP fetch, and uses htmx.ajax in cases where we're
2323- // preloading an htmx resource (this sends the same HTTP headers as a regular htmx request)
2424- var load = function(node) {
2525-2626- // Called after a successful AJAX request, to mark the
2727- // content as loaded (and prevent additional AJAX calls.)
2828- var done = function(html) {
2929- if (!node.preloadAlways) {
3030- node.preloadState = "DONE"
3131- }
3232-3333- if (attr(node, "preload-images") == "true") {
3434- document.createElement("div").innerHTML = html // create and populate a node to load linked resources, too.
3535- }
3636- }
3737-3838- return function() {
3939-4040- // If this value has already been loaded, then do not try again.
4141- if (node.preloadState !== "READY") {
4242- return;
4343- }
4444-4545- // Special handling for HX-GET - use built-in htmx.ajax function
4646- // so that headers match other htmx requests, then set
4747- // node.preloadState = TRUE so that requests are not duplicated
4848- // in the future
4949- var hxGet = node.getAttribute("hx-get") || node.getAttribute("data-hx-get")
5050- if (hxGet) {
5151- htmx.ajax("GET", hxGet, {
5252- source: node,
5353- handler:function(elt, info) {
5454- done(info.xhr.responseText);
5555- }
5656- });
5757- return;
5858- }
5959-6060- // Otherwise, perform a standard xhr request, then set
6161- // node.preloadState = TRUE so that requests are not duplicated
6262- // in the future.
6363- if (node.getAttribute("href")) {
6464- var r = new XMLHttpRequest();
6565- r.open("GET", node.getAttribute("href"));
6666- r.onload = function() {done(r.responseText);};
6767- r.send();
6868- return;
6969- }
7070- }
7171- }
7272-7373- // This function processes a specific node and sets up event handlers.
7474- // We'll search for nodes and use it below.
7575- var init = function(node) {
7676-7777- // If this node DOES NOT include a "GET" transaction, then there's nothing to do here.
7878- if (node.getAttribute("href") + node.getAttribute("hx-get") + node.getAttribute("data-hx-get") == "") {
7979- return;
8080- }
8181-8282- // Guarantee that we only initialize each node once.
8383- if (node.preloadState !== undefined) {
8484- return;
8585- }
8686-8787- // Get event name from config.
8888- var on = attr(node, "preload") || "mousedown"
8989- const always = on.indexOf("always") !== -1
9090- if (always) {
9191- on = on.replace('always', '').trim()
9292- }
9393-9494- // FALL THROUGH to here means we need to add an EventListener
9595-9696- // Apply the listener to the node
9797- node.addEventListener(on, function(evt) {
9898- if (node.preloadState === "PAUSE") { // Only add one event listener
9999- node.preloadState = "READY"; // Required for the `load` function to trigger
100100-101101- // Special handling for "mouseover" events. Wait 100ms before triggering load.
102102- if (on === "mouseover") {
103103- window.setTimeout(load(node), 100);
104104- } else {
105105- load(node)() // all other events trigger immediately.
106106- }
107107- }
108108- })
109109-110110- // Special handling for certain built-in event handlers
111111- switch (on) {
112112-113113- case "mouseover":
114114- // Mirror `touchstart` events (fires immediately)
115115- node.addEventListener("touchstart", load(node));
116116-117117- // WHhen the mouse leaves, immediately disable the preload
118118- node.addEventListener("mouseout", function(evt) {
119119- if ((evt.target === node) && (node.preloadState === "READY")) {
120120- node.preloadState = "PAUSE";
121121- }
122122- })
123123- break;
124124-125125- case "mousedown":
126126- // Mirror `touchstart` events (fires immediately)
127127- node.addEventListener("touchstart", load(node));
128128- break;
129129- }
130130-131131- // Mark the node as ready to run.
132132- node.preloadState = "PAUSE";
133133- node.preloadAlways = always;
134134- htmx.trigger(node, "preload:init") // This event can be used to load content immediately.
135135- }
136136-137137- // Search for all child nodes that have a "preload" attribute
138138- event.target.querySelectorAll("[preload]").forEach(function(node) {
139139-140140- // Initialize the node with the "preload" attribute
141141- init(node)
142142-143143- // Initialize all child elements that are anchors or have `hx-get` (use with care)
144144- node.querySelectorAll("a,[hx-get],[data-hx-get]").forEach(init)
145145- })
146146- }
147147-})
-3922
public/script/lib/htmx.js
···11-// UMD insanity
22-// This code sets up support for (in order) AMD, ES6 modules, and globals.
33-(function (root, factory) {
44- //@ts-ignore
55- if (typeof define === 'function' && define.amd) {
66- // AMD. Register as an anonymous module.
77- //@ts-ignore
88- define([], factory);
99- } else if (typeof module === 'object' && module.exports) {
1010- // Node. Does not work with strict CommonJS, but
1111- // only CommonJS-like environments that support module.exports,
1212- // like Node.
1313- module.exports = factory();
1414- } else {
1515- // Browser globals
1616- root.htmx = root.htmx || factory();
1717- }
1818-}(typeof self !== 'undefined' ? self : this, function () {
1919-return (function () {
2020- 'use strict';
2121-2222- // Public API
2323- //** @type {import("./htmx").HtmxApi} */
2424- // TODO: list all methods in public API
2525- var htmx = {
2626- onLoad: onLoadHelper,
2727- process: processNode,
2828- on: addEventListenerImpl,
2929- off: removeEventListenerImpl,
3030- trigger : triggerEvent,
3131- ajax : ajaxHelper,
3232- find : find,
3333- findAll : findAll,
3434- closest : closest,
3535- values : function(elt, type){
3636- var inputValues = getInputValues(elt, type || "post");
3737- return inputValues.values;
3838- },
3939- remove : removeElement,
4040- addClass : addClassToElement,
4141- removeClass : removeClassFromElement,
4242- toggleClass : toggleClassOnElement,
4343- takeClass : takeClassForElement,
4444- defineExtension : defineExtension,
4545- removeExtension : removeExtension,
4646- logAll : logAll,
4747- logNone : logNone,
4848- logger : null,
4949- config : {
5050- historyEnabled:true,
5151- historyCacheSize:10,
5252- refreshOnHistoryMiss:false,
5353- defaultSwapStyle:'innerHTML',
5454- defaultSwapDelay:0,
5555- defaultSettleDelay:20,
5656- includeIndicatorStyles:true,
5757- indicatorClass:'htmx-indicator',
5858- requestClass:'htmx-request',
5959- addedClass:'htmx-added',
6060- settlingClass:'htmx-settling',
6161- swappingClass:'htmx-swapping',
6262- allowEval:true,
6363- allowScriptTags:true,
6464- inlineScriptNonce:'',
6565- attributesToSettle:["class", "style", "width", "height"],
6666- withCredentials:false,
6767- timeout:0,
6868- wsReconnectDelay: 'full-jitter',
6969- wsBinaryType: 'blob',
7070- disableSelector: "[hx-disable], [data-hx-disable]",
7171- useTemplateFragments: false,
7272- scrollBehavior: 'smooth',
7373- defaultFocusScroll: false,
7474- getCacheBusterParam: false,
7575- globalViewTransitions: false,
7676- methodsThatUseUrlParams: ["get"],
7777- selfRequestsOnly: false,
7878- ignoreTitle: false,
7979- scrollIntoViewOnBoost: true,
8080- triggerSpecsCache: null,
8181- },
8282- parseInterval:parseInterval,
8383- _:internalEval,
8484- createEventSource: function(url){
8585- return new EventSource(url, {withCredentials:true})
8686- },
8787- createWebSocket: function(url){
8888- var sock = new WebSocket(url, []);
8989- sock.binaryType = htmx.config.wsBinaryType;
9090- return sock;
9191- },
9292- version: "1.9.11"
9393- };
9494-9595- /** @type {import("./htmx").HtmxInternalApi} */
9696- var internalAPI = {
9797- addTriggerHandler: addTriggerHandler,
9898- bodyContains: bodyContains,
9999- canAccessLocalStorage: canAccessLocalStorage,
100100- findThisElement: findThisElement,
101101- filterValues: filterValues,
102102- hasAttribute: hasAttribute,
103103- getAttributeValue: getAttributeValue,
104104- getClosestAttributeValue: getClosestAttributeValue,
105105- getClosestMatch: getClosestMatch,
106106- getExpressionVars: getExpressionVars,
107107- getHeaders: getHeaders,
108108- getInputValues: getInputValues,
109109- getInternalData: getInternalData,
110110- getSwapSpecification: getSwapSpecification,
111111- getTriggerSpecs: getTriggerSpecs,
112112- getTarget: getTarget,
113113- makeFragment: makeFragment,
114114- mergeObjects: mergeObjects,
115115- makeSettleInfo: makeSettleInfo,
116116- oobSwap: oobSwap,
117117- querySelectorExt: querySelectorExt,
118118- selectAndSwap: selectAndSwap,
119119- settleImmediately: settleImmediately,
120120- shouldCancel: shouldCancel,
121121- triggerEvent: triggerEvent,
122122- triggerErrorEvent: triggerErrorEvent,
123123- withExtensions: withExtensions,
124124- }
125125-126126- var VERBS = ['get', 'post', 'put', 'delete', 'patch'];
127127- var VERB_SELECTOR = VERBS.map(function(verb){
128128- return "[hx-" + verb + "], [data-hx-" + verb + "]"
129129- }).join(", ");
130130-131131- var HEAD_TAG_REGEX = makeTagRegEx('head'),
132132- TITLE_TAG_REGEX = makeTagRegEx('title'),
133133- SVG_TAGS_REGEX = makeTagRegEx('svg', true);
134134-135135- //====================================================================
136136- // Utilities
137137- //====================================================================
138138-139139- /**
140140- * @param {string} tag
141141- * @param {boolean} global
142142- * @returns {RegExp}
143143- */
144144- function makeTagRegEx(tag, global = false) {
145145- return new RegExp(`<${tag}(\\s[^>]*>|>)([\\s\\S]*?)<\\/${tag}>`,
146146- global ? 'gim' : 'im');
147147- }
148148-149149- function parseInterval(str) {
150150- if (str == undefined) {
151151- return undefined;
152152- }
153153-154154- let interval = NaN;
155155- if (str.slice(-2) == "ms") {
156156- interval = parseFloat(str.slice(0, -2));
157157- } else if (str.slice(-1) == "s") {
158158- interval = parseFloat(str.slice(0, -1)) * 1000;
159159- } else if (str.slice(-1) == "m") {
160160- interval = parseFloat(str.slice(0, -1)) * 1000 * 60;
161161- } else {
162162- interval = parseFloat(str);
163163- }
164164- return isNaN(interval) ? undefined : interval;
165165- }
166166-167167- /**
168168- * @param {HTMLElement} elt
169169- * @param {string} name
170170- * @returns {(string | null)}
171171- */
172172- function getRawAttribute(elt, name) {
173173- return elt.getAttribute && elt.getAttribute(name);
174174- }
175175-176176- // resolve with both hx and data-hx prefixes
177177- function hasAttribute(elt, qualifiedName) {
178178- return elt.hasAttribute && (elt.hasAttribute(qualifiedName) ||
179179- elt.hasAttribute("data-" + qualifiedName));
180180- }
181181-182182- /**
183183- *
184184- * @param {HTMLElement} elt
185185- * @param {string} qualifiedName
186186- * @returns {(string | null)}
187187- */
188188- function getAttributeValue(elt, qualifiedName) {
189189- return getRawAttribute(elt, qualifiedName) || getRawAttribute(elt, "data-" + qualifiedName);
190190- }
191191-192192- /**
193193- * @param {HTMLElement} elt
194194- * @returns {HTMLElement | null}
195195- */
196196- function parentElt(elt) {
197197- return elt.parentElement;
198198- }
199199-200200- /**
201201- * @returns {Document}
202202- */
203203- function getDocument() {
204204- return document;
205205- }
206206-207207- /**
208208- * @param {HTMLElement} elt
209209- * @param {(e:HTMLElement) => boolean} condition
210210- * @returns {HTMLElement | null}
211211- */
212212- function getClosestMatch(elt, condition) {
213213- while (elt && !condition(elt)) {
214214- elt = parentElt(elt);
215215- }
216216-217217- return elt ? elt : null;
218218- }
219219-220220- function getAttributeValueWithDisinheritance(initialElement, ancestor, attributeName){
221221- var attributeValue = getAttributeValue(ancestor, attributeName);
222222- var disinherit = getAttributeValue(ancestor, "hx-disinherit");
223223- if (initialElement !== ancestor && disinherit && (disinherit === "*" || disinherit.split(" ").indexOf(attributeName) >= 0)) {
224224- return "unset";
225225- } else {
226226- return attributeValue
227227- }
228228- }
229229-230230- /**
231231- * @param {HTMLElement} elt
232232- * @param {string} attributeName
233233- * @returns {string | null}
234234- */
235235- function getClosestAttributeValue(elt, attributeName) {
236236- var closestAttr = null;
237237- getClosestMatch(elt, function (e) {
238238- return closestAttr = getAttributeValueWithDisinheritance(elt, e, attributeName);
239239- });
240240- if (closestAttr !== "unset") {
241241- return closestAttr;
242242- }
243243- }
244244-245245- /**
246246- * @param {HTMLElement} elt
247247- * @param {string} selector
248248- * @returns {boolean}
249249- */
250250- function matches(elt, selector) {
251251- // @ts-ignore: non-standard properties for browser compatibility
252252- // noinspection JSUnresolvedVariable
253253- var matchesFunction = elt.matches || elt.matchesSelector || elt.msMatchesSelector || elt.mozMatchesSelector || elt.webkitMatchesSelector || elt.oMatchesSelector;
254254- return matchesFunction && matchesFunction.call(elt, selector);
255255- }
256256-257257- /**
258258- * @param {string} str
259259- * @returns {string}
260260- */
261261- function getStartTag(str) {
262262- var tagMatcher = /<([a-z][^\/\0>\x20\t\r\n\f]*)/i
263263- var match = tagMatcher.exec( str );
264264- if (match) {
265265- return match[1].toLowerCase();
266266- } else {
267267- return "";
268268- }
269269- }
270270-271271- /**
272272- *
273273- * @param {string} resp
274274- * @param {number} depth
275275- * @returns {Element}
276276- */
277277- function parseHTML(resp, depth) {
278278- var parser = new DOMParser();
279279- var responseDoc = parser.parseFromString(resp, "text/html");
280280-281281- /** @type {Element} */
282282- var responseNode = responseDoc.body;
283283- while (depth > 0) {
284284- depth--;
285285- // @ts-ignore
286286- responseNode = responseNode.firstChild;
287287- }
288288- if (responseNode == null) {
289289- // @ts-ignore
290290- responseNode = getDocument().createDocumentFragment();
291291- }
292292- return responseNode;
293293- }
294294-295295- function aFullPageResponse(resp) {
296296- return /<body/.test(resp)
297297- }
298298-299299- /**
300300- *
301301- * @param {string} response
302302- * @returns {Element}
303303- */
304304- function makeFragment(response) {
305305- var partialResponse = !aFullPageResponse(response);
306306- var startTag = getStartTag(response);
307307- var content = response;
308308- if (startTag === 'head') {
309309- content = content.replace(HEAD_TAG_REGEX, '');
310310- }
311311- if (htmx.config.useTemplateFragments && partialResponse) {
312312- var fragment = parseHTML("<body><template>" + content + "</template></body>", 0);
313313- // @ts-ignore type mismatch between DocumentFragment and Element.
314314- // TODO: Are these close enough for htmx to use interchangeably?
315315- var fragmentContent = fragment.querySelector('template').content;
316316- if (htmx.config.allowScriptTags) {
317317- // if there is a nonce set up, set it on the new script tags
318318- forEach(fragmentContent.querySelectorAll("script"), function (script) {
319319- if (htmx.config.inlineScriptNonce) {
320320- script.nonce = htmx.config.inlineScriptNonce;
321321- }
322322- // mark as executed due to template insertion semantics on all browsers except firefox fml
323323- script.htmxExecuted = navigator.userAgent.indexOf("Firefox") === -1;
324324- })
325325- } else {
326326- forEach(fragmentContent.querySelectorAll("script"), function (script) {
327327- // remove all script tags if scripts are disabled
328328- removeElement(script);
329329- })
330330- }
331331- return fragmentContent;
332332- }
333333- switch (startTag) {
334334- case "thead":
335335- case "tbody":
336336- case "tfoot":
337337- case "colgroup":
338338- case "caption":
339339- return parseHTML("<table>" + content + "</table>", 1);
340340- case "col":
341341- return parseHTML("<table><colgroup>" + content + "</colgroup></table>", 2);
342342- case "tr":
343343- return parseHTML("<table><tbody>" + content + "</tbody></table>", 2);
344344- case "td":
345345- case "th":
346346- return parseHTML("<table><tbody><tr>" + content + "</tr></tbody></table>", 3);
347347- case "script":
348348- case "style":
349349- return parseHTML("<div>" + content + "</div>", 1);
350350- default:
351351- return parseHTML(content, 0);
352352- }
353353- }
354354-355355- /**
356356- * @param {Function} func
357357- */
358358- function maybeCall(func){
359359- if(func) {
360360- func();
361361- }
362362- }
363363-364364- /**
365365- * @param {any} o
366366- * @param {string} type
367367- * @returns
368368- */
369369- function isType(o, type) {
370370- return Object.prototype.toString.call(o) === "[object " + type + "]";
371371- }
372372-373373- /**
374374- * @param {*} o
375375- * @returns {o is Function}
376376- */
377377- function isFunction(o) {
378378- return isType(o, "Function");
379379- }
380380-381381- /**
382382- * @param {*} o
383383- * @returns {o is Object}
384384- */
385385- function isRawObject(o) {
386386- return isType(o, "Object");
387387- }
388388-389389- /**
390390- * getInternalData retrieves "private" data stored by htmx within an element
391391- * @param {HTMLElement} elt
392392- * @returns {*}
393393- */
394394- function getInternalData(elt) {
395395- var dataProp = 'htmx-internal-data';
396396- var data = elt[dataProp];
397397- if (!data) {
398398- data = elt[dataProp] = {};
399399- }
400400- return data;
401401- }
402402-403403- /**
404404- * toArray converts an ArrayLike object into a real array.
405405- * @param {ArrayLike} arr
406406- * @returns {any[]}
407407- */
408408- function toArray(arr) {
409409- var returnArr = [];
410410- if (arr) {
411411- for (var i = 0; i < arr.length; i++) {
412412- returnArr.push(arr[i]);
413413- }
414414- }
415415- return returnArr
416416- }
417417-418418- function forEach(arr, func) {
419419- if (arr) {
420420- for (var i = 0; i < arr.length; i++) {
421421- func(arr[i]);
422422- }
423423- }
424424- }
425425-426426- function isScrolledIntoView(el) {
427427- var rect = el.getBoundingClientRect();
428428- var elemTop = rect.top;
429429- var elemBottom = rect.bottom;
430430- return elemTop < window.innerHeight && elemBottom >= 0;
431431- }
432432-433433- function bodyContains(elt) {
434434- // IE Fix
435435- if (elt.getRootNode && elt.getRootNode() instanceof window.ShadowRoot) {
436436- return getDocument().body.contains(elt.getRootNode().host);
437437- } else {
438438- return getDocument().body.contains(elt);
439439- }
440440- }
441441-442442- function splitOnWhitespace(trigger) {
443443- return trigger.trim().split(/\s+/);
444444- }
445445-446446- /**
447447- * mergeObjects takes all of the keys from
448448- * obj2 and duplicates them into obj1
449449- * @param {Object} obj1
450450- * @param {Object} obj2
451451- * @returns {Object}
452452- */
453453- function mergeObjects(obj1, obj2) {
454454- for (var key in obj2) {
455455- if (obj2.hasOwnProperty(key)) {
456456- obj1[key] = obj2[key];
457457- }
458458- }
459459- return obj1;
460460- }
461461-462462- function parseJSON(jString) {
463463- try {
464464- return JSON.parse(jString);
465465- } catch(error) {
466466- logError(error);
467467- return null;
468468- }
469469- }
470470-471471- function canAccessLocalStorage() {
472472- var test = 'htmx:localStorageTest';
473473- try {
474474- localStorage.setItem(test, test);
475475- localStorage.removeItem(test);
476476- return true;
477477- } catch(e) {
478478- return false;
479479- }
480480- }
481481-482482- function normalizePath(path) {
483483- try {
484484- var url = new URL(path);
485485- if (url) {
486486- path = url.pathname + url.search;
487487- }
488488- // remove trailing slash, unless index page
489489- if (!(/^\/$/.test(path))) {
490490- path = path.replace(/\/+$/, '');
491491- }
492492- return path;
493493- } catch (e) {
494494- // be kind to IE11, which doesn't support URL()
495495- return path;
496496- }
497497- }
498498-499499- //==========================================================================================
500500- // public API
501501- //==========================================================================================
502502-503503- function internalEval(str){
504504- return maybeEval(getDocument().body, function () {
505505- return eval(str);
506506- });
507507- }
508508-509509- function onLoadHelper(callback) {
510510- var value = htmx.on("htmx:load", function(evt) {
511511- callback(evt.detail.elt);
512512- });
513513- return value;
514514- }
515515-516516- function logAll(){
517517- htmx.logger = function(elt, event, data) {
518518- if(console) {
519519- console.log(event, elt, data);
520520- }
521521- }
522522- }
523523-524524- function logNone() {
525525- htmx.logger = null
526526- }
527527-528528- function find(eltOrSelector, selector) {
529529- if (selector) {
530530- return eltOrSelector.querySelector(selector);
531531- } else {
532532- return find(getDocument(), eltOrSelector);
533533- }
534534- }
535535-536536- function findAll(eltOrSelector, selector) {
537537- if (selector) {
538538- return eltOrSelector.querySelectorAll(selector);
539539- } else {
540540- return findAll(getDocument(), eltOrSelector);
541541- }
542542- }
543543-544544- function removeElement(elt, delay) {
545545- elt = resolveTarget(elt);
546546- if (delay) {
547547- setTimeout(function(){
548548- removeElement(elt);
549549- elt = null;
550550- }, delay);
551551- } else {
552552- elt.parentElement.removeChild(elt);
553553- }
554554- }
555555-556556- function addClassToElement(elt, clazz, delay) {
557557- elt = resolveTarget(elt);
558558- if (delay) {
559559- setTimeout(function(){
560560- addClassToElement(elt, clazz);
561561- elt = null;
562562- }, delay);
563563- } else {
564564- elt.classList && elt.classList.add(clazz);
565565- }
566566- }
567567-568568- function removeClassFromElement(elt, clazz, delay) {
569569- elt = resolveTarget(elt);
570570- if (delay) {
571571- setTimeout(function(){
572572- removeClassFromElement(elt, clazz);
573573- elt = null;
574574- }, delay);
575575- } else {
576576- if (elt.classList) {
577577- elt.classList.remove(clazz);
578578- // if there are no classes left, remove the class attribute
579579- if (elt.classList.length === 0) {
580580- elt.removeAttribute("class");
581581- }
582582- }
583583- }
584584- }
585585-586586- function toggleClassOnElement(elt, clazz) {
587587- elt = resolveTarget(elt);
588588- elt.classList.toggle(clazz);
589589- }
590590-591591- function takeClassForElement(elt, clazz) {
592592- elt = resolveTarget(elt);
593593- forEach(elt.parentElement.children, function(child){
594594- removeClassFromElement(child, clazz);
595595- })
596596- addClassToElement(elt, clazz);
597597- }
598598-599599- function closest(elt, selector) {
600600- elt = resolveTarget(elt);
601601- if (elt.closest) {
602602- return elt.closest(selector);
603603- } else {
604604- // TODO remove when IE goes away
605605- do{
606606- if (elt == null || matches(elt, selector)){
607607- return elt;
608608- }
609609- }
610610- while (elt = elt && parentElt(elt));
611611- return null;
612612- }
613613- }
614614-615615- function startsWith(str, prefix) {
616616- return str.substring(0, prefix.length) === prefix
617617- }
618618-619619- function endsWith(str, suffix) {
620620- return str.substring(str.length - suffix.length) === suffix
621621- }
622622-623623- function normalizeSelector(selector) {
624624- var trimmedSelector = selector.trim();
625625- if (startsWith(trimmedSelector, "<") && endsWith(trimmedSelector, "/>")) {
626626- return trimmedSelector.substring(1, trimmedSelector.length - 2);
627627- } else {
628628- return trimmedSelector;
629629- }
630630- }
631631-632632- function querySelectorAllExt(elt, selector) {
633633- if (selector.indexOf("closest ") === 0) {
634634- return [closest(elt, normalizeSelector(selector.substr(8)))];
635635- } else if (selector.indexOf("find ") === 0) {
636636- return [find(elt, normalizeSelector(selector.substr(5)))];
637637- } else if (selector === "next") {
638638- return [elt.nextElementSibling]
639639- } else if (selector.indexOf("next ") === 0) {
640640- return [scanForwardQuery(elt, normalizeSelector(selector.substr(5)))];
641641- } else if (selector === "previous") {
642642- return [elt.previousElementSibling]
643643- } else if (selector.indexOf("previous ") === 0) {
644644- return [scanBackwardsQuery(elt, normalizeSelector(selector.substr(9)))];
645645- } else if (selector === 'document') {
646646- return [document];
647647- } else if (selector === 'window') {
648648- return [window];
649649- } else if (selector === 'body') {
650650- return [document.body];
651651- } else {
652652- return getDocument().querySelectorAll(normalizeSelector(selector));
653653- }
654654- }
655655-656656- var scanForwardQuery = function(start, match) {
657657- var results = getDocument().querySelectorAll(match);
658658- for (var i = 0; i < results.length; i++) {
659659- var elt = results[i];
660660- if (elt.compareDocumentPosition(start) === Node.DOCUMENT_POSITION_PRECEDING) {
661661- return elt;
662662- }
663663- }
664664- }
665665-666666- var scanBackwardsQuery = function(start, match) {
667667- var results = getDocument().querySelectorAll(match);
668668- for (var i = results.length - 1; i >= 0; i--) {
669669- var elt = results[i];
670670- if (elt.compareDocumentPosition(start) === Node.DOCUMENT_POSITION_FOLLOWING) {
671671- return elt;
672672- }
673673- }
674674- }
675675-676676- function querySelectorExt(eltOrSelector, selector) {
677677- if (selector) {
678678- return querySelectorAllExt(eltOrSelector, selector)[0];
679679- } else {
680680- return querySelectorAllExt(getDocument().body, eltOrSelector)[0];
681681- }
682682- }
683683-684684- function resolveTarget(arg2) {
685685- if (isType(arg2, 'String')) {
686686- return find(arg2);
687687- } else {
688688- return arg2;
689689- }
690690- }
691691-692692- function processEventArgs(arg1, arg2, arg3) {
693693- if (isFunction(arg2)) {
694694- return {
695695- target: getDocument().body,
696696- event: arg1,
697697- listener: arg2
698698- }
699699- } else {
700700- return {
701701- target: resolveTarget(arg1),
702702- event: arg2,
703703- listener: arg3
704704- }
705705- }
706706-707707- }
708708-709709- function addEventListenerImpl(arg1, arg2, arg3) {
710710- ready(function(){
711711- var eventArgs = processEventArgs(arg1, arg2, arg3);
712712- eventArgs.target.addEventListener(eventArgs.event, eventArgs.listener);
713713- })
714714- var b = isFunction(arg2);
715715- return b ? arg2 : arg3;
716716- }
717717-718718- function removeEventListenerImpl(arg1, arg2, arg3) {
719719- ready(function(){
720720- var eventArgs = processEventArgs(arg1, arg2, arg3);
721721- eventArgs.target.removeEventListener(eventArgs.event, eventArgs.listener);
722722- })
723723- return isFunction(arg2) ? arg2 : arg3;
724724- }
725725-726726- //====================================================================
727727- // Node processing
728728- //====================================================================
729729-730730- var DUMMY_ELT = getDocument().createElement("output"); // dummy element for bad selectors
731731- function findAttributeTargets(elt, attrName) {
732732- var attrTarget = getClosestAttributeValue(elt, attrName);
733733- if (attrTarget) {
734734- if (attrTarget === "this") {
735735- return [findThisElement(elt, attrName)];
736736- } else {
737737- var result = querySelectorAllExt(elt, attrTarget);
738738- if (result.length === 0) {
739739- logError('The selector "' + attrTarget + '" on ' + attrName + " returned no matches!");
740740- return [DUMMY_ELT]
741741- } else {
742742- return result;
743743- }
744744- }
745745- }
746746- }
747747-748748- function findThisElement(elt, attribute){
749749- return getClosestMatch(elt, function (elt) {
750750- return getAttributeValue(elt, attribute) != null;
751751- })
752752- }
753753-754754- function getTarget(elt) {
755755- var targetStr = getClosestAttributeValue(elt, "hx-target");
756756- if (targetStr) {
757757- if (targetStr === "this") {
758758- return findThisElement(elt,'hx-target');
759759- } else {
760760- return querySelectorExt(elt, targetStr)
761761- }
762762- } else {
763763- var data = getInternalData(elt);
764764- if (data.boosted) {
765765- return getDocument().body;
766766- } else {
767767- return elt;
768768- }
769769- }
770770- }
771771-772772- function shouldSettleAttribute(name) {
773773- var attributesToSettle = htmx.config.attributesToSettle;
774774- for (var i = 0; i < attributesToSettle.length; i++) {
775775- if (name === attributesToSettle[i]) {
776776- return true;
777777- }
778778- }
779779- return false;
780780- }
781781-782782- function cloneAttributes(mergeTo, mergeFrom) {
783783- forEach(mergeTo.attributes, function (attr) {
784784- if (!mergeFrom.hasAttribute(attr.name) && shouldSettleAttribute(attr.name)) {
785785- mergeTo.removeAttribute(attr.name)
786786- }
787787- });
788788- forEach(mergeFrom.attributes, function (attr) {
789789- if (shouldSettleAttribute(attr.name)) {
790790- mergeTo.setAttribute(attr.name, attr.value);
791791- }
792792- });
793793- }
794794-795795- function isInlineSwap(swapStyle, target) {
796796- var extensions = getExtensions(target);
797797- for (var i = 0; i < extensions.length; i++) {
798798- var extension = extensions[i];
799799- try {
800800- if (extension.isInlineSwap(swapStyle)) {
801801- return true;
802802- }
803803- } catch(e) {
804804- logError(e);
805805- }
806806- }
807807- return swapStyle === "outerHTML";
808808- }
809809-810810- /**
811811- *
812812- * @param {string} oobValue
813813- * @param {HTMLElement} oobElement
814814- * @param {*} settleInfo
815815- * @returns
816816- */
817817- function oobSwap(oobValue, oobElement, settleInfo) {
818818- var selector = "#" + getRawAttribute(oobElement, "id");
819819- var swapStyle = "outerHTML";
820820- if (oobValue === "true") {
821821- // do nothing
822822- } else if (oobValue.indexOf(":") > 0) {
823823- swapStyle = oobValue.substr(0, oobValue.indexOf(":"));
824824- selector = oobValue.substr(oobValue.indexOf(":") + 1, oobValue.length);
825825- } else {
826826- swapStyle = oobValue;
827827- }
828828-829829- var targets = getDocument().querySelectorAll(selector);
830830- if (targets) {
831831- forEach(
832832- targets,
833833- function (target) {
834834- var fragment;
835835- var oobElementClone = oobElement.cloneNode(true);
836836- fragment = getDocument().createDocumentFragment();
837837- fragment.appendChild(oobElementClone);
838838- if (!isInlineSwap(swapStyle, target)) {
839839- fragment = oobElementClone; // if this is not an inline swap, we use the content of the node, not the node itself
840840- }
841841-842842- var beforeSwapDetails = {shouldSwap: true, target: target, fragment:fragment };
843843- if (!triggerEvent(target, 'htmx:oobBeforeSwap', beforeSwapDetails)) return;
844844-845845- target = beforeSwapDetails.target; // allow re-targeting
846846- if (beforeSwapDetails['shouldSwap']){
847847- swap(swapStyle, target, target, fragment, settleInfo);
848848- }
849849- forEach(settleInfo.elts, function (elt) {
850850- triggerEvent(elt, 'htmx:oobAfterSwap', beforeSwapDetails);
851851- });
852852- }
853853- );
854854- oobElement.parentNode.removeChild(oobElement);
855855- } else {
856856- oobElement.parentNode.removeChild(oobElement);
857857- triggerErrorEvent(getDocument().body, "htmx:oobErrorNoTarget", {content: oobElement});
858858- }
859859- return oobValue;
860860- }
861861-862862- function handleOutOfBandSwaps(elt, fragment, settleInfo) {
863863- var oobSelects = getClosestAttributeValue(elt, "hx-select-oob");
864864- if (oobSelects) {
865865- var oobSelectValues = oobSelects.split(",");
866866- for (var i = 0; i < oobSelectValues.length; i++) {
867867- var oobSelectValue = oobSelectValues[i].split(":", 2);
868868- var id = oobSelectValue[0].trim();
869869- if (id.indexOf("#") === 0) {
870870- id = id.substring(1);
871871- }
872872- var oobValue = oobSelectValue[1] || "true";
873873- var oobElement = fragment.querySelector("#" + id);
874874- if (oobElement) {
875875- oobSwap(oobValue, oobElement, settleInfo);
876876- }
877877- }
878878- }
879879- forEach(findAll(fragment, '[hx-swap-oob], [data-hx-swap-oob]'), function (oobElement) {
880880- var oobValue = getAttributeValue(oobElement, "hx-swap-oob");
881881- if (oobValue != null) {
882882- oobSwap(oobValue, oobElement, settleInfo);
883883- }
884884- });
885885- }
886886-887887- function handlePreservedElements(fragment) {
888888- forEach(findAll(fragment, '[hx-preserve], [data-hx-preserve]'), function (preservedElt) {
889889- var id = getAttributeValue(preservedElt, "id");
890890- var oldElt = getDocument().getElementById(id);
891891- if (oldElt != null) {
892892- preservedElt.parentNode.replaceChild(oldElt, preservedElt);
893893- }
894894- });
895895- }
896896-897897- function handleAttributes(parentNode, fragment, settleInfo) {
898898- forEach(fragment.querySelectorAll("[id]"), function (newNode) {
899899- var id = getRawAttribute(newNode, "id")
900900- if (id && id.length > 0) {
901901- var normalizedId = id.replace("'", "\\'");
902902- var normalizedTag = newNode.tagName.replace(':', '\\:');
903903- var oldNode = parentNode.querySelector(normalizedTag + "[id='" + normalizedId + "']");
904904- if (oldNode && oldNode !== parentNode) {
905905- var newAttributes = newNode.cloneNode();
906906- cloneAttributes(newNode, oldNode);
907907- settleInfo.tasks.push(function () {
908908- cloneAttributes(newNode, newAttributes);
909909- });
910910- }
911911- }
912912- });
913913- }
914914-915915- function makeAjaxLoadTask(child) {
916916- return function () {
917917- removeClassFromElement(child, htmx.config.addedClass);
918918- processNode(child);
919919- processScripts(child);
920920- processFocus(child)
921921- triggerEvent(child, 'htmx:load');
922922- };
923923- }
924924-925925- function processFocus(child) {
926926- var autofocus = "[autofocus]";
927927- var autoFocusedElt = matches(child, autofocus) ? child : child.querySelector(autofocus)
928928- if (autoFocusedElt != null) {
929929- autoFocusedElt.focus();
930930- }
931931- }
932932-933933- function insertNodesBefore(parentNode, insertBefore, fragment, settleInfo) {
934934- handleAttributes(parentNode, fragment, settleInfo);
935935- while(fragment.childNodes.length > 0){
936936- var child = fragment.firstChild;
937937- addClassToElement(child, htmx.config.addedClass);
938938- parentNode.insertBefore(child, insertBefore);
939939- if (child.nodeType !== Node.TEXT_NODE && child.nodeType !== Node.COMMENT_NODE) {
940940- settleInfo.tasks.push(makeAjaxLoadTask(child));
941941- }
942942- }
943943- }
944944-945945- // based on https://gist.github.com/hyamamoto/fd435505d29ebfa3d9716fd2be8d42f0,
946946- // derived from Java's string hashcode implementation
947947- function stringHash(string, hash) {
948948- var char = 0;
949949- while (char < string.length){
950950- hash = (hash << 5) - hash + string.charCodeAt(char++) | 0; // bitwise or ensures we have a 32-bit int
951951- }
952952- return hash;
953953- }
954954-955955- function attributeHash(elt) {
956956- var hash = 0;
957957- // IE fix
958958- if (elt.attributes) {
959959- for (var i = 0; i < elt.attributes.length; i++) {
960960- var attribute = elt.attributes[i];
961961- if(attribute.value){ // only include attributes w/ actual values (empty is same as non-existent)
962962- hash = stringHash(attribute.name, hash);
963963- hash = stringHash(attribute.value, hash);
964964- }
965965- }
966966- }
967967- return hash;
968968- }
969969-970970- function deInitOnHandlers(elt) {
971971- var internalData = getInternalData(elt);
972972- if (internalData.onHandlers) {
973973- for (var i = 0; i < internalData.onHandlers.length; i++) {
974974- const handlerInfo = internalData.onHandlers[i];
975975- elt.removeEventListener(handlerInfo.event, handlerInfo.listener);
976976- }
977977- delete internalData.onHandlers
978978- }
979979- }
980980-981981- function deInitNode(element) {
982982- var internalData = getInternalData(element);
983983- if (internalData.timeout) {
984984- clearTimeout(internalData.timeout);
985985- }
986986- if (internalData.webSocket) {
987987- internalData.webSocket.close();
988988- }
989989- if (internalData.sseEventSource) {
990990- internalData.sseEventSource.close();
991991- }
992992- if (internalData.listenerInfos) {
993993- forEach(internalData.listenerInfos, function (info) {
994994- if (info.on) {
995995- info.on.removeEventListener(info.trigger, info.listener);
996996- }
997997- });
998998- }
999999- deInitOnHandlers(element);
10001000- forEach(Object.keys(internalData), function(key) { delete internalData[key] });
10011001- }
10021002-10031003- function cleanUpElement(element) {
10041004- triggerEvent(element, "htmx:beforeCleanupElement")
10051005- deInitNode(element);
10061006- if (element.children) { // IE
10071007- forEach(element.children, function(child) { cleanUpElement(child) });
10081008- }
10091009- }
10101010-10111011- function swapOuterHTML(target, fragment, settleInfo) {
10121012- if (target.tagName === "BODY") {
10131013- return swapInnerHTML(target, fragment, settleInfo);
10141014- } else {
10151015- // @type {HTMLElement}
10161016- var newElt
10171017- var eltBeforeNewContent = target.previousSibling;
10181018- insertNodesBefore(parentElt(target), target, fragment, settleInfo);
10191019- if (eltBeforeNewContent == null) {
10201020- newElt = parentElt(target).firstChild;
10211021- } else {
10221022- newElt = eltBeforeNewContent.nextSibling;
10231023- }
10241024- settleInfo.elts = settleInfo.elts.filter(function(e) { return e != target });
10251025- while(newElt && newElt !== target) {
10261026- if (newElt.nodeType === Node.ELEMENT_NODE) {
10271027- settleInfo.elts.push(newElt);
10281028- }
10291029- newElt = newElt.nextElementSibling;
10301030- }
10311031- cleanUpElement(target);
10321032- parentElt(target).removeChild(target);
10331033- }
10341034- }
10351035-10361036- function swapAfterBegin(target, fragment, settleInfo) {
10371037- return insertNodesBefore(target, target.firstChild, fragment, settleInfo);
10381038- }
10391039-10401040- function swapBeforeBegin(target, fragment, settleInfo) {
10411041- return insertNodesBefore(parentElt(target), target, fragment, settleInfo);
10421042- }
10431043-10441044- function swapBeforeEnd(target, fragment, settleInfo) {
10451045- return insertNodesBefore(target, null, fragment, settleInfo);
10461046- }
10471047-10481048- function swapAfterEnd(target, fragment, settleInfo) {
10491049- return insertNodesBefore(parentElt(target), target.nextSibling, fragment, settleInfo);
10501050- }
10511051- function swapDelete(target, fragment, settleInfo) {
10521052- cleanUpElement(target);
10531053- return parentElt(target).removeChild(target);
10541054- }
10551055-10561056- function swapInnerHTML(target, fragment, settleInfo) {
10571057- var firstChild = target.firstChild;
10581058- insertNodesBefore(target, firstChild, fragment, settleInfo);
10591059- if (firstChild) {
10601060- while (firstChild.nextSibling) {
10611061- cleanUpElement(firstChild.nextSibling)
10621062- target.removeChild(firstChild.nextSibling);
10631063- }
10641064- cleanUpElement(firstChild)
10651065- target.removeChild(firstChild);
10661066- }
10671067- }
10681068-10691069- function maybeSelectFromResponse(elt, fragment, selectOverride) {
10701070- var selector = selectOverride || getClosestAttributeValue(elt, "hx-select");
10711071- if (selector) {
10721072- var newFragment = getDocument().createDocumentFragment();
10731073- forEach(fragment.querySelectorAll(selector), function (node) {
10741074- newFragment.appendChild(node);
10751075- });
10761076- fragment = newFragment;
10771077- }
10781078- return fragment;
10791079- }
10801080-10811081- function swap(swapStyle, elt, target, fragment, settleInfo) {
10821082- switch (swapStyle) {
10831083- case "none":
10841084- return;
10851085- case "outerHTML":
10861086- swapOuterHTML(target, fragment, settleInfo);
10871087- return;
10881088- case "afterbegin":
10891089- swapAfterBegin(target, fragment, settleInfo);
10901090- return;
10911091- case "beforebegin":
10921092- swapBeforeBegin(target, fragment, settleInfo);
10931093- return;
10941094- case "beforeend":
10951095- swapBeforeEnd(target, fragment, settleInfo);
10961096- return;
10971097- case "afterend":
10981098- swapAfterEnd(target, fragment, settleInfo);
10991099- return;
11001100- case "delete":
11011101- swapDelete(target, fragment, settleInfo);
11021102- return;
11031103- default:
11041104- var extensions = getExtensions(elt);
11051105- for (var i = 0; i < extensions.length; i++) {
11061106- var ext = extensions[i];
11071107- try {
11081108- var newElements = ext.handleSwap(swapStyle, target, fragment, settleInfo);
11091109- if (newElements) {
11101110- if (typeof newElements.length !== 'undefined') {
11111111- // if handleSwap returns an array (like) of elements, we handle them
11121112- for (var j = 0; j < newElements.length; j++) {
11131113- var child = newElements[j];
11141114- if (child.nodeType !== Node.TEXT_NODE && child.nodeType !== Node.COMMENT_NODE) {
11151115- settleInfo.tasks.push(makeAjaxLoadTask(child));
11161116- }
11171117- }
11181118- }
11191119- return;
11201120- }
11211121- } catch (e) {
11221122- logError(e);
11231123- }
11241124- }
11251125- if (swapStyle === "innerHTML") {
11261126- swapInnerHTML(target, fragment, settleInfo);
11271127- } else {
11281128- swap(htmx.config.defaultSwapStyle, elt, target, fragment, settleInfo);
11291129- }
11301130- }
11311131- }
11321132-11331133- function findTitle(content) {
11341134- if (content.indexOf('<title') > -1) {
11351135- var contentWithSvgsRemoved = content.replace(SVG_TAGS_REGEX, '');
11361136- var result = contentWithSvgsRemoved.match(TITLE_TAG_REGEX);
11371137- if (result) {
11381138- return result[2];
11391139- }
11401140- }
11411141- }
11421142-11431143- function selectAndSwap(swapStyle, target, elt, responseText, settleInfo, selectOverride) {
11441144- settleInfo.title = findTitle(responseText);
11451145- var fragment = makeFragment(responseText);
11461146- if (fragment) {
11471147- handleOutOfBandSwaps(elt, fragment, settleInfo);
11481148- fragment = maybeSelectFromResponse(elt, fragment, selectOverride);
11491149- handlePreservedElements(fragment);
11501150- return swap(swapStyle, elt, target, fragment, settleInfo);
11511151- }
11521152- }
11531153-11541154- function handleTrigger(xhr, header, elt) {
11551155- var triggerBody = xhr.getResponseHeader(header);
11561156- if (triggerBody.indexOf("{") === 0) {
11571157- var triggers = parseJSON(triggerBody);
11581158- for (var eventName in triggers) {
11591159- if (triggers.hasOwnProperty(eventName)) {
11601160- var detail = triggers[eventName];
11611161- if (!isRawObject(detail)) {
11621162- detail = {"value": detail}
11631163- }
11641164- triggerEvent(elt, eventName, detail);
11651165- }
11661166- }
11671167- } else {
11681168- var eventNames = triggerBody.split(",")
11691169- for (var i = 0; i < eventNames.length; i++) {
11701170- triggerEvent(elt, eventNames[i].trim(), []);
11711171- }
11721172- }
11731173- }
11741174-11751175- var WHITESPACE = /\s/;
11761176- var WHITESPACE_OR_COMMA = /[\s,]/;
11771177- var SYMBOL_START = /[_$a-zA-Z]/;
11781178- var SYMBOL_CONT = /[_$a-zA-Z0-9]/;
11791179- var STRINGISH_START = ['"', "'", "/"];
11801180- var NOT_WHITESPACE = /[^\s]/;
11811181- var COMBINED_SELECTOR_START = /[{(]/;
11821182- var COMBINED_SELECTOR_END = /[})]/;
11831183- function tokenizeString(str) {
11841184- var tokens = [];
11851185- var position = 0;
11861186- while (position < str.length) {
11871187- if(SYMBOL_START.exec(str.charAt(position))) {
11881188- var startPosition = position;
11891189- while (SYMBOL_CONT.exec(str.charAt(position + 1))) {
11901190- position++;
11911191- }
11921192- tokens.push(str.substr(startPosition, position - startPosition + 1));
11931193- } else if (STRINGISH_START.indexOf(str.charAt(position)) !== -1) {
11941194- var startChar = str.charAt(position);
11951195- var startPosition = position;
11961196- position++;
11971197- while (position < str.length && str.charAt(position) !== startChar ) {
11981198- if (str.charAt(position) === "\\") {
11991199- position++;
12001200- }
12011201- position++;
12021202- }
12031203- tokens.push(str.substr(startPosition, position - startPosition + 1));
12041204- } else {
12051205- var symbol = str.charAt(position);
12061206- tokens.push(symbol);
12071207- }
12081208- position++;
12091209- }
12101210- return tokens;
12111211- }
12121212-12131213- function isPossibleRelativeReference(token, last, paramName) {
12141214- return SYMBOL_START.exec(token.charAt(0)) &&
12151215- token !== "true" &&
12161216- token !== "false" &&
12171217- token !== "this" &&
12181218- token !== paramName &&
12191219- last !== ".";
12201220- }
12211221-12221222- function maybeGenerateConditional(elt, tokens, paramName) {
12231223- if (tokens[0] === '[') {
12241224- tokens.shift();
12251225- var bracketCount = 1;
12261226- var conditionalSource = " return (function(" + paramName + "){ return (";
12271227- var last = null;
12281228- while (tokens.length > 0) {
12291229- var token = tokens[0];
12301230- if (token === "]") {
12311231- bracketCount--;
12321232- if (bracketCount === 0) {
12331233- if (last === null) {
12341234- conditionalSource = conditionalSource + "true";
12351235- }
12361236- tokens.shift();
12371237- conditionalSource += ")})";
12381238- try {
12391239- var conditionFunction = maybeEval(elt,function () {
12401240- return Function(conditionalSource)();
12411241- },
12421242- function(){return true})
12431243- conditionFunction.source = conditionalSource;
12441244- return conditionFunction;
12451245- } catch (e) {
12461246- triggerErrorEvent(getDocument().body, "htmx:syntax:error", {error:e, source:conditionalSource})
12471247- return null;
12481248- }
12491249- }
12501250- } else if (token === "[") {
12511251- bracketCount++;
12521252- }
12531253- if (isPossibleRelativeReference(token, last, paramName)) {
12541254- conditionalSource += "((" + paramName + "." + token + ") ? (" + paramName + "." + token + ") : (window." + token + "))";
12551255- } else {
12561256- conditionalSource = conditionalSource + token;
12571257- }
12581258- last = tokens.shift();
12591259- }
12601260- }
12611261- }
12621262-12631263- function consumeUntil(tokens, match) {
12641264- var result = "";
12651265- while (tokens.length > 0 && !match.test(tokens[0])) {
12661266- result += tokens.shift();
12671267- }
12681268- return result;
12691269- }
12701270-12711271- function consumeCSSSelector(tokens) {
12721272- var result;
12731273- if (tokens.length > 0 && COMBINED_SELECTOR_START.test(tokens[0])) {
12741274- tokens.shift();
12751275- result = consumeUntil(tokens, COMBINED_SELECTOR_END).trim();
12761276- tokens.shift();
12771277- } else {
12781278- result = consumeUntil(tokens, WHITESPACE_OR_COMMA);
12791279- }
12801280- return result;
12811281- }
12821282-12831283- var INPUT_SELECTOR = 'input, textarea, select';
12841284-12851285- /**
12861286- * @param {HTMLElement} elt
12871287- * @param {string} explicitTrigger
12881288- * @param {cache} cache for trigger specs
12891289- * @returns {import("./htmx").HtmxTriggerSpecification[]}
12901290- */
12911291- function parseAndCacheTrigger(elt, explicitTrigger, cache) {
12921292- var triggerSpecs = [];
12931293- var tokens = tokenizeString(explicitTrigger);
12941294- do {
12951295- consumeUntil(tokens, NOT_WHITESPACE);
12961296- var initialLength = tokens.length;
12971297- var trigger = consumeUntil(tokens, /[,\[\s]/);
12981298- if (trigger !== "") {
12991299- if (trigger === "every") {
13001300- var every = {trigger: 'every'};
13011301- consumeUntil(tokens, NOT_WHITESPACE);
13021302- every.pollInterval = parseInterval(consumeUntil(tokens, /[,\[\s]/));
13031303- consumeUntil(tokens, NOT_WHITESPACE);
13041304- var eventFilter = maybeGenerateConditional(elt, tokens, "event");
13051305- if (eventFilter) {
13061306- every.eventFilter = eventFilter;
13071307- }
13081308- triggerSpecs.push(every);
13091309- } else if (trigger.indexOf("sse:") === 0) {
13101310- triggerSpecs.push({trigger: 'sse', sseEvent: trigger.substr(4)});
13111311- } else {
13121312- var triggerSpec = {trigger: trigger};
13131313- var eventFilter = maybeGenerateConditional(elt, tokens, "event");
13141314- if (eventFilter) {
13151315- triggerSpec.eventFilter = eventFilter;
13161316- }
13171317- while (tokens.length > 0 && tokens[0] !== ",") {
13181318- consumeUntil(tokens, NOT_WHITESPACE)
13191319- var token = tokens.shift();
13201320- if (token === "changed") {
13211321- triggerSpec.changed = true;
13221322- } else if (token === "once") {
13231323- triggerSpec.once = true;
13241324- } else if (token === "consume") {
13251325- triggerSpec.consume = true;
13261326- } else if (token === "delay" && tokens[0] === ":") {
13271327- tokens.shift();
13281328- triggerSpec.delay = parseInterval(consumeUntil(tokens, WHITESPACE_OR_COMMA));
13291329- } else if (token === "from" && tokens[0] === ":") {
13301330- tokens.shift();
13311331- if (COMBINED_SELECTOR_START.test(tokens[0])) {
13321332- var from_arg = consumeCSSSelector(tokens);
13331333- } else {
13341334- var from_arg = consumeUntil(tokens, WHITESPACE_OR_COMMA);
13351335- if (from_arg === "closest" || from_arg === "find" || from_arg === "next" || from_arg === "previous") {
13361336- tokens.shift();
13371337- var selector = consumeCSSSelector(tokens);
13381338- // `next` and `previous` allow a selector-less syntax
13391339- if (selector.length > 0) {
13401340- from_arg += " " + selector;
13411341- }
13421342- }
13431343- }
13441344- triggerSpec.from = from_arg;
13451345- } else if (token === "target" && tokens[0] === ":") {
13461346- tokens.shift();
13471347- triggerSpec.target = consumeCSSSelector(tokens);
13481348- } else if (token === "throttle" && tokens[0] === ":") {
13491349- tokens.shift();
13501350- triggerSpec.throttle = parseInterval(consumeUntil(tokens, WHITESPACE_OR_COMMA));
13511351- } else if (token === "queue" && tokens[0] === ":") {
13521352- tokens.shift();
13531353- triggerSpec.queue = consumeUntil(tokens, WHITESPACE_OR_COMMA);
13541354- } else if (token === "root" && tokens[0] === ":") {
13551355- tokens.shift();
13561356- triggerSpec[token] = consumeCSSSelector(tokens);
13571357- } else if (token === "threshold" && tokens[0] === ":") {
13581358- tokens.shift();
13591359- triggerSpec[token] = consumeUntil(tokens, WHITESPACE_OR_COMMA);
13601360- } else {
13611361- triggerErrorEvent(elt, "htmx:syntax:error", {token:tokens.shift()});
13621362- }
13631363- }
13641364- triggerSpecs.push(triggerSpec);
13651365- }
13661366- }
13671367- if (tokens.length === initialLength) {
13681368- triggerErrorEvent(elt, "htmx:syntax:error", {token:tokens.shift()});
13691369- }
13701370- consumeUntil(tokens, NOT_WHITESPACE);
13711371- } while (tokens[0] === "," && tokens.shift())
13721372- if (cache) {
13731373- cache[explicitTrigger] = triggerSpecs
13741374- }
13751375- return triggerSpecs
13761376- }
13771377-13781378- /**
13791379- * @param {HTMLElement} elt
13801380- * @returns {import("./htmx").HtmxTriggerSpecification[]}
13811381- */
13821382- function getTriggerSpecs(elt) {
13831383- var explicitTrigger = getAttributeValue(elt, 'hx-trigger');
13841384- var triggerSpecs = [];
13851385- if (explicitTrigger) {
13861386- var cache = htmx.config.triggerSpecsCache
13871387- triggerSpecs = (cache && cache[explicitTrigger]) || parseAndCacheTrigger(elt, explicitTrigger, cache)
13881388- }
13891389-13901390- if (triggerSpecs.length > 0) {
13911391- return triggerSpecs;
13921392- } else if (matches(elt, 'form')) {
13931393- return [{trigger: 'submit'}];
13941394- } else if (matches(elt, 'input[type="button"], input[type="submit"]')){
13951395- return [{trigger: 'click'}];
13961396- } else if (matches(elt, INPUT_SELECTOR)) {
13971397- return [{trigger: 'change'}];
13981398- } else {
13991399- return [{trigger: 'click'}];
14001400- }
14011401- }
14021402-14031403- function cancelPolling(elt) {
14041404- getInternalData(elt).cancelled = true;
14051405- }
14061406-14071407- function processPolling(elt, handler, spec) {
14081408- var nodeData = getInternalData(elt);
14091409- nodeData.timeout = setTimeout(function () {
14101410- if (bodyContains(elt) && nodeData.cancelled !== true) {
14111411- if (!maybeFilterEvent(spec, elt, makeEvent('hx:poll:trigger', {
14121412- triggerSpec: spec,
14131413- target: elt
14141414- }))) {
14151415- handler(elt);
14161416- }
14171417- processPolling(elt, handler, spec);
14181418- }
14191419- }, spec.pollInterval);
14201420- }
14211421-14221422- function isLocalLink(elt) {
14231423- return location.hostname === elt.hostname &&
14241424- getRawAttribute(elt,'href') &&
14251425- getRawAttribute(elt,'href').indexOf("#") !== 0;
14261426- }
14271427-14281428- function boostElement(elt, nodeData, triggerSpecs) {
14291429- if ((elt.tagName === "A" && isLocalLink(elt) && (elt.target === "" || elt.target === "_self")) || elt.tagName === "FORM") {
14301430- nodeData.boosted = true;
14311431- var verb, path;
14321432- if (elt.tagName === "A") {
14331433- verb = "get";
14341434- path = getRawAttribute(elt, 'href')
14351435- } else {
14361436- var rawAttribute = getRawAttribute(elt, "method");
14371437- verb = rawAttribute ? rawAttribute.toLowerCase() : "get";
14381438- if (verb === "get") {
14391439- }
14401440- path = getRawAttribute(elt, 'action');
14411441- }
14421442- triggerSpecs.forEach(function(triggerSpec) {
14431443- addEventListener(elt, function(elt, evt) {
14441444- if (closest(elt, htmx.config.disableSelector)) {
14451445- cleanUpElement(elt)
14461446- return
14471447- }
14481448- issueAjaxRequest(verb, path, elt, evt)
14491449- }, nodeData, triggerSpec, true);
14501450- });
14511451- }
14521452- }
14531453-14541454- /**
14551455- *
14561456- * @param {Event} evt
14571457- * @param {HTMLElement} elt
14581458- * @returns
14591459- */
14601460- function shouldCancel(evt, elt) {
14611461- if (evt.type === "submit" || evt.type === "click") {
14621462- if (elt.tagName === "FORM") {
14631463- return true;
14641464- }
14651465- if (matches(elt, 'input[type="submit"], button') && closest(elt, 'form') !== null) {
14661466- return true;
14671467- }
14681468- if (elt.tagName === "A" && elt.href &&
14691469- (elt.getAttribute('href') === '#' || elt.getAttribute('href').indexOf("#") !== 0)) {
14701470- return true;
14711471- }
14721472- }
14731473- return false;
14741474- }
14751475-14761476- function ignoreBoostedAnchorCtrlClick(elt, evt) {
14771477- return getInternalData(elt).boosted && elt.tagName === "A" && evt.type === "click" && (evt.ctrlKey || evt.metaKey);
14781478- }
14791479-14801480- function maybeFilterEvent(triggerSpec, elt, evt) {
14811481- var eventFilter = triggerSpec.eventFilter;
14821482- if(eventFilter){
14831483- try {
14841484- return eventFilter.call(elt, evt) !== true;
14851485- } catch(e) {
14861486- triggerErrorEvent(getDocument().body, "htmx:eventFilter:error", {error: e, source:eventFilter.source});
14871487- return true;
14881488- }
14891489- }
14901490- return false;
14911491- }
14921492-14931493- function addEventListener(elt, handler, nodeData, triggerSpec, explicitCancel) {
14941494- var elementData = getInternalData(elt);
14951495- var eltsToListenOn;
14961496- if (triggerSpec.from) {
14971497- eltsToListenOn = querySelectorAllExt(elt, triggerSpec.from);
14981498- } else {
14991499- eltsToListenOn = [elt];
15001500- }
15011501- // store the initial values of the elements, so we can tell if they change
15021502- if (triggerSpec.changed) {
15031503- eltsToListenOn.forEach(function (eltToListenOn) {
15041504- var eltToListenOnData = getInternalData(eltToListenOn);
15051505- eltToListenOnData.lastValue = eltToListenOn.value;
15061506- })
15071507- }
15081508- forEach(eltsToListenOn, function (eltToListenOn) {
15091509- var eventListener = function (evt) {
15101510- if (!bodyContains(elt)) {
15111511- eltToListenOn.removeEventListener(triggerSpec.trigger, eventListener);
15121512- return;
15131513- }
15141514- if (ignoreBoostedAnchorCtrlClick(elt, evt)) {
15151515- return;
15161516- }
15171517- if (explicitCancel || shouldCancel(evt, elt)) {
15181518- evt.preventDefault();
15191519- }
15201520- if (maybeFilterEvent(triggerSpec, elt, evt)) {
15211521- return;
15221522- }
15231523- var eventData = getInternalData(evt);
15241524- eventData.triggerSpec = triggerSpec;
15251525- if (eventData.handledFor == null) {
15261526- eventData.handledFor = [];
15271527- }
15281528- if (eventData.handledFor.indexOf(elt) < 0) {
15291529- eventData.handledFor.push(elt);
15301530- if (triggerSpec.consume) {
15311531- evt.stopPropagation();
15321532- }
15331533- if (triggerSpec.target && evt.target) {
15341534- if (!matches(evt.target, triggerSpec.target)) {
15351535- return;
15361536- }
15371537- }
15381538- if (triggerSpec.once) {
15391539- if (elementData.triggeredOnce) {
15401540- return;
15411541- } else {
15421542- elementData.triggeredOnce = true;
15431543- }
15441544- }
15451545- if (triggerSpec.changed) {
15461546- var eltToListenOnData = getInternalData(eltToListenOn)
15471547- if (eltToListenOnData.lastValue === eltToListenOn.value) {
15481548- return;
15491549- }
15501550- eltToListenOnData.lastValue = eltToListenOn.value
15511551- }
15521552- if (elementData.delayed) {
15531553- clearTimeout(elementData.delayed);
15541554- }
15551555- if (elementData.throttle) {
15561556- return;
15571557- }
15581558-15591559- if (triggerSpec.throttle > 0) {
15601560- if (!elementData.throttle) {
15611561- handler(elt, evt);
15621562- elementData.throttle = setTimeout(function () {
15631563- elementData.throttle = null;
15641564- }, triggerSpec.throttle);
15651565- }
15661566- } else if (triggerSpec.delay > 0) {
15671567- elementData.delayed = setTimeout(function() { handler(elt, evt) }, triggerSpec.delay);
15681568- } else {
15691569- triggerEvent(elt, 'htmx:trigger')
15701570- handler(elt, evt);
15711571- }
15721572- }
15731573- };
15741574- if (nodeData.listenerInfos == null) {
15751575- nodeData.listenerInfos = [];
15761576- }
15771577- nodeData.listenerInfos.push({
15781578- trigger: triggerSpec.trigger,
15791579- listener: eventListener,
15801580- on: eltToListenOn
15811581- })
15821582- eltToListenOn.addEventListener(triggerSpec.trigger, eventListener);
15831583- });
15841584- }
15851585-15861586- var windowIsScrolling = false // used by initScrollHandler
15871587- var scrollHandler = null;
15881588- function initScrollHandler() {
15891589- if (!scrollHandler) {
15901590- scrollHandler = function() {
15911591- windowIsScrolling = true
15921592- };
15931593- window.addEventListener("scroll", scrollHandler)
15941594- setInterval(function() {
15951595- if (windowIsScrolling) {
15961596- windowIsScrolling = false;
15971597- forEach(getDocument().querySelectorAll("[hx-trigger='revealed'],[data-hx-trigger='revealed']"), function (elt) {
15981598- maybeReveal(elt);
15991599- })
16001600- }
16011601- }, 200);
16021602- }
16031603- }
16041604-16051605- function maybeReveal(elt) {
16061606- if (!hasAttribute(elt,'data-hx-revealed') && isScrolledIntoView(elt)) {
16071607- elt.setAttribute('data-hx-revealed', 'true');
16081608- var nodeData = getInternalData(elt);
16091609- if (nodeData.initHash) {
16101610- triggerEvent(elt, 'revealed');
16111611- } else {
16121612- // if the node isn't initialized, wait for it before triggering the request
16131613- elt.addEventListener("htmx:afterProcessNode", function(evt) { triggerEvent(elt, 'revealed') }, {once: true});
16141614- }
16151615- }
16161616- }
16171617-16181618- //====================================================================
16191619- // Web Sockets
16201620- //====================================================================
16211621-16221622- function processWebSocketInfo(elt, nodeData, info) {
16231623- var values = splitOnWhitespace(info);
16241624- for (var i = 0; i < values.length; i++) {
16251625- var value = values[i].split(/:(.+)/);
16261626- if (value[0] === "connect") {
16271627- ensureWebSocket(elt, value[1], 0);
16281628- }
16291629- if (value[0] === "send") {
16301630- processWebSocketSend(elt);
16311631- }
16321632- }
16331633- }
16341634-16351635- function ensureWebSocket(elt, wssSource, retryCount) {
16361636- if (!bodyContains(elt)) {
16371637- return; // stop ensuring websocket connection when socket bearing element ceases to exist
16381638- }
16391639-16401640- if (wssSource.indexOf("/") == 0) { // complete absolute paths only
16411641- var base_part = location.hostname + (location.port ? ':'+location.port: '');
16421642- if (location.protocol == 'https:') {
16431643- wssSource = "wss://" + base_part + wssSource;
16441644- } else if (location.protocol == 'http:') {
16451645- wssSource = "ws://" + base_part + wssSource;
16461646- }
16471647- }
16481648- var socket = htmx.createWebSocket(wssSource);
16491649- socket.onerror = function (e) {
16501650- triggerErrorEvent(elt, "htmx:wsError", {error:e, socket:socket});
16511651- maybeCloseWebSocketSource(elt);
16521652- };
16531653-16541654- socket.onclose = function (e) {
16551655- if ([1006, 1012, 1013].indexOf(e.code) >= 0) { // Abnormal Closure/Service Restart/Try Again Later
16561656- var delay = getWebSocketReconnectDelay(retryCount);
16571657- setTimeout(function() {
16581658- ensureWebSocket(elt, wssSource, retryCount+1); // creates a websocket with a new timeout
16591659- }, delay);
16601660- }
16611661- };
16621662- socket.onopen = function (e) {
16631663- retryCount = 0;
16641664- }
16651665-16661666- getInternalData(elt).webSocket = socket;
16671667- socket.addEventListener('message', function (event) {
16681668- if (maybeCloseWebSocketSource(elt)) {
16691669- return;
16701670- }
16711671-16721672- var response = event.data;
16731673- withExtensions(elt, function(extension){
16741674- response = extension.transformResponse(response, null, elt);
16751675- });
16761676-16771677- var settleInfo = makeSettleInfo(elt);
16781678- var fragment = makeFragment(response);
16791679- var children = toArray(fragment.children);
16801680- for (var i = 0; i < children.length; i++) {
16811681- var child = children[i];
16821682- oobSwap(getAttributeValue(child, "hx-swap-oob") || "true", child, settleInfo);
16831683- }
16841684-16851685- settleImmediately(settleInfo.tasks);
16861686- });
16871687- }
16881688-16891689- function maybeCloseWebSocketSource(elt) {
16901690- if (!bodyContains(elt)) {
16911691- getInternalData(elt).webSocket.close();
16921692- return true;
16931693- }
16941694- }
16951695-16961696- function processWebSocketSend(elt) {
16971697- var webSocketSourceElt = getClosestMatch(elt, function (parent) {
16981698- return getInternalData(parent).webSocket != null;
16991699- });
17001700- if (webSocketSourceElt) {
17011701- elt.addEventListener(getTriggerSpecs(elt)[0].trigger, function (evt) {
17021702- var webSocket = getInternalData(webSocketSourceElt).webSocket;
17031703- var headers = getHeaders(elt, webSocketSourceElt);
17041704- var results = getInputValues(elt, 'post');
17051705- var errors = results.errors;
17061706- var rawParameters = results.values;
17071707- var expressionVars = getExpressionVars(elt);
17081708- var allParameters = mergeObjects(rawParameters, expressionVars);
17091709- var filteredParameters = filterValues(allParameters, elt);
17101710- filteredParameters['HEADERS'] = headers;
17111711- if (errors && errors.length > 0) {
17121712- triggerEvent(elt, 'htmx:validation:halted', errors);
17131713- return;
17141714- }
17151715- webSocket.send(JSON.stringify(filteredParameters));
17161716- if(shouldCancel(evt, elt)){
17171717- evt.preventDefault();
17181718- }
17191719- });
17201720- } else {
17211721- triggerErrorEvent(elt, "htmx:noWebSocketSourceError");
17221722- }
17231723- }
17241724-17251725- function getWebSocketReconnectDelay(retryCount) {
17261726- var delay = htmx.config.wsReconnectDelay;
17271727- if (typeof delay === 'function') {
17281728- // @ts-ignore
17291729- return delay(retryCount);
17301730- }
17311731- if (delay === 'full-jitter') {
17321732- var exp = Math.min(retryCount, 6);
17331733- var maxDelay = 1000 * Math.pow(2, exp);
17341734- return maxDelay * Math.random();
17351735- }
17361736- logError('htmx.config.wsReconnectDelay must either be a function or the string "full-jitter"');
17371737- }
17381738-17391739- //====================================================================
17401740- // Server Sent Events
17411741- //====================================================================
17421742-17431743- function processSSEInfo(elt, nodeData, info) {
17441744- var values = splitOnWhitespace(info);
17451745- for (var i = 0; i < values.length; i++) {
17461746- var value = values[i].split(/:(.+)/);
17471747- if (value[0] === "connect") {
17481748- processSSESource(elt, value[1]);
17491749- }
17501750-17511751- if ((value[0] === "swap")) {
17521752- processSSESwap(elt, value[1])
17531753- }
17541754- }
17551755- }
17561756-17571757- function processSSESource(elt, sseSrc) {
17581758- var source = htmx.createEventSource(sseSrc);
17591759- source.onerror = function (e) {
17601760- triggerErrorEvent(elt, "htmx:sseError", {error:e, source:source});
17611761- maybeCloseSSESource(elt);
17621762- };
17631763- getInternalData(elt).sseEventSource = source;
17641764- }
17651765-17661766- function processSSESwap(elt, sseEventName) {
17671767- var sseSourceElt = getClosestMatch(elt, hasEventSource);
17681768- if (sseSourceElt) {
17691769- var sseEventSource = getInternalData(sseSourceElt).sseEventSource;
17701770- var sseListener = function (event) {
17711771- if (maybeCloseSSESource(sseSourceElt)) {
17721772- return;
17731773- }
17741774- if (!bodyContains(elt)) {
17751775- sseEventSource.removeEventListener(sseEventName, sseListener);
17761776- return;
17771777- }
17781778-17791779- ///////////////////////////
17801780- // TODO: merge this code with AJAX and WebSockets code in the future.
17811781-17821782- var response = event.data;
17831783- withExtensions(elt, function(extension){
17841784- response = extension.transformResponse(response, null, elt);
17851785- });
17861786-17871787- var swapSpec = getSwapSpecification(elt)
17881788- var target = getTarget(elt)
17891789- var settleInfo = makeSettleInfo(elt);
17901790-17911791- selectAndSwap(swapSpec.swapStyle, target, elt, response, settleInfo)
17921792- settleImmediately(settleInfo.tasks)
17931793- triggerEvent(elt, "htmx:sseMessage", event)
17941794- };
17951795-17961796- getInternalData(elt).sseListener = sseListener;
17971797- sseEventSource.addEventListener(sseEventName, sseListener);
17981798- } else {
17991799- triggerErrorEvent(elt, "htmx:noSSESourceError");
18001800- }
18011801- }
18021802-18031803- function processSSETrigger(elt, handler, sseEventName) {
18041804- var sseSourceElt = getClosestMatch(elt, hasEventSource);
18051805- if (sseSourceElt) {
18061806- var sseEventSource = getInternalData(sseSourceElt).sseEventSource;
18071807- var sseListener = function () {
18081808- if (!maybeCloseSSESource(sseSourceElt)) {
18091809- if (bodyContains(elt)) {
18101810- handler(elt);
18111811- } else {
18121812- sseEventSource.removeEventListener(sseEventName, sseListener);
18131813- }
18141814- }
18151815- };
18161816- getInternalData(elt).sseListener = sseListener;
18171817- sseEventSource.addEventListener(sseEventName, sseListener);
18181818- } else {
18191819- triggerErrorEvent(elt, "htmx:noSSESourceError");
18201820- }
18211821- }
18221822-18231823- function maybeCloseSSESource(elt) {
18241824- if (!bodyContains(elt)) {
18251825- getInternalData(elt).sseEventSource.close();
18261826- return true;
18271827- }
18281828- }
18291829-18301830- function hasEventSource(node) {
18311831- return getInternalData(node).sseEventSource != null;
18321832- }
18331833-18341834- //====================================================================
18351835-18361836- function loadImmediately(elt, handler, nodeData, delay) {
18371837- var load = function(){
18381838- if (!nodeData.loaded) {
18391839- nodeData.loaded = true;
18401840- handler(elt);
18411841- }
18421842- }
18431843- if (delay > 0) {
18441844- setTimeout(load, delay);
18451845- } else {
18461846- load();
18471847- }
18481848- }
18491849-18501850- function processVerbs(elt, nodeData, triggerSpecs) {
18511851- var explicitAction = false;
18521852- forEach(VERBS, function (verb) {
18531853- if (hasAttribute(elt,'hx-' + verb)) {
18541854- var path = getAttributeValue(elt, 'hx-' + verb);
18551855- explicitAction = true;
18561856- nodeData.path = path;
18571857- nodeData.verb = verb;
18581858- triggerSpecs.forEach(function(triggerSpec) {
18591859- addTriggerHandler(elt, triggerSpec, nodeData, function (elt, evt) {
18601860- if (closest(elt, htmx.config.disableSelector)) {
18611861- cleanUpElement(elt)
18621862- return
18631863- }
18641864- issueAjaxRequest(verb, path, elt, evt)
18651865- })
18661866- });
18671867- }
18681868- });
18691869- return explicitAction;
18701870- }
18711871-18721872- function addTriggerHandler(elt, triggerSpec, nodeData, handler) {
18731873- if (triggerSpec.sseEvent) {
18741874- processSSETrigger(elt, handler, triggerSpec.sseEvent);
18751875- } else if (triggerSpec.trigger === "revealed") {
18761876- initScrollHandler();
18771877- addEventListener(elt, handler, nodeData, triggerSpec);
18781878- maybeReveal(elt);
18791879- } else if (triggerSpec.trigger === "intersect") {
18801880- var observerOptions = {};
18811881- if (triggerSpec.root) {
18821882- observerOptions.root = querySelectorExt(elt, triggerSpec.root)
18831883- }
18841884- if (triggerSpec.threshold) {
18851885- observerOptions.threshold = parseFloat(triggerSpec.threshold);
18861886- }
18871887- var observer = new IntersectionObserver(function (entries) {
18881888- for (var i = 0; i < entries.length; i++) {
18891889- var entry = entries[i];
18901890- if (entry.isIntersecting) {
18911891- triggerEvent(elt, "intersect");
18921892- break;
18931893- }
18941894- }
18951895- }, observerOptions);
18961896- observer.observe(elt);
18971897- addEventListener(elt, handler, nodeData, triggerSpec);
18981898- } else if (triggerSpec.trigger === "load") {
18991899- if (!maybeFilterEvent(triggerSpec, elt, makeEvent("load", {elt: elt}))) {
19001900- loadImmediately(elt, handler, nodeData, triggerSpec.delay);
19011901- }
19021902- } else if (triggerSpec.pollInterval > 0) {
19031903- nodeData.polling = true;
19041904- processPolling(elt, handler, triggerSpec);
19051905- } else {
19061906- addEventListener(elt, handler, nodeData, triggerSpec);
19071907- }
19081908- }
19091909-19101910- function evalScript(script) {
19111911- if (!script.htmxExecuted && htmx.config.allowScriptTags &&
19121912- (script.type === "text/javascript" || script.type === "module" || script.type === "") ) {
19131913- var newScript = getDocument().createElement("script");
19141914- forEach(script.attributes, function (attr) {
19151915- newScript.setAttribute(attr.name, attr.value);
19161916- });
19171917- newScript.textContent = script.textContent;
19181918- newScript.async = false;
19191919- if (htmx.config.inlineScriptNonce) {
19201920- newScript.nonce = htmx.config.inlineScriptNonce;
19211921- }
19221922- var parent = script.parentElement;
19231923-19241924- try {
19251925- parent.insertBefore(newScript, script);
19261926- } catch (e) {
19271927- logError(e);
19281928- } finally {
19291929- // remove old script element, but only if it is still in DOM
19301930- if (script.parentElement) {
19311931- script.parentElement.removeChild(script);
19321932- }
19331933- }
19341934- }
19351935- }
19361936-19371937- function processScripts(elt) {
19381938- if (matches(elt, "script")) {
19391939- evalScript(elt);
19401940- }
19411941- forEach(findAll(elt, "script"), function (script) {
19421942- evalScript(script);
19431943- });
19441944- }
19451945-19461946- function shouldProcessHxOn(elt) {
19471947- var attributes = elt.attributes
19481948- for (var j = 0; j < attributes.length; j++) {
19491949- var attrName = attributes[j].name
19501950- if (startsWith(attrName, "hx-on:") || startsWith(attrName, "data-hx-on:") ||
19511951- startsWith(attrName, "hx-on-") || startsWith(attrName, "data-hx-on-")) {
19521952- return true
19531953- }
19541954- }
19551955- return false
19561956- }
19571957-19581958- function findHxOnWildcardElements(elt) {
19591959- var node = null
19601960- var elements = []
19611961-19621962- if (shouldProcessHxOn(elt)) {
19631963- elements.push(elt)
19641964- }
19651965-19661966- if (document.evaluate) {
19671967- var iter = document.evaluate('.//*[@*[ starts-with(name(), "hx-on:") or starts-with(name(), "data-hx-on:") or' +
19681968- ' starts-with(name(), "hx-on-") or starts-with(name(), "data-hx-on-") ]]', elt)
19691969- while (node = iter.iterateNext()) elements.push(node)
19701970- } else {
19711971- var allElements = elt.getElementsByTagName("*")
19721972- for (var i = 0; i < allElements.length; i++) {
19731973- if (shouldProcessHxOn(allElements[i])) {
19741974- elements.push(allElements[i])
19751975- }
19761976- }
19771977- }
19781978-19791979- return elements
19801980- }
19811981-19821982- function findElementsToProcess(elt) {
19831983- if (elt.querySelectorAll) {
19841984- var boostedSelector = ", [hx-boost] a, [data-hx-boost] a, a[hx-boost], a[data-hx-boost]";
19851985- var results = elt.querySelectorAll(VERB_SELECTOR + boostedSelector + ", form, [type='submit'], [hx-sse], [data-hx-sse], [hx-ws]," +
19861986- " [data-hx-ws], [hx-ext], [data-hx-ext], [hx-trigger], [data-hx-trigger], [hx-on], [data-hx-on]");
19871987- return results;
19881988- } else {
19891989- return [];
19901990- }
19911991- }
19921992-19931993- // Handle submit buttons/inputs that have the form attribute set
19941994- // see https://developer.mozilla.org/docs/Web/HTML/Element/button
19951995- function maybeSetLastButtonClicked(evt) {
19961996- var elt = closest(evt.target, "button, input[type='submit']");
19971997- var internalData = getRelatedFormData(evt)
19981998- if (internalData) {
19991999- internalData.lastButtonClicked = elt;
20002000- }
20012001- };
20022002- function maybeUnsetLastButtonClicked(evt){
20032003- var internalData = getRelatedFormData(evt)
20042004- if (internalData) {
20052005- internalData.lastButtonClicked = null;
20062006- }
20072007- }
20082008- function getRelatedFormData(evt) {
20092009- var elt = closest(evt.target, "button, input[type='submit']");
20102010- if (!elt) {
20112011- return;
20122012- }
20132013- var form = resolveTarget('#' + getRawAttribute(elt, 'form')) || closest(elt, 'form');
20142014- if (!form) {
20152015- return;
20162016- }
20172017- return getInternalData(form);
20182018- }
20192019- function initButtonTracking(elt) {
20202020- // need to handle both click and focus in:
20212021- // focusin - in case someone tabs in to a button and hits the space bar
20222022- // click - on OSX buttons do not focus on click see https://bugs.webkit.org/show_bug.cgi?id=13724
20232023- elt.addEventListener('click', maybeSetLastButtonClicked)
20242024- elt.addEventListener('focusin', maybeSetLastButtonClicked)
20252025- elt.addEventListener('focusout', maybeUnsetLastButtonClicked)
20262026- }
20272027-20282028- function countCurlies(line) {
20292029- var tokens = tokenizeString(line);
20302030- var netCurlies = 0;
20312031- for (var i = 0; i < tokens.length; i++) {
20322032- const token = tokens[i];
20332033- if (token === "{") {
20342034- netCurlies++;
20352035- } else if (token === "}") {
20362036- netCurlies--;
20372037- }
20382038- }
20392039- return netCurlies;
20402040- }
20412041-20422042- function addHxOnEventHandler(elt, eventName, code) {
20432043- var nodeData = getInternalData(elt);
20442044- if (!Array.isArray(nodeData.onHandlers)) {
20452045- nodeData.onHandlers = [];
20462046- }
20472047- var func;
20482048- var listener = function (e) {
20492049- return maybeEval(elt, function() {
20502050- if (!func) {
20512051- func = new Function("event", code);
20522052- }
20532053- func.call(elt, e);
20542054- });
20552055- };
20562056- elt.addEventListener(eventName, listener);
20572057- nodeData.onHandlers.push({event:eventName, listener:listener});
20582058- }
20592059-20602060- function processHxOn(elt) {
20612061- var hxOnValue = getAttributeValue(elt, 'hx-on');
20622062- if (hxOnValue) {
20632063- var handlers = {}
20642064- var lines = hxOnValue.split("\n");
20652065- var currentEvent = null;
20662066- var curlyCount = 0;
20672067- while (lines.length > 0) {
20682068- var line = lines.shift();
20692069- var match = line.match(/^\s*([a-zA-Z:\-\.]+:)(.*)/);
20702070- if (curlyCount === 0 && match) {
20712071- line.split(":")
20722072- currentEvent = match[1].slice(0, -1); // strip last colon
20732073- handlers[currentEvent] = match[2];
20742074- } else {
20752075- handlers[currentEvent] += line;
20762076- }
20772077- curlyCount += countCurlies(line);
20782078- }
20792079-20802080- for (var eventName in handlers) {
20812081- addHxOnEventHandler(elt, eventName, handlers[eventName]);
20822082- }
20832083- }
20842084- }
20852085-20862086- function processHxOnWildcard(elt) {
20872087- // wipe any previous on handlers so that this function takes precedence
20882088- deInitOnHandlers(elt)
20892089-20902090- for (var i = 0; i < elt.attributes.length; i++) {
20912091- var name = elt.attributes[i].name
20922092- var value = elt.attributes[i].value
20932093- if (startsWith(name, "hx-on") || startsWith(name, "data-hx-on")) {
20942094- var afterOnPosition = name.indexOf("-on") + 3;
20952095- var nextChar = name.slice(afterOnPosition, afterOnPosition + 1);
20962096- if (nextChar === "-" || nextChar === ":") {
20972097- var eventName = name.slice(afterOnPosition + 1);
20982098- // if the eventName starts with a colon or dash, prepend "htmx" for shorthand support
20992099- if (startsWith(eventName, ":")) {
21002100- eventName = "htmx" + eventName
21012101- } else if (startsWith(eventName, "-")) {
21022102- eventName = "htmx:" + eventName.slice(1);
21032103- } else if (startsWith(eventName, "htmx-")) {
21042104- eventName = "htmx:" + eventName.slice(5);
21052105- }
21062106-21072107- addHxOnEventHandler(elt, eventName, value)
21082108- }
21092109- }
21102110- }
21112111- }
21122112-21132113- function initNode(elt) {
21142114- if (closest(elt, htmx.config.disableSelector)) {
21152115- cleanUpElement(elt)
21162116- return;
21172117- }
21182118- var nodeData = getInternalData(elt);
21192119- if (nodeData.initHash !== attributeHash(elt)) {
21202120- // clean up any previously processed info
21212121- deInitNode(elt);
21222122-21232123- nodeData.initHash = attributeHash(elt);
21242124-21252125- processHxOn(elt);
21262126-21272127- triggerEvent(elt, "htmx:beforeProcessNode")
21282128-21292129- if (elt.value) {
21302130- nodeData.lastValue = elt.value;
21312131- }
21322132-21332133- var triggerSpecs = getTriggerSpecs(elt);
21342134- var hasExplicitHttpAction = processVerbs(elt, nodeData, triggerSpecs);
21352135-21362136- if (!hasExplicitHttpAction) {
21372137- if (getClosestAttributeValue(elt, "hx-boost") === "true") {
21382138- boostElement(elt, nodeData, triggerSpecs);
21392139- } else if (hasAttribute(elt, 'hx-trigger')) {
21402140- triggerSpecs.forEach(function (triggerSpec) {
21412141- // For "naked" triggers, don't do anything at all
21422142- addTriggerHandler(elt, triggerSpec, nodeData, function () {
21432143- })
21442144- })
21452145- }
21462146- }
21472147-21482148- // Handle submit buttons/inputs that have the form attribute set
21492149- // see https://developer.mozilla.org/docs/Web/HTML/Element/button
21502150- if (elt.tagName === "FORM" || (getRawAttribute(elt, "type") === "submit" && hasAttribute(elt, "form"))) {
21512151- initButtonTracking(elt)
21522152- }
21532153-21542154- var sseInfo = getAttributeValue(elt, 'hx-sse');
21552155- if (sseInfo) {
21562156- processSSEInfo(elt, nodeData, sseInfo);
21572157- }
21582158-21592159- var wsInfo = getAttributeValue(elt, 'hx-ws');
21602160- if (wsInfo) {
21612161- processWebSocketInfo(elt, nodeData, wsInfo);
21622162- }
21632163- triggerEvent(elt, "htmx:afterProcessNode");
21642164- }
21652165- }
21662166-21672167- function processNode(elt) {
21682168- elt = resolveTarget(elt);
21692169- if (closest(elt, htmx.config.disableSelector)) {
21702170- cleanUpElement(elt)
21712171- return;
21722172- }
21732173- initNode(elt);
21742174- forEach(findElementsToProcess(elt), function(child) { initNode(child) });
21752175- // Because it happens second, the new way of adding onHandlers superseeds the old one
21762176- // i.e. if there are any hx-on:eventName attributes, the hx-on attribute will be ignored
21772177- forEach(findHxOnWildcardElements(elt), processHxOnWildcard);
21782178- }
21792179-21802180- //====================================================================
21812181- // Event/Log Support
21822182- //====================================================================
21832183-21842184- function kebabEventName(str) {
21852185- return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
21862186- }
21872187-21882188- function makeEvent(eventName, detail) {
21892189- var evt;
21902190- if (window.CustomEvent && typeof window.CustomEvent === 'function') {
21912191- evt = new CustomEvent(eventName, {bubbles: true, cancelable: true, detail: detail});
21922192- } else {
21932193- evt = getDocument().createEvent('CustomEvent');
21942194- evt.initCustomEvent(eventName, true, true, detail);
21952195- }
21962196- return evt;
21972197- }
21982198-21992199- function triggerErrorEvent(elt, eventName, detail) {
22002200- triggerEvent(elt, eventName, mergeObjects({error:eventName}, detail));
22012201- }
22022202-22032203- function ignoreEventForLogging(eventName) {
22042204- return eventName === "htmx:afterProcessNode"
22052205- }
22062206-22072207- /**
22082208- * `withExtensions` locates all active extensions for a provided element, then
22092209- * executes the provided function using each of the active extensions. It should
22102210- * be called internally at every extendable execution point in htmx.
22112211- *
22122212- * @param {HTMLElement} elt
22132213- * @param {(extension:import("./htmx").HtmxExtension) => void} toDo
22142214- * @returns void
22152215- */
22162216- function withExtensions(elt, toDo) {
22172217- forEach(getExtensions(elt), function(extension){
22182218- try {
22192219- toDo(extension);
22202220- } catch (e) {
22212221- logError(e);
22222222- }
22232223- });
22242224- }
22252225-22262226- function logError(msg) {
22272227- if(console.error) {
22282228- console.error(msg);
22292229- } else if (console.log) {
22302230- console.log("ERROR: ", msg);
22312231- }
22322232- }
22332233-22342234- function triggerEvent(elt, eventName, detail) {
22352235- elt = resolveTarget(elt);
22362236- if (detail == null) {
22372237- detail = {};
22382238- }
22392239- detail["elt"] = elt;
22402240- var event = makeEvent(eventName, detail);
22412241- if (htmx.logger && !ignoreEventForLogging(eventName)) {
22422242- htmx.logger(elt, eventName, detail);
22432243- }
22442244- if (detail.error) {
22452245- logError(detail.error);
22462246- triggerEvent(elt, "htmx:error", {errorInfo:detail})
22472247- }
22482248- var eventResult = elt.dispatchEvent(event);
22492249- var kebabName = kebabEventName(eventName);
22502250- if (eventResult && kebabName !== eventName) {
22512251- var kebabedEvent = makeEvent(kebabName, event.detail);
22522252- eventResult = eventResult && elt.dispatchEvent(kebabedEvent)
22532253- }
22542254- withExtensions(elt, function (extension) {
22552255- eventResult = eventResult && (extension.onEvent(eventName, event) !== false && !event.defaultPrevented)
22562256- });
22572257- return eventResult;
22582258- }
22592259-22602260- //====================================================================
22612261- // History Support
22622262- //====================================================================
22632263- var currentPathForHistory = location.pathname+location.search;
22642264-22652265- function getHistoryElement() {
22662266- var historyElt = getDocument().querySelector('[hx-history-elt],[data-hx-history-elt]');
22672267- return historyElt || getDocument().body;
22682268- }
22692269-22702270- function saveToHistoryCache(url, content, title, scroll) {
22712271- if (!canAccessLocalStorage()) {
22722272- return;
22732273- }
22742274-22752275- if (htmx.config.historyCacheSize <= 0) {
22762276- // make sure that an eventually already existing cache is purged
22772277- localStorage.removeItem("htmx-history-cache");
22782278- return;
22792279- }
22802280-22812281- url = normalizePath(url);
22822282-22832283- var historyCache = parseJSON(localStorage.getItem("htmx-history-cache")) || [];
22842284- for (var i = 0; i < historyCache.length; i++) {
22852285- if (historyCache[i].url === url) {
22862286- historyCache.splice(i, 1);
22872287- break;
22882288- }
22892289- }
22902290- var newHistoryItem = {url:url, content: content, title:title, scroll:scroll};
22912291- triggerEvent(getDocument().body, "htmx:historyItemCreated", {item:newHistoryItem, cache: historyCache})
22922292- historyCache.push(newHistoryItem)
22932293- while (historyCache.length > htmx.config.historyCacheSize) {
22942294- historyCache.shift();
22952295- }
22962296- while(historyCache.length > 0){
22972297- try {
22982298- localStorage.setItem("htmx-history-cache", JSON.stringify(historyCache));
22992299- break;
23002300- } catch (e) {
23012301- triggerErrorEvent(getDocument().body, "htmx:historyCacheError", {cause:e, cache: historyCache})
23022302- historyCache.shift(); // shrink the cache and retry
23032303- }
23042304- }
23052305- }
23062306-23072307- function getCachedHistory(url) {
23082308- if (!canAccessLocalStorage()) {
23092309- return null;
23102310- }
23112311-23122312- url = normalizePath(url);
23132313-23142314- var historyCache = parseJSON(localStorage.getItem("htmx-history-cache")) || [];
23152315- for (var i = 0; i < historyCache.length; i++) {
23162316- if (historyCache[i].url === url) {
23172317- return historyCache[i];
23182318- }
23192319- }
23202320- return null;
23212321- }
23222322-23232323- function cleanInnerHtmlForHistory(elt) {
23242324- var className = htmx.config.requestClass;
23252325- var clone = elt.cloneNode(true);
23262326- forEach(findAll(clone, "." + className), function(child){
23272327- removeClassFromElement(child, className);
23282328- });
23292329- return clone.innerHTML;
23302330- }
23312331-23322332- function saveCurrentPageToHistory() {
23332333- var elt = getHistoryElement();
23342334- var path = currentPathForHistory || location.pathname+location.search;
23352335-23362336- // Allow history snapshot feature to be disabled where hx-history="false"
23372337- // is present *anywhere* in the current document we're about to save,
23382338- // so we can prevent privileged data entering the cache.
23392339- // The page will still be reachable as a history entry, but htmx will fetch it
23402340- // live from the server onpopstate rather than look in the localStorage cache
23412341- var disableHistoryCache
23422342- try {
23432343- disableHistoryCache = getDocument().querySelector('[hx-history="false" i],[data-hx-history="false" i]')
23442344- } catch (e) {
23452345- // IE11: insensitive modifier not supported so fallback to case sensitive selector
23462346- disableHistoryCache = getDocument().querySelector('[hx-history="false"],[data-hx-history="false"]')
23472347- }
23482348- if (!disableHistoryCache) {
23492349- triggerEvent(getDocument().body, "htmx:beforeHistorySave", {path: path, historyElt: elt});
23502350- saveToHistoryCache(path, cleanInnerHtmlForHistory(elt), getDocument().title, window.scrollY);
23512351- }
23522352-23532353- if (htmx.config.historyEnabled) history.replaceState({htmx: true}, getDocument().title, window.location.href);
23542354- }
23552355-23562356- function pushUrlIntoHistory(path) {
23572357- // remove the cache buster parameter, if any
23582358- if (htmx.config.getCacheBusterParam) {
23592359- path = path.replace(/org\.htmx\.cache-buster=[^&]*&?/, '')
23602360- if (endsWith(path, '&') || endsWith(path, "?")) {
23612361- path = path.slice(0, -1);
23622362- }
23632363- }
23642364- if(htmx.config.historyEnabled) {
23652365- history.pushState({htmx:true}, "", path);
23662366- }
23672367- currentPathForHistory = path;
23682368- }
23692369-23702370- function replaceUrlInHistory(path) {
23712371- if(htmx.config.historyEnabled) history.replaceState({htmx:true}, "", path);
23722372- currentPathForHistory = path;
23732373- }
23742374-23752375- function settleImmediately(tasks) {
23762376- forEach(tasks, function (task) {
23772377- task.call();
23782378- });
23792379- }
23802380-23812381- function loadHistoryFromServer(path) {
23822382- var request = new XMLHttpRequest();
23832383- var details = {path: path, xhr:request};
23842384- triggerEvent(getDocument().body, "htmx:historyCacheMiss", details);
23852385- request.open('GET', path, true);
23862386- request.setRequestHeader("HX-Request", "true");
23872387- request.setRequestHeader("HX-History-Restore-Request", "true");
23882388- request.setRequestHeader("HX-Current-URL", getDocument().location.href);
23892389- request.onload = function () {
23902390- if (this.status >= 200 && this.status < 400) {
23912391- triggerEvent(getDocument().body, "htmx:historyCacheMissLoad", details);
23922392- var fragment = makeFragment(this.response);
23932393- // @ts-ignore
23942394- fragment = fragment.querySelector('[hx-history-elt],[data-hx-history-elt]') || fragment;
23952395- var historyElement = getHistoryElement();
23962396- var settleInfo = makeSettleInfo(historyElement);
23972397- var title = findTitle(this.response);
23982398- if (title) {
23992399- var titleElt = find("title");
24002400- if (titleElt) {
24012401- titleElt.innerHTML = title;
24022402- } else {
24032403- window.document.title = title;
24042404- }
24052405- }
24062406- // @ts-ignore
24072407- swapInnerHTML(historyElement, fragment, settleInfo)
24082408- settleImmediately(settleInfo.tasks);
24092409- currentPathForHistory = path;
24102410- triggerEvent(getDocument().body, "htmx:historyRestore", {path: path, cacheMiss:true, serverResponse:this.response});
24112411- } else {
24122412- triggerErrorEvent(getDocument().body, "htmx:historyCacheMissLoadError", details);
24132413- }
24142414- };
24152415- request.send();
24162416- }
24172417-24182418- function restoreHistory(path) {
24192419- saveCurrentPageToHistory();
24202420- path = path || location.pathname+location.search;
24212421- var cached = getCachedHistory(path);
24222422- if (cached) {
24232423- var fragment = makeFragment(cached.content);
24242424- var historyElement = getHistoryElement();
24252425- var settleInfo = makeSettleInfo(historyElement);
24262426- swapInnerHTML(historyElement, fragment, settleInfo)
24272427- settleImmediately(settleInfo.tasks);
24282428- document.title = cached.title;
24292429- setTimeout(function () {
24302430- window.scrollTo(0, cached.scroll);
24312431- }, 0); // next 'tick', so browser has time to render layout
24322432- currentPathForHistory = path;
24332433- triggerEvent(getDocument().body, "htmx:historyRestore", {path:path, item:cached});
24342434- } else {
24352435- if (htmx.config.refreshOnHistoryMiss) {
24362436-24372437- // @ts-ignore: optional parameter in reload() function throws error
24382438- window.location.reload(true);
24392439- } else {
24402440- loadHistoryFromServer(path);
24412441- }
24422442- }
24432443- }
24442444-24452445- function addRequestIndicatorClasses(elt) {
24462446- var indicators = findAttributeTargets(elt, 'hx-indicator');
24472447- if (indicators == null) {
24482448- indicators = [elt];
24492449- }
24502450- forEach(indicators, function (ic) {
24512451- var internalData = getInternalData(ic);
24522452- internalData.requestCount = (internalData.requestCount || 0) + 1;
24532453- ic.classList["add"].call(ic.classList, htmx.config.requestClass);
24542454- });
24552455- return indicators;
24562456- }
24572457-24582458- function disableElements(elt) {
24592459- var disabledElts = findAttributeTargets(elt, 'hx-disabled-elt');
24602460- if (disabledElts == null) {
24612461- disabledElts = [];
24622462- }
24632463- forEach(disabledElts, function (disabledElement) {
24642464- var internalData = getInternalData(disabledElement);
24652465- internalData.requestCount = (internalData.requestCount || 0) + 1;
24662466- disabledElement.setAttribute("disabled", "");
24672467- });
24682468- return disabledElts;
24692469- }
24702470-24712471- function removeRequestIndicators(indicators, disabled) {
24722472- forEach(indicators, function (ic) {
24732473- var internalData = getInternalData(ic);
24742474- internalData.requestCount = (internalData.requestCount || 0) - 1;
24752475- if (internalData.requestCount === 0) {
24762476- ic.classList["remove"].call(ic.classList, htmx.config.requestClass);
24772477- }
24782478- });
24792479- forEach(disabled, function (disabledElement) {
24802480- var internalData = getInternalData(disabledElement);
24812481- internalData.requestCount = (internalData.requestCount || 0) - 1;
24822482- if (internalData.requestCount === 0) {
24832483- disabledElement.removeAttribute('disabled');
24842484- }
24852485- });
24862486- }
24872487-24882488- //====================================================================
24892489- // Input Value Processing
24902490- //====================================================================
24912491-24922492- function haveSeenNode(processed, elt) {
24932493- for (var i = 0; i < processed.length; i++) {
24942494- var node = processed[i];
24952495- if (node.isSameNode(elt)) {
24962496- return true;
24972497- }
24982498- }
24992499- return false;
25002500- }
25012501-25022502- function shouldInclude(elt) {
25032503- if(elt.name === "" || elt.name == null || elt.disabled || closest(elt, "fieldset[disabled]")) {
25042504- return false;
25052505- }
25062506- // ignore "submitter" types (see jQuery src/serialize.js)
25072507- if (elt.type === "button" || elt.type === "submit" || elt.tagName === "image" || elt.tagName === "reset" || elt.tagName === "file" ) {
25082508- return false;
25092509- }
25102510- if (elt.type === "checkbox" || elt.type === "radio" ) {
25112511- return elt.checked;
25122512- }
25132513- return true;
25142514- }
25152515-25162516- function addValueToValues(name, value, values) {
25172517- // This is a little ugly because both the current value of the named value in the form
25182518- // and the new value could be arrays, so we have to handle all four cases :/
25192519- if (name != null && value != null) {
25202520- var current = values[name];
25212521- if (current === undefined) {
25222522- values[name] = value;
25232523- } else if (Array.isArray(current)) {
25242524- if (Array.isArray(value)) {
25252525- values[name] = current.concat(value);
25262526- } else {
25272527- current.push(value);
25282528- }
25292529- } else {
25302530- if (Array.isArray(value)) {
25312531- values[name] = [current].concat(value);
25322532- } else {
25332533- values[name] = [current, value];
25342534- }
25352535- }
25362536- }
25372537- }
25382538-25392539- function processInputValue(processed, values, errors, elt, validate) {
25402540- if (elt == null || haveSeenNode(processed, elt)) {
25412541- return;
25422542- } else {
25432543- processed.push(elt);
25442544- }
25452545- if (shouldInclude(elt)) {
25462546- var name = getRawAttribute(elt,"name");
25472547- var value = elt.value;
25482548- if (elt.multiple && elt.tagName === "SELECT") {
25492549- value = toArray(elt.querySelectorAll("option:checked")).map(function (e) { return e.value });
25502550- }
25512551- // include file inputs
25522552- if (elt.files) {
25532553- value = toArray(elt.files);
25542554- }
25552555- addValueToValues(name, value, values);
25562556- if (validate) {
25572557- validateElement(elt, errors);
25582558- }
25592559- }
25602560- if (matches(elt, 'form')) {
25612561- var inputs = elt.elements;
25622562- forEach(inputs, function(input) {
25632563- processInputValue(processed, values, errors, input, validate);
25642564- });
25652565- }
25662566- }
25672567-25682568- function validateElement(element, errors) {
25692569- if (element.willValidate) {
25702570- triggerEvent(element, "htmx:validation:validate")
25712571- if (!element.checkValidity()) {
25722572- errors.push({elt: element, message:element.validationMessage, validity:element.validity});
25732573- triggerEvent(element, "htmx:validation:failed", {message:element.validationMessage, validity:element.validity})
25742574- }
25752575- }
25762576- }
25772577-25782578- /**
25792579- * @param {HTMLElement} elt
25802580- * @param {string} verb
25812581- */
25822582- function getInputValues(elt, verb) {
25832583- var processed = [];
25842584- var values = {};
25852585- var formValues = {};
25862586- var errors = [];
25872587- var internalData = getInternalData(elt);
25882588- if (internalData.lastButtonClicked && !bodyContains(internalData.lastButtonClicked)) {
25892589- internalData.lastButtonClicked = null
25902590- }
25912591-25922592- // only validate when form is directly submitted and novalidate or formnovalidate are not set
25932593- // or if the element has an explicit hx-validate="true" on it
25942594- var validate = (matches(elt, 'form') && elt.noValidate !== true) || getAttributeValue(elt, "hx-validate") === "true";
25952595- if (internalData.lastButtonClicked) {
25962596- validate = validate && internalData.lastButtonClicked.formNoValidate !== true;
25972597- }
25982598-25992599- // for a non-GET include the closest form
26002600- if (verb !== 'get') {
26012601- processInputValue(processed, formValues, errors, closest(elt, 'form'), validate);
26022602- }
26032603-26042604- // include the element itself
26052605- processInputValue(processed, values, errors, elt, validate);
26062606-26072607- // if a button or submit was clicked last, include its value
26082608- if (internalData.lastButtonClicked || elt.tagName === "BUTTON" ||
26092609- (elt.tagName === "INPUT" && getRawAttribute(elt, "type") === "submit")) {
26102610- var button = internalData.lastButtonClicked || elt
26112611- var name = getRawAttribute(button, "name")
26122612- addValueToValues(name, button.value, formValues)
26132613- }
26142614-26152615- // include any explicit includes
26162616- var includes = findAttributeTargets(elt, "hx-include");
26172617- forEach(includes, function(node) {
26182618- processInputValue(processed, values, errors, node, validate);
26192619- // if a non-form is included, include any input values within it
26202620- if (!matches(node, 'form')) {
26212621- forEach(node.querySelectorAll(INPUT_SELECTOR), function (descendant) {
26222622- processInputValue(processed, values, errors, descendant, validate);
26232623- })
26242624- }
26252625- });
26262626-26272627- // form values take precedence, overriding the regular values
26282628- values = mergeObjects(values, formValues);
26292629-26302630- return {errors:errors, values:values};
26312631- }
26322632-26332633- function appendParam(returnStr, name, realValue) {
26342634- if (returnStr !== "") {
26352635- returnStr += "&";
26362636- }
26372637- if (String(realValue) === "[object Object]") {
26382638- realValue = JSON.stringify(realValue);
26392639- }
26402640- var s = encodeURIComponent(realValue);
26412641- returnStr += encodeURIComponent(name) + "=" + s;
26422642- return returnStr;
26432643- }
26442644-26452645- function urlEncode(values) {
26462646- var returnStr = "";
26472647- for (var name in values) {
26482648- if (values.hasOwnProperty(name)) {
26492649- var value = values[name];
26502650- if (Array.isArray(value)) {
26512651- forEach(value, function(v) {
26522652- returnStr = appendParam(returnStr, name, v);
26532653- });
26542654- } else {
26552655- returnStr = appendParam(returnStr, name, value);
26562656- }
26572657- }
26582658- }
26592659- return returnStr;
26602660- }
26612661-26622662- function makeFormData(values) {
26632663- var formData = new FormData();
26642664- for (var name in values) {
26652665- if (values.hasOwnProperty(name)) {
26662666- var value = values[name];
26672667- if (Array.isArray(value)) {
26682668- forEach(value, function(v) {
26692669- formData.append(name, v);
26702670- });
26712671- } else {
26722672- formData.append(name, value);
26732673- }
26742674- }
26752675- }
26762676- return formData;
26772677- }
26782678-26792679- //====================================================================
26802680- // Ajax
26812681- //====================================================================
26822682-26832683- /**
26842684- * @param {HTMLElement} elt
26852685- * @param {HTMLElement} target
26862686- * @param {string} prompt
26872687- * @returns {Object} // TODO: Define/Improve HtmxHeaderSpecification
26882688- */
26892689- function getHeaders(elt, target, prompt) {
26902690- var headers = {
26912691- "HX-Request" : "true",
26922692- "HX-Trigger" : getRawAttribute(elt, "id"),
26932693- "HX-Trigger-Name" : getRawAttribute(elt, "name"),
26942694- "HX-Target" : getAttributeValue(target, "id"),
26952695- "HX-Current-URL" : getDocument().location.href,
26962696- }
26972697- getValuesForElement(elt, "hx-headers", false, headers)
26982698- if (prompt !== undefined) {
26992699- headers["HX-Prompt"] = prompt;
27002700- }
27012701- if (getInternalData(elt).boosted) {
27022702- headers["HX-Boosted"] = "true";
27032703- }
27042704- return headers;
27052705- }
27062706-27072707- /**
27082708- * filterValues takes an object containing form input values
27092709- * and returns a new object that only contains keys that are
27102710- * specified by the closest "hx-params" attribute
27112711- * @param {Object} inputValues
27122712- * @param {HTMLElement} elt
27132713- * @returns {Object}
27142714- */
27152715- function filterValues(inputValues, elt) {
27162716- var paramsValue = getClosestAttributeValue(elt, "hx-params");
27172717- if (paramsValue) {
27182718- if (paramsValue === "none") {
27192719- return {};
27202720- } else if (paramsValue === "*") {
27212721- return inputValues;
27222722- } else if(paramsValue.indexOf("not ") === 0) {
27232723- forEach(paramsValue.substr(4).split(","), function (name) {
27242724- name = name.trim();
27252725- delete inputValues[name];
27262726- });
27272727- return inputValues;
27282728- } else {
27292729- var newValues = {}
27302730- forEach(paramsValue.split(","), function (name) {
27312731- name = name.trim();
27322732- newValues[name] = inputValues[name];
27332733- });
27342734- return newValues;
27352735- }
27362736- } else {
27372737- return inputValues;
27382738- }
27392739- }
27402740-27412741- function isAnchorLink(elt) {
27422742- return getRawAttribute(elt, 'href') && getRawAttribute(elt, 'href').indexOf("#") >=0
27432743- }
27442744-27452745- /**
27462746- *
27472747- * @param {HTMLElement} elt
27482748- * @param {string} swapInfoOverride
27492749- * @returns {import("./htmx").HtmxSwapSpecification}
27502750- */
27512751- function getSwapSpecification(elt, swapInfoOverride) {
27522752- var swapInfo = swapInfoOverride ? swapInfoOverride : getClosestAttributeValue(elt, "hx-swap");
27532753- var swapSpec = {
27542754- "swapStyle" : getInternalData(elt).boosted ? 'innerHTML' : htmx.config.defaultSwapStyle,
27552755- "swapDelay" : htmx.config.defaultSwapDelay,
27562756- "settleDelay" : htmx.config.defaultSettleDelay
27572757- }
27582758- if (htmx.config.scrollIntoViewOnBoost && getInternalData(elt).boosted && !isAnchorLink(elt)) {
27592759- swapSpec["show"] = "top"
27602760- }
27612761- if (swapInfo) {
27622762- var split = splitOnWhitespace(swapInfo);
27632763- if (split.length > 0) {
27642764- for (var i = 0; i < split.length; i++) {
27652765- var value = split[i];
27662766- if (value.indexOf("swap:") === 0) {
27672767- swapSpec["swapDelay"] = parseInterval(value.substr(5));
27682768- } else if (value.indexOf("settle:") === 0) {
27692769- swapSpec["settleDelay"] = parseInterval(value.substr(7));
27702770- } else if (value.indexOf("transition:") === 0) {
27712771- swapSpec["transition"] = value.substr(11) === "true";
27722772- } else if (value.indexOf("ignoreTitle:") === 0) {
27732773- swapSpec["ignoreTitle"] = value.substr(12) === "true";
27742774- } else if (value.indexOf("scroll:") === 0) {
27752775- var scrollSpec = value.substr(7);
27762776- var splitSpec = scrollSpec.split(":");
27772777- var scrollVal = splitSpec.pop();
27782778- var selectorVal = splitSpec.length > 0 ? splitSpec.join(":") : null;
27792779- swapSpec["scroll"] = scrollVal;
27802780- swapSpec["scrollTarget"] = selectorVal;
27812781- } else if (value.indexOf("show:") === 0) {
27822782- var showSpec = value.substr(5);
27832783- var splitSpec = showSpec.split(":");
27842784- var showVal = splitSpec.pop();
27852785- var selectorVal = splitSpec.length > 0 ? splitSpec.join(":") : null;
27862786- swapSpec["show"] = showVal;
27872787- swapSpec["showTarget"] = selectorVal;
27882788- } else if (value.indexOf("focus-scroll:") === 0) {
27892789- var focusScrollVal = value.substr("focus-scroll:".length);
27902790- swapSpec["focusScroll"] = focusScrollVal == "true";
27912791- } else if (i == 0) {
27922792- swapSpec["swapStyle"] = value;
27932793- } else {
27942794- logError('Unknown modifier in hx-swap: ' + value);
27952795- }
27962796- }
27972797- }
27982798- }
27992799- return swapSpec;
28002800- }
28012801-28022802- function usesFormData(elt) {
28032803- return getClosestAttributeValue(elt, "hx-encoding") === "multipart/form-data" ||
28042804- (matches(elt, "form") && getRawAttribute(elt, 'enctype') === "multipart/form-data");
28052805- }
28062806-28072807- function encodeParamsForBody(xhr, elt, filteredParameters) {
28082808- var encodedParameters = null;
28092809- withExtensions(elt, function (extension) {
28102810- if (encodedParameters == null) {
28112811- encodedParameters = extension.encodeParameters(xhr, filteredParameters, elt);
28122812- }
28132813- });
28142814- if (encodedParameters != null) {
28152815- return encodedParameters;
28162816- } else {
28172817- if (usesFormData(elt)) {
28182818- return makeFormData(filteredParameters);
28192819- } else {
28202820- return urlEncode(filteredParameters);
28212821- }
28222822- }
28232823- }
28242824-28252825- /**
28262826- *
28272827- * @param {Element} target
28282828- * @returns {import("./htmx").HtmxSettleInfo}
28292829- */
28302830- function makeSettleInfo(target) {
28312831- return {tasks: [], elts: [target]};
28322832- }
28332833-28342834- function updateScrollState(content, swapSpec) {
28352835- var first = content[0];
28362836- var last = content[content.length - 1];
28372837- if (swapSpec.scroll) {
28382838- var target = null;
28392839- if (swapSpec.scrollTarget) {
28402840- target = querySelectorExt(first, swapSpec.scrollTarget);
28412841- }
28422842- if (swapSpec.scroll === "top" && (first || target)) {
28432843- target = target || first;
28442844- target.scrollTop = 0;
28452845- }
28462846- if (swapSpec.scroll === "bottom" && (last || target)) {
28472847- target = target || last;
28482848- target.scrollTop = target.scrollHeight;
28492849- }
28502850- }
28512851- if (swapSpec.show) {
28522852- var target = null;
28532853- if (swapSpec.showTarget) {
28542854- var targetStr = swapSpec.showTarget;
28552855- if (swapSpec.showTarget === "window") {
28562856- targetStr = "body";
28572857- }
28582858- target = querySelectorExt(first, targetStr);
28592859- }
28602860- if (swapSpec.show === "top" && (first || target)) {
28612861- target = target || first;
28622862- target.scrollIntoView({block:'start', behavior: htmx.config.scrollBehavior});
28632863- }
28642864- if (swapSpec.show === "bottom" && (last || target)) {
28652865- target = target || last;
28662866- target.scrollIntoView({block:'end', behavior: htmx.config.scrollBehavior});
28672867- }
28682868- }
28692869- }
28702870-28712871- /**
28722872- * @param {HTMLElement} elt
28732873- * @param {string} attr
28742874- * @param {boolean=} evalAsDefault
28752875- * @param {Object=} values
28762876- * @returns {Object}
28772877- */
28782878- function getValuesForElement(elt, attr, evalAsDefault, values) {
28792879- if (values == null) {
28802880- values = {};
28812881- }
28822882- if (elt == null) {
28832883- return values;
28842884- }
28852885- var attributeValue = getAttributeValue(elt, attr);
28862886- if (attributeValue) {
28872887- var str = attributeValue.trim();
28882888- var evaluateValue = evalAsDefault;
28892889- if (str === "unset") {
28902890- return null;
28912891- }
28922892- if (str.indexOf("javascript:") === 0) {
28932893- str = str.substr(11);
28942894- evaluateValue = true;
28952895- } else if (str.indexOf("js:") === 0) {
28962896- str = str.substr(3);
28972897- evaluateValue = true;
28982898- }
28992899- if (str.indexOf('{') !== 0) {
29002900- str = "{" + str + "}";
29012901- }
29022902- var varsValues;
29032903- if (evaluateValue) {
29042904- varsValues = maybeEval(elt,function () {return Function("return (" + str + ")")();}, {});
29052905- } else {
29062906- varsValues = parseJSON(str);
29072907- }
29082908- for (var key in varsValues) {
29092909- if (varsValues.hasOwnProperty(key)) {
29102910- if (values[key] == null) {
29112911- values[key] = varsValues[key];
29122912- }
29132913- }
29142914- }
29152915- }
29162916- return getValuesForElement(parentElt(elt), attr, evalAsDefault, values);
29172917- }
29182918-29192919- function maybeEval(elt, toEval, defaultVal) {
29202920- if (htmx.config.allowEval) {
29212921- return toEval();
29222922- } else {
29232923- triggerErrorEvent(elt, 'htmx:evalDisallowedError');
29242924- return defaultVal;
29252925- }
29262926- }
29272927-29282928- /**
29292929- * @param {HTMLElement} elt
29302930- * @param {*} expressionVars
29312931- * @returns
29322932- */
29332933- function getHXVarsForElement(elt, expressionVars) {
29342934- return getValuesForElement(elt, "hx-vars", true, expressionVars);
29352935- }
29362936-29372937- /**
29382938- * @param {HTMLElement} elt
29392939- * @param {*} expressionVars
29402940- * @returns
29412941- */
29422942- function getHXValsForElement(elt, expressionVars) {
29432943- return getValuesForElement(elt, "hx-vals", false, expressionVars);
29442944- }
29452945-29462946- /**
29472947- * @param {HTMLElement} elt
29482948- * @returns {Object}
29492949- */
29502950- function getExpressionVars(elt) {
29512951- return mergeObjects(getHXVarsForElement(elt), getHXValsForElement(elt));
29522952- }
29532953-29542954- function safelySetHeaderValue(xhr, header, headerValue) {
29552955- if (headerValue !== null) {
29562956- try {
29572957- xhr.setRequestHeader(header, headerValue);
29582958- } catch (e) {
29592959- // On an exception, try to set the header URI encoded instead
29602960- xhr.setRequestHeader(header, encodeURIComponent(headerValue));
29612961- xhr.setRequestHeader(header + "-URI-AutoEncoded", "true");
29622962- }
29632963- }
29642964- }
29652965-29662966- function getPathFromResponse(xhr) {
29672967- // NB: IE11 does not support this stuff
29682968- if (xhr.responseURL && typeof(URL) !== "undefined") {
29692969- try {
29702970- var url = new URL(xhr.responseURL);
29712971- return url.pathname + url.search;
29722972- } catch (e) {
29732973- triggerErrorEvent(getDocument().body, "htmx:badResponseUrl", {url: xhr.responseURL});
29742974- }
29752975- }
29762976- }
29772977-29782978- function hasHeader(xhr, regexp) {
29792979- return regexp.test(xhr.getAllResponseHeaders())
29802980- }
29812981-29822982- function ajaxHelper(verb, path, context) {
29832983- verb = verb.toLowerCase();
29842984- if (context) {
29852985- if (context instanceof Element || isType(context, 'String')) {
29862986- return issueAjaxRequest(verb, path, null, null, {
29872987- targetOverride: resolveTarget(context),
29882988- returnPromise: true
29892989- });
29902990- } else {
29912991- return issueAjaxRequest(verb, path, resolveTarget(context.source), context.event,
29922992- {
29932993- handler : context.handler,
29942994- headers : context.headers,
29952995- values : context.values,
29962996- targetOverride: resolveTarget(context.target),
29972997- swapOverride: context.swap,
29982998- select: context.select,
29992999- returnPromise: true
30003000- });
30013001- }
30023002- } else {
30033003- return issueAjaxRequest(verb, path, null, null, {
30043004- returnPromise: true
30053005- });
30063006- }
30073007- }
30083008-30093009- function hierarchyForElt(elt) {
30103010- var arr = [];
30113011- while (elt) {
30123012- arr.push(elt);
30133013- elt = elt.parentElement;
30143014- }
30153015- return arr;
30163016- }
30173017-30183018- function verifyPath(elt, path, requestConfig) {
30193019- var sameHost
30203020- var url
30213021- if (typeof URL === "function") {
30223022- url = new URL(path, document.location.href);
30233023- var origin = document.location.origin;
30243024- sameHost = origin === url.origin;
30253025- } else {
30263026- // IE11 doesn't support URL
30273027- url = path
30283028- sameHost = startsWith(path, document.location.origin)
30293029- }
30303030-30313031- if (htmx.config.selfRequestsOnly) {
30323032- if (!sameHost) {
30333033- return false;
30343034- }
30353035- }
30363036- return triggerEvent(elt, "htmx:validateUrl", mergeObjects({url: url, sameHost: sameHost}, requestConfig));
30373037- }
30383038-30393039- function issueAjaxRequest(verb, path, elt, event, etc, confirmed) {
30403040- var resolve = null;
30413041- var reject = null;
30423042- etc = etc != null ? etc : {};
30433043- if(etc.returnPromise && typeof Promise !== "undefined"){
30443044- var promise = new Promise(function (_resolve, _reject) {
30453045- resolve = _resolve;
30463046- reject = _reject;
30473047- });
30483048- }
30493049- if(elt == null) {
30503050- elt = getDocument().body;
30513051- }
30523052- var responseHandler = etc.handler || handleAjaxResponse;
30533053- var select = etc.select || null;
30543054-30553055- if (!bodyContains(elt)) {
30563056- // do not issue requests for elements removed from the DOM
30573057- maybeCall(resolve);
30583058- return promise;
30593059- }
30603060- var target = etc.targetOverride || getTarget(elt);
30613061- if (target == null || target == DUMMY_ELT) {
30623062- triggerErrorEvent(elt, 'htmx:targetError', {target: getAttributeValue(elt, "hx-target")});
30633063- maybeCall(reject);
30643064- return promise;
30653065- }
30663066-30673067- var eltData = getInternalData(elt);
30683068- var submitter = eltData.lastButtonClicked;
30693069-30703070- if (submitter) {
30713071- var buttonPath = getRawAttribute(submitter, "formaction");
30723072- if (buttonPath != null) {
30733073- path = buttonPath;
30743074- }
30753075-30763076- var buttonVerb = getRawAttribute(submitter, "formmethod")
30773077- if (buttonVerb != null) {
30783078- // ignore buttons with formmethod="dialog"
30793079- if (buttonVerb.toLowerCase() !== "dialog") {
30803080- verb = buttonVerb;
30813081- }
30823082- }
30833083- }
30843084-30853085- var confirmQuestion = getClosestAttributeValue(elt, "hx-confirm");
30863086- // allow event-based confirmation w/ a callback
30873087- if (confirmed === undefined) {
30883088- var issueRequest = function(skipConfirmation) {
30893089- return issueAjaxRequest(verb, path, elt, event, etc, !!skipConfirmation);
30903090- }
30913091- var confirmDetails = {target: target, elt: elt, path: path, verb: verb, triggeringEvent: event, etc: etc, issueRequest: issueRequest, question: confirmQuestion};
30923092- if (triggerEvent(elt, 'htmx:confirm', confirmDetails) === false) {
30933093- maybeCall(resolve);
30943094- return promise;
30953095- }
30963096- }
30973097-30983098- var syncElt = elt;
30993099- var syncStrategy = getClosestAttributeValue(elt, "hx-sync");
31003100- var queueStrategy = null;
31013101- var abortable = false;
31023102- if (syncStrategy) {
31033103- var syncStrings = syncStrategy.split(":");
31043104- var selector = syncStrings[0].trim();
31053105- if (selector === "this") {
31063106- syncElt = findThisElement(elt, 'hx-sync');
31073107- } else {
31083108- syncElt = querySelectorExt(elt, selector);
31093109- }
31103110- // default to the drop strategy
31113111- syncStrategy = (syncStrings[1] || 'drop').trim();
31123112- eltData = getInternalData(syncElt);
31133113- if (syncStrategy === "drop" && eltData.xhr && eltData.abortable !== true) {
31143114- maybeCall(resolve);
31153115- return promise;
31163116- } else if (syncStrategy === "abort") {
31173117- if (eltData.xhr) {
31183118- maybeCall(resolve);
31193119- return promise;
31203120- } else {
31213121- abortable = true;
31223122- }
31233123- } else if (syncStrategy === "replace") {
31243124- triggerEvent(syncElt, 'htmx:abort'); // abort the current request and continue
31253125- } else if (syncStrategy.indexOf("queue") === 0) {
31263126- var queueStrArray = syncStrategy.split(" ");
31273127- queueStrategy = (queueStrArray[1] || "last").trim();
31283128- }
31293129- }
31303130-31313131- if (eltData.xhr) {
31323132- if (eltData.abortable) {
31333133- triggerEvent(syncElt, 'htmx:abort'); // abort the current request and continue
31343134- } else {
31353135- if(queueStrategy == null){
31363136- if (event) {
31373137- var eventData = getInternalData(event);
31383138- if (eventData && eventData.triggerSpec && eventData.triggerSpec.queue) {
31393139- queueStrategy = eventData.triggerSpec.queue;
31403140- }
31413141- }
31423142- if (queueStrategy == null) {
31433143- queueStrategy = "last";
31443144- }
31453145- }
31463146- if (eltData.queuedRequests == null) {
31473147- eltData.queuedRequests = [];
31483148- }
31493149- if (queueStrategy === "first" && eltData.queuedRequests.length === 0) {
31503150- eltData.queuedRequests.push(function () {
31513151- issueAjaxRequest(verb, path, elt, event, etc)
31523152- });
31533153- } else if (queueStrategy === "all") {
31543154- eltData.queuedRequests.push(function () {
31553155- issueAjaxRequest(verb, path, elt, event, etc)
31563156- });
31573157- } else if (queueStrategy === "last") {
31583158- eltData.queuedRequests = []; // dump existing queue
31593159- eltData.queuedRequests.push(function () {
31603160- issueAjaxRequest(verb, path, elt, event, etc)
31613161- });
31623162- }
31633163- maybeCall(resolve);
31643164- return promise;
31653165- }
31663166- }
31673167-31683168- var xhr = new XMLHttpRequest();
31693169- eltData.xhr = xhr;
31703170- eltData.abortable = abortable;
31713171- var endRequestLock = function(){
31723172- eltData.xhr = null;
31733173- eltData.abortable = false;
31743174- if (eltData.queuedRequests != null &&
31753175- eltData.queuedRequests.length > 0) {
31763176- var queuedRequest = eltData.queuedRequests.shift();
31773177- queuedRequest();
31783178- }
31793179- }
31803180- var promptQuestion = getClosestAttributeValue(elt, "hx-prompt");
31813181- if (promptQuestion) {
31823182- var promptResponse = prompt(promptQuestion);
31833183- // prompt returns null if cancelled and empty string if accepted with no entry
31843184- if (promptResponse === null ||
31853185- !triggerEvent(elt, 'htmx:prompt', {prompt: promptResponse, target:target})) {
31863186- maybeCall(resolve);
31873187- endRequestLock();
31883188- return promise;
31893189- }
31903190- }
31913191-31923192- if (confirmQuestion && !confirmed) {
31933193- if(!confirm(confirmQuestion)) {
31943194- maybeCall(resolve);
31953195- endRequestLock()
31963196- return promise;
31973197- }
31983198- }
31993199-32003200-32013201- var headers = getHeaders(elt, target, promptResponse);
32023202-32033203- if (verb !== 'get' && !usesFormData(elt)) {
32043204- headers['Content-Type'] = 'application/x-www-form-urlencoded';
32053205- }
32063206-32073207- if (etc.headers) {
32083208- headers = mergeObjects(headers, etc.headers);
32093209- }
32103210- var results = getInputValues(elt, verb);
32113211- var errors = results.errors;
32123212- var rawParameters = results.values;
32133213- if (etc.values) {
32143214- rawParameters = mergeObjects(rawParameters, etc.values);
32153215- }
32163216- var expressionVars = getExpressionVars(elt);
32173217- var allParameters = mergeObjects(rawParameters, expressionVars);
32183218- var filteredParameters = filterValues(allParameters, elt);
32193219-32203220- if (htmx.config.getCacheBusterParam && verb === 'get') {
32213221- filteredParameters['org.htmx.cache-buster'] = getRawAttribute(target, "id") || "true";
32223222- }
32233223-32243224- // behavior of anchors w/ empty href is to use the current URL
32253225- if (path == null || path === "") {
32263226- path = getDocument().location.href;
32273227- }
32283228-32293229-32303230- var requestAttrValues = getValuesForElement(elt, 'hx-request');
32313231-32323232- var eltIsBoosted = getInternalData(elt).boosted;
32333233-32343234- var useUrlParams = htmx.config.methodsThatUseUrlParams.indexOf(verb) >= 0
32353235-32363236- var requestConfig = {
32373237- boosted: eltIsBoosted,
32383238- useUrlParams: useUrlParams,
32393239- parameters: filteredParameters,
32403240- unfilteredParameters: allParameters,
32413241- headers:headers,
32423242- target:target,
32433243- verb:verb,
32443244- errors:errors,
32453245- withCredentials: etc.credentials || requestAttrValues.credentials || htmx.config.withCredentials,
32463246- timeout: etc.timeout || requestAttrValues.timeout || htmx.config.timeout,
32473247- path:path,
32483248- triggeringEvent:event
32493249- };
32503250-32513251- if(!triggerEvent(elt, 'htmx:configRequest', requestConfig)){
32523252- maybeCall(resolve);
32533253- endRequestLock();
32543254- return promise;
32553255- }
32563256-32573257- // copy out in case the object was overwritten
32583258- path = requestConfig.path;
32593259- verb = requestConfig.verb;
32603260- headers = requestConfig.headers;
32613261- filteredParameters = requestConfig.parameters;
32623262- errors = requestConfig.errors;
32633263- useUrlParams = requestConfig.useUrlParams;
32643264-32653265- if(errors && errors.length > 0){
32663266- triggerEvent(elt, 'htmx:validation:halted', requestConfig)
32673267- maybeCall(resolve);
32683268- endRequestLock();
32693269- return promise;
32703270- }
32713271-32723272- var splitPath = path.split("#");
32733273- var pathNoAnchor = splitPath[0];
32743274- var anchor = splitPath[1];
32753275-32763276- var finalPath = path
32773277- if (useUrlParams) {
32783278- finalPath = pathNoAnchor;
32793279- var values = Object.keys(filteredParameters).length !== 0;
32803280- if (values) {
32813281- if (finalPath.indexOf("?") < 0) {
32823282- finalPath += "?";
32833283- } else {
32843284- finalPath += "&";
32853285- }
32863286- finalPath += urlEncode(filteredParameters);
32873287- if (anchor) {
32883288- finalPath += "#" + anchor;
32893289- }
32903290- }
32913291- }
32923292-32933293- if (!verifyPath(elt, finalPath, requestConfig)) {
32943294- triggerErrorEvent(elt, 'htmx:invalidPath', requestConfig)
32953295- maybeCall(reject);
32963296- return promise;
32973297- };
32983298-32993299- xhr.open(verb.toUpperCase(), finalPath, true);
33003300- xhr.overrideMimeType("text/html");
33013301- xhr.withCredentials = requestConfig.withCredentials;
33023302- xhr.timeout = requestConfig.timeout;
33033303-33043304- // request headers
33053305- if (requestAttrValues.noHeaders) {
33063306- // ignore all headers
33073307- } else {
33083308- for (var header in headers) {
33093309- if (headers.hasOwnProperty(header)) {
33103310- var headerValue = headers[header];
33113311- safelySetHeaderValue(xhr, header, headerValue);
33123312- }
33133313- }
33143314- }
33153315-33163316- var responseInfo = {
33173317- xhr: xhr, target: target, requestConfig: requestConfig, etc: etc, boosted: eltIsBoosted, select: select,
33183318- pathInfo: {
33193319- requestPath: path,
33203320- finalRequestPath: finalPath,
33213321- anchor: anchor
33223322- }
33233323- };
33243324-33253325- xhr.onload = function () {
33263326- try {
33273327- var hierarchy = hierarchyForElt(elt);
33283328- responseInfo.pathInfo.responsePath = getPathFromResponse(xhr);
33293329- responseHandler(elt, responseInfo);
33303330- removeRequestIndicators(indicators, disableElts);
33313331- triggerEvent(elt, 'htmx:afterRequest', responseInfo);
33323332- triggerEvent(elt, 'htmx:afterOnLoad', responseInfo);
33333333- // if the body no longer contains the element, trigger the event on the closest parent
33343334- // remaining in the DOM
33353335- if (!bodyContains(elt)) {
33363336- var secondaryTriggerElt = null;
33373337- while (hierarchy.length > 0 && secondaryTriggerElt == null) {
33383338- var parentEltInHierarchy = hierarchy.shift();
33393339- if (bodyContains(parentEltInHierarchy)) {
33403340- secondaryTriggerElt = parentEltInHierarchy;
33413341- }
33423342- }
33433343- if (secondaryTriggerElt) {
33443344- triggerEvent(secondaryTriggerElt, 'htmx:afterRequest', responseInfo);
33453345- triggerEvent(secondaryTriggerElt, 'htmx:afterOnLoad', responseInfo);
33463346- }
33473347- }
33483348- maybeCall(resolve);
33493349- endRequestLock();
33503350- } catch (e) {
33513351- triggerErrorEvent(elt, 'htmx:onLoadError', mergeObjects({error:e}, responseInfo));
33523352- throw e;
33533353- }
33543354- }
33553355- xhr.onerror = function () {
33563356- removeRequestIndicators(indicators, disableElts);
33573357- triggerErrorEvent(elt, 'htmx:afterRequest', responseInfo);
33583358- triggerErrorEvent(elt, 'htmx:sendError', responseInfo);
33593359- maybeCall(reject);
33603360- endRequestLock();
33613361- }
33623362- xhr.onabort = function() {
33633363- removeRequestIndicators(indicators, disableElts);
33643364- triggerErrorEvent(elt, 'htmx:afterRequest', responseInfo);
33653365- triggerErrorEvent(elt, 'htmx:sendAbort', responseInfo);
33663366- maybeCall(reject);
33673367- endRequestLock();
33683368- }
33693369- xhr.ontimeout = function() {
33703370- removeRequestIndicators(indicators, disableElts);
33713371- triggerErrorEvent(elt, 'htmx:afterRequest', responseInfo);
33723372- triggerErrorEvent(elt, 'htmx:timeout', responseInfo);
33733373- maybeCall(reject);
33743374- endRequestLock();
33753375- }
33763376- if(!triggerEvent(elt, 'htmx:beforeRequest', responseInfo)){
33773377- maybeCall(resolve);
33783378- endRequestLock()
33793379- return promise
33803380- }
33813381- var indicators = addRequestIndicatorClasses(elt);
33823382- var disableElts = disableElements(elt);
33833383-33843384- forEach(['loadstart', 'loadend', 'progress', 'abort'], function(eventName) {
33853385- forEach([xhr, xhr.upload], function (target) {
33863386- target.addEventListener(eventName, function(event){
33873387- triggerEvent(elt, "htmx:xhr:" + eventName, {
33883388- lengthComputable:event.lengthComputable,
33893389- loaded:event.loaded,
33903390- total:event.total
33913391- });
33923392- })
33933393- });
33943394- });
33953395- triggerEvent(elt, 'htmx:beforeSend', responseInfo);
33963396- var params = useUrlParams ? null : encodeParamsForBody(xhr, elt, filteredParameters)
33973397- xhr.send(params);
33983398- return promise;
33993399- }
34003400-34013401- function determineHistoryUpdates(elt, responseInfo) {
34023402-34033403- var xhr = responseInfo.xhr;
34043404-34053405- //===========================================
34063406- // First consult response headers
34073407- //===========================================
34083408- var pathFromHeaders = null;
34093409- var typeFromHeaders = null;
34103410- if (hasHeader(xhr,/HX-Push:/i)) {
34113411- pathFromHeaders = xhr.getResponseHeader("HX-Push");
34123412- typeFromHeaders = "push";
34133413- } else if (hasHeader(xhr,/HX-Push-Url:/i)) {
34143414- pathFromHeaders = xhr.getResponseHeader("HX-Push-Url");
34153415- typeFromHeaders = "push";
34163416- } else if (hasHeader(xhr,/HX-Replace-Url:/i)) {
34173417- pathFromHeaders = xhr.getResponseHeader("HX-Replace-Url");
34183418- typeFromHeaders = "replace";
34193419- }
34203420-34213421- // if there was a response header, that has priority
34223422- if (pathFromHeaders) {
34233423- if (pathFromHeaders === "false") {
34243424- return {}
34253425- } else {
34263426- return {
34273427- type: typeFromHeaders,
34283428- path : pathFromHeaders
34293429- }
34303430- }
34313431- }
34323432-34333433- //===========================================
34343434- // Next resolve via DOM values
34353435- //===========================================
34363436- var requestPath = responseInfo.pathInfo.finalRequestPath;
34373437- var responsePath = responseInfo.pathInfo.responsePath;
34383438-34393439- var pushUrl = getClosestAttributeValue(elt, "hx-push-url");
34403440- var replaceUrl = getClosestAttributeValue(elt, "hx-replace-url");
34413441- var elementIsBoosted = getInternalData(elt).boosted;
34423442-34433443- var saveType = null;
34443444- var path = null;
34453445-34463446- if (pushUrl) {
34473447- saveType = "push";
34483448- path = pushUrl;
34493449- } else if (replaceUrl) {
34503450- saveType = "replace";
34513451- path = replaceUrl;
34523452- } else if (elementIsBoosted) {
34533453- saveType = "push";
34543454- path = responsePath || requestPath; // if there is no response path, go with the original request path
34553455- }
34563456-34573457- if (path) {
34583458- // false indicates no push, return empty object
34593459- if (path === "false") {
34603460- return {};
34613461- }
34623462-34633463- // true indicates we want to follow wherever the server ended up sending us
34643464- if (path === "true") {
34653465- path = responsePath || requestPath; // if there is no response path, go with the original request path
34663466- }
34673467-34683468- // restore any anchor associated with the request
34693469- if (responseInfo.pathInfo.anchor &&
34703470- path.indexOf("#") === -1) {
34713471- path = path + "#" + responseInfo.pathInfo.anchor;
34723472- }
34733473-34743474- return {
34753475- type:saveType,
34763476- path: path
34773477- }
34783478- } else {
34793479- return {};
34803480- }
34813481- }
34823482-34833483- function handleAjaxResponse(elt, responseInfo) {
34843484- var xhr = responseInfo.xhr;
34853485- var target = responseInfo.target;
34863486- var etc = responseInfo.etc;
34873487- var requestConfig = responseInfo.requestConfig;
34883488- var select = responseInfo.select;
34893489-34903490- if (!triggerEvent(elt, 'htmx:beforeOnLoad', responseInfo)) return;
34913491-34923492- if (hasHeader(xhr, /HX-Trigger:/i)) {
34933493- handleTrigger(xhr, "HX-Trigger", elt);
34943494- }
34953495-34963496- if (hasHeader(xhr, /HX-Location:/i)) {
34973497- saveCurrentPageToHistory();
34983498- var redirectPath = xhr.getResponseHeader("HX-Location");
34993499- var swapSpec;
35003500- if (redirectPath.indexOf("{") === 0) {
35013501- swapSpec = parseJSON(redirectPath);
35023502- // what's the best way to throw an error if the user didn't include this
35033503- redirectPath = swapSpec['path'];
35043504- delete swapSpec['path'];
35053505- }
35063506- ajaxHelper('GET', redirectPath, swapSpec).then(function(){
35073507- pushUrlIntoHistory(redirectPath);
35083508- });
35093509- return;
35103510- }
35113511-35123512- var shouldRefresh = hasHeader(xhr, /HX-Refresh:/i) && "true" === xhr.getResponseHeader("HX-Refresh");
35133513-35143514- if (hasHeader(xhr, /HX-Redirect:/i)) {
35153515- location.href = xhr.getResponseHeader("HX-Redirect");
35163516- shouldRefresh && location.reload();
35173517- return;
35183518- }
35193519-35203520- if (shouldRefresh) {
35213521- location.reload();
35223522- return;
35233523- }
35243524-35253525- if (hasHeader(xhr,/HX-Retarget:/i)) {
35263526- if (xhr.getResponseHeader("HX-Retarget") === "this") {
35273527- responseInfo.target = elt;
35283528- } else {
35293529- responseInfo.target = querySelectorExt(elt, xhr.getResponseHeader("HX-Retarget"));
35303530- }
35313531- }
35323532-35333533- var historyUpdate = determineHistoryUpdates(elt, responseInfo);
35343534-35353535- // by default htmx only swaps on 200 return codes and does not swap
35363536- // on 204 'No Content'
35373537- // this can be ovverriden by responding to the htmx:beforeSwap event and
35383538- // overriding the detail.shouldSwap property
35393539- var shouldSwap = xhr.status >= 200 && xhr.status < 400 && xhr.status !== 204;
35403540- var serverResponse = xhr.response;
35413541- var isError = xhr.status >= 400;
35423542- var ignoreTitle = htmx.config.ignoreTitle
35433543- var beforeSwapDetails = mergeObjects({shouldSwap: shouldSwap, serverResponse:serverResponse, isError:isError, ignoreTitle:ignoreTitle }, responseInfo);
35443544- if (!triggerEvent(target, 'htmx:beforeSwap', beforeSwapDetails)) return;
35453545-35463546- target = beforeSwapDetails.target; // allow re-targeting
35473547- serverResponse = beforeSwapDetails.serverResponse; // allow updating content
35483548- isError = beforeSwapDetails.isError; // allow updating error
35493549- ignoreTitle = beforeSwapDetails.ignoreTitle; // allow updating ignoring title
35503550-35513551- responseInfo.target = target; // Make updated target available to response events
35523552- responseInfo.failed = isError; // Make failed property available to response events
35533553- responseInfo.successful = !isError; // Make successful property available to response events
35543554-35553555- if (beforeSwapDetails.shouldSwap) {
35563556- if (xhr.status === 286) {
35573557- cancelPolling(elt);
35583558- }
35593559-35603560- withExtensions(elt, function (extension) {
35613561- serverResponse = extension.transformResponse(serverResponse, xhr, elt);
35623562- });
35633563-35643564- // Save current page if there will be a history update
35653565- if (historyUpdate.type) {
35663566- saveCurrentPageToHistory();
35673567- }
35683568-35693569- var swapOverride = etc.swapOverride;
35703570- if (hasHeader(xhr,/HX-Reswap:/i)) {
35713571- swapOverride = xhr.getResponseHeader("HX-Reswap");
35723572- }
35733573- var swapSpec = getSwapSpecification(elt, swapOverride);
35743574-35753575- if (swapSpec.hasOwnProperty('ignoreTitle')) {
35763576- ignoreTitle = swapSpec.ignoreTitle;
35773577- }
35783578-35793579- target.classList.add(htmx.config.swappingClass);
35803580-35813581- // optional transition API promise callbacks
35823582- var settleResolve = null;
35833583- var settleReject = null;
35843584-35853585- var doSwap = function () {
35863586- try {
35873587- var activeElt = document.activeElement;
35883588- var selectionInfo = {};
35893589- try {
35903590- selectionInfo = {
35913591- elt: activeElt,
35923592- // @ts-ignore
35933593- start: activeElt ? activeElt.selectionStart : null,
35943594- // @ts-ignore
35953595- end: activeElt ? activeElt.selectionEnd : null
35963596- };
35973597- } catch (e) {
35983598- // safari issue - see https://github.com/microsoft/playwright/issues/5894
35993599- }
36003600-36013601- var selectOverride;
36023602- if (select) {
36033603- selectOverride = select;
36043604- }
36053605-36063606- if (hasHeader(xhr, /HX-Reselect:/i)) {
36073607- selectOverride = xhr.getResponseHeader("HX-Reselect");
36083608- }
36093609-36103610- // if we need to save history, do so, before swapping so that relative resources have the correct base URL
36113611- if (historyUpdate.type) {
36123612- triggerEvent(getDocument().body, 'htmx:beforeHistoryUpdate', mergeObjects({ history: historyUpdate }, responseInfo));
36133613- if (historyUpdate.type === "push") {
36143614- pushUrlIntoHistory(historyUpdate.path);
36153615- triggerEvent(getDocument().body, 'htmx:pushedIntoHistory', {path: historyUpdate.path});
36163616- } else {
36173617- replaceUrlInHistory(historyUpdate.path);
36183618- triggerEvent(getDocument().body, 'htmx:replacedInHistory', {path: historyUpdate.path});
36193619- }
36203620- }
36213621-36223622- var settleInfo = makeSettleInfo(target);
36233623- selectAndSwap(swapSpec.swapStyle, target, elt, serverResponse, settleInfo, selectOverride);
36243624-36253625- if (selectionInfo.elt &&
36263626- !bodyContains(selectionInfo.elt) &&
36273627- getRawAttribute(selectionInfo.elt, "id")) {
36283628- var newActiveElt = document.getElementById(getRawAttribute(selectionInfo.elt, "id"));
36293629- var focusOptions = { preventScroll: swapSpec.focusScroll !== undefined ? !swapSpec.focusScroll : !htmx.config.defaultFocusScroll };
36303630- if (newActiveElt) {
36313631- // @ts-ignore
36323632- if (selectionInfo.start && newActiveElt.setSelectionRange) {
36333633- // @ts-ignore
36343634- try {
36353635- newActiveElt.setSelectionRange(selectionInfo.start, selectionInfo.end);
36363636- } catch (e) {
36373637- // the setSelectionRange method is present on fields that don't support it, so just let this fail
36383638- }
36393639- }
36403640- newActiveElt.focus(focusOptions);
36413641- }
36423642- }
36433643-36443644- target.classList.remove(htmx.config.swappingClass);
36453645- forEach(settleInfo.elts, function (elt) {
36463646- if (elt.classList) {
36473647- elt.classList.add(htmx.config.settlingClass);
36483648- }
36493649- triggerEvent(elt, 'htmx:afterSwap', responseInfo);
36503650- });
36513651-36523652- if (hasHeader(xhr, /HX-Trigger-After-Swap:/i)) {
36533653- var finalElt = elt;
36543654- if (!bodyContains(elt)) {
36553655- finalElt = getDocument().body;
36563656- }
36573657- handleTrigger(xhr, "HX-Trigger-After-Swap", finalElt);
36583658- }
36593659-36603660- var doSettle = function () {
36613661- forEach(settleInfo.tasks, function (task) {
36623662- task.call();
36633663- });
36643664- forEach(settleInfo.elts, function (elt) {
36653665- if (elt.classList) {
36663666- elt.classList.remove(htmx.config.settlingClass);
36673667- }
36683668- triggerEvent(elt, 'htmx:afterSettle', responseInfo);
36693669- });
36703670-36713671- if (responseInfo.pathInfo.anchor) {
36723672- var anchorTarget = getDocument().getElementById(responseInfo.pathInfo.anchor);
36733673- if(anchorTarget) {
36743674- anchorTarget.scrollIntoView({block:'start', behavior: "auto"});
36753675- }
36763676- }
36773677-36783678- if(settleInfo.title && !ignoreTitle) {
36793679- var titleElt = find("title");
36803680- if(titleElt) {
36813681- titleElt.innerHTML = settleInfo.title;
36823682- } else {
36833683- window.document.title = settleInfo.title;
36843684- }
36853685- }
36863686-36873687- updateScrollState(settleInfo.elts, swapSpec);
36883688-36893689- if (hasHeader(xhr, /HX-Trigger-After-Settle:/i)) {
36903690- var finalElt = elt;
36913691- if (!bodyContains(elt)) {
36923692- finalElt = getDocument().body;
36933693- }
36943694- handleTrigger(xhr, "HX-Trigger-After-Settle", finalElt);
36953695- }
36963696- maybeCall(settleResolve);
36973697- }
36983698-36993699- if (swapSpec.settleDelay > 0) {
37003700- setTimeout(doSettle, swapSpec.settleDelay)
37013701- } else {
37023702- doSettle();
37033703- }
37043704- } catch (e) {
37053705- triggerErrorEvent(elt, 'htmx:swapError', responseInfo);
37063706- maybeCall(settleReject);
37073707- throw e;
37083708- }
37093709- };
37103710-37113711- var shouldTransition = htmx.config.globalViewTransitions
37123712- if(swapSpec.hasOwnProperty('transition')){
37133713- shouldTransition = swapSpec.transition;
37143714- }
37153715-37163716- if(shouldTransition &&
37173717- triggerEvent(elt, 'htmx:beforeTransition', responseInfo) &&
37183718- typeof Promise !== "undefined" && document.startViewTransition){
37193719- var settlePromise = new Promise(function (_resolve, _reject) {
37203720- settleResolve = _resolve;
37213721- settleReject = _reject;
37223722- });
37233723- // wrap the original doSwap() in a call to startViewTransition()
37243724- var innerDoSwap = doSwap;
37253725- doSwap = function() {
37263726- document.startViewTransition(function () {
37273727- innerDoSwap();
37283728- return settlePromise;
37293729- });
37303730- }
37313731- }
37323732-37333733-37343734- if (swapSpec.swapDelay > 0) {
37353735- setTimeout(doSwap, swapSpec.swapDelay)
37363736- } else {
37373737- doSwap();
37383738- }
37393739- }
37403740- if (isError) {
37413741- triggerErrorEvent(elt, 'htmx:responseError', mergeObjects({error: "Response Status Error Code " + xhr.status + " from " + responseInfo.pathInfo.requestPath}, responseInfo));
37423742- }
37433743- }
37443744-37453745- //====================================================================
37463746- // Extensions API
37473747- //====================================================================
37483748-37493749- /** @type {Object<string, import("./htmx").HtmxExtension>} */
37503750- var extensions = {};
37513751-37523752- /**
37533753- * extensionBase defines the default functions for all extensions.
37543754- * @returns {import("./htmx").HtmxExtension}
37553755- */
37563756- function extensionBase() {
37573757- return {
37583758- init: function(api) {return null;},
37593759- onEvent : function(name, evt) {return true;},
37603760- transformResponse : function(text, xhr, elt) {return text;},
37613761- isInlineSwap : function(swapStyle) {return false;},
37623762- handleSwap : function(swapStyle, target, fragment, settleInfo) {return false;},
37633763- encodeParameters : function(xhr, parameters, elt) {return null;}
37643764- }
37653765- }
37663766-37673767- /**
37683768- * defineExtension initializes the extension and adds it to the htmx registry
37693769- *
37703770- * @param {string} name
37713771- * @param {import("./htmx").HtmxExtension} extension
37723772- */
37733773- function defineExtension(name, extension) {
37743774- if(extension.init) {
37753775- extension.init(internalAPI)
37763776- }
37773777- extensions[name] = mergeObjects(extensionBase(), extension);
37783778- }
37793779-37803780- /**
37813781- * removeExtension removes an extension from the htmx registry
37823782- *
37833783- * @param {string} name
37843784- */
37853785- function removeExtension(name) {
37863786- delete extensions[name];
37873787- }
37883788-37893789- /**
37903790- * getExtensions searches up the DOM tree to return all extensions that can be applied to a given element
37913791- *
37923792- * @param {HTMLElement} elt
37933793- * @param {import("./htmx").HtmxExtension[]=} extensionsToReturn
37943794- * @param {import("./htmx").HtmxExtension[]=} extensionsToIgnore
37953795- */
37963796- function getExtensions(elt, extensionsToReturn, extensionsToIgnore) {
37973797-37983798- if (elt == undefined) {
37993799- return extensionsToReturn;
38003800- }
38013801- if (extensionsToReturn == undefined) {
38023802- extensionsToReturn = [];
38033803- }
38043804- if (extensionsToIgnore == undefined) {
38053805- extensionsToIgnore = [];
38063806- }
38073807- var extensionsForElement = getAttributeValue(elt, "hx-ext");
38083808- if (extensionsForElement) {
38093809- forEach(extensionsForElement.split(","), function(extensionName){
38103810- extensionName = extensionName.replace(/ /g, '');
38113811- if (extensionName.slice(0, 7) == "ignore:") {
38123812- extensionsToIgnore.push(extensionName.slice(7));
38133813- return;
38143814- }
38153815- if (extensionsToIgnore.indexOf(extensionName) < 0) {
38163816- var extension = extensions[extensionName];
38173817- if (extension && extensionsToReturn.indexOf(extension) < 0) {
38183818- extensionsToReturn.push(extension);
38193819- }
38203820- }
38213821- });
38223822- }
38233823- return getExtensions(parentElt(elt), extensionsToReturn, extensionsToIgnore);
38243824- }
38253825-38263826- //====================================================================
38273827- // Initialization
38283828- //====================================================================
38293829- var isReady = false
38303830- getDocument().addEventListener('DOMContentLoaded', function() {
38313831- isReady = true
38323832- })
38333833-38343834- /**
38353835- * Execute a function now if DOMContentLoaded has fired, otherwise listen for it.
38363836- *
38373837- * This function uses isReady because there is no realiable way to ask the browswer whether
38383838- * the DOMContentLoaded event has already been fired; there's a gap between DOMContentLoaded
38393839- * firing and readystate=complete.
38403840- */
38413841- function ready(fn) {
38423842- // Checking readyState here is a failsafe in case the htmx script tag entered the DOM by
38433843- // some means other than the initial page load.
38443844- if (isReady || getDocument().readyState === 'complete') {
38453845- fn();
38463846- } else {
38473847- getDocument().addEventListener('DOMContentLoaded', fn);
38483848- }
38493849- }
38503850-38513851- function insertIndicatorStyles() {
38523852- if (htmx.config.includeIndicatorStyles !== false) {
38533853- getDocument().head.insertAdjacentHTML("beforeend",
38543854- "<style>\
38553855- ." + htmx.config.indicatorClass + "{opacity:0}\
38563856- ." + htmx.config.requestClass + " ." + htmx.config.indicatorClass + "{opacity:1; transition: opacity 200ms ease-in;}\
38573857- ." + htmx.config.requestClass + "." + htmx.config.indicatorClass + "{opacity:1; transition: opacity 200ms ease-in;}\
38583858- </style>");
38593859- }
38603860- }
38613861-38623862- function getMetaConfig() {
38633863- var element = getDocument().querySelector('meta[name="htmx-config"]');
38643864- if (element) {
38653865- // @ts-ignore
38663866- return parseJSON(element.content);
38673867- } else {
38683868- return null;
38693869- }
38703870- }
38713871-38723872- function mergeMetaConfig() {
38733873- var metaConfig = getMetaConfig();
38743874- if (metaConfig) {
38753875- htmx.config = mergeObjects(htmx.config , metaConfig)
38763876- }
38773877- }
38783878-38793879- // initialize the document
38803880- ready(function () {
38813881- mergeMetaConfig();
38823882- insertIndicatorStyles();
38833883- var body = getDocument().body;
38843884- processNode(body);
38853885- var restoredElts = getDocument().querySelectorAll(
38863886- "[hx-trigger='restored'],[data-hx-trigger='restored']"
38873887- );
38883888- body.addEventListener("htmx:abort", function (evt) {
38893889- var target = evt.target;
38903890- var internalData = getInternalData(target);
38913891- if (internalData && internalData.xhr) {
38923892- internalData.xhr.abort();
38933893- }
38943894- });
38953895- /** @type {(ev: PopStateEvent) => any} */
38963896- const originalPopstate = window.onpopstate ? window.onpopstate.bind(window) : null;
38973897- /** @type {(ev: PopStateEvent) => any} */
38983898- window.onpopstate = function (event) {
38993899- if (event.state && event.state.htmx) {
39003900- restoreHistory();
39013901- forEach(restoredElts, function(elt){
39023902- triggerEvent(elt, 'htmx:restored', {
39033903- 'document': getDocument(),
39043904- 'triggerEvent': triggerEvent
39053905- });
39063906- });
39073907- } else {
39083908- if (originalPopstate) {
39093909- originalPopstate(event);
39103910- }
39113911- }
39123912- };
39133913- setTimeout(function () {
39143914- triggerEvent(body, 'htmx:load', {}); // give ready handlers a chance to load up before firing this event
39153915- body = null; // kill reference for gc
39163916- }, 0);
39173917- })
39183918-39193919- return htmx;
39203920- }
39213921-)()
39223922-}));