UNPKG

htmx.org

Version:

high power tools for html

1,310 lines (1,208 loc) 162 kB
// UMD insanity // This code sets up support for (in order) AMD, ES6 modules, and globals. (function (root, factory) { //@ts-ignore if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. //@ts-ignore define([], factory); } else if (typeof module === 'object' && module.exports) { // Node. Does not work with strict CommonJS, but // only CommonJS-like environments that support module.exports, // like Node. module.exports = factory(); } else { // Browser globals root.htmx = root.htmx || factory(); } }(typeof self !== 'undefined' ? self : this, function () { return (function () { 'use strict'; // Public API //** @type {import("./htmx").HtmxApi} */ // TODO: list all methods in public API var htmx = { onLoad: onLoadHelper, process: processNode, on: addEventListenerImpl, off: removeEventListenerImpl, trigger : triggerEvent, ajax : ajaxHelper, find : find, findAll : findAll, closest : closest, values : function(elt, type){ var inputValues = getInputValues(elt, type || "post"); return inputValues.values; }, remove : removeElement, addClass : addClassToElement, removeClass : removeClassFromElement, toggleClass : toggleClassOnElement, takeClass : takeClassForElement, defineExtension : defineExtension, removeExtension : removeExtension, logAll : logAll, logNone : logNone, logger : null, config : { historyEnabled:true, historyCacheSize:10, refreshOnHistoryMiss:false, defaultSwapStyle:'innerHTML', defaultSwapDelay:0, defaultSettleDelay:20, includeIndicatorStyles:true, indicatorClass:'htmx-indicator', requestClass:'htmx-request', addedClass:'htmx-added', settlingClass:'htmx-settling', swappingClass:'htmx-swapping', allowEval:true, allowScriptTags:true, inlineScriptNonce:'', attributesToSettle:["class", "style", "width", "height"], withCredentials:false, timeout:0, wsReconnectDelay: 'full-jitter', wsBinaryType: 'blob', disableSelector: "[hx-disable], [data-hx-disable]", useTemplateFragments: false, scrollBehavior: 'smooth', defaultFocusScroll: false, getCacheBusterParam: false, globalViewTransitions: false, methodsThatUseUrlParams: ["get"], selfRequestsOnly: false, ignoreTitle: false, scrollIntoViewOnBoost: true, triggerSpecsCache: null, }, parseInterval:parseInterval, _:internalEval, createEventSource: function(url){ return new EventSource(url, {withCredentials:true}) }, createWebSocket: function(url){ var sock = new WebSocket(url, []); sock.binaryType = htmx.config.wsBinaryType; return sock; }, version: "1.9.11" }; /** @type {import("./htmx").HtmxInternalApi} */ var internalAPI = { addTriggerHandler: addTriggerHandler, bodyContains: bodyContains, canAccessLocalStorage: canAccessLocalStorage, findThisElement: findThisElement, filterValues: filterValues, hasAttribute: hasAttribute, getAttributeValue: getAttributeValue, getClosestAttributeValue: getClosestAttributeValue, getClosestMatch: getClosestMatch, getExpressionVars: getExpressionVars, getHeaders: getHeaders, getInputValues: getInputValues, getInternalData: getInternalData, getSwapSpecification: getSwapSpecification, getTriggerSpecs: getTriggerSpecs, getTarget: getTarget, makeFragment: makeFragment, mergeObjects: mergeObjects, makeSettleInfo: makeSettleInfo, oobSwap: oobSwap, querySelectorExt: querySelectorExt, selectAndSwap: selectAndSwap, settleImmediately: settleImmediately, shouldCancel: shouldCancel, triggerEvent: triggerEvent, triggerErrorEvent: triggerErrorEvent, withExtensions: withExtensions, } var VERBS = ['get', 'post', 'put', 'delete', 'patch']; var VERB_SELECTOR = VERBS.map(function(verb){ return "[hx-" + verb + "], [data-hx-" + verb + "]" }).join(", "); var HEAD_TAG_REGEX = makeTagRegEx('head'), TITLE_TAG_REGEX = makeTagRegEx('title'), SVG_TAGS_REGEX = makeTagRegEx('svg', true); //==================================================================== // Utilities //==================================================================== /** * @param {string} tag * @param {boolean} global * @returns {RegExp} */ function makeTagRegEx(tag, global = false) { return new RegExp(`<${tag}(\\s[^>]*>|>)([\\s\\S]*?)<\\/${tag}>`, global ? 'gim' : 'im'); } function parseInterval(str) { if (str == undefined) { return undefined; } let interval = NaN; if (str.slice(-2) == "ms") { interval = parseFloat(str.slice(0, -2)); } else if (str.slice(-1) == "s") { interval = parseFloat(str.slice(0, -1)) * 1000; } else if (str.slice(-1) == "m") { interval = parseFloat(str.slice(0, -1)) * 1000 * 60; } else { interval = parseFloat(str); } return isNaN(interval) ? undefined : interval; } /** * @param {HTMLElement} elt * @param {string} name * @returns {(string | null)} */ function getRawAttribute(elt, name) { return elt.getAttribute && elt.getAttribute(name); } // resolve with both hx and data-hx prefixes function hasAttribute(elt, qualifiedName) { return elt.hasAttribute && (elt.hasAttribute(qualifiedName) || elt.hasAttribute("data-" + qualifiedName)); } /** * * @param {HTMLElement} elt * @param {string} qualifiedName * @returns {(string | null)} */ function getAttributeValue(elt, qualifiedName) { return getRawAttribute(elt, qualifiedName) || getRawAttribute(elt, "data-" + qualifiedName); } /** * @param {HTMLElement} elt * @returns {HTMLElement | null} */ function parentElt(elt) { return elt.parentElement; } /** * @returns {Document} */ function getDocument() { return document; } /** * @param {HTMLElement} elt * @param {(e:HTMLElement) => boolean} condition * @returns {HTMLElement | null} */ function getClosestMatch(elt, condition) { while (elt && !condition(elt)) { elt = parentElt(elt); } return elt ? elt : null; } function getAttributeValueWithDisinheritance(initialElement, ancestor, attributeName){ var attributeValue = getAttributeValue(ancestor, attributeName); var disinherit = getAttributeValue(ancestor, "hx-disinherit"); if (initialElement !== ancestor && disinherit && (disinherit === "*" || disinherit.split(" ").indexOf(attributeName) >= 0)) { return "unset"; } else { return attributeValue } } /** * @param {HTMLElement} elt * @param {string} attributeName * @returns {string | null} */ function getClosestAttributeValue(elt, attributeName) { var closestAttr = null; getClosestMatch(elt, function (e) { return closestAttr = getAttributeValueWithDisinheritance(elt, e, attributeName); }); if (closestAttr !== "unset") { return closestAttr; } } /** * @param {HTMLElement} elt * @param {string} selector * @returns {boolean} */ function matches(elt, selector) { // @ts-ignore: non-standard properties for browser compatibility // noinspection JSUnresolvedVariable var matchesFunction = elt.matches || elt.matchesSelector || elt.msMatchesSelector || elt.mozMatchesSelector || elt.webkitMatchesSelector || elt.oMatchesSelector; return matchesFunction && matchesFunction.call(elt, selector); } /** * @param {string} str * @returns {string} */ function getStartTag(str) { var tagMatcher = /<([a-z][^\/\0>\x20\t\r\n\f]*)/i var match = tagMatcher.exec( str ); if (match) { return match[1].toLowerCase(); } else { return ""; } } /** * * @param {string} resp * @param {number} depth * @returns {Element} */ function parseHTML(resp, depth) { var parser = new DOMParser(); var responseDoc = parser.parseFromString(resp, "text/html"); /** @type {Element} */ var responseNode = responseDoc.body; while (depth > 0) { depth--; // @ts-ignore responseNode = responseNode.firstChild; } if (responseNode == null) { // @ts-ignore responseNode = getDocument().createDocumentFragment(); } return responseNode; } function aFullPageResponse(resp) { return /<body/.test(resp) } /** * * @param {string} response * @returns {Element} */ function makeFragment(response) { var partialResponse = !aFullPageResponse(response); var startTag = getStartTag(response); var content = response; if (startTag === 'head') { content = content.replace(HEAD_TAG_REGEX, ''); } if (htmx.config.useTemplateFragments && partialResponse) { var fragment = parseHTML("<body><template>" + content + "</template></body>", 0); // @ts-ignore type mismatch between DocumentFragment and Element. // TODO: Are these close enough for htmx to use interchangeably? var fragmentContent = fragment.querySelector('template').content; if (htmx.config.allowScriptTags) { // if there is a nonce set up, set it on the new script tags forEach(fragmentContent.querySelectorAll("script"), function (script) { if (htmx.config.inlineScriptNonce) { script.nonce = htmx.config.inlineScriptNonce; } // mark as executed due to template insertion semantics on all browsers except firefox fml script.htmxExecuted = navigator.userAgent.indexOf("Firefox") === -1; }) } else { forEach(fragmentContent.querySelectorAll("script"), function (script) { // remove all script tags if scripts are disabled removeElement(script); }) } return fragmentContent; } switch (startTag) { case "thead": case "tbody": case "tfoot": case "colgroup": case "caption": return parseHTML("<table>" + content + "</table>", 1); case "col": return parseHTML("<table><colgroup>" + content + "</colgroup></table>", 2); case "tr": return parseHTML("<table><tbody>" + content + "</tbody></table>", 2); case "td": case "th": return parseHTML("<table><tbody><tr>" + content + "</tr></tbody></table>", 3); case "script": case "style": return parseHTML("<div>" + content + "</div>", 1); default: return parseHTML(content, 0); } } /** * @param {Function} func */ function maybeCall(func){ if(func) { func(); } } /** * @param {any} o * @param {string} type * @returns */ function isType(o, type) { return Object.prototype.toString.call(o) === "[object " + type + "]"; } /** * @param {*} o * @returns {o is Function} */ function isFunction(o) { return isType(o, "Function"); } /** * @param {*} o * @returns {o is Object} */ function isRawObject(o) { return isType(o, "Object"); } /** * getInternalData retrieves "private" data stored by htmx within an element * @param {HTMLElement} elt * @returns {*} */ function getInternalData(elt) { var dataProp = 'htmx-internal-data'; var data = elt[dataProp]; if (!data) { data = elt[dataProp] = {}; } return data; } /** * toArray converts an ArrayLike object into a real array. * @param {ArrayLike} arr * @returns {any[]} */ function toArray(arr) { var returnArr = []; if (arr) { for (var i = 0; i < arr.length; i++) { returnArr.push(arr[i]); } } return returnArr } function forEach(arr, func) { if (arr) { for (var i = 0; i < arr.length; i++) { func(arr[i]); } } } function isScrolledIntoView(el) { var rect = el.getBoundingClientRect(); var elemTop = rect.top; var elemBottom = rect.bottom; return elemTop < window.innerHeight && elemBottom >= 0; } function bodyContains(elt) { // IE Fix if (elt.getRootNode && elt.getRootNode() instanceof window.ShadowRoot) { return getDocument().body.contains(elt.getRootNode().host); } else { return getDocument().body.contains(elt); } } function splitOnWhitespace(trigger) { return trigger.trim().split(/\s+/); } /** * mergeObjects takes all of the keys from * obj2 and duplicates them into obj1 * @param {Object} obj1 * @param {Object} obj2 * @returns {Object} */ function mergeObjects(obj1, obj2) { for (var key in obj2) { if (obj2.hasOwnProperty(key)) { obj1[key] = obj2[key]; } } return obj1; } function parseJSON(jString) { try { return JSON.parse(jString); } catch(error) { logError(error); return null; } } function canAccessLocalStorage() { var test = 'htmx:localStorageTest'; try { localStorage.setItem(test, test); localStorage.removeItem(test); return true; } catch(e) { return false; } } function normalizePath(path) { try { var url = new URL(path); if (url) { path = url.pathname + url.search; } // remove trailing slash, unless index page if (!(/^\/$/.test(path))) { path = path.replace(/\/+$/, ''); } return path; } catch (e) { // be kind to IE11, which doesn't support URL() return path; } } //========================================================================================== // public API //========================================================================================== function internalEval(str){ return maybeEval(getDocument().body, function () { return eval(str); }); } function onLoadHelper(callback) { var value = htmx.on("htmx:load", function(evt) { callback(evt.detail.elt); }); return value; } function logAll(){ htmx.logger = function(elt, event, data) { if(console) { console.log(event, elt, data); } } } function logNone() { htmx.logger = null } function find(eltOrSelector, selector) { if (selector) { return eltOrSelector.querySelector(selector); } else { return find(getDocument(), eltOrSelector); } } function findAll(eltOrSelector, selector) { if (selector) { return eltOrSelector.querySelectorAll(selector); } else { return findAll(getDocument(), eltOrSelector); } } function removeElement(elt, delay) { elt = resolveTarget(elt); if (delay) { setTimeout(function(){ removeElement(elt); elt = null; }, delay); } else { elt.parentElement.removeChild(elt); } } function addClassToElement(elt, clazz, delay) { elt = resolveTarget(elt); if (delay) { setTimeout(function(){ addClassToElement(elt, clazz); elt = null; }, delay); } else { elt.classList && elt.classList.add(clazz); } } function removeClassFromElement(elt, clazz, delay) { elt = resolveTarget(elt); if (delay) { setTimeout(function(){ removeClassFromElement(elt, clazz); elt = null; }, delay); } else { if (elt.classList) { elt.classList.remove(clazz); // if there are no classes left, remove the class attribute if (elt.classList.length === 0) { elt.removeAttribute("class"); } } } } function toggleClassOnElement(elt, clazz) { elt = resolveTarget(elt); elt.classList.toggle(clazz); } function takeClassForElement(elt, clazz) { elt = resolveTarget(elt); forEach(elt.parentElement.children, function(child){ removeClassFromElement(child, clazz); }) addClassToElement(elt, clazz); } function closest(elt, selector) { elt = resolveTarget(elt); if (elt.closest) { return elt.closest(selector); } else { // TODO remove when IE goes away do{ if (elt == null || matches(elt, selector)){ return elt; } } while (elt = elt && parentElt(elt)); return null; } } function startsWith(str, prefix) { return str.substring(0, prefix.length) === prefix } function endsWith(str, suffix) { return str.substring(str.length - suffix.length) === suffix } function normalizeSelector(selector) { var trimmedSelector = selector.trim(); if (startsWith(trimmedSelector, "<") && endsWith(trimmedSelector, "/>")) { return trimmedSelector.substring(1, trimmedSelector.length - 2); } else { return trimmedSelector; } } function querySelectorAllExt(elt, selector) { if (selector.indexOf("closest ") === 0) { return [closest(elt, normalizeSelector(selector.substr(8)))]; } else if (selector.indexOf("find ") === 0) { return [find(elt, normalizeSelector(selector.substr(5)))]; } else if (selector === "next") { return [elt.nextElementSibling] } else if (selector.indexOf("next ") === 0) { return [scanForwardQuery(elt, normalizeSelector(selector.substr(5)))]; } else if (selector === "previous") { return [elt.previousElementSibling] } else if (selector.indexOf("previous ") === 0) { return [scanBackwardsQuery(elt, normalizeSelector(selector.substr(9)))]; } else if (selector === 'document') { return [document]; } else if (selector === 'window') { return [window]; } else if (selector === 'body') { return [document.body]; } else { return getDocument().querySelectorAll(normalizeSelector(selector)); } } var scanForwardQuery = function(start, match) { var results = getDocument().querySelectorAll(match); for (var i = 0; i < results.length; i++) { var elt = results[i]; if (elt.compareDocumentPosition(start) === Node.DOCUMENT_POSITION_PRECEDING) { return elt; } } } var scanBackwardsQuery = function(start, match) { var results = getDocument().querySelectorAll(match); for (var i = results.length - 1; i >= 0; i--) { var elt = results[i]; if (elt.compareDocumentPosition(start) === Node.DOCUMENT_POSITION_FOLLOWING) { return elt; } } } function querySelectorExt(eltOrSelector, selector) { if (selector) { return querySelectorAllExt(eltOrSelector, selector)[0]; } else { return querySelectorAllExt(getDocument().body, eltOrSelector)[0]; } } function resolveTarget(arg2) { if (isType(arg2, 'String')) { return find(arg2); } else { return arg2; } } function processEventArgs(arg1, arg2, arg3) { if (isFunction(arg2)) { return { target: getDocument().body, event: arg1, listener: arg2 } } else { return { target: resolveTarget(arg1), event: arg2, listener: arg3 } } } function addEventListenerImpl(arg1, arg2, arg3) { ready(function(){ var eventArgs = processEventArgs(arg1, arg2, arg3); eventArgs.target.addEventListener(eventArgs.event, eventArgs.listener); }) var b = isFunction(arg2); return b ? arg2 : arg3; } function removeEventListenerImpl(arg1, arg2, arg3) { ready(function(){ var eventArgs = processEventArgs(arg1, arg2, arg3); eventArgs.target.removeEventListener(eventArgs.event, eventArgs.listener); }) return isFunction(arg2) ? arg2 : arg3; } //==================================================================== // Node processing //==================================================================== var DUMMY_ELT = getDocument().createElement("output"); // dummy element for bad selectors function findAttributeTargets(elt, attrName) { var attrTarget = getClosestAttributeValue(elt, attrName); if (attrTarget) { if (attrTarget === "this") { return [findThisElement(elt, attrName)]; } else { var result = querySelectorAllExt(elt, attrTarget); if (result.length === 0) { logError('The selector "' + attrTarget + '" on ' + attrName + " returned no matches!"); return [DUMMY_ELT] } else { return result; } } } } function findThisElement(elt, attribute){ return getClosestMatch(elt, function (elt) { return getAttributeValue(elt, attribute) != null; }) } function getTarget(elt) { var targetStr = getClosestAttributeValue(elt, "hx-target"); if (targetStr) { if (targetStr === "this") { return findThisElement(elt,'hx-target'); } else { return querySelectorExt(elt, targetStr) } } else { var data = getInternalData(elt); if (data.boosted) { return getDocument().body; } else { return elt; } } } function shouldSettleAttribute(name) { var attributesToSettle = htmx.config.attributesToSettle; for (var i = 0; i < attributesToSettle.length; i++) { if (name === attributesToSettle[i]) { return true; } } return false; } function cloneAttributes(mergeTo, mergeFrom) { forEach(mergeTo.attributes, function (attr) { if (!mergeFrom.hasAttribute(attr.name) && shouldSettleAttribute(attr.name)) { mergeTo.removeAttribute(attr.name) } }); forEach(mergeFrom.attributes, function (attr) { if (shouldSettleAttribute(attr.name)) { mergeTo.setAttribute(attr.name, attr.value); } }); } function isInlineSwap(swapStyle, target) { var extensions = getExtensions(target); for (var i = 0; i < extensions.length; i++) { var extension = extensions[i]; try { if (extension.isInlineSwap(swapStyle)) { return true; } } catch(e) { logError(e); } } return swapStyle === "outerHTML"; } /** * * @param {string} oobValue * @param {HTMLElement} oobElement * @param {*} settleInfo * @returns */ function oobSwap(oobValue, oobElement, settleInfo) { var selector = "#" + getRawAttribute(oobElement, "id"); var swapStyle = "outerHTML"; if (oobValue === "true") { // do nothing } else if (oobValue.indexOf(":") > 0) { swapStyle = oobValue.substr(0, oobValue.indexOf(":")); selector = oobValue.substr(oobValue.indexOf(":") + 1, oobValue.length); } else { swapStyle = oobValue; } var targets = getDocument().querySelectorAll(selector); if (targets) { forEach( targets, function (target) { var fragment; var oobElementClone = oobElement.cloneNode(true); fragment = getDocument().createDocumentFragment(); fragment.appendChild(oobElementClone); if (!isInlineSwap(swapStyle, target)) { fragment = oobElementClone; // if this is not an inline swap, we use the content of the node, not the node itself } var beforeSwapDetails = {shouldSwap: true, target: target, fragment:fragment }; if (!triggerEvent(target, 'htmx:oobBeforeSwap', beforeSwapDetails)) return; target = beforeSwapDetails.target; // allow re-targeting if (beforeSwapDetails['shouldSwap']){ swap(swapStyle, target, target, fragment, settleInfo); } forEach(settleInfo.elts, function (elt) { triggerEvent(elt, 'htmx:oobAfterSwap', beforeSwapDetails); }); } ); oobElement.parentNode.removeChild(oobElement); } else { oobElement.parentNode.removeChild(oobElement); triggerErrorEvent(getDocument().body, "htmx:oobErrorNoTarget", {content: oobElement}); } return oobValue; } function handleOutOfBandSwaps(elt, fragment, settleInfo) { var oobSelects = getClosestAttributeValue(elt, "hx-select-oob"); if (oobSelects) { var oobSelectValues = oobSelects.split(","); for (var i = 0; i < oobSelectValues.length; i++) { var oobSelectValue = oobSelectValues[i].split(":", 2); var id = oobSelectValue[0].trim(); if (id.indexOf("#") === 0) { id = id.substring(1); } var oobValue = oobSelectValue[1] || "true"; var oobElement = fragment.querySelector("#" + id); if (oobElement) { oobSwap(oobValue, oobElement, settleInfo); } } } forEach(findAll(fragment, '[hx-swap-oob], [data-hx-swap-oob]'), function (oobElement) { var oobValue = getAttributeValue(oobElement, "hx-swap-oob"); if (oobValue != null) { oobSwap(oobValue, oobElement, settleInfo); } }); } function handlePreservedElements(fragment) { forEach(findAll(fragment, '[hx-preserve], [data-hx-preserve]'), function (preservedElt) { var id = getAttributeValue(preservedElt, "id"); var oldElt = getDocument().getElementById(id); if (oldElt != null) { preservedElt.parentNode.replaceChild(oldElt, preservedElt); } }); } function handleAttributes(parentNode, fragment, settleInfo) { forEach(fragment.querySelectorAll("[id]"), function (newNode) { var id = getRawAttribute(newNode, "id") if (id && id.length > 0) { var normalizedId = id.replace("'", "\\'"); var normalizedTag = newNode.tagName.replace(':', '\\:'); var oldNode = parentNode.querySelector(normalizedTag + "[id='" + normalizedId + "']"); if (oldNode && oldNode !== parentNode) { var newAttributes = newNode.cloneNode(); cloneAttributes(newNode, oldNode); settleInfo.tasks.push(function () { cloneAttributes(newNode, newAttributes); }); } } }); } function makeAjaxLoadTask(child) { return function () { removeClassFromElement(child, htmx.config.addedClass); processNode(child); processScripts(child); processFocus(child) triggerEvent(child, 'htmx:load'); }; } function processFocus(child) { var autofocus = "[autofocus]"; var autoFocusedElt = matches(child, autofocus) ? child : child.querySelector(autofocus) if (autoFocusedElt != null) { autoFocusedElt.focus(); } } function insertNodesBefore(parentNode, insertBefore, fragment, settleInfo) { handleAttributes(parentNode, fragment, settleInfo); while(fragment.childNodes.length > 0){ var child = fragment.firstChild; addClassToElement(child, htmx.config.addedClass); parentNode.insertBefore(child, insertBefore); if (child.nodeType !== Node.TEXT_NODE && child.nodeType !== Node.COMMENT_NODE) { settleInfo.tasks.push(makeAjaxLoadTask(child)); } } } // based on https://gist.github.com/hyamamoto/fd435505d29ebfa3d9716fd2be8d42f0, // derived from Java's string hashcode implementation function stringHash(string, hash) { var char = 0; while (char < string.length){ hash = (hash << 5) - hash + string.charCodeAt(char++) | 0; // bitwise or ensures we have a 32-bit int } return hash; } function attributeHash(elt) { var hash = 0; // IE fix if (elt.attributes) { for (var i = 0; i < elt.attributes.length; i++) { var attribute = elt.attributes[i]; if(attribute.value){ // only include attributes w/ actual values (empty is same as non-existent) hash = stringHash(attribute.name, hash); hash = stringHash(attribute.value, hash); } } } return hash; } function deInitOnHandlers(elt) { var internalData = getInternalData(elt); if (internalData.onHandlers) { for (var i = 0; i < internalData.onHandlers.length; i++) { const handlerInfo = internalData.onHandlers[i]; elt.removeEventListener(handlerInfo.event, handlerInfo.listener); } delete internalData.onHandlers } } function deInitNode(element) { var internalData = getInternalData(element); if (internalData.timeout) { clearTimeout(internalData.timeout); } if (internalData.webSocket) { internalData.webSocket.close(); } if (internalData.sseEventSource) { internalData.sseEventSource.close(); } if (internalData.listenerInfos) { forEach(internalData.listenerInfos, function (info) { if (info.on) { info.on.removeEventListener(info.trigger, info.listener); } }); } deInitOnHandlers(element); forEach(Object.keys(internalData), function(key) { delete internalData[key] }); } function cleanUpElement(element) { triggerEvent(element, "htmx:beforeCleanupElement") deInitNode(element); if (element.children) { // IE forEach(element.children, function(child) { cleanUpElement(child) }); } } function swapOuterHTML(target, fragment, settleInfo) { if (target.tagName === "BODY") { return swapInnerHTML(target, fragment, settleInfo); } else { // @type {HTMLElement} var newElt var eltBeforeNewContent = target.previousSibling; insertNodesBefore(parentElt(target), target, fragment, settleInfo); if (eltBeforeNewContent == null) { newElt = parentElt(target).firstChild; } else { newElt = eltBeforeNewContent.nextSibling; } settleInfo.elts = settleInfo.elts.filter(function(e) { return e != target }); while(newElt && newElt !== target) { if (newElt.nodeType === Node.ELEMENT_NODE) { settleInfo.elts.push(newElt); } newElt = newElt.nextElementSibling; } cleanUpElement(target); parentElt(target).removeChild(target); } } function swapAfterBegin(target, fragment, settleInfo) { return insertNodesBefore(target, target.firstChild, fragment, settleInfo); } function swapBeforeBegin(target, fragment, settleInfo) { return insertNodesBefore(parentElt(target), target, fragment, settleInfo); } function swapBeforeEnd(target, fragment, settleInfo) { return insertNodesBefore(target, null, fragment, settleInfo); } function swapAfterEnd(target, fragment, settleInfo) { return insertNodesBefore(parentElt(target), target.nextSibling, fragment, settleInfo); } function swapDelete(target, fragment, settleInfo) { cleanUpElement(target); return parentElt(target).removeChild(target); } function swapInnerHTML(target, fragment, settleInfo) { var firstChild = target.firstChild; insertNodesBefore(target, firstChild, fragment, settleInfo); if (firstChild) { while (firstChild.nextSibling) { cleanUpElement(firstChild.nextSibling) target.removeChild(firstChild.nextSibling); } cleanUpElement(firstChild) target.removeChild(firstChild); } } function maybeSelectFromResponse(elt, fragment, selectOverride) { var selector = selectOverride || getClosestAttributeValue(elt, "hx-select"); if (selector) { var newFragment = getDocument().createDocumentFragment(); forEach(fragment.querySelectorAll(selector), function (node) { newFragment.appendChild(node); }); fragment = newFragment; } return fragment; } function swap(swapStyle, elt, target, fragment, settleInfo) { switch (swapStyle) { case "none": return; case "outerHTML": swapOuterHTML(target, fragment, settleInfo); return; case "afterbegin": swapAfterBegin(target, fragment, settleInfo); return; case "beforebegin": swapBeforeBegin(target, fragment, settleInfo); return; case "beforeend": swapBeforeEnd(target, fragment, settleInfo); return; case "afterend": swapAfterEnd(target, fragment, settleInfo); return; case "delete": swapDelete(target, fragment, settleInfo); return; default: var extensions = getExtensions(elt); for (var i = 0; i < extensions.length; i++) { var ext = extensions[i]; try { var newElements = ext.handleSwap(swapStyle, target, fragment, settleInfo); if (newElements) { if (typeof newElements.length !== 'undefined') { // if handleSwap returns an array (like) of elements, we handle them for (var j = 0; j < newElements.length; j++) { var child = newElements[j]; if (child.nodeType !== Node.TEXT_NODE && child.nodeType !== Node.COMMENT_NODE) { settleInfo.tasks.push(makeAjaxLoadTask(child)); } } } return; } } catch (e) { logError(e); } } if (swapStyle === "innerHTML") { swapInnerHTML(target, fragment, settleInfo); } else { swap(htmx.config.defaultSwapStyle, elt, target, fragment, settleInfo); } } } function findTitle(content) { if (content.indexOf('<title') > -1) { var contentWithSvgsRemoved = content.replace(SVG_TAGS_REGEX, ''); var result = contentWithSvgsRemoved.match(TITLE_TAG_REGEX); if (result) { return result[2]; } } } function selectAndSwap(swapStyle, target, elt, responseText, settleInfo, selectOverride) { settleInfo.title = findTitle(responseText); var fragment = makeFragment(responseText); if (fragment) { handleOutOfBandSwaps(elt, fragment, settleInfo); fragment = maybeSelectFromResponse(elt, fragment, selectOverride); handlePreservedElements(fragment); return swap(swapStyle, elt, target, fragment, settleInfo); } } function handleTrigger(xhr, header, elt) { var triggerBody = xhr.getResponseHeader(header); if (triggerBody.indexOf("{") === 0) { var triggers = parseJSON(triggerBody); for (var eventName in triggers) { if (triggers.hasOwnProperty(eventName)) { var detail = triggers[eventName]; if (!isRawObject(detail)) { detail = {"value": detail} } triggerEvent(elt, eventName, detail); } } } else { var eventNames = triggerBody.split(",") for (var i = 0; i < eventNames.length; i++) { triggerEvent(elt, eventNames[i].trim(), []); } } } var WHITESPACE = /\s/; var WHITESPACE_OR_COMMA = /[\s,]/; var SYMBOL_START = /[_$a-zA-Z]/; var SYMBOL_CONT = /[_$a-zA-Z0-9]/; var STRINGISH_START = ['"', "'", "/"]; var NOT_WHITESPACE = /[^\s]/; var COMBINED_SELECTOR_START = /[{(]/; var COMBINED_SELECTOR_END = /[})]/; function tokenizeString(str) { var tokens = []; var position = 0; while (position < str.length) { if(SYMBOL_START.exec(str.charAt(position))) { var startPosition = position; while (SYMBOL_CONT.exec(str.charAt(position + 1))) { position++; } tokens.push(str.substr(startPosition, position - startPosition + 1)); } else if (STRINGISH_START.indexOf(str.charAt(position)) !== -1) { var startChar = str.charAt(position); var startPosition = position; position++; while (position < str.length && str.charAt(position) !== startChar ) { if (str.charAt(position) === "\\") { position++; } position++; } tokens.push(str.substr(startPosition, position - startPosition + 1)); } else { var symbol = str.charAt(position); tokens.push(symbol); } position++; } return tokens; } function isPossibleRelativeReference(token, last, paramName) { return SYMBOL_START.exec(token.charAt(0)) && token !== "true" && token !== "false" && token !== "this" && token !== paramName && last !== "."; } function maybeGenerateConditional(elt, tokens, paramName) { if (tokens[0] === '[') { tokens.shift(); var bracketCount = 1; var conditionalSource = " return (function(" + paramName + "){ return ("; var last = null; while (tokens.length > 0) { var token = tokens[0]; if (token === "]") { bracketCount--; if (bracketCount === 0) { if (last === null) { conditionalSource = conditionalSource + "true"; } tokens.shift(); conditionalSource += ")})"; try { var conditionFunction = maybeEval(elt,function () { return Function(conditionalSource)(); }, function(){return true}) conditionFunction.source = conditionalSource; return conditionFunction; } catch (e) { triggerErrorEvent(getDocument().body, "htmx:syntax:error", {error:e, source:conditionalSource}) return null; } } } else if (token === "[") { bracketCount++; } if (isPossibleRelativeReference(token, last, paramName)) { conditionalSource += "((" + paramName + "." + token + ") ? (" + paramName + "." + token + ") : (window." + token + "))"; } else { conditionalSource = conditionalSource + token; } last = tokens.shift(); } } } function consumeUntil(tokens, match) { var result = ""; while (tokens.length > 0 && !match.test(tokens[0])) { result += tokens.shift(); } return result; } function consumeCSSSelector(tokens) { var result; if (tokens.length > 0 && COMBINED_SELECTOR_START.test(tokens[0])) { tokens.shift(); result = consumeUntil(tokens, COMBINED_SELECTOR_END).trim(); tokens.shift(); } else { result = consumeUntil(tokens, WHITESPACE_OR_COMMA); } return result; } var INPUT_SELECTOR = 'input, textarea, select'; /** * @param {HTMLElement} elt * @param {string} explicitTrigger * @param {cache} cache for trigger specs * @returns {import("./htmx").HtmxTriggerSpecification[]} */ function parseAndCacheTrigger(elt, explicitTrigger, cache) { var triggerSpecs = []; var tokens = tokenizeString(explicitTrigger); do { consumeUntil(tokens, NOT_WHITESPACE); var initialLength = tokens.length; var trigger = consumeUntil(tokens, /[,\[\s]/); if (trigger !== "") { if (trigger === "every") { var every = {trigger: 'every'}; consumeUntil(tokens, NOT_WHITESPACE); every.pollInterval = parseInterval(consumeUntil(tokens, /[,\[\s]/)); consumeUntil(tokens, NOT_WHITESPACE); var eventFilter = maybeGenerateConditional(elt, tokens, "event"); if (eventFilter) { every.eventFilter = eventFilter; } triggerSpecs.push(every); } else if (trigger.indexOf("sse:") === 0) { triggerSpecs.push({trigger: 'sse', sseEvent: trigger.substr(