mirror of
				https://github.com/bigskysoftware/htmx.git
				synced 2025-11-04 07:24:06 +00:00 
			
		
		
		
	Restore hx-ws and hx-sse tags (#811)
absolute 👑 
* Restore WS and SSE code
First pass at restoring removed ws and sse code.  More to come.
* More progress on WS and SSE restore
* Update htmx.js
crucial whitespace
* Update documentation
* Combine SSE and WS servers into single "realtime" demo
* Realtime Test Server Updates
- separated tests for old- and new- style calling
- updated intro content and stylesheet
- removed extensions from manual test suite
* Remove SSE/WS from manual tests
			
			
This commit is contained in:
		
							parent
							
								
									eb1367fb11
								
							
						
					
					
						commit
						546e346e98
					
				
							
								
								
									
										245
									
								
								src/htmx.js
									
									
									
									
									
								
							
							
						
						
									
										245
									
								
								src/htmx.js
									
									
									
									
									
								
							@ -57,12 +57,19 @@ return (function () {
 | 
				
			|||||||
                attributesToSettle:["class", "style", "width", "height"],
 | 
					                attributesToSettle:["class", "style", "width", "height"],
 | 
				
			||||||
                withCredentials:false,
 | 
					                withCredentials:false,
 | 
				
			||||||
                timeout:0,
 | 
					                timeout:0,
 | 
				
			||||||
 | 
					                wsReconnectDelay: 'full-jitter',
 | 
				
			||||||
                disableSelector: "[hx-disable], [data-hx-disable]",
 | 
					                disableSelector: "[hx-disable], [data-hx-disable]",
 | 
				
			||||||
                useTemplateFragments: false,
 | 
					                useTemplateFragments: false,
 | 
				
			||||||
                scrollBehavior: 'smooth',
 | 
					                scrollBehavior: 'smooth',
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            parseInterval:parseInterval,
 | 
					            parseInterval:parseInterval,
 | 
				
			||||||
            _:internalEval,
 | 
					            _:internalEval,
 | 
				
			||||||
 | 
					            createEventSource: function(url){
 | 
				
			||||||
 | 
					                return new EventSource(url, {withCredentials:true})
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            createWebSocket: function(url){
 | 
				
			||||||
 | 
					                return new WebSocket(url, []);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
            version: "1.7.0"
 | 
					            version: "1.7.0"
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -562,6 +569,7 @@ return (function () {
 | 
				
			|||||||
        //====================================================================
 | 
					        //====================================================================
 | 
				
			||||||
        // Node processing
 | 
					        // Node processing
 | 
				
			||||||
        //====================================================================
 | 
					        //====================================================================
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        var DUMMY_ELT = getDocument().createElement("output"); // dummy element for bad selectors
 | 
					        var DUMMY_ELT = getDocument().createElement("output"); // dummy element for bad selectors
 | 
				
			||||||
        function findAttributeTargets(elt, attrName) {
 | 
					        function findAttributeTargets(elt, attrName) {
 | 
				
			||||||
            var attrTarget = getClosestAttributeValue(elt, attrName);
 | 
					            var attrTarget = getClosestAttributeValue(elt, attrName);
 | 
				
			||||||
@ -760,6 +768,12 @@ return (function () {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        function cleanUpElement(element) {
 | 
					        function cleanUpElement(element) {
 | 
				
			||||||
            var internalData = getInternalData(element);
 | 
					            var internalData = getInternalData(element);
 | 
				
			||||||
 | 
					            if (internalData.webSocket) {
 | 
				
			||||||
 | 
					                internalData.webSocket.close();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (internalData.sseEventSource) {
 | 
				
			||||||
 | 
					                internalData.sseEventSource.close();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            triggerEvent(element, "htmx:beforeCleanupElement")
 | 
					            triggerEvent(element, "htmx:beforeCleanupElement")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1066,6 +1080,8 @@ return (function () {
 | 
				
			|||||||
                                every.eventFilter = eventFilter;
 | 
					                                every.eventFilter = eventFilter;
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                            triggerSpecs.push(every);
 | 
					                            triggerSpecs.push(every);
 | 
				
			||||||
 | 
					                        } else if (trigger.indexOf("sse:") === 0) {
 | 
				
			||||||
 | 
					                            triggerSpecs.push({trigger: 'sse', sseEvent: trigger.substr(4)});
 | 
				
			||||||
                        } else {
 | 
					                        } else {
 | 
				
			||||||
                            var triggerSpec = {trigger: trigger};
 | 
					                            var triggerSpec = {trigger: trigger};
 | 
				
			||||||
                            var eventFilter = maybeGenerateConditional(elt, tokens, "event");
 | 
					                            var eventFilter = maybeGenerateConditional(elt, tokens, "event");
 | 
				
			||||||
@ -1339,6 +1355,219 @@ return (function () {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //====================================================================
 | 
				
			||||||
 | 
					        // Web Sockets
 | 
				
			||||||
 | 
					        //====================================================================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function processWebSocketInfo(elt, nodeData, info) {
 | 
				
			||||||
 | 
					            var values = splitOnWhitespace(info);
 | 
				
			||||||
 | 
					            for (var i = 0; i < values.length; i++) {
 | 
				
			||||||
 | 
					                var value = values[i].split(/:(.+)/);
 | 
				
			||||||
 | 
					                if (value[0] === "connect") {
 | 
				
			||||||
 | 
					                    ensureWebSocket(elt, value[1], 0);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (value[0] === "send") {
 | 
				
			||||||
 | 
					                    processWebSocketSend(elt);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function ensureWebSocket(elt, wssSource, retryCount) {
 | 
				
			||||||
 | 
					            if (!bodyContains(elt)) {
 | 
				
			||||||
 | 
					                return;  // stop ensuring websocket connection when socket bearing element ceases to exist
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (wssSource.indexOf("/") == 0) {  // complete absolute paths only
 | 
				
			||||||
 | 
					                var base_part = location.hostname + (location.port ? ':'+location.port: '');
 | 
				
			||||||
 | 
					                if (location.protocol == 'https:') {
 | 
				
			||||||
 | 
					                    wssSource = "wss://" + base_part + wssSource;
 | 
				
			||||||
 | 
					                } else if (location.protocol == 'http:') {
 | 
				
			||||||
 | 
					                    wssSource = "ws://" + base_part + wssSource;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            var socket = htmx.createWebSocket(wssSource);
 | 
				
			||||||
 | 
					            socket.onerror = function (e) {
 | 
				
			||||||
 | 
					                triggerErrorEvent(elt, "htmx:wsError", {error:e, socket:socket});
 | 
				
			||||||
 | 
					                maybeCloseWebSocketSource(elt);
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            socket.onclose = function (e) {
 | 
				
			||||||
 | 
					                if ([1006, 1012, 1013].indexOf(e.code) >= 0) {  // Abnormal Closure/Service Restart/Try Again Later
 | 
				
			||||||
 | 
					                    var delay = getWebSocketReconnectDelay(retryCount);
 | 
				
			||||||
 | 
					                    setTimeout(function() {
 | 
				
			||||||
 | 
					                        ensureWebSocket(elt, wssSource, retryCount+1);  // creates a websocket with a new timeout
 | 
				
			||||||
 | 
					                    }, delay);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            socket.onopen = function (e) {
 | 
				
			||||||
 | 
					                retryCount = 0;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            getInternalData(elt).webSocket = socket;
 | 
				
			||||||
 | 
					            socket.addEventListener('message', function (event) {
 | 
				
			||||||
 | 
					                if (maybeCloseWebSocketSource(elt)) {
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                var response = event.data;
 | 
				
			||||||
 | 
					                withExtensions(elt, function(extension){
 | 
				
			||||||
 | 
					                    response = extension.transformResponse(response, null, elt);
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                var settleInfo = makeSettleInfo(elt);
 | 
				
			||||||
 | 
					                var fragment = makeFragment(response);
 | 
				
			||||||
 | 
					                var children = toArray(fragment.children);
 | 
				
			||||||
 | 
					                for (var i = 0; i < children.length; i++) {
 | 
				
			||||||
 | 
					                    var child = children[i];
 | 
				
			||||||
 | 
					                    oobSwap(getAttributeValue(child, "hx-swap-oob") || "true", child, settleInfo);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                settleImmediately(settleInfo.tasks);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function maybeCloseWebSocketSource(elt) {
 | 
				
			||||||
 | 
					            if (!bodyContains(elt)) {
 | 
				
			||||||
 | 
					                getInternalData(elt).webSocket.close();
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function processWebSocketSend(elt) {
 | 
				
			||||||
 | 
					            var webSocketSourceElt = getClosestMatch(elt, function (parent) {
 | 
				
			||||||
 | 
					                return getInternalData(parent).webSocket != null;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            if (webSocketSourceElt) {
 | 
				
			||||||
 | 
					                elt.addEventListener(getTriggerSpecs(elt)[0].trigger, function (evt) {
 | 
				
			||||||
 | 
					                    var webSocket = getInternalData(webSocketSourceElt).webSocket;
 | 
				
			||||||
 | 
					                    var headers = getHeaders(elt, webSocketSourceElt);
 | 
				
			||||||
 | 
					                    var results = getInputValues(elt, 'post');
 | 
				
			||||||
 | 
					                    var errors = results.errors;
 | 
				
			||||||
 | 
					                    var rawParameters = results.values;
 | 
				
			||||||
 | 
					                    var expressionVars = getExpressionVars(elt);
 | 
				
			||||||
 | 
					                    var allParameters = mergeObjects(rawParameters, expressionVars);
 | 
				
			||||||
 | 
					                    var filteredParameters = filterValues(allParameters, elt);
 | 
				
			||||||
 | 
					                    filteredParameters['HEADERS'] = headers;
 | 
				
			||||||
 | 
					                    if (errors && errors.length > 0) {
 | 
				
			||||||
 | 
					                        triggerEvent(elt, 'htmx:validation:halted', errors);
 | 
				
			||||||
 | 
					                        return;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    webSocket.send(JSON.stringify(filteredParameters));
 | 
				
			||||||
 | 
					                    if(shouldCancel(evt, elt)){
 | 
				
			||||||
 | 
					                        evt.preventDefault();
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                triggerErrorEvent(elt, "htmx:noWebSocketSourceError");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function getWebSocketReconnectDelay(retryCount) {
 | 
				
			||||||
 | 
					            var delay = htmx.config.wsReconnectDelay;
 | 
				
			||||||
 | 
					            if (typeof delay === 'function') {
 | 
				
			||||||
 | 
					                // @ts-ignore
 | 
				
			||||||
 | 
					                return delay(retryCount);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (delay === 'full-jitter') {
 | 
				
			||||||
 | 
					                var exp = Math.min(retryCount, 6);
 | 
				
			||||||
 | 
					                var maxDelay = 1000 * Math.pow(2, exp);
 | 
				
			||||||
 | 
					                return maxDelay * Math.random();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            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) {
 | 
				
			||||||
@ -1364,7 +1593,9 @@ return (function () {
 | 
				
			|||||||
                    nodeData.path = path;
 | 
					                    nodeData.path = path;
 | 
				
			||||||
                    nodeData.verb = verb;
 | 
					                    nodeData.verb = verb;
 | 
				
			||||||
                    triggerSpecs.forEach(function(triggerSpec) {
 | 
					                    triggerSpecs.forEach(function(triggerSpec) {
 | 
				
			||||||
                        if (triggerSpec.trigger === "revealed") {
 | 
					                        if (triggerSpec.sseEvent) {
 | 
				
			||||||
 | 
					                            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") {
 | 
				
			||||||
@ -1439,7 +1670,8 @@ 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-ext], [data-hx-ext]");
 | 
					                var results = elt.querySelectorAll(VERB_SELECTOR + boostedElts + ", [hx-sse], [data-hx-sse], [hx-ws]," +
 | 
				
			||||||
 | 
					                    " [data-hx-ws], [hx-ext], [hx-data-ext]");
 | 
				
			||||||
                return results;
 | 
					                return results;
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                return [];
 | 
					                return [];
 | 
				
			||||||
@ -1490,6 +1722,15 @@ return (function () {
 | 
				
			|||||||
                    initButtonTracking(elt);
 | 
					                    initButtonTracking(elt);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                var sseInfo = getAttributeValue(elt, 'hx-sse');
 | 
				
			||||||
 | 
					                if (sseInfo) {
 | 
				
			||||||
 | 
					                    processSSEInfo(elt, nodeData, sseInfo);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                var wsInfo = getAttributeValue(elt, 'hx-ws');
 | 
				
			||||||
 | 
					                if (wsInfo) {
 | 
				
			||||||
 | 
					                    processWebSocketInfo(elt, nodeData, wsInfo);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
                triggerEvent(elt, "htmx:afterProcessNode");
 | 
					                triggerEvent(elt, "htmx:afterProcessNode");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -126,9 +126,6 @@
 | 
				
			|||||||
<script src="../src/ext/event-header.js"></script>
 | 
					<script src="../src/ext/event-header.js"></script>
 | 
				
			||||||
<script src="ext/event-header.js"></script>
 | 
					<script src="ext/event-header.js"></script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script src="../src/ext/sse.js"></script>
 | 
					 | 
				
			||||||
<script src="../src/ext/ws.js"></script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<!-- events last so they don't screw up other tests -->
 | 
					<!-- events last so they don't screw up other tests -->
 | 
				
			||||||
<script src="core/events.js"></script>
 | 
					<script src="core/events.js"></script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -24,19 +24,6 @@
 | 
				
			|||||||
            <li><a href="scroll-test-targets.html">Targets</a></li>
 | 
					            <li><a href="scroll-test-targets.html">Targets</a></li>
 | 
				
			||||||
        </ul>
 | 
					        </ul>
 | 
				
			||||||
    </li>
 | 
					    </li>
 | 
				
			||||||
    <li>SSE
 | 
					 | 
				
			||||||
        <ul>
 | 
					 | 
				
			||||||
            <li><a href="sse.html">Core SSE Test</a></li>
 | 
					 | 
				
			||||||
            <li><a href="sse-multichannel.html">SSE Multichannel</a></li>
 | 
					 | 
				
			||||||
            <li><a href="sse-multichannel.html">SSE Triggers</a></li>
 | 
					 | 
				
			||||||
            <li><a href="sse-settle.html">SSE Settle</a></li>
 | 
					 | 
				
			||||||
        </ul>
 | 
					 | 
				
			||||||
    </li>
 | 
					 | 
				
			||||||
    <li>Websocket
 | 
					 | 
				
			||||||
        <ul>
 | 
					 | 
				
			||||||
            <li><a href="websocket-reconnect.html">Reconnect</a></li>
 | 
					 | 
				
			||||||
        </ul>
 | 
					 | 
				
			||||||
    </li>
 | 
					 | 
				
			||||||
    <li>History
 | 
					    <li>History
 | 
				
			||||||
        <ul>
 | 
					        <ul>
 | 
				
			||||||
            <li><a href="history">Core History Test</a></li>
 | 
					            <li><a href="history">Core History Test</a></li>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										57
									
								
								test/realtime/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								test/realtime/README.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,57 @@
 | 
				
			|||||||
 | 
					# Htmx - Realtime Test Suite
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This package implements a realtime server for testing WebSockets and Server Sent Events (SSE) in htmx.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## How to Use This Server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. If you do not already have Go (version 1.17 or higher) installed on your machine, you can download an installation for your machine from [https://golang.org](the Go website)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					2. Open up a terminal window and navigate to this directory.  Start up the WebSocket server by typing `go run server.go`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					3. Your browser should open the test suite web page automatically.  If it doesn't, then navigate to [http://localhost](http://localhost) to run the manual tests.  Huzzah!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Web Sockets
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This listens for incoming WebSocket connections coming in to ws://localhost:1323/echo and ws://localhost:1323/heartbeat.  When it receives messages from any WebSocket client, it responds with that same content in a way that htmx can process.  This means, that the response message will look like this: `<div id="idMessage" hx-swap-oob="true">{your message here}</div>`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Echo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The echo endpont listens for incoming WebSocket connections coming in to `ws://localhost:1323/echo`.  When it receives messages from any WebSocket client, it responds with that same content wrapped as an OOB Swap.  So, if you post the message `Hello There. General Kenobi.` the server will respond with this: `<div id="idMessage" hx-swap-oob="true">Hello There. General Kenobi.</div>`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Heartbeat
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The heartbeat endpoint `ws://localhost:1323/heartbeat`. It does not process any messages that are sent to it, but it does send messages containing random numbers to every listener at random intervals. Heartbeat message will look like this: `<div id="idMessage" hx-swap-oob="true">12345678901234567890</div>`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Server Sent Events
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This package implements a simple server that generates Server Sent Events for your test pages to read.  It streams fake data from [jsonplaceholder](https://jsonplaceholder.typicode.com) to your website on a semi-regular schedule.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### JSON Event Streams
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Streams random JSON records every second (or so) to your client.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `/posts.json`
 | 
				
			||||||
 | 
					* `/comments.json`
 | 
				
			||||||
 | 
					* `/albums.json`
 | 
				
			||||||
 | 
					* `/photos.json`
 | 
				
			||||||
 | 
					* `/todos.json`
 | 
				
			||||||
 | 
					* `/users.json`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### HTML Event Streams
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Streams random HTML fragments every second (or so) to your client.  These streams are used by the manual htmx tests.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `/posts.html`
 | 
				
			||||||
 | 
					* `/comments.html`
 | 
				
			||||||
 | 
					* `/albums.html`
 | 
				
			||||||
 | 
					* `/photos.html`
 | 
				
			||||||
 | 
					* `/todos.html`
 | 
				
			||||||
 | 
					* `/users.html`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Specifying Event Types
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You can add a `type=` parameter to your URLs to specify the event name(s) that you want the server to use.  You can specify multiple names in a comma separated list and the server will alternate between them.  If you do not specify a type, then the default message name of `message` is used.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Credits
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					It is inspired by [jsonplaceholder](https://jsonplaceholder.typicode.com) -- *"a free online REST API that you can use whenever you need some fake data."*
 | 
				
			||||||
							
								
								
									
										22
									
								
								test/realtime/go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								test/realtime/go.mod
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					module github.com/bigskysoftware/htmx/test/realtime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					go 1.17
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require (
 | 
				
			||||||
 | 
						github.com/benpate/derp v0.20.0
 | 
				
			||||||
 | 
						github.com/benpate/htmlconv v0.3.0
 | 
				
			||||||
 | 
						github.com/labstack/echo/v4 v4.1.17
 | 
				
			||||||
 | 
						github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
 | 
				
			||||||
 | 
						golang.org/x/net v0.0.0-20200822124328-c89045814202
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require (
 | 
				
			||||||
 | 
						github.com/labstack/gommon v0.3.0 // indirect
 | 
				
			||||||
 | 
						github.com/mattn/go-colorable v0.1.7 // indirect
 | 
				
			||||||
 | 
						github.com/mattn/go-isatty v0.0.12 // indirect
 | 
				
			||||||
 | 
						github.com/valyala/bytebufferpool v1.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/valyala/fasttemplate v1.2.1 // indirect
 | 
				
			||||||
 | 
						golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect
 | 
				
			||||||
 | 
						golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71 // indirect
 | 
				
			||||||
 | 
						golang.org/x/text v0.3.3 // indirect
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@ -17,6 +17,8 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
 | 
				
			|||||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
 | 
					github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
 | 
				
			||||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
 | 
					github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
 | 
				
			||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 | 
					github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 | 
				
			||||||
 | 
					github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
 | 
				
			||||||
 | 
					github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
 | 
				
			||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
					github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
				
			||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
					github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
				
			||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
					github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
				
			||||||
@ -42,13 +44,13 @@ golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7w
 | 
				
			|||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6 h1:DvY3Zkh7KabQE/kfzMvYvKirSiguP9Q/veMtkYyf0o8=
 | 
					 | 
				
			||||||
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71 h1:X/2sJAybVknnUnV7AD2HdT6rm2p5BP6eH2j+igduWgk=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
					golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
				
			||||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
 | 
					golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
 | 
				
			||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
					golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
				
			||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
					golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
				
			||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 | 
					 | 
				
			||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
					gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
				
			||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
					gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
				
			||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 | 
					gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 | 
				
			||||||
@ -16,6 +16,8 @@ import (
 | 
				
			|||||||
	"github.com/benpate/derp"
 | 
						"github.com/benpate/derp"
 | 
				
			||||||
	"github.com/benpate/htmlconv"
 | 
						"github.com/benpate/htmlconv"
 | 
				
			||||||
	"github.com/labstack/echo/v4"
 | 
						"github.com/labstack/echo/v4"
 | 
				
			||||||
 | 
						"github.com/pkg/browser"
 | 
				
			||||||
 | 
						"golang.org/x/net/websocket"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type formatFunc func(interface{}) string
 | 
					type formatFunc func(interface{}) string
 | 
				
			||||||
@ -39,9 +41,13 @@ func main() {
 | 
				
			|||||||
	e := echo.New()
 | 
						e := echo.New()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	e.Static("/", "static")
 | 
						e.Static("/", "static")
 | 
				
			||||||
	e.Static("/htmx", "../../../src")
 | 
						e.Static("/htmx", "../../src")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// JSON Event Streams
 | 
						// Web Socket Handlers
 | 
				
			||||||
 | 
						e.GET("/echo", wsEcho)
 | 
				
			||||||
 | 
						e.GET("/heartbeat", wsHeartbeat)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// SSE - JSON Event Streams
 | 
				
			||||||
	e.GET("/posts.json", handleStream(makeStream(data["posts"], jsonFormatFunc)))
 | 
						e.GET("/posts.json", handleStream(makeStream(data["posts"], jsonFormatFunc)))
 | 
				
			||||||
	e.GET("/comments.json", handleStream(makeStream(data["comments"], jsonFormatFunc)))
 | 
						e.GET("/comments.json", handleStream(makeStream(data["comments"], jsonFormatFunc)))
 | 
				
			||||||
	e.GET("/photos.json", handleStream(makeStream(data["comments"], jsonFormatFunc)))
 | 
						e.GET("/photos.json", handleStream(makeStream(data["comments"], jsonFormatFunc)))
 | 
				
			||||||
@ -49,7 +55,7 @@ func main() {
 | 
				
			|||||||
	e.GET("/todos.json", handleStream(makeStream(data["todos"], jsonFormatFunc)))
 | 
						e.GET("/todos.json", handleStream(makeStream(data["todos"], jsonFormatFunc)))
 | 
				
			||||||
	e.GET("/users.json", handleStream(makeStream(data["users"], jsonFormatFunc)))
 | 
						e.GET("/users.json", handleStream(makeStream(data["users"], jsonFormatFunc)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// HTML Event Streams (with HTMX extension tags)
 | 
						// SSE - HTML Event Streams (with HTMX extension tags)
 | 
				
			||||||
	e.GET("/posts.html", handleStream(makeStream(data["posts"], postTemplate())))
 | 
						e.GET("/posts.html", handleStream(makeStream(data["posts"], postTemplate())))
 | 
				
			||||||
	e.GET("/comments.html", handleStream(makeStream(data["comments"], commentTemplate())))
 | 
						e.GET("/comments.html", handleStream(makeStream(data["comments"], commentTemplate())))
 | 
				
			||||||
	e.GET("/photos.json", handleStream(makeStream(data["comments"], jsonFormatFunc)))
 | 
						e.GET("/photos.json", handleStream(makeStream(data["comments"], jsonFormatFunc)))
 | 
				
			||||||
@ -105,9 +111,72 @@ func main() {
 | 
				
			|||||||
		return ctx.HTML(200, content)
 | 
							return ctx.HTML(200, content)
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// On first run, open web browser in admin mode
 | 
				
			||||||
 | 
						browser.OpenURL("http://localhost/")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	e.Logger.Fatal(e.Start(":80"))
 | 
						e.Logger.Fatal(e.Start(":80"))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*******************************************
 | 
				
			||||||
 | 
					 * Web Socket Handlers
 | 
				
			||||||
 | 
					 *******************************************/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func wsHeartbeat(c echo.Context) error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						handler := websocket.Handler(func(ws *websocket.Conn) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							defer ws.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for i := 0; ; i = i + 1 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								time.Sleep(1 * time.Second)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								random := rand.Int()
 | 
				
			||||||
 | 
								message := `<div id="idMessage" hx-swap-oob="true">Message ` + strconv.Itoa(i) + `: ` + strconv.Itoa(random) + `</div>`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if err := websocket.Message.Send(ws, message); err != nil {
 | 
				
			||||||
 | 
									c.Logger().Error("send", err)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						handler.ServeHTTP(c.Response(), c.Request())
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func wsEcho(c echo.Context) error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						handler := websocket.Handler(func(ws *websocket.Conn) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							defer ws.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								msg := ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if err := websocket.Message.Receive(ws, &msg); err != nil {
 | 
				
			||||||
 | 
									c.Logger().Error("receive", err)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								response := `<div id="idMessage" hx-swap-oob="true">` + msg + `</div>`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if err := websocket.Message.Send(ws, response); err != nil {
 | 
				
			||||||
 | 
									c.Logger().Error("send", err)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						handler.ServeHTTP(c.Response(), c.Request())
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*******************************************
 | 
				
			||||||
 | 
					 * SSE Handlers
 | 
				
			||||||
 | 
					 *******************************************/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func pageHandler(ctx echo.Context, page int) error {
 | 
					func pageHandler(ctx echo.Context, page int) error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pageString := strconv.Itoa(page)
 | 
						pageString := strconv.Itoa(page)
 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB  | 
							
								
								
									
										52
									
								
								test/realtime/static/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								test/realtime/static/index.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					<html>
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
						<link rel="stylesheet" href="/stylesheet.css">
 | 
				
			||||||
 | 
						<title></> htmx Realtime Test Server</title>
 | 
				
			||||||
 | 
						<script src="/htmx/htmx.js"></script>
 | 
				
			||||||
 | 
						<script src="/htmx/ext/sse.js"></script>
 | 
				
			||||||
 | 
						<script src="/htmx/ext/ws.js"></script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<script src="https://unpkg.com/hyperscript.org"></script>
 | 
				
			||||||
 | 
						<script type="text/hyperscript">
 | 
				
			||||||
 | 
							on click(target) from <#navigation a/>
 | 
				
			||||||
 | 
								take .selected for target
 | 
				
			||||||
 | 
						</script>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
						<div id="header"></div>
 | 
				
			||||||
 | 
						<div id="navigation" hx-target="#page" hx-push-url="false">
 | 
				
			||||||
 | 
							<a href="index.html" class="selected" hx-boost="false">Introduction</a>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<div class="group">
 | 
				
			||||||
 | 
								<a href="" hx-get="/ws-about.html">WebSockets</a>
 | 
				
			||||||
 | 
								<a href="" hx-get="/ws-echo.html">Echo</a>
 | 
				
			||||||
 | 
								<a href="" hx-get="/ws-heartbeat.html">Heartbeat</a>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<div class="group">
 | 
				
			||||||
 | 
								<a href="" hx-get="/sse-about.html">Server Sent Events</a>
 | 
				
			||||||
 | 
								<a href="" hx-get="/sse-simple.html">Simple</a>
 | 
				
			||||||
 | 
								<a href="" hx-get="/sse-multiple.html">Multiple</a>
 | 
				
			||||||
 | 
								<a href="" hx-get="/sse-multichannel.html">Multi-Channel</a>
 | 
				
			||||||
 | 
								<a href="" hx-get="/sse-triggers.html">Event Trigger</a>
 | 
				
			||||||
 | 
								<a href="" hx-get="/sse-target.html">Event Target</a>
 | 
				
			||||||
 | 
								<a href="" hx-get="/sse-settle.html">Settling</a>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
						<div id="page">
 | 
				
			||||||
 | 
							<h1>Realtime Test Server</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<h2>New Extensions</h2>
 | 
				
			||||||
 | 
							<p>As of version 1.7, we have created two new extensions <b>ws.js</b> and <b>sse.js</b> to support realtime development in htmx.  All new effort on WebSockets and Server Sent Events will occur in these extensions.</p>
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							<h2>Old Tags Deprecated</h2>
 | 
				
			||||||
 | 
							<p>The existing <b>hx-ws</b> and <b>hx-sse</b> tags have been deprecated and will not receive any more updates.  We plan to remove these two tags from the core library in htmx version 2.0.</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<h2>Try It For Yourself</h2>
 | 
				
			||||||
 | 
							<p>Because extensions use a different calling syntax, there are minor differences in the way that this new code is invoked.  This test server includes several demos / manual tests for each extension that you can try out for yourself.  Each is presented side-by-side with test cases for the original code so that you can see the difference.</p>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										34
									
								
								test/realtime/static/sse-about.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								test/realtime/static/sse-about.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					<h1>Server Sent Events (SSE)</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<p>SSE create a lightweight, uni-directional connection from your server to a client's web browser.  They are often easier to manage than WebSockets, and are built on top ofHTTP connections (making them less likely to be blocked by firewalls).</p>
 | 
				
			||||||
 | 
					<p>As of version 1.7, SSE support has been moved into a new extension, and the existing <b>hx-sse</b> tag has been deprecated.  All future development will occur in the extension code, and the deprecated tag will be removed in htmx version 2.0</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h3>Required Attributes</h3>
 | 
				
			||||||
 | 
					<table>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
						<td class="bold nowrap">hx-ext</td>
 | 
				
			||||||
 | 
						<td>Make sure the SSE extension is initialized on every page or page fragment where you use SSE streams.</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
						<td class="bold nowrap">sse-connect</td>
 | 
				
			||||||
 | 
						<td>Connects to a SSE event stream</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
						<td class="bold nowrap">sse-swap</td>
 | 
				
			||||||
 | 
						<td>Specifies the messages that a particular DOM element will listen to.</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					</table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h3>Example Code</h3>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<pre class="code">
 | 
				
			||||||
 | 
					<body hx-ext="sse">
 | 
				
			||||||
 | 
					<div sse-connect="https://my.sse.server.com" sse-swap="message"></div>
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</pre>
 | 
				
			||||||
 | 
					<h3>SSE Resources</h3>
 | 
				
			||||||
 | 
					<ul>
 | 
				
			||||||
 | 
						<li><a href="https://en.wikipedia.org/wiki/Server-sent_events" target="_blank">Wikipedia</a></li>
 | 
				
			||||||
 | 
						<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events" target="_blank">MDN Web Docs</a></li>
 | 
				
			||||||
 | 
						<li><a href="https://caniuse.com/eventsource" target="_blank">Can I Use?</a></li>
 | 
				
			||||||
 | 
					</ul>
 | 
				
			||||||
@ -1,4 +1,10 @@
 | 
				
			|||||||
<h1>Multi-Channel Test</h1>
 | 
					<h1>Multi-Channel Test</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div role="tablist" hx-target="#page" hx-push-url="false">
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/sse-multichannel.html">Legacy Style</a>
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/sse-multichannel-ext.html" aria-selected="true">New Style</a>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<h3>Description</h3>
 | 
					<h3>Description</h3>
 | 
				
			||||||
<p>
 | 
					<p>
 | 
				
			||||||
	This page connects to a single different Server Sent Event (SSE) stream, listening on events named "Event1", "Event2", "Event3", and "Event4".
 | 
						This page connects to a single different Server Sent Event (SSE) stream, listening on events named "Event1", "Event2", "Event3", and "Event4".
 | 
				
			||||||
							
								
								
									
										25
									
								
								test/realtime/static/sse-multichannel.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								test/realtime/static/sse-multichannel.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					<h1>Multi-Channel Test</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div role="tablist" hx-target="#page" hx-push-url="false">
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/sse-multichannel.html" aria-selected="true">Legacy Style</a>
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/sse-multichannel-ext.html">New Style</a>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h3>Description</h3>
 | 
				
			||||||
 | 
					<p>
 | 
				
			||||||
 | 
						This page connects to a single different Server Sent Event (SSE) stream, listening on events named "Event1", "Event2", "Event3", and "Event4".
 | 
				
			||||||
 | 
						Each separate kind of event should swap into a different container.
 | 
				
			||||||
 | 
					</p>
 | 
				
			||||||
 | 
					<h3>Example HTML</h3>
 | 
				
			||||||
 | 
					<pre class="code">
 | 
				
			||||||
 | 
					<div hx-sse="connect:http://localhost/posts.html?types=Event1%2cEvent2%2cEvent3%2cEvent4">
 | 
				
			||||||
 | 
						<div hx-sse="swap:Event1">Waiting for Posts in Event1 channel...</div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</pre>
 | 
				
			||||||
 | 
					<div hx-sse="connect:http://localhost/posts.html?types=Event1%2cEvent2%2cEvent3%2cEvent4">
 | 
				
			||||||
 | 
						<h3>Test Cases</h3>
 | 
				
			||||||
 | 
						<div class="container" hx-sse="swap:Event1">Waiting for Posts in Event1 channel...</div>
 | 
				
			||||||
 | 
						<div class="container" hx-sse="swap:Event2">Waiting for Posts in Event2 channel...</div>
 | 
				
			||||||
 | 
						<div class="container" hx-sse="swap:Event3">Waiting for Posts in Event3 channel...</div>
 | 
				
			||||||
 | 
						<div class="container" hx-sse="swap:Event4">Waiting for Posts in Event4 channel...</div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
@ -1,4 +1,10 @@
 | 
				
			|||||||
<h1>Multiple Events Test</h1>
 | 
					<h1>Multiple Events Test</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div role="tablist" hx-target="#page" hx-push-url="false">
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/sse-multiple.html">Legacy Style</a>
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/sse-multiple-ext.html" aria-selected="true">New Style</a>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<h3>Description</h3>
 | 
					<h3>Description</h3>
 | 
				
			||||||
<p>
 | 
					<p>
 | 
				
			||||||
	This page connects to a single Server Sent Event (SSE) streams, but listens to multiple events.  
 | 
						This page connects to a single Server Sent Event (SSE) streams, but listens to multiple events.  
 | 
				
			||||||
							
								
								
									
										22
									
								
								test/realtime/static/sse-multiple.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								test/realtime/static/sse-multiple.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					<h1>Multiple Events Test</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div role="tablist" hx-target="#page" hx-push-url="false">
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/sse-multiple.html" aria-selected="true">Legacy Style</a>
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/sse-multiple-ext.html">New Style</a>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h3>Description</h3>
 | 
				
			||||||
 | 
					<p>
 | 
				
			||||||
 | 
						This page connects to a single Server Sent Event (SSE) streams, but listens to multiple events.  
 | 
				
			||||||
 | 
					</p>
 | 
				
			||||||
 | 
					<h3>Example HTML</h3>
 | 
				
			||||||
 | 
					<pre class="code">
 | 
				
			||||||
 | 
					<div hx-sse="connect:http://localhost/posts.html?types=Event1,Event2 swap:Event1 swap:Event2">Waiting for Posts...</div>
 | 
				
			||||||
 | 
					</pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h3>Test Cases</h3>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div>
 | 
				
			||||||
 | 
						<div class="container" hx-sse="connect:http://localhost/posts.html?types=Event1,Event2 swap:Event1 swap:Event2">Waiting for Posts...</div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
@ -1,4 +1,10 @@
 | 
				
			|||||||
<h1>Settling Test</h1>
 | 
					<h1>Settling Test</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div role="tablist" hx-target="#page" hx-push-url="false">
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/sse-settle.html">Legacy Style</a>
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/sse-settle-ext.html" aria-selected="true">New Style</a>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<h3>Description</h3>
 | 
					<h3>Description</h3>
 | 
				
			||||||
<p>
 | 
					<p>
 | 
				
			||||||
	This page connects to a single different Server Sent Event (SSE) stream.  
 | 
						This page connects to a single different Server Sent Event (SSE) stream.  
 | 
				
			||||||
@ -10,6 +16,7 @@
 | 
				
			|||||||
	<div sse-swap="message" hx-swap="settle:100ms">Waiting for Comments...</div>
 | 
						<div sse-swap="message" hx-swap="settle:100ms">Waiting for Comments...</div>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</pre>
 | 
					</pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<h3>Test Cases</h3>
 | 
					<h3>Test Cases</h3>
 | 
				
			||||||
<div hx-ext="sse" sse-connect="http://localhost/comments.html">
 | 
					<div hx-ext="sse" sse-connect="http://localhost/comments.html">
 | 
				
			||||||
	<div class="container" sse-swap="message" hx-swap="innerHTML settle:50ms">Waiting for Comments...</div>
 | 
						<div class="container" sse-swap="message" hx-swap="innerHTML settle:50ms">Waiting for Comments...</div>
 | 
				
			||||||
							
								
								
									
										28
									
								
								test/realtime/static/sse-settle.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								test/realtime/static/sse-settle.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					<h1>Settling Test</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div role="tablist" hx-target="#page" hx-push-url="false">
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/sse-settle.html" aria-selected="true">Legacy Style</a>
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/sse-settle-ext.html">New Style</a>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h3>Description</h3>
 | 
				
			||||||
 | 
					<p>
 | 
				
			||||||
 | 
						This page connects to a single different Server Sent Event (SSE) stream.  
 | 
				
			||||||
 | 
						Multiple containers all listen for the same default "message" event name, but using different values for hx-swap.
 | 
				
			||||||
 | 
					</p>
 | 
				
			||||||
 | 
					<h3>Example HTML</h3>
 | 
				
			||||||
 | 
					<pre class="code">
 | 
				
			||||||
 | 
					<div hx-sse="connect:http://localhost/comments.html">
 | 
				
			||||||
 | 
						<div hx-sse="swap:message" hx-swap="settle:100ms">Waiting for Comments...</div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h3>Test Cases</h3>
 | 
				
			||||||
 | 
					<div hx-sse="connect:http://localhost/comments.html">
 | 
				
			||||||
 | 
						<div class="container" hx-sse="swap:message" hx-swap="innerHTML settle:50ms">Waiting for Comments...</div>
 | 
				
			||||||
 | 
						<div class="container" hx-sse="swap:message" hx-swap="innerHTML settle:100ms">Waiting for Comments...</div>
 | 
				
			||||||
 | 
						<div class="container" hx-sse="swap:message" hx-swap="innerHTML settle:150ms">Waiting for Comments...</div>
 | 
				
			||||||
 | 
						<div class="container" hx-sse="swap:message" hx-swap="innerHTML settle:200ms">Waiting for Comments...</div>
 | 
				
			||||||
 | 
						<div class="container" hx-sse="swap:message" hx-swap="innerHTML settle:250ms">Waiting for Comments...</div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
@ -1,4 +1,10 @@
 | 
				
			|||||||
<h1>Simple Test</h1>
 | 
					<h1>Simple Test</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div role="tablist" hx-target="#page" hx-push-url="false">
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/sse-simple.html">Legacy Style</a>
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/sse-simple-ext.html" aria-selected="true">New Style</a>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<h3>Description</h3>
 | 
					<h3>Description</h3>
 | 
				
			||||||
<p>
 | 
					<p>
 | 
				
			||||||
	This page connects to several different Server Sent Event (SSE) streams, listening on the default event name "message".  
 | 
						This page connects to several different Server Sent Event (SSE) streams, listening on the default event name "message".  
 | 
				
			||||||
							
								
								
									
										26
									
								
								test/realtime/static/sse-simple.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								test/realtime/static/sse-simple.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					<h1>Simple Test</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div role="tablist" hx-target="#page" hx-push-url="false">
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/sse-simple.html" aria-selected="true">Legacy Style</a>
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/sse-simple-ext.html">New Style</a>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h3>Description</h3>
 | 
				
			||||||
 | 
					<p>
 | 
				
			||||||
 | 
						This page connects to several different Server Sent Event (SSE) streams, listening on the default event name "message".  
 | 
				
			||||||
 | 
						Each stream should populate its own container.
 | 
				
			||||||
 | 
					</p>
 | 
				
			||||||
 | 
					<h3>Example HTML</h3>
 | 
				
			||||||
 | 
					<pre class="code">
 | 
				
			||||||
 | 
					<div hx-sse="connect:http://localhost/posts.html swap:message">Waiting for Posts...</div>
 | 
				
			||||||
 | 
					</pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h3>Test Cases</h3>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div>
 | 
				
			||||||
 | 
						<div class="container" hx-sse="connect:http://localhost/posts.html swap:message">Waiting for Posts...</div>
 | 
				
			||||||
 | 
						<div class="container" hx-sse="connect:http://localhost/comments.html swap:message">Waiting for Comments...</div>
 | 
				
			||||||
 | 
						<div class="container" hx-sse="connect:http://localhost/albums.html swap:message">Waiting for Albums...</div>
 | 
				
			||||||
 | 
						<div class="container" hx-sse="connect:http://localhost/todos.html swap:message">Waiting for ToDos...</div>
 | 
				
			||||||
 | 
						<div class="container" hx-sse="connect:http://localhost/users.html swap:message">Waiting for Users...</div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
@ -1,5 +1,10 @@
 | 
				
			|||||||
<h1>Event Target Test</h1>
 | 
					<h1>Event Target Test</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div role="tablist" hx-target="#page" hx-push-url="false">
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/sse-target.html">Legacy Style</a>
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/sse-target-ext.html" aria-selected="true">New Style</a>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<h3>Description</h3>
 | 
					<h3>Description</h3>
 | 
				
			||||||
<p>
 | 
					<p>
 | 
				
			||||||
	This page connects to several different different Server Sent Event (SSE) stream.  
 | 
						This page connects to several different different Server Sent Event (SSE) stream.  
 | 
				
			||||||
							
								
								
									
										12
									
								
								test/realtime/static/sse-target.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								test/realtime/static/sse-target.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					<h1>Event Target Test</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div role="tablist" hx-target="#page" hx-push-url="false">
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/sse-target.html" aria-selected="true">Legacy Style</a>
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/sse-target-ext.html">New Style</a>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h3>Description</h3>
 | 
				
			||||||
 | 
					<p>
 | 
				
			||||||
 | 
						The original implementation of <b>hx-sse</b> does not use for <b>hx-target</b> attributes.
 | 
				
			||||||
 | 
						Therefore, this test is not implemented on this page.
 | 
				
			||||||
 | 
					</p>
 | 
				
			||||||
@ -1,5 +1,10 @@
 | 
				
			|||||||
<h1>Event Trigger Test</h1>
 | 
					<h1>Event Trigger Test</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div role="tablist" hx-target="#page" hx-push-url="false">
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/sse-triggers.html">Legacy Style</a>
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/sse-triggers-ext.html" aria-selected="true">New Style</a>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<h3>Description</h3>
 | 
					<h3>Description</h3>
 | 
				
			||||||
<p>
 | 
					<p>
 | 
				
			||||||
	This page connects to a single different Server Sent Event (SSE) streams, listening on events named "Event1", "Event2", "Event3", and "Event4".
 | 
						This page connects to a single different Server Sent Event (SSE) streams, listening on events named "Event1", "Event2", "Event3", and "Event4".
 | 
				
			||||||
							
								
								
									
										26
									
								
								test/realtime/static/sse-triggers.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								test/realtime/static/sse-triggers.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					<h1>Event Trigger Test</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div role="tablist" hx-target="#page" hx-push-url="false">
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/sse-triggers.html" aria-selected="true">Legacy Style</a>
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/sse-triggers-ext.html">New Style</a>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h3>Description</h3>
 | 
				
			||||||
 | 
					<p>
 | 
				
			||||||
 | 
						This page connects to a single different Server Sent Event (SSE) streams, listening on events named "Event1", "Event2", "Event3", and "Event4".
 | 
				
			||||||
 | 
						Each event is used as a trigger for hx-get to load a random page from the server.
 | 
				
			||||||
 | 
					</p>
 | 
				
			||||||
 | 
					<h3>Example HTML</h3>
 | 
				
			||||||
 | 
					<pre class="code">
 | 
				
			||||||
 | 
					<div hx-sse="connect:http://localhost/posts.html?types=Event1%2cEvent2%2cEvent3%2cEvent4">
 | 
				
			||||||
 | 
						<div hx-get="http://localhost/page/random" hx-trigger="sse:Event1">Waiting for Posts...</div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div hx-sse="connect:http://localhost/posts.html?types=Event1%2cEvent2%2cEvent3%2cEvent4">
 | 
				
			||||||
 | 
						<h3>Test Cases</h3>
 | 
				
			||||||
 | 
						<div class="container" hx-get="http://localhost/page/random" hx-trigger="sse:Event1">Waiting for Posts in Event1 channel...</div>
 | 
				
			||||||
 | 
						<div class="container" hx-get="http://localhost/page/random" hx-trigger="sse:Event2">Waiting for Posts in Event2 channel...</div>
 | 
				
			||||||
 | 
						<div class="container" hx-get="http://localhost/page/random" hx-trigger="sse:Event3">Waiting for Posts in Event3 channel...</div>
 | 
				
			||||||
 | 
						<div class="container" hx-get="http://localhost/page/random" hx-trigger="sse:Event4">Waiting for Posts in Event4 channel...</div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
							
								
								
									
										222
									
								
								test/realtime/static/stylesheet.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								test/realtime/static/stylesheet.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,222 @@
 | 
				
			|||||||
 | 
					/***************************
 | 
				
			||||||
 | 
					 * GLOBAL RESET
 | 
				
			||||||
 | 
					 ***************************/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 *{
 | 
				
			||||||
 | 
						font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
 | 
				
			||||||
 | 
						box-sizing: border-box;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					:root {
 | 
				
			||||||
 | 
						--white: #ffffff;
 | 
				
			||||||
 | 
						--gray05: #fafafa;
 | 
				
			||||||
 | 
						--gray10: #f4f4f4;
 | 
				
			||||||
 | 
						--gray15: #eaeaea;
 | 
				
			||||||
 | 
						--gray20: #e0e0e0;
 | 
				
			||||||
 | 
						--gray30: #c6c6c6;
 | 
				
			||||||
 | 
						--gray40: #a8a8a8;
 | 
				
			||||||
 | 
						--gray50: #8d8d8d;
 | 
				
			||||||
 | 
						--gray60: #6f6f6f;
 | 
				
			||||||
 | 
						--gray70: #525252;
 | 
				
			||||||
 | 
						--gray80: #393939;
 | 
				
			||||||
 | 
						--gray90: #262626;
 | 
				
			||||||
 | 
						--black: #000000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						--blue10: #edf5ff;
 | 
				
			||||||
 | 
						--blue20: #d0e2ff;
 | 
				
			||||||
 | 
						--blue30: #a6c8ff;
 | 
				
			||||||
 | 
						--blue40: #78a9ff;
 | 
				
			||||||
 | 
						--blue50: #4589ff;
 | 
				
			||||||
 | 
						--blue60: #0f62fe;
 | 
				
			||||||
 | 
						--blue70: #0043ce;
 | 
				
			||||||
 | 
						--blue80: #002d9c;
 | 
				
			||||||
 | 
						--blue90: #001d6c;
 | 
				
			||||||
 | 
						--blue100: #001141;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						--red10: #fff1f1;
 | 
				
			||||||
 | 
						--red20: #ffd7d9;
 | 
				
			||||||
 | 
						--red30: #ffb3b8;
 | 
				
			||||||
 | 
						--red40: #ff8389;
 | 
				
			||||||
 | 
						--red50: #fa4d56;
 | 
				
			||||||
 | 
						--red60: #da1e28;
 | 
				
			||||||
 | 
						--red70: #a2191f;
 | 
				
			||||||
 | 
						--red80: #750e13;
 | 
				
			||||||
 | 
						--red90: #520408;
 | 
				
			||||||
 | 
						--red100: #2d0709;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						--input-border: var(--gray30);
 | 
				
			||||||
 | 
						--input-background: var(--gray05);
 | 
				
			||||||
 | 
						--input-color: var(--gray80);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						--input-border-invalid: var(--red40);
 | 
				
			||||||
 | 
						--input-background-invalid: var(--red20);
 | 
				
			||||||
 | 
						--input-color-invalid: var(--red50);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						--border-radius: 7px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/***************************
 | 
				
			||||||
 | 
					 * CHROME AND LAYOUT
 | 
				
			||||||
 | 
					 ***************************/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					body {
 | 
				
			||||||
 | 
						background-color: white;
 | 
				
			||||||
 | 
						padding:0px;
 | 
				
			||||||
 | 
						margin:0px;
 | 
				
			||||||
 | 
						width:100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#header {
 | 
				
			||||||
 | 
						width:100%;
 | 
				
			||||||
 | 
						height: 100px;
 | 
				
			||||||
 | 
						background-image:url('white_transparent.svg');
 | 
				
			||||||
 | 
						background-position:left 50px center;
 | 
				
			||||||
 | 
						background-repeat:no-repeat;
 | 
				
			||||||
 | 
						background-size: 300px;
 | 
				
			||||||
 | 
						background-color:black;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#navigation {
 | 
				
			||||||
 | 
						position:absolute;
 | 
				
			||||||
 | 
						width:200px;
 | 
				
			||||||
 | 
						margin-top:50px;
 | 
				
			||||||
 | 
						margin-left:20px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#navigation .group {
 | 
				
			||||||
 | 
						margin-top:20px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#navigation a:first-child {
 | 
				
			||||||
 | 
						color: black;
 | 
				
			||||||
 | 
						font-weight:500;
 | 
				
			||||||
 | 
						display:block;
 | 
				
			||||||
 | 
						text-decoration:none;
 | 
				
			||||||
 | 
						padding:10px 20px;
 | 
				
			||||||
 | 
						white-space: nowrap;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#navigation a {
 | 
				
			||||||
 | 
						display:block;
 | 
				
			||||||
 | 
						cursor: pointer;
 | 
				
			||||||
 | 
						text-decoration:none;
 | 
				
			||||||
 | 
						padding:10px 20px;
 | 
				
			||||||
 | 
						white-space: nowrap;
 | 
				
			||||||
 | 
						border-radius:5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#navigation a:hover {
 | 
				
			||||||
 | 
						background-color:#eee;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#navigation a.selected {
 | 
				
			||||||
 | 
						color:white;
 | 
				
			||||||
 | 
						background-color:#3465a4;
 | 
				
			||||||
 | 
						font-weight:bold;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#page {
 | 
				
			||||||
 | 
						margin: 50px;
 | 
				
			||||||
 | 
						padding-left:200px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.container {
 | 
				
			||||||
 | 
						padding: 10px;
 | 
				
			||||||
 | 
						border: solid 1px gray;
 | 
				
			||||||
 | 
						margin-bottom: 20px;
 | 
				
			||||||
 | 
						background-color:#f7f7f7;
 | 
				
			||||||
 | 
						height:130px;
 | 
				
			||||||
 | 
						overflow:hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.container.htmx-settling {
 | 
				
			||||||
 | 
						border:solid 3px red!important;
 | 
				
			||||||
 | 
						padding:8px!important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/***************************
 | 
				
			||||||
 | 
					 * TAB STYLES
 | 
				
			||||||
 | 
					 ***************************/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[role="tablist"] {
 | 
				
			||||||
 | 
					    border-bottom: solid 1px var(--gray40);
 | 
				
			||||||
 | 
					    margin-bottom:20px;
 | 
				
			||||||
 | 
					    line-height:normal;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[role="tablist"] > [role="tab"] {
 | 
				
			||||||
 | 
					    cursor: pointer;
 | 
				
			||||||
 | 
					    display:inline-block;
 | 
				
			||||||
 | 
					    padding: 8px 16px 4px 16px;
 | 
				
			||||||
 | 
					    margin:0px 2px -1px 0px;
 | 
				
			||||||
 | 
					    background-color:var(--gray05);
 | 
				
			||||||
 | 
					    border:solid 1px var(--gray30);
 | 
				
			||||||
 | 
					    border-bottom: solid 1px var(--gray40);
 | 
				
			||||||
 | 
					    border-radius: 4px 4px 0px 0px; 
 | 
				
			||||||
 | 
					    color: var(--gray50);
 | 
				
			||||||
 | 
					    font-family: inherit;
 | 
				
			||||||
 | 
					    font-size:1.1rem;
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[role="tablist"] > [role="tab"]:hover,
 | 
				
			||||||
 | 
					[role="tablist"] > [role="tab"]:focus {
 | 
				
			||||||
 | 
					    background-color: var(--gray20);
 | 
				
			||||||
 | 
					    border-color:var(--gray10);
 | 
				
			||||||
 | 
					    border-bottom: solid 1px var(--gray40);
 | 
				
			||||||
 | 
					    color:#666;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[role="tablist"] > [role="tab"][aria-selected="true"] {
 | 
				
			||||||
 | 
					    border-color: var(--gray40);
 | 
				
			||||||
 | 
					    border-bottom: solid 1px white;
 | 
				
			||||||
 | 
					    background-color: white;
 | 
				
			||||||
 | 
					    color: var(--gray100);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/***************************
 | 
				
			||||||
 | 
					 * OTHER UTILITIES
 | 
				
			||||||
 | 
					 ***************************/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pre.code {
 | 
				
			||||||
 | 
						font-family:'Courier New', Courier, monospace;
 | 
				
			||||||
 | 
						background-color: #444440;
 | 
				
			||||||
 | 
						color: #0f0;
 | 
				
			||||||
 | 
						padding:30px 5px 30px 15px;
 | 
				
			||||||
 | 
						overflow-y:scroll;
 | 
				
			||||||
 | 
						display:block;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.bold {
 | 
				
			||||||
 | 
						font-weight:bold;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.nowrap {
 | 
				
			||||||
 | 
						white-space: nowrap;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table {
 | 
				
			||||||
 | 
						border-collapse: collapse;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					td {
 | 
				
			||||||
 | 
						padding:10px 20px;
 | 
				
			||||||
 | 
						border:solid 1px #ddd;
 | 
				
			||||||
 | 
						vertical-align: top;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.demo {
 | 
				
			||||||
 | 
						padding:10px;
 | 
				
			||||||
 | 
						margin:20px 0px;
 | 
				
			||||||
 | 
						color:white;
 | 
				
			||||||
 | 
						background-color: #999;
 | 
				
			||||||
 | 
						height:100px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a, a:visited {
 | 
				
			||||||
 | 
						color:#3465a4;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB  | 
							
								
								
									
										34
									
								
								test/realtime/static/ws-about.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								test/realtime/static/ws-about.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					<h1>WebSockets</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<p>WebSockets create a fast, bi-directional connection between your server and a client's web browser.</p>
 | 
				
			||||||
 | 
					<p>As of version 1.7, WebSocket support has been moved into a new extension, and the existing <b>hx-ws</b> tag has been deprecated.  All future development will occur in the extension code, and the deprecated tag will be removed in htmx version 2.0</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h3>Required Attributes</h3>
 | 
				
			||||||
 | 
					<table>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
						<td class="bold nowrap">hx-ext</td>
 | 
				
			||||||
 | 
						<td>Make sure the WS extension is initialized on every page or page fragment where you use WebSockets.</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
						<td class="bold nowrap">ws-connect</td>
 | 
				
			||||||
 | 
						<td>Connects to a WebSocket.  All received messages parsed as OOB Swaps.</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
						<td class="bold nowrap">ws-send</td>
 | 
				
			||||||
 | 
						<td>Marks a form that, when submitted, will have its contents serialized and sent to the connected WebSocket server</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					</table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h3>Example Code</h3>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<pre class="code">
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					    <div hx-ext="ws" ws-connect="https://my.websocket.server.com"></div>
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</pre>
 | 
				
			||||||
 | 
					<h3>WebSocket Resources</h3>
 | 
				
			||||||
 | 
					<ul>
 | 
				
			||||||
 | 
						<li><a href="https://en.wikipedia.org/wiki/WebSocket" target="_blank">Wikipedia</a></li>
 | 
				
			||||||
 | 
						<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket" target="_blank">MDN Web Docs</a></li>
 | 
				
			||||||
 | 
						<li><a href="https://caniuse.com/websocket" target="_blank">Can I Use?</a></li>
 | 
				
			||||||
 | 
					</ul>
 | 
				
			||||||
@ -1,4 +1,10 @@
 | 
				
			|||||||
<h1>Echo Test</h1>
 | 
					<h1>Echo Test</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div role="tablist" hx-target="#page" hx-push-url="false">
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/ws-echo.html">Legacy Style</a>
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/ws-echo-ext.html" aria-selected="true">New Style</a>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<h3>Description</h3>
 | 
					<h3>Description</h3>
 | 
				
			||||||
<p>This test lets you send and receive data to and from the WebSocket server.  Every message that you send to the server will be "echoed"
 | 
					<p>This test lets you send and receive data to and from the WebSocket server.  Every message that you send to the server will be "echoed"
 | 
				
			||||||
back to you in a separate message</p>
 | 
					back to you in a separate message</p>
 | 
				
			||||||
							
								
								
									
										39
									
								
								test/realtime/static/ws-echo.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								test/realtime/static/ws-echo.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					<h1>Echo Test</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div role="tablist" hx-target="#page" hx-push-url="false">
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/ws-echo.html" aria-selected="true">Legacy Style</a>
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/ws-echo-ext.html">New Style</a>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h3>Description</h3>
 | 
				
			||||||
 | 
					<p>This test lets you send and receive data to and from the WebSocket server.  Every message that you send to the server will be "echoed"
 | 
				
			||||||
 | 
					back to you in a separate message</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h3>Example HTML</h3>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<pre class="code">
 | 
				
			||||||
 | 
					<div hx-ws="connect:ws://localhost/echo">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <form hx-ws="send">
 | 
				
			||||||
 | 
					        <input type="text" name="message" style="width:500px;" value="This Is The Message" />
 | 
				
			||||||
 | 
					        <input type="submit"/>
 | 
				
			||||||
 | 
					    </form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div id="idMessage"></div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div class="container" hx-ws="connect:ws://localhost/echo">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <form hx-ws="send">
 | 
				
			||||||
 | 
					        <h3>Send a Message</h3>
 | 
				
			||||||
 | 
					        <div>
 | 
				
			||||||
 | 
					            <input type="text" name="message" style="width:500px;" value="This Is The Message" />
 | 
				
			||||||
 | 
					            <input type="submit" value="Send" class="btn primary"/>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <br>
 | 
				
			||||||
 | 
					    <h3>Receive a Message</h3>
 | 
				
			||||||
 | 
					    <div id="idMessage"></div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
@ -1,4 +1,10 @@
 | 
				
			|||||||
<h1>Heartbeat Test</h1>
 | 
					<h1>Heartbeat Test</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div role="tablist" hx-target="#page" hx-push-url="false">
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/ws-heartbeat.html">Legacy Style</a>
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/ws-heartbeat-ext.html" aria-selected="true">New Style</a>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<h3>Description</h3>
 | 
					<h3>Description</h3>
 | 
				
			||||||
<p>This test receives messages from the WebSocket server every second.
 | 
					<p>This test receives messages from the WebSocket server every second.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										23
									
								
								test/realtime/static/ws-heartbeat.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								test/realtime/static/ws-heartbeat.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					<h1>Heartbeat Test</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div role="tablist" hx-target="#page" hx-push-url="false">
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/ws-heartbeat.html" aria-selected="true">Legacy Style</a>
 | 
				
			||||||
 | 
						<a role="tab" hx-get="/ws-heartbeat-ext.html">New Style</a>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h3>Description</h3>
 | 
				
			||||||
 | 
					<p>This test receives messages from the WebSocket server every second.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h3>Example HTML</h3>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<pre class="code">
 | 
				
			||||||
 | 
					<div hx-ws="connect:ws://localhost/heartbeat">
 | 
				
			||||||
 | 
					    <div id="idMessage"></div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div class="container" hx-ws="connect:ws://localhost/heartbeat">
 | 
				
			||||||
 | 
					    <h3>WebSocket Messages</h3>
 | 
				
			||||||
 | 
					    <p>Each message just contains a random number generated by the server</p>
 | 
				
			||||||
 | 
					    <div id="idMessage">Waiting...</div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
							
								
								
									
										22
									
								
								test/realtime/static/ws-reconnect.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								test/realtime/static/ws-reconnect.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					<html>
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <script src="../../src/htmx.js"></script>
 | 
				
			||||||
 | 
					    <title>WebSockets Test</title>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body hx-ws="connect:wss://echo.websocket.org">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <form hx-ws="send">
 | 
				
			||||||
 | 
					        Send Message to Echo Server...<br>
 | 
				
			||||||
 | 
					        <textarea name="message" style="width:500px; height:300px;"><div id="idMessage">This Is The Message<div></textarea>
 | 
				
			||||||
 | 
					        <br/><input type="submit"/>
 | 
				
			||||||
 | 
					    </form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <!--
 | 
				
			||||||
 | 
					    Receive doesn't work with this `echo` server because of differences in the way HTMX formats
 | 
				
			||||||
 | 
					    `send` messages vs. what it expects for replies.  It has no bearing on the reconnect test.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <br><hr><br>
 | 
				
			||||||
 | 
					    <div id="idMessage"></div>
 | 
				
			||||||
 | 
					    -->
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
@ -1,43 +0,0 @@
 | 
				
			|||||||
# Server Sent Events - Test Server
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
This package implements a simple server that generates Server Sent Events for your test pages to read.  It streams fake data from [jsonplaceholder](https://jsonplaceholder.typicode.com) to your website on a semi-regular schedule.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## How to Use This Server
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
1. If you do not already have Go (version 1.17 or higher) installed on your machine, you can download an installation for your machine from [the Go website](https://golang.org)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
2. Open up a terminal window and navigate to this directory.  Start up the WebSocket server by typing `go run server.go`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
3. Open your web browser to [http://localhost](http://localhost) to run the manual tests.  Huzzah!
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## JSON Event Streams
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Streams random JSON records every second (or so) to your client.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* `/posts.json`
 | 
					 | 
				
			||||||
* `/comments.json`
 | 
					 | 
				
			||||||
* `/albums.json`
 | 
					 | 
				
			||||||
* `/photos.json`
 | 
					 | 
				
			||||||
* `/todos.json`
 | 
					 | 
				
			||||||
* `/users.json`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## HTML Event Streams
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Streams random HTML fragments every second (or so) to your client.  These streams are used by the manual htmx tests.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* `/posts.html`
 | 
					 | 
				
			||||||
* `/comments.html`
 | 
					 | 
				
			||||||
* `/albums.html`
 | 
					 | 
				
			||||||
* `/photos.html`
 | 
					 | 
				
			||||||
* `/todos.html`
 | 
					 | 
				
			||||||
* `/users.html`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Specifying Event Types
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
You can add a `type=` parameter to your URLs to specify the event name(s) that you want the server to use.  You can specify multiple names in a comma separated list and the server will alternate between them.  If you do not specify a type, then the default message name of `message` is used.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## About
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
This server is also published independently at [https://github.com/benpate/sseplaceholder]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
It is inspired by [jsonplaceholder](https://jsonplaceholder.typicode.com) -- *"a free online REST API that you can use whenever you need some fake data."*
 | 
					 | 
				
			||||||
@ -1,9 +0,0 @@
 | 
				
			|||||||
module github.com/benpate/sseplaceholder
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
go 1.16
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
require (
 | 
					 | 
				
			||||||
	github.com/benpate/derp v0.20.0
 | 
					 | 
				
			||||||
	github.com/benpate/htmlconv v0.3.0
 | 
					 | 
				
			||||||
	github.com/labstack/echo/v4 v4.1.17
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
@ -1,63 +0,0 @@
 | 
				
			|||||||
<html>
 | 
					 | 
				
			||||||
<head>
 | 
					 | 
				
			||||||
	<link rel="stylesheet" href="/stylesheet.css">
 | 
					 | 
				
			||||||
	<title></> htmx SSE Test Server</title>
 | 
					 | 
				
			||||||
	<script src="/htmx/htmx.js"></script>
 | 
					 | 
				
			||||||
	<script src="/htmx/ext/sse.js"></script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	<script src="https://unpkg.com/hyperscript.org@0.8.3"></script>
 | 
					 | 
				
			||||||
	<script type="text/hyperscript">
 | 
					 | 
				
			||||||
		on click(target) from <#navigation a/>
 | 
					 | 
				
			||||||
			take .selected for target
 | 
					 | 
				
			||||||
	</script>
 | 
					 | 
				
			||||||
</head>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<body>
 | 
					 | 
				
			||||||
	<div id="header"></div>
 | 
					 | 
				
			||||||
	<div id="navigation" hx-target="#page" hx-push-url="false">
 | 
					 | 
				
			||||||
		<a href="index.html" class="selected" hx-boost="false">About</a>
 | 
					 | 
				
			||||||
		<a href="" hx-get="sse-simple.html">Simple</a>
 | 
					 | 
				
			||||||
		<a href="" hx-get="sse-multiple.html">Multiple</a>
 | 
					 | 
				
			||||||
		<a href="" hx-get="sse-multichannel.html">Multi-Channel</a>
 | 
					 | 
				
			||||||
		<a href="" hx-get="sse-triggers.html">Event Trigger</a>
 | 
					 | 
				
			||||||
		<a href="" hx-get="sse-target.html">Event Target</a>
 | 
					 | 
				
			||||||
		<a href="" hx-get="sse-settle.html">Settling</a>
 | 
					 | 
				
			||||||
	</div>
 | 
					 | 
				
			||||||
	<div id="page">
 | 
					 | 
				
			||||||
		<h1>Server Sent Events (SSE) Extension Tests</h1>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		<p>As of version 1.7, SSE support has been moved out of the core htmx library and into an extension.  This server runs a test suite for the htmx SSE extension.</p>
 | 
					 | 
				
			||||||
		<p>This extension listens for real-time events that are pushed from the server and can swap them into your htmx webpage.</p>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		<h3>Required Attributes</h3>
 | 
					 | 
				
			||||||
		<table>
 | 
					 | 
				
			||||||
		<tr>
 | 
					 | 
				
			||||||
			<td class="bold nowrap">hx-ext</td>
 | 
					 | 
				
			||||||
			<td>Make sure the SSE extension is initialized on every page or page fragment where you use SSE streams.</td>
 | 
					 | 
				
			||||||
		</tr>
 | 
					 | 
				
			||||||
		<tr>
 | 
					 | 
				
			||||||
			<td class="bold nowrap">sse-connect</td>
 | 
					 | 
				
			||||||
			<td>Connects to a SSE event stream</td>
 | 
					 | 
				
			||||||
		</tr>
 | 
					 | 
				
			||||||
		<tr>
 | 
					 | 
				
			||||||
			<td class="bold nowrap">sse-swap</td>
 | 
					 | 
				
			||||||
			<td>Specifies the messages that a particular DOM element will listen to.</td>
 | 
					 | 
				
			||||||
		</tr>
 | 
					 | 
				
			||||||
		</table>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		<h3>Example Code</h3>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<pre class="code">
 | 
					 | 
				
			||||||
<body hx-ext="sse">
 | 
					 | 
				
			||||||
    <div sse-connect="https://my.sse.server.com" sse-swap="message"></div>
 | 
					 | 
				
			||||||
</body>
 | 
					 | 
				
			||||||
</pre>
 | 
					 | 
				
			||||||
		<h3>SSE Resources</h3>
 | 
					 | 
				
			||||||
		<ul>
 | 
					 | 
				
			||||||
			<li><a href="https://en.wikipedia.org/wiki/Server-sent_events">Wikipedia</a></li>
 | 
					 | 
				
			||||||
			<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events">MDN Web Docs</a></li>
 | 
					 | 
				
			||||||
			<li><a href="https://caniuse.com/eventsource">Can I Use?</a></li>
 | 
					 | 
				
			||||||
		</ul>
 | 
					 | 
				
			||||||
	</div>
 | 
					 | 
				
			||||||
</body>
 | 
					 | 
				
			||||||
</html>
 | 
					 | 
				
			||||||
@ -1,102 +0,0 @@
 | 
				
			|||||||
*{
 | 
					 | 
				
			||||||
	font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
 | 
					 | 
				
			||||||
	box-sizing: border-box;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
body {
 | 
					 | 
				
			||||||
	background-color: white;
 | 
					 | 
				
			||||||
	padding:0px;
 | 
					 | 
				
			||||||
	margin:0px;
 | 
					 | 
				
			||||||
	width:100%;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#header {
 | 
					 | 
				
			||||||
	width:100%;
 | 
					 | 
				
			||||||
	height: 100px;
 | 
					 | 
				
			||||||
	background-image:url('white_transparent.svg');
 | 
					 | 
				
			||||||
	background-position:left 50px center;
 | 
					 | 
				
			||||||
	background-repeat:no-repeat;
 | 
					 | 
				
			||||||
	background-size: 300px;
 | 
					 | 
				
			||||||
	background-color:black;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#navigation {
 | 
					 | 
				
			||||||
	position:absolute;
 | 
					 | 
				
			||||||
	width:150px;
 | 
					 | 
				
			||||||
	margin-top:50px;
 | 
					 | 
				
			||||||
	margin-left:20px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#navigation > a {
 | 
					 | 
				
			||||||
	display:block;
 | 
					 | 
				
			||||||
	cursor: pointer;
 | 
					 | 
				
			||||||
	text-decoration:none;
 | 
					 | 
				
			||||||
	padding:10px 20px;
 | 
					 | 
				
			||||||
	white-space: nowrap;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#navigation > a:hover {
 | 
					 | 
				
			||||||
	background-color:#eee;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#navigation > a.selected {
 | 
					 | 
				
			||||||
	font-weight:bold;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#page {
 | 
					 | 
				
			||||||
	margin: 50px;
 | 
					 | 
				
			||||||
	padding-left:150px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.container {
 | 
					 | 
				
			||||||
	padding: 10px;
 | 
					 | 
				
			||||||
	border: solid 1px gray;
 | 
					 | 
				
			||||||
	margin-bottom: 20px;
 | 
					 | 
				
			||||||
	background-color:#f7f7f7;
 | 
					 | 
				
			||||||
	height:130px;
 | 
					 | 
				
			||||||
	overflow:hidden;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.container.htmx-settling {
 | 
					 | 
				
			||||||
	border:solid 3px red!important;
 | 
					 | 
				
			||||||
	padding:8px!important;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pre.code {
 | 
					 | 
				
			||||||
	font-family:'Courier New', Courier, monospace;
 | 
					 | 
				
			||||||
	background-color: #444440;
 | 
					 | 
				
			||||||
	color: #0f0;
 | 
					 | 
				
			||||||
	padding:30px 5px 30px 15px;
 | 
					 | 
				
			||||||
	overflow-y:scroll;
 | 
					 | 
				
			||||||
	display:block;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.bold {
 | 
					 | 
				
			||||||
	font-weight:bold;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.nowrap {
 | 
					 | 
				
			||||||
	white-space: nowrap;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
table {
 | 
					 | 
				
			||||||
	border-collapse: collapse;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
td {
 | 
					 | 
				
			||||||
	padding:10px 20px;
 | 
					 | 
				
			||||||
	border:solid 1px #ddd;
 | 
					 | 
				
			||||||
	vertical-align: top;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.demo {
 | 
					 | 
				
			||||||
	padding:10px;
 | 
					 | 
				
			||||||
	margin:20px 0px;
 | 
					 | 
				
			||||||
	color:white;
 | 
					 | 
				
			||||||
	background-color: #999;
 | 
					 | 
				
			||||||
	height:100px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
a, a:visited {
 | 
					 | 
				
			||||||
	color:#3465a4;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
# WebSocket - Test Server
 | 
					# Htmx Realtime Test Server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This package implements a test-suite WebSocket server for testing htmx.
 | 
					This package implements a realtime server for testing WebSockets and Server Sent Events (SSE) in htmx.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## What It Does
 | 
					## What It Does
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -3,9 +3,9 @@ layout: layout.njk
 | 
				
			|||||||
title: </> htmx - hx-sse
 | 
					title: </> htmx - hx-sse
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## `hx-sse` *HAS BEEN MIGRATED TO AN EXTENSION*
 | 
					## `hx-sse` *HAS BEEN DEPRECATED*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**If you are using htmx version 1.6.1 or greater, please visit the [SSE extension page](../extensions/server-sent-events) to learn about the new implementation of Server Sent Events as an extension.
 | 
					**This tag will be removed in htmx version 2.0.  If you are using htmx version 1.7 or greater, please visit the [SSE extension page](../extensions/server-sent-events) to learn about the new implementation of Server Sent Events as an extension.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## This Reference Applies To Version 1.6 And Below
 | 
					## This Reference Applies To Version 1.6 And Below
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -15,7 +15,6 @@ The `hx-sse` allows you to work with [Server Sent Event](https://developer.mozil
 | 
				
			|||||||
* `connect:<url>` - A URL to establish an `EventSource` against
 | 
					* `connect:<url>` - A URL to establish an `EventSource` against
 | 
				
			||||||
* `swap:<eventName>` - Swap SSE message content into a DOM node on matching event names
 | 
					* `swap:<eventName>` - Swap SSE message content into a DOM node on matching event names
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
### Swap Message Content
 | 
					### Swap Message Content
 | 
				
			||||||
 | 
					
 | 
				
			||||||
When an SSE connection has been established (using the `connect` keyword) the contents of SSE messages can be swapped into the DOM using the `swap` keyword.  This can be done on the element that creates the SSE connection, or any child element of it.  Multiple elements can use `swap` to listen for Server Sent Events.
 | 
					When an SSE connection has been established (using the `connect` keyword) the contents of SSE messages can be swapped into the DOM using the `swap` keyword.  This can be done on the element that creates the SSE connection, or any child element of it.  Multiple elements can use `swap` to listen for Server Sent Events.
 | 
				
			||||||
@ -87,6 +86,10 @@ data: <div>Content to swap into your HTML page.</div>
 | 
				
			|||||||
<div hx-sse="connect:/server-url swap:message"></div>
 | 
					<div hx-sse="connect:/server-url swap:message"></div>
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Test SSE Server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Htmx includes an SSE test server with many more examples of how to use Server Sent Events.  Download the htmx source code from github and navigate to /test/servers/sse to experiment.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Notes
 | 
					### Notes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* `hx-sse` is not inherited
 | 
					* `hx-sse` is not inherited
 | 
				
			||||||
 | 
				
			|||||||
@ -3,9 +3,9 @@ layout: layout.njk
 | 
				
			|||||||
title: </> htmx - hx-ws
 | 
					title: </> htmx - hx-ws
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## `hx-ws` *HAS BEEN MIGRATED TO AN EXTENSION*
 | 
					## `hx-ws` *HAS BEEN DEPRECATED*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**If you are using htmx version 1.6.1 or greater, please visit the [WebSockets extension page](../extensions/web-sockets) to learn about the new implementation of Web Sockets as an extension.
 | 
					**This tag will be removed in htmx version 2.0.  If you are using htmx version 1.7 or greater, please visit the [WebSockets extension page](../extensions/web-sockets) to learn about the new implementation of Web Sockets as an extension.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## This Reference Applies To Version 1.6 And Below
 | 
					## This Reference Applies To Version 1.6 And Below
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -45,6 +45,11 @@ The default reconnection interval is implemented with the full-jitter exponentia
 | 
				
			|||||||
Own implementations can be provided by setting `htmx.config.wsReconnectDelay` to a function with
 | 
					Own implementations can be provided by setting `htmx.config.wsReconnectDelay` to a function with
 | 
				
			||||||
`retryCount` as its only parameter.
 | 
					`retryCount` as its only parameter.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Test Web Sockets Server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Htmx includes a WebSockets test server with many more examples of how to use Server Sent Events.  Download the htmx source code from github and navigate to /test/servers/ws to experiment.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Notes
 | 
					### Notes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* `hx-ws` is not inherited
 | 
					* `hx-ws` is not inherited
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user