Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Static template that doesn't parse the response #3082

Open
thenewguy opened this issue Dec 17, 2024 · 10 comments
Open

Static template that doesn't parse the response #3082

thenewguy opened this issue Dec 17, 2024 · 10 comments

Comments

@thenewguy
Copy link

thenewguy commented Dec 17, 2024

With a current use case, the response from the POST isn't important - the side effect is handled by path-deps and that handles the re-render. It is only important to let the user know there if was an error.

As such, it would be helpful to be able to provide a static template block that does not parse the response.

For example:

<div id="hidden" style="display:none"></div>
<div id="unrecoverable-error"></div>
<template id="unrecoverable-error-template">Some static message explaining that an unknown error was encountered.</template>
<form 
	hx-patch="/endpoint"
	hx-target="#hidden"
	hx-target-error="#unhandled-error"
	handlebars-template="unhandled-error-template"
	hx-on-htmx-before-request="$('#unhandled-error').empty()"
>
	<input type="hidden" name="some-trigger-key" value="1">
	<button type="submit">Submit</button>
</form>

Using the available template engines causes a Parsing error in Javascript when JSON isn't returned by a 500 error and htmx falls back to inserting the response into "#unhandled-error" instead of the static message.

Being able to force the static message to display would be very beneficial.

@Telroshan
Copy link
Collaborator

As you linked the v1 docs, I suppose you're using htmx v1 and not v2 here ?
Looking at the code, I suppose you're using the response targets extension too along client-side-templates?

@scrhartley
Copy link
Contributor

Is the mismatch between ids relevant (unrecoverable vs. unhandled)?
<template id="unrecoverable-error-template"> vs. handlebars-template="unhandled-error-template"
and
<div id="unrecoverable-error"></div> vs hx-target-error="#unhandled-error" and $('#unhandled-error').empty()

@thenewguy
Copy link
Author

Is the mismatch between ids relevant (unrecoverable vs. unhandled)? <template id="unrecoverable-error-template"> vs. handlebars-template="unhandled-error-template" and <div id="unrecoverable-error"></div> vs hx-target-error="#unhandled-error" and $('#unhandled-error').empty()

No - sorry that is a copy paste error from reformatting the real markup to be easier to read for the example.

What happens here is the server returns a response with an error status code. HTMX produces a Json Decoding error because the error response isn't formatted with JSON. And then HTMX inserts the server response instead of the static message from the template.

@scrhartley
Copy link
Contributor

scrhartley commented Dec 18, 2024

htmx:beforeSwap has currently undocumented isError and serverResponse properties.

Perhaps you could add something like:
hx-on-htmx-before-swap="if (event.detail.isError) event.detail.serverResponse = '{}'"

@thenewguy
Copy link
Author

htmx:beforeSwap has currently undocumented isError and serverResponse properties.

Perhaps you could add something like: hx-on-htmx-before-swap="if (event.detail.isError) event.detail.serverResponse = '{}'"

Awesome! Thanks!

@thenewguy
Copy link
Author

htmx:beforeSwap has currently undocumented isError and serverResponse properties.

Perhaps you could add something like: hx-on-htmx-before-swap="if (event.detail.isError) event.detail.serverResponse = '{}'"

I tried this:

<form 
	hx-patch="/placeholder/url"
	hx-target="#devnull"
	hx-target-error="#unhandled-error"
	handlebars-template="unhandled-error-template"
	hx-on-htmx-before-request="$('#unhandled-error').empty()"
	hx-on-htmx-before-swap="if (event.detail.isError) event.detail.serverResponse = '{}'"
>

It did not solve the issue. Note that it seems to work without hx-on-htmx-before-swap="if (event.detail.isError) event.detail.serverResponse = '{}'" for 4XX errors. I am observing this issue with a 500 error. Is error 500 significant here?

@scrhartley
Copy link
Contributor

scrhartley commented Dec 19, 2024

According to docs, default handling is:

responseHandling: [
    {code:"204", swap: false},   // 204 - No Content by default does nothing, but is not an error
    {code:"[23]..", swap: true}, // 200 & 300 responses are non-errors and are swapped
    {code:"[45]..", swap: false, error:true}, // 400 & 500 responses are not swapped and are errors
    {code:"...", swap: false}    // catch all for any other response code
]

So there should be no difference between 4xx and 5xx errors.

The response-targets extension can change the isError value, so possibly it's always setting it to false:

isError flag on the detail member of an event associated with swapping the content with hx-target-[CODE] will be set to false when error response code is received. This is different from the default behavior. You may change this by setting a configuration flag htmx.config.responseTargetUnsetsError to false (default is true).

Possibly also need to set event.detail.shouldSwap to true, although this may already be handled by the response-targets extension.

You can get value event.detail.xhr.status for response code number.

Here is how htmx matches status code:

function codeMatches(responseHandlingConfig, status) {
    var regExp = new RegExp(responseHandlingConfig.code)
    return regExp.test(status.toString(10))
}

@Telroshan
Copy link
Collaborator

Note that it seems to work without hx-on-htmx-before-swap="if (event.detail.isError) event.detail.serverResponse = '{}'" for 4XX errors. I am observing this issue with a 500 error. Is error 500 significant here?

As you said earlier @thenewguy ,

HTMX produces a Json Decoding error because the error response isn't formatted with JSON

So the issue is not the error code, but rather that the response isn't JSON.
The client-side-templates indeed expects the response to be JSON, from what I understand, as contextual data to pass to the templating engine (I never used this extension, so I hope I get this right)

var handlebarsTemplate = htmx.closest(elt, "[handlebars-template]");
if (handlebarsTemplate) {
var data = JSON.parse(text);

return renderTemplate(data);

If your server properly answers with a JSON on a 4xx error, then it works as expected, if it doesn't answer with JSON (error 5xx in your case) then it can't.
If you were to simply add a try catch here and avoid throwing an error, it would likely not solve your usecase as the extension would simply return the unmodified text, that htmx would then insert instead of the static template, giving the same result that you're seeing, simply without the error in the console.
As so, I think it's a server-response issue that we can't expect the extension to manage on its own, as it's doing what is advertised.

What to do

I think you have to make it a proper JSON in any case, be it by making sure your server returns a JSON even on a 5xx error, or by adding some custom client-side code to replace the server response if it is not valid JSON.
By default, if (event.detail.isError) will be true for any error code, though what you want to check here instead is likely whether the response text is valid JSON or not (if it is valid JSON, you likely want to pass that JSON to the templating engine rather than an empty one, which you likely only want as a fallback).
You can probably handle this with a try catch trying to JSON.parse the answer ; if you end up in the catch block, the answer is not valid JSON, then you replace it with an empty object {}.
Note that the response-targets extension can reset isError depending on htmx.config.responseTargetUnsetsError that it introduces, see its documentation about that ; as @scrhartley said, you can then rely on event.detail.xhr.status to check if the code is >= 400 yourself.

Hope this helps!

PS: as you're using htmx 1, make sure to use the V1 docs which doesn't include responseHandling that is a htmx 2 feature
PS2: if you can and don't need IE11 support, I strongly encourage upgrading to htmx2

@thenewguy
Copy link
Author

Note that it seems to work without hx-on-htmx-before-swap="if (event.detail.isError) event.detail.serverResponse = '{}'" for 4XX errors. I am observing this issue with a 500 error. Is error 500 significant here?

As you said earlier @thenewguy ,

HTMX produces a Json Decoding error because the error response isn't formatted with JSON

So the issue is not the error code, but rather that the response isn't JSON. The client-side-templates indeed expects the response to be JSON, from what I understand, as contextual data to pass to the templating engine (I never used this extension, so I hope I get this right)

var handlebarsTemplate = htmx.closest(elt, "[handlebars-template]");
if (handlebarsTemplate) {
var data = JSON.parse(text);

return renderTemplate(data);

If your server properly answers with a JSON on a 4xx error, then it works as expected, if it doesn't answer with JSON (error 5xx in your case) then it can't. If you were to simply add a try catch here and avoid throwing an error, it would likely not solve your usecase as the extension would simply return the unmodified text, that htmx would then insert instead of the static template, giving the same result that you're seeing, simply without the error in the console. As so, I think it's a server-response issue that we can't expect the extension to manage on its own, as it's doing what is advertised.

What to do

I think you have to make it a proper JSON in any case, be it by making sure your server returns a JSON even on a 5xx error, or by adding some custom client-side code to replace the server response if it is not valid JSON. By default, if (event.detail.isError) will be true for any error code, though what you want to check here instead is likely whether the response text is valid JSON or not (if it is valid JSON, you likely want to pass that JSON to the templating engine rather than an empty one, which you likely only want as a fallback). You can probably handle this with a try catch trying to JSON.parse the answer ; if you end up in the catch block, the answer is not valid JSON, then you replace it with an empty object {}. Note that the response-targets extension can reset isError depending on htmx.config.responseTargetUnsetsError that it introduces, see its documentation about that ; as @scrhartley said, you can then rely on event.detail.xhr.status to check if the code is >= 400 yourself.

Hope this helps!

PS: as you're using htmx 1, make sure to use the V1 docs which doesn't include responseHandling that is a htmx 2 feature PS2: if you can and don't need IE11 support, I strongly encourage upgrading to htmx2

I appreciate your attention to this matter. I understand that returning JSON from the server would prevent this error from occuring but unfortunately that is not a viable solution in this case.

I had hoped that I was overlooking a simple workaround. Since it appears that this cannot be easily handled in client code, please consider adding a new template type option that makes it possible to insert static html templates in situations such as this.

Thanks!

@Telroshan
Copy link
Collaborator

Since it appears that this cannot be easily handled in client code

Ofc depends on what you consider "easy" or not @thenewguy , but I feel the following would do the trick:

document.body.addEventListener("htmx:beforeSwap", function (event) {
    const isError = event.detail.xhr.status >= 400
    const isHandlebarTemplate = htmx.closest(event.detail.elt, "[handlebars-template], [handlebars-array-template]")
    if (isError && isHandlebarTemplate) {
        try {
            JSON.parse(event.detail.serverResponse)
        } catch {
            event.detail.serverResponse = "{}"
        }
    }
})

Disclaimer: I didn't test this at all, but that's what I had in mind when typing the previous message
The ideas here are:

  • listening on the body as a general strategy to fix the JSON whenever needed, without having to care about where we are in the DOM
  • checking error responses only (depending on your usecase, you might even want to do it for all statuses if the response to be swapped in a handlebars template isn't JSON)
  • checking if the target element is a client side template (note that I'm simply running the same closest check that the extension does
  • if the response cannot be parsed to JSON, replace that response by an empty JSON object

Hope this helps!

please consider adding a new template type option that makes it possible to insert static html templates in situations such as this

Sounds reasonable to me! If you are interested btw, feel free to investigate & open a PR for that!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants