groups: a ticked row is always shown
A hidden box still submits, so a filter that could hide a ticked
one was hiding a decision rather than narrowing a list — the tick
is read off the checkbox at each evaluation, and "No groups match"
follows the rows that stand visible.
Assisted-by: GLM-5.3 via pi
diff --git a/lib/pro_tacts/admin/group_filter.rb b/lib/pro_tacts/admin/group_filter.rb
index c593db2..e6acec2 100644
--- a/lib/pro_tacts/admin/group_filter.rb
+++ b/lib/pro_tacts/admin/group_filter.rb
@@ -36,20 +36,30 @@ module ProTacts
# cap adds to `shows`: a capped row joins the list once the
# filter is typed into or the whole list is asked for, and a
# filter is the one of the two that can reach a single group
- # without the rest. `named` is the names a tick on the offer
- # committed to making (Named): `creatable` and `none` read it
- # directly rather than off the rows' `data-label`s, which those
- # two only re-read when the filter changes — a name committed
- # since would be invisible to both until the next keystroke.
+ # without the rest. Neither hides a ticked row — a ticked box
+ # submits whether or not it can be seen, so a filter that could
+ # make one vanish would be hiding a decision rather than
+ # narrowing a list — and the tick is read off the checkbox at
+ # each evaluation, so a row answers the next filter change
+ # already ticked. `rows` is every row standing in the options,
+ # the element list `none` reads: no row visible, which is what
+ # "No groups match" claims. `named` is the names a tick on the
+ # offer committed to making (Named): `creatable` and `none`
+ # read it directly rather than off the rows' `data-label`s,
+ # which those two only re-read when the filter changes — a
+ # name committed since would be invisible to both until the
+ # next keystroke.
STATE = "{ filter: '', all: false, named: [], " \
"get needle() { return this.filter.trim().toLowerCase() }, " \
"get labels() { return [...this.$refs.options.querySelectorAll('[data-label]')]" \
".map(row => row.dataset.label) }, " \
+ "get rows() { return [...this.$refs.options.querySelectorAll('label')] }, " \
"shows(label) { return label.includes(this.needle) }, " \
- "visible(row) { return this.shows(row.dataset.label) && " \
- "(this.all || this.needle !== '' || !('capped' in row.dataset)) }, " \
- "get none() { return !this.labels.some(label => this.shows(label)) && " \
- "!this.named.some(name => name.toLowerCase().includes(this.needle)) }, " \
+ "visible(row) { return row.querySelector('input[type=checkbox]').checked || " \
+ "(this.shows(row.dataset.label) && " \
+ "(this.all || this.needle !== '' || !('capped' in row.dataset))) }, " \
+ "get none() { return !this.named.length && " \
+ "!this.rows.some(row => this.visible(row)) }, " \
"get creatable() { return this.needle !== '' && !this.labels.includes(this.needle) && " \
"!this.named.some(name => name.toLowerCase() === this.needle) } }" #: String
@@ -140,15 +150,13 @@ module ProTacts
# independent of the filter — which is the whole difference
# from the offer, whose row lives on the filter's own text. An
# untick withdraws the name, back to the offer if the filter
- # still says it. Never capped, being ticked, and hidden by the
- # filter like any row — said straight to `shows` rather than
- # through `data-label`/`visible($el)`, those reading the DOM
- # the server rendered, and this row being made client-side
- # where the name is in hand.
+ # still says it. Never capped and never filtered, being ticked
+ # — `visible`'s own rule — so the row needs no attrs to say it:
+ # a bare label, always standing.
class Named < Phlex::HTML
def view_template
template(x_for: "name in named") do
- label(":hidden": "!shows(name.toLowerCase())") do
+ label do
input(type: "checkbox", name: "named[]", ":value": "name", checked: true,
"@change": "named = named.filter(n => n !== name)")
span(class: "gl-muted", style: "font-style: italic;", x_text: "name")
diff --git a/lib/pro_tacts/admin/import_groups.rb b/lib/pro_tacts/admin/import_groups.rb
index 64c3649..9129517 100644
--- a/lib/pro_tacts/admin/import_groups.rb
+++ b/lib/pro_tacts/admin/import_groups.rb
@@ -91,9 +91,10 @@ module ProTacts
# Italic and muted, the shape the groups dialog gives a
# name that is not a group yet: the box behaves like the
# ones above it, and the styling is what says this one is
- # about to be made. Filterable like them too, so that a
- # name this import is already making is not offered as a
- # name to make, and never capped, being ticked.
+ # about to be made. Always shown and never capped, being
+ # ticked (`visible`'s own rule); the `data-label` is for
+ # `creatable`, so that a name this import is already
+ # making is not offered as a name to make.
@named.each do |name|
label(**GroupFilter.row(name)) do
input(type: "checkbox", name: "named[]", value: name, checked: true)
diff --git a/test/pro_tacts/admin/test_import_pages.rb b/test/pro_tacts/admin/test_import_pages.rb
index 790125c..4454238 100644
--- a/test/pro_tacts/admin/test_import_pages.rb
+++ b/test/pro_tacts/admin/test_import_pages.rb
@@ -697,6 +697,11 @@ class AdminImportPagesTest < Minitest::Test
%(<input type="checkbox" @change="if ($event.target.checked) named.push(filter.trim())">)
assert_includes last_response.body,
%(<input type="checkbox" name="named[]" :value="name" checked @change="named = named.filter(n => n !== name)">)
+ # A committed name is a ticked row, and a ticked row answers
+ # neither the filter nor the cap: a decision the form still
+ # submits is never hidden.
+ assert_includes last_response.body, %(<template x-for="name in named"><label>)
+ assert_includes last_response.body, %(row.querySelector('input[type=checkbox]').checked)
end
end