-
Notifications
You must be signed in to change notification settings - Fork 1.1k
(WIP) Adds support to allow Htmx compatibility #1569
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
Conversation
I don't really like the idea of adding the toolbar HTML to fragment responses. The toolbar HTML will massively outweigh the fragment size. The "History" panel can already be used to inspect fragments responses, no? |
Yes, it should be captured there. |
I'm open to another approach, but I really like DX of the oob approach. Can you help me understand what cases the html size would be a consideration in?
Yes and no - Although it can capture the response, It can't "in the moment". You'll have to refresh the page and then use the history to move back to it, making it much less useful. I've put up an example of this at https://sampleapp-w4bl.onrender.com/ - it's on the free tier so it's slow. |
🤷♂️ I don't have concrete evidence, but it feels wrong. Certain htmx patterns may fetch many small fragment responses, e.g. for autocompletes. Attaching all the toolbar HTML for every response will massively increase the work done by the browser.
The history panel has a "refresh" button which allows you to view new requests without reloading the page. IMO a better approach would be to enhance the history panel functionality, perhaps by making it auto-refresh when opened, or even adding an "auto refresh" mode. (I tried the refresh button on your demo app and it's broken there, perhaps due to this PR?) |
I dont see this as a valid reason. The debug toolbar is only used during development and debugging. Why is size important in this context? OOB makes sense to me.
No. Its not working for me with fragments. In fact its throwing errors:
|
Simply update the toolbar after every response. Sure with autocomplete this will slow the browser down. But remember. Were debugging here. Why does it need to be fast? |
I think the auto-refresh is a really good idea and it's applicable for a lot of different usecases like vanilla ajax requests. Let me get a PoC up on that to see how it feels |
We could always defend against browser overload by debouncing or something IF it becomes a problem. |
Ok Initial PoC for auto refresh mode. I would love any feedback/suggestions but the core changes to get this working:
I would love any feedback about the approach! Some open questions I have from working through this: Why are we signing the request for the switch request? We don't sign loading the panel so I'm wondering if this can be removed or if it serves a purpose I am calling toolbar.store twice: https://github.com/jazzband/django-debug-toolbar/pull/1569/files#diff-4c34c65dc0e8564d6d1b67b93a2099a4a8f7a5264cded1f09de5eb724bb6523dR27 and in the main render call. Can we remove the second one? The comment there says it's being doing for the purpose of the history panel. This is not in a merge ready state and I have some clean up to do. Some of the changes from the prior approach I need to discard as they no longer matter, and some of them are still important for the boosted case. Making a todo list for myself:
|
adding view that will return the toolbar for a given store id Adding header containing toolbar url Refactoring history javascript to allow switching state by store id
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the work on this so far. It would be great if you could pull out the auto-refresh code into it's own PR. I feel like the scope of that should be smaller than the changes to support HTMX.
A general concern is that the changes to support auto-refreshing of the toolbar's history panel is that I couldn't find the spot that actually does the auto-refreshing. Can you give me a little bit of a rundown of how your changes are supporting that?
Why are we signing the request for the switch request? We don't sign loading the panel so I'm wondering if this can be removed or if it serves a purpose
Admittedly, it may not be necessary, but the purpose of it is to verify that the client isn't changing the store id of the toolbar that is being requested.
I am calling toolbar.store twice: https://github.com/jazzband/django-debug-toolbar/pull/1569/files#diff-4c34c65dc0e8564d6d1b67b93a2099a4a8f7a5264cded1f09de5eb724bb6523dR27 and in the main render call. Can we remove the second one? The comment there says it's being doing for the purpose of the history panel.
We can't rely on all of the panels always being enabled. If the only call to toolbar.store()
was made in the HistoryPanel, we run the risk of it never being called if the HistoryPanel is not included.
@@ -91,6 +106,14 @@ function ajax(url, init) { | |||
}); | |||
} | |||
|
|||
function pluckData(array, key) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function can likely be replaced with:
return array.map(function(obj) {
return obj.dataset[key]
})
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was my first attempt, but annoyingly Set objects don't have a map function.
abort() { | ||
controller.abort(); | ||
resetAbortController(); | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like the only part of the code that uses the controller and the signal. Outside of allowing another third party panel to abort a request, what purpose does this serve?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling abort will unregister the click handlers - the intent here is that init will configure the JS click handlers and this will undo it to put us back in the state we were immediately before init.
The Purpose of that is that for boosted requests / Turbolinks the new page will contain it's own toolbar html, and I was finding conflicts with multiple click handlers registered.
sig = SignedDataForm( | ||
initial=HistoryStoreForm(initial={"store_id": store_id}).initial | ||
).initial.get("signed") | ||
response["dj-history-sidebar-url"] = reverse("djdt:history_sidebar") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this isn't likely to change between requests, this could be moved out of the per-request cycle and into the template as a JS variable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
if not self.toolbar.should_render_panels(): | ||
self.toolbar.store() | ||
store_id = self.toolbar.store_id | ||
response["DJ-TOOLBAR-BASE-URL"] = ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is being used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not, I will remove
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you could pull out the auto-refresh code into it's own PR
I agree - I think the line is going to be fragment responses will probably be handled by the "auto-refresh" stuff and the htmx specific changes will be around boosted links. Let me make a test case with a vanilla hxr/fetch update.
Can you give me a little bit of a rundown of how your changes are supporting that?
That's happening here: https://github.com/jazzband/django-debug-toolbar/pull/1569/files#diff-ec4138914976be8d39d9780b045095caba36dfae38e8f8e671279b0d41e3aad6R323
An ajax request is made by the client, and when it comes back from the server that JS function will inspect the incoming request, and if it's a good candidate for updating will make a follow up js request to grab the toolbar data and switch it in.
if not self.toolbar.should_render_panels(): | ||
self.toolbar.store() | ||
store_id = self.toolbar.store_id | ||
response["DJ-TOOLBAR-BASE-URL"] = ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not, I will remove
sig = SignedDataForm( | ||
initial=HistoryStoreForm(initial={"store_id": store_id}).initial | ||
).initial.get("signed") | ||
response["dj-history-sidebar-url"] = reverse("djdt:history_sidebar") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
abort() { | ||
controller.abort(); | ||
resetAbortController(); | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling abort will unregister the click handlers - the intent here is that init will configure the JS click handlers and this will undo it to put us back in the state we were immediately before init.
The Purpose of that is that for boosted requests / Turbolinks the new page will contain it's own toolbar html, and I was finding conflicts with multiple click handlers registered.
@@ -91,6 +106,14 @@ function ajax(url, init) { | |||
}); | |||
} | |||
|
|||
function pluckData(array, key) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was my first attempt, but annoyingly Set objects don't have a map function.
Closing this in favor of #1577 which I think is a more fundamental case. I'll reopen this once that's got a clear way forward and this can focus on just htmx stuff (I think just the boosted link case) |
I've been playing with HTMX and djdt and I wanted to get them working better together. HTMX is like turbolinks, in that it loads html from the server and then inserts it into the page without reloading the page as a whole. Unlike turbolinks, it has a lot of support for html fragments, to allow loading chunks of the page in place. There are three modes of use with HTMX - the first page load, an htmx fragment load, and a boosted
<a>
tag.Here's what I'm thinking for the three differnt modes of operation:
First page load
Nothing needs to change here. This works exactly like any existing page load.
Fragments
The goal here is to append the toolbar content to the end of the request and allow htmx use the hx-oob (https://htmx.org/attributes/hx-swap-oob/) attribute to just swap out the content. The attribute can be added with the setting value ROOT_TAG_EXTRA_ATTRS
This needs a little more consideration. The issues I've found so far:
- The fragments coming from the server don't have a consistent wrapper. It could be as simple as
<p>hey<p>
So to update the debug info we can't have a consistent string to look for to decide where to insert the toolbar content.- The javascript on the page needs to be disabled to prevent multiple click handlers
- The javascript on the page needs to be re-inited after the content is swapped in
- The javascript and css that normally come with the response shouldn't be included
This works like turbolinks, and I've been able to get this working pretty well using the same strategy used for turbolinks (Debug toolbar doesn't work with turbolinks after first page load #1179). The fly in the ointment is that unlike the fragment case, this case should NOT include the hx-oob attribute as it's not piggybacking on another request. So I need a way to conditionally include the ROOT_TAG_EXTRA_ATTRS values. Additionally since in this case we've already loaded the css/js we should also exclude those.
An example use of these changes is here: gone/django-hydra@031d4ce#diff-bed13003085067d79e49d240de44a575e7c9c57073f67710eeb0a59fc32bf21aR31