UNPKG

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