UNPKG

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