Add a "clear" button for quick search

Fixes #9857
This commit is contained in:
Jonathan Senecal 2022-08-09 17:32:20 -04:00 committed by jeremystretch
parent aabe8f7c5b
commit f74b7aa7ac
13 changed files with 169 additions and 96 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -27,6 +27,23 @@ function handleSearchDropdownClick(event: Event, button: HTMLButtonElement): voi
} }
} }
/**
* Show/hide quicksearch clear button.
*
* @param event "keyup" or "search" event for the quicksearch input
*/
function quickSearchEventHandler(event: Event): void {
const quicksearch = event.currentTarget as HTMLInputElement;
const inputgroup = quicksearch.parentElement as HTMLDivElement;
if (isTruthy(inputgroup)) {
if (quicksearch.value === "") {
inputgroup.classList.add("hide-last-child");
} else {
inputgroup.classList.remove("hide-last-child");
}
}
}
/** /**
* Initialize Search Bar Elements. * Initialize Search Bar Elements.
*/ */
@ -40,8 +57,35 @@ function initSearchBar(): void {
} }
} }
/**
* Initialize Quicksearch Event listener/handlers.
*/
function initQuickSearch(): void {
const quicksearch = document.getElementById("quicksearch") as HTMLInputElement;
const clearbtn = document.getElementById("quicksearch_clear") as HTMLButtonElement;
if (isTruthy(quicksearch)) {
quicksearch.addEventListener("keyup", quickSearchEventHandler, {
passive: true
})
quicksearch.addEventListener("search", quickSearchEventHandler, {
passive: true
})
if (isTruthy(clearbtn)) {
clearbtn.addEventListener("click", async () => {
const search = new Event('search');
quicksearch.value = '';
await new Promise(f => setTimeout(f, 100));
quicksearch.dispatchEvent(search);
}, {
passive: true
})
}
}
}
export function initSearch(): void { export function initSearch(): void {
for (const func of [initSearchBar]) { for (const func of [initSearchBar]) {
func(); func();
} }
initQuickSearch();
} }

View File

@ -416,6 +416,27 @@ nav.search {
} }
} }
// Styles for the quicksearch and its clear button;
// Overrides input-group styles and adds transition effects
.quicksearch {
input[type="search"] {
border-radius: $border-radius !important;
}
button {
margin-left: -32px !important;
z-index: 100 !important;
outline: none !important;
border-radius: $border-radius !important;
transition: visibility 0s, opacity 0.2s linear;
}
button :hover {
opacity: 50%;
transition: visibility 0s, opacity 0.1s linear;
}
}
main.layout { main.layout {
display: flex; display: flex;
flex-wrap: nowrap; flex-wrap: nowrap;

View File

@ -34,3 +34,11 @@ a[type='button'] {
.badge { .badge {
font-size: $font-size-xs; font-size: $font-size-xs;
} }
/* clears the 'X' in search inputs from webkit browsers */
input[type='search']::-webkit-search-decoration,
input[type='search']::-webkit-search-cancel-button,
input[type='search']::-webkit-search-results-button,
input[type='search']::-webkit-search-results-decoration {
-webkit-appearance: none !important;
}

View File

@ -92,6 +92,10 @@ $input-focus-color: $input-color;
$input-placeholder-color: $gray-700; $input-placeholder-color: $gray-700;
$input-plaintext-color: $body-color; $input-plaintext-color: $body-color;
input {
color-scheme: dark;
}
$form-check-input-active-filter: brightness(90%); $form-check-input-active-filter: brightness(90%);
$form-check-input-bg: $input-bg; $form-check-input-bg: $input-bg;
$form-check-input-border: 1px solid rgba(255, 255, 255, 0.25); $form-check-input-border: 1px solid rgba(255, 255, 255, 0.25);

View File

@ -22,7 +22,6 @@ $theme-colors: (
'danger': $danger, 'danger': $danger,
'light': $light, 'light': $light,
'dark': $dark, 'dark': $dark,
// General-purpose palette // General-purpose palette
'blue': $blue-500, 'blue': $blue-500,
'indigo': $indigo-500, 'indigo': $indigo-500,
@ -36,7 +35,7 @@ $theme-colors: (
'cyan': $cyan-500, 'cyan': $cyan-500,
'gray': $gray-500, 'gray': $gray-500,
'black': $black, 'black': $black,
'white': $white, 'white': $white
); );
$light: $gray-200; $light: $gray-200;

View File

@ -42,3 +42,9 @@ table td {
visibility: visible !important; visibility: visible !important;
} }
} }
// Hides the last child of an element
.hide-last-child :last-child {
visibility: hidden;
opacity: 0;
}

View File

@ -4,84 +4,85 @@
{% load static %} {% load static %}
{% block content %} {% block content %}
<div class="row mb-3 justify-content-between"> <div class="row mb-3 justify-content-between">
<div class="col col-12 col-lg-4 my-3 my-lg-0 d-flex noprint table-controls"> <div class="col col-12 col-lg-4 my-3 my-lg-0 d-flex noprint table-controls">
<div class="input-group input-group-sm"> <div class="input-group input-group-sm quicksearch hide-last-child">
<input <input type="search" results=5 name="q" id="quicksearch" class="form-control" placeholder="Quick search"
type="text" hx-get="{{ request.full_path }}" hx-target="#object_list" hx-trigger="keyup changed delay:500ms, search" />
name="q" <button class="btn bg-transparent" type="button" id="quicksearch_clear"><i
class="form-control" class="mdi mdi-close-circle"></i></button>
placeholder="Quick search"
hx-get="{{ request.full_path }}"
hx-target="#object_list"
hx-trigger="keyup changed delay:500ms"
/>
</div>
</div> </div>
<div class="col col-md-3 mb-0 d-flex noprint table-controls"> </div>
<div class="input-group input-group-sm justify-content-end"> <div class="col col-md-3 mb-0 d-flex noprint table-controls">
{% if request.user.is_authenticated %} <div class="input-group input-group-sm justify-content-end">
<button {% if request.user.is_authenticated %}
type="button" <button type="button" class="btn btn-sm btn-outline-dark" data-bs-toggle="modal"
class="btn btn-sm btn-outline-dark" data-bs-target="#DeviceInterfaceTable_config" title="Configure Table">
data-bs-toggle="modal" <i class="mdi mdi-cog"></i> Configure Table
data-bs-target="#DeviceInterfaceTable_config" </button>
title="Configure Table"> {% endif %}
<i class="mdi mdi-cog"></i> Configure Table <button class="btn btn-sm btn-outline-dark dropdown-toggle" type="button" data-bs-toggle="dropdown"
</button> aria-expanded="false">
{% endif %} <i class="mdi mdi-eye"></i>
<button class="btn btn-sm btn-outline-dark dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false"> </button>
<i class="mdi mdi-eye"></i> <ul class="dropdown-menu">
</button> <button type="button" class="dropdown-item toggle-enabled" data-state="show">Hide Enabled</button>
<ul class="dropdown-menu"> <button type="button" class="dropdown-item toggle-disabled" data-state="show">Hide Disabled</button>
<button type="button" class="dropdown-item toggle-enabled" data-state="show">Hide Enabled</button> </ul>
<button type="button" class="dropdown-item toggle-disabled" data-state="show">Hide Disabled</button> </div>
</ul> </div>
</div> </div>
<form method="post">
{% csrf_token %}
<div class="card">
<div class="card-body" id="object_list">
{% include 'htmx/table.html' %}
</div> </div>
</div> </div>
<form method="post"> <div class="noprint bulk-buttons">
{% csrf_token %} <div class="bulk-button-group">
{% if perms.dcim.change_interface %}
<button type="submit" name="_rename"
<div class="card"> formaction="{% url 'dcim:interface_bulk_rename' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}"
<div class="card-body" id="object_list"> class="btn btn-outline-warning btn-sm">
{% include 'htmx/table.html' %} <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
</div> </button>
<button type="submit" name="_edit"
formaction="{% url 'dcim:interface_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}"
class="btn btn-warning btn-sm">
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
</button>
<button type="submit" name="_disconnect"
formaction="{% url 'dcim:interface_bulk_disconnect' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}"
class="btn btn-outline-danger btn-sm">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
</button>
{% endif %}
{% if perms.dcim.delete_interface %}
<button type="submit" name="_delete"
formaction="{% url 'dcim:interface_bulk_delete' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}"
class="btn btn-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
</button>
{% endif %}
</div> </div>
{% if perms.dcim.add_interface %}
<div class="noprint bulk-buttons"> <div class="bulk-button-group">
<div class="bulk-button-group"> <a href="{% url 'dcim:interface_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}"
{% if perms.dcim.change_interface %} class="btn btn-primary btn-sm">
<button type="submit" name="_rename" formaction="{% url 'dcim:interface_bulk_rename' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}" class="btn btn-outline-warning btn-sm"> <i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Interfaces
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename </a>
</button>
<button type="submit" name="_edit" formaction="{% url 'dcim:interface_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}" class="btn btn-warning btn-sm">
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
</button>
<button type="submit" name="_disconnect" formaction="{% url 'dcim:interface_bulk_disconnect' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
</button>
{% endif %}
{% if perms.dcim.delete_interface %}
<button type="submit" name="_delete" formaction="{% url 'dcim:interface_bulk_delete' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}" class="btn btn-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
</button>
{% endif %}
</div>
{% if perms.dcim.add_interface %}
<div class="bulk-button-group">
<a href="{% url 'dcim:interface_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}" class="btn btn-primary btn-sm">
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Interfaces
</a>
</div>
{% endif %}
</div> </div>
</form> {% endif %}
</div>
</form>
{% endblock %} {% endblock %}
{% block modals %} {% block modals %}
{{ block.super }} {{ block.super }}
{% table_config_form table %} {% table_config_form table %}
{% endblock modals %} {% endblock modals %}

View File

@ -2,31 +2,21 @@
<div class="row mb-3 justify-content-between"> <div class="row mb-3 justify-content-between">
<div class="table-controls noprint col col-12 col-md-8 col-lg-4"> <div class="table-controls noprint col col-12 col-md-8 col-lg-4">
<div class="input-group input-group-sm"> <div class="input-group input-group-sm quicksearch hide-last-child">
<input <input type="search" results=5 name="q" id="quicksearch" class="form-control" placeholder="Quick search"
type="text" hx-get="{{ request.full_path }}" hx-target="#object_list" hx-trigger="keyup changed delay:500ms, search" />
name="q" <button class="btn bg-transparent" type="button" id="quicksearch_clear"><i
class="form-control" class="mdi mdi-close-circle"></i></button>
placeholder="Quick search"
hx-get="{{ request.full_path }}"
hx-target="#object_list"
hx-trigger="keyup changed delay:500ms"
/>
</div> </div>
</div> </div>
<div class="table-controls noprint col col-md-3 mb-0"> <div class="table-controls noprint col col-md-3 mb-0">
{% if request.user.is_authenticated and table_modal %} {% if request.user.is_authenticated and table_modal %}
<div class="table-configure input-group input-group-sm"> <div class="table-configure input-group input-group-sm">
<button <button type="button" data-bs-toggle="modal" title="Configure Table" data-bs-target="#{{ table_modal }}"
type="button" class="btn btn-sm btn-outline-dark">
data-bs-toggle="modal" <i class="mdi mdi-cog"></i> Configure Table
title="Configure Table" </button>
data-bs-target="#{{ table_modal }}" </div>
class="btn btn-sm btn-outline-dark"
>
<i class="mdi mdi-cog"></i> Configure Table
</button>
</div>
{% endif %} {% endif %}
</div> </div>
</div> </div>