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,50 +66,64 @@ 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
83- values =
84- case PhoenixForm . input_value ( assigns . form , name ) do
85- value when is_binary ( value ) -> [ value ]
86- value when is_list ( value ) -> value
87- _value -> [ ]
88- end
89-
90- Enum . map ( values , & to_string / 1 )
106+ case PhoenixForm . input_value ( assigns . form , name ) do
107+ value when is_binary ( value ) -> [ value ]
108+ value when is_list ( value ) -> value
109+ _value -> [ ]
110+ end
91111 else
92- value = Map . get ( item , name )
93-
94- if value , do: value , else: [ ]
112+ Map . get ( item , name ) || [ ]
95113 end
96114
97- selected =
98- Enum . reduce ( options , [ ] , fn { _label , value } = option , acc ->
99- if value in selected_ids do
100- [ option | acc ]
101- else
102- acc
103- end
104- end )
105- |> Enum . reverse ( )
115+ selected_ids = Enum . map ( selected_ids , & to_string / 1 )
116+
117+ selected = Enum . filter ( options , fn { _label , value } -> value in selected_ids end )
106118
107119 assign ( socket , :selected , selected )
108120 end
109121
110122 defp maybe_assign_form ( % { assigns: % { type: :form } = assigns } = socket ) do
111123 % { selected: selected , options: options } = assigns
112124
125+ options = flatten_options ( options )
126+
113127 show_select_all = length ( selected ) != length ( options )
114128
115129 socket
@@ -167,21 +181,20 @@ defmodule Backpex.Fields.MultiSelect do
167181
168182 @ impl Phoenix.LiveComponent
169183 def handle_event ( "toggle-option" , % { "id" => id } , socket ) do
170- % { assigns: % { selected: selected , options: options , field_options: field_options } } = socket
184+ % { assigns: % { selected: selected , options: options } } = socket
171185
172- selected_item = Enum . find ( selected , fn { _label , value } -> value == id end )
186+ options = flatten_options ( options )
187+
188+ clicked_item = Enum . find ( options , fn { _label , value } -> value == id end )
173189
174190 new_selected =
175- if selected_item do
176- Enum . reject ( selected , fn { _label , value } -> value == id end )
191+ if clicked_item in selected do
192+ selected -- [ clicked_item ]
177193 else
178- selected
179- |> Enum . reverse ( )
180- |> Kernel . then ( & [ Enum . find ( options , fn { _label , value } -> value == id end ) | & 1 ] )
181- |> Enum . reverse ( )
194+ [ clicked_item ] ++ selected
182195 end
183196
184- show_select_all = length ( new_selected ) != length ( field_options . options . ( socket . assigns ) )
197+ show_select_all = length ( new_selected ) != length ( options )
185198
186199 socket
187200 |> assign ( :selected , new_selected )
@@ -191,13 +204,12 @@ defmodule Backpex.Fields.MultiSelect do
191204
192205 @ impl Phoenix.LiveComponent
193206 def handle_event ( "search" , params , socket ) do
194- % { assigns: % { name: name , field_options: field_options } = assigns } = socket
207+ socket = assign_options ( socket )
208+ % { assigns: % { name: name , options: options } } = socket
195209
196210 search_input = Map . get ( params , "change[#{ name } ]_search" )
197211
198- options =
199- field_options . options . ( assigns )
200- |> maybe_apply_search ( search_input )
212+ options = apply_search ( options , search_input )
201213
202214 socket
203215 |> assign ( :options , options )
@@ -207,27 +219,37 @@ defmodule Backpex.Fields.MultiSelect do
207219
208220 @ impl Phoenix.LiveComponent
209221 def handle_event ( "toggle-select-all" , _params , socket ) do
210- % { assigns: % { field_options: field_options , show_select_all: show_select_all } = assigns } = socket
222+ % { assigns: % { options: options , show_select_all: show_select_all } } = socket
211223
212- new_selected = if show_select_all , do: field_options . options . ( assigns ) , else: [ ]
224+ options = flatten_options ( options )
225+
226+ new_selected = if show_select_all , do: options , else: [ ]
213227
214228 socket
215229 |> assign ( :selected , new_selected )
216230 |> assign ( :show_select_all , not show_select_all )
217231 |> noreply ( )
218232 end
219233
220- defp maybe_apply_search ( options , search_input ) do
221- if String . trim ( search_input ) == "" do
222- options
223- else
224- 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
225237
226- Enum . filter ( options , fn { label , _value } ->
227- String . downcase ( label )
228- |> String . contains? ( search_input_downcase )
229- end )
230- 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 )
231253 end
232254
233255 defp prompt ( assigns , field_options ) do
0 commit comments