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

Support rerendering the toolbar on HTMX boosted pages. #1686

Merged
merged 6 commits into from Oct 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 0 additions & 1 deletion debug_toolbar/static/debug_toolbar/js/timer.js
@@ -1,7 +1,6 @@
import { $$ } from "./utils.js";

function insertBrowserTiming() {
console.log(["inserted"]);
const timingOffset = performance.timing.navigationStart,
timingEnd = performance.timing.loadEventEnd,
totalTime = timingEnd - timingOffset;
Expand Down
141 changes: 78 additions & 63 deletions debug_toolbar/static/debug_toolbar/js/toolbar.js
Expand Up @@ -2,19 +2,26 @@ import { $$, ajax, replaceToolbarState, debounce } from "./utils.js";

function onKeyDown(event) {
if (event.keyCode === 27) {
djdt.hide_one_level();
djdt.hideOneLevel();
}
}

function getDebugElement() {
// Fetch the debug element from the DOM.
// This is used to avoid writing the element's id
// everywhere the element is being selected. A fixed reference
// to the element should be avoided because the entire DOM could
// be reloaded such as via HTMX boosting.
return document.getElementById("djDebug");
}

const djdt = {
handleDragged: false,
init() {
const djDebug = document.getElementById("djDebug");
$$.show(djDebug);
$$.on(
document.getElementById("djDebugPanelList"),
document.body,
"click",
"li a",
"#djDebugPanelList li a",
function (event) {
event.preventDefault();
if (!this.className) {
Expand All @@ -23,23 +30,24 @@ const djdt = {
const panelId = this.className;
const current = document.getElementById(panelId);
if ($$.visible(current)) {
djdt.hide_panels();
djdt.hidePanels();
} else {
djdt.hide_panels();
djdt.hidePanels();

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

const djDebug = getDebugElement();
const inner = current.querySelector(
".djDebugPanelContent .djdt-scroll"
),
store_id = djDebug.dataset.storeId;
if (store_id && inner.children.length === 0) {
storeId = djDebug.dataset.storeId;
if (storeId && inner.children.length === 0) {
const url = new URL(
djDebug.dataset.renderPanelUrl,
window.location
);
url.searchParams.append("store_id", store_id);
url.searchParams.append("store_id", storeId);
url.searchParams.append("panel_id", panelId);
ajax(url).then(function (data) {
inner.previousElementSibling.remove(); // Remove AJAX loader
Expand All @@ -62,13 +70,13 @@ const djdt = {
}
}
);
$$.on(djDebug, "click", ".djDebugClose", function () {
djdt.hide_one_level();
$$.on(document.body, "click", "#djDebug .djDebugClose", function () {
djdt.hideOneLevel();
});
$$.on(
djDebug,
document.body,
"click",
".djDebugPanelButton input[type=checkbox]",
"#djDebug .djDebugPanelButton input[type=checkbox]",
function () {
djdt.cookie.set(
this.dataset.cookie,
Expand All @@ -82,51 +90,51 @@ const djdt = {
);

// Used by the SQL and template panels
$$.on(djDebug, "click", ".remoteCall", function (event) {
$$.on(document.body, "click", "#djDebug .remoteCall", function (event) {
event.preventDefault();

let url;
const ajax_data = {};
const ajaxData = {};

if (this.tagName === "BUTTON") {
const form = this.closest("form");
url = this.formAction;
ajax_data.method = form.method.toUpperCase();
ajax_data.body = new FormData(form);
ajaxData.method = form.method.toUpperCase();
ajaxData.body = new FormData(form);
} else if (this.tagName === "A") {
url = this.href;
}

ajax(url, ajax_data).then(function (data) {
ajax(url, ajaxData).then(function (data) {
const win = document.getElementById("djDebugWindow");
win.innerHTML = data.content;
$$.show(win);
});
});

// Used by the cache, profiling and SQL panels
$$.on(djDebug, "click", ".djToggleSwitch", function () {
$$.on(document.body, "click", "#djDebug .djToggleSwitch", function () {
const id = this.dataset.toggleId;
const toggleOpen = "+";
const toggleClose = "-";
const open_me = this.textContent === toggleOpen;
const openMe = this.textContent === toggleOpen;
const name = this.dataset.toggleName;
const container = document.getElementById(name + "_" + id);
container
.querySelectorAll(".djDebugCollapsed")
.forEach(function (e) {
$$.toggle(e, open_me);
$$.toggle(e, openMe);
});
container
.querySelectorAll(".djDebugUncollapsed")
.forEach(function (e) {
$$.toggle(e, !open_me);
$$.toggle(e, !openMe);
});
const self = this;
this.closest(".djDebugPanelContent")
.querySelectorAll(".djToggleDetails_" + id)
.forEach(function (e) {
if (open_me) {
if (openMe) {
e.classList.add("djSelected");
e.classList.remove("djUnselected");
self.textContent = toggleClose;
Expand All @@ -142,19 +150,16 @@ const djdt = {
});
});

document
.getElementById("djHideToolBarButton")
.addEventListener("click", function (event) {
event.preventDefault();
djdt.hide_toolbar();
});
document
.getElementById("djShowToolBarButton")
.addEventListener("click", function () {
if (!djdt.handleDragged) {
djdt.show_toolbar();
}
});
$$.on(document.body, "click", "#djHideToolBarButton", function (event) {
event.preventDefault();
djdt.hideToolbar();
});

$$.on(document.body, "click", "#djShowToolBarButton", function () {
if (!djdt.handleDragged) {
djdt.showToolbar();
}
});
let startPageY, baseY;
const handle = document.getElementById("djDebugToolbarHandle");
function onHandleMove(event) {
Expand All @@ -174,14 +179,18 @@ const djdt = {
djdt.handleDragged = true;
}
}
document
.getElementById("djShowToolBarButton")
.addEventListener("mousedown", function (event) {
$$.on(
document.body,
"mousedown",
"#djShowToolBarButton",
function (event) {
event.preventDefault();
startPageY = event.pageY;
baseY = handle.offsetTop - startPageY;
document.addEventListener("mousemove", onHandleMove);
});
}
);

document.addEventListener("mouseup", function (event) {
document.removeEventListener("mousemove", onHandleMove);
if (djdt.handleDragged) {
Expand All @@ -190,22 +199,27 @@ const djdt = {
requestAnimationFrame(function () {
djdt.handleDragged = false;
});
djdt.ensure_handle_visibility();
djdt.ensureHandleVisibility();
}
});
const djDebug = getDebugElement();
// Make sure the debug element is rendered at least once.
// showToolbar will continue to show it in the future if the
// entire DOM is reloaded.
$$.show(djDebug);
const show =
localStorage.getItem("djdt.show") || djDebug.dataset.defaultShow;
if (show === "true") {
djdt.show_toolbar();
djdt.showToolbar();
} else {
djdt.hide_toolbar();
djdt.hideToolbar();
}
if (djDebug.dataset.sidebarUrl !== undefined) {
djdt.update_on_ajax();
djdt.updateOnAjax();
}
},
hide_panels() {
const djDebug = document.getElementById("djDebug");
hidePanels() {
const djDebug = getDebugElement();
$$.hide(document.getElementById("djDebugWindow"));
djDebug.querySelectorAll(".djdt-panelContent").forEach(function (e) {
$$.hide(e);
Expand All @@ -214,7 +228,7 @@ const djdt = {
e.classList.remove("djdt-active");
});
},
ensure_handle_visibility() {
ensureHandleVisibility() {
const handle = document.getElementById("djDebugToolbarHandle");
// set handle position
const handleTop = Math.min(
Expand All @@ -223,47 +237,48 @@ const djdt = {
);
handle.style.top = handleTop + "px";
},
hide_toolbar() {
djdt.hide_panels();
hideToolbar() {
djdt.hidePanels();

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

const handle = document.getElementById("djDebugToolbarHandle");
$$.show(handle);
djdt.ensure_handle_visibility();
window.addEventListener("resize", djdt.ensure_handle_visibility);
djdt.ensureHandleVisibility();
window.addEventListener("resize", djdt.ensureHandleVisibility);
document.removeEventListener("keydown", onKeyDown);

localStorage.setItem("djdt.show", "false");
},
hide_one_level() {
hideOneLevel() {
const win = document.getElementById("djDebugWindow");
if ($$.visible(win)) {
$$.hide(win);
} else {
const toolbar = document.getElementById("djDebugToolbar");
if (toolbar.querySelector("li.djdt-active")) {
djdt.hide_panels();
djdt.hidePanels();
} else {
djdt.hide_toolbar();
djdt.hideToolbar();
}
}
},
show_toolbar() {
showToolbar() {
document.addEventListener("keydown", onKeyDown);
$$.show(document.getElementById("djDebug"));
$$.hide(document.getElementById("djDebugToolbarHandle"));
$$.show(document.getElementById("djDebugToolbar"));
localStorage.setItem("djdt.show", "true");
window.removeEventListener("resize", djdt.ensure_handle_visibility);
window.removeEventListener("resize", djdt.ensureHandleVisibility);
},
update_on_ajax() {
const sidebar_url =
updateOnAjax() {
const sidebarUrl =
document.getElementById("djDebug").dataset.sidebarUrl;
const slowjax = debounce(ajax, 200);

function handleAjaxResponse(storeId) {
storeId = encodeURIComponent(storeId);
const dest = `${sidebar_url}?store_id=${storeId}`;
const dest = `${sidebarUrl}?store_id=${storeId}`;
slowjax(dest).then(function (data) {
replaceToolbarState(storeId, data);
});
Expand Down Expand Up @@ -342,10 +357,10 @@ const djdt = {
},
};
window.djdt = {
show_toolbar: djdt.show_toolbar,
hide_toolbar: djdt.hide_toolbar,
show_toolbar: djdt.showToolbar,
hide_toolbar: djdt.hideToolbar,
init: djdt.init,
close: djdt.hide_one_level,
close: djdt.hideOneLevel,
cookie: djdt.cookie,
};

Expand Down
3 changes: 2 additions & 1 deletion debug_toolbar/static/debug_toolbar/js/utils.js
@@ -1,5 +1,6 @@
const $$ = {
on(root, eventName, selector, fn) {
root.removeEventListener(eventName, fn);
root.addEventListener(eventName, function (event) {
const target = event.target.closest(selector);
if (root.contains(target)) {
Expand Down Expand Up @@ -107,7 +108,7 @@ function ajaxForm(element) {
function replaceToolbarState(newStoreId, data) {
const djDebug = document.getElementById("djDebug");
djDebug.setAttribute("data-store-id", newStoreId);
// Check if response is empty, it could be due to an expired store_id.
// Check if response is empty, it could be due to an expired storeId.
Object.keys(data).forEach(function (panelId) {
const panel = document.getElementById(panelId);
if (panel) {
Expand Down
5 changes: 5 additions & 0 deletions docs/changes.rst
Expand Up @@ -5,6 +5,11 @@ Pending
-------

* Auto-update History panel for JavaScript ``fetch`` requests.
* Support `HTMX boosting <https://htmx.org/docs/#boosting/>`__ and
re-rendering the toolbar after the DOM has been replaced. 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.


3.7.0 (2022-09-25)
Expand Down
34 changes: 34 additions & 0 deletions docs/installation.rst
Expand Up @@ -199,3 +199,37 @@ The Debug Toolbar currently doesn't support Django Channels or async projects.
If you are using Django channels are having issues getting panels to load,
please review the documentation for the configuration option
:ref:`RENDER_PANELS <RENDER_PANELS>`.


HTMX
^^^^

If you're using `HTMX`_ to `boost a page`_ you will need to add the following
event handler to your code:

.. code-block:: javascript

{% if debug %}
if (typeof window.htmx !== "undefined") {
htmx.on("htmx:afterSettle", function(detail) {
if (
typeof window.djdt !== "undefined"
&& detail.target instanceof HTMLBodyElement
) {
djdt.show_toolbar();
}
});
}
{% endif %}


The use of ``{% if debug %}`` requires
`django.template.context_processors.debug`_ be included in the
``'context_processors'`` option of the `TEMPLATES`_ setting. Django's
default configuration includes this context processor.


.. _HTMX: https://htmx.org/
.. _boost a page: https://htmx.org/docs/#boosting
.. _django.template.context_processors.debug: https://docs.djangoproject.com/en/4.1/ref/templates/api/#django-template-context-processors-debug
.. _TEMPLATES: https://docs.djangoproject.com/en/4.1/ref/settings/#std-setting-TEMPLATES
5 changes: 4 additions & 1 deletion docs/panels.rst
Expand Up @@ -427,7 +427,10 @@ common methods available.

.. js:function:: djdt.show_toolbar

Shows the toolbar.
Shows the toolbar. This can be used to re-render the toolbar when reloading the
entire DOM. For example, then using `HTMX's boosting`_.

.. _HTMX's boosting: https://htmx.org/docs/#boosting

Events
^^^^^^
Expand Down