|
async __issueRequest(ctx) { |
|
let elt = ctx.sourceElement |
|
let syncStrategy = this.__determineSyncStrategy(elt); |
|
let requestQueue = this.__getRequestQueue(elt); |
|
|
|
if (!requestQueue.issue(ctx, syncStrategy)) return |
|
|
|
ctx.status = "issuing" |
|
|
|
let indicators = []; |
|
let disableElements = []; |
|
try { |
|
// Handle confirmation |
|
if (ctx.confirm) { |
|
let confirmed = await new Promise(resolve => { |
|
let detail = {ctx, issueRequest: () => resolve(true), dropRequest: () => resolve(false)}; |
|
if (this.__trigger(elt, "htmx:confirm", detail)) { |
|
let js = this.__extractJavascriptContent(ctx.confirm); |
|
resolve(js ? this.__executeJavaScript(elt, {ctx}, js, true) : window.confirm(ctx.confirm)); |
|
} |
|
}); |
|
if (!confirmed) return; |
|
} |
|
|
|
// initialize timeout & indicators after confirmation |
|
this.__initTimeout(ctx); |
|
indicators = this.__showIndicators(elt); |
|
disableElements = this.__disableElements(elt); |
|
|
|
ctx.fetch ||= window.fetch.bind(window) |
|
if (!this.__trigger(elt, "htmx:before:request", {ctx})) return; |
|
|
|
let response = await ctx.fetch(ctx.request.action, ctx.request); |
|
|
|
ctx.response = { |
|
raw: response, |
|
status: response.status, |
|
headers: response.headers, |
|
} |
|
this.__extractHxHeaders(ctx); |
|
if (!this.__trigger(elt, "htmx:before:response", {ctx})) return; |
|
ctx.text = await response.text(); |
|
if (!this.__trigger(elt, "htmx:after:request", {ctx})) return; |
|
|
|
if (ctx.response.status >= 400) { |
|
this.__trigger(elt, "htmx:response:error", {ctx}) |
|
} |
|
|
|
if(this.__handleHeadersAndMaybeReturnEarly(ctx)){ |
|
ctx.keepIndicators = true; |
|
return |
|
} |
|
|
|
if (ctx.status === "issuing") { |
|
if (ctx.hx.retarget) ctx.target = ctx.hx.retarget; // HX-Retarget |
|
if (ctx.hx.reswap) ctx.swap = ctx.hx.reswap; // HX-Reswap |
|
if (ctx.hx.reselect) ctx.select = ctx.hx.reselect; // HX-Reselect |
|
ctx.status = "response received"; |
|
this.__handleStatusCodes(ctx); |
|
await this.swap(ctx); |
|
ctx.status = "swapped"; |
|
} |
|
|
|
} catch (error) { |
|
ctx.status = "error: " + error; |
|
this.__trigger(elt, "htmx:error", {ctx, error}) |
|
} finally { |
|
clearTimeout(ctx.requestTimeout); |
|
if (ctx.hx?.trigger) { // HX-Trigger |
|
this.__handleTriggerHeader(ctx.hx.trigger, ctx.sourceElement); |
|
} |
|
this.__trigger(elt, "htmx:finally:request", {ctx}) |
|
if (!ctx.keepIndicators) { |
|
this.__hideIndicators(indicators); |
|
this.__enableElements(disableElements); |
|
} |
|
|
|
requestQueue.finish() |
|
if (requestQueue.more()) { |
|
// intentionally not awaited — __issueRequest has its own try/catch |
|
this.__issueRequest(requestQueue.next()) |
|
} |
|
} |
|
} |
In Four,
hx-disablecontrols remain disabled throughout__issueRequestwith no event fired after they are re-enabled. The issue-request sequence is roughly:htmx:before:requesthtmx:before:responsehtmx:after:requesthtmx:finally:requesthtmx/src/htmx.js
Lines 579 to 662 in 85b8378
hx-disableexists to prevent the user from interacting with the form while a request is in flight. One obvious thing one might want to do after the request is completed is re-focus the form element so the user can continue, say, typing in todo list items.Without any event after the disabled status is removed, this kind of workflow becomes awkward to implement:
It would be nice if there were some event that fired after the elements are re-enabled.
A few ideas:
htmx:finally:requestevent. This would be my preference; IMO "finally" should be fired after all default request behavior is completed.htmx:finallyfinally:request(joke)hx-disable-specific event(s):htmx:{before,after}:{disable,reenable}. Maybe justhtmx:after:reenableRelated issue: #3363