Skip to content

Commit

Permalink
support multi-select of incident types on Incidents page (#1582)
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 authored Feb 7, 2025
1 parent 78c7e32 commit 700175b
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 700175b

Please sign in to comment.