From c8aeb63f6322f0a5d91daeab2ab2f10a6a98c0ea Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 5 Feb 2025 02:13:36 -0500 Subject: [PATCH 01/10] initial implementation the multiselect rendered in the casa_cases/form partial gets used in both casa_cases/new view and casa_cases/edit view --- .../form/multiple_select_component.html.erb | 13 +++ .../form/multiple_select_component.rb | 3 +- .../controllers/multiple_select_controller.js | 79 +++++++++++++++++-- .../form/_contact_types.html.erb | 3 +- 4 files changed, 88 insertions(+), 10 deletions(-) diff --git a/app/components/form/multiple_select_component.html.erb b/app/components/form/multiple_select_component.html.erb index 58bb6f3098..79f8efaaa2 100644 --- a/app/components/form/multiple_select_component.html.erb +++ b/app/components/form/multiple_select_component.html.erb @@ -3,6 +3,7 @@ data-multiple-select-options-value="<%= @options %>" data-multiple-select-selected-items-value="<%= @selected_items %>" data-multiple-select-placeholder-term-value="<%= @placeholder_term %>" + data-multiple-select-show-all-option-value="<%= @show_all_option %>" data-multiple-select-with-options-value="true"> + <% if @show_all_option %> + + + + <% end %> + <%= @form.select @name, {}, { multiple: true } , { data: { "multiple-select-target": "select", } , class: "form-control-lg form-select form-select-lg input-group-lg" } %> diff --git a/app/components/form/multiple_select_component.rb b/app/components/form/multiple_select_component.rb index 4df7c864bc..c2f0725c1d 100644 --- a/app/components/form/multiple_select_component.rb +++ b/app/components/form/multiple_select_component.rb @@ -1,12 +1,13 @@ # frozen_string_literal: true class Form::MultipleSelectComponent < ViewComponent::Base - def initialize(form:, name:, options:, selected_items:, render_option_subtext: false, placeholder_term: nil) + def initialize(form:, name:, options:, selected_items:, render_option_subtext: false, placeholder_term: nil, show_all_option: false) @form = form @name = name @options = options.to_json @selected_items = selected_items @render_option_subtext = render_option_subtext @placeholder_term = placeholder_term + @show_all_option = show_all_option end end diff --git a/app/javascript/controllers/multiple_select_controller.js b/app/javascript/controllers/multiple_select_controller.js index 1ebd2dd6ec..574d4cf638 100644 --- a/app/javascript/controllers/multiple_select_controller.js +++ b/app/javascript/controllers/multiple_select_controller.js @@ -2,7 +2,7 @@ import { Controller } from '@hotwired/stimulus' import TomSelect from 'tom-select' export default class extends Controller { - static targets = ['select', 'option', 'item'] + static targets = ['select', 'option', 'item', 'hiddenItem', 'showAllOption'] // add 'selectAllBtn' if going with button static values = { options: Array, selectedItems: Array, @@ -10,7 +10,8 @@ export default class extends Controller { placeholderTerm: { type: String, default: 'contact(s)' - } + }, + showAllOption: Boolean, } connect () { @@ -37,11 +38,52 @@ export default class extends Controller { const itemTemplate = this.itemTarget.innerHTML const placeholder = `Select or search ${this.placeholderTermValue}` + const showAllOptionCheck = this.showAllOptionValue + const hiddenItemTemplate = showAllOptionCheck && this.hiddenItemTarget && this.hiddenItemTarget.innerHTML + const showAllOptionTemplate = showAllOptionCheck && this.showAllOptionTarget && this.showAllOptionTarget.innerHTML + + // orderedOptionVals is of type (" " | number)[] - the " " could appear + // because using it as the value for the select/unselect all option + let orderedOptionVals = this.optionsValue.map(opt => opt.value) + if (showAllOptionCheck) { + // using " " as value instead of "" bc tom-select doesn't init the "" in the item list + orderedOptionVals = [" "].concat(orderedOptionVals) + } + + // used to determine initial items selected by tom-select + const initItems = this.selectedItemsValue?.length ? this.selectedItemsValue : orderedOptionVals + + const dropdownOptions = showAllOptionCheck ? + [{ text: "Select/Unseselect all", subtext: "", value: " ", group: ""}].concat(this.optionsValue) + : this.optionsValue + + // const selectAllBtn = this.selectAllBtnTarget + // assign TomSelect instance to this.selectEl if going with button implementation + /* eslint-disable no-new */ new TomSelect(this.selectTarget, { - onItemAdd: function () { + onItemRemove: function(value, data) { + // for the select/unselect all button - add in short circuit in case showAllBtn doesn't exist + // if (this.items.length < orderedOptionVals.length) { + // selectAllBtn.innerText = 'Select all' + // } + + if (value === " ") { + this.clear() + } + }, + onItemAdd: function (value) { this.setTextboxValue('') this.refreshOptions() + + // for the select/unselect all button - add in short circuit in case showAllBtn doesn't exist + // if (this.items.length < orderedOptionVals.length) { + // selectAllBtn.innerText = 'Select all' + // } + + if (value === " ") { + this.addItems(orderedOptionVals); + } }, plugins: { remove_button: { @@ -54,21 +96,42 @@ export default class extends Controller { uncheckedClassNames: ['form-check-input'] } }, - options: this.optionsValue, - items: this.selectedItemsValue, + options: dropdownOptions, + items: initItems, placeholder, hidePlaceholder: true, searchField: ['text', 'group'], render: { option: function (data, escape) { - let html = optionTemplate.replace(/DATA_LABEL/g, escape(data.text)) - html = html.replace(/DATA_SUB_TEXT/g, escape(data.subtext)) + let html + + if (showAllOptionCheck && data && data.value === " ") { + html = showAllOptionTemplate.replace(/DATA_LABEL/g, escape(data.text)) + } else { + html = optionTemplate.replace(/DATA_LABEL/g, escape(data.text)) + html = html.replace(/DATA_SUB_TEXT/g, escape(data.subtext)) + } return html }, item: function (data, escape) { - return itemTemplate.replace(/DATA_LABEL/g, escape(data.text)) + return showAllOptionCheck && data.value === " " ? hiddenItemTemplate : itemTemplate.replace(/DATA_LABEL/g, escape(data.text)) } } }) } + + // action for the select/unselect all button - add in short circuit in case showAllBtn or selectEl doesn't exist + // toggleSelectAll() { + // if (!this.selectEl || !this.selectAllBtnTarget) return + + // const checkedStatus = this.selectEl.items.length === Object.keys(this.selectEl.options).length ? "all" : "not-all" + + // if (checkedStatus === "all") { + // this.selectEl.clear() + // this.selectAllBtnTarget.textContent = "Select all" + // } else { + // this.selectEl.addItems(this.optionsValue.map(opt => opt.value)) + // this.selectAllBtnTarget.textContent = "Unselect all" + // } + // } } diff --git a/app/views/case_contacts/form/_contact_types.html.erb b/app/views/case_contacts/form/_contact_types.html.erb index 1f91e5f1d1..517d71b65b 100644 --- a/app/views/case_contacts/form/_contact_types.html.erb +++ b/app/views/case_contacts/form/_contact_types.html.erb @@ -5,6 +5,7 @@ name: :contact_type_ids, options: options.decorate.map { |ct| ct.hash_for_multi_select_with_cases(casa_cases&.pluck(:id)) }, selected_items: selected_items, - render_option_subtext: true + render_option_subtext: true, + show_all_option: true, )) %> From ba3f142a2410ea05068bfc273b349b23791e886d Mon Sep 17 00:00:00 2001 From: alex Date: Sun, 9 Feb 2025 22:21:03 -0500 Subject: [PATCH 02/10] adds select all option that is initially checked when editing casa case --- app/components/form/multiple_select_component.html.erb | 1 + app/javascript/controllers/multiple_select_controller.js | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components/form/multiple_select_component.html.erb b/app/components/form/multiple_select_component.html.erb index 79f8efaaa2..3b47516ca6 100644 --- a/app/components/form/multiple_select_component.html.erb +++ b/app/components/form/multiple_select_component.html.erb @@ -19,6 +19,7 @@
DATA_LABEL
+ <%# not needed, but want to be explicit when including this markup %> <% if @show_all_option %> <% end %> - + <%= @form.select @name, {}, { multiple: true } , { data: { "multiple-select-target": "select", } , class: "form-control-lg form-select form-select-lg input-group-lg" } %> diff --git a/app/javascript/controllers/multiple_select_controller.js b/app/javascript/controllers/multiple_select_controller.js index a7161f643e..c7f88c7d21 100644 --- a/app/javascript/controllers/multiple_select_controller.js +++ b/app/javascript/controllers/multiple_select_controller.js @@ -11,7 +11,7 @@ export default class extends Controller { type: String, default: 'contact(s)' }, - showAllOption: Boolean, + showAllOption: Boolean } connect () { @@ -41,33 +41,37 @@ export default class extends Controller { const showAllOptionCheck = this.showAllOptionValue const hiddenItemTemplate = showAllOptionCheck && this.hiddenItemTarget && this.hiddenItemTarget.innerHTML const showAllOptionTemplate = showAllOptionCheck && this.showAllOptionTarget && this.showAllOptionTarget.innerHTML - + // orderedOptionVals is of type (" " | number)[] - the " " could appear // because using it as the value for the select/unselect all option let orderedOptionVals = this.optionsValue.map(opt => opt.value) if (showAllOptionCheck) { // using " " as value instead of "" bc tom-select doesn't init the "" in the item list - orderedOptionVals = [" "].concat(orderedOptionVals) + orderedOptionVals = [' '].concat(orderedOptionVals) } - const initItems = Array.isArray(this.selectedItemsValue) && this.selectedItemsValue.length ? showAllOptionCheck ? [" "].concat(this.selectedItemsValue) : this.selectedItemsValue : orderedOptionVals + const initItems = Array.isArray(this.selectedItemsValue) && this.selectedItemsValue.length + ? showAllOptionCheck + ? [' '].concat(this.selectedItemsValue) + : this.selectedItemsValue + : orderedOptionVals + + const dropdownOptions = showAllOptionCheck + ? [{ text: 'Select/Unseselect all', subtext: '', value: ' ', group: '' }].concat(this.optionsValue) + : this.optionsValue - const dropdownOptions = showAllOptionCheck ? - [{ text: "Select/Unseselect all", subtext: "", value: " ", group: ""}].concat(this.optionsValue) - : this.optionsValue - // const selectAllBtn = this.selectAllBtnTarget // assign TomSelect instance to this.selectEl if going with button implementation /* eslint-disable no-new */ new TomSelect(this.selectTarget, { - onItemRemove: function(value, data) { + onItemRemove: function (value, data) { // for the select/unselect all button - add in short circuit in case showAllBtn doesn't exist // if (this.items.length < orderedOptionVals.length) { // selectAllBtn.innerText = 'Select all' // } - if (value === " ") { + if (value === ' ') { this.clear() } }, @@ -80,8 +84,8 @@ export default class extends Controller { // selectAllBtn.innerText = 'Select all' // } - if (value === " ") { - this.addItems(orderedOptionVals); + if (value === ' ') { + this.addItems(orderedOptionVals) } }, plugins: { @@ -104,7 +108,7 @@ export default class extends Controller { option: function (data, escape) { let html - if (showAllOptionCheck && data && data.value === " ") { + if (showAllOptionCheck && data && data.value === ' ') { html = showAllOptionTemplate.replace(/DATA_LABEL/g, escape(data.text)) } else { html = optionTemplate.replace(/DATA_LABEL/g, escape(data.text)) @@ -113,7 +117,7 @@ export default class extends Controller { return html }, item: function (data, escape) { - return showAllOptionCheck && data.value === " " ? hiddenItemTemplate : itemTemplate.replace(/DATA_LABEL/g, escape(data.text)) + return showAllOptionCheck && data.value === ' ' ? hiddenItemTemplate : itemTemplate.replace(/DATA_LABEL/g, escape(data.text)) } } }) @@ -124,7 +128,7 @@ export default class extends Controller { // if (!this.selectEl || !this.selectAllBtnTarget) return // const checkedStatus = this.selectEl.items.length === Object.keys(this.selectEl.options).length ? "all" : "not-all" - + // if (checkedStatus === "all") { // this.selectEl.clear() // this.selectAllBtnTarget.textContent = "Select all" From 14c7aa35ceafa89533c051e2dd8f34649f982aca Mon Sep 17 00:00:00 2001 From: alex Date: Sun, 9 Feb 2025 22:56:52 -0500 Subject: [PATCH 04/10] remove comments --- .../controllers/multiple_select_controller.js | 32 ++----------------- 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/app/javascript/controllers/multiple_select_controller.js b/app/javascript/controllers/multiple_select_controller.js index c7f88c7d21..6add7f495e 100644 --- a/app/javascript/controllers/multiple_select_controller.js +++ b/app/javascript/controllers/multiple_select_controller.js @@ -2,7 +2,7 @@ import { Controller } from '@hotwired/stimulus' import TomSelect from 'tom-select' export default class extends Controller { - static targets = ['select', 'option', 'item', 'hiddenItem', 'showAllOption'] // add 'selectAllBtn' if going with button + static targets = ['select', 'option', 'item', 'hiddenItem', 'showAllOption'] static values = { options: Array, selectedItems: Array, @@ -60,17 +60,9 @@ export default class extends Controller { ? [{ text: 'Select/Unseselect all', subtext: '', value: ' ', group: '' }].concat(this.optionsValue) : this.optionsValue - // const selectAllBtn = this.selectAllBtnTarget - // assign TomSelect instance to this.selectEl if going with button implementation - /* eslint-disable no-new */ new TomSelect(this.selectTarget, { - onItemRemove: function (value, data) { - // for the select/unselect all button - add in short circuit in case showAllBtn doesn't exist - // if (this.items.length < orderedOptionVals.length) { - // selectAllBtn.innerText = 'Select all' - // } - + onItemRemove: function (value) { if (value === ' ') { this.clear() } @@ -79,11 +71,6 @@ export default class extends Controller { this.setTextboxValue('') this.refreshOptions() - // for the select/unselect all button - add in short circuit in case showAllBtn doesn't exist - // if (this.items.length < orderedOptionVals.length) { - // selectAllBtn.innerText = 'Select all' - // } - if (value === ' ') { this.addItems(orderedOptionVals) } @@ -122,19 +109,4 @@ export default class extends Controller { } }) } - - // action for the select/unselect all button - add in short circuit in case showAllBtn or selectEl doesn't exist - // toggleSelectAll() { - // if (!this.selectEl || !this.selectAllBtnTarget) return - - // const checkedStatus = this.selectEl.items.length === Object.keys(this.selectEl.options).length ? "all" : "not-all" - - // if (checkedStatus === "all") { - // this.selectEl.clear() - // this.selectAllBtnTarget.textContent = "Select all" - // } else { - // this.selectEl.addItems(this.optionsValue.map(opt => opt.value)) - // this.selectAllBtnTarget.textContent = "Unselect all" - // } - // } } From 13ff0d173c0555b0e78ca6c204cacd8e038aef49 Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 10 Feb 2025 13:18:03 -0500 Subject: [PATCH 05/10] clean up unused code --- app/components/form/multiple_select_component.html.erb | 4 ++-- app/javascript/controllers/multiple_select_controller.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/components/form/multiple_select_component.html.erb b/app/components/form/multiple_select_component.html.erb index 08851cea08..abd03c3197 100644 --- a/app/components/form/multiple_select_component.html.erb +++ b/app/components/form/multiple_select_component.html.erb @@ -25,8 +25,8 @@
-