Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 21 additions & 20 deletions src/htmx.js
Original file line number Diff line number Diff line change
Expand Up @@ -555,9 +555,9 @@ var htmx = (() => {
) !== "run") return

ctx.status = "issuing"
ctx.indicators ??= [];
ctx.disabledElements ??= [];

let indicators = [];
let disableElements = [];
try {
// Handle confirmation
if (ctx.confirm) {
Expand All @@ -573,8 +573,8 @@ var htmx = (() => {

// initialize timeout & indicators after confirmation
this.__initTimeout(ctx);
indicators = this.__showIndicators(elt);
disableElements = this.__disableElements(elt);
this.__showIndicators(ctx);
this.__disableElements(ctx);

ctx.fetch ||= window.fetch.bind(window)
// Set HX-Request-Type based on final target/select (after all modifications)
Expand Down Expand Up @@ -625,8 +625,8 @@ var htmx = (() => {
}
this.__trigger(elt, "htmx:finally:request", {ctx})
if (!ctx.keepIndicators) {
this.__hideIndicators(indicators);
this.__enableElements(disableElements);
this.__hideIndicators(ctx.indicators);
this.__enableElements(ctx.disabledElements);
}

requestQueue.continue(abortRequest)
Expand Down Expand Up @@ -664,6 +664,8 @@ var htmx = (() => {
delete opts.path;
}
if (opts.push == null && opts.replace == null) opts.push = 'true';
opts.indicators = ctx.indicators;
opts.disabledElements = ctx.disabledElements;
this.ajax('GET', path, opts);
return true
}
Expand Down Expand Up @@ -1733,21 +1735,22 @@ var htmx = (() => {
}
}

__showIndicators(elt) {
__showIndicators(ctx) {
let elt = ctx.sourceElement;
let hxIndicator = this.__attributeValue(elt, "hx-indicator");
let indicatorElements;
if (!hxIndicator) {
if (elt === document.body) return [];
indicatorElements = [elt]
if (elt === document.body) return;
indicatorElements = [elt];
} else {
indicatorElements = this.__findAllExt(elt, hxIndicator, "hx-indicator");
}
for (const indicator of indicatorElements) {
ctx.indicators.push(indicator);
let s = this.__htmxState(indicator);
s.rc = (s.rc || 0) + 1;
this.__addClass(indicator, this.config.requestClass)
this.__addClass(indicator, this.config.requestClass);
}
return indicatorElements
}

__hideIndicators(indicatorElements) {
Expand All @@ -1760,18 +1763,16 @@ var htmx = (() => {
}
}

__disableElements(elt) {
__disableElements(ctx) {
let elt = ctx.sourceElement;
let hxDisable = this.__attributeValue(elt, "hx-disable");
let disabledElements = []
if (hxDisable) {
disabledElements = this.__findAllExt(elt, hxDisable, "hx-disable");
for (let indicator of disabledElements) {
let s = this.__htmxState(indicator);
if (!hxDisable) return;
for (let el of this.__findAllExt(elt, hxDisable, "hx-disable")) {
ctx.disabledElements.push(el);
let s = this.__htmxState(el);
s.dc = (s.dc || 0) + 1;
indicator.disabled = true
}
el.disabled = true;
}
return disabledElements
}

__enableElements(disabledElements) {
Expand Down
105 changes: 67 additions & 38 deletions test/tests/unit/__disableEnableElements.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ describe('__disableElements / __enableElements unit tests', function() {
cleanupTest();
});

function makeCtx(elt) {
return { sourceElement: elt, indicators: [], disabledElements: [] };
}

it('disables element', function () {
let container = createProcessedHTML('<div hx-disable=".disable-me"><button class="disable-me"></button></div>')
let button = container.querySelector('button')

htmx.__disableElements(container)
htmx.__disableElements(makeCtx(container))

assert.isTrue(button.disabled)
})
Expand All @@ -21,8 +25,9 @@ describe('__disableElements / __enableElements unit tests', function() {
let container = createProcessedHTML('<div hx-disable=".disable-me"><button class="disable-me"></button></div>')
let button = container.querySelector('button')

let elements = htmx.__disableElements(container)
htmx.__enableElements(elements)
let ctx = makeCtx(container)
htmx.__disableElements(ctx)
htmx.__enableElements(ctx.disabledElements)

assert.isFalse(button.disabled)
})
Expand All @@ -31,8 +36,8 @@ describe('__disableElements / __enableElements unit tests', function() {
let container = createProcessedHTML('<div hx-disable=".disable-me"><button class="disable-me"></button></div>')
let button = container.querySelector('button')

htmx.__disableElements(container)
htmx.__disableElements(container)
htmx.__disableElements(makeCtx(container))
htmx.__disableElements(makeCtx(container))

assert.equal(htmx.__htmxState(button).dc, 2)
assert.isTrue(button.disabled)
Expand All @@ -42,9 +47,11 @@ describe('__disableElements / __enableElements unit tests', function() {
let container = createProcessedHTML('<div hx-disable=".disable-me"><button class="disable-me"></button></div>')
let button = container.querySelector('button')

let elements1 = htmx.__disableElements(container)
let elements2 = htmx.__disableElements(container)
htmx.__enableElements(elements1)
let ctx1 = makeCtx(container)
let ctx2 = makeCtx(container)
htmx.__disableElements(ctx1)
htmx.__disableElements(ctx2)
htmx.__enableElements(ctx1.disabledElements)

assert.equal(htmx.__htmxState(button).dc, 1)
assert.isTrue(button.disabled)
Expand All @@ -54,10 +61,12 @@ describe('__disableElements / __enableElements unit tests', function() {
let container = createProcessedHTML('<div hx-disable=".disable-me"><button class="disable-me"></button></div>')
let button = container.querySelector('button')

let elements1 = htmx.__disableElements(container)
let elements2 = htmx.__disableElements(container)
htmx.__enableElements(elements1)
htmx.__enableElements(elements2)
let ctx1 = makeCtx(container)
let ctx2 = makeCtx(container)
htmx.__disableElements(ctx1)
htmx.__disableElements(ctx2)
htmx.__enableElements(ctx1.disabledElements)
htmx.__enableElements(ctx2.disabledElements)

assert.isFalse(button.disabled)
assert.isUndefined(htmx.__htmxState(button).dc)
Expand All @@ -68,7 +77,7 @@ describe('__disableElements / __enableElements unit tests', function() {
let button = container.querySelector('button')
let input = container.querySelector('input')

htmx.__disableElements(container)
htmx.__disableElements(makeCtx(container))

assert.isTrue(button.disabled)
assert.isTrue(input.disabled)
Expand All @@ -78,15 +87,15 @@ describe('__disableElements / __enableElements unit tests', function() {
let container = createProcessedHTML('<div><button class="disable-me"></button></div>')
let button = container.querySelector('button')

htmx.__disableElements(container)
htmx.__disableElements(makeCtx(container))

assert.isFalse(button.disabled)
})

it('includes element itself', function () {
let button = createProcessedHTML('<button hx-disable=".disable-me" class="disable-me"></button>')

htmx.__disableElements(button)
htmx.__disableElements(makeCtx(button))

assert.isTrue(button.disabled)
})
Expand All @@ -106,12 +115,13 @@ describe('__disableElements / __enableElements unit tests', function() {
let outer = container
let inner = container.querySelector('button')

let elements = htmx.__disableElements(container)
let ctx = makeCtx(container)
htmx.__disableElements(ctx)

assert.isTrue(outer.disabled)
assert.isTrue(inner.disabled)

htmx.__enableElements(elements)
htmx.__enableElements(ctx.disabledElements)

assert.isFalse(outer.disabled)
assert.isFalse(inner.disabled)
Expand All @@ -122,63 +132,82 @@ describe('__disableElements / __enableElements unit tests', function() {
let button = container.querySelector('button')
let input = container.querySelector('input')

htmx.__disableElements(button.parentElement)
htmx.__disableElements(makeCtx(button.parentElement))
button.parentElement.setAttribute('hx-disable', 'button.disable-me')
htmx.__disableElements(button.parentElement)
htmx.__disableElements(makeCtx(button.parentElement))

assert.equal(htmx.__htmxState(button).dc, 2)
assert.equal(htmx.__htmxState(input).dc, 1)
})

it('resolves this selector for disable', function () {
let container = createProcessedHTML('<button hx-disable="this" hx-get="/test"></button>');

let elements = htmx.__disableElements(container);


let ctx = makeCtx(container)
htmx.__disableElements(ctx);

assert.isTrue(container.disabled);
assert.equal(elements.length, 1);
assert.equal(elements[0], container);
assert.equal(ctx.disabledElements.length, 1);
assert.equal(ctx.disabledElements[0], container);
})

it('resolves this selector with inherited disable', function () {
let container = createProcessedHTML('<button hx-disable:inherited="this"><span hx-get="/test"></span></button>');
let span = container.querySelector('span');
let elements = htmx.__disableElements(span);

htmx.__disableElements(makeCtx(span));

assert.isTrue(container.disabled);
})

it('resolves this selector respecting disable override', function () {
let html = '<button hx-disable="this"><span hx-disable=".other"><input hx-get="/test"></span></button>';
let outer = createProcessedHTML(html);
let input = outer.querySelector('input');

let elements = htmx.__disableElements(input);


let ctx = makeCtx(input)
htmx.__disableElements(ctx);

assert.isFalse(outer.disabled);
assert.equal(elements.length, 0);
assert.equal(ctx.disabledElements.length, 0);
})

it('resolves this selector with append for disable', function () {
let html = '<button hx-disable:inherited="this"><input hx-disable:append="this" hx-get="/test"></button>';
let outer = createProcessedHTML(html);
let inner = outer.querySelector('input');

let elements = htmx.__disableElements(inner);

assert.equal(elements.length, 2);

let ctx = makeCtx(inner)
htmx.__disableElements(ctx);

assert.equal(ctx.disabledElements.length, 2);
assert.isTrue(inner.disabled);
assert.isTrue(outer.disabled);
})

it('resolves this selector with comma-separated disable values', function () {
let html = '<button hx-disable="this, .other" hx-get="/test"></button>';
let button = createProcessedHTML(html);
let elements = htmx.__disableElements(button);

htmx.__disableElements(makeCtx(button));

assert.isTrue(button.disabled);
})

it('disabled elements are carried through an hx-location chain', async function() {
mockResponse('GET', '/first', '', { headers: { 'HX-Location': '{"path":"/second","target":"#dest"}' } })
mockResponse('GET', '/second', 'Done')
createProcessedHTML(
'<div id="dest"></div>' +
'<button id="btn" hx-get="/first" hx-disable="this" hx-target="#dest">Click</button>'
)
find('#btn').click()
await forRequest() // /first
assert.isTrue(find('#btn').disabled, 'button should still be disabled during chain')
await forRequest() // /second
await htmx.timeout(10)
assert.isFalse(find('#btn').disabled, 'button should be re-enabled after chain completes')
assert.equal(find('#dest').textContent, 'Done')
})

});
8 changes: 6 additions & 2 deletions test/tests/unit/__handleHxHeadersAndMaybeReturnEarly.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ describe('__handleHxHeadersAndMaybeReturnEarly unit tests', function() {
let result = htmx.__handleHeadersAndMaybeReturnEarly({hx: {location: '/files/a,b'}})

assert.isTrue(result)
assert.deepEqual(request, ['GET', '/files/a,b', {push: 'true'}])
assert.equal(request[0], 'GET')
assert.equal(request[1], '/files/a,b')
assert.equal(request[2].push, 'true')
} finally {
htmx.ajax = originalAjax
}
Expand All @@ -64,7 +66,9 @@ describe('__handleHxHeadersAndMaybeReturnEarly unit tests', function() {
let result = htmx.__handleHeadersAndMaybeReturnEarly({hx: {location: 'path:/search'}})

assert.isTrue(result)
assert.deepEqual(request, ['GET', '/search', {push: 'true'}])
assert.equal(request[0], 'GET')
assert.equal(request[1], '/search')
assert.equal(request[2].push, 'true')
} finally {
htmx.ajax = originalAjax
}
Expand Down
Loading
Loading