Skip to content

Multi Select: aka tags

Conor White-Sullivan edited this page Apr 30, 2017 · 1 revision

Problem:

You want a multi-select component with just reagent

Solution:

use something like this

(let [selections (r/atom [])]
[multi {:typeahead-atom (r/atom "")
                     :on-delete #(reset! selections (vec (filter (partial not= %)) @selections)))
                     :attr {:auto-focus true}
                     :selections-visible true
                     :selections-atom selections
                     :suggestions ["Apple" "Bannanas""Grapes""Algebra"]
                     :save! #(swap! selections conj %)}])

defined like this

(defn component [{:keys [highlight-class
                              placeholder
                              item-class
                              list-class
                              highlight-style
                              container-class
                              suggestions
                              filter-fn
                              save!
                              attr
                              item-style
                              on-remove!
                              selections-atom
                              selection-class
                              on-delete
                              use-styles
                              input-style
                              ]
                       :or {attr {}
                            container-class "tags"
                            list-class ""
                            input-style {:outline "1px solid transparent"}
                            highlight-style {:background-color "gray"}
                            item-style {}
                            ;; highlight-class "highlight"
                            on-remove #(swap! selections-atom (fn [y] (remove #{%} y)))
                            selection-class "tags-output-item"
                            filter-fn (fn [typeahead-atom x]
                                         (-> (.toLowerCase x)
                                             (.indexOf (.toLowerCase @typeahead-atom))
                                             (> -1)))}}]
  (let [typeahead-atom (r/atom "")
        selected-index (r/atom -1)
        typeahead-hidden? (r/atom false)
        mouse-on-list? (r/atom false)
        reset-internals! #(do
                           (reset! selected-index 0)
                           (reset! typeahead-atom ""))]
    (fn []
      (let [options  (if (clojure.string/blank? @typeahead-atom)
                       []
                       (filter (partial filter-fn typeahead-atom) (deref-or-value suggestions)))
            matching-options (filter (comp not (set @selections-atom)) options)
            choose-selected #(if (and (not-empty matching-options)
                                       (> @selected-index -1))
                              (let [choice (nth matching-options @selected-index)]
                                (save! choice)
                                (reset-internals!))
                              (when (and (not (str/blank? @typeahead-atom))
                                         ;; might be nice to return typeahead-atom warning to user
                                         ;; idea is not to allow duplicate items
                                         (not ((set @selections-atom) @typeahead-atom)))
                                (do
                                  (save! @typeahead-atom)
                                  (reset-internals!))))]
        [:div
         {:class (if use-styles "tags flex"
                     )}
        
         [:div.tags-output
          (when @selections-atom
            (for [x @selections-atom]
              ^{:key x}
              [:span
               {:class selection-class
                :on-click #(on-remove! x)}
               (str x)]
              ))
          [:input
           (merge attr        {:value @typeahead-atom
                               :placeholder placeholder
                               :on-change #(reset! typeahead-atom (-> % .-target .-value))
                               :style input-style
                               :on-key-down #(do
                                               (case (.-which %)
                                                 38 (do
                                                      (.preventDefault %)
                                                      (when-not (= @selected-index -1)
                                                        (swap! selected-index dec)))
                                                 40 (do
                                                      (.preventDefault %)
                                                      (when-not (= @selected-index (dec (count matching-options)))
                                                        (swap! selected-index inc)))
                                                 9  (choose-selected)
                                                 13 (choose-selected)
                                                 8  (when (clojure.string/blank? @typeahead-atom)
                                                      (on-delete))
                                                 27 (do #_(reset! typeahead-hidden? true)
                                                        (reset! selected-index -1))
                                                 "default"))})]

          [:ul {:style
                {:display (if (or (empty? matching-options) @typeahead-hidden?) :none :block) }
                :class list-class
                :on-mouse-enter #(reset! mouse-on-list? true)
                :on-mouse-leave #(reset! selected-index -1)}
           (doall
            (map-indexed
             (fn [index result]
               [:li {:tab-index     index
                     :key           index
                     :style (if (= @selected-index index) highlight-style item-style)
                     :class         (if (= @selected-index index) highlight-class item-class)
                     :on-mouse-over #(do
                                       (reset! selected-index (js/parseInt (.getAttribute (.-target %) "tabIndex"))))
                     :on-click      #(do
                                       (reset! typeahead-atom "")
                                       (save! result)
                                       )}
                result])
             matching-options))]]])
       )))

Or just read the re-com source code, strip it apart and figure out what you like -- that's how I arrived at the component above

Clone this wiki locally