UNPKG

110 kBJavaScriptView Raw
1//AMD insanity
2(function (root, factory) {
3 if (typeof define === 'function' && define.amd) {
4 // AMD. Register as an anonymous module.
5 define([], factory);
6 } else {
7 // Browser globals
8 root.htmx = factory();
9 }
10}(typeof self !== 'undefined' ? self : this, function () {
11return (function () {
12 'use strict';
13
14 // Public API
15 var htmx = {
16 onLoad: onLoadHelper,
17 process: processNode,
18 on: addEventListenerImpl,
19 off: removeEventListenerImpl,
20 trigger : triggerEvent,
21 ajax : ajaxHelper,
22 find : find,
23 findAll : findAll,
24 closest : closest,
25 values : function(elt, type){
26 var inputValues = getInputValues(elt, type || "post");
27 return inputValues.values;
28 },
29 remove : removeElement,
30 addClass : addClassToElement,
31 removeClass : removeClassFromElement,
32 toggleClass : toggleClassOnElement,
33 takeClass : takeClassForElement,
34 defineExtension : defineExtension,
35 removeExtension : removeExtension,
36 logAll : logAll,
37 logger : null,
38 config : {
39 historyEnabled:true,
40 historyCacheSize:10,
41 refreshOnHistoryMiss:false,
42 defaultSwapStyle:'innerHTML',
43 defaultSwapDelay:0,
44 defaultSettleDelay:20,
45 includeIndicatorStyles:true,
46 indicatorClass:'htmx-indicator',
47 requestClass:'htmx-request',
48 settlingClass:'htmx-settling',
49 swappingClass:'htmx-swapping',
50 allowEval:true,
51 attributesToSettle:["class", "style", "width", "height"],
52 withCredentials:false,
53 timeout:0,
54 wsReconnectDelay: 'full-jitter',
55 disableSelector: "[hx-disable], [data-hx-disable]",
56 useTemplateFragments: false,
57 scrollBehavior: 'smooth',
58 },
59 parseInterval:parseInterval,
60 _:internalEval,
61 createEventSource: function(url){
62 return new EventSource(url, {withCredentials:true})
63 },
64 createWebSocket: function(url){
65 return new WebSocket(url, []);
66 },
67 version: "1.5.0"
68 };
69
70 var VERBS = ['get', 'post', 'put', 'delete', 'patch'];
71 var VERB_SELECTOR = VERBS.map(function(verb){
72 return "[hx-" + verb + "], [data-hx-" + verb + "]"
73 }).join(", ");
74
75 //====================================================================
76 // Utilities
77 //====================================================================
78
79 function parseInterval(str) {
80 if (str == undefined) {
81 return undefined
82 }
83 if (str.slice(-2) == "ms") {
84 return parseFloat(str.slice(0,-2)) || undefined
85 }
86 if (str.slice(-1) == "s") {
87 return (parseFloat(str.slice(0,-1)) * 1000) || undefined
88 }
89 return parseFloat(str) || undefined
90 }
91
92 function getRawAttribute(elt, name) {
93 return elt.getAttribute && elt.getAttribute(name);
94 }
95
96 // resolve with both hx and data-hx prefixes
97 function hasAttribute(elt, qualifiedName) {
98 return elt.hasAttribute && (elt.hasAttribute(qualifiedName) ||
99 elt.hasAttribute("data-" + qualifiedName));
100 }
101
102 function getAttributeValue(elt, qualifiedName) {
103 return getRawAttribute(elt, qualifiedName) || getRawAttribute(elt, "data-" + qualifiedName);
104 }
105
106 function parentElt(elt) {
107 return elt.parentElement;
108 }
109
110 function getDocument() {
111 return document;
112 }
113
114 function getClosestMatch(elt, condition) {
115 if (condition(elt)) {
116 return elt;
117 } else if (parentElt(elt)) {
118 return getClosestMatch(parentElt(elt), condition);
119 } else {
120 return null;
121 }
122 }
123
124 function getClosestAttributeValue(elt, attributeName) {
125 var closestAttr = null;
126 getClosestMatch(elt, function (e) {
127 return closestAttr = getAttributeValue(e, attributeName);
128 });
129 return closestAttr;
130 }
131
132 function matches(elt, selector) {
133 // noinspection JSUnresolvedVariable
134 var matchesFunction = elt.matches ||
135 elt.matchesSelector || elt.msMatchesSelector || elt.mozMatchesSelector
136 || elt.webkitMatchesSelector || elt.oMatchesSelector;
137 return matchesFunction && matchesFunction.call(elt, selector);
138 }
139
140 function getStartTag(str) {
141 var tagMatcher = /<([a-z][^\/\0>\x20\t\r\n\f]*)/i
142 var match = tagMatcher.exec( str );
143 if (match) {
144 return match[1].toLowerCase();
145 } else {
146 return "";
147 }
148 }
149
150 function parseHTML(resp, depth) {
151 var parser = new DOMParser();
152 var responseDoc = parser.parseFromString(resp, "text/html");
153 var responseNode = responseDoc.body;
154 while (depth > 0) {
155 depth--;
156 responseNode = responseNode.firstChild;
157 }
158 if (responseNode == null) {
159 responseNode = getDocument().createDocumentFragment();
160 }
161 return responseNode;
162 }
163
164 function makeFragment(resp) {
165 if (htmx.config.useTemplateFragments) {
166 var documentFragment = parseHTML("<body><template>" + resp + "</template></body>", 0);
167 return documentFragment.querySelector('template').content;
168 } else {
169 var startTag = getStartTag(resp);
170 switch (startTag) {
171 case "thead":
172 case "tbody":
173 case "tfoot":
174 case "colgroup":
175 case "caption":
176 return parseHTML("<table>" + resp + "</table>", 1);
177 case "col":
178 return parseHTML("<table><colgroup>" + resp + "</colgroup></table>", 2);
179 case "tr":
180 return parseHTML("<table><tbody>" + resp + "</tbody></table>", 2);
181 case "td":
182 case "th":
183 return parseHTML("<table><tbody><tr>" + resp + "</tr></tbody></table>", 3);
184 case "script":
185 return parseHTML("<div>" + resp + "</div>", 1);
186 default:
187 return parseHTML(resp, 0);
188 }
189 }
190 }
191
192 function maybeCall(func){
193 if(func) {
194 func();
195 }
196 }
197
198 function isType(o, type) {
199 return Object.prototype.toString.call(o) === "[object " + type + "]";
200 }
201
202 function isFunction(o) {
203 return isType(o, "Function");
204 }
205
206 function isRawObject(o) {
207 return isType(o, "Object");
208 }
209
210 function getInternalData(elt) {
211 var dataProp = 'htmx-internal-data';
212 var data = elt[dataProp];
213 if (!data) {
214 data = elt[dataProp] = {};
215 }
216 return data;
217 }
218
219 function toArray(arr) {
220 var returnArr = [];
221 if (arr) {
222 for (var i = 0; i < arr.length; i++) {
223 returnArr.push(arr[i]);
224 }
225 }
226 return returnArr
227 }
228
229 function forEach(arr, func) {
230 if (arr) {
231 for (var i = 0; i < arr.length; i++) {
232 func(arr[i]);
233 }
234 }
235 }
236
237 function isScrolledIntoView(el) {
238 var rect = el.getBoundingClientRect();
239 var elemTop = rect.top;
240 var elemBottom = rect.bottom;
241 return elemTop < window.innerHeight && elemBottom >= 0;
242 }
243
244 function bodyContains(elt) {
245 return getDocument().body.contains(elt);
246 }
247
248 function splitOnWhitespace(trigger) {
249 return trigger.trim().split(/\s+/);
250 }
251
252 function mergeObjects(obj1, obj2) {
253 for (var key in obj2) {
254 if (obj2.hasOwnProperty(key)) {
255 obj1[key] = obj2[key];
256 }
257 }
258 return obj1;
259 }
260
261 function parseJSON(jString) {
262 try {
263 return JSON.parse(jString);
264 } catch(error) {
265 logError(error);
266 return null;
267 }
268 }
269
270 //==========================================================================================
271 // public API
272 //==========================================================================================
273
274 function internalEval(str){
275 return maybeEval(getDocument().body, function () {
276 return eval(str);
277 });
278 }
279
280 function onLoadHelper(callback) {
281 var value = htmx.on("htmx:load", function(evt) {
282 callback(evt.detail.elt);
283 });
284 return value;
285 }
286
287 function logAll(){
288 htmx.logger = function(elt, event, data) {
289 if(console) {
290 console.log(event, elt, data);
291 }
292 }
293 }
294
295 function find(eltOrSelector, selector) {
296 if (selector) {
297 return eltOrSelector.querySelector(selector);
298 } else {
299 return find(getDocument(), eltOrSelector);
300 }
301 }
302
303 function findAll(eltOrSelector, selector) {
304 if (selector) {
305 return eltOrSelector.querySelectorAll(selector);
306 } else {
307 return findAll(getDocument(), eltOrSelector);
308 }
309 }
310
311 function removeElement(elt, delay) {
312 elt = resolveTarget(elt);
313 if (delay) {
314 setTimeout(function(){removeElement(elt);}, delay)
315 } else {
316 elt.parentElement.removeChild(elt);
317 }
318 }
319
320 function addClassToElement(elt, clazz, delay) {
321 elt = resolveTarget(elt);
322 if (delay) {
323 setTimeout(function(){addClassToElement(elt, clazz);}, delay)
324 } else {
325 elt.classList.add(clazz);
326 }
327 }
328
329 function removeClassFromElement(elt, clazz, delay) {
330 elt = resolveTarget(elt);
331 if (delay) {
332 setTimeout(function(){removeClassFromElement(elt, clazz);}, delay)
333 } else {
334 elt.classList.remove(clazz);
335 }
336 }
337
338 function toggleClassOnElement(elt, clazz) {
339 elt = resolveTarget(elt);
340 elt.classList.toggle(clazz);
341 }
342
343 function takeClassForElement(elt, clazz) {
344 elt = resolveTarget(elt);
345 forEach(elt.parentElement.children, function(child){
346 removeClassFromElement(child, clazz);
347 })
348 addClassToElement(elt, clazz);
349 }
350
351 function closest(elt, selector) {
352 elt = resolveTarget(elt);
353 if (elt.closest) {
354 return elt.closest(selector);
355 } else {
356 do{
357 if (elt == null || matches(elt, selector)){
358 return elt;
359 }
360 }
361 while (elt = elt && parentElt(elt));
362 }
363 }
364
365 function querySelectorAllExt(elt, selector) {
366 if (selector.indexOf("closest ") === 0) {
367 return [closest(elt, selector.substr(8))];
368 } else if (selector.indexOf("find ") === 0) {
369 return [find(elt, selector.substr(5))];
370 } else if (selector === 'document') {
371 return [document];
372 } else {
373 return getDocument().querySelectorAll(selector);
374 }
375 }
376
377 function querySelectorExt(eltOrSelector, selector) {
378 return querySelectorAllExt(eltOrSelector, selector)[0]
379 }
380
381 function resolveTarget(arg2) {
382 if (isType(arg2, 'String')) {
383 return find(arg2);
384 } else {
385 return arg2;
386 }
387 }
388
389 function processEventArgs(arg1, arg2, arg3) {
390 if (isFunction(arg2)) {
391 return {
392 target: getDocument().body,
393 event: arg1,
394 listener: arg2
395 }
396 } else {
397 return {
398 target: resolveTarget(arg1),
399 event: arg2,
400 listener: arg3
401 }
402 }
403
404 }
405
406 function addEventListenerImpl(arg1, arg2, arg3) {
407 ready(function(){
408 var eventArgs = processEventArgs(arg1, arg2, arg3);
409 eventArgs.target.addEventListener(eventArgs.event, eventArgs.listener);
410 })
411 var b = isFunction(arg2);
412 return b ? arg2 : arg3;
413 }
414
415 function removeEventListenerImpl(arg1, arg2, arg3) {
416 ready(function(){
417 var eventArgs = processEventArgs(arg1, arg2, arg3);
418 eventArgs.target.removeEventListener(eventArgs.event, eventArgs.listener);
419 })
420 return isFunction(arg2) ? arg2 : arg3;
421 }
422
423 //====================================================================
424 // Node processing
425 //====================================================================
426
427 function getTarget(elt) {
428 var explicitTarget = getClosestMatch(elt, function(e){return getAttributeValue(e,"hx-target") !== null});
429 if (explicitTarget) {
430 var targetStr = getAttributeValue(explicitTarget, "hx-target");
431 if (targetStr === "this") {
432 return explicitTarget;
433 } else {
434 return querySelectorExt(elt, targetStr)
435 }
436 } else {
437 var data = getInternalData(elt);
438 if (data.boosted) {
439 return getDocument().body;
440 } else {
441 return elt;
442 }
443 }
444 }
445
446 function shouldSettleAttribute(name) {
447 var attributesToSettle = htmx.config.attributesToSettle;
448 for (var i = 0; i < attributesToSettle.length; i++) {
449 if (name === attributesToSettle[i]) {
450 return true;
451 }
452 }
453 return false;
454 }
455
456 function cloneAttributes(mergeTo, mergeFrom) {
457 forEach(mergeTo.attributes, function (attr) {
458 if (!mergeFrom.hasAttribute(attr.name) && shouldSettleAttribute(attr.name)) {
459 mergeTo.removeAttribute(attr.name)
460 }
461 });
462 forEach(mergeFrom.attributes, function (attr) {
463 if (shouldSettleAttribute(attr.name)) {
464 mergeTo.setAttribute(attr.name, attr.value);
465 }
466 });
467 }
468
469 function isInlineSwap(swapStyle, target) {
470 var extensions = getExtensions(target);
471 for (var i = 0; i < extensions.length; i++) {
472 var extension = extensions[i];
473 try {
474 if (extension.isInlineSwap(swapStyle)) {
475 return true;
476 }
477 } catch(e) {
478 logError(e);
479 }
480 }
481 return swapStyle === "outerHTML";
482 }
483
484 function oobSwap(oobValue, oobElement, settleInfo) {
485 var selector = "#" + oobElement.id;
486 var swapStyle = "outerHTML";
487 if (oobValue === "true") {
488 // do nothing
489 } else if (oobValue.indexOf(":") > 0) {
490 swapStyle = oobValue.substr(0, oobValue.indexOf(":"));
491 selector = oobValue.substr(oobValue.indexOf(":") + 1, oobValue.length);
492 } else {
493 swapStyle = oobValue;
494 }
495
496 var target = getDocument().querySelector(selector);
497 if (target) {
498 var fragment;
499 fragment = getDocument().createDocumentFragment();
500 fragment.appendChild(oobElement); // pulls the child out of the existing fragment
501 if (!isInlineSwap(swapStyle, target)) {
502 fragment = oobElement; // if this is not an inline swap, we use the content of the node, not the node itself
503 }
504 swap(swapStyle, target, target, fragment, settleInfo);
505 } else {
506 oobElement.parentNode.removeChild(oobElement);
507 triggerErrorEvent(getDocument().body, "htmx:oobErrorNoTarget", {content: oobElement})
508 }
509 return oobValue;
510 }
511
512 function handleOutOfBandSwaps(fragment, settleInfo) {
513 forEach(findAll(fragment, '[hx-swap-oob], [data-hx-swap-oob]'), function (oobElement) {
514 var oobValue = getAttributeValue(oobElement, "hx-swap-oob");
515 if (oobValue != null) {
516 oobSwap(oobValue, oobElement, settleInfo);
517 }
518 });
519 }
520
521 function handlePreservedElements(fragment) {
522 forEach(findAll(fragment, '[hx-preserve], [data-hx-preserve]'), function (preservedElt) {
523 var id = getAttributeValue(preservedElt, "id");
524 var oldElt = getDocument().getElementById(id);
525 if (oldElt != null) {
526 preservedElt.parentNode.replaceChild(oldElt, preservedElt);
527 }
528 });
529 }
530
531 function handleAttributes(parentNode, fragment, settleInfo) {
532 forEach(fragment.querySelectorAll("[id]"), function (newNode) {
533 if (newNode.id && newNode.id.length > 0) {
534 var oldNode = parentNode.querySelector(newNode.tagName + "[id='" + newNode.id + "']");
535 if (oldNode && oldNode !== parentNode) {
536 var newAttributes = newNode.cloneNode();
537 cloneAttributes(newNode, oldNode);
538 settleInfo.tasks.push(function () {
539 cloneAttributes(newNode, newAttributes);
540 });
541 }
542 }
543 });
544 }
545
546 function makeAjaxLoadTask(child) {
547 return function () {
548 processNode(child);
549 processScripts(child);
550 processFocus(child)
551 triggerEvent(child, 'htmx:load');
552 };
553 }
554
555 function processFocus(child) {
556 var autofocus = "[autofocus]";
557 var autoFocusedElt = matches(child, autofocus) ? child : child.querySelector(autofocus)
558 if (autoFocusedElt != null) {
559 autoFocusedElt.focus();
560 }
561 }
562
563 function insertNodesBefore(parentNode, insertBefore, fragment, settleInfo) {
564 handleAttributes(parentNode, fragment, settleInfo);
565 while(fragment.childNodes.length > 0){
566 var child = fragment.firstChild;
567 parentNode.insertBefore(child, insertBefore);
568 if (child.nodeType !== Node.TEXT_NODE && child.nodeType !== Node.COMMENT_NODE) {
569 settleInfo.tasks.push(makeAjaxLoadTask(child));
570 }
571 }
572 }
573
574 function cleanUpElement(element) {
575 var internalData = getInternalData(element);
576 if (internalData.webSocket) {
577 internalData.webSocket.close();
578 }
579 if (internalData.sseEventSource) {
580 internalData.sseEventSource.close();
581 }
582 if (internalData.listenerInfos) {
583 forEach(internalData.listenerInfos, function(info) {
584 if (element !== info.on) {
585 info.on.removeEventListener(info.trigger, info.listener);
586 }
587 });
588 }
589 if (element.children) { // IE
590 forEach(element.children, function(child) { cleanUpElement(child) });
591 }
592 }
593
594 function swapOuterHTML(target, fragment, settleInfo) {
595 if (target.tagName === "BODY") {
596 return swapInnerHTML(target, fragment, settleInfo);
597 } else {
598 var eltBeforeNewContent = target.previousSibling;
599 insertNodesBefore(parentElt(target), target, fragment, settleInfo);
600 if (eltBeforeNewContent == null) {
601 var newElt = parentElt(target).firstChild;
602 } else {
603 var newElt = eltBeforeNewContent.nextSibling;
604 }
605 getInternalData(target).replacedWith = newElt; // tuck away so we can fire events on it later
606 settleInfo.elts = [] // clear existing elements
607 while(newElt && newElt !== target) {
608 if (newElt.nodeType === Node.ELEMENT_NODE) {
609 settleInfo.elts.push(newElt);
610 }
611 newElt = newElt.nextElementSibling;
612 }
613 cleanUpElement(target);
614 parentElt(target).removeChild(target);
615 }
616 }
617
618 function swapAfterBegin(target, fragment, settleInfo) {
619 return insertNodesBefore(target, target.firstChild, fragment, settleInfo);
620 }
621
622 function swapBeforeBegin(target, fragment, settleInfo) {
623 return insertNodesBefore(parentElt(target), target, fragment, settleInfo);
624 }
625
626 function swapBeforeEnd(target, fragment, settleInfo) {
627 return insertNodesBefore(target, null, fragment, settleInfo);
628 }
629
630 function swapAfterEnd(target, fragment, settleInfo) {
631 return insertNodesBefore(parentElt(target), target.nextSibling, fragment, settleInfo);
632 }
633
634 function swapInnerHTML(target, fragment, settleInfo) {
635 var firstChild = target.firstChild;
636 insertNodesBefore(target, firstChild, fragment, settleInfo);
637 if (firstChild) {
638 while (firstChild.nextSibling) {
639 cleanUpElement(firstChild.nextSibling)
640 target.removeChild(firstChild.nextSibling);
641 }
642 cleanUpElement(firstChild)
643 target.removeChild(firstChild);
644 }
645 }
646
647 function maybeSelectFromResponse(elt, fragment) {
648 var selector = getClosestAttributeValue(elt, "hx-select");
649 if (selector) {
650 var newFragment = getDocument().createDocumentFragment();
651 forEach(fragment.querySelectorAll(selector), function (node) {
652 newFragment.appendChild(node);
653 });
654 fragment = newFragment;
655 }
656 return fragment;
657 }
658
659 function swap(swapStyle, elt, target, fragment, settleInfo) {
660 switch (swapStyle) {
661 case "none":
662 return;
663 case "outerHTML":
664 swapOuterHTML(target, fragment, settleInfo);
665 return;
666 case "afterbegin":
667 swapAfterBegin(target, fragment, settleInfo);
668 return;
669 case "beforebegin":
670 swapBeforeBegin(target, fragment, settleInfo);
671 return;
672 case "beforeend":
673 swapBeforeEnd(target, fragment, settleInfo);
674 return;
675 case "afterend":
676 swapAfterEnd(target, fragment, settleInfo);
677 return;
678 default:
679 var extensions = getExtensions(elt);
680 for (var i = 0; i < extensions.length; i++) {
681 var ext = extensions[i];
682 try {
683 var newElements = ext.handleSwap(swapStyle, target, fragment, settleInfo);
684 if (newElements) {
685 if (typeof newElements.length !== 'undefined') {
686 // if handleSwap returns an array (like) of elements, we handle them
687 for (var j = 0; j < newElements.length; j++) {
688 var child = newElements[j];
689 if (child.nodeType !== Node.TEXT_NODE && child.nodeType !== Node.COMMENT_NODE) {
690 settleInfo.tasks.push(makeAjaxLoadTask(child));
691 }
692 }
693 }
694 return;
695 }
696 } catch (e) {
697 logError(e);
698 }
699 }
700 swapInnerHTML(target, fragment, settleInfo);
701 }
702 }
703
704 var TITLE_FINDER = /<title>([\s\S]+?)<\/title>/im;
705 function findTitle(content) {
706 if(content.indexOf('<title>') > -1 &&
707 (content.indexOf('<svg>') == -1 ||
708 content.indexOf('<title>') < content.indexOf('<svg>'))) {
709 var result = TITLE_FINDER.exec(content);
710 if (result) {
711 return result[1];
712 }
713 }
714 }
715
716 function selectAndSwap(swapStyle, target, elt, responseText, settleInfo) {
717 var title = findTitle(responseText);
718 if(title) {
719 var titleElt = find("title");
720 if(titleElt) {
721 titleElt.innerHTML = title;
722 } else {
723 window.document.title = title;
724 }
725 }
726 var fragment = makeFragment(responseText);
727 if (fragment) {
728 handleOutOfBandSwaps(fragment, settleInfo);
729 fragment = maybeSelectFromResponse(elt, fragment);
730 handlePreservedElements(fragment);
731 return swap(swapStyle, elt, target, fragment, settleInfo);
732 }
733 }
734
735 function handleTrigger(xhr, header, elt) {
736 var triggerBody = xhr.getResponseHeader(header);
737 if (triggerBody.indexOf("{") === 0) {
738 var triggers = parseJSON(triggerBody);
739 for (var eventName in triggers) {
740 if (triggers.hasOwnProperty(eventName)) {
741 var detail = triggers[eventName];
742 if (!isRawObject(detail)) {
743 detail = {"value": detail}
744 }
745 triggerEvent(elt, eventName, detail);
746 }
747 }
748 } else {
749 triggerEvent(elt, triggerBody, []);
750 }
751 }
752
753 var WHITESPACE = /\s/;
754 var WHITESPACE_OR_COMMA = /[\s,]/;
755 var SYMBOL_START = /[_$a-zA-Z]/;
756 var SYMBOL_CONT = /[_$a-zA-Z0-9]/;
757 var STRINGISH_START = ['"', "'", "/"];
758 var NOT_WHITESPACE = /[^\s]/;
759 function tokenizeString(str) {
760 var tokens = [];
761 var position = 0;
762 while (position < str.length) {
763 if(SYMBOL_START.exec(str.charAt(position))) {
764 var startPosition = position;
765 while (SYMBOL_CONT.exec(str.charAt(position + 1))) {
766 position++;
767 }
768 tokens.push(str.substr(startPosition, position - startPosition + 1));
769 } else if (STRINGISH_START.indexOf(str.charAt(position)) !== -1) {
770 var startChar = str.charAt(position);
771 var startPosition = position;
772 position++;
773 while (position < str.length && str.charAt(position) !== startChar ) {
774 if (str.charAt(position) === "\\") {
775 position++;
776 }
777 position++;
778 }
779 tokens.push(str.substr(startPosition, position - startPosition + 1));
780 } else {
781 var symbol = str.charAt(position);
782 tokens.push(symbol);
783 }
784 position++;
785 }
786 return tokens;
787 }
788
789 function isPossibleRelativeReference(token, last, paramName) {
790 return SYMBOL_START.exec(token.charAt(0)) &&
791 token !== "true" &&
792 token !== "false" &&
793 token !== "this" &&
794 token !== paramName &&
795 last !== ".";
796 }
797
798 function maybeGenerateConditional(elt, tokens, paramName) {
799 if (tokens[0] === '[') {
800 tokens.shift();
801 var bracketCount = 1;
802 var conditionalSource = " return (function(" + paramName + "){ return (";
803 var last = null;
804 while (tokens.length > 0) {
805 var token = tokens[0];
806 if (token === "]") {
807 bracketCount--;
808 if (bracketCount === 0) {
809 if (last === null) {
810 conditionalSource = conditionalSource + "true";
811 }
812 tokens.shift();
813 conditionalSource += ")})";
814 try {
815 var conditionFunction = maybeEval(elt,function () {
816 return Function(conditionalSource)();
817 },
818 function(){return true})
819 conditionFunction.source = conditionalSource;
820 return conditionFunction;
821 } catch (e) {
822 triggerErrorEvent(getDocument().body, "htmx:syntax:error", {error:e, source:conditionalSource})
823 return null;
824 }
825 }
826 } else if (token === "[") {
827 bracketCount++;
828 }
829 if (isPossibleRelativeReference(token, last, paramName)) {
830 conditionalSource += "((" + paramName + "." + token + ") ? (" + paramName + "." + token + ") : (window." + token + "))";
831 } else {
832 conditionalSource = conditionalSource + token;
833 }
834 last = tokens.shift();
835 }
836 }
837 }
838
839 function consumeUntil(tokens, match) {
840 var result = "";
841 while (tokens.length > 0 && !tokens[0].match(match)) {
842 result += tokens.shift();
843 }
844 return result;
845 }
846
847 var INPUT_SELECTOR = 'input, textarea, select';
848 function getTriggerSpecs(elt) {
849 var explicitTrigger = getAttributeValue(elt, 'hx-trigger');
850 var triggerSpecs = [];
851 if (explicitTrigger) {
852 var tokens = tokenizeString(explicitTrigger);
853 do {
854 consumeUntil(tokens, NOT_WHITESPACE);
855 var initialLength = tokens.length;
856 var trigger = consumeUntil(tokens, /[,\[\s]/);
857 if (trigger !== "") {
858 if (trigger === "every") {
859 var every = {trigger: 'every'};
860 consumeUntil(tokens, NOT_WHITESPACE);
861 every.pollInterval = parseInterval(consumeUntil(tokens, /[,\[\s]/));
862 consumeUntil(tokens, NOT_WHITESPACE);
863 var eventFilter = maybeGenerateConditional(elt, tokens, "event");
864 if (eventFilter) {
865 every.eventFilter = eventFilter;
866 }
867 triggerSpecs.push(every);
868 } else if (trigger.indexOf("sse:") === 0) {
869 triggerSpecs.push({trigger: 'sse', sseEvent: trigger.substr(4)});
870 } else {
871 var triggerSpec = {trigger: trigger};
872 var eventFilter = maybeGenerateConditional(elt, tokens, "event");
873 if (eventFilter) {
874 triggerSpec.eventFilter = eventFilter;
875 }
876 while (tokens.length > 0 && tokens[0] !== ",") {
877 consumeUntil(tokens, NOT_WHITESPACE)
878 var token = tokens.shift();
879 if (token === "changed") {
880 triggerSpec.changed = true;
881 } else if (token === "once") {
882 triggerSpec.once = true;
883 } else if (token === "consume") {
884 triggerSpec.consume = true;
885 } else if (token === "delay" && tokens[0] === ":") {
886 tokens.shift();
887 triggerSpec.delay = parseInterval(consumeUntil(tokens, WHITESPACE_OR_COMMA));
888 } else if (token === "from" && tokens[0] === ":") {
889 tokens.shift();
890 triggerSpec.from = consumeUntil(tokens, WHITESPACE_OR_COMMA);
891 } else if (token === "target" && tokens[0] === ":") {
892 tokens.shift();
893 triggerSpec.target = consumeUntil(tokens, WHITESPACE_OR_COMMA);
894 } else if (token === "throttle" && tokens[0] === ":") {
895 tokens.shift();
896 triggerSpec.throttle = parseInterval(consumeUntil(tokens, WHITESPACE_OR_COMMA));
897 } else if (token === "queue" && tokens[0] === ":") {
898 tokens.shift();
899 triggerSpec.queue = consumeUntil(tokens, WHITESPACE_OR_COMMA);
900 } else if ((token === "root" || token === "threshold") && tokens[0] === ":") {
901 tokens.shift();
902 triggerSpec[token] = consumeUntil(tokens, WHITESPACE_OR_COMMA);
903 } else {
904 triggerErrorEvent(elt, "htmx:syntax:error", {token:tokens.shift()});
905 }
906 }
907 triggerSpecs.push(triggerSpec);
908 }
909 }
910 if (tokens.length === initialLength) {
911 triggerErrorEvent(elt, "htmx:syntax:error", {token:tokens.shift()});
912 }
913 consumeUntil(tokens, NOT_WHITESPACE);
914 } while (tokens[0] === "," && tokens.shift())
915 }
916
917 if (triggerSpecs.length > 0) {
918 return triggerSpecs;
919 } else if (matches(elt, 'form')) {
920 return [{trigger: 'submit'}];
921 } else if (matches(elt, INPUT_SELECTOR)) {
922 return [{trigger: 'change'}];
923 } else {
924 return [{trigger: 'click'}];
925 }
926 }
927
928 function cancelPolling(elt) {
929 getInternalData(elt).cancelled = true;
930 }
931
932 function processPolling(elt, verb, path, spec) {
933 var nodeData = getInternalData(elt);
934 nodeData.timeout = setTimeout(function () {
935 if (bodyContains(elt) && nodeData.cancelled !== true) {
936 if (!maybeFilterEvent(spec, makeEvent('hx:poll:trigger', {triggerSpec:spec}))) {
937 issueAjaxRequest(verb, path, elt);
938 }
939 processPolling(elt, verb, getAttributeValue(elt, "hx-" + verb), spec);
940 }
941 }, spec.pollInterval);
942 }
943
944 function isLocalLink(elt) {
945 return location.hostname === elt.hostname &&
946 getRawAttribute(elt,'href') &&
947 getRawAttribute(elt,'href').indexOf("#") !== 0;
948 }
949
950 function boostElement(elt, nodeData, triggerSpecs) {
951 if ((elt.tagName === "A" && isLocalLink(elt)) || elt.tagName === "FORM") {
952 nodeData.boosted = true;
953 var verb, path;
954 if (elt.tagName === "A") {
955 verb = "get";
956 path = getRawAttribute(elt, 'href');
957 nodeData.pushURL = true;
958 } else {
959 var rawAttribute = getRawAttribute(elt, "method");
960 verb = rawAttribute ? rawAttribute.toLowerCase() : "get";
961 if (verb === "get") {
962 nodeData.pushURL = true;
963 }
964 path = getRawAttribute(elt, 'action');
965 }
966 triggerSpecs.forEach(function(triggerSpec) {
967 addEventListener(elt, verb, path, nodeData, triggerSpec, true);
968 });
969 }
970 }
971
972 function shouldCancel(elt) {
973 return elt.tagName === "FORM" ||
974 (matches(elt, 'input[type="submit"], button') && closest(elt, 'form') !== null) ||
975 (elt.tagName === "A" && elt.href && (elt.getAttribute('href') === '#' ||
976 elt.getAttribute('href').indexOf("#") !== 0));
977 }
978
979 function ignoreBoostedAnchorCtrlClick(elt, evt) {
980 return getInternalData(elt).boosted && elt.tagName === "A" && evt.type === "click" && (evt.ctrlKey || evt.metaKey);
981 }
982
983 function maybeFilterEvent(triggerSpec, evt) {
984 var eventFilter = triggerSpec.eventFilter;
985 if(eventFilter){
986 try {
987 return eventFilter(evt) !== true;
988 } catch(e) {
989 triggerErrorEvent(getDocument().body, "htmx:eventFilter:error", {error: e, source:eventFilter.source});
990 return true;
991 }
992 }
993 return false;
994 }
995
996 function addEventListener(elt, verb, path, nodeData, triggerSpec, explicitCancel) {
997 var eltsToListenOn;
998 if (triggerSpec.from) {
999 eltsToListenOn = querySelectorAllExt(elt, triggerSpec.from);
1000 } else {
1001 eltsToListenOn = [elt];
1002 }
1003 forEach(eltsToListenOn, function (eltToListenOn) {
1004 var eventListener = function (evt) {
1005 if (!bodyContains(elt)) {
1006 eltToListenOn.removeEventListener(triggerSpec.trigger, eventListener);
1007 return;
1008 }
1009 if (ignoreBoostedAnchorCtrlClick(elt, evt)) {
1010 return;
1011 }
1012 if (explicitCancel || shouldCancel(elt)) {
1013 evt.preventDefault();
1014 }
1015 if (maybeFilterEvent(triggerSpec, evt)) {
1016 return;
1017 }
1018 var eventData = getInternalData(evt);
1019 eventData.triggerSpec = triggerSpec;
1020 if (eventData.handledFor == null) {
1021 eventData.handledFor = [];
1022 }
1023 var elementData = getInternalData(elt);
1024 if (eventData.handledFor.indexOf(elt) < 0) {
1025 eventData.handledFor.push(elt);
1026 if (triggerSpec.consume) {
1027 evt.stopPropagation();
1028 }
1029 if (triggerSpec.target && evt.target) {
1030 if (!matches(evt.target, triggerSpec.target)) {
1031 return;
1032 }
1033 }
1034 if (triggerSpec.once) {
1035 if (elementData.triggeredOnce) {
1036 return;
1037 } else {
1038 elementData.triggeredOnce = true;
1039 }
1040 }
1041 if (triggerSpec.changed) {
1042 if (elementData.lastValue === elt.value) {
1043 return;
1044 } else {
1045 elementData.lastValue = elt.value;
1046 }
1047 }
1048 if (elementData.delayed) {
1049 clearTimeout(elementData.delayed);
1050 }
1051 if (elementData.throttle) {
1052 return;
1053 }
1054
1055 if (triggerSpec.throttle) {
1056 if (!elementData.throttle) {
1057 issueAjaxRequest(verb, path, elt, evt);
1058 elementData.throttle = setTimeout(function () {
1059 elementData.throttle = null;
1060 }, triggerSpec.throttle);
1061 }
1062 } else if (triggerSpec.delay) {
1063 elementData.delayed = setTimeout(function () {
1064 issueAjaxRequest(verb, path, elt, evt);
1065 }, triggerSpec.delay);
1066 } else {
1067 issueAjaxRequest(verb, path, elt, evt);
1068 }
1069 }
1070 };
1071 if (nodeData.listenerInfos == null) {
1072 nodeData.listenerInfos = [];
1073 }
1074 nodeData.listenerInfos.push({
1075 trigger: triggerSpec.trigger,
1076 listener: eventListener,
1077 on: eltToListenOn
1078 })
1079 eltToListenOn.addEventListener(triggerSpec.trigger, eventListener);
1080 })
1081 }
1082
1083 var windowIsScrolling = false // used by initScrollHandler
1084 var scrollHandler = null;
1085 function initScrollHandler() {
1086 if (!scrollHandler) {
1087 scrollHandler = function() {
1088 windowIsScrolling = true
1089 };
1090 window.addEventListener("scroll", scrollHandler)
1091 setInterval(function() {
1092 if (windowIsScrolling) {
1093 windowIsScrolling = false;
1094 forEach(getDocument().querySelectorAll("[hx-trigger='revealed'],[data-hx-trigger='revealed']"), function (elt) {
1095 maybeReveal(elt);
1096 })
1097 }
1098 }, 200);
1099 }
1100 }
1101
1102 function maybeReveal(elt) {
1103 var nodeData = getInternalData(elt);
1104 if (!nodeData.revealed && isScrolledIntoView(elt)) {
1105 nodeData.revealed = true;
1106 if (nodeData.initialized) {
1107 issueAjaxRequest(nodeData.verb, nodeData.path, elt);
1108 } else {
1109 // if the node isn't initialized, wait for it before triggering the request
1110 elt.addEventListener("htmx:afterProcessNode",
1111 function () {
1112 issueAjaxRequest(nodeData.verb, nodeData.path, elt);
1113 }, {once: true});
1114 }
1115 }
1116 }
1117
1118 function processWebSocketInfo(elt, nodeData, info) {
1119 var values = splitOnWhitespace(info);
1120 for (var i = 0; i < values.length; i++) {
1121 var value = values[i].split(/:(.+)/);
1122 if (value[0] === "connect") {
1123 ensureWebSocket(elt, value[1], 0);
1124 }
1125 if (value[0] === "send") {
1126 processWebSocketSend(elt);
1127 }
1128 }
1129 }
1130
1131 function ensureWebSocket(elt, wssSource, retryCount) {
1132 if (!bodyContains(elt)) {
1133 return; // stop ensuring websocket connection when socket bearing element ceases to exist
1134 }
1135
1136 if (wssSource.indexOf("/") == 0) { // complete absolute paths only
1137 var base_part = location.hostname + (location.port ? ':'+location.port: '');
1138 if (location.protocol == 'https:') {
1139 wssSource = "wss://" + base_part + wssSource;
1140 } else if (location.protocol == 'http:') {
1141 wssSource = "ws://" + base_part + wssSource;
1142 }
1143 }
1144 var socket = htmx.createWebSocket(wssSource);
1145 socket.onerror = function (e) {
1146 triggerErrorEvent(elt, "htmx:wsError", {error:e, socket:socket});
1147 maybeCloseWebSocketSource(elt);
1148 };
1149
1150 socket.onclose = function (e) {
1151 if ([1006, 1012, 1013].indexOf(e.code) >= 0) { // Abnormal Closure/Service Restart/Try Again Later
1152 var delay = getWebSocketReconnectDelay(retryCount);
1153 setTimeout(function() {
1154 ensureWebSocket(elt, wssSource, retryCount+1); // creates a websocket with a new timeout
1155 }, delay);
1156 }
1157 };
1158 socket.onopen = function (e) {
1159 retryCount = 0;
1160 }
1161
1162 getInternalData(elt).webSocket = socket;
1163 socket.addEventListener('message', function (event) {
1164 if (maybeCloseWebSocketSource(elt)) {
1165 return;
1166 }
1167
1168 var response = event.data;
1169 withExtensions(elt, function(extension){
1170 response = extension.transformResponse(response, null, elt);
1171 });
1172
1173 var settleInfo = makeSettleInfo(elt);
1174 var fragment = makeFragment(response);
1175 var children = toArray(fragment.children);
1176 for (var i = 0; i < children.length; i++) {
1177 var child = children[i];
1178 oobSwap(getAttributeValue(child, "hx-swap-oob") || "true", child, settleInfo);
1179 }
1180
1181 settleImmediately(settleInfo.tasks);
1182 });
1183 }
1184
1185 function maybeCloseWebSocketSource(elt) {
1186 if (!bodyContains(elt)) {
1187 getInternalData(elt).webSocket.close();
1188 return true;
1189 }
1190 }
1191
1192 function processWebSocketSend(elt) {
1193 var webSocketSourceElt = getClosestMatch(elt, function (parent) {
1194 return getInternalData(parent).webSocket != null;
1195 });
1196 if (webSocketSourceElt) {
1197 elt.addEventListener(getTriggerSpecs(elt)[0].trigger, function (evt) {
1198 var webSocket = getInternalData(webSocketSourceElt).webSocket;
1199 var headers = getHeaders(elt, webSocketSourceElt);
1200 var results = getInputValues(elt, 'post');
1201 var errors = results.errors;
1202 var rawParameters = results.values;
1203 var expressionVars = getExpressionVars(elt);
1204 var allParameters = mergeObjects(rawParameters, expressionVars);
1205 var filteredParameters = filterValues(allParameters, elt);
1206 filteredParameters['HEADERS'] = headers;
1207 if (errors && errors.length > 0) {
1208 triggerEvent(elt, 'htmx:validation:halted', errors);
1209 return;
1210 }
1211 webSocket.send(JSON.stringify(filteredParameters));
1212 if(shouldCancel(elt)){
1213 evt.preventDefault();
1214 }
1215 });
1216 } else {
1217 triggerErrorEvent(elt, "htmx:noWebSocketSourceError");
1218 }
1219 }
1220
1221 function getWebSocketReconnectDelay(retryCount) {
1222 var delay = htmx.config.wsReconnectDelay;
1223 if (typeof delay === 'function') {
1224 return delay(retryCount);
1225 }
1226 if (delay === 'full-jitter') {
1227 var exp = Math.min(retryCount, 6);
1228 var maxDelay = 1000 * Math.pow(2, exp);
1229 return maxDelay * Math.random();
1230 }
1231 logError('htmx.config.wsReconnectDelay must either be a function or the string "full-jitter"');
1232 }
1233
1234 //====================================================================
1235 // Server Sent Events
1236 //====================================================================
1237
1238 function processSSEInfo(elt, nodeData, info) {
1239 var values = splitOnWhitespace(info);
1240 for (var i = 0; i < values.length; i++) {
1241 var value = values[i].split(/:(.+)/);
1242 if (value[0] === "connect") {
1243 processSSESource(elt, value[1]);
1244 }
1245
1246 if ((value[0] === "swap")) {
1247 processSSESwap(elt, value[1])
1248 }
1249 }
1250 }
1251
1252 function processSSESource(elt, sseSrc) {
1253 var source = htmx.createEventSource(sseSrc);
1254 source.onerror = function (e) {
1255 triggerErrorEvent(elt, "htmx:sseError", {error:e, source:source});
1256 maybeCloseSSESource(elt);
1257 };
1258 getInternalData(elt).sseEventSource = source;
1259 }
1260
1261 function processSSESwap(elt, sseEventName) {
1262 var sseSourceElt = getClosestMatch(elt, hasEventSource);
1263 if (sseSourceElt) {
1264 var sseEventSource = getInternalData(sseSourceElt).sseEventSource;
1265 var sseListener = function (event) {
1266 if (maybeCloseSSESource(sseSourceElt)) {
1267 sseEventSource.removeEventListener(sseEventName, sseListener);
1268 return;
1269 }
1270
1271 ///////////////////////////
1272 // TODO: merge this code with AJAX and WebSockets code in the future.
1273
1274 var response = event.data;
1275 withExtensions(elt, function(extension){
1276 response = extension.transformResponse(response, null, elt);
1277 });
1278
1279 var swapSpec = getSwapSpecification(elt)
1280 var target = getTarget(elt)
1281 var settleInfo = makeSettleInfo(elt);
1282
1283 selectAndSwap(swapSpec.swapStyle, elt, target, response, settleInfo)
1284 settleImmediately(settleInfo.tasks)
1285 triggerEvent(elt, "htmx:sseMessage", event)
1286 };
1287
1288 getInternalData(elt).sseListener = sseListener;
1289 sseEventSource.addEventListener(sseEventName, sseListener);
1290 } else {
1291 triggerErrorEvent(elt, "htmx:noSSESourceError");
1292 }
1293 }
1294
1295 function processSSETrigger(elt, verb, path, sseEventName) {
1296 var sseSourceElt = getClosestMatch(elt, hasEventSource);
1297 if (sseSourceElt) {
1298 var sseEventSource = getInternalData(sseSourceElt).sseEventSource;
1299 var sseListener = function () {
1300 if (!maybeCloseSSESource(sseSourceElt)) {
1301 if (bodyContains(elt)) {
1302 issueAjaxRequest(verb, path, elt);
1303 } else {
1304 sseEventSource.removeEventListener(sseEventName, sseListener);
1305 }
1306 }
1307 };
1308 getInternalData(elt).sseListener = sseListener;
1309 sseEventSource.addEventListener(sseEventName, sseListener);
1310 } else {
1311 triggerErrorEvent(elt, "htmx:noSSESourceError");
1312 }
1313 }
1314
1315 function maybeCloseSSESource(elt) {
1316 if (!bodyContains(elt)) {
1317 getInternalData(elt).sseEventSource.close();
1318 return true;
1319 }
1320 }
1321
1322 function hasEventSource(node) {
1323 return getInternalData(node).sseEventSource != null;
1324 }
1325
1326 //====================================================================
1327
1328 function loadImmediately(elt, verb, path, nodeData, delay) {
1329 var load = function(){
1330 if (!nodeData.loaded) {
1331 nodeData.loaded = true;
1332 issueAjaxRequest(verb, path, elt);
1333 }
1334 }
1335 if (delay) {
1336 setTimeout(load, delay);
1337 } else {
1338 load();
1339 }
1340 }
1341
1342 function processVerbs(elt, nodeData, triggerSpecs) {
1343 var explicitAction = false;
1344 forEach(VERBS, function (verb) {
1345 if (hasAttribute(elt,'hx-' + verb)) {
1346 var path = getAttributeValue(elt, 'hx-' + verb);
1347 explicitAction = true;
1348 nodeData.path = path;
1349 nodeData.verb = verb;
1350 triggerSpecs.forEach(function(triggerSpec) {
1351 if (triggerSpec.sseEvent) {
1352 processSSETrigger(elt, verb, path, triggerSpec.sseEvent);
1353 } else if (triggerSpec.trigger === "revealed") {
1354 initScrollHandler();
1355 maybeReveal(elt);
1356 } else if (triggerSpec.trigger === "intersect") {
1357 var observerOptions = {};
1358 if (triggerSpec.root) {
1359 observerOptions.root = querySelectorExt(triggerSpec.root)
1360 }
1361 if (triggerSpec.threshold) {
1362 observerOptions.threshold = parseFloat(triggerSpec.threshold);
1363 }
1364 var observer = new IntersectionObserver(function (entries) {
1365 for (var i = 0; i < entries.length; i++) {
1366 var entry = entries[i];
1367 if (entry.isIntersecting) {
1368 triggerEvent(elt, "intersect");
1369 break;
1370 }
1371 }
1372 }, observerOptions);
1373 observer.observe(elt);
1374 addEventListener(elt, verb, path, nodeData, triggerSpec);
1375 } else if (triggerSpec.trigger === "load") {
1376 loadImmediately(elt, verb, path, nodeData, triggerSpec.delay);
1377 } else if (triggerSpec.pollInterval) {
1378 nodeData.polling = true;
1379 processPolling(elt, verb, path, triggerSpec);
1380 } else {
1381 addEventListener(elt, verb, path, nodeData, triggerSpec);
1382 }
1383 });
1384 }
1385 });
1386 return explicitAction;
1387 }
1388
1389 function evalScript(script) {
1390 if (script.type === "text/javascript" || script.type === "") {
1391 try {
1392 maybeEval(script, function () {
1393 // wtf - https://stackoverflow.com/questions/9107240/1-evalthis-vs-evalthis-in-javascript
1394 (1, eval)(script.innerText);
1395 });
1396 } catch (e) {
1397 logError(e);
1398 }
1399 }
1400 }
1401
1402 function processScripts(elt) {
1403 if (matches(elt, "script")) {
1404 evalScript(elt);
1405 }
1406 forEach(findAll(elt, "script"), function (script) {
1407 evalScript(script);
1408 });
1409 }
1410
1411 function isBoosted() {
1412 return document.querySelector("[hx-boost], [data-hx-boost]");
1413 }
1414
1415 function findElementsToProcess(elt) {
1416 if (elt.querySelectorAll) {
1417 var boostedElts = isBoosted() ? ", a, form" : "";
1418 var results = elt.querySelectorAll(VERB_SELECTOR + boostedElts + ", [hx-sse], [data-hx-sse], [hx-ws]," +
1419 " [data-hx-ws]");
1420 return results;
1421 } else {
1422 return [];
1423 }
1424 }
1425
1426 function initButtonTracking(form){
1427 var maybeSetLastButtonClicked = function(evt){
1428 if (matches(evt.target, "button, input[type='submit']")) {
1429 var internalData = getInternalData(form);
1430 internalData.lastButtonClicked = evt.target;
1431 }
1432 };
1433
1434 // need to handle both click and focus in:
1435 // focusin - in case someone tabs in to a button and hits the space bar
1436 // click - on OSX buttons do not focus on click see https://bugs.webkit.org/show_bug.cgi?id=13724
1437
1438 form.addEventListener('click', maybeSetLastButtonClicked)
1439 form.addEventListener('focusin', maybeSetLastButtonClicked)
1440 form.addEventListener('focusout', function(evt){
1441 var internalData = getInternalData(form);
1442 internalData.lastButtonClicked = null;
1443 })
1444 }
1445
1446 function initNode(elt) {
1447 if (elt.closest && elt.closest(htmx.config.disableSelector)) {
1448 return;
1449 }
1450 var nodeData = getInternalData(elt);
1451 if (!nodeData.initialized) {
1452 nodeData.initialized = true;
1453 triggerEvent(elt, "htmx:beforeProcessNode")
1454
1455 if (elt.value) {
1456 nodeData.lastValue = elt.value;
1457 }
1458
1459 var triggerSpecs = getTriggerSpecs(elt);
1460 var explicitAction = processVerbs(elt, nodeData, triggerSpecs);
1461
1462 if (!explicitAction && getClosestAttributeValue(elt, "hx-boost") === "true") {
1463 boostElement(elt, nodeData, triggerSpecs);
1464 }
1465
1466 if (elt.tagName === "FORM") {
1467 initButtonTracking(elt);
1468 }
1469
1470 var sseInfo = getAttributeValue(elt, 'hx-sse');
1471 if (sseInfo) {
1472 processSSEInfo(elt, nodeData, sseInfo);
1473 }
1474
1475 var wsInfo = getAttributeValue(elt, 'hx-ws');
1476 if (wsInfo) {
1477 processWebSocketInfo(elt, nodeData, wsInfo);
1478 }
1479 triggerEvent(elt, "htmx:afterProcessNode");
1480 }
1481 }
1482
1483 function processNode(elt) {
1484 elt = resolveTarget(elt);
1485 initNode(elt);
1486 forEach(findElementsToProcess(elt), function(child) { initNode(child) });
1487 }
1488
1489 //====================================================================
1490 // Event/Log Support
1491 //====================================================================
1492
1493 function kebabEventName(str) {
1494 return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
1495 }
1496
1497 function makeEvent(eventName, detail) {
1498 var evt;
1499 if (window.CustomEvent && typeof window.CustomEvent === 'function') {
1500 evt = new CustomEvent(eventName, {bubbles: true, cancelable: true, detail: detail});
1501 } else {
1502 evt = getDocument().createEvent('CustomEvent');
1503 evt.initCustomEvent(eventName, true, true, detail);
1504 }
1505 return evt;
1506 }
1507
1508 function triggerErrorEvent(elt, eventName, detail) {
1509 triggerEvent(elt, eventName, mergeObjects({error:eventName}, detail));
1510 }
1511
1512 function ignoreEventForLogging(eventName) {
1513 return eventName === "htmx:afterProcessNode"
1514 }
1515
1516 function withExtensions(elt, toDo) {
1517 forEach(getExtensions(elt), function(extension){
1518 try {
1519 toDo(extension);
1520 } catch (e) {
1521 logError(e);
1522 }
1523 });
1524 }
1525
1526 function logError(msg) {
1527 if(console.error) {
1528 console.error(msg);
1529 } else if (console.log) {
1530 console.log("ERROR: ", msg);
1531 }
1532 }
1533
1534 function triggerEvent(elt, eventName, detail) {
1535 elt = resolveTarget(elt);
1536 if (detail == null) {
1537 detail = {};
1538 }
1539 detail["elt"] = elt;
1540 var event = makeEvent(eventName, detail);
1541 if (htmx.logger && !ignoreEventForLogging(eventName)) {
1542 htmx.logger(elt, eventName, detail);
1543 }
1544 if (detail.error) {
1545 logError(detail.error);
1546 triggerEvent(elt, "htmx:error", {errorInfo:detail})
1547 }
1548 var eventResult = elt.dispatchEvent(event);
1549 var kebabName = kebabEventName(eventName);
1550 if (eventResult && kebabName !== eventName) {
1551 var kebabedEvent = makeEvent(kebabName, event.detail);
1552 eventResult = eventResult && elt.dispatchEvent(kebabedEvent)
1553 }
1554 withExtensions(elt, function (extension) {
1555 eventResult = eventResult && (extension.onEvent(eventName, event) !== false)
1556 });
1557 return eventResult;
1558 }
1559
1560 //====================================================================
1561 // History Support
1562 //====================================================================
1563 var currentPathForHistory = null;
1564
1565 function getHistoryElement() {
1566 var historyElt = getDocument().querySelector('[hx-history-elt],[data-hx-history-elt]');
1567 return historyElt || getDocument().body;
1568 }
1569
1570 function saveToHistoryCache(url, content, title, scroll) {
1571 var historyCache = parseJSON(localStorage.getItem("htmx-history-cache")) || [];
1572 for (var i = 0; i < historyCache.length; i++) {
1573 if (historyCache[i].url === url) {
1574 historyCache.splice(i, 1);
1575 break;
1576 }
1577 }
1578 historyCache.push({url:url, content: content, title:title, scroll:scroll})
1579 while (historyCache.length > htmx.config.historyCacheSize) {
1580 historyCache.shift();
1581 }
1582 while(historyCache.length > 0){
1583 try {
1584 localStorage.setItem("htmx-history-cache", JSON.stringify(historyCache));
1585 break;
1586 } catch (e) {
1587 triggerErrorEvent(getDocument().body, "htmx:historyCacheError", {cause:e, cache: historyCache})
1588 historyCache.shift(); // shrink the cache and retry
1589 }
1590 }
1591 }
1592
1593 function getCachedHistory(url) {
1594 var historyCache = parseJSON(localStorage.getItem("htmx-history-cache")) || [];
1595 for (var i = 0; i < historyCache.length; i++) {
1596 if (historyCache[i].url === url) {
1597 return historyCache[i];
1598 }
1599 }
1600 return null;
1601 }
1602
1603 function cleanInnerHtmlForHistory(elt) {
1604 var className = htmx.config.requestClass;
1605 var clone = elt.cloneNode(true);
1606 forEach(findAll(clone, "." + className), function(child){
1607 removeClassFromElement(child, className);
1608 });
1609 return clone.innerHTML;
1610 }
1611
1612 function saveHistory() {
1613 var elt = getHistoryElement();
1614 var path = currentPathForHistory || location.pathname+location.search;
1615 triggerEvent(getDocument().body, "htmx:beforeHistorySave", {path:path, historyElt:elt});
1616 if(htmx.config.historyEnabled) history.replaceState({htmx:true}, getDocument().title, window.location.href);
1617 saveToHistoryCache(path, cleanInnerHtmlForHistory(elt), getDocument().title, window.scrollY);
1618 }
1619
1620 function pushUrlIntoHistory(path) {
1621 if(htmx.config.historyEnabled) history.pushState({htmx:true}, "", path);
1622 currentPathForHistory = path;
1623 }
1624
1625 function settleImmediately(tasks) {
1626 forEach(tasks, function (task) {
1627 task.call();
1628 });
1629 }
1630
1631 function loadHistoryFromServer(path) {
1632 var request = new XMLHttpRequest();
1633 var details = {path: path, xhr:request};
1634 triggerEvent(getDocument().body, "htmx:historyCacheMiss", details);
1635 request.open('GET', path, true);
1636 request.setRequestHeader("HX-History-Restore-Request", "true");
1637 request.onload = function () {
1638 if (this.status >= 200 && this.status < 400) {
1639 triggerEvent(getDocument().body, "htmx:historyCacheMissLoad", details);
1640 var fragment = makeFragment(this.response);
1641 fragment = fragment.querySelector('[hx-history-elt],[data-hx-history-elt]') || fragment;
1642 var historyElement = getHistoryElement();
1643 var settleInfo = makeSettleInfo(historyElement);
1644 swapInnerHTML(historyElement, fragment, settleInfo)
1645 settleImmediately(settleInfo.tasks);
1646 currentPathForHistory = path;
1647 triggerEvent(getDocument().body, "htmx:historyRestore", {path:path});
1648 } else {
1649 triggerErrorEvent(getDocument().body, "htmx:historyCacheMissLoadError", details);
1650 }
1651 };
1652 request.send();
1653 }
1654
1655 function restoreHistory(path) {
1656 saveHistory();
1657 path = path || location.pathname+location.search;
1658 var cached = getCachedHistory(path);
1659 if (cached) {
1660 var fragment = makeFragment(cached.content);
1661 var historyElement = getHistoryElement();
1662 var settleInfo = makeSettleInfo(historyElement);
1663 swapInnerHTML(historyElement, fragment, settleInfo)
1664 settleImmediately(settleInfo.tasks);
1665 document.title = cached.title;
1666 window.scrollTo(0, cached.scroll);
1667 currentPathForHistory = path;
1668 triggerEvent(getDocument().body, "htmx:historyRestore", {path:path});
1669 } else {
1670 if (htmx.config.refreshOnHistoryMiss) {
1671 window.location.reload(true);
1672 } else {
1673 loadHistoryFromServer(path);
1674 }
1675 }
1676 }
1677
1678 function shouldPush(elt) {
1679 var pushUrl = getClosestAttributeValue(elt, "hx-push-url");
1680 return (pushUrl && pushUrl !== "false") ||
1681 (getInternalData(elt).boosted && getInternalData(elt).pushURL);
1682 }
1683
1684 function getPushUrl(elt) {
1685 var pushUrl = getClosestAttributeValue(elt, "hx-push-url");
1686 return (pushUrl === "true" || pushUrl === "false") ? null : pushUrl;
1687 }
1688
1689 function addRequestIndicatorClasses(elt) {
1690 var indicator = getClosestAttributeValue(elt, 'hx-indicator');
1691 if (indicator) {
1692 var indicators = querySelectorAllExt(elt, indicator);
1693 } else {
1694 indicators = [elt];
1695 }
1696 forEach(indicators, function (ic) {
1697 ic.classList["add"].call(ic.classList, htmx.config.requestClass);
1698 });
1699 return indicators;
1700 }
1701
1702 function removeRequestIndicatorClasses(indicators) {
1703 forEach(indicators, function (ic) {
1704 ic.classList["remove"].call(ic.classList, htmx.config.requestClass);
1705 });
1706 }
1707
1708 //====================================================================
1709 // Input Value Processing
1710 //====================================================================
1711
1712 function haveSeenNode(processed, elt) {
1713 for (var i = 0; i < processed.length; i++) {
1714 var node = processed[i];
1715 if (node.isSameNode(elt)) {
1716 return true;
1717 }
1718 }
1719 return false;
1720 }
1721
1722 function shouldInclude(elt) {
1723 if(elt.name === "" || elt.name == null || elt.disabled) {
1724 return false;
1725 }
1726 // ignore "submitter" types (see jQuery src/serialize.js)
1727 if (elt.type === "button" || elt.type === "submit" || elt.tagName === "image" || elt.tagName === "reset" || elt.tagName === "file" ) {
1728 return false;
1729 }
1730 if (elt.type === "checkbox" || elt.type === "radio" ) {
1731 return elt.checked;
1732 }
1733 return true;
1734 }
1735
1736 function processInputValue(processed, values, errors, elt, validate) {
1737 if (elt == null || haveSeenNode(processed, elt)) {
1738 return;
1739 } else {
1740 processed.push(elt);
1741 }
1742 if (shouldInclude(elt)) {
1743 var name = getRawAttribute(elt,"name");
1744 var value = elt.value;
1745 if (elt.multiple) {
1746 value = toArray(elt.querySelectorAll("option:checked")).map(function (e) { return e.value });
1747 }
1748 // include file inputs
1749 if (elt.files) {
1750 value = toArray(elt.files);
1751 }
1752 // This is a little ugly because both the current value of the named value in the form
1753 // and the new value could be arrays, so we have to handle all four cases :/
1754 if (name != null && value != null) {
1755 var current = values[name];
1756 if(current) {
1757 if (Array.isArray(current)) {
1758 if (Array.isArray(value)) {
1759 values[name] = current.concat(value);
1760 } else {
1761 current.push(value);
1762 }
1763 } else {
1764 if (Array.isArray(value)) {
1765 values[name] = [current].concat(value);
1766 } else {
1767 values[name] = [current, value];
1768 }
1769 }
1770 } else {
1771 values[name] = value;
1772 }
1773 }
1774 if (validate) {
1775 validateElement(elt, errors);
1776 }
1777 }
1778 if (matches(elt, 'form')) {
1779 var inputs = elt.elements;
1780 forEach(inputs, function(input) {
1781 processInputValue(processed, values, errors, input, validate);
1782 });
1783 }
1784 }
1785
1786 function validateElement(element, errors) {
1787 if (element.willValidate) {
1788 triggerEvent(element, "htmx:validation:validate")
1789 if (!element.checkValidity()) {
1790 errors.push({elt: element, message:element.validationMessage, validity:element.validity});
1791 triggerEvent(element, "htmx:validation:failed", {message:element.validationMessage, validity:element.validity})
1792 }
1793 }
1794 }
1795
1796 function getInputValues(elt, verb) {
1797 var processed = [];
1798 var values = {};
1799 var formValues = {};
1800 var errors = [];
1801
1802 // only validate when form is directly submitted and novalidate is not set
1803 var validate = matches(elt, 'form') && elt.noValidate !== true;
1804
1805 // for a non-GET include the closest form
1806 if (verb !== 'get') {
1807 processInputValue(processed, formValues, errors, closest(elt, 'form'), validate);
1808 }
1809
1810 // include the element itself
1811 processInputValue(processed, values, errors, elt, validate);
1812
1813 // if a button or submit was clicked last, include its value
1814 var internalData = getInternalData(elt);
1815 if (internalData.lastButtonClicked) {
1816 var name = getRawAttribute(internalData.lastButtonClicked,"name");
1817 if (name) {
1818 values[name] = internalData.lastButtonClicked.value;
1819 }
1820 }
1821
1822 // include any explicit includes
1823 var includes = getClosestAttributeValue(elt, "hx-include");
1824 if (includes) {
1825 var nodes = querySelectorAllExt(elt, includes);
1826 forEach(nodes, function(node) {
1827 processInputValue(processed, values, errors, node, validate);
1828 // if a non-form is included, include any input values within it
1829 if (!matches(node, 'form')) {
1830 forEach(node.querySelectorAll(INPUT_SELECTOR), function (descendant) {
1831 processInputValue(processed, values, errors, descendant, validate);
1832 })
1833 }
1834 });
1835 }
1836
1837 // form values take precedence, overriding the regular values
1838 values = mergeObjects(values, formValues);
1839
1840 return {errors:errors, values:values};
1841 }
1842
1843 function appendParam(returnStr, name, realValue) {
1844 if (returnStr !== "") {
1845 returnStr += "&";
1846 }
1847 returnStr += encodeURIComponent(name) + "=" + encodeURIComponent(realValue);
1848 return returnStr;
1849 }
1850
1851 function urlEncode(values) {
1852 var returnStr = "";
1853 for (var name in values) {
1854 if (values.hasOwnProperty(name)) {
1855 var value = values[name];
1856 if (Array.isArray(value)) {
1857 forEach(value, function(v) {
1858 returnStr = appendParam(returnStr, name, v);
1859 });
1860 } else {
1861 returnStr = appendParam(returnStr, name, value);
1862 }
1863 }
1864 }
1865 return returnStr;
1866 }
1867
1868 function makeFormData(values) {
1869 var formData = new FormData();
1870 for (var name in values) {
1871 if (values.hasOwnProperty(name)) {
1872 var value = values[name];
1873 if (Array.isArray(value)) {
1874 forEach(value, function(v) {
1875 formData.append(name, v);
1876 });
1877 } else {
1878 formData.append(name, value);
1879 }
1880 }
1881 }
1882 return formData;
1883 }
1884
1885 //====================================================================
1886 // Ajax
1887 //====================================================================
1888
1889 function getHeaders(elt, target, prompt) {
1890 var headers = {
1891 "HX-Request" : "true",
1892 "HX-Trigger" : getRawAttribute(elt, "id"),
1893 "HX-Trigger-Name" : getRawAttribute(elt, "name"),
1894 "HX-Target" : getAttributeValue(target, "id"),
1895 "HX-Current-URL" : getDocument().location.href,
1896 }
1897 getValuesForElement(elt, "hx-headers", false, headers)
1898 if (prompt !== undefined) {
1899 headers["HX-Prompt"] = prompt;
1900 }
1901 return headers;
1902 }
1903
1904 function filterValues(inputValues, elt) {
1905 var paramsValue = getClosestAttributeValue(elt, "hx-params");
1906 if (paramsValue) {
1907 if (paramsValue === "none") {
1908 return {};
1909 } else if (paramsValue === "*") {
1910 return inputValues;
1911 } else if(paramsValue.indexOf("not ") === 0) {
1912 forEach(paramsValue.substr(4).split(","), function (name) {
1913 name = name.trim();
1914 delete inputValues[name];
1915 });
1916 return inputValues;
1917 } else {
1918 var newValues = {}
1919 forEach(paramsValue.split(","), function (name) {
1920 name = name.trim();
1921 newValues[name] = inputValues[name];
1922 });
1923 return newValues;
1924 }
1925 } else {
1926 return inputValues;
1927 }
1928 }
1929
1930 function isAnchorLink(elt) {
1931 return getRawAttribute(elt, 'href') && getRawAttribute(elt, 'href').indexOf("#") >=0
1932 }
1933
1934 function getSwapSpecification(elt) {
1935 var swapInfo = getClosestAttributeValue(elt, "hx-swap");
1936 var swapSpec = {
1937 "swapStyle" : getInternalData(elt).boosted ? 'innerHTML' : htmx.config.defaultSwapStyle,
1938 "swapDelay" : htmx.config.defaultSwapDelay,
1939 "settleDelay" : htmx.config.defaultSettleDelay
1940 }
1941 if (getInternalData(elt).boosted && !isAnchorLink(elt)) {
1942 swapSpec["show"] = "top"
1943 }
1944 if (swapInfo) {
1945 var split = splitOnWhitespace(swapInfo);
1946 if (split.length > 0) {
1947 swapSpec["swapStyle"] = split[0];
1948 for (var i = 1; i < split.length; i++) {
1949 var modifier = split[i];
1950 if (modifier.indexOf("swap:") === 0) {
1951 swapSpec["swapDelay"] = parseInterval(modifier.substr(5));
1952 }
1953 if (modifier.indexOf("settle:") === 0) {
1954 swapSpec["settleDelay"] = parseInterval(modifier.substr(7));
1955 }
1956 if (modifier.indexOf("scroll:") === 0) {
1957 var scrollSpec = modifier.substr(7);
1958 var splitSpec = scrollSpec.split(":");
1959 var scrollVal = splitSpec.pop();
1960 var selectorVal = splitSpec.length > 0 ? splitSpec.join(":") : null;
1961 swapSpec["scroll"] = scrollVal;
1962 swapSpec["scrollTarget"] = selectorVal;
1963 }
1964 if (modifier.indexOf("show:") === 0) {
1965 var showSpec = modifier.substr(5);
1966 var splitSpec = showSpec.split(":");
1967 var showVal = splitSpec.pop();
1968 var selectorVal = splitSpec.length > 0 ? splitSpec.join(":") : null;
1969 swapSpec["show"] = showVal;
1970 swapSpec["showTarget"] = selectorVal;
1971 }
1972 }
1973 }
1974 }
1975 return swapSpec;
1976 }
1977
1978 function encodeParamsForBody(xhr, elt, filteredParameters) {
1979 var encodedParameters = null;
1980 withExtensions(elt, function (extension) {
1981 if (encodedParameters == null) {
1982 encodedParameters = extension.encodeParameters(xhr, filteredParameters, elt);
1983 }
1984 });
1985 if (encodedParameters != null) {
1986 return encodedParameters;
1987 } else {
1988 if (getClosestAttributeValue(elt, "hx-encoding") === "multipart/form-data") {
1989 return makeFormData(filteredParameters);
1990 } else {
1991 return urlEncode(filteredParameters);
1992 }
1993 }
1994 }
1995
1996 function makeSettleInfo(target) {
1997 return {tasks: [], elts: [target]};
1998 }
1999
2000 function updateScrollState(content, swapSpec) {
2001 var first = content[0];
2002 var last = content[content.length - 1];
2003 if (swapSpec.scroll) {
2004 var target = null;
2005 if (swapSpec.scrollTarget) {
2006 target = querySelectorExt(first, swapSpec.scrollTarget);
2007 }
2008 if (swapSpec.scroll === "top" && (first || target)) {
2009 target = target || first;
2010 target.scrollTop = 0;
2011 }
2012 if (swapSpec.scroll === "bottom" && (last || target)) {
2013 target = target || last;
2014 target.scrollTop = target.scrollHeight;
2015 }
2016 }
2017 if (swapSpec.show) {
2018 var target = null;
2019 if (swapSpec.showTarget) {
2020 var targetStr = swapSpec.showTarget;
2021 if (swapSpec.showTarget === "window") {
2022 targetStr = "body";
2023 }
2024 target = querySelectorExt(first, targetStr);
2025 }
2026 if (swapSpec.show === "top" && (first || target)) {
2027 target = target || first;
2028 target.scrollIntoView({block:'start', behavior: htmx.config.scrollBehavior});
2029 }
2030 if (swapSpec.show === "bottom" && (last || target)) {
2031 target = target || last;
2032 target.scrollIntoView({block:'end', behavior: htmx.config.scrollBehavior});
2033 }
2034 }
2035 }
2036
2037 function getValuesForElement(elt, attr, evalAsDefault, values) {
2038 if (values == null) {
2039 values = {};
2040 }
2041 if (elt == null) {
2042 return values;
2043 }
2044 var attributeValue = getAttributeValue(elt, attr);
2045 if (attributeValue) {
2046 var str = attributeValue.trim();
2047 var evaluateValue = evalAsDefault;
2048 if (str.indexOf("javascript:") === 0) {
2049 str = str.substr(11);
2050 evaluateValue = true;
2051 } else if (str.indexOf("js:") === 0) {
2052 str = str.substr(3);
2053 evaluateValue = true;
2054 }
2055 if (str.indexOf('{') !== 0) {
2056 str = "{" + str + "}";
2057 }
2058 var varsValues;
2059 if (evaluateValue) {
2060 varsValues = maybeEval(elt,function () {return Function("return (" + str + ")")();}, {});
2061 } else {
2062 varsValues = parseJSON(str);
2063 }
2064 for (var key in varsValues) {
2065 if (varsValues.hasOwnProperty(key)) {
2066 if (values[key] == null) {
2067 values[key] = varsValues[key];
2068 }
2069 }
2070 }
2071 }
2072 return getValuesForElement(parentElt(elt), attr, evalAsDefault, values);
2073 }
2074
2075 function maybeEval(elt, toEval, defaultVal) {
2076 if (htmx.config.allowEval) {
2077 return toEval();
2078 } else {
2079 triggerErrorEvent(elt, 'htmx:evalDisallowedError');
2080 return defaultVal;
2081 }
2082 }
2083
2084 function getHXVarsForElement(elt, expressionVars) {
2085 return getValuesForElement(elt, "hx-vars", true, expressionVars);
2086 }
2087
2088 function getHXValsForElement(elt, expressionVars) {
2089 return getValuesForElement(elt, "hx-vals", false, expressionVars);
2090 }
2091
2092 function getExpressionVars(elt) {
2093 return mergeObjects(getHXVarsForElement(elt), getHXValsForElement(elt));
2094 }
2095
2096 function safelySetHeaderValue(xhr, header, headerValue) {
2097 if (headerValue !== null) {
2098 try {
2099 xhr.setRequestHeader(header, headerValue);
2100 } catch (e) {
2101 // On an exception, try to set the header URI encoded instead
2102 xhr.setRequestHeader(header, encodeURIComponent(headerValue));
2103 xhr.setRequestHeader(header + "-URI-AutoEncoded", "true");
2104 }
2105 }
2106 }
2107
2108 function getResponseURL(xhr) {
2109 // NB: IE11 does not support this stuff
2110 if (xhr.responseURL && typeof(URL) !== "undefined") {
2111 try {
2112 var url = new URL(xhr.responseURL);
2113 return url.pathname + url.search;
2114 } catch (e) {
2115 triggerErrorEvent(getDocument().body, "htmx:badResponseUrl", {url: xhr.responseURL});
2116 }
2117 }
2118 }
2119
2120 function hasHeader(xhr, regexp) {
2121 return xhr.getAllResponseHeaders().match(regexp);
2122 }
2123
2124 function ajaxHelper(verb, path, context) {
2125 if (context) {
2126 if (context instanceof Element || isType(context, 'String')) {
2127 return issueAjaxRequest(verb, path, null, null, {
2128 targetOverride: resolveTarget(context)
2129 });
2130 } else {
2131 return issueAjaxRequest(verb, path, resolveTarget(context.source), context.event,
2132 {
2133 handler : context.handler,
2134 headers : context.headers,
2135 values : context.values,
2136 targetOverride: resolveTarget(context.target)
2137 });
2138 }
2139 } else {
2140 return issueAjaxRequest(verb, path);
2141 }
2142 }
2143
2144 function hierarchyForElt(elt) {
2145 var arr = [];
2146 while (elt) {
2147 arr.push(elt);
2148 elt = elt.parentElement;
2149 }
2150 return arr;
2151 }
2152
2153 function issueAjaxRequest(verb, path, elt, event, etc) {
2154 var resolve = null;
2155 var reject = null;
2156 etc = etc != null ? etc : {};
2157 if(typeof Promise !== "undefined"){
2158 var promise = new Promise(function (_resolve, _reject) {
2159 resolve = _resolve;
2160 reject = _reject;
2161 });
2162 }
2163 if(elt == null) {
2164 elt = getDocument().body;
2165 }
2166 var responseHandler = etc.handler || handleAjaxResponse;
2167
2168 if (!bodyContains(elt)) {
2169 return; // do not issue requests for elements removed from the DOM
2170 }
2171 var target = etc.targetOverride || getTarget(elt);
2172 if (target == null) {
2173 triggerErrorEvent(elt, 'htmx:targetError', {target: getAttributeValue(elt, "hx-target")});
2174 return;
2175 }
2176 var eltData = getInternalData(elt);
2177 if (eltData.requestInFlight) {
2178 var queueStrategy = 'last';
2179 if (event) {
2180 var eventData = getInternalData(event);
2181 if (eventData && eventData.triggerSpec && eventData.triggerSpec.queue) {
2182 queueStrategy = eventData.triggerSpec.queue;
2183 }
2184 }
2185 if (eltData.queuedRequests == null) {
2186 eltData.queuedRequests = [];
2187 }
2188 if (queueStrategy === "first" && eltData.queuedRequests.length === 0) {
2189 eltData.queuedRequests.push(function () {
2190 issueAjaxRequest(verb, path, elt, event)
2191 });
2192 } else if (queueStrategy === "all") {
2193 eltData.queuedRequests.push(function () {
2194 issueAjaxRequest(verb, path, elt, event)
2195 });
2196 } else if (queueStrategy === "last") {
2197 eltData.queuedRequests = []; // dump existing queue
2198 eltData.queuedRequests.push(function () {
2199 issueAjaxRequest(verb, path, elt, event)
2200 });
2201 }
2202 return;
2203 } else {
2204 eltData.requestInFlight = true;
2205 }
2206 var endRequestLock = function(){
2207 eltData.requestInFlight = false
2208 if (eltData.queuedRequests != null &&
2209 eltData.queuedRequests.length > 0) {
2210 var queuedRequest = eltData.queuedRequests.shift();
2211 queuedRequest();
2212 }
2213 }
2214 var promptQuestion = getClosestAttributeValue(elt, "hx-prompt");
2215 if (promptQuestion) {
2216 var promptResponse = prompt(promptQuestion);
2217 // prompt returns null if cancelled and empty string if accepted with no entry
2218 if (promptResponse === null ||
2219 !triggerEvent(elt, 'htmx:prompt', {prompt: promptResponse, target:target})) {
2220 maybeCall(resolve);
2221 endRequestLock();
2222 return promise;
2223 }
2224 }
2225
2226 var confirmQuestion = getClosestAttributeValue(elt, "hx-confirm");
2227 if (confirmQuestion) {
2228 if(!confirm(confirmQuestion)) {
2229 maybeCall(resolve);
2230 endRequestLock()
2231 return promise;
2232 }
2233 }
2234
2235 var xhr = new XMLHttpRequest();
2236
2237 var headers = getHeaders(elt, target, promptResponse);
2238 if (etc.headers) {
2239 headers = mergeObjects(headers, etc.headers);
2240 }
2241 var results = getInputValues(elt, verb);
2242 var errors = results.errors;
2243 var rawParameters = results.values;
2244 if (etc.values) {
2245 rawParameters = mergeObjects(rawParameters, etc.values);
2246 }
2247 var expressionVars = getExpressionVars(elt);
2248 var allParameters = mergeObjects(rawParameters, expressionVars);
2249 var filteredParameters = filterValues(allParameters, elt);
2250
2251 if (verb !== 'get' && getClosestAttributeValue(elt, "hx-encoding") == null) {
2252 headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
2253 }
2254
2255 // behavior of anchors w/ empty href is to use the current URL
2256 if (path == null || path === "") {
2257 path = getDocument().location.href;
2258 }
2259
2260 var requestAttrValues = getValuesForElement(elt, 'hx-request');
2261
2262 var requestConfig = {
2263 parameters: filteredParameters,
2264 unfilteredParameters: allParameters,
2265 headers:headers,
2266 target:target,
2267 verb:verb,
2268 errors:errors,
2269 withCredentials: etc.credentials || requestAttrValues.credentials || htmx.config.withCredentials,
2270 timeout: etc.timeout || requestAttrValues.timeout || htmx.config.timeout,
2271 path:path,
2272 triggeringEvent:event
2273 };
2274
2275 if(!triggerEvent(elt, 'htmx:configRequest', requestConfig)){
2276 maybeCall(resolve);
2277 endRequestLock();
2278 return promise;
2279 }
2280
2281 // copy out in case the object was overwritten
2282 path = requestConfig.path;
2283 verb = requestConfig.verb;
2284 headers = requestConfig.headers;
2285 filteredParameters = requestConfig.parameters;
2286 errors = requestConfig.errors;
2287
2288 if(errors && errors.length > 0){
2289 triggerEvent(elt, 'htmx:validation:halted', requestConfig)
2290 maybeCall(resolve);
2291 endRequestLock();
2292 return promise;
2293 }
2294
2295 var splitPath = path.split("#");
2296 var pathNoAnchor = splitPath[0];
2297 var anchor = splitPath[1];
2298 if (verb === 'get') {
2299 var finalPathForGet = pathNoAnchor;
2300 var values = Object.keys(filteredParameters).length !== 0;
2301 if (values) {
2302 if (finalPathForGet.indexOf("?") < 0) {
2303 finalPathForGet += "?";
2304 } else {
2305 finalPathForGet += "&";
2306 }
2307 finalPathForGet += urlEncode(filteredParameters);
2308 if (anchor) {
2309 finalPathForGet += "#" + anchor;
2310 }
2311 }
2312 xhr.open('GET', finalPathForGet, true);
2313 } else {
2314 xhr.open(verb.toUpperCase(), path, true);
2315 }
2316
2317 xhr.overrideMimeType("text/html");
2318 xhr.withCredentials = requestConfig.withCredentials;
2319 xhr.timeout = requestConfig.timeout;
2320
2321 // request headers
2322 if (requestAttrValues.noHeaders) {
2323 // ignore all headers
2324 } else {
2325 for (var header in headers) {
2326 if (headers.hasOwnProperty(header)) {
2327 var headerValue = headers[header];
2328 safelySetHeaderValue(xhr, header, headerValue);
2329 }
2330 }
2331 }
2332
2333 var responseInfo = {xhr: xhr, target: target, requestConfig: requestConfig, pathInfo:{
2334 path:path, finalPath:finalPathForGet, anchor:anchor
2335 }
2336 };
2337
2338 xhr.onload = function () {
2339 try {
2340 var hierarchy = hierarchyForElt(elt);
2341 responseHandler(elt, responseInfo);
2342 removeRequestIndicatorClasses(indicators);
2343 triggerEvent(elt, 'htmx:afterRequest', responseInfo);
2344 triggerEvent(elt, 'htmx:afterOnLoad', responseInfo);
2345 // if the body no longer contains the element, trigger the even on the closest parent
2346 // remaining in the DOM
2347 if (!bodyContains(elt)) {
2348 var secondaryTriggerElt = null;
2349 while (hierarchy.length > 0 && secondaryTriggerElt == null) {
2350 var parentEltInHierarchy = hierarchy.shift();
2351 if (bodyContains(parentEltInHierarchy)) {
2352 secondaryTriggerElt = parentEltInHierarchy;
2353 }
2354 }
2355 if (secondaryTriggerElt) {
2356 triggerEvent(secondaryTriggerElt, 'htmx:afterRequest', responseInfo);
2357 triggerEvent(secondaryTriggerElt, 'htmx:afterOnLoad', responseInfo);
2358 }
2359 }
2360 maybeCall(resolve);
2361 endRequestLock();
2362 } catch (e) {
2363 triggerErrorEvent(elt, 'htmx:onLoadError', mergeObjects({error:e}, responseInfo));
2364 throw e;
2365 }
2366 }
2367 xhr.onerror = function () {
2368 removeRequestIndicatorClasses(indicators);
2369 triggerErrorEvent(elt, 'htmx:afterRequest', responseInfo);
2370 triggerErrorEvent(elt, 'htmx:sendError', responseInfo);
2371 maybeCall(reject);
2372 endRequestLock();
2373 }
2374 xhr.onabort = function() {
2375 removeRequestIndicatorClasses(indicators);
2376 triggerErrorEvent(elt, 'htmx:afterRequest', responseInfo);
2377 triggerErrorEvent(elt, 'htmx:sendAbort', responseInfo);
2378 maybeCall(reject);
2379 endRequestLock();
2380 }
2381 xhr.ontimeout = function() {
2382 removeRequestIndicatorClasses(indicators);
2383 triggerErrorEvent(elt, 'htmx:afterRequest', responseInfo);
2384 triggerErrorEvent(elt, 'htmx:timeout', responseInfo);
2385 maybeCall(reject);
2386 endRequestLock();
2387 }
2388 if(!triggerEvent(elt, 'htmx:beforeRequest', responseInfo)){
2389 maybeCall(resolve);
2390 endRequestLock()
2391 return promise
2392 }
2393 var indicators = addRequestIndicatorClasses(elt);
2394
2395 forEach(['loadstart', 'loadend', 'progress', 'abort'], function(eventName) {
2396 forEach([xhr, xhr.upload], function (target) {
2397 target.addEventListener(eventName, function(event){
2398 triggerEvent(elt, "htmx:xhr:" + eventName, {
2399 lengthComputable:event.lengthComputable,
2400 loaded:event.loaded,
2401 total:event.total
2402 });
2403 })
2404 });
2405 });
2406 triggerEvent(elt, 'htmx:beforeSend', responseInfo);
2407 xhr.send(verb === 'get' ? null : encodeParamsForBody(xhr, elt, filteredParameters));
2408 return promise;
2409 }
2410
2411 function handleAjaxResponse(elt, responseInfo) {
2412 var xhr = responseInfo.xhr;
2413 var target = responseInfo.target;
2414
2415 if (!triggerEvent(elt, 'htmx:beforeOnLoad', responseInfo)) return;
2416
2417 if (hasHeader(xhr, /HX-Trigger:/i)) {
2418 handleTrigger(xhr, "HX-Trigger", elt);
2419 }
2420
2421 if (hasHeader(xhr,/HX-Push:/i)) {
2422 var pushedUrl = xhr.getResponseHeader("HX-Push");
2423 }
2424
2425 if (hasHeader(xhr, /HX-Redirect:/i)) {
2426 window.location.href = xhr.getResponseHeader("HX-Redirect");
2427 return;
2428 }
2429
2430 if (hasHeader(xhr,/HX-Refresh:/i)) {
2431 if ("true" === xhr.getResponseHeader("HX-Refresh")) {
2432 location.reload();
2433 return;
2434 }
2435 }
2436
2437 var shouldSaveHistory = shouldPush(elt) || pushedUrl;
2438
2439 if (xhr.status >= 200 && xhr.status < 400) {
2440 if (xhr.status === 286) {
2441 cancelPolling(elt);
2442 }
2443 // don't process 'No Content'
2444 if (xhr.status !== 204) {
2445 if (!triggerEvent(target, 'htmx:beforeSwap', responseInfo)) return;
2446
2447 var serverResponse = xhr.response;
2448 withExtensions(elt, function(extension){
2449 serverResponse = extension.transformResponse(serverResponse, xhr, elt);
2450 });
2451
2452 // Save current page
2453 if (shouldSaveHistory) {
2454 saveHistory();
2455 }
2456
2457 var swapSpec = getSwapSpecification(elt);
2458
2459 target.classList.add(htmx.config.swappingClass);
2460 var doSwap = function () {
2461 try {
2462
2463 var activeElt = document.activeElement;
2464 var selectionInfo = {};
2465 try {
2466 selectionInfo = {
2467 elt: activeElt,
2468 start: activeElt ? activeElt.selectionStart : null,
2469 end: activeElt ? activeElt.selectionEnd : null
2470 };
2471 } catch (e) {
2472 // safari issue - see https://github.com/microsoft/playwright/issues/5894
2473 }
2474
2475 var settleInfo = makeSettleInfo(target);
2476 selectAndSwap(swapSpec.swapStyle, target, elt, serverResponse, settleInfo);
2477
2478 if (selectionInfo.elt &&
2479 !bodyContains(selectionInfo.elt) &&
2480 selectionInfo.elt.id) {
2481 var newActiveElt = document.getElementById(selectionInfo.elt.id);
2482 if (newActiveElt) {
2483 if (selectionInfo.start && newActiveElt.setSelectionRange) {
2484 newActiveElt.setSelectionRange(selectionInfo.start, selectionInfo.end);
2485 }
2486 newActiveElt.focus();
2487 }
2488 }
2489
2490 target.classList.remove(htmx.config.swappingClass);
2491 forEach(settleInfo.elts, function (elt) {
2492 if (elt.classList) {
2493 elt.classList.add(htmx.config.settlingClass);
2494 }
2495 triggerEvent(elt, 'htmx:afterSwap', responseInfo);
2496 });
2497 if (responseInfo.pathInfo.anchor) {
2498 location.hash = responseInfo.pathInfo.anchor;
2499 }
2500
2501 if (hasHeader(xhr, /HX-Trigger-After-Swap:/i)) {
2502 var finalElt = elt;
2503 if (!bodyContains(elt)) {
2504 finalElt = getDocument().body;
2505 }
2506 handleTrigger(xhr, "HX-Trigger-After-Swap", finalElt);
2507 }
2508
2509 var doSettle = function(){
2510 forEach(settleInfo.tasks, function (task) {
2511 task.call();
2512 });
2513 forEach(settleInfo.elts, function (elt) {
2514 if (elt.classList) {
2515 elt.classList.remove(htmx.config.settlingClass);
2516 }
2517 triggerEvent(elt, 'htmx:afterSettle', responseInfo);
2518 });
2519 // push URL and save new page
2520 if (shouldSaveHistory) {
2521 var pathToPush = pushedUrl || getPushUrl(elt) || getResponseURL(xhr) || responseInfo.pathInfo.finalPath || responseInfo.pathInfo.path;
2522 pushUrlIntoHistory(pathToPush);
2523 triggerEvent(getDocument().body, 'htmx:pushedIntoHistory', {path:pathToPush});
2524 }
2525 updateScrollState(settleInfo.elts, swapSpec);
2526
2527 if (hasHeader(xhr, /HX-Trigger-After-Settle:/i)) {
2528 var finalElt = elt;
2529 if (!bodyContains(elt)) {
2530 finalElt = getDocument().body;
2531 }
2532 handleTrigger(xhr, "HX-Trigger-After-Settle", finalElt);
2533 }
2534 }
2535
2536 if (swapSpec.settleDelay > 0) {
2537 setTimeout(doSettle, swapSpec.settleDelay)
2538 } else {
2539 doSettle();
2540 }
2541 } catch (e) {
2542 triggerErrorEvent(elt, 'htmx:swapError', responseInfo);
2543 throw e;
2544 }
2545 };
2546
2547 if (swapSpec.swapDelay > 0) {
2548 setTimeout(doSwap, swapSpec.swapDelay)
2549 } else {
2550 doSwap();
2551 }
2552 }
2553 } else {
2554 triggerErrorEvent(elt, 'htmx:responseError', mergeObjects({error: "Response Status Error Code " + xhr.status + " from " + responseInfo.pathInfo.path}, responseInfo));
2555 }
2556 }
2557
2558 //====================================================================
2559 // Extensions API
2560 //====================================================================
2561 var extensions = {};
2562 function extensionBase() {
2563 return {
2564 onEvent : function(name, evt) {return true;},
2565 transformResponse : function(text, xhr, elt) {return text;},
2566 isInlineSwap : function(swapStyle) {return false;},
2567 handleSwap : function(swapStyle, target, fragment, settleInfo) {return false;},
2568 encodeParameters : function(xhr, parameters, elt) {return null;}
2569 }
2570 }
2571
2572 function defineExtension(name, extension) {
2573 extensions[name] = mergeObjects(extensionBase(), extension);
2574 }
2575
2576 function removeExtension(name) {
2577 delete extensions[name];
2578 }
2579
2580 function getExtensions(elt, extensionsToReturn, extensionsToIgnore) {
2581 if (elt == undefined) {
2582 return extensionsToReturn;
2583 }
2584 if (extensionsToReturn == undefined) {
2585 extensionsToReturn = [];
2586 }
2587 if (extensionsToIgnore == undefined) {
2588 extensionsToIgnore = [];
2589 }
2590 var extensionsForElement = getAttributeValue(elt, "hx-ext");
2591 if (extensionsForElement) {
2592 forEach(extensionsForElement.split(","), function(extensionName){
2593 extensionName = extensionName.replace(/ /g, '');
2594 if (extensionName.slice(0, 7) == "ignore:") {
2595 extensionsToIgnore.push(extensionName.slice(7));
2596 return;
2597 }
2598 if (extensionsToIgnore.indexOf(extensionName) < 0) {
2599 var extension = extensions[extensionName];
2600 if (extension && extensionsToReturn.indexOf(extension) < 0) {
2601 extensionsToReturn.push(extension);
2602 }
2603 }
2604 });
2605 }
2606 return getExtensions(parentElt(elt), extensionsToReturn, extensionsToIgnore);
2607 }
2608
2609 //====================================================================
2610 // Initialization
2611 //====================================================================
2612
2613 function ready(fn) {
2614 if (getDocument().readyState !== 'loading') {
2615 fn();
2616 } else {
2617 getDocument().addEventListener('DOMContentLoaded', fn);
2618 }
2619 }
2620
2621 function insertIndicatorStyles() {
2622 if (htmx.config.includeIndicatorStyles !== false) {
2623 getDocument().head.insertAdjacentHTML("beforeend",
2624 "<style>\
2625 ." + htmx.config.indicatorClass + "{opacity:0;transition: opacity 200ms ease-in;}\
2626 ." + htmx.config.requestClass + " ." + htmx.config.indicatorClass + "{opacity:1}\
2627 ." + htmx.config.requestClass + "." + htmx.config.indicatorClass + "{opacity:1}\
2628 </style>");
2629 }
2630 }
2631
2632 function getMetaConfig() {
2633 var element = getDocument().querySelector('meta[name="htmx-config"]');
2634 if (element) {
2635 return parseJSON(element.content);
2636 } else {
2637 return null;
2638 }
2639 }
2640
2641 function mergeMetaConfig() {
2642 var metaConfig = getMetaConfig();
2643 if (metaConfig) {
2644 htmx.config = mergeObjects(htmx.config , metaConfig)
2645 }
2646 }
2647
2648 // initialize the document
2649 ready(function () {
2650 mergeMetaConfig();
2651 insertIndicatorStyles();
2652 var body = getDocument().body;
2653 processNode(body);
2654 window.onpopstate = function (event) {
2655 if (event.state && event.state.htmx) {
2656 restoreHistory();
2657 }
2658 };
2659 setTimeout(function () {
2660 triggerEvent(body, 'htmx:load', {}); // give ready handlers a chance to load up before firing this event
2661 }, 0);
2662 })
2663
2664 return htmx;
2665 }
2666)()
2667}));