Skip to content

Add custom start week to date picker #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
238d323
Fix negative months bug
fabricioc8 Jun 28, 2025
50f14af
Add week-start component option
fabricioc8 Jun 28, 2025
b683844
Pass argument to child components and sort days based on value
fabricioc8 Jun 28, 2025
0164482
Pass argument to weeks-component and fix days-to-fill logic
fabricioc8 Jun 28, 2025
4e14d27
Refactor current-month
fabricioc8 Jun 28, 2025
d725ddc
Add datepicker dosctring
fabricioc8 Jun 28, 2025
103a110
Add datepicker example
fabricioc8 Jun 28, 2025
2601efa
Improve readability with more idiomatic expressions
fabricioc8 Jun 28, 2025
7a239e3
Add :date-format opt
fabricioc8 Jun 28, 2025
af69ba7
Modify convert-input to receive custom date format
fabricioc8 Jun 28, 2025
56e8157
Modify update-target to re-convert string into #inst
fabricioc8 Jun 28, 2025
7007be9
Calculate placeholder from dat-format if exists
fabricioc8 Jun 28, 2025
486c568
Add missing require
fabricioc8 Jun 28, 2025
78c6a51
Add textinput component example
fabricioc8 Jun 28, 2025
f5fa906
Add initial value to example state
fabricioc8 Jun 28, 2025
bbfa9d4
Apply mask only if an initial value exists, if not, show placeholder
fabricioc8 Jun 28, 2025
d33040f
Validate date-format to match with mask structure
fabricioc8 Jun 28, 2025
5a60f77
Add missing dependency
fabricioc8 Jun 28, 2025
351a66d
Fix readme doc
fabricioc8 Jun 28, 2025
6812225
Add textinput docstring
fabricioc8 Jun 28, 2025
ee2a10c
Improve idiomatic expression
fabricioc8 Jun 28, 2025
791cd38
Fix label name
fabricioc8 Jun 28, 2025
036298f
Fix indentation
fabricioc8 Jun 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
30 changes: 26 additions & 4 deletions src/examples/basic/datepicker_example.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down Expand Up @@ -62,7 +77,14 @@
{:for "btn-cal-close-on-select"})]]

[:div.col-lg-6
[:div.well
(w/datepicker app :inline)
]
[:label (str (:inline app))]]]]]))))
[: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))]]]]]]))))
3 changes: 2 additions & 1 deletion src/examples/basic/state_example.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -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 ""
Expand Down
40 changes: 26 additions & 14 deletions src/om_widgets/datepicker.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ojo que snake case no se usa en clj

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

si... fue un typo...

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

que problema le ves a esto?

Copy link
Author

@fabricioc8 fabricioc8 Jul 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mm te referis al uso de case? o notas un bug en el calculo?

Copy link
Author

@fabricioc8 fabricioc8 Jul 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

si es por el case, no tiene valor por default lo que causario un error por el nil. No se lo agregue porque la funcion principal ya tiene un default con el :or. Pero podria agregarse para hacerlo mas robusto supongo

Copy link
Contributor

@kernelp4nic kernelp4nic Jul 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

claro, si paso nil va a funcionar, pero si paso algo que no es :sunday o :monday va a reventar, yo esperaría o que defaultea a :monday que es el 90% de los casos, o que directamente te avise que la inicialización está mal y que los valores esperados son :monday :sunday (usar otra cosa ya es extremadamente raro)

: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)))

Expand Down Expand Up @@ -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))]
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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}}))
65 changes: 35 additions & 30 deletions src/om_widgets/textinput.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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")
Expand All @@ -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"}]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

particularmente creo que llevaría este let más cerca de donde se usa y no en la raíz del componente (porque en general algo a ese nivel asumimos que va a ser usado por todos lados pero no es el caso)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sisi, es verdad, podria moverse tranquilamente a la llave :date-format

(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"))}))})))