New dropdown widget!

This commit is contained in:
Yaro Kasear 2025-10-22 11:23:43 -05:00
parent 43b3df9938
commit 5718deee6b
4 changed files with 163 additions and 63 deletions

View file

@ -0,0 +1,4 @@
.inventory-dropdown {
border-color: rgb(222, 226, 230);
overflow-y: auto;
}

View file

@ -0,0 +1,95 @@
const DropDown = globalThis.DropDown ?? (globalThis.DropDown = {});
DropDown.utilities = {
filterList(id) {
value = document.getElementById(`${id}-filter`).value;
list = document.querySelectorAll(`#${id}-dropdown li`);
list.forEach(item => {
const txt = item.textContent.toLowerCase();
if (txt.includes(value)) {
item.style.display = 'list-item';
} else {
item.style.display = 'none';
};
});
},
selectItem(id, value) {
const btn = document.getElementById(`${id}-button`);
const txt = document.getElementById(`${id}-${value}`).textContent;
const inp = document.getElementById(id);
btn.dataset.value = value;
btn.textContent = txt;
inp.value = value;
},
};
(() => {
const VISIBLE_ITEMS = 10;
function setMenuMaxHeight(buttonEl) {
const menu = buttonEl?.nextElementSibling;
if (!menu || !menu.classList.contains('dropdown-menu')) return;
const input = menu.querySelector('input.form-control');
const firstItem = menu.querySelector('.dropdown-item');
if (!firstItem) return;
// Measure even if the menu is closed
const computed = getComputedStyle(menu);
const wasHidden = computed.display === 'none' || computed.visibility === 'hidden';
if (wasHidden) {
menu.style.visibility = 'hidden';
menu.style.display = 'block';
}
const inputH = input ? input.getBoundingClientRect().height : 0;
const itemH = firstItem.getBoundingClientRect().height || 0;
const itemCount = Math.min(
VISIBLE_ITEMS,
menu.querySelectorAll('.dropdown-item').length
);
const target = Math.ceil(inputH + itemH * itemCount);
menu.style.maxHeight = `${target + 10}px`;
menu.style.overflowY = 'auto';
if (wasHidden) {
menu.style.display = '';
menu.style.visibility = '';
}
}
function onShow(e) {
setMenuMaxHeight(e.target);
}
function onResize() {
document.querySelectorAll('.dropdown-toggle[data-bs-toggle="dropdown"]').forEach(btn => {
const menu = btn.nextElementSibling;
if (menu && menu.classList.contains('dropdown-menu') && menu.classList.contains('show')) {
setMenuMaxHeight(btn);
}
});
}
function init(root = document) {
// Delegate so dynamically-added dropdowns work too
root.addEventListener('show.bs.dropdown', onShow);
window.addEventListener('resize', onResize);
}
// Expose for manyal calls or tests
DropDown.utilities.setMenuMaxHeight = setMenuMaxHeight;
DropDown.init = init;
// Auto-init
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => init());
} else {
init();
}
})();

View file

@ -1,9 +1,7 @@
<!-- FIELD: {{ field_name }} ({{ field_type }}) -->
{% if field_type != 'hidden' and field_label %}
<label for="{{ field_name }}"
{% if label_attrs %}{% for k,v in label_attrs.items() %}
{{k}}{% if v is not sameas true %}="{{ v }}"{% endif %}
{% endfor %}{% endif %}>
<label for="{{ field_name }}" {% if label_attrs %}{% for k,v in label_attrs.items() %} {{k}}{% if v is not sameas true
%}="{{ v }}" {% endif %} {% endfor %}{% endif %}>
{% if link_href %}
<a href="{{ link_href }}" class="link-success link-underline link-underline-opacity-0 fw-semibold">
{% endif %}
@ -11,81 +9,76 @@
{% if link_href %}
</a>
{% endif %}
</label>
</label>
{% endif %}
{% if field_type == 'select' %}
{#
<select name="{{ field_name }}" id="{{ field_name }}"
{% if attrs %}{% for k,v in attrs.items() %}
{{k}}{% if v is not sameas true %}="{{ v }}"{% endif %}
{% endfor %}{% endif %}
{%- if not options %} disabled{% endif %}>
<select name="{{ field_name }}" id="{{ field_name }}" {% if attrs %}{% for k,v in attrs.items() %} {{k}}{% if v is not
sameas true %}="{{ v }}" {% endif %} {% endfor %}{% endif %} {%- if not options %} disabled{% endif %}>
{% if options %}
<option value="">-- Select --</option>
{% for opt in options %}
<option value="{{ opt.value }}" {% if opt.value|string == value|string %}selected{% endif %}>
<option value="{{ opt.value }}" {% if opt.value|string==value|string %}selected{% endif %}>
{{ opt.label }}
</option>
{% endfor %}
{% else %}
<option value="">-- No selection available --</option>
{% endif %}
</select>
</select>
#}
{% if value %}
{% set sel_label = (options | selectattr('value', 'equalto', value) | first)['label'] %}
{% else %}
{% set sel_label = "-- Select --" %}
{% endif %}
<button type="button" class="btn btn-outline-secondary d-block w-100" id="{{ field_name }}-button" data-value="{{ value }}">{{ sel_label }}</button>
{% if value %}
{% set sel_label = (options | selectattr('value', 'equalto', value) | first)['label'] %}
{% else %}
{% set sel_label = "-- Select --" %}
{% endif %}
<button type="button" class="btn btn-outline-dark d-block w-100 text-start dropdown-toggle inventory-dropdown"
id="{{ field_name }}-button" data-bs-toggle="dropdown" data-value="{{ value }}">{{ sel_label }}</button>
<div class="dropdown-menu pt-0" id="{{ field_name }}-dropdown">
<input type="text" class="form-control mt-0 border-top-0 border-start-0 border-end-0 rounded-bottom-0"
id="{{ field_name }}-filter" placeholder="Filter..." oninput="DropDown.utilities.filterList('{{ field_name }}')">
{% for opt in options %}
<li><a class="dropdown-item{% if opt.value|string == value|string %} active{% endif %}"
data-value="{{ opt['value'] }}" onclick="DropDown.utilities.selectItem('{{ field_name }}', '{{ opt['value'] }}')" id="{{ field_name }}-{{ opt['value'] }}">{{ opt['label'] }}</a></li>
{% endfor %}
</div>
<input type="hidden" name="{{ field_name }}" id="{{ field_name }}" value="{{ value }}">
{% elif field_type == 'textarea' %}
<textarea name="{{ field_name }}" id="{{ field_name }}"
{% if attrs %}{% for k,v in attrs.items() %}
{{k}}{% if v is not sameas true %}="{{ v }}"{% endif %}
{% endfor %}{% endif %}>{{ value if value else "" }}</textarea>
<textarea name="{{ field_name }}" id="{{ field_name }}" {% if attrs %}{% for k,v in attrs.items() %} {{k}}{% if v is not
sameas true %}="{{ v }}" {% endif %} {% endfor %}{% endif %}>{{ value if value else "" }}</textarea>
{% elif field_type == 'checkbox' %}
<input type="checkbox" name="{{ field_name }}" id="{{ field_name }}" value="1"
{% if value %}checked{% endif %}
{% if attrs %}{% for k,v in attrs.items() %}
{{k}}{% if v is not sameas true %}="{{ v }}"{% endif %}
{% endfor %}{% endif %}>
<input type="checkbox" name="{{ field_name }}" id="{{ field_name }}" value="1" {% if value %}checked{% endif %} {% if
attrs %}{% for k,v in attrs.items() %} {{k}}{% if v is not sameas true %}="{{ v }}" {% endif %} {% endfor %}{% endif
%}>
{% elif field_type == 'hidden' %}
<input type="hidden" name="{{ field_name }}" id="{{ field_name }}" value="{{ value if value else "" }}">
<input type="hidden" name="{{ field_name }}" id="{{ field_name }}" value="{{ value if value else "" }}">
{% elif field_type == 'display' %}
<div {% if attrs %}{% for k,v in attrs.items() %}
{{k}}{% if v is not sameas true %}="{{ v }}"{% endif %}
{% endfor %}{% endif %}>{{ value_label if value_label else (value if value else "") }}</div>
<div {% if attrs %}{% for k,v in attrs.items() %} {{k}}{% if v is not sameas true %}="{{ v }}" {% endif %} {% endfor
%}{% endif %}>{{ value_label if value_label else (value if value else "") }}</div>
{% elif field_type == "date" %}
<input type="date" name="{{ field_name }}" id="{{ field_name }}" value="{{ value if value else "" }}"
{% if attrs %}{% for k,v in attrs.items() %}
{{k}}{% if v is not sameas true %}="{{ v }}"{% endif %}
{% endfor %}{% endif %}>
<input type="date" name="{{ field_name }}" id="{{ field_name }}" value="{{ value if value else "" }}" {% if attrs %}{%
for k,v in attrs.items() %} {{k}}{% if v is not sameas true %}="{{ v }}" {% endif %} {% endfor %}{% endif %}>
{% elif field_type == "time" %}
<input type="time" name="{{ field_name }}" id="{{ field_name }}" value="{{ value if value else "" }}"
{% if attrs %}{% for k,v in attrs.items() %}
{{k}}{% if v is not sameas true %}="{{ v }}"{% endif %}
{% endfor %}{% endif %}>
<input type="time" name="{{ field_name }}" id="{{ field_name }}" value="{{ value if value else "" }}" {% if attrs %}{%
for k,v in attrs.items() %} {{k}}{% if v is not sameas true %}="{{ v }}" {% endif %} {% endfor %}{% endif %}>
{% elif field_type == "datetime" %}
<input type="datetime-local" name="{{ field_name }}" id="{{ field_name }}" value="{{ value if value else "" }}"
{% if attrs %}{% for k,v in attrs.items() %}
{{k}}{% if v is not sameas true %}="{{ v }}"{% endif %}
{% endfor %}{% endif %}>
<input type="datetime-local" name="{{ field_name }}" id="{{ field_name }}" value="{{ value if value else "" }}" {% if
attrs %}{% for k,v in attrs.items() %} {{k}}{% if v is not sameas true %}="{{ v }}" {% endif %} {% endfor %}{% endif
%}>
{% else %}
<input type="text" name="{{ field_name }}" id="{{ field_name }}" value="{{ value if value else "" }}"
{% if attrs %}{% for k,v in attrs.items() %}
{{k}}{% if v is not sameas true %}="{{ v }}"{% endif %}
{% endfor %}{% endif %}>
<input type="text" name="{{ field_name }}" id="{{ field_name }}" value="{{ value if value else "" }}" {% if attrs %}{%
for k,v in attrs.items() %} {{k}}{% if v is not sameas true %}="{{ v }}" {% endif %} {% endfor %}{% endif %}>
{% endif %}
{% if help %}
<div class="form-text">{{ help }}</div>
<div class="form-text">{{ help }}</div>
{% endif %}

View file

@ -1,8 +1,16 @@
{% extends 'base.html' %}
{% block styleincludes %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/components/dropdown.css') }}">
{% endblock %}
{% block main %}
<div class="container mt-5">
{{ form | safe }}
</div>
{% endblock %}
{% block scriptincludes %}
<script src="{{ url_for('static', filename='js/components/dropdown.js') }}" defer></script>
{% endblock %}