Simple clojurescript datepicker using rum and cljs-time

Simple clojurescript datepicker using rum and cljs-time

I created a simple clojurescript datepicker for a pet project and figured someone else might find it useful. It uses:

  • The rum library but should be easy to adapt for reagent etc.
  • cljs-time for convenience. Those with time and bravery should be able to replace the date functions easily.
  • bulma.io for style, which can also be easily swapped out with your stylesheet of choice.

See the Github Gist for any updates.

;;;; IMPLEMENTATION

(ns date-select.core
  (:require [rum.core :as rum]
            [cljs-time.core :as time]))

(defn initial-date
  ([] (let [selected (time/now)]
        {:year (time/year selected)
         :month (time/month selected)
         :day (time/day selected)}))
  ([year] (initial-date year 1 1))
  ([year month] (initial-date year month 1))
  ([year month day]
   (let [selected (time/date-time year month day)]
     {:year (time/year selected)
      :month (time/month selected)
      :day (time/day selected)})))


(rum/defcs DateCapture
 "Params
     start-year :int
     end-year: int
     label: string
     change: fn taking a map as param. example {:year 2019 :month 2 :day 9}
     initial-date: a map of {:year 2019 :month 2 :day 8} | use initial-date fn to construct"
  < (rum/local nil ::year) (rum/local nil ::month) (rum/local nil ::day)
  {:will-mount (fn [{*day ::day *month ::month *year ::year :as state}]
                 (let [now (time/now)
                       year (time/year now)
                       [start-year end-year _ change init-opts] (:rum/args state)]
                   (reset! *year (get init-opts :year year))
                   (reset! *month (get init-opts :month (time/month now)))
                   (reset! *day (get init-opts :day (time/day now)))
                   (assoc state
                          ::years (range start-year (inc end-year))
                          ::months (range 1 13)
                          ::notify-change (fn [] (change {:year @*year :month @*month :day @*day})))))
   :did-update (fn [{notify-change ::notify-change :as state}]
                 (notify-change)
                 state)}
  [{*day ::day *month ::month *year ::year :as state} start-year end-year label change]
  (let [last-day-of-month (time/day (time/last-day-of-the-month @*year @*month))
        days (range 1 (inc last-day-of-month))
        on-change (fn [*k]
                    (fn [e]
                      (reset! *k (js/parseInt (.. e -target -value)))
                      (let [new-last-day-of-month (time/day (time/last-day-of-the-month @*year @*month))]
                        (when (< new-last-day-of-month @*day)
                          (reset! *day 1)))))
        field (fn [range-coll *k]
                [:div.field
                 [:div.control
                  [:div.select
                   [:select {:value @*k :on-change (on-change *k)}
                    (map-indexed (fn [i x] [:option {:key i} x]) range-coll)]]]])]
    [:div.field.is-horizontal
     [:div.field-label.is-normal
      [:label.label label]]
     [:div.field-body
      (field (::years state) *year)
      (field (::months state) *month)
      (field days *day)]]))
;;;; USAGE

(ns date-select.example
  (:require [date-select.core :as ds]))

(def on-change prn)

(def label "My Date Selector")

(def initial-date (ds/initial-date 2018 1 1))

(ds/DateCapture 2018 2020 label on-change initial-date)