UNPKG

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