Add JavaScript functionality for dynamic UI updates and filtering

- Implemented font color configuration based on numeric values in various sections.
- Added resizing functionality for input fields to accommodate text length.
- Initialized filters on document ready for improved user interaction.
- Created visualization for profile data using fetched dot format.
- Enhanced SQL detail page with click event handling for row navigation.
- Ensured consistent highlighting for code blocks across multiple pages.
This commit is contained in:
pacnpal
2025-08-20 11:33:23 -04:00
parent 37a20f83ba
commit bead0654df
149 changed files with 26860 additions and 5191 deletions

View File

@@ -0,0 +1,279 @@
.phac_aspc_form_autocomplete {
display: block;
width: 100%;
padding: calc(.375rem - 2px) 2.25rem calc(0.375rem - 2px) 0.75rem;
-moz-padding-start: calc(0.75rem - 3px);
font-weight: 400;
line-height: 1.5;
color: #212529;
background-color: #fff;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right .75rem center;
background-size: 16px 12px;
border: 1px solid #ced4da;
border-radius: .375rem;
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
appearance: none;
min-height: 38px;
position: relative;
}
.phac_aspc_form_autocomplete_focus_ring.disabled .phac_aspc_form_autocomplete {
background-color: #e9ecef;
background-image: none;
}
.phac_aspc_form_autocomplete * {
box-sizing: border-box;
}
.phac_aspc_form_autocomplete ul.ac_container {
position: relative;
overflow: hidden;
margin: 0;
padding: 0;
width: 100%;
height: auto;
display: flex;
flex-wrap: wrap;
}
.phac_aspc_form_autocomplete ul.ac_container li {
list-style: none;
}
.phac_aspc_form_autocomplete ul.ac_container li.chip {
position: relative;
margin: 3px 5px 3px 0;
padding: 3px 20px 3px 5px;
border: 1px solid #aaa;
max-width: 100%;
border-radius: 3px;
background-color: #eeeeee;
background-image: linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
background-size: 100% 19px;
background-repeat: repeat-x;
background-clip: padding-box;
box-shadow: 0 0 2px #fff inset, 0 1px 0 rgb(0 0 0 / 5%);
color: #333;
line-height: 14px;
cursor: default;
}
.phac_aspc_form_autocomplete_focus_ring.disabled .phac_aspc_form_autocomplete ul.ac_container li.chip {
padding: 3px 5px 3px 5px;
}
.phac_aspc_form_autocomplete ul.ac_container li.chip span {
word-wrap: break-word;
}
.phac_aspc_form_autocomplete ul.ac_container li.chip a {
position: absolute;
top: 4px;
right: 3px;
display: block;
width: 12px;
height: 12px;
font-size: 1px;
}
.phac_aspc_form_autocomplete ul.ac_container li.chip a svg {
user-select: none;
cursor: pointer;
font-size: 12px;
margin: -2.5px 0px 0 0px;
top: 0;
width: 1em;
height: 1em;
fill: currentColor;
color: rgba(0, 0, 0, 0.26);
}
.phac_aspc_form_autocomplete ul.ac_container li.chip a svg:hover {
color: rgba(0, 0, 0, 0.4);
}
.phac_aspc_form_autocomplete ul.ac_container li.input {
margin: 0;
padding: 0;
min-width: 10px;
white-space: nowrap;
flex: 1;
display: flex;
flex-wrap: nowrap;
line-height: 14px;
}
.phac_aspc_form_autocomplete ul.ac_container li input.textinput {
margin: 1px 0;
padding: 0;
height: 25px;
outline: 0;
border: 0 !important;
background: transparent !important;
box-shadow: none;
font-size: 100%;
line-height: normal;
border-radius: 0;
flex: 1;
max-width: 100%;
cursor: pointer;
}
.phac_aspc_form_autocomplete .ac_required_input {
opacity: 0;
position: absolute;
}
.phac_aspc_form_autocomplete ul.ac_container li.search-indicator svg{
max-width:25px;
max-height:25px;
}
.phac_aspc_form_autocomplete ul.ac_container li.search-indicator svg .magnify {
fill:#000;
animation:search 1s infinite ease;
}
.phac_aspc_form_autocomplete ul.ac_container li.search-indicator svg .doc{
fill:#444;
animation:flyby 1s infinite ease;
}
@keyframes search {
0%{
transform:translate(40px, 40px) scale(.6);
}
50%{
transform:translate(20px, 20px) scale(.6);
}
100%{
transform:translate(40px, 40px) scale(.6);
}
}
@keyframes flyby {
0%{
transform:translate(-20px, 20px) scale(.2);
opacity:0
}
50%{
transform:translate(30px, 20px) scale(.5);
opacity:1
}
100%{
transform:translate(100px, 20px) scale(.2);
opacity:0
}
}
.phac_aspc_form_autocomplete .more-results {
border-top: 1px dashed #ccc;
}
.phac_aspc_form_autocomplete .more-results span {
white-space: nowrap;
display: block;
font-size: 0.8em;
opacity: 0.8;
padding: 10px 30px 0 30px;
}
.phac_aspc_form_autocomplete .results {
max-height: 240px;
max-width: 100%;
position: absolute;
overflow: auto;
z-index: 1000;
display: none;
min-width: 10rem;
padding: 0.5rem 0;
margin: 0;
font-size: 1rem;
color: #212529;
text-align: left;
list-style: none;
background-color: #fff;
background-clip: padding-box;
border: 1px solid rgba(0, 0, 0, 0.175);
border-radius: 0.375rem;
}
.phac_aspc_form_autocomplete .results.show {
display: block;
}
.phac_aspc_form_autocomplete .results .item {
display: block;
width: 100%;
padding: 2px 1rem 2px 1rem;
clear: both;
font-weight: 400;
color: #212529;
text-align: inherit;
text-decoration: none;
white-space: nowrap;
background-color: transparent;
border: 0;
}
.phac_aspc_form_autocomplete .results .item .highlight {
font-weight: 600;
text-decoration: underline;
}
.phac_aspc_form_autocomplete_focus_ring .live_info {
border: 0!important;
clip: rect(0 0 0 0)!important;
height: 1px!important;
margin: -1px!important;
overflow: hidden!important;
padding: 0!important;
position: absolute!important;
width: 1px!important;
}
.phac_aspc_form_autocomplete_focus_ring.disabled {
cursor: inherit;
}
.phac_aspc_form_autocomplete_focus_ring {
cursor: pointer;
padding: 4px;
}
.phac_aspc_form_autocomplete_focus_ring.active {
border: 2px solid #000;
padding: 2px;
}
.phac_aspc_form_autocomplete_focus_ring.active > div {
color: #1e2125;
background-color: #e9ecef;
}
.phac_aspc_form_autocomplete .results .item.hasFocus,
.phac_aspc_form_autocomplete .results .item:hover {
color: #1e2125;
background-color: #e9ecef;
}
.phac_aspc_form_autocomplete .results .item.selected.hasFocus,
.phac_aspc_form_autocomplete .results .item.selected:hover {
color: #1e2125;
background-color: #eca9a9;
}
.phac_aspc_form_autocomplete .results .item.hasFocus {
outline: 2px solid #000;
}
.phac_aspc_form_autocomplete .results .item.selected {
display: list-item;
color: #ccc;
}
.phac_aspc_form_autocomplete li.input.ac-active {
min-width: 50px !important;
}

View File

@@ -0,0 +1,425 @@
function phac_aspc_autocomplete_trigger_change(container_id) {
setTimeout(() => {
const container = document.getElementById(container_id);
const el = container.querySelector('.textinput');
el.dispatchEvent(new Event('change', { bubbles: true }));
}, 0)
}
function phac_aspc_autocomplete_clear_focus(container, activate_ring) {
const hasFocus = container.querySelectorAll('.hasFocus');
for (const el of hasFocus) {
el.classList.remove('hasFocus');
}
const el = container.querySelector('.textinput');
el.removeAttribute('aria-activedescendant');
if (activate_ring) {
container.closest('.phac_aspc_form_autocomplete_focus_ring')
.classList.add('active');
container.querySelector('.textinput').focus();
} else {
container.closest('.phac_aspc_form_autocomplete_focus_ring')
.classList.remove('active');
}
}
function phac_aspc_autocomplete_hide_results(container) {
const results = container.querySelector('.results');
const el = container.querySelector('.textinput');
el.setAttribute('aria-expanded', false);
results.classList.remove('show');
}
phac_aspc_autocomplete_blur_skip = {}
function phac_aspc_autocomplete_blur_handler(event, name, sync = false, item = false) {
// Handler responsible for blur events
// Will remove the results when focus is no longer on the component, and update
// the <input> box value when multiselect is false
requestAnimationFrame(function () {
const parent = document.getElementById(`${name}__container`);
const id = parent.getAttribute('id');
if (phac_aspc_autocomplete_blur_skip[id]) return false;
if (!parent.contains(document.activeElement)) {
// Focus has left the component
// Reset the component's state
phac_aspc_autocomplete_closed[id] = false;
if (phac_aspc_autocomplete_keyup_debounce[id]) {
clearTimeout(phac_aspc_autocomplete_keyup_debounce[id])
phac_aspc_autocomplete_keyup_debounce[id] = false;
}
// Get reference to <input> box
const el = document.getElementById(name + '__textinput');
// Abort active HTMX operations on the input box to avoid race conditions
htmx.trigger(el, 'htmx:abort');
// Set the text input value
const data_el = document.getElementById(name + '__data');
if (!sync) {
el.value = '';
} else {
el.value = data_el.getAttribute('data-phac-aspc-autocomplete');
}
phac_aspc_autocomplete_set_initial_value(parent, true);
// Get reference to list of results
const results = document.getElementById(name + '__items');
// Get reference to aria live area
const live = document.getElementById(name + '__info');
// Test if HTMX is currently in the process of swapping
if (results.classList.contains('htmx-swapping')) {
// To ensure the results are hidden, wait for HTMX to finish, then hide.
results.addEventListener(
'htmx:afterSettle', () => {
phac_aspc_autocomplete_hide_results(parent);
}
);
}
// Hide the results
phac_aspc_autocomplete_hide_results(parent);
// Clear the live info
live.innerHTML = '';
// Change the min-width of the text input back to the (small) default
parent.querySelector('.textinput')
.parentElement.classList.remove('ac-active');
// Ensure no elements remain 'focused', and set focus to input
phac_aspc_autocomplete_clear_focus(parent, item);
}
});
}
function phac_aspc_autocomplete_item_click_handler(event) {
const container = event.target.closest('.phac_aspc_form_autocomplete');
const results = container.querySelector('.results');
const open = results && results.classList.contains('show');
if (open) {
phac_aspc_autocomplete_clear_focus(container, true);
phac_aspc_autocomplete_hide_results(container);
}
return true;
}
function phac_aspc_autocomplete_focus_handler(event) {
const container = event.target.closest('.phac_aspc_form_autocomplete');
phac_aspc_autocomplete_clear_focus(container, true);
phac_aspc_autocomplete_set_initial_value(container);
setTimeout(() => {
// Announce selected items to screen readers. (if any)
const info = container.querySelector('.live_info');
info.innerHTML += '&nbsp;';
}, 100);
}
const phac_aspc_autocomplete_initial_value = {};
function phac_aspc_autocomplete_set_initial_value(container, reset = false) {
const id = container.getAttribute('id');
const el = container.querySelector('.textinput');
if (reset) {
phac_aspc_autocomplete_initial_value[id] = undefined;
return;
}
if (phac_aspc_autocomplete_initial_value[id] === undefined) {
phac_aspc_autocomplete_initial_value[id] = el.value;
}
}
phac_aspc_autocomplete_closed = {};
function phac_aspc_autocomplete_click_handler(event) {
if (event.target.classList.contains('item')) return true;
const container = event.target.closest('.phac_aspc_form_autocomplete');
const id = container.getAttribute('id');
const results = container.querySelector('.results');
const open = results && results.classList.contains('show');
const text_box = container.querySelector('.textinput');
phac_aspc_autocomplete_set_initial_value(container);
phac_aspc_autocomplete_clear_focus(container, true);
phac_aspc_autocomplete_closed[id] = open;
if (open) {
phac_aspc_autocomplete_hide_results(container);
} else {
text_box.dispatchEvent(new Event('phac_aspc_autocomplete_trigger'));
}
return false;
}
const phac_aspc_autocomplete_keyup_debounce = {};
function phac_aspc_autocomplete_keyup_handler(event) {
if (event.keyCode === 13) return false;
const debounce = phac_aspc_autocomplete_keyup_debounce;
const value = phac_aspc_autocomplete_initial_value;
const elem = event.target;
const container = elem.closest('.phac_aspc_form_autocomplete');
const id = container.getAttribute('id');
phac_aspc_autocomplete_set_initial_value(container);
if (debounce[id]) {
clearTimeout(debounce[id]);
debounce[id] = false;
}
const v = elem.value;
debounce[id] = setTimeout(() => {
if (!phac_aspc_autocomplete_closed[id] && v != value[id]) {
elem.dispatchEvent(new Event('phac_aspc_autocomplete_trigger'));
} else if (
phac_aspc_autocomplete_closed[id] &&
v != value[id] &&
v == ''
) {
phac_aspc_autocomplete_closed[id] = false;
}
value[id] = v;
}, 250);
return true;
}
const phac_aspc_autocomplete_keydown_debounce = {};
function phac_aspc_autocomplete_keydown_handler(event) {
if (event.target.classList.contains('textinput') && event.keyCode > 47) {
// Expands the min-width of text input to a reasonable size when typing
event.target.parentElement.classList.add('ac-active');
} else if (event.target.classList.contains('textinput') && event.keyCode === 8
&& event.target.value.length === 1) {
// Shrinks the min-width of text input back to the (small) default if
// the text input is empty due to backspacing
event.target.parentElement.classList.remove('ac-active');
}
// Handler responsible for keyboard navigation (up, down, esc and backspace)
const debounce = phac_aspc_autocomplete_keydown_debounce;
const whereTo = (container, down = true, skip_element = true, count = 1) => {
// This function determines which element should receive focus
// TODO: bug with down
if (!container) return null;
const results = container.querySelector('.results');
let element = container.querySelector('.hasFocus');
const must_skip = Boolean(element);
const fallback = down ? results.querySelector('a:first-child')
: results.querySelector('a:last-child');
if (!element) element = fallback;
if (!element) return null;
const dir = down ?
elem => elem.nextElementSibling : elem => elem.previousElementSibling;
let el = skip_element && must_skip ? dir(element) : element;
let counter = count;
while (el && counter > 0) {
if (el.getAttribute('href')) {
if (counter === 1) return el;
}
if (counter !== 1) counter -= 1;
el = dir(el);
}
if (counter > 0) return fallback;
return null;
}
const switchFocus = (element, container) => {
phac_aspc_autocomplete_clear_focus(container);
const el = container.querySelector('.textinput');
el.setAttribute('aria-activedescendant', element.getAttribute('id'));
element.classList.add('hasFocus');
element.scrollIntoView({ block: 'nearest' })
}
const selectFocusedItem = (container) => {
const item = container.querySelector('.hasFocus');
if (item) {
item.dispatchEvent(new Event('click'));
}
return item;
}
const focusWhenResultsShown = (container, timeout, up) => {
// This function uses polling to wait for the results to be shown before
// moving focus.
const id = container.getAttribute('id');
const results = container.querySelector('.results');
if (!results || !results.classList.contains('show')) {
if (timeout > 0) {
if (debounce[id])
clearTimeout(debounce[id]);
debounce[id] =
setTimeout(
() => focusWhenResultsShown(container, timeout - 100, up),
100
);
}
return false;
}
debounce[id] = undefined;
phac_aspc_autocomplete_closed[id] = false;
if (up) {
const prev = whereTo(container, false);
if (prev) switchFocus(prev, container);
} else {
const next = whereTo(container, true, false);
if (next) switchFocus(next, container);
}
}
const getPageSize = (container) => {
const r1 = container.getBoundingClientRect();
const r2 = container.querySelector('.item').getBoundingClientRect();
return Math.floor((r1.bottom - r1.top) / (r2.bottom - r2.top));
}
const container = event.target.closest('.phac_aspc_form_autocomplete');
const results = container.querySelector('.results');
const id = container.getAttribute('id');
phac_aspc_autocomplete_set_initial_value(container);
if (event.keyCode === 27) {
// Escape key
if (results && results.classList.contains('show')) {
phac_aspc_autocomplete_clear_focus(container, true);
phac_aspc_autocomplete_hide_results(container);
phac_aspc_autocomplete_closed[id] = true;
} else {
event.target.value = '';
}
} else if (event.keyCode === 13) {
// Enter key
if (results && results.classList.contains('show')) {
selectFocusedItem(container);
phac_aspc_autocomplete_clear_focus(container, true);
phac_aspc_autocomplete_hide_results(container);
}
return false;
} else if (
event.keyCode === 8 &&
event.target.value.length === 0
) {
// Backspace key on text input
const chip = container.querySelectorAll('.chip a');
if (chip.length > 0) chip[chip.length - 1].dispatchEvent(new Event('click'));
} else if (event.keyCode === 33) {
// Page up key
if (results) {
const prev = whereTo(
container,
false,
true,
getPageSize(results)
);
if (prev) switchFocus(prev, container);
return false;
}
} else if (event.keyCode === 34) {
// Page down key
if (results) {
const next = whereTo(
container,
true,
true,
getPageSize(results)
);
if (next) switchFocus(next, container);
return false;
}
} else if (event.keyCode === 40) {
// down arrow
// Open the results if they are not shown
if (!results || !results.classList.contains('show')) {
event.target.dispatchEvent(new Event('phac_aspc_autocomplete_trigger'));
if (event.altKey) {
phac_aspc_autocomplete_closed[id] = false;
} else {
focusWhenResultsShown(container, 3000);
}
} else {
const next = whereTo(container);
if (next) switchFocus(next, container);
}
return false;
} else if (event.keyCode === 38) {
// up arrow on item
// Open the results if they are not shown
if (!results || !results.classList.contains('show')) {
event.target.dispatchEvent(new Event('phac_aspc_autocomplete_trigger'));
if (event.altKey) {
phac_aspc_autocomplete_closed[id] = false;
} else {
focusWhenResultsShown(container, 3000, true);
}
} else {
const prev = whereTo(container, false);
if (prev) switchFocus(prev, container);
}
return false;
} else {
phac_aspc_autocomplete_closed[id] = false;
}
phac_aspc_autocomplete_clear_focus(container, true);
return true;
}
class AbstractAutocompleteHelper {
/*
this is a helper class to manipulate autocomplete components
creating instances has zero side-effects
it's assumed you may instantiate the same component multiple times
*/
constructor(fieldName, componentPrefix="") {
this.fieldName = fieldName;
this.componentPrefix = componentPrefix;
}
getComponentId(){
return this.componentPrefix + this.fieldName;
}
getContainer(){
return document.getElementById(`${this.getComponentId()}__container`);
}
getInput(){
return this.getContainer().querySelector(`#${this.getComponentId()}__textinput`);
}
getInputWrapper(){
return this.getContainer().querySelector(`#${this.getComponentId()}`);
}
getResultItems(){
return this.getContainer().querySelector(`#${this.getComponentId()}__items`);
}
getInfo(){
return this.getContainer().querySelector(`#${this.getComponentId()}__info`);
}
getDataContainer(){
return this.getContainer().querySelector(`#${this.getComponentId()}__data`);
}
// behavioral methods
clear(){
this.getInput().value = '';
this.getInputWrapper().innerHTML = '';
this.getResultItems().innerHTML = '';
this.getInfo().innerHTML = '';
this.getDataContainer().removeAttribute('data-phac-aspc-autocomplete');
}
}
class SingleAutocompleteHelper extends AbstractAutocompleteHelper {}
class MultiAutocompleteHelper extends AbstractAutocompleteHelper {
getSrDescription(){
return this.getContainer().querySelector(`#${this.getComponentId()}__sr_description`);
}
getChips(){
return this.getContainer().querySelectorAll(`#${this.getComponentId()}_ac_container li.chip`);
}
clear(){
super.clear();
this.getChips().forEach(chip => chip.remove());
this.getSrDescription().innerHTML = '';
}
}