mirror of
https://github.com/bigskysoftware/htmx.git
synced 2026-01-25 05:06:13 +00:00
Handle not preventing link when inside htmx enabled element (#3396)
* Handle not preventing link when inside htmx enabled element * Simplify shouldCancel and pass in eltToListenOn to solve from: issue without regressions * move regex to local variable format
This commit is contained in:
29
src/htmx.js
29
src/htmx.js
@@ -2431,23 +2431,22 @@ var htmx = (function() {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function shouldCancel(evt, elt) {
|
||||
if (evt.type === 'submit' || evt.type === 'click') {
|
||||
// use elt from event that was submitted/clicked where possible to determining if default form/link behavior should be canceled
|
||||
elt = asElement(evt.target) || elt
|
||||
if (elt.tagName === 'FORM') {
|
||||
return true
|
||||
}
|
||||
// find button wrapping the event elt
|
||||
const btn = elt.closest('input[type="submit"], button')
|
||||
// @ts-ignore Do not cancel on buttons that 1) don't have a related form or 2) have a type attribute of 'reset'/'button'.
|
||||
// The properties will resolve to undefined for elements that don't define 'type' or 'form', which is fine
|
||||
if (evt.type === 'submit' && elt.tagName === 'FORM') {
|
||||
return true
|
||||
} else if (evt.type === 'click') {
|
||||
// find button wrapping the trigger element
|
||||
const btn = /** @type {HTMLButtonElement|HTMLInputElement|null} */ (elt.closest('input[type="submit"], button'))
|
||||
// Do not cancel on buttons that 1) don't have a related form or 2) have a type attribute of 'reset'/'button'.
|
||||
if (btn && btn.form && btn.type === 'submit') {
|
||||
return true
|
||||
}
|
||||
elt = elt.closest('a')
|
||||
// @ts-ignore check for a link wrapping the event elt or if elt is a link. elt will be link so href check is fine
|
||||
if (elt && elt.href &&
|
||||
(elt.getAttribute('href') === '#' || elt.getAttribute('href').indexOf('#') !== 0)) {
|
||||
|
||||
// find link wrapping the trigger element
|
||||
const link = elt.closest('a')
|
||||
// Allow links with href="#fragment" (anchors with content after #) to perform normal fragment navigation.
|
||||
// Cancel default action for links with href="#" (bare hash) to prevent scrolling to top and unwanted URL changes.
|
||||
const samePageAnchor = /^#.+/
|
||||
if (link && link.href && !samePageAnchor.test(link.getAttribute('href'))) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -2524,7 +2523,7 @@ var htmx = (function() {
|
||||
if (ignoreBoostedAnchorCtrlClick(elt, evt)) {
|
||||
return
|
||||
}
|
||||
if (explicitCancel || shouldCancel(evt, elt)) {
|
||||
if (explicitCancel || shouldCancel(evt, eltToListenOn)) {
|
||||
evt.preventDefault()
|
||||
}
|
||||
if (maybeFilterEvent(triggerSpec, elt, evt)) {
|
||||
|
||||
@@ -98,13 +98,8 @@ describe('Core htmx internals Tests', function() {
|
||||
|
||||
var form = make('<form></form>')
|
||||
htmx._('shouldCancel')({ type: 'submit', target: form }, form).should.equal(true)
|
||||
htmx._('shouldCancel')({ type: 'click', target: form }, form).should.equal(true)
|
||||
|
||||
// falls back to check elt tag when target is not an element
|
||||
htmx._('shouldCancel')({ type: 'click', target: null }, form).should.equal(true)
|
||||
|
||||
// check that events targeting elements that shouldn't cancel don't cancel
|
||||
htmx._('shouldCancel')({ type: 'submit', target: anchorThatShouldNotCancel }, form).should.equal(false)
|
||||
htmx._('shouldCancel')({ type: 'click', target: divThatShouldNotCancel }, form).should.equal(false)
|
||||
|
||||
// check elements inside links getting click events should cancel parent links
|
||||
@@ -112,6 +107,11 @@ describe('Core htmx internals Tests', function() {
|
||||
htmx._('shouldCancel')({ type: 'click', target: anchorWithButton.firstChild }, anchorWithButton).should.equal(true)
|
||||
htmx._('shouldCancel')({ type: 'click', target: anchorWithButton.firstChild }, anchorWithButton.firstChild).should.equal(true)
|
||||
|
||||
// check that links inside htmx elements should not cancel
|
||||
var divWithLink = make("<div hx-get='/data'><a href='/page'>Link</a></div>")
|
||||
var link = divWithLink.querySelector('a')
|
||||
htmx._('shouldCancel')({ type: 'click', target: link }, divWithLink).should.equal(false)
|
||||
|
||||
form = make('<form id="f1">' +
|
||||
'<input id="insideInput" type="submit">' +
|
||||
'<button id="insideFormBtn"></button>' +
|
||||
|
||||
@@ -100,11 +100,11 @@ describe('Core htmx Regression Tests', function() {
|
||||
it('does not submit with a false condition on a form', function() {
|
||||
this.server.respondWith('POST', '/test', 'Submitted')
|
||||
var defaultPrevented = false
|
||||
htmx.on('click', function(evt) {
|
||||
htmx.on('submit', function(evt) {
|
||||
defaultPrevented = evt.defaultPrevented
|
||||
})
|
||||
var form = make('<form hx-post="/test" hx-trigger="click[false]"></form>')
|
||||
form.click()
|
||||
var form = make('<form hx-post="/test" hx-trigger="submit[false]"><button id="b1">submit</button></form>')
|
||||
byId('b1').click()
|
||||
this.server.respond()
|
||||
defaultPrevented.should.equal(true)
|
||||
})
|
||||
@@ -385,6 +385,93 @@ describe('Core htmx Regression Tests', function() {
|
||||
span.click()
|
||||
})
|
||||
|
||||
it('a htmx enabled element inside a form button will prevent the button submitting a form', function(done) {
|
||||
var defaultPrevented = 'unset'
|
||||
var form = make('<form><button><span hx-get="/foo">test</span></button></form>')
|
||||
var button = form.firstChild
|
||||
var span = button.firstChild
|
||||
|
||||
htmx.on(button, 'click', function(evt) {
|
||||
// we need to wait so the state of the evt is finalized
|
||||
setTimeout(() => {
|
||||
defaultPrevented = evt.defaultPrevented
|
||||
try {
|
||||
defaultPrevented.should.equal(true)
|
||||
done()
|
||||
} catch (err) {
|
||||
done(err)
|
||||
}
|
||||
}, 0)
|
||||
})
|
||||
|
||||
span.click()
|
||||
})
|
||||
|
||||
it('from: trigger on form prevents default form submission', function(done) {
|
||||
var defaultPrevented = 'unset'
|
||||
var form = make('<form id="test-form" action="/submit"><input type="submit" value="Submit"></form>')
|
||||
var div = make('<div hx-post="/test" hx-trigger="submit from:#test-form"></div>')
|
||||
var submitBtn = form.firstChild
|
||||
|
||||
htmx.on(form, 'submit', function(evt) {
|
||||
defaultPrevented = evt.defaultPrevented // Capture state before our preventDefault
|
||||
evt.preventDefault() // Prevent navigation in case test fails
|
||||
setTimeout(() => {
|
||||
try {
|
||||
defaultPrevented.should.equal(true)
|
||||
done()
|
||||
} catch (err) {
|
||||
done(err)
|
||||
}
|
||||
}, 0)
|
||||
})
|
||||
|
||||
submitBtn.click()
|
||||
})
|
||||
|
||||
it('from: trigger on button prevents default form submission', function(done) {
|
||||
var defaultPrevented = 'unset'
|
||||
var form = make('<form><button id="test-btn" type="submit">Submit</button></form>')
|
||||
var div = make('<div hx-post="/test" hx-trigger="click from:#test-btn"></div>')
|
||||
var button = byId('test-btn')
|
||||
|
||||
htmx.on(button, 'click', function(evt) {
|
||||
defaultPrevented = evt.defaultPrevented // Capture state before our preventDefault
|
||||
evt.preventDefault() // Prevent form submission in case test fails
|
||||
setTimeout(() => {
|
||||
try {
|
||||
defaultPrevented.should.equal(true)
|
||||
done()
|
||||
} catch (err) {
|
||||
done(err)
|
||||
}
|
||||
}, 0)
|
||||
})
|
||||
|
||||
button.click()
|
||||
})
|
||||
|
||||
it('from: trigger on link prevents default navigation', function(done) {
|
||||
var defaultPrevented = 'unset'
|
||||
var link = make('<a id="test-link" href="/page">Go to page</a>')
|
||||
var div = make('<div hx-get="/test" hx-trigger="click from:#test-link"></div>')
|
||||
|
||||
htmx.on(link, 'click', function(evt) {
|
||||
defaultPrevented = evt.defaultPrevented // Capture state before our preventDefault
|
||||
evt.preventDefault() // Prevent navigation in case test fails
|
||||
setTimeout(() => {
|
||||
try {
|
||||
defaultPrevented.should.equal(true)
|
||||
done()
|
||||
} catch (err) {
|
||||
done(err)
|
||||
}
|
||||
}, 0)
|
||||
})
|
||||
|
||||
link.click()
|
||||
})
|
||||
|
||||
it('check deleting button during click does not trigger exception error in getRelatedFormData when button can no longer find form', function() {
|
||||
var defaultPrevented = 'unset'
|
||||
var form = make('<form><button>delete</button></form>')
|
||||
|
||||
Reference in New Issue
Block a user