@@ -358,7 +358,6 @@ function Bonito.jsrender(session::Session, multiselect::MultiSelect)
358
358
359
359
onany (jsselection) do _jssel
360
360
sel = jsselection_to_selection (multiselect. options[], _jssel)
361
- # @info "New jsselection triggers new selection:" _jssel sel
362
361
if sel != multiselect. selection[]
363
362
multiselect. selection[] = sel
364
363
end
@@ -578,6 +577,210 @@ function _selection_to_jsselection(options, selection)
578
577
end
579
578
end
580
579
580
+ struct TomSelect{T}
581
+ options:: Observable {Vector{Union{T, OptionGroup{T}}}}
582
+ selection:: Observable{Vector{T}}
583
+ placeholder:: String
584
+ multi:: Bool
585
+ option_to_string:: Any
586
+ class:: String
587
+ id:: String
588
+ function TomSelect (_options, _selection= nothing ; T= Any, placeholder= " " , multi= true , option_to_string= repr, class= " " , id= " " )
589
+ options = _options isa Observable ? _options : Observable {Vector{T}} (_options)
590
+ selection = if isnothing (_selection)
591
+ Observable (T[])
592
+ elseif _selection isa Observable
593
+ @assert _selection isa Observable{Vector{T}}
594
+ _selection
595
+ else
596
+ Observable {Vector{T}} (_selection)
597
+ end
598
+ _class = multi ? " bonito-tomselect-multi" : " bonito-tomselect-single"
599
+ if class != " "
600
+ _class *= " " * class
601
+ end
602
+ if id == " "
603
+ id = gendomid (" tomselect" )
604
+ end
605
+ new {T} (options, selection, placeholder, multi, option_to_string, _class, id)
606
+ end
607
+ end
608
+
609
+ function Bonito. jsrender (session:: Session , tomselect:: TomSelect{T} ) where {T}
610
+ # generate internal observables of js representation of options and selection
611
+ tsoptions = Observable {Any} ()
612
+ tsselection = Observable {Vector{String}} (String[])
613
+
614
+ onany (tsselection) do _tssel
615
+ sel = tsselection_to_selection (tomselect, _tssel)
616
+ if sel != tomselect. selection[]
617
+ @debug " MS \" $(tomselect. placeholder) \" : New tsselection triggers new selection:" _tssel sel
618
+ tomselect. selection[] = sel
619
+ end
620
+ nothing
621
+ end
622
+
623
+ on (tomselect. selection; update= true ) do _sel
624
+ # check validity of selection
625
+ if ! tomselect. multi && length (_sel) > 1
626
+ deleteat! (_sel, firstindex (_sel)+ 1 : lastindex (_sel))
627
+ end
628
+ _tssel = selection_to_tsselection (tomselect, _sel)
629
+ _validsel = tsselection_to_selection (tomselect, _tssel)
630
+ tomselect. selection. val = _validsel
631
+ _validtssel = selection_to_tsselection (tomselect, _validsel)
632
+
633
+ @debug " MS \" $(tomselect. placeholder) \" : New selection trigger tsselection" _sel _validsel _validtssel
634
+ # musst update tsselection because options may have changed
635
+ tsselection[] = _validtssel
636
+
637
+ nothing
638
+ end
639
+
640
+ on (tomselect. options, update= true ) do _opts
641
+ # update options on js side
642
+ @debug " MS \" $(tomselect. placeholder) \" : New options" _opts
643
+ tsoptions[] = options_to_tsoptions (tomselect, _opts)
644
+ notify (tomselect. selection)
645
+ nothing
646
+ end
647
+
648
+
649
+ # Create a multi-select element
650
+ style = Styles (
651
+ " width" => " 100%" ,
652
+ )
653
+
654
+ domtype = tomselect. multi ? DOM. input : DOM. select
655
+ tom_dom = domtype (;
656
+ class= tomselect. class,
657
+ style,
658
+ id= tomselect. id,
659
+ )
660
+
661
+ # node fence see https://github.com/electron/electron/issues/254
662
+ container = DOM. div (
663
+ TOMSELECT_ESS,
664
+ TOMSELECT_CSS,
665
+ tom_dom
666
+ )
667
+
668
+ js_init = js """
669
+ ($TOMSELECT_ESS ).then ((ts ) => {
670
+ const tom_dom = document .getElementById ($ (tomselect .id ));
671
+ if (! tom_dom) {
672
+ console .error (" TomSelect not found for id $(tomselect.id)" );
673
+ }
674
+
675
+ const settings = {
676
+ options: $ (tsoptions).value .opts ,
677
+ optgroups: $ (tsoptions).value .groups ,
678
+ hideSelected: false ,
679
+ items: Array .from ($ (tsselection).value ),
680
+ optgroupField: ' class' ,
681
+ placeholder: $ (tomselect .placeholder ),
682
+ plugins: {},
683
+ };
684
+ // push remove button to plugins when multi
685
+ if ($ (tomselect .multi )) {
686
+ settings .plugins .remove_button = {
687
+ title: ' Remove this item'
688
+ }
689
+ }
690
+ const tomselect = new TomSelect (tom_dom, settings);
691
+
692
+ function selEvent (){
693
+ const newsel = Array .from (tomselect .items );
694
+ console .log (" Change event, update tsselection" , newsel);
695
+ $ (tsselection).notify (newsel);
696
+ }
697
+ // tomselect.on('item_add', selEvent);
698
+ // tomselect.on('item_remove', selEvent);
699
+ tomselect .on (' change' , selEvent);
700
+ // tomselect.on('item_select', selEvent); // click on tag?
701
+
702
+ $ (tsoptions).on (val => {
703
+ console .log (" Got new options" , val);
704
+ tomselect .clear (true ); // false -> silent
705
+ tomselect .clearOptions ();
706
+ tomselect .clearOptionGroups ();
707
+ tomselect .addOptions (val .opts );
708
+ val .groups .forEach (group => {
709
+ tomselect .addOptionGroup (group .value , {label: group .label });
710
+ });
711
+ tomselect .refreshOptions (false ); // false -> dont open
712
+ console .log (" Finished update of options" )
713
+ // seting value will update the tsselection
714
+ // which triggers check in julia
715
+ // console.log("Setting value in option update", $(tsselection).value);
716
+ // tomselect.setValue($(tsselection).value);
717
+ // tomselect.refreshItems();
718
+ });
719
+
720
+ function array_equal (a1 , a2 ) {
721
+ if (a1 === a2) return true ; // If both are the same reference
722
+ if (a1 == null || a2 == null ) return false ; // If one is null or undefined
723
+ if (a1 .length !== a2 .length ) return false ; // If lengths are different
724
+
725
+ return a1 .every (function (val , i ){return val === a2[i];})
726
+ }
727
+
728
+ $ (tsselection).on (val => {
729
+ const current_items = Array .from (tomselect .items );
730
+ if (! array_equal (val, current_items)) {
731
+ console .log (" Update displayed value of tomselect" , val, current_items);
732
+ tomselect .setValue (val, true ); // do not notify
733
+ // tomselect.refreshItems();
734
+ }
735
+ });
736
+ });
737
+ """
738
+ Bonito. evaljs (session, js_init)
739
+
740
+ return Bonito. jsrender (session, container)
741
+ end
742
+
743
+ function options_to_tsoptions (tomselect, options)
744
+ to_string = tomselect. option_to_string
745
+ tsoptions = []
746
+ jsgroups = []
747
+ for option in options
748
+ if option isa OptionGroup
749
+ class = option. label
750
+ push! (jsgroups, (;value= option. label, label= option. label))
751
+ for suboption in option. options
752
+ text = to_string (suboption)
753
+ push! (tsoptions, (;class, value= text, text= text))
754
+ end
755
+ else
756
+ text = to_string (option)
757
+ push! (tsoptions, (;value= text, text= text))
758
+ end
759
+ end
760
+ (; opts= tsoptions, groups= jsgroups)
761
+ end
762
+
763
+ function tsselection_to_selection (tomselect:: TomSelect{T} , jsselection) where {T}
764
+ isempty (jsselection) && return T[]
765
+ newsel = _tsselection_to_selection .(Ref (tomselect. options[]), jsselection, tomselect. option_to_string)
766
+ filter (! isnothing, newsel)
767
+ end
768
+ function _tsselection_to_selection (options, tsselection:: String , tostring)
769
+ for option in options
770
+ if option isa OptionGroup
771
+ for suboption in option. options
772
+ tostring (suboption) == tsselection && return suboption
773
+ end
774
+ else
775
+ tostring (option) == tsselection && return option
776
+ end
777
+ end
778
+ end
779
+ function selection_to_tsselection (tomselect, selection)
780
+ tomselect. option_to_string .(selection)
781
+ end
782
+
783
+
581
784
@kwdef struct ToggleSwitch
582
785
value:: Observable{Bool} = Observable {Bool} ()
583
786
height:: Int = 24
0 commit comments