Skip to content

Commit

Permalink
support multi-select of incident types on Incidents page
Browse files Browse the repository at this point in the history
This also swaps us over to using the URL fragment (i.e. the part after
the "#") to save filter parameters. Those allow for longer inputs than
search params, and they're fully client-side, which is fine for our
purposes.

To support that, I had to get a bunch of onclick handlers to return
false, to prevent redirects to href="#", since that would stomp over
my attempts to set the fragment.
  • Loading branch information
srabraham committed Feb 7, 2025
1 parent 78c7e32 commit 4b935f2
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 95 deletions.
39 changes: 18 additions & 21 deletions src/ims/element/incident/incidents_template/template.xhtml
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,14 @@
<span class="selection">All</span>
</button>
<ul class="dropdown-menu">
<li id="show_state_all" onclick="showState('all', true);">
<a href="#" class="name dropdown-item">All States</a>
<li id="show_state_all">
<a href="#" class="name dropdown-item" onclick="showState('all', true); return false;">All States</a>
</li>
<li id="show_state_open" onclick="showState('open', true);">
<a href="#" class="name dropdown-item">Open</a>
<li id="show_state_open">
<a href="#" class="name dropdown-item" onclick="showState('open', true); return false;">Open</a>
</li>
<li id="show_state_active" onclick="showState('active', true);">
<a href="#" class="name dropdown-item">Active</a>
<li id="show_state_active">
<a href="#" class="name dropdown-item" onclick="showState('active', true); return false;">Active</a>
</li>
</ul>

Expand All @@ -82,19 +82,19 @@
<span class="selection">All Days</span>
</button>
<ul class="dropdown-menu">
<li id="show_days_all" onclick="showDays('all', true);">
<li id="show_days_all" onclick="showDays('all', true); return false;">
<a href="#" class="name dropdown-item">All Days</a>
</li>
<li id="show_days_0" onclick="showDays(0, true);">
<li id="show_days_0" onclick="showDays(0, true); return false;">
<a href="#" class="name dropdown-item">Today</a>
</li>
<li id="show_days_1" onclick="showDays(1, true);">
<li id="show_days_1" onclick="showDays(1, true); return false;">
<a href="#" class="name dropdown-item">Last 2 Days</a>
</li>
<li id="show_days_2" onclick="showDays(2, true);">
<li id="show_days_2" onclick="showDays(2, true); return false;">
<a href="#" class="name dropdown-item">Last 3 Days</a>
</li>
<li id="show_days_3" onclick="showDays(3, true);">
<li id="show_days_3" onclick="showDays(3, true); return false;">
<a href="#" class="name dropdown-item">Last 4 Days</a>
</li>
</ul>
Expand All @@ -104,15 +104,12 @@
type="button"
class="btn btn-light btn-sm dropdown-toggle"
data-bs-toggle="dropdown"
data-bs-auto-close="outside"
>

<span class="selection">All Types</span>
Types
</button>
<ul id="ul_show_type" class="dropdown-menu">
<li id="show_type_all" onclick="showType('all', true);">
<a href="#" class="name dropdown-item">All Types</a>
</li>
<!-- Additional li will be inserted here by JQuery -->
<!-- li will be inserted here by JQuery -->
</ul>

<button
Expand All @@ -125,16 +122,16 @@
<span class="selection">All Rows</span>
</button>
<ul class="dropdown-menu">
<li id="show_rows_all" onclick="showRows('all', true);">
<li id="show_rows_all" onclick="showRows('all', true); return false;">
<a href="#" class="name dropdown-item">All Rows</a>
</li>
<li id="show_rows_25" onclick="showRows( 25, true);">
<li id="show_rows_25" onclick="showRows( 25, true); return false;">
<a href="#" class="name dropdown-item">25 Rows</a>
</li>
<li id="show_rows_50" onclick="showRows( 50, true);">
<li id="show_rows_50" onclick="showRows( 50, true); return false;">
<a href="#" class="name dropdown-item">50 Rows</a>
</li>
<li id="show_rows_100" onclick="showRows( 100, true);">
<li id="show_rows_100" onclick="showRows( 100, true); return false;">
<a href="#" class="name dropdown-item">100 Rows</a>
</li>
</ul>
Expand Down
36 changes: 18 additions & 18 deletions src/ims/element/incident/reports_template/template.xhtml
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,20 @@
<span class="selection">All Days</span>
</button>
<ul class="dropdown-menu">
<li id="show_days_all" onclick="showDays('all', true);">
<a href="#" class="name dropdown-item">All Days</a>
<li id="show_days_all">
<a href="#" class="name dropdown-item" onclick="showDays('all', true); return false;">All Days</a>
</li>
<li id="show_days_0" onclick="showDays(0, true);">
<a href="#" class="name dropdown-item">Today</a>
<li id="show_days_0">
<a href="#" class="name dropdown-item" onclick="showDays(0, true); return false;">Today</a>
</li>
<li id="show_days_1" onclick="showDays(1, true);">
<a href="#" class="name dropdown-item">Last 2 Days</a>
<li id="show_days_1">
<a href="#" class="name dropdown-item" onclick="showDays(1, true); return false;">Last 2 Days</a>
</li>
<li id="show_days_2" onclick="showDays(2, true);">
<a href="#" class="name dropdown-item">Last 3 Days</a>
<li id="show_days_2">
<a href="#" class="name dropdown-item" onclick="showDays(2, true); return false;">Last 3 Days</a>
</li>
<li id="show_days_3" onclick="showDays(3, true);">
<a href="#" class="name dropdown-item">Last 4 Days</a>
<li id="show_days_3">
<a href="#" class="name dropdown-item" onclick="showDays(3, true); return false;">Last 4 Days</a>
</li>
</ul>

Expand All @@ -86,17 +86,17 @@
<span class="selection">All Rows</span>
</button>
<ul class="dropdown-menu">
<li id="show_rows_all" onclick="showRows('all', true);">
<a href="#" class="name dropdown-item">All Rows</a>
<li id="show_rows_all">
<a href="#" class="name dropdown-item" onclick="showRows('all', true); return false;">All Rows</a>
</li>
<li id="show_rows_25" onclick="showRows( 25, true);">
<a href="#" class="name dropdown-item">25 Rows</a>
<li id="show_rows_25">
<a href="#" class="name dropdown-item" onclick="showRows( 25, true); return false;">25 Rows</a>
</li>
<li id="show_rows_50" onclick="showRows( 50, true);">
<a href="#" class="name dropdown-item">50 Rows</a>
<li id="show_rows_50">
<a href="#" class="name dropdown-item" onclick="showRows( 50, true); return false;">50 Rows</a>
</li>
<li id="show_rows_100" onclick="showRows( 100, true);">
<a href="#" class="name dropdown-item">100 Rows</a>
<li id="show_rows_100">
<a href="#" class="name dropdown-item" onclick="showRows( 100, true); return false;">100 Rows</a>
</li>
</ul>
</div>
Expand Down
16 changes: 9 additions & 7 deletions src/ims/element/static/field_reports.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,12 +228,13 @@ function initTableButtons() {
.children(".col-sm-6:first")
.replaceWith($("#button_container"));

const urlParams = new URLSearchParams(window.location.search);
const fragment = window.location.hash.startsWith("#") ? window.location.hash.substring(1) : window.location.hash;
const fragmentParams = new URLSearchParams(fragment);

// Set button defaults

showDays(urlParams.get("days")??defaultDaysBack, false);
showRows(urlParams.get("rows")??defaultRows, false);
showDays(fragmentParams.get("days")??defaultDaysBack, false);
showRows(fragmentParams.get("rows")??defaultRows, false);
}


Expand All @@ -256,7 +257,7 @@ function initSearchField() {
const searchInput = document.getElementById("search_input");

const searchAndDraw = function () {
pushWindowState();
replaceWindowState();
let q = searchInput.value;
let isRegex = false;
if (q.startsWith("/") && q.endsWith("/")) {
Expand All @@ -267,8 +268,9 @@ function initSearchField() {
fieldReportsTable.draw();
}

const urlParams = new URLSearchParams(window.location.search);
const queryString = urlParams.get("q");
const fragment = window.location.hash.startsWith("#") ? window.location.hash.substring(1) : window.location.hash;
const fragmentParams = new URLSearchParams(fragment);
const queryString = fragmentParams.get("q");
if (queryString) {
searchInput.value = queryString;
searchAndDraw();
Expand Down Expand Up @@ -438,6 +440,6 @@ function replaceWindowState() {

// Next step is to create search params for the other filters too

const newURL = `${urlReplace(url_viewFieldReports)}?${new URLSearchParams(newParams).toString()}`;
const newURL = `${urlReplace(url_viewFieldReports)}#${new URLSearchParams(newParams).toString()}`;
window.history.replaceState(null, null, newURL);
}
111 changes: 62 additions & 49 deletions src/ims/element/static/incidents.js
Original file line number Diff line number Diff line change
Expand Up @@ -312,29 +312,41 @@ function initTableButtons() {
.children(".col-sm-6:first")
.replaceWith($("#button_container"));

$(document).on('click', '.dropdown-item-checkable', function(event) {
event.preventDefault();
$(this).toggleClass('dropdown-item-checked');
showCheckedTypes(true);
});

const $typeFilter = $("#ul_show_type");
for (const i in allIncidentTypes) {
const type = allIncidentTypes[i];
const $a = $("<a>", {class: "name dropdown-item", href:"#"});
const $a = $("<a>", {
class: "dropdown-item dropdown-item-checkable dropdown-item-checked",
href:"#",
});
$a.text(type.toString());
const $li = $("<li>", {id: "show_type_" + i, onclick: "showType(" + i + ", true)"});
$li.append($a);
$typeFilter.append($li);
$typeFilter.append($a);
}

const urlParams = new URLSearchParams(window.location.search);
const fragment = window.location.hash.startsWith("#") ? window.location.hash.substring(1) : window.location.hash;
const fragmentParams = new URLSearchParams(fragment);

// Set button defaults

const type = urlParams.get("type");
if (type && allIncidentTypes.indexOf(type) !== -1) {
showType(allIncidentTypes.indexOf(type), false);
} else {
showType(defaultType, false);
const types = fragmentParams.getAll("type");
const validTypes = [];
for (const t of types) {
console.log(`reading type ${t} checking`);
if (t && allIncidentTypes.indexOf(t) !== -1) {
validTypes.push(t);
}
}
showState(urlParams.get("state")??defaultState, false);
showDays(urlParams.get("days")??defaultDaysBack, false);
showRows(urlParams.get("rows")??defaultRows, false);
setCheckedTypes(validTypes);
showCheckedTypes(false);
showState(fragmentParams.get("state")??defaultState, false);
showDays(fragmentParams.get("days")??defaultDaysBack, false);
showRows(fragmentParams.get("rows")??defaultRows, false);
}


Expand Down Expand Up @@ -368,8 +380,9 @@ function initSearchField() {
incidentsTable.draw();
}

const urlParams = new URLSearchParams(window.location.search);
const queryString = urlParams.get("q");
const fragment = window.location.hash.startsWith("#") ? window.location.hash.substring(1) : window.location.hash;
const fragmentParams = new URLSearchParams(fragment);
const queryString = fragmentParams.get("q");
if (queryString) {
searchInput.value = queryString;
searchAndDraw();
Expand Down Expand Up @@ -456,19 +469,11 @@ function initSearch() {
return false
}

switch (_showType) {
case null:
// fallthrough
case "all":
break;
default:
if (_showType >= 0 && _showType < allIncidentTypes.length) {
const st = allIncidentTypes[_showType];
if (!(incident.incident_types??[]).includes(st)) {
return false;
}
}
break;
if (_showTypes && _showTypes.length > 0) {
const intersect = Object.values(incident.incident_types).filter(t => _showTypes.includes(t)).length > 0;
if (!intersect) {
return false;
}
}

return true;
Expand Down Expand Up @@ -547,26 +552,34 @@ function showDays(daysBackToShow, replaceState) {
// Show type button handling
//

// _showType will be one of:
// "all" or null (meaning show everything)
// a numeric index into allIncidentTypes
let _showType = null;
const defaultType = "all";

function showType(typeToShow, replaceState) {
// see _showType above for values of "typeToShow"
const id = typeToShow??defaultType;
// list of Incident Types to show, in text form
let _showTypes = [];

const $menu = $("#show_type");
const $item = $("#show_type_" + id);
function setCheckedTypes(types) {
for (const $type of $('#ul_show_type > a')) {
if (types.length === 0 || types.includes($type.innerHTML)) {
$type.classList.add("dropdown-item-checked")
} else {
$type.classList.remove("dropdown-item-checked")
}
}
}

// Get title from selected item
const selection = $item.children(".name").html();
function readCheckedTypes() {
const checkedTypes = [];
for (const $type of $('#ul_show_type > a')) {
if ($type.classList.contains("dropdown-item-checked")) {
checkedTypes.push($type.innerHTML);
}
}
return checkedTypes;
}

// Update menu title to reflect selected item
$menu.children(".selection").html(selection);
function showCheckedTypes(replaceState) {
_showTypes = readCheckedTypes();

_showType = typeToShow;
const numTypes = _showTypes.length === allIncidentTypes.length ? "All" : _showTypes.length;
document.getElementById("show_type").textContent = `${numTypes} Types`;

if (replaceState) {
replaceWindowState();
Expand Down Expand Up @@ -618,8 +631,10 @@ function replaceWindowState() {
if (searchVal) {
newParams.push(["q", searchVal]);
}
if (_showType != null && _showType !== defaultType) {
newParams.push(["type", allIncidentTypes[_showType]]);
if (_showTypes != null && _showTypes.length !== allIncidentTypes.length) {
for (const t of _showTypes) {
newParams.push(["type", t]);
}
}
if (_showState != null && _showState !== defaultState) {
newParams.push(["state", _showState]);
Expand All @@ -631,8 +646,6 @@ function replaceWindowState() {
newParams.push(["rows", _showRows]);
}

// Next step is to create search params for the other filters too

const newURL = `${viewIncidentsURL}?${new URLSearchParams(newParams).toString()}`;
const newURL = `${viewIncidentsURL}#${new URLSearchParams(newParams).toString()}`;
window.history.replaceState(null, null, newURL);
}
11 changes: 11 additions & 0 deletions src/ims/element/static/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -316,3 +316,14 @@ h1 {
.hidden {
display: none !important;
}

.dropdown-item-checkable {
padding: var(--bs-dropdown-item-padding-y) 1.5rem;
}

.dropdown-item-checked::before {
position: absolute;
left: .4rem;
content: '✓';
font-weight: 600;
}

0 comments on commit 4b935f2

Please sign in to comment.