UNPKG

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