UNPKG

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