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, )) %>