UNPKG

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