Skip to content

(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

Closed
wants to merge 12 commits into from
Closed
12 changes: 12 additions & 0 deletions debug_toolbar/panels/history/panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ class HistoryPanel(Panel):
nav_title = _("History")
template = "debug_toolbar/panels/history.html"

def process_request(self, request):
response = super().process_request(request)
if not self.toolbar.should_render_panels():
self.toolbar.store()
store_id = self.toolbar.store_id
sig = SignedDataForm(
initial=HistoryStoreForm(initial={"store_id": store_id}).initial
).initial.get("signed")
response["DJ-TOOLBAR-STORE-ID"] = store_id
response["DJ-TOOLBAR-STORE-ID-SIGNATURE"] = sig
return response

@property
def enabled(self):
# Do not show the history panel if the panels are rendered on request
Expand Down
2 changes: 0 additions & 2 deletions debug_toolbar/panels/history/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ def history_sidebar(request, verified_data):
# RESULTS_CACHE_SIZE
return JsonResponse(context)
for panel in toolbar.panels:
if not panel.is_historical:
continue
panel_context = {"panel": panel}
context[panel.panel_id] = {
"button": render_to_string(
Expand Down
1 change: 1 addition & 0 deletions debug_toolbar/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"SHOW_TEMPLATE_CONTEXT": True,
"SKIP_TEMPLATE_PREFIXES": ("django/forms/widgets/", "admin/widgets/"),
"SQL_WARNING_THRESHOLD": 500, # milliseconds
"UPDATE_ON_AJAX": False,
}


Expand Down
91 changes: 61 additions & 30 deletions debug_toolbar/static/debug_toolbar/js/history.js
Original file line number Diff line number Diff line change
@@ -1,51 +1,82 @@
import { $$, ajaxForm } from "./utils.js";
import { $$, ajaxForm, pluckData, replaceToolbarState } from "./utils.js";

const djDebug = document.getElementById("djDebug");

$$.on(djDebug, "click", ".switchHistory", function (event) {
event.preventDefault();
const newStoreId = this.dataset.storeId;
const tbody = this.closest("tbody");
function difference(setA, setB) {
const _difference = new Set(setA);
for (const elem of setB) {
_difference.delete(elem);
}
return _difference;
}

function switchHistory(newStoreId) {
const formTarget = djDebug.querySelector(
".switchHistory[data-store-id='" + newStoreId + "']"
);
const tbody = formTarget.closest("tbody");

const highlighted = tbody.querySelector(".djdt-highlighted");
if (highlighted) {
highlighted.classList.remove("djdt-highlighted");
}
this.closest("tr").classList.add("djdt-highlighted");
formTarget.closest("tr").classList.add("djdt-highlighted");

ajaxForm(this).then(function (data) {
djDebug.setAttribute("data-store-id", newStoreId);
// Check if response is empty, it could be due to an expired store_id.
ajaxForm(formTarget).then(function (data) {
if (Object.keys(data).length === 0) {
const container = document.getElementById("djdtHistoryRequests");
container.querySelector(
'button[data-store-id="' + newStoreId + '"]'
).innerHTML = "Switch [EXPIRED]";
} else {
Object.keys(data).forEach(function (panelId) {
const panel = document.getElementById(panelId);
if (panel) {
panel.outerHTML = data[panelId].content;
document.getElementById("djdt-" + panelId).outerHTML =
data[panelId].button;
}
});
}
//we're already in history panel, so handle locally vs replacing active html
delete data.HistoryPanel;
replaceToolbarState(newStoreId, data);
});
});
}

$$.on(djDebug, "click", ".refreshHistory", function (event) {
event.preventDefault();
function refreshHistory() {
const formTarget = djDebug.querySelector(".refreshHistory");
const container = document.getElementById("djdtHistoryRequests");
ajaxForm(this).then(function (data) {
// Remove existing rows first then re-populate with new data
container
.querySelectorAll("tr[data-store-id]")
.forEach(function (node) {
node.remove();
const oldIds = new Set(
pluckData(container.querySelectorAll("tr[data-store-id]"), "storeId")
);

return ajaxForm(formTarget)
.then(function (data) {
// Remove existing rows first then re-populate with new data
container
.querySelectorAll("tr[data-store-id]")
.forEach(function (node) {
node.remove();
});
data.requests.forEach(function (request) {
container.innerHTML = request.content + container.innerHTML;
});
data.requests.forEach(function (request) {
container.innerHTML = request.content + container.innerHTML;
})
.then(function () {
const allIds = new Set(
pluckData(
container.querySelectorAll("tr[data-store-id]"),
"storeId"
)
);
const newIds = difference(allIds, oldIds);
const lastRequestId = newIds.values().next().value;
return {
allIds,
newIds,
lastRequestId,
};
});
});
}

$$.on(djDebug, "click", ".switchHistory", function (event) {
event.preventDefault();
switchHistory(this.dataset.storeId);
});

$$.on(djDebug, "click", ".refreshHistory", function (event) {
event.preventDefault();
refreshHistory();
});
114 changes: 87 additions & 27 deletions debug_toolbar/static/debug_toolbar/js/toolbar.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { $$, ajax } from "./utils.js";
import {
$$,
ajax,
controller,
resetAbortController,
replaceToolbarState,
} from "./utils.js";

function onKeyDown(event) {
if (event.keyCode === 27) {
Expand All @@ -8,6 +14,10 @@ function onKeyDown(event) {

const djdt = {
handleDragged: false,
abort() {
controller.abort();
resetAbortController();
},
Comment on lines +17 to +20
Copy link
Member

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?

Copy link
Contributor Author

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.

init() {
const djDebug = document.getElementById("djDebug");
$$.show(djDebug);
Expand Down Expand Up @@ -142,19 +152,23 @@ const djdt = {
});
});

document
.getElementById("djHideToolBarButton")
.addEventListener("click", function (event) {
document.getElementById("djHideToolBarButton").addEventListener(
"click",
function (event) {
event.preventDefault();
djdt.hide_toolbar();
});
document
.getElementById("djShowToolBarButton")
.addEventListener("click", function () {
},
{ signal: controller.signal }
);
document.getElementById("djShowToolBarButton").addEventListener(
"click",
function () {
if (!djdt.handleDragged) {
djdt.show_toolbar();
}
});
},
{ signal: controller.signal }
);
let startPageY, baseY;
const handle = document.getElementById("djDebugToolbarHandle");
function onHandleMove(event) {
Expand All @@ -174,32 +188,41 @@ const djdt = {
djdt.handleDragged = true;
}
}
document
.getElementById("djShowToolBarButton")
.addEventListener("mousedown", function (event) {
document.getElementById("djShowToolBarButton").addEventListener(
"mousedown",
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) {
event.preventDefault();
localStorage.setItem("djdt.top", handle.offsetTop);
requestAnimationFrame(function () {
djdt.handleDragged = false;
});
djdt.ensure_handle_visibility();
}
});
},
{ signal: controller.signal }
);
document.addEventListener(
"mouseup",
function (event) {
document.removeEventListener("mousemove", onHandleMove);
if (djdt.handleDragged) {
event.preventDefault();
localStorage.setItem("djdt.top", handle.offsetTop);
requestAnimationFrame(function () {
djdt.handleDragged = false;
});
djdt.ensure_handle_visibility();
}
},
{ signal: controller.signal }
);
const show =
localStorage.getItem("djdt.show") || djDebug.dataset.defaultShow;
if (show === "true") {
djdt.show_toolbar();
} else {
djdt.hide_toolbar();
}
if (djDebug.dataset.sidebarUrl !== undefined) {
djdt.update_on_ajax();
}
},
hide_panels() {
const djDebug = document.getElementById("djDebug");
Expand Down Expand Up @@ -228,7 +251,9 @@ const djdt = {
const handle = document.getElementById("djDebugToolbarHandle");
$$.show(handle);
djdt.ensure_handle_visibility();
window.addEventListener("resize", djdt.ensure_handle_visibility);
window.addEventListener("resize", djdt.ensure_handle_visibility, {
signal: controller.signal,
});
document.removeEventListener("keydown", onKeyDown);

localStorage.setItem("djdt.show", "false");
Expand All @@ -247,12 +272,43 @@ const djdt = {
}
},
show_toolbar() {
document.addEventListener("keydown", onKeyDown);
document.addEventListener("keydown", onKeyDown, {
signal: controller.signal,
});
$$.hide(document.getElementById("djDebugToolbarHandle"));
$$.show(document.getElementById("djDebugToolbar"));
localStorage.setItem("djdt.show", "true");
window.removeEventListener("resize", djdt.ensure_handle_visibility);
},
update_on_ajax() {
const sidebar_url =
document.getElementById("djDebug").dataset.sidebarUrl;

const origOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function () {
this.addEventListener("load", function () {
if (
this.responseURL !== "" &&
this.responseURL.indexOf("__debug__") === -1
) {
let signed = this.getResponseHeader(
"dj-toolbar-store-id-signature"
);
const store_id = this.getResponseHeader(
"dj-toolbar-store-id"
);
if (signed !== null) {
signed = encodeURIComponent(signed);
const dest = `${sidebar_url}?signed=${signed}`;
ajax(dest).then(function (data) {
replaceToolbarState(store_id, data);
});
}
}
});
origOpen.apply(this, arguments);
};
},
cookie: {
get(key) {
if (!document.cookie.includes(key)) {
Expand Down Expand Up @@ -295,16 +351,20 @@ const djdt = {
},
},
};

window.djdt = {
show_toolbar: djdt.show_toolbar,
hide_toolbar: djdt.hide_toolbar,
init: djdt.init,
abort: djdt.abort,
close: djdt.hide_one_level,
cookie: djdt.cookie,
};

if (document.readyState !== "loading") {
djdt.init();
} else {
document.addEventListener("DOMContentLoaded", djdt.init);
document.addEventListener("DOMContentLoaded", djdt.init, {
signal: controller.signal,
});
}
Loading