mirror of
https://github.com/bigskysoftware/htmx.git
synced 2025-10-02 07:21:05 +00:00
Initial port
- rebuilding this fork because of a corrupted git repository. This branch is "working" but needs more testing to be certain. - major changes to the way that htmx handles extensions. this will need to be vetted more carefully before we proceed.
This commit is contained in:
parent
e9434be443
commit
260b82314a
173
src/ext/sse.js
Normal file
173
src/ext/sse.js
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
/*
|
||||||
|
Server Sent Events Extension
|
||||||
|
============================
|
||||||
|
This extension adds support for Server Sent Events to htmx. See /www/extensions/sse.md for usage instructions.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function(){
|
||||||
|
|
||||||
|
htmx.defineExtension("sse", {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* onEvent handles all events passed to this extension.
|
||||||
|
*
|
||||||
|
* @param {string} name
|
||||||
|
* @param {Event} evt
|
||||||
|
* @param {import("../htmx").HtmxExtensionApi} api
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
onEvent: function(name, evt, api) {
|
||||||
|
|
||||||
|
switch (name) {
|
||||||
|
|
||||||
|
// Try to remove remove an EventSource when elements are removed
|
||||||
|
case "htmx:beforeCleanupElement":
|
||||||
|
if (api.hasAttribute("hx-swap")) {
|
||||||
|
var source = api.getInternalData(evt.target, "sseEventSource")
|
||||||
|
if (source != null) {
|
||||||
|
source.close();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to create EventSources when elements are processed
|
||||||
|
case "htmx:afterProcessNode":
|
||||||
|
|
||||||
|
var parent = evt.target;
|
||||||
|
|
||||||
|
// get URL from element's attribute
|
||||||
|
var sseURL = api.getAttributeValue(evt.target, "sse-url")
|
||||||
|
|
||||||
|
if (sseURL == undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default function for creating new EventSource objects
|
||||||
|
if (htmx.createEventSource == undefined) {
|
||||||
|
htmx.createEventSource = createEventSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to the EventSource
|
||||||
|
var source = htmx.createEventSource(sseURL);
|
||||||
|
|
||||||
|
source.onerror = function (err) {
|
||||||
|
api.triggerErrorEvent(parent, "htmx:sseError", {error:err, source:source});
|
||||||
|
maybeCloseSSESource(parent);
|
||||||
|
};
|
||||||
|
|
||||||
|
api.getInternalData(parent).sseEventSource = source;
|
||||||
|
|
||||||
|
// Add message handlers for every `sse-swap` attribute
|
||||||
|
queryAttributeOnThisOrChildren(api, parent, "sse-swap").forEach(function(child) {
|
||||||
|
|
||||||
|
var sseEventName = api.getAttributeValue(child, "sse-swap")
|
||||||
|
|
||||||
|
var listener = function(event) {
|
||||||
|
|
||||||
|
// If the parent is missing then close SSE and remove listener
|
||||||
|
if (maybeCloseSSESource(api, parent)) {
|
||||||
|
source.removeEventListener(sseEventName, listener);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// swap the response into the DOM and trigger a notification
|
||||||
|
api.swap(child, event.data)
|
||||||
|
api.triggerEvent(parent, "htmx:sseMessage", event)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register the new listener
|
||||||
|
api.getInternalData(parent).sseEventListener = listener;
|
||||||
|
source.addEventListener(sseEventName, listener);
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add message handlers for every `hx-trigger="sse:*"` attribute
|
||||||
|
queryAttributeOnThisOrChildren(api, parent, "hx-trigger").forEach(function(child) {
|
||||||
|
|
||||||
|
var sseEventName = api.getAttributeValue(child, "hx-trigger")
|
||||||
|
|
||||||
|
// Only process hx-triggers for events with the "sse:" prefix
|
||||||
|
if (sseEventName.slice(0, 4) != "sse:") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var listener = function(event) {
|
||||||
|
|
||||||
|
// If parent is missing, then close SSE and remove listener
|
||||||
|
if (maybeCloseSSESource(api, parent)) {
|
||||||
|
source.removeEventListener(sseEventName, listener);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger events to be handled by the rest of htmx
|
||||||
|
api.triggerEvent(child, sseEventName, event)
|
||||||
|
api.triggerEvent(child, "htmx:sseMessage", event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the new listener
|
||||||
|
api.getInternalData(parent).sseEventListener = listener;
|
||||||
|
source.addEventListener(sseEventName, listener);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
// HELPER FUNCTIONS
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* createEventSource is the default method for creating new EventSource objects.
|
||||||
|
* it is hoisted into htmx.config.createEventSource to be overridden by the user, if needed.
|
||||||
|
*
|
||||||
|
* @param {string} url
|
||||||
|
* @returns EventSource
|
||||||
|
*/
|
||||||
|
function createEventSource(url){
|
||||||
|
return new EventSource(url, {withCredentials:true})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* maybeCloseSSESource confirms that the parent element still exists.
|
||||||
|
* If not, then any associated SSE source is closed and the function returns true.
|
||||||
|
*
|
||||||
|
* @param {import("../htmx").HtmxExtensionApi} api
|
||||||
|
* @param {HTMLElement} elt
|
||||||
|
* @returns boolean
|
||||||
|
*/
|
||||||
|
function maybeCloseSSESource(api, elt) {
|
||||||
|
if (!api.bodyContains(elt)) {
|
||||||
|
var source = api.getInternalData("sseEventSource")
|
||||||
|
if (source != undefined) {
|
||||||
|
source.close()
|
||||||
|
// source = null
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* queryAttributeOnThisOrChildren returns all nodes that contain the requested attributeName, INCLUDING THE PROVIDED ROOT ELEMENT.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} elt
|
||||||
|
* @param {string} attributeName
|
||||||
|
*/
|
||||||
|
function queryAttributeOnThisOrChildren(api, elt, attributeName) {
|
||||||
|
|
||||||
|
var result = []
|
||||||
|
|
||||||
|
// If the parent element also contains the requested attribute, then add it to the results too.
|
||||||
|
if (api.hasAttribute(elt, attributeName)) {
|
||||||
|
result.push(elt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search all child nodes that match the requested attribute
|
||||||
|
elt.querySelectorAll("[" + attributeName + "], [data-" + attributeName + "]").forEach(function(node) {
|
||||||
|
result.push(node)
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
})();
|
18
src/htmx.d.ts
vendored
18
src/htmx.d.ts
vendored
@ -24,3 +24,21 @@ export interface HtmxConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export declare var htmx: HtmxApi
|
export declare var htmx: HtmxApi
|
||||||
|
|
||||||
|
export interface HtmxExtension {
|
||||||
|
onEvent: (name: string, event: Event, api: HtmxExtensionApi) => boolean;
|
||||||
|
transformResponse: (text: string, xhr: XMLHttpRequest, elt: HTMLElement, api: HtmxExtensionApi) => string;
|
||||||
|
isInlineSwap: (swapStyle: string, api: HtmxExtensionApi) => boolean;
|
||||||
|
handleSwap: (swapStyle: string, target: HTMLElement, fragment: string, settleInfo: Object, api: HtmxExtensionApi) => boolean;
|
||||||
|
encodeParameters: (xhr: XMLHttpRequest, parameters: Object, elt: HTMLElement, api: HtmxExtensionApi) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HtmxExtensionApi {
|
||||||
|
bodyContains: (element: HTMLElement) => boolean;
|
||||||
|
hasAttribute: (element: HTMLElement, qualifiedName: string) => boolean;
|
||||||
|
getAttributeValue: (element: HTMLElement, qualifiedName: string) => string | null;
|
||||||
|
getInternalData: (element: HTMLElement) => Object;
|
||||||
|
triggerEvent: (element: HTMLElement, eventName: string, detail: any) => void;
|
||||||
|
triggerErrorEvent: (element: HTMLElement, eventName: string, detail: any) => void;
|
||||||
|
swap: (element: HTMLElement, content: string) => void;
|
||||||
|
}
|
167
src/htmx.js
167
src/htmx.js
@ -61,9 +61,6 @@ return (function () {
|
|||||||
},
|
},
|
||||||
parseInterval:parseInterval,
|
parseInterval:parseInterval,
|
||||||
_:internalEval,
|
_:internalEval,
|
||||||
createEventSource: function(url){
|
|
||||||
return new EventSource(url, {withCredentials:true})
|
|
||||||
},
|
|
||||||
createWebSocket: function(url){
|
createWebSocket: function(url){
|
||||||
return new WebSocket(url, []);
|
return new WebSocket(url, []);
|
||||||
},
|
},
|
||||||
@ -594,12 +591,12 @@ return (function () {
|
|||||||
|
|
||||||
function cleanUpElement(element) {
|
function cleanUpElement(element) {
|
||||||
var internalData = getInternalData(element);
|
var internalData = getInternalData(element);
|
||||||
|
|
||||||
|
triggerEvent(element, "htmx:beforeCleanupElement")
|
||||||
|
|
||||||
if (internalData.webSocket) {
|
if (internalData.webSocket) {
|
||||||
internalData.webSocket.close();
|
internalData.webSocket.close();
|
||||||
}
|
}
|
||||||
if (internalData.sseEventSource) {
|
|
||||||
internalData.sseEventSource.close();
|
|
||||||
}
|
|
||||||
if (internalData.listenerInfos) {
|
if (internalData.listenerInfos) {
|
||||||
forEach(internalData.listenerInfos, function(info) {
|
forEach(internalData.listenerInfos, function(info) {
|
||||||
if (element !== info.on) {
|
if (element !== info.on) {
|
||||||
@ -1186,8 +1183,8 @@ return (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var response = event.data;
|
var response = event.data;
|
||||||
withExtensions(elt, function(extension){
|
withExtensions(elt, function(extension, api){
|
||||||
response = extension.transformResponse(response, null, elt);
|
response = extension.transformResponse(response, null, elt, api);
|
||||||
});
|
});
|
||||||
|
|
||||||
var settleInfo = makeSettleInfo(elt);
|
var settleInfo = makeSettleInfo(elt);
|
||||||
@ -1252,98 +1249,6 @@ return (function () {
|
|||||||
logError('htmx.config.wsReconnectDelay must either be a function or the string "full-jitter"');
|
logError('htmx.config.wsReconnectDelay must either be a function or the string "full-jitter"');
|
||||||
}
|
}
|
||||||
|
|
||||||
//====================================================================
|
|
||||||
// Server Sent Events
|
|
||||||
//====================================================================
|
|
||||||
|
|
||||||
function processSSEInfo(elt, nodeData, info) {
|
|
||||||
var values = splitOnWhitespace(info);
|
|
||||||
for (var i = 0; i < values.length; i++) {
|
|
||||||
var value = values[i].split(/:(.+)/);
|
|
||||||
if (value[0] === "connect") {
|
|
||||||
processSSESource(elt, value[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((value[0] === "swap")) {
|
|
||||||
processSSESwap(elt, value[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function processSSESource(elt, sseSrc) {
|
|
||||||
var source = htmx.createEventSource(sseSrc);
|
|
||||||
source.onerror = function (e) {
|
|
||||||
triggerErrorEvent(elt, "htmx:sseError", {error:e, source:source});
|
|
||||||
maybeCloseSSESource(elt);
|
|
||||||
};
|
|
||||||
getInternalData(elt).sseEventSource = source;
|
|
||||||
}
|
|
||||||
|
|
||||||
function processSSESwap(elt, sseEventName) {
|
|
||||||
var sseSourceElt = getClosestMatch(elt, hasEventSource);
|
|
||||||
if (sseSourceElt) {
|
|
||||||
var sseEventSource = getInternalData(sseSourceElt).sseEventSource;
|
|
||||||
var sseListener = function (event) {
|
|
||||||
if (maybeCloseSSESource(sseSourceElt)) {
|
|
||||||
sseEventSource.removeEventListener(sseEventName, sseListener);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////
|
|
||||||
// TODO: merge this code with AJAX and WebSockets code in the future.
|
|
||||||
|
|
||||||
var response = event.data;
|
|
||||||
withExtensions(elt, function(extension){
|
|
||||||
response = extension.transformResponse(response, null, elt);
|
|
||||||
});
|
|
||||||
|
|
||||||
var swapSpec = getSwapSpecification(elt)
|
|
||||||
var target = getTarget(elt)
|
|
||||||
var settleInfo = makeSettleInfo(elt);
|
|
||||||
|
|
||||||
selectAndSwap(swapSpec.swapStyle, elt, target, response, settleInfo)
|
|
||||||
settleImmediately(settleInfo.tasks)
|
|
||||||
triggerEvent(elt, "htmx:sseMessage", event)
|
|
||||||
};
|
|
||||||
|
|
||||||
getInternalData(elt).sseListener = sseListener;
|
|
||||||
sseEventSource.addEventListener(sseEventName, sseListener);
|
|
||||||
} else {
|
|
||||||
triggerErrorEvent(elt, "htmx:noSSESourceError");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function processSSETrigger(elt, verb, path, sseEventName) {
|
|
||||||
var sseSourceElt = getClosestMatch(elt, hasEventSource);
|
|
||||||
if (sseSourceElt) {
|
|
||||||
var sseEventSource = getInternalData(sseSourceElt).sseEventSource;
|
|
||||||
var sseListener = function () {
|
|
||||||
if (!maybeCloseSSESource(sseSourceElt)) {
|
|
||||||
if (bodyContains(elt)) {
|
|
||||||
issueAjaxRequest(verb, path, elt);
|
|
||||||
} else {
|
|
||||||
sseEventSource.removeEventListener(sseEventName, sseListener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
getInternalData(elt).sseListener = sseListener;
|
|
||||||
sseEventSource.addEventListener(sseEventName, sseListener);
|
|
||||||
} else {
|
|
||||||
triggerErrorEvent(elt, "htmx:noSSESourceError");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function maybeCloseSSESource(elt) {
|
|
||||||
if (!bodyContains(elt)) {
|
|
||||||
getInternalData(elt).sseEventSource.close();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasEventSource(node) {
|
|
||||||
return getInternalData(node).sseEventSource != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
//====================================================================
|
//====================================================================
|
||||||
|
|
||||||
function loadImmediately(elt, verb, path, nodeData, delay) {
|
function loadImmediately(elt, verb, path, nodeData, delay) {
|
||||||
@ -1369,9 +1274,7 @@ return (function () {
|
|||||||
nodeData.path = path;
|
nodeData.path = path;
|
||||||
nodeData.verb = verb;
|
nodeData.verb = verb;
|
||||||
triggerSpecs.forEach(function(triggerSpec) {
|
triggerSpecs.forEach(function(triggerSpec) {
|
||||||
if (triggerSpec.sseEvent) {
|
if (triggerSpec.trigger === "revealed") {
|
||||||
processSSETrigger(elt, verb, path, triggerSpec.sseEvent);
|
|
||||||
} else if (triggerSpec.trigger === "revealed") {
|
|
||||||
initScrollHandler();
|
initScrollHandler();
|
||||||
maybeReveal(elt);
|
maybeReveal(elt);
|
||||||
} else if (triggerSpec.trigger === "intersect") {
|
} else if (triggerSpec.trigger === "intersect") {
|
||||||
@ -1443,8 +1346,9 @@ return (function () {
|
|||||||
function findElementsToProcess(elt) {
|
function findElementsToProcess(elt) {
|
||||||
if (elt.querySelectorAll) {
|
if (elt.querySelectorAll) {
|
||||||
var boostedElts = isBoosted() ? ", a, form" : "";
|
var boostedElts = isBoosted() ? ", a, form" : "";
|
||||||
var results = elt.querySelectorAll(VERB_SELECTOR + boostedElts + ", [hx-sse], [data-hx-sse], [hx-ws]," +
|
// var results = elt.querySelectorAll(VERB_SELECTOR + boostedElts + ", [data-hx-ext], [hx-ws], [data-hx-ws]");
|
||||||
" [data-hx-ws]");
|
// TODO: Probably **remove** [hx-ext] from this list before done. I'm not sure it actually belongs here long-term.
|
||||||
|
var results = elt.querySelectorAll(VERB_SELECTOR + boostedElts + ", [hx-ext], [data-hx-ext], [hx-ws], [data-hx-ws]");
|
||||||
return results;
|
return results;
|
||||||
} else {
|
} else {
|
||||||
return [];
|
return [];
|
||||||
@ -1495,11 +1399,6 @@ return (function () {
|
|||||||
initButtonTracking(elt);
|
initButtonTracking(elt);
|
||||||
}
|
}
|
||||||
|
|
||||||
var sseInfo = getAttributeValue(elt, 'hx-sse');
|
|
||||||
if (sseInfo) {
|
|
||||||
processSSEInfo(elt, nodeData, sseInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
var wsInfo = getAttributeValue(elt, 'hx-ws');
|
var wsInfo = getAttributeValue(elt, 'hx-ws');
|
||||||
if (wsInfo) {
|
if (wsInfo) {
|
||||||
processWebSocketInfo(elt, nodeData, wsInfo);
|
processWebSocketInfo(elt, nodeData, wsInfo);
|
||||||
@ -1541,10 +1440,43 @@ return (function () {
|
|||||||
return eventName === "htmx:afterProcessNode"
|
return eventName === "htmx:afterProcessNode"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `withExtensions` locates all active extensions for a provided element, then
|
||||||
|
* executes the provided function using each of the active extensions. It should
|
||||||
|
* be called internally at every extendable execution point in htmx.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} elt
|
||||||
|
* @param {(elt:any, api?: Object) => void} toDo
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
function withExtensions(elt, toDo) {
|
function withExtensions(elt, toDo) {
|
||||||
|
|
||||||
|
// create API object to pass into each toDo function
|
||||||
|
/** @type {import("./htmx").HtmxExtensionApi} */
|
||||||
|
var api = {
|
||||||
|
hasAttribute: hasAttribute,
|
||||||
|
getAttributeValue: getAttributeValue,
|
||||||
|
getInternalData: getInternalData,
|
||||||
|
bodyContains: bodyContains,
|
||||||
|
triggerEvent: triggerEvent,
|
||||||
|
triggerErrorEvent: triggerErrorEvent,
|
||||||
|
swap: function(elt, content) {
|
||||||
|
withExtensions(elt, function(extension){
|
||||||
|
content = extension.transformResponse(content, null, elt);
|
||||||
|
});
|
||||||
|
|
||||||
|
var swapSpec = getSwapSpecification(elt)
|
||||||
|
var target = getTarget(elt)
|
||||||
|
var settleInfo = makeSettleInfo(elt);
|
||||||
|
|
||||||
|
selectAndSwap(swapSpec.swapStyle, elt, target, content, settleInfo)
|
||||||
|
settleImmediately(settleInfo.tasks)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
forEach(getExtensions(elt), function(extension){
|
forEach(getExtensions(elt), function(extension){
|
||||||
try {
|
try {
|
||||||
toDo(extension);
|
toDo(extension, api);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logError(e);
|
logError(e);
|
||||||
}
|
}
|
||||||
@ -1579,8 +1511,8 @@ return (function () {
|
|||||||
var kebabedEvent = makeEvent(kebabName, event.detail);
|
var kebabedEvent = makeEvent(kebabName, event.detail);
|
||||||
eventResult = eventResult && elt.dispatchEvent(kebabedEvent)
|
eventResult = eventResult && elt.dispatchEvent(kebabedEvent)
|
||||||
}
|
}
|
||||||
withExtensions(elt, function (extension) {
|
withExtensions(elt, function (extension, api) {
|
||||||
eventResult = eventResult && (extension.onEvent(eventName, event) !== false)
|
eventResult = eventResult && (extension.onEvent(eventName, event, api) !== false)
|
||||||
});
|
});
|
||||||
return eventResult;
|
return eventResult;
|
||||||
}
|
}
|
||||||
@ -2010,9 +1942,9 @@ return (function () {
|
|||||||
|
|
||||||
function encodeParamsForBody(xhr, elt, filteredParameters) {
|
function encodeParamsForBody(xhr, elt, filteredParameters) {
|
||||||
var encodedParameters = null;
|
var encodedParameters = null;
|
||||||
withExtensions(elt, function (extension) {
|
withExtensions(elt, function (extension, api) {
|
||||||
if (encodedParameters == null) {
|
if (encodedParameters == null) {
|
||||||
encodedParameters = extension.encodeParameters(xhr, filteredParameters, elt);
|
encodedParameters = extension.encodeParameters(xhr, filteredParameters, elt, api);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (encodedParameters != null) {
|
if (encodedParameters != null) {
|
||||||
@ -2492,8 +2424,8 @@ return (function () {
|
|||||||
cancelPolling(elt);
|
cancelPolling(elt);
|
||||||
}
|
}
|
||||||
|
|
||||||
withExtensions(elt, function (extension) {
|
withExtensions(elt, function (extension, api) {
|
||||||
serverResponse = extension.transformResponse(serverResponse, xhr, elt);
|
serverResponse = extension.transformResponse(serverResponse, xhr, elt, api);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Save current page
|
// Save current page
|
||||||
@ -2628,6 +2560,7 @@ return (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getExtensions(elt, extensionsToReturn, extensionsToIgnore) {
|
function getExtensions(elt, extensionsToReturn, extensionsToIgnore) {
|
||||||
|
|
||||||
if (elt == undefined) {
|
if (elt == undefined) {
|
||||||
return extensionsToReturn;
|
return extensionsToReturn;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<script src="../../src/htmx.js"></script>
|
<script src="../../src/htmx.js"></script>
|
||||||
|
<script src="../../src/ext/server-sent-events.js"></script>
|
||||||
|
<script src="../../src/ext/debug.js"></script>
|
||||||
<script>
|
<script>
|
||||||
// "withCredentials:false" is necessary to circumvent CORS restrictions
|
// "withCredentials:false" is necessary to circumvent CORS restrictions
|
||||||
htmx.createEventSource = function(url){
|
htmx.createEventSource = function(url){
|
||||||
@ -30,14 +32,13 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<div id="page" hx-ext="debug">
|
||||||
<div id="page">
|
|
||||||
<h3>Multiple Listeners. message only</h3>
|
<h3>Multiple Listeners. message only</h3>
|
||||||
<div class="container" hx-sse="connect:http://sseplaceholder.openfollow.org/posts.html swap:message">Waiting for Posts...</div>
|
<div class="container" hx-ext="sse" sse-url="http://localhost/posts.html" sse-swap="message">Waiting for Posts...</div>
|
||||||
<div class="container" hx-sse="connect:http://sseplaceholder.openfollow.org/comments.html swap:message">Waiting for Comments...</div>
|
<div class="container" hx-ext="sse" sse-url="http://localhost/comments.html" sse-swap="message">Waiting for Comments...</div>
|
||||||
<div class="container" hx-sse="connect:http://sseplaceholder.openfollow.org/albums.html swap:message">Waiting for Albums...</div>
|
<div class="container" hx-ext="sse" sse-url="http://localhost/albums.html" sse-swap="message">Waiting for Albums...</div>
|
||||||
<div class="container" hx-sse="connect:http://sseplaceholder.openfollow.org/todos.html swap:message">Waiting for ToDos...</div>
|
<div class="container" hx-ext="sse" sse-url="http://localhost/todos.html" sse-swap="message">Waiting for ToDos...</div>
|
||||||
<div class="container" hx-sse="connect:http://sseplaceholder.openfollow.org/users.html swap:message">Waiting for Users...</div>
|
<div class="container" hx-ext="sse" sse-url="http://localhost/users.html" sse-swap="message">Waiting for Users...</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
Loading…
x
Reference in New Issue
Block a user