Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions bitmapist/cohort/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,24 @@

# --- HTML rendering

# Tom Select is vendored (not loaded from a CDN) so the cohort form makes no
# third-party network calls at runtime and works in offline / CSP-restricted
# deployments. The assets are inlined into the fragment because the library has
# no static-serving mechanism of its own -- consumers just embed the string
# returned by render_html_form().
_TOM_SELECT_CSS = "tmpl/vendor/tom-select-2.3.1.default.min.css"
_TOM_SELECT_JS = "tmpl/vendor/tom-select-2.3.1.complete.min.js"

_VENDOR_CACHE: dict[str, str] = {}


def _read_vendor_asset(relative_path: str) -> str:
if relative_path not in _VENDOR_CACHE:
file_path = path.join(path.dirname(path.abspath(__file__)), relative_path)
with open(file_path, encoding="utf-8") as f:
_VENDOR_CACHE[relative_path] = f.read()
return _VENDOR_CACHE[relative_path]


def render_html_form(
action_url,
Expand Down Expand Up @@ -141,6 +159,8 @@ def render_html_form(
num_results=int(num_results),
num_of_rows=int(num_of_rows),
start_date=start_date,
tom_select_css=_read_vendor_asset(_TOM_SELECT_CSS),
Comment thread
lukemerrett marked this conversation as resolved.
Outdated
Comment thread
lukemerrett marked this conversation as resolved.
Outdated
tom_select_js=_read_vendor_asset(_TOM_SELECT_JS),
)
)

Expand Down
47 changes: 46 additions & 1 deletion bitmapist/cohort/tmpl/form_data.mako
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
<%doc>
Tom Select 2.3.1 is vendored and inlined (see bitmapist/cohort/tmpl/vendor/)
rather than loaded from a CDN, so the cohort form makes no third-party
network calls at runtime and works in offline / CSP-restricted deployments.
</%doc>
<style>${ tom_select_css | n }</style>

<style>
.cohort_form dd {
display: inline-block;
Expand All @@ -7,6 +14,14 @@
.cohort_form select {
max-width: 150px;
}

/* The event selects are enhanced with Tom Select, which renders its own
searchable control. Give that control a usable width. */
.cohort_form .ts-wrapper {
display: inline-block;
min-width: 200px;
vertical-align: middle;
}
</style>

<form action="${ action_url }" method="GET" class="cohort_form">
Expand Down Expand Up @@ -83,8 +98,38 @@
</dl>
</form>

<%doc>
The library is inlined here, after the form markup, so the browser can
render the form before parsing/executing ~50KB of JS.
</%doc>
<script>${ tom_select_js | n }</script>
Comment thread
lukemerrett marked this conversation as resolved.

<script>
// Turn the event dropdowns into searchable selects. With hundreds of
// bitmapist events, scrolling a native <select> is painful; Tom Select
// adds a type-to-filter search box while still submitting the same value.
if (typeof TomSelect === 'undefined') {
// Library failed to load for some reason; leave the native <select>s
// in place rather than throwing.
console.warn('Tom Select unavailable; cohort event dropdowns not enhanced.');
} else {
document.querySelectorAll('.cohort_form .cohort-event-select').forEach(function (el) {
// The form fragment may be rendered more than once on a page, so
// this initializer can run again over selects that are already
// enhanced. Tom Select throws if re-initialized, so skip those.
if (el.tomselect) {
return;
}
new TomSelect(el, {
Comment thread
lukemerrett marked this conversation as resolved.
maxOptions: null, // never truncate the filtered list
searchField: ['text', 'value'],
});
});
}
</script>

<%def name="render_options(select_name, selections, current_selection)">
<select name="${ select_name }">
<select name="${ select_name }" class="cohort-event-select">
%for option in selections:
%if option == '---':
<option value="" disabled="disabled">----</option>
Expand Down
Loading
Loading