Skip to content

Commit

Permalink
Merge pull request #18 from bigskysoftware/16-double-processing
Browse files Browse the repository at this point in the history
fix sse.js double element processing issue with expanded extension API
  • Loading branch information
1cg committed Apr 25, 2024
2 parents 783b981 + 76232b4 commit cabecde
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 60 deletions.
82 changes: 22 additions & 60 deletions src/sse/sse.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
}
},

getSelectors: function() {
return ['[sse-connect]', '[data-sse-connect]', '[sse-swap]', '[data-sse-swap]']
},

/**
* onEvent handles all events passed to this extension.
*
Expand Down Expand Up @@ -77,9 +81,9 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
*/
function registerSSE(elt) {
// Add message handlers for every `sse-swap` attribute
queryAttributeOnThisOrChildren(elt, 'sse-swap').forEach(function(child) {
if (api.getAttributeValue(elt, 'sse-swap')) {
// Find closest existing event source
var sourceElement = api.getClosestMatch(child, hasEventSource)
var sourceElement = api.getClosestMatch(elt, hasEventSource)
if (sourceElement == null) {
// api.triggerErrorEvent(elt, "htmx:noSSESourceError")
return null // no eventsource in parentage, orphaned element
Expand All @@ -89,7 +93,7 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
var internalData = api.getInternalData(sourceElement)
var source = internalData.sseEventSource

var sseSwapAttr = api.getAttributeValue(child, 'sse-swap')
var sseSwapAttr = api.getAttributeValue(elt, 'sse-swap')
var sseEventNames = sseSwapAttr.split(',')

for (var i = 0; i < sseEventNames.length; i++) {
Expand All @@ -101,7 +105,7 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
}

// If the body no longer contains the element, remove the listener
if (!api.bodyContains(child)) {
if (!api.bodyContains(elt)) {
source.removeEventListener(sseEventName, listener)
return
}
Expand All @@ -110,20 +114,20 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
if (!api.triggerEvent(elt, 'htmx:sseBeforeMessage', event)) {
return
}
swap(child, event.data)
swap(elt, event.data)
api.triggerEvent(elt, 'htmx:sseMessage', event)
}

// Register the new listener
api.getInternalData(child).sseEventListener = listener
api.getInternalData(elt).sseEventListener = listener
source.addEventListener(sseEventName, listener)
}
})
}

// Add message handlers for every `hx-trigger="sse:*"` attribute
queryAttributeOnThisOrChildren(elt, 'hx-trigger').forEach(function(child) {
if (api.getAttributeValue(elt, 'hx-trigger')) {
// Find closest existing event source
var sourceElement = api.getClosestMatch(child, hasEventSource)
var sourceElement = api.getClosestMatch(elt, hasEventSource)
if (sourceElement == null) {
// api.triggerErrorEvent(elt, "htmx:noSSESourceError")
return null // no eventsource in parentage, orphaned element
Expand All @@ -133,7 +137,7 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
var internalData = api.getInternalData(sourceElement)
var source = internalData.sseEventSource

var sseEventName = api.getAttributeValue(child, 'hx-trigger')
var sseEventName = api.getAttributeValue(elt, 'hx-trigger')
if (sseEventName == null) {
return
}
Expand All @@ -148,19 +152,19 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
return
}

if (!api.bodyContains(child)) {
if (!api.bodyContains(elt)) {
source.removeEventListener(sseEventName, listener)
}

// Trigger events to be handled by the rest of htmx
htmx.trigger(child, sseEventName, event)
htmx.trigger(child, 'htmx:sseMessage', event)
htmx.trigger(elt, sseEventName, event)
htmx.trigger(elt, 'htmx:sseMessage', event)
}

// Register the new listener
api.getInternalData(elt).sseEventListener = listener
source.addEventListener(sseEventName.slice(4), listener)
})
}
}

/**
Expand All @@ -177,14 +181,14 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
}

// handle extension source creation attribute
queryAttributeOnThisOrChildren(elt, 'sse-connect').forEach(function(child) {
var sseURL = api.getAttributeValue(child, 'sse-connect')
if (api.getAttributeValue(elt, 'sse-connect')) {
var sseURL = api.getAttributeValue(elt, 'sse-connect')
if (sseURL == null) {
return
}

ensureEventSource(child, sseURL, retryCount)
})
ensureEventSource(elt, sseURL, retryCount)
}

registerSSE(elt)
}
Expand Down Expand Up @@ -246,27 +250,6 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
return false
}

/**
* queryAttributeOnThisOrChildren returns all nodes that contain the requested attributeName, INCLUDING THE PROVIDED ROOT ELEMENT.
*
* @param {HTMLElement} elt
* @param {string} attributeName
*/
function queryAttributeOnThisOrChildren(elt, attributeName) {
var result = []

// If the parent element also contains the requested attribute, then add it to the results too.
if (api.hasAttribute(elt, attributeName)) {
result.push(elt)
}

// Search all child nodes that match the requested attribute
elt.querySelectorAll('[' + attributeName + '], [data-' + attributeName + ']').forEach(function(node) {
result.push(node)
})

return result
}

/**
* @param {HTMLElement} elt
Expand All @@ -282,27 +265,6 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
api.swap(target, content, swapSpec)
}

/**
* doSettle mirrors much of the functionality in htmx that
* settles elements after their content has been swapped.
* TODO: this should be published by htmx, and not duplicated here
* @param {import("../htmx").HtmxSettleInfo} settleInfo
* @returns () => void
*/
function doSettle(settleInfo) {
return function() {
settleInfo.tasks.forEach(function(task) {
task.call()
})

settleInfo.elts.forEach(function(elt) {
if (elt.classList) {
elt.classList.remove(htmx.config.settlingClass)
}
api.triggerEvent(elt, 'htmx:afterSettle')
})
}
}

function hasEventSource(node) {
return api.getInternalData(node).sseEventSource != null
Expand Down
36 changes: 36 additions & 0 deletions src/sse/test/ext/sse.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,28 @@ describe('sse extension', function() {
clearWorkArea()
})

it('correctly subscribes to events', function() {
make('<div hx-ext="sse" sse-connect="/foo">' +
'<div sse-connect="/foo">' +
'<div id="d1" hx-trigger="sse:e1" hx-get="/d1">div1</div>' +
'</div>' +
'</div>')

this.eventSource.url.should.be.equal('/foo');
this.eventSource._listeners.e1.should.be.lengthOf(1)
})

it('correctly behaves when ignored', function() {
make('<div hx-ext="sse" sse-connect="/foo">' +
'<div hx-ext="ignore:sse" sse-connect="/foo">' +
'<div id="d1" hx-trigger="sse:e1" hx-get="/d1">div1</div>' +
'</div>' +
'</div>');

this.eventSource.url.should.be.equal('/foo');
(this.eventSource._listeners.e1 == undefined).should.be.true
})

it('handles basic sse triggering', function() {
this.server.respondWith('GET', '/d1', 'div1 updated')
this.server.respondWith('GET', '/d2', 'div2 updated')
Expand Down Expand Up @@ -231,6 +253,20 @@ describe('sse extension', function() {
(byId('d2')['htmx-internal-data'].sseEventSource == undefined).should.be.true
})

it('triggers events with naked hx-trigger', function() {
var div = make( '<div hx-ext="sse"><div sse-connect="/foo"><div id="d2" hx-trigger="sse:e2">div2</div></div></div>')

let triggerCounter = 0
div.addEventListener("htmx:trigger", () => triggerCounter++)
let sseMessageCounter = 0
div.addEventListener("htmx:sseMessage", () => sseMessageCounter++)

this.eventSource.sendEvent('e2')

triggerCounter.should.be.equal(1)
sseMessageCounter.should.be.equal(1)
})

it('initializes connections in swapped content', function() {
this.server.respondWith('GET', '/d1', '<div><div sse-connect="/foo"><div id="d2" hx-trigger="sse:e2" hx-get="/d2">div2</div></div></div>')
this.server.respondWith('GET', '/d2', 'div2 updated')
Expand Down

0 comments on commit cabecde

Please sign in to comment.