UNPKG

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