Skip to content

Commit 7a8920c

Browse files
Support rerendering the toolbar on HTMX boosted pages. (#1686)
* Support rerendering the toolbar on HTMX boosted pages. This reworks the JavaScript integration to put most event handlers on document.body. This means we'll have slightly slower performance, but it's easier to handle re-rendering the toolbar when the DOM has been replaced. * Improve docs' JavaScript usage and links for django std lib docs. * Make getDebugElement a private method and improve docs. * Switch internal JavaScript functions and members to camelCase. This leaves the public API with two snake case functions, show_toolbar and hide_toolbar.
1 parent 0cff109 commit 7a8920c

File tree

8 files changed

+151
-66
lines changed

8 files changed

+151
-66
lines changed

debug_toolbar/static/debug_toolbar/js/timer.js

-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { $$ } from "./utils.js";
22

33
function insertBrowserTiming() {
4-
console.log(["inserted"]);
54
const timingOffset = performance.timing.navigationStart,
65
timingEnd = performance.timing.loadEventEnd,
76
totalTime = timingEnd - timingOffset;

debug_toolbar/static/debug_toolbar/js/toolbar.js

+78-63
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,26 @@ import { $$, ajax, replaceToolbarState, debounce } from "./utils.js";
22

33
function onKeyDown(event) {
44
if (event.keyCode === 27) {
5-
djdt.hide_one_level();
5+
djdt.hideOneLevel();
66
}
77
}
88

9+
function getDebugElement() {
10+
// Fetch the debug element from the DOM.
11+
// This is used to avoid writing the element's id
12+
// everywhere the element is being selected. A fixed reference
13+
// to the element should be avoided because the entire DOM could
14+
// be reloaded such as via HTMX boosting.
15+
return document.getElementById("djDebug");
16+
}
17+
918
const djdt = {
1019
handleDragged: false,
1120
init() {
12-
const djDebug = document.getElementById("djDebug");
13-
$$.show(djDebug);
1421
$$.on(
15-
document.getElementById("djDebugPanelList"),
22+
document.body,
1623
"click",
17-
"li a",
24+
"#djDebugPanelList li a",
1825
function (event) {
1926
event.preventDefault();
2027
if (!this.className) {
@@ -23,23 +30,24 @@ const djdt = {
2330
const panelId = this.className;
2431
const current = document.getElementById(panelId);
2532
if ($$.visible(current)) {
26-
djdt.hide_panels();
33+
djdt.hidePanels();
2734
} else {
28-
djdt.hide_panels();
35+
djdt.hidePanels();
2936

3037
$$.show(current);
3138
this.parentElement.classList.add("djdt-active");
3239

40+
const djDebug = getDebugElement();
3341
const inner = current.querySelector(
3442
".djDebugPanelContent .djdt-scroll"
3543
),
36-
store_id = djDebug.dataset.storeId;
37-
if (store_id && inner.children.length === 0) {
44+
storeId = djDebug.dataset.storeId;
45+
if (storeId && inner.children.length === 0) {
3846
const url = new URL(
3947
djDebug.dataset.renderPanelUrl,
4048
window.location
4149
);
42-
url.searchParams.append("store_id", store_id);
50+
url.searchParams.append("store_id", storeId);
4351
url.searchParams.append("panel_id", panelId);
4452
ajax(url).then(function (data) {
4553
inner.previousElementSibling.remove(); // Remove AJAX loader
@@ -62,13 +70,13 @@ const djdt = {
6270
}
6371
}
6472
);
65-
$$.on(djDebug, "click", ".djDebugClose", function () {
66-
djdt.hide_one_level();
73+
$$.on(document.body, "click", "#djDebug .djDebugClose", function () {
74+
djdt.hideOneLevel();
6775
});
6876
$$.on(
69-
djDebug,
77+
document.body,
7078
"click",
71-
".djDebugPanelButton input[type=checkbox]",
79+
"#djDebug .djDebugPanelButton input[type=checkbox]",
7280
function () {
7381
djdt.cookie.set(
7482
this.dataset.cookie,
@@ -82,51 +90,51 @@ const djdt = {
8290
);
8391

8492
// Used by the SQL and template panels
85-
$$.on(djDebug, "click", ".remoteCall", function (event) {
93+
$$.on(document.body, "click", "#djDebug .remoteCall", function (event) {
8694
event.preventDefault();
8795

8896
let url;
89-
const ajax_data = {};
97+
const ajaxData = {};
9098

9199
if (this.tagName === "BUTTON") {
92100
const form = this.closest("form");
93101
url = this.formAction;
94-
ajax_data.method = form.method.toUpperCase();
95-
ajax_data.body = new FormData(form);
102+
ajaxData.method = form.method.toUpperCase();
103+
ajaxData.body = new FormData(form);
96104
} else if (this.tagName === "A") {
97105
url = this.href;
98106
}
99107

100-
ajax(url, ajax_data).then(function (data) {
108+
ajax(url, ajaxData).then(function (data) {
101109
const win = document.getElementById("djDebugWindow");
102110
win.innerHTML = data.content;
103111
$$.show(win);
104112
});
105113
});
106114

107115
// Used by the cache, profiling and SQL panels
108-
$$.on(djDebug, "click", ".djToggleSwitch", function () {
116+
$$.on(document.body, "click", "#djDebug .djToggleSwitch", function () {
109117
const id = this.dataset.toggleId;
110118
const toggleOpen = "+";
111119
const toggleClose = "-";
112-
const open_me = this.textContent === toggleOpen;
120+
const openMe = this.textContent === toggleOpen;
113121
const name = this.dataset.toggleName;
114122
const container = document.getElementById(name + "_" + id);
115123
container
116124
.querySelectorAll(".djDebugCollapsed")
117125
.forEach(function (e) {
118-
$$.toggle(e, open_me);
126+
$$.toggle(e, openMe);
119127
});
120128
container
121129
.querySelectorAll(".djDebugUncollapsed")
122130
.forEach(function (e) {
123-
$$.toggle(e, !open_me);
131+
$$.toggle(e, !openMe);
124132
});
125133
const self = this;
126134
this.closest(".djDebugPanelContent")
127135
.querySelectorAll(".djToggleDetails_" + id)
128136
.forEach(function (e) {
129-
if (open_me) {
137+
if (openMe) {
130138
e.classList.add("djSelected");
131139
e.classList.remove("djUnselected");
132140
self.textContent = toggleClose;
@@ -142,19 +150,16 @@ const djdt = {
142150
});
143151
});
144152

145-
document
146-
.getElementById("djHideToolBarButton")
147-
.addEventListener("click", function (event) {
148-
event.preventDefault();
149-
djdt.hide_toolbar();
150-
});
151-
document
152-
.getElementById("djShowToolBarButton")
153-
.addEventListener("click", function () {
154-
if (!djdt.handleDragged) {
155-
djdt.show_toolbar();
156-
}
157-
});
153+
$$.on(document.body, "click", "#djHideToolBarButton", function (event) {
154+
event.preventDefault();
155+
djdt.hideToolbar();
156+
});
157+
158+
$$.on(document.body, "click", "#djShowToolBarButton", function () {
159+
if (!djdt.handleDragged) {
160+
djdt.showToolbar();
161+
}
162+
});
158163
let startPageY, baseY;
159164
const handle = document.getElementById("djDebugToolbarHandle");
160165
function onHandleMove(event) {
@@ -174,14 +179,18 @@ const djdt = {
174179
djdt.handleDragged = true;
175180
}
176181
}
177-
document
178-
.getElementById("djShowToolBarButton")
179-
.addEventListener("mousedown", function (event) {
182+
$$.on(
183+
document.body,
184+
"mousedown",
185+
"#djShowToolBarButton",
186+
function (event) {
180187
event.preventDefault();
181188
startPageY = event.pageY;
182189
baseY = handle.offsetTop - startPageY;
183190
document.addEventListener("mousemove", onHandleMove);
184-
});
191+
}
192+
);
193+
185194
document.addEventListener("mouseup", function (event) {
186195
document.removeEventListener("mousemove", onHandleMove);
187196
if (djdt.handleDragged) {
@@ -190,22 +199,27 @@ const djdt = {
190199
requestAnimationFrame(function () {
191200
djdt.handleDragged = false;
192201
});
193-
djdt.ensure_handle_visibility();
202+
djdt.ensureHandleVisibility();
194203
}
195204
});
205+
const djDebug = getDebugElement();
206+
// Make sure the debug element is rendered at least once.
207+
// showToolbar will continue to show it in the future if the
208+
// entire DOM is reloaded.
209+
$$.show(djDebug);
196210
const show =
197211
localStorage.getItem("djdt.show") || djDebug.dataset.defaultShow;
198212
if (show === "true") {
199-
djdt.show_toolbar();
213+
djdt.showToolbar();
200214
} else {
201-
djdt.hide_toolbar();
215+
djdt.hideToolbar();
202216
}
203217
if (djDebug.dataset.sidebarUrl !== undefined) {
204-
djdt.update_on_ajax();
218+
djdt.updateOnAjax();
205219
}
206220
},
207-
hide_panels() {
208-
const djDebug = document.getElementById("djDebug");
221+
hidePanels() {
222+
const djDebug = getDebugElement();
209223
$$.hide(document.getElementById("djDebugWindow"));
210224
djDebug.querySelectorAll(".djdt-panelContent").forEach(function (e) {
211225
$$.hide(e);
@@ -214,7 +228,7 @@ const djdt = {
214228
e.classList.remove("djdt-active");
215229
});
216230
},
217-
ensure_handle_visibility() {
231+
ensureHandleVisibility() {
218232
const handle = document.getElementById("djDebugToolbarHandle");
219233
// set handle position
220234
const handleTop = Math.min(
@@ -223,47 +237,48 @@ const djdt = {
223237
);
224238
handle.style.top = handleTop + "px";
225239
},
226-
hide_toolbar() {
227-
djdt.hide_panels();
240+
hideToolbar() {
241+
djdt.hidePanels();
228242

229243
$$.hide(document.getElementById("djDebugToolbar"));
230244

231245
const handle = document.getElementById("djDebugToolbarHandle");
232246
$$.show(handle);
233-
djdt.ensure_handle_visibility();
234-
window.addEventListener("resize", djdt.ensure_handle_visibility);
247+
djdt.ensureHandleVisibility();
248+
window.addEventListener("resize", djdt.ensureHandleVisibility);
235249
document.removeEventListener("keydown", onKeyDown);
236250

237251
localStorage.setItem("djdt.show", "false");
238252
},
239-
hide_one_level() {
253+
hideOneLevel() {
240254
const win = document.getElementById("djDebugWindow");
241255
if ($$.visible(win)) {
242256
$$.hide(win);
243257
} else {
244258
const toolbar = document.getElementById("djDebugToolbar");
245259
if (toolbar.querySelector("li.djdt-active")) {
246-
djdt.hide_panels();
260+
djdt.hidePanels();
247261
} else {
248-
djdt.hide_toolbar();
262+
djdt.hideToolbar();
249263
}
250264
}
251265
},
252-
show_toolbar() {
266+
showToolbar() {
253267
document.addEventListener("keydown", onKeyDown);
268+
$$.show(document.getElementById("djDebug"));
254269
$$.hide(document.getElementById("djDebugToolbarHandle"));
255270
$$.show(document.getElementById("djDebugToolbar"));
256271
localStorage.setItem("djdt.show", "true");
257-
window.removeEventListener("resize", djdt.ensure_handle_visibility);
272+
window.removeEventListener("resize", djdt.ensureHandleVisibility);
258273
},
259-
update_on_ajax() {
260-
const sidebar_url =
274+
updateOnAjax() {
275+
const sidebarUrl =
261276
document.getElementById("djDebug").dataset.sidebarUrl;
262277
const slowjax = debounce(ajax, 200);
263278

264279
function handleAjaxResponse(storeId) {
265280
storeId = encodeURIComponent(storeId);
266-
const dest = `${sidebar_url}?store_id=${storeId}`;
281+
const dest = `${sidebarUrl}?store_id=${storeId}`;
267282
slowjax(dest).then(function (data) {
268283
replaceToolbarState(storeId, data);
269284
});
@@ -342,10 +357,10 @@ const djdt = {
342357
},
343358
};
344359
window.djdt = {
345-
show_toolbar: djdt.show_toolbar,
346-
hide_toolbar: djdt.hide_toolbar,
360+
show_toolbar: djdt.showToolbar,
361+
hide_toolbar: djdt.hideToolbar,
347362
init: djdt.init,
348-
close: djdt.hide_one_level,
363+
close: djdt.hideOneLevel,
349364
cookie: djdt.cookie,
350365
};
351366

debug_toolbar/static/debug_toolbar/js/utils.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const $$ = {
22
on(root, eventName, selector, fn) {
3+
root.removeEventListener(eventName, fn);
34
root.addEventListener(eventName, function (event) {
45
const target = event.target.closest(selector);
56
if (root.contains(target)) {
@@ -107,7 +108,7 @@ function ajaxForm(element) {
107108
function replaceToolbarState(newStoreId, data) {
108109
const djDebug = document.getElementById("djDebug");
109110
djDebug.setAttribute("data-store-id", newStoreId);
110-
// Check if response is empty, it could be due to an expired store_id.
111+
// Check if response is empty, it could be due to an expired storeId.
111112
Object.keys(data).forEach(function (panelId) {
112113
const panel = document.getElementById(panelId);
113114
if (panel) {

docs/changes.rst

+5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ Pending
55
-------
66

77
* Auto-update History panel for JavaScript ``fetch`` requests.
8+
* Support `HTMX boosting <https://htmx.org/docs/#boosting/>`__ and
9+
re-rendering the toolbar after the DOM has been replaced. This reworks
10+
the JavaScript integration to put most event handlers on document.body.
11+
This means we'll have slightly slower performance, but it's easier
12+
to handle re-rendering the toolbar when the DOM has been replaced.
813

914

1015
3.7.0 (2022-09-25)

docs/installation.rst

+34
Original file line numberDiff line numberDiff line change
@@ -199,3 +199,37 @@ The Debug Toolbar currently doesn't support Django Channels or async projects.
199199
If you are using Django channels are having issues getting panels to load,
200200
please review the documentation for the configuration option
201201
:ref:`RENDER_PANELS <RENDER_PANELS>`.
202+
203+
204+
HTMX
205+
^^^^
206+
207+
If you're using `HTMX`_ to `boost a page`_ you will need to add the following
208+
event handler to your code:
209+
210+
.. code-block:: javascript
211+
212+
{% if debug %}
213+
if (typeof window.htmx !== "undefined") {
214+
htmx.on("htmx:afterSettle", function(detail) {
215+
if (
216+
typeof window.djdt !== "undefined"
217+
&& detail.target instanceof HTMLBodyElement
218+
) {
219+
djdt.show_toolbar();
220+
}
221+
});
222+
}
223+
{% endif %}
224+
225+
226+
The use of ``{% if debug %}`` requires
227+
`django.template.context_processors.debug`_ be included in the
228+
``'context_processors'`` option of the `TEMPLATES`_ setting. Django's
229+
default configuration includes this context processor.
230+
231+
232+
.. _HTMX: https://htmx.org/
233+
.. _boost a page: https://htmx.org/docs/#boosting
234+
.. _django.template.context_processors.debug: https://docs.djangoproject.com/en/4.1/ref/templates/api/#django-template-context-processors-debug
235+
.. _TEMPLATES: https://docs.djangoproject.com/en/4.1/ref/settings/#std-setting-TEMPLATES

docs/panels.rst

+4-1
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,10 @@ common methods available.
427427

428428
.. js:function:: djdt.show_toolbar
429429

430-
Shows the toolbar.
430+
Shows the toolbar. This can be used to re-render the toolbar when reloading the
431+
entire DOM. For example, then using `HTMX's boosting`_.
432+
433+
.. _HTMX's boosting: https://htmx.org/docs/#boosting
431434

432435
Events
433436
^^^^^^

0 commit comments

Comments
 (0)