11defmodule Backpex.Fields.MultiSelect do
22 @ config_schema [
33 options: [
4- doc: "List of options or function that receives the assigns." ,
5- type: { :or , [ { :list , :any } , { :fun , 1 } ] } ,
4+ doc: "List of possibly grouped options or function that receives the assigns." ,
5+ type: { :or , [ { :list , :any } , { :map , :any , :any } , { : fun, 1 } ] } ,
66 required: true
77 ] ,
88 prompt: [
@@ -66,18 +66,41 @@ defmodule Backpex.Fields.MultiSelect do
6666 % { assigns: % { field_options: field_options } = assigns } = socket
6767
6868 options =
69- assigns
70- |> field_options . options . ( )
69+ case Map . get ( field_options , :options ) do
70+ options when is_function ( options ) -> options . ( assigns )
71+ options -> options
72+ end
73+
74+ options =
75+ options
7176 |> Enum . map ( fn { label , value } ->
72- { to_string ( label ) , to_string ( value ) }
77+ case value do
78+ value when is_list ( value ) or is_map ( value ) ->
79+ { to_string ( label ) , Enum . map ( value , fn { lab , val } -> { to_string ( lab ) , to_string ( val ) } end ) }
80+
81+ value ->
82+ { to_string ( label ) , to_string ( value ) }
83+ end
7384 end )
7485
7586 assign ( socket , :options , options )
7687 end
7788
89+ defp flatten_options ( options ) do
90+ Enum . map ( options , fn { _label , value } = option ->
91+ case value do
92+ value when is_list ( value ) or is_map ( value ) -> value
93+ _value -> option
94+ end
95+ end )
96+ |> List . flatten ( )
97+ end
98+
7899 defp assign_selected ( socket ) do
79100 % { assigns: % { type: type , options: options , item: item , name: name } = assigns } = socket
80101
102+ options = flatten_options ( options )
103+
81104 selected_ids =
82105 if type == :form do
83106 case PhoenixForm . input_value ( assigns . form , name ) do
@@ -99,6 +122,8 @@ defmodule Backpex.Fields.MultiSelect do
99122 defp maybe_assign_form ( % { assigns: % { type: :form } = assigns } = socket ) do
100123 % { selected: selected , options: options } = assigns
101124
125+ options = flatten_options ( options )
126+
102127 show_select_all = length ( selected ) != length ( options )
103128
104129 socket
@@ -156,21 +181,20 @@ defmodule Backpex.Fields.MultiSelect do
156181
157182 @ impl Phoenix.LiveComponent
158183 def handle_event ( "toggle-option" , % { "id" => id } , socket ) do
159- % { assigns: % { selected: selected , options: options , field_options: field_options } } = socket
184+ % { assigns: % { selected: selected , options: options } } = socket
185+
186+ options = flatten_options ( options )
160187
161- selected_item = Enum . find ( selected , fn { _label , value } -> value == id end )
188+ clicked_item = Enum . find ( options , fn { _label , value } -> value == id end )
162189
163190 new_selected =
164- if selected_item do
165- Enum . reject ( selected , fn { _label , value } -> value == id end )
191+ if clicked_item in selected do
192+ selected -- [ clicked_item ]
166193 else
167- selected
168- |> Enum . reverse ( )
169- |> Kernel . then ( & [ Enum . find ( options , fn { _label , value } -> value == id end ) | & 1 ] )
170- |> Enum . reverse ( )
194+ [ clicked_item ] ++ selected
171195 end
172196
173- show_select_all = length ( new_selected ) != length ( field_options . options . ( socket . assigns ) )
197+ show_select_all = length ( new_selected ) != length ( options )
174198
175199 socket
176200 |> assign ( :selected , new_selected )
@@ -180,13 +204,12 @@ defmodule Backpex.Fields.MultiSelect do
180204
181205 @ impl Phoenix.LiveComponent
182206 def handle_event ( "search" , params , socket ) do
183- % { assigns: % { name: name , field_options: field_options } = assigns } = socket
207+ socket = assign_options ( socket )
208+ % { assigns: % { name: name , options: options } } = socket
184209
185210 search_input = Map . get ( params , "change[#{ name } ]_search" )
186211
187- options =
188- field_options . options . ( assigns )
189- |> maybe_apply_search ( search_input )
212+ options = apply_search ( options , search_input )
190213
191214 socket
192215 |> assign ( :options , options )
@@ -196,27 +219,37 @@ defmodule Backpex.Fields.MultiSelect do
196219
197220 @ impl Phoenix.LiveComponent
198221 def handle_event ( "toggle-select-all" , _params , socket ) do
199- % { assigns: % { field_options: field_options , show_select_all: show_select_all } = assigns } = socket
222+ % { assigns: % { options: options , show_select_all: show_select_all } } = socket
223+
224+ options = flatten_options ( options )
200225
201- new_selected = if show_select_all , do: field_options . options . ( assigns ) , else: [ ]
226+ new_selected = if show_select_all , do: options , else: [ ]
202227
203228 socket
204229 |> assign ( :selected , new_selected )
205230 |> assign ( :show_select_all , not show_select_all )
206231 |> noreply ( )
207232 end
208233
209- defp maybe_apply_search ( options , search_input ) do
210- if String . trim ( search_input ) == "" do
211- options
212- else
213- search_input_downcase = String . downcase ( search_input )
234+ defp apply_search ( options , search_input ) do
235+ Enum . map ( options , fn { label , value } -> filter_option ( label , value , search_input ) end ) |> Enum . filter ( & & 1 )
236+ end
214237
215- Enum . filter ( options , fn { label , _value } ->
216- String . downcase ( label )
217- |> String . contains? ( search_input_downcase )
218- end )
219- end
238+ defp filter_option ( label , value , search_input ) when is_list ( value ) or is_map ( value ) do
239+ search_input_downcase = String . downcase ( search_input )
240+
241+ filtered =
242+ Enum . filter ( value , fn { _label , _value } -> String . downcase ( label ) |> String . contains? ( search_input_downcase ) end )
243+
244+ if not Enum . empty? ( filtered ) , do: { label , filtered }
245+ end
246+
247+ defp filter_option ( label , _value , search_input ) do
248+ search_input_downcase = String . downcase ( search_input )
249+
250+ label
251+ |> String . downcase ( )
252+ |> String . contains? ( search_input_downcase )
220253 end
221254
222255 defp prompt ( assigns , field_options ) do
0 commit comments