diff --git a/README.md b/README.md index e579e40..929bf51 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ just run: ```bash lein figwheel basic ``` -inside the repo and this will start a webserver serving the sample files, point your browser to: `http://localhost:3449/examples/basic/index.html` +inside the repo and this will start a webserver serving the sample files, point your browser to: `http://localhost:3450/examples/basic/index.html` and you are ready to go. ## License diff --git a/project.clj b/project.clj index 5da1501..c32397a 100644 --- a/project.clj +++ b/project.clj @@ -7,6 +7,7 @@ :dependencies [[org.clojure/clojure "1.7.0"] [org.clojure/clojurescript "1.7.122" :scope "provided"] [org.clojure/core.async "0.1.346.0-17112a-alpha"] + [org.clojure/tools.nrepl "0.2.13"] [org.omcljs/om "0.8.8" :scope "provided"] [org.clojars.intception/thread-expr "1.4.0"] [com.andrewmcveigh/cljs-time "0.3.13"] diff --git a/src/examples/basic/datepicker_example.cljs b/src/examples/basic/datepicker_example.cljs index e7aa12f..e197298 100644 --- a/src/examples/basic/datepicker_example.cljs +++ b/src/examples/basic/datepicker_example.cljs @@ -30,6 +30,21 @@ (fn [close] (w/datepicker app :input-group-left)) {:for "btn-cal-left"})] + [:div.well + [:label "Input Group - left side (:date-format dd/MM/yyyy)"] + (w/popover + (fn [show] + [:div.input-group + [:span.input-group-btn + [:button.btn.btn-primary {:id "btn-cal-left-dt-format" :onClick show} + [:span.glyphicon.glyphicon-calendar]]] + (w/textinput app :input-group-date-format {:input-class "form-control" + :input-format "date" + :date-format "dd/MM/yyyy" + :placeholder "MM/DD/YYYY"})]) + (fn [close] + (w/datepicker app :input-group-date-format)) + {:for "btn-cal-left-dt-format"})] [:div.well [:label "Input Group - right side"] @@ -62,7 +77,14 @@ {:for "btn-cal-close-on-select"})]] [:div.col-lg-6 - [:div.well - (w/datepicker app :inline) - ] - [:label (str (:inline app))]]]]])))) \ No newline at end of file + [:div + [:span "Week starting on Monday"] + [:div.well + (w/datepicker app :inline)] + [:label (str (:inline app))]] + [:hr] + [:div + [:span "Week starting on Sunday"] + [:div.well + (w/datepicker app :inline {:week-start :sunday})] + [:label (str (:inline app))]]]]]])))) \ No newline at end of file diff --git a/src/examples/basic/state_example.cljs b/src/examples/basic/state_example.cljs index e195399..c970174 100644 --- a/src/examples/basic/state_example.cljs +++ b/src/examples/basic/state_example.cljs @@ -72,7 +72,8 @@ :datepicker {:inline #inst "1991-01-25" :input-group-left #inst "1991-01-25" :input-group-right #inst "1991-01-25" - :input-group-close-on-change #inst "1991-01-25"} + :input-group-close-on-change #inst "1991-01-25" + :input-group-date-format #inst "1991-01-25"} :sex :male :tab {:selected-tab :inbox} :form {:name "" diff --git a/src/om_widgets/datepicker.cljs b/src/om_widgets/datepicker.cljs index 19e3cc1..f1f322e 100644 --- a/src/om_widgets/datepicker.cljs +++ b/src/om_widgets/datepicker.cljs @@ -9,14 +9,20 @@ ;; TODO translate (defonce days-short ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]) -(defn- build-previous-month-days [date] - (let [current-month (time/date-time (time/year date) (time/month date)) - weekday-current-month (time/day-of-week current-month) - previous-month (time/minus current-month (time/months 1)) +(defn- build-previous-month-days [date week-start] + (let [current_month (time/month date) + first-day-of-month (time/date-time (time/year date) current_month) + weekday-current-month (time/day-of-week first-day-of-month) + previous-month (time/minus first-day-of-month (time/months 1)) last-day (time/number-of-days-in-the-month previous-month) - days-to-fill (range (inc (- last-day (dec weekday-current-month))) (inc last-day))] + days-to-fill (range (inc + (- last-day + (case week-start + :monday (dec weekday-current-month) + :sunday weekday-current-month))) + (inc last-day))] (mapv (fn [d] {:day d - :month (- 1 (time/month date)) + :month (if (= current_month 1) 12 (dec current_month)) :year (time/year date) :belongs-to-month :previous}) days-to-fill))) @@ -55,8 +61,8 @@ [...] [{:day 1 :month 3 :year 2014 :belongs-to-month :next}] ] " - [date] - (let [previous-days (build-previous-month-days date) + [date week-start] + (let [previous-days (build-previous-month-days date week-start) currrent-days (build-current-month-days date) next-days (build-next-month-days date) days (into [] (concat previous-days currrent-days next-days))] @@ -167,13 +173,13 @@ om/IDisplayName (display-name [_] "DatepickerWeeks") om/IRenderState - (render-state [this {:keys [date path onChange] :as state}] + (render-state [this {:keys [date path onChange week-start] :as state}] (apply dom/tbody nil (map (fn [week] (apply dom/tr nil (map (fn [d] (om/build day-component app {:state {:day d :path path :date date :onChange onChange}})) week))) - (build-weeks date)))))) + (build-weeks date week-start)))))) (defn- year-component [app owner] (reify @@ -202,7 +208,7 @@ om/IDisplayName (display-name [_] "DatepickerBody") om/IRenderState - (render-state [this {:keys [path date onChange] :as state}] + (render-state [this {:keys [path date onChange week-start] :as state}] (dom/div #js {:className "datepicker datepicker-days" :style #js {:display "block"}} (dom/table #js {:className "table-condensed"} (dom/thead nil @@ -223,10 +229,12 @@ :onClick (fn [e] (om/set-state! owner :date (time/plus date (time/months 1))))} ">")) (apply dom/tr nil - (om/build-all day-header days-short))) + (om/build-all day-header (case week-start + :monday days-short + :sunday (concat [(last days-short)] (butlast days-short)))))) ;; datepicker body - (om/build weeks-component app {:state {:path path :date date :onChange onChange}})))))) + (om/build weeks-component app {:state {:path path :date date :onChange onChange :week-start week-start}})))))) (defn datepicker "Datepicker public API @@ -237,13 +245,17 @@ app >> the cursor path >> the internal path to update the cursor + opts: + - week-start: defines whether the week starts on Monday or Sunday. Dafults to Monday + note: we assume today date if the cursor does not have a date " - [app path {:keys [id hidden onChange] :or {hidden true}}] + [app path {:keys [id hidden onChange week-start] :or {hidden true week-start :monday}}] (om/build body-component app {:state {:id id :hidden hidden :date (if (instance? js/Date (utils/om-get app [path])) (time/date-time (utils/om-get app [path])) (time/now)) + :week-start week-start :path path :onChange onChange}})) diff --git a/src/om_widgets/textinput.cljs b/src/om_widgets/textinput.cljs index f52d6d2..c5218f5 100644 --- a/src/om_widgets/textinput.cljs +++ b/src/om_widgets/textinput.cljs @@ -6,8 +6,8 @@ [cljs-time.format :as time-format] [cljs-time.coerce :as timec] [goog.object :as gobj] - [pallet.thread-expr :as th])) - + [pallet.thread-expr :as th] + [clojure.string :as str])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def date-local-mask "00/00/0000") @@ -41,20 +41,20 @@ (time-format/unparse (time-format/formatter fmt) dt))) (defn- convert-input - [input-type value] + [input-type value fmt] (condp = input-type "date" (try - (string-from-date value (infer-date-format-pattern)) + (string-from-date value (or fmt (infer-date-format-pattern))) (catch js/Error e ;; assume empty string for unhandled values (str value))) value)) (defn- convert-output - [output-type value] + [output-type value fmt] (condp = output-type "date" (try - (date-from-localstring value (infer-date-format-pattern)) + (date-from-localstring value (or fmt (infer-date-format-pattern))) (catch js/Error e value)) "numeric" (let [f (js/parseFloat value)] @@ -132,11 +132,11 @@ :mask)) (defn- update-target - [target owner {:keys [input-format path onChange private-state] :as state} bInternal] + [target owner {:keys [input-format date-format path onChange private-state] :as state} bInternal] (when (and target (not= 0 (:cbtimeout @private-state))) (let [dom-node (:dom-node @private-state) - value (convert-output input-format (.-value dom-node))] + value (convert-output input-format (.-value dom-node) date-format)] (do (.clearTimeout js/window (:cbtimeout @private-state)) (swap! private-state assoc :cbtimeout 0 :prev-value value) @@ -331,11 +331,11 @@ (recur (next mv) (next cv) (conj r (if (re-matches m c) c \_)))) r))) (:mask-vector @private-state) - (vec (convert-input (:input-format state) value))) + (vec (convert-input (:input-format state) value (:date-format state)))) prev-value (:prev-value @private-state) new-value (apply str entered-values) dom-node (:dom-node @private-state)] - (when (and (not= prev-value new-value) dom-node) + (when (and value (not= prev-value new-value) dom-node) (do (swap! private-state assoc :entered-values entered-values :prev-value new-value) @@ -451,10 +451,12 @@ :onPaste #(if (false? (handlepaste target owner state %)) (.preventDefault %) nil) - :placeholder (:placeholder state) + :placeholder (if (:date-format state) + (str/upper-case (:date-format state)) + (:placeholder state)) :disabled (:disabled state) ;:typing-timeout (:typing-timeout state) - :type (condp = (:input-format state) + :type (case (:input-format state) "password" "password" "numeric" "number" "text") @@ -471,22 +473,25 @@ (merge {:resize (name (:resize state))})))))))) (defn textinput - [target path {:keys [input-class input-format align] :as opts + "Opts: + - date-format: only applied when input-format is \"date\". Should be one of the valid values, otherwise is ignored" + [target path {:keys [input-class input-format date-format align] :as opts :or {input-class ""}}] - (om/build create-textinput target - {:state (-> opts - (cond-> (nil? (:read-only opts)) - (assoc :read-only false)) - (merge {:path path - :input-mask (cond - (= input-format "numeric") "numeric" - (= input-format "integer") "numeric" - (= input-format "currency") "numeric" - (= input-format "date") date-local-mask - :else input-format) - :currency (if (= input-format "currency") true false) - :align (or align - (cond (= input-format "numeric") "right" - (= input-format "integer") "right" - (= input-format "currency") "right" - :else "left"))}))})) + (let [valid-date-formats #{"MM/dd/yyy" "dd/MM/yyyy"}] + (om/build create-textinput target + {:state (-> opts + (cond-> (nil? (:read-only opts)) + (assoc :read-only false)) + (merge {:path path + :input-mask (case input-format + ("numeric" "integer" "currency") "numeric" + "date" date-local-mask + input-format) + :date-format (when (and (= input-format "date") + (valid-date-formats date-format)) + date-format) + :currency (= input-format "currency") + :align (or align + (case input-format + ("numeric" "integer" "currency") "right" + "left"))}))})))