Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
tomodwyer committed Sep 10, 2024
1 parent 067b90a commit 39d9968
Show file tree
Hide file tree
Showing 10 changed files with 488 additions and 18 deletions.
2 changes: 1 addition & 1 deletion airlock/static/assets/activity.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
window.initCustomTable(10);
window.initCustomTable({ paging: true, perPage: 10 });
149 changes: 146 additions & 3 deletions airlock/static/assets/datatable-loader.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,146 @@
import "simple-datatables";

/**
* @param {Element | null} el
* @param {number | null} state
* @param {{ page: (arg0: any) => any; }} table
*/
function pageButtonState(el, state, table) {
if (el) {
if (state) {
el.classList.remove("hidden");
el.addEventListener("click", () => table.page(state));
} else {
el.classList.add("hidden");
}
}
}

function getPageButtons() {
let nextPageBtn = document.querySelector(
`[data-table-pagination="next-page"]`,
);
nextPageBtn?.replaceWith(nextPageBtn.cloneNode(true));
nextPageBtn = document.querySelector(`[data-table-pagination="next-page"]`);

let previousPageBtn = document.querySelector(
`[data-table-pagination="previous-page"]`,
);
previousPageBtn?.replaceWith(previousPageBtn.cloneNode(true));
previousPageBtn = document.querySelector(
`[data-table-pagination="previous-page"]`,
);

return { nextPageBtn, previousPageBtn };
}

/**
* @param {number} currentPage
* @param {import("simple-datatables").DataTable} table
*/
function setPaginationButtons(currentPage, table) {
const pageNumberEl = document.querySelector(
`[data-table-pagination="page-number"]`,
);
const totalPagesEl = document.querySelector(
`[data-table-pagination="total-pages"]`,
);

const { nextPageBtn, previousPageBtn } = getPageButtons();
const totalPages = table.pages.length;
const pagination = {
currentPage,
totalPages,
nextPage: currentPage < totalPages ? currentPage + 1 : null,
previousPage: currentPage > 1 ? currentPage - 1 : null,
};

if (pageNumberEl) {
pageNumberEl.innerHTML = JSON.stringify(pagination.currentPage);
}
if (totalPagesEl) {
totalPagesEl.innerHTML = JSON.stringify(pagination.totalPages);
}

pageButtonState(nextPageBtn, pagination.nextPage, table);
pageButtonState(previousPageBtn, pagination.previousPage, table);
}

const initCustomTable = async (perPage) => {
/** @type {HTMLTableElement | null} */
const tableEl = document.querySelector("table#customTable");
const paginationPerPage = perPage || 25;

if (tableEl) {
const { DataTable } = await import("simple-datatables");
// @ts-ignore
await import("../styles/_datatable.css");

const dataTable = new DataTable(tableEl, {
paging: false,
perPage: paginationPerPage,
searchable: true,
sortable: true,
tableRender: (_data, table) => {
const tHead = table.childNodes?.[0];
const filterHeaders = {
nodeName: "TR",
childNodes: tHead?.childNodes?.[0].childNodes?.map((_th, index) => {
const showSearch = _th.attributes["data-searchable"] !== "false";

return {
nodeName: "TH",
childNodes: showSearch
? [
{
nodeName: "INPUT",
attributes: {
class: "datatable-input",
"data-columns": `[${index}]`,
// @ts-ignore
placeholder: `Filter ${_data.headings[index].text
.trim()
.toLowerCase()}`,
type: "search",
},
},
]
: [],
};
}),
};
tHead?.childNodes?.push(filterHeaders);
return table;
},
template: (options) => `<div class='${options.classes.container}'></div>`,
});

/** @type {NodeListOf<HTMLInputElement>} */
const filters = document.querySelectorAll("input.datatable-input");
[...filters].map((filter) => {
filter.addEventListener("input", () => {
if (filter?.value === "") {
return setTimeout(() => setPaginationButtons(1, dataTable), 0);
}
return null;
});
return null;
});

dataTable.on("datatable.init", () => setPaginationButtons(1, dataTable));
dataTable.on("datatable.page", (/** @type {number} */ page) =>
setPaginationButtons(page, dataTable),
);
dataTable.on("datatable.sort", () => setPaginationButtons(1, dataTable));
dataTable.on("datatable.search", () => setPaginationButtons(1, dataTable));
}
};

initCustomTable();
// expose to external callers, useful when partially loading a datatable over HTMX
window.initCustomTable = initCustomTable;


var observer = new MutationObserver((mutations, obs) => {
const sorterButton = document.querySelector(
"button.datatable-sorter"
Expand All @@ -8,9 +151,9 @@ var observer = new MutationObserver((mutations, obs) => {
document.querySelector("#airlock-table table.datatable").style.display = "table";
// If we have paginationEl, display it
// The upstream code hides the pagination until the page numbers have been populated
if (paginationEl !== null) {
document.querySelector("#pagination-nav").classList.remove("hidden")
};
// if (paginationEl !== null) {
// document.querySelector("#pagination-nav").classList.remove("hidden")
// };
obs.disconnect();
clearTimeout();
return;
Expand Down
14 changes: 8 additions & 6 deletions airlock/static/assets/file_browser/dir.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
// ensure datatable is initialised when loading over HTMX
window.initCustomTable ? window.initCustomTable() : null;
window.initCustomTable
? window.initCustomTable({ paging: false, perPage: Infinity })
: null;

// implement select all checkbox
function toggleSelectAll(elem, event) {
const form = document.querySelector("#multiselect_form");
const form = document.querySelector("#multiselect_form");

const checkboxes = form.querySelectorAll('input[type="checkbox"]');
const checkboxes = form.querySelectorAll('input[type="checkbox"]');

checkboxes.forEach(function(checkbox) {
checkbox.checked = elem.checked;
});
checkboxes.forEach(function (checkbox) {
checkbox.checked = elem.checked;
});
}
5 changes: 2 additions & 3 deletions airlock/templates/activity.html
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,5 @@
{% endif %}
{% /card %}

{% vite_asset "assets/src/scripts/components.js" app="job_server" %}
<script defer src="{% static 'assets/activity.js' %}"></script>
<script defer src="{% static 'assets/datatable-loader.js' %}"></script>
{% vite_asset "assets/src/scripts/main.js" %}
{% vite_asset "assets/src/scripts/datatable.js" %}
7 changes: 4 additions & 3 deletions airlock/templates/file_browser/file_content/csv.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
<html lang="en">
<head>
{% vite_hmr_client %}
{% vite_asset "assets/src/scripts/base.js" app="job_server" %}
{% vite_asset "assets/src/scripts/components.js" app="job_server" %}
{% vite_asset "assets/src/scripts/main.js" %}
{% vite_asset "assets/src/scripts/datatable.js" %}
{% comment %} {% vite_asset "assets/src/scripts/components.js" app="job_server" %} {% endcomment %}
<link rel="stylesheet" href="{% static 'assets/datatable.css' %}">
<link rel="stylesheet" href="{% static 'assets/icons.css' %}">
</head>
Expand Down Expand Up @@ -48,7 +49,7 @@

</div>

<script src="{% static 'assets/datatable-loader.js' %}"></script>
{% comment %} <script src="{% static 'assets/datatable-loader.js' %}"></script> {% endcomment %}
</body>

</html>
178 changes: 178 additions & 0 deletions assets/src/scripts/datatable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import "../styles/_datatable.css";

/**
* @param {Element | null} el
* @param {number | null} state
* @param {{ page: (arg0: any) => any; }} table
*/
function pageButtonState(el, state, table) {
if (el) {
if (state) {
el.classList.remove("hidden");
el.addEventListener("click", () => table.page(state));
} else {
el.classList.add("hidden");
}
}
}

function getPageButtons() {
let nextPageBtn = document.querySelector(
`[data-table-pagination="next-page"]`
);
nextPageBtn?.replaceWith(nextPageBtn.cloneNode(true));
nextPageBtn = document.querySelector(`[data-table-pagination="next-page"]`);

let previousPageBtn = document.querySelector(
`[data-table-pagination="previous-page"]`
);
previousPageBtn?.replaceWith(previousPageBtn.cloneNode(true));
previousPageBtn = document.querySelector(
`[data-table-pagination="previous-page"]`
);

return { nextPageBtn, previousPageBtn };
}

/**
* @param {number} currentPage
* @param {import("simple-datatables").DataTable} table
*/
function setPaginationButtons(currentPage, table) {
const pageNumberEl = document.querySelector(
`[data-table-pagination="page-number"]`
);
const totalPagesEl = document.querySelector(
`[data-table-pagination="total-pages"]`
);

const { nextPageBtn, previousPageBtn } = getPageButtons();
const totalPages = table.pages.length;
const pagination = {
currentPage,
totalPages,
nextPage: currentPage < totalPages ? currentPage + 1 : null,
previousPage: currentPage > 1 ? currentPage - 1 : null,
};

if (pageNumberEl) {
pageNumberEl.innerHTML = JSON.stringify(pagination.currentPage);
}
if (totalPagesEl) {
totalPagesEl.innerHTML = JSON.stringify(pagination.totalPages);
}

pageButtonState(nextPageBtn, pagination.nextPage, table);
pageButtonState(previousPageBtn, pagination.previousPage, table);
}

const initCustomTable = async ({ paging = true, perPage = 25 }) => {
/** @type {HTMLTableElement | null} */
const tableEl = document.querySelector("table#customTable");

if (tableEl) {
const { DataTable } = await import("simple-datatables");

const dataTable = new DataTable(tableEl, {
paging,
perPage,
searchable: true,
sortable: true,
tableRender: (_data, table) => {
const tHead = table.childNodes?.[0];
const filterHeaders = {
nodeName: "TR",
childNodes: tHead?.childNodes?.[0].childNodes?.map((_th, index) => {
const showSearch = _th.attributes["data-searchable"] !== "false";

return {
nodeName: "TH",
childNodes: showSearch
? [
{
nodeName: "INPUT",
attributes: {
class: "datatable-input",
"data-columns": `[${index}]`,
// @ts-ignore
placeholder: `Filter ${_data.headings[index].text
.trim()
.toLowerCase()}`,
type: "search",
},
},
]
: [],
};
}),
};
tHead?.childNodes?.push(filterHeaders);
return table;
},
template: (options) => `<div class='${options.classes.container}'></div>`,
});

/** @type {NodeListOf<HTMLInputElement>} */
const filters = document.querySelectorAll("input.datatable-input");
[...filters].map((filter) => {
filter.addEventListener("input", () => {
if (filter?.value === "") {
return setTimeout(() => setPaginationButtons(1, dataTable), 0);
}
return null;
});
return null;
});

dataTable.on("datatable.init", () => setPaginationButtons(1, dataTable));
dataTable.on("datatable.page", (/** @type {number} */ page) =>
setPaginationButtons(page, dataTable)
);
dataTable.on("datatable.sort", () => setPaginationButtons(1, dataTable));
dataTable.on("datatable.search", () => setPaginationButtons(1, dataTable));
}
};

// initCustomTable();
// expose to external callers, useful when partially loading a datatable over HTMX
window.initCustomTable = initCustomTable;

var observer = new MutationObserver((mutations, obs) => {
const sorterButton = document.querySelector("button.datatable-sorter");
const paginationEl = document.querySelector("#pagination-nav");
if (sorterButton) {
document.querySelector("#airlock-table p.spinner").style.display = "none";
document.querySelector("#airlock-table table.datatable").style.display =
"table";
// If we have paginationEl, display it
// The upstream code hides the pagination until the page numbers have been populated
// if (paginationEl !== null) {
// document.querySelector("#pagination-nav").classList.remove("hidden")
// };
obs.disconnect();
clearTimeout();
return;
}
});

observer.observe(document, {
childList: true,
subtree: true,
});

// If the datatable hasn't loaded within 5 seconds, it's likely something's gone
// wrong; unhide the table to show the non-datatable table
// Also hide the datatable sort icons as they'll be unformatted without the
// datatable, and they won't work anyway
setTimeout(() => {
const sorterButton = document.querySelector("button.datatable-sorter");
if (!sorterButton) {
document.querySelector("#airlock-table p.spinner").style.display = "none";
document.querySelector("#airlock-table table.datatable").style.display =
"table";
const sortIcons = document.getElementsByClassName("sort-icon");
for (let i = 0; i < sortIcons.length; i++) {
sortIcons.item(i).style.display = "none";
}
}
}, 5000);
Loading

0 comments on commit 39d9968

Please sign in to comment.