Skip to content

Commit

Permalink
Initial suggestion (squashed)
Browse files Browse the repository at this point in the history
Support multiple extended selectors for hx-include

Additional test for nested standard selector

Add @MichaelWest22 hx-disabled-elt multiple selector test

Add hx-trigger `from` test with multiple extended selectors

Simplify

Include #2915 fix

Update htmx.js

Split for readability

Don't apply global to previous selectors

Rewrite loop, restore global recursive call, minimize diff

Use break for better readability

Co-Authored-By: MichaelWest22 <[email protected]>
  • Loading branch information
Telroshan and MichaelWest22 committed Sep 25, 2024
1 parent 3d1a2e5 commit 4466697
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 24 deletions.
72 changes: 48 additions & 24 deletions src/htmx.js
Original file line number Diff line number Diff line change
Expand Up @@ -1141,31 +1141,55 @@ var htmx = (function() {
*/
function querySelectorAllExt(elt, selector, global) {
elt = resolveTarget(elt)
if (selector.indexOf('closest ') === 0) {
return [closest(asElement(elt), normalizeSelector(selector.substr(8)))]
} else if (selector.indexOf('find ') === 0) {
return [find(asParentNode(elt), normalizeSelector(selector.substr(5)))]
} else if (selector === 'next') {
return [asElement(elt).nextElementSibling]
} else if (selector.indexOf('next ') === 0) {
return [scanForwardQuery(elt, normalizeSelector(selector.substr(5)), !!global)]
} else if (selector === 'previous') {
return [asElement(elt).previousElementSibling]
} else if (selector.indexOf('previous ') === 0) {
return [scanBackwardsQuery(elt, normalizeSelector(selector.substr(9)), !!global)]
} else if (selector === 'document') {
return [document]
} else if (selector === 'window') {
return [window]
} else if (selector === 'body') {
return [document.body]
} else if (selector === 'root') {
return [getRootNode(elt, !!global)]
} else if (selector.indexOf('global ') === 0) {
return querySelectorAllExt(elt, selector.slice(7), true)
} else {
return toArray(asParentNode(getRootNode(elt, !!global)).querySelectorAll(normalizeSelector(selector)))

const parts = selector.split(',')
const result = []
const unprocessedParts = []
while (parts.length > 0) {
const selector = normalizeSelector(parts.shift())
let item
if (selector.indexOf('closest ') === 0) {
item = closest(asElement(elt), normalizeSelector(selector.substr(8)))
} else if (selector.indexOf('find ') === 0) {
item = find(asParentNode(elt), normalizeSelector(selector.substr(5)))
} else if (selector === 'next' || selector === 'nextElementSibling') {
item = asElement(elt).nextElementSibling
} else if (selector.indexOf('next ') === 0) {
item = scanForwardQuery(elt, normalizeSelector(selector.substr(5)), !!global)
} else if (selector === 'previous' || selector === 'previousElementSibling') {
item = asElement(elt).previousElementSibling
} else if (selector.indexOf('previous ') === 0) {
item = scanBackwardsQuery(elt, normalizeSelector(selector.substr(9)), !!global)
} else if (selector === 'document') {
item = document
} else if (selector === 'window') {
item = window
} else if (selector === 'body') {
item = document.body
} else if (selector === 'root') {
item = getRootNode(elt, !!global)
} else if (selector.indexOf('global ') === 0) {
// Previous implementation of `global` only supported it at the first position and applied it to the entire selector string.
// For backward compatibility and to maintain logical consistency, we make it apply to everything that follows.
parts.unshift(selector.slice(7))
result.push(...querySelectorAllExt(elt, parts.join(','), true))
break
} else {
unprocessedParts.push(selector)
}

if (item) {
result.push(item)
}
}

if (unprocessedParts.length > 0) {
const standardSelector = unprocessedParts.join(',')
const rootNode = asParentNode(getRootNode(elt, !!global))
result.push(...toArray(rootNode.querySelectorAll(standardSelector)))
}

return result
}

/**
Expand Down
39 changes: 39 additions & 0 deletions test/attributes/hx-disabled-elt.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,43 @@ describe('hx-disabled-elt attribute', function() {
div.innerHTML.should.equal('Loaded!')
btn.hasAttribute('disabled').should.equal(false)
})

Check failure on line 95 in test/attributes/hx-disabled-elt.js

View workflow job for this annotation

GitHub Actions / test_suite

Trailing spaces not allowed
it('hx-disabled-elt supports multiple extended selectors', function() {
this.server.respondWith('GET', '/test', 'Clicked!')
var form = make('<form hx-get="/test" hx-disabled-elt="find input[type=\'text\'], find button" hx-swap="none"><input id="i1" type="text" placeholder="Type here..."><button id="b2" type="submit">Send</button></form>')
var i1 = byId('i1')
var b2 = byId('b2')

i1.hasAttribute('disabled').should.equal(false)
b2.hasAttribute('disabled').should.equal(false)

b2.click()
i1.hasAttribute('disabled').should.equal(true)
b2.hasAttribute('disabled').should.equal(true)

this.server.respond()

i1.hasAttribute('disabled').should.equal(false)
b2.hasAttribute('disabled').should.equal(false)
})

it('closest/find/next/previous handle nothing to find without exception', function() {
this.server.respondWith('GET', '/test', 'Clicked!')
var btn1 = make('<button hx-get="/test" hx-disabled-elt="closest input">Click Me!</button>')
var btn2 = make('<button hx-get="/test" hx-disabled-elt="find input">Click Me!</button>')
var btn3 = make('<button hx-get="/test" hx-disabled-elt="next input">Click Me!</button>')
var btn4 = make('<button hx-get="/test" hx-disabled-elt="previous input">Click Me!</button>')
btn1.click()
btn1.hasAttribute('disabled').should.equal(false)
this.server.respond()
btn2.click()
btn2.hasAttribute('disabled').should.equal(false)
this.server.respond()
btn3.click()
btn3.hasAttribute('disabled').should.equal(false)
this.server.respond()
btn4.click()
btn4.hasAttribute('disabled').should.equal(false)
this.server.respond()
})
})
68 changes: 68 additions & 0 deletions test/attributes/hx-include.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,4 +224,72 @@ describe('hx-include attribute', function() {
this.server.respond()
btn.innerHTML.should.equal('Clicked!')
})

it('Multiple extended selectors can be used in hx-include', function() {
this.server.respondWith('POST', '/include', function(xhr) {
var params = getParameters(xhr)
params.i1.should.equal('test')
params.i2.should.equal('foo')
params.i3.should.equal('bar')
params.i4.should.equal('test2')
xhr.respond(200, {}, 'Clicked!')
})
make('<input name="i4" value="test2" id="i4"/>' +
'<div id="i">' +
'<input name="i1" value="test"/>' +
'<input name="i2" value="foo"/>' +
'<button id="btn" hx-post="/include" hx-include="closest div, next input, #i4"></button>' +
'</div>' +
'<input name="i3" value="bar"/>')
var btn = byId('btn')
btn.click()
this.server.respond()
btn.innerHTML.should.equal('Clicked!')
})

it('hx-include processes extended selector in between standard selectors', function() {
this.server.respondWith('POST', '/include', function(xhr) {
var params = getParameters(xhr)
params.i1.should.equal('test')
should.equal(params.i2, undefined)
params.i3.should.equal('bar')
params.i4.should.equal('test2')
xhr.respond(200, {}, 'Clicked!')
})
make('<input name="i4" value="test2" id="i4"/>' +
'<div id="i">' +
'<input name="i1" value="test" id="i1"/>' +
'<input name="i2" value="foo"/>' +
'<button id="btn" hx-post="/include" hx-include="#i1, next input, #i4"></button>' +
'</div>' +
'<input name="i3" value="bar"/>')
var btn = byId('btn')
btn.click()
this.server.respond()
btn.innerHTML.should.equal('Clicked!')
})

it('hx-include processes nested standard selectors correctly', function() {
this.server.respondWith('POST', '/include', function(xhr) {
var params = getParameters(xhr)
params.i1.should.equal('test')
params.i2.should.equal('foo')
params.i3.should.equal('bar')
should.equal(params.i4, undefined)
should.equal(params.i5, undefined)
xhr.respond(200, {}, 'Clicked!')
})
make('<input name="i4" value="test2" id="i4"/>' +
'<div id="i">' +
'<input name="i1" value="test" id="i1"/>' +
'<input name="i2" value="foo"/>' +
'<input name="i5" value="test"/>' +
'<button id="btn" hx-post="/include" hx-include="next input, #i > :is([name=\'i1\'], [name=\'i2\'])"></button>' +
'</div>' +
'<input name="i3" value="bar"/>')
var btn = byId('btn')
btn.click()
this.server.respond()
btn.innerHTML.should.equal('Clicked!')
})
})
20 changes: 20 additions & 0 deletions test/attributes/hx-trigger.js
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,26 @@ describe('hx-trigger attribute', function() {
div1.innerHTML.should.equal('Requests: 2')
})

it('from clause works with multiple extended selectors', function() {
var requests = 0
this.server.respondWith('GET', '/test', function(xhr) {
requests++
xhr.respond(200, {}, 'Requests: ' + requests)
})
make('<button id="btn" type="button">Click me</button>' +
'<div hx-trigger="click from:(previous button, next a)" hx-target="#a1" hx-get="/test"></div>' +
'<a id="a1">Requests: 0</a>')
var btn = byId('btn')
var a1 = byId('a1')
a1.innerHTML.should.equal('Requests: 0')
btn.click()
this.server.respond()
a1.innerHTML.should.equal('Requests: 1')
a1.click()
this.server.respond()
a1.innerHTML.should.equal('Requests: 2')
})

it('event listeners can filter on target', function() {
var requests = 0
this.server.respondWith('GET', '/test', function(xhr) {
Expand Down
22 changes: 22 additions & 0 deletions test/core/shadowdom.js
Original file line number Diff line number Diff line change
Expand Up @@ -1313,4 +1313,26 @@ describe('Core htmx Shadow DOM Tests', function() {
window.foo.should.equal(true)
delete window.foo
})

it('global selector modifier does not apply to previous selectors', function() {
this.server.respondWith('POST', '/include', function(xhr) {
var params = getParameters(xhr)
params.i1.should.equal('test')
should.equal(params.i2, undefined)
params.i3.should.equal('bar')
params.i4.should.equal('test2')
xhr.respond(200, {}, 'Clicked!')
})
make('<div>' +
'<input id="i1" name="i1" value="test"/>' +
'<input name="i2" value="foo"/>' +
'<button id="btn" hx-post="/include" hx-include="#i1, next input, global #i4"></button>' +
'</div>' +
'<input name="i3" value="bar"/>')
getWorkArea().innerHTML += '<input name="i4" value="test2" id="i4"/>'
var btn = byId('btn')
btn.click()
this.server.respond()
btn.innerHTML.should.equal('Clicked!')
})
})

0 comments on commit 4466697

Please sign in to comment.