diff --git a/source/Ui/DatePicker.elm b/source/Ui/DatePicker.elm index 68d3932..610226a 100644 --- a/source/Ui/DatePicker.elm +++ b/source/Ui/DatePicker.elm @@ -1,66 +1,46 @@ -module Ui.DatePicker - exposing - ( Model - , Msg - , init - , update - , subscriptions - , onChange - , view - , render - , setValue - , closeOnSelect - ) +module Ui.DatePicker exposing + ( Model, Msg, init, update, subscriptions, onChange, view, render, setValue + , closeOnSelect ) {-| An input component that displays a **Calendar** (in a dropdown) when focused, allowing the user to manipulate the selected date. - # Model - @docs Model, Msg, init, subscriptions, update - # DSL - @docs closeOnSelect - # Events - @docs onChange - # View - @docs view, render -@docs setValue - - # Functions - +@docs setValue -} import Html.Events.Extra exposing (onPreventDefault) import Html exposing (node, text) import Html.Lazy + import Date.Extra.Format exposing (isoDateFormat, format) import Date.Extra.Config.Configs as DateConfigs import Time import Date + import Ui.Helpers.Dropdown as Dropdown exposing (Dropdown) import Ui.Helpers.Picker as Picker import Ui.Native.Uid as Uid import Ui.Calendar import Ui.Icons import Ui + import Ui.Styles.DatePicker exposing (defaultStyle) import Ui.Styles - {-| Representation of a date picker: - - **closeOnSelect** - Whether or not to close the dropdown after selecting - **format** - The format of the date to render in the input - **readonly** - Whether or not the date picker is readonly @@ -68,177 +48,165 @@ import Ui.Styles - **uid** - The unique identifier of the date picker - **calendar** - The model of the calendar - **dropdown** - The model of the dropdown - -} type alias Model = - { calendar : Ui.Calendar.Model - , closeOnSelect : Bool - , dropdown : Dropdown - , format : String - , disabled : Bool - , readonly : Bool - , uid : String - } + { calendar : Ui.Calendar.Model + , closeOnSelect : Bool + , dropdown : Dropdown + , format : String + , disabled : Bool + , readonly : Bool + , uid : String + } {-| Messages that a date picker can receive. -} type Msg - = Calendar Ui.Calendar.Msg - | Picker Picker.Msg - | Select Time.Time - | Increment - | Decrement - | NoOp + = Calendar Ui.Calendar.Msg + | Picker Picker.Msg + | Select Time.Time + | Increment + | Decrement + | NoOp {-| Initializes a date picker with the given date. datePicker = - Ui.DatePicker.init () - |> Ui.DatePicker.closeOnSelect true - + Ui.DatePicker.init () + |> Ui.DatePicker.closeOnSelect true -} init : () -> Model init _ = - { calendar = Ui.Calendar.init () - , dropdown = Dropdown.init - , format = isoDateFormat - , closeOnSelect = False - , disabled = False - , readonly = False - , uid = Uid.uid () - } - |> Dropdown.offset 5 + { calendar = Ui.Calendar.init () + , dropdown = Dropdown.init + , format = isoDateFormat + , closeOnSelect = False + , disabled = False + , readonly = False + , uid = Uid.uid () + } + |> Dropdown.offset 5 {-| Subscribe to the changes of a date picker. - subscriptions = - Ui.DatePicker.onChange DatePickerChanged datePicker - + subscriptions = Ui.DatePicker.onChange DatePickerChanged datePicker -} onChange : (Time.Time -> msg) -> Model -> Sub msg onChange msg model = - Ui.Calendar.onChange msg model.calendar + Ui.Calendar.onChange msg model.calendar {-| Subscriptions for a date picker. - subscriptions = - Sub.map DatePicker (Ui.DatePicker.subscriptions datePicker) - + subscriptions = Sub.map DatePicker (Ui.DatePicker.subscriptions datePicker) -} subscriptions : Model -> Sub Msg subscriptions model = - Sub.batch - [ Ui.Calendar.onChange Select model.calendar - , Sub.map Picker (Picker.subscriptions model) - ] + Sub.batch + [ Ui.Calendar.onChange Select model.calendar + , Sub.map Picker (Picker.subscriptions model) + ] {-| Sets whether or not to close the dropdown when selecting an other date. -} closeOnSelect : Bool -> Model -> Model closeOnSelect value model = - { model | closeOnSelect = value } + { model | closeOnSelect = value } {-| Updates a date picker. - ( updatedDatePicker, cmd ) = - Ui.DatePicker.update msg datePicker - + ( updatedDatePicker, cmd ) = Ui.DatePicker.update msg datePicker -} update : Msg -> Model -> ( Model, Cmd Msg ) update action model = - case action of - NoOp -> - ( model, Cmd.none ) - - Calendar act -> - let - ( calendar, effect ) = - Ui.Calendar.update act model.calendar - in - ( { model | calendar = calendar }, Cmd.map Calendar effect ) - - Select time -> - let - updatedModel = - if model.closeOnSelect then - Dropdown.close model - else - model - in - ( updatedModel, Cmd.none ) - - Picker act -> - ( Picker.update act model, Cmd.none ) - - Decrement -> - ( { model | calendar = Ui.Calendar.previousDay model.calendar } - |> Dropdown.open - , Cmd.none - ) - - Increment -> - ( { model | calendar = Ui.Calendar.nextDay model.calendar } - |> Dropdown.open - , Cmd.none - ) + case action of + NoOp -> + ( model, Cmd.none ) + + Calendar act -> + let + ( calendar, effect ) = + Ui.Calendar.update act model.calendar + in + ( { model | calendar = calendar }, Cmd.map Calendar effect ) + + Select time -> + let + updatedModel = + if model.closeOnSelect then + Dropdown.close model + else + model + in + ( updatedModel, Cmd.none ) + + Picker act -> + ( Picker.update act model, Cmd.none ) + + Decrement -> + ( { model | calendar = Ui.Calendar.previousDay model.calendar } + |> Dropdown.open + , Cmd.none + ) + + Increment -> + ( { model | calendar = Ui.Calendar.nextDay model.calendar } + |> Dropdown.open + , Cmd.none + ) {-| Lazily renders a date picker in the given locale. Ui.DatePicker.view "en_us" model - -} view : String -> Model -> Html.Html Msg view locale model = - Html.Lazy.lazy2 render locale model + Html.Lazy.lazy2 render locale model {-| Renders a date picker in the given locale. Ui.DatePicker.render "en_us" model - -} render : String -> Model -> Html.Html Msg render locale model = - let - dateText = - (format (DateConfigs.getConfig locale) model.format model.calendar.value) - in - Picker.view - { attributes = Ui.Styles.apply defaultStyle - , address = Picker - , keyActions = - [ ( 40, Increment ) - , ( 38, Decrement ) - , ( 39, Increment ) - , ( 37, Decrement ) - ] - , contents = - [ node "ui-date-picker-content" [] [ text dateText ] - , Ui.Icons.calendar [] - ] - , dropdownContents = - [ node "ui-date-picker-calendar" - [ onPreventDefault "mousedown" NoOp ] - [ Html.map Calendar (Ui.Calendar.view locale model.calendar) - ] - ] - } - model + let + dateText = + (format (DateConfigs.getConfig locale) model.format model.calendar.value) + in + Picker.view + { attributes = Ui.Styles.apply defaultStyle + , address = Picker + , keyActions = + [ ( 40, Increment ) + , ( 38, Decrement ) + , ( 39, Increment ) + , ( 37, Decrement ) + ] + , contents = + [ node "ui-date-picker-content" [] [ text dateText ] + , Ui.Icons.calendar [] + ] + , dropdownContents = + [ node "ui-date-picker-calendar" + [ onPreventDefault "mousedown" NoOp ] + [ Html.map Calendar (Ui.Calendar.view locale model.calendar) + ] + ] + } model {-| Sets the value of a date picker ( updatedDatePicker, cmd ) = - Ui.DatePicker.setValue (Ext.Date.create 1980 5 17) datePicker - + Ui.DatePicker.setValue (Ext.Date.create 1980 5 17) datePicker -} setValue : Date.Date -> Model -> Model setValue date model = - { model | calendar = Ui.Calendar.setValue date model.calendar } + { model | calendar = Ui.Calendar.setValue date model.calendar } diff --git a/source/Ui/DateRangePicker.elm b/source/Ui/DateRangePicker.elm index 964abe7..8a6e7d3 100644 --- a/source/Ui/DateRangePicker.elm +++ b/source/Ui/DateRangePicker.elm @@ -1,7 +1,8 @@ -module Ui.DateRangePicker exposing (..) +module Ui.DateRangePicker exposing (WhichDatePicker(..), Model, Msg, init, update, view, onSecondDatePickerChange, onFirstDatePickerChange, setCalendarValue, setValue, closeWhichOnSelect, disableWhich, disable) import Html exposing (..) import Time +import Ui.Container import Ui.DatePicker import Ui.Calendar @@ -12,11 +13,16 @@ type alias Model = } +type WhichDatePicker + = First + | Second + + {-| Msg is messaging that's been contextualized per date picker -} type Msg - = Calendar1 Ui.DatePicker.Msg - | Calendar2 Ui.DatePicker.Msg + = DatePicker1 Ui.DatePicker.Msg + | DatePicker2 Ui.DatePicker.Msg init : () -> Model @@ -29,24 +35,87 @@ init _ = update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of - Calendar1 calendarMsg -> + DatePicker1 calendarMsg -> let ( updatedModel, effect ) = Ui.DatePicker.update calendarMsg model.datePicker1 in - ( { model | datePicker1 = updatedModel }, Cmd.map Calendar1 effect ) + ( { model | datePicker1 = updatedModel }, Cmd.map DatePicker1 effect ) - Calendar2 calendarMsg -> + DatePicker2 calendarMsg -> let ( updatedModel, effect ) = Ui.DatePicker.update calendarMsg model.datePicker2 in - ( { model | datePicker2 = updatedModel }, Cmd.map Calendar2 effect ) + ( { model | datePicker2 = updatedModel }, Cmd.map DatePicker2 effect ) view : String -> Model -> Html Msg view locale model = - div [] - [ Html.map Calendar1 (Ui.DatePicker.view locale model.datePicker1) - , Html.map Calendar2 (Ui.DatePicker.view locale model.datePicker2) + Ui.Container.row [] + [ Html.map DatePicker1 (Ui.DatePicker.view locale model.datePicker1) + , Html.map DatePicker2 (Ui.DatePicker.view locale model.datePicker2) ] + + +{-| Subscribe to the changes of an individual date picker. + +e.g. + +subscriptions = +Ui.DateRangePicker.onSecondDatePickerChange DateRangePickerChanged datePicker + +-} +onSecondDatePickerChange : (Time.Time -> msg) -> Model -> Sub msg +onSecondDatePickerChange msg model = + Ui.DatePicker.onChange msg model.datePicker2 + + +onFirstDatePickerChange : (Time.Time -> msg) -> Model -> Sub msg +onFirstDatePickerChange msg model = + Ui.DatePicker.onChange msg model.datePicker1 + + + +{-- +Helpful setters +-} + + +setCalendarValue : (Ui.Calendar.Model -> Ui.Calendar.Model) -> Ui.DatePicker.Model -> Ui.DatePicker.Model +setCalendarValue setter picker = + { picker | calendar = setter picker.calendar } + + +setValue : (Ui.DatePicker.Model -> Ui.DatePicker.Model) -> WhichDatePicker -> Model -> Model +setValue updater which picker = + case which of + First -> + { datePicker2 = picker.datePicker2 + , datePicker1 = updater picker.datePicker1 + } + + Second -> + { datePicker2 = updater picker.datePicker2 + , datePicker1 = picker.datePicker1 + } + + +closeWhichOnSelect : Bool -> WhichDatePicker -> Model -> Model +closeWhichOnSelect value which model = + setValue (\d -> Ui.DatePicker.closeOnSelect value d) which model + + +disableWhich : WhichDatePicker -> Model -> Model +disableWhich which picker = + setValue (\d -> { d | disabled = True }) which picker + + +disable : Model -> Model +disable picker = + let + disabler = + (\d -> { d | disabled = True }) + in + setValue disabler First picker + |> setValue disabler Second diff --git a/spec/Ui/DateRangePickerSpec.elm b/spec/Ui/DateRangePickerSpec.elm index e63fb3e..e005f71 100644 --- a/spec/Ui/DateRangePickerSpec.elm +++ b/spec/Ui/DateRangePickerSpec.elm @@ -8,27 +8,25 @@ import Ui.Native.Uid as Uid import Date.Extra.Format exposing (isoDateFormat, format) import Date import Ext.Date -import Ui.DateRangePicker +import Ui.DateRangePicker as DRP exposing (setCalendarValue, setValue, WhichDatePicker(..), disableWhich) import Ui.DatePicker -import Ui.Calendar exposing (..) import Html exposing (..) import Html.Attributes exposing (class) -import Ui.Helpers.Dropdown as Dropdown exposing (Dropdown) type alias Model = - { simple : Ui.DateRangePicker.Model - , oneDisabled : Ui.DateRangePicker.Model - , withPreselectedDates : Ui.DateRangePicker.Model - , readOnly : Ui.DateRangePicker.Model + { simple : DRP.Model + , oneDisabled : DRP.Model + , withPreselectedDates : DRP.Model + , readOnly : DRP.Model } type Msg - = Simple Ui.DateRangePicker.Msg - | OneDisabled Ui.DateRangePicker.Msg - | WithPreselectedDates Ui.DateRangePicker.Msg - | ReadOnly Ui.DateRangePicker.Msg + = Simple DRP.Msg + | OneDisabled DRP.Msg + | WithPreselectedDates DRP.Msg + | ReadOnly DRP.Msg init : () -> Model @@ -46,33 +44,15 @@ init _ = secondCalendar = secondDatePicker.calendar in - ({ simple = Ui.DateRangePicker.init () + ({ simple = DRP.init () , oneDisabled = - { datePicker1 = { firstDatePicker | disabled = True } - , datePicker2 = secondDatePicker - } + disableWhich First (DRP.init ()) , withPreselectedDates = - let - updatedCalendar1 = - { firstCalendar | value = (Ext.Date.createDate 2018 5 20), date = (Ext.Date.createDate 2018 5 20) } - - updatedCalendar2 = - { secondCalendar | value = (Ext.Date.createDate 2018 5 28), date = (Ext.Date.createDate 2018 5 28) } - in - { datePicker1 = { firstDatePicker | calendar = updatedCalendar1 } - , datePicker2 = { secondDatePicker | calendar = updatedCalendar2 } - } + setValue (setCalendarValue (\c -> { c | value = (Ext.Date.createDate 2018 5 20), date = (Ext.Date.createDate 2018 5 20) })) First (DRP.init ()) + |> setValue (setCalendarValue (\c -> { c | value = (Ext.Date.createDate 2018 5 28), date = (Ext.Date.createDate 2018 5 28) })) Second , readOnly = - let - updatedCalendar1 = - { firstCalendar | readonly = True } - - updatedCalendar2 = - { secondCalendar | readonly = True } - in - { datePicker1 = { firstDatePicker | calendar = updatedCalendar1 } - , datePicker2 = { secondDatePicker | calendar = updatedCalendar2 } - } + setValue (setCalendarValue (\c -> { c | readonly = True, value = (Ext.Date.createDate 2018 5 20), date = (Ext.Date.createDate 2018 5 20) })) First (DRP.init ()) + |> setValue (setCalendarValue (\c -> { c | readonly = True, value = (Ext.Date.createDate 2018 5 28), date = (Ext.Date.createDate 2018 5 28) })) Second } ) @@ -83,28 +63,28 @@ update msg_ model = Simple msg -> let ( updatedModel, cmd ) = - Ui.DateRangePicker.update msg model.simple + DRP.update msg model.simple in ( { model | simple = updatedModel }, Cmd.none ) OneDisabled msg -> let ( updatedModel, cmd ) = - Ui.DateRangePicker.update msg model.oneDisabled + DRP.update msg model.oneDisabled in ( { model | oneDisabled = updatedModel }, Cmd.none ) WithPreselectedDates msg -> let ( updatedModel, cmd ) = - Ui.DateRangePicker.update msg model.withPreselectedDates + DRP.update msg model.withPreselectedDates in ( { model | withPreselectedDates = updatedModel }, Cmd.none ) ReadOnly msg -> let ( updatedModel, cmd ) = - Ui.DateRangePicker.update msg model.readOnly + DRP.update msg model.readOnly in ( { model | readOnly = updatedModel }, Cmd.none ) @@ -120,10 +100,10 @@ uiSnapshot title key ui = view : Model -> Html.Html Msg view model = div [] - [ uiSnapshot "Without Presets" "without-presets" (Html.map Simple (Ui.DateRangePicker.view "en_us" model.simple)) - , uiSnapshot "With One Disabled Date Picker" "one-disabled" (Html.map OneDisabled (Ui.DateRangePicker.view "en_us" model.oneDisabled)) - , uiSnapshot "With Preselected Dates" "preselected" (Html.map WithPreselectedDates (Ui.DateRangePicker.view "en_us" model.withPreselectedDates)) - , uiSnapshot "With A Readonly Date Picker" "readonly" (Html.map ReadOnly (Ui.DateRangePicker.view "en_us" model.readOnly)) + [ uiSnapshot "Without Presets" "without-presets" (Html.map Simple (DRP.view "en_us" model.simple)) + , uiSnapshot "With One Disabled Date Picker" "one-disabled" (Html.map OneDisabled (DRP.view "en_us" model.oneDisabled)) + , uiSnapshot "With Preselected Dates" "preselected" (Html.map WithPreselectedDates (DRP.view "en_us" model.withPreselectedDates)) + , uiSnapshot "With A Readonly Date Picker" "readonly" (Html.map ReadOnly (DRP.view "en_us" model.readOnly)) ] @@ -131,8 +111,22 @@ specs : Node specs = describe "Ui.DateRangePicker" [ it "displays two calendars" - [ assert.elementPresent "ui-picker:nth-child(1)" - , assert.elementPresent "ui-picker:nth-child(2)" + [ assert.elementPresent ".without-presets ui-picker:nth-child(1)" + , assert.elementPresent ".without-presets ui-picker:nth-child(2)" + ] + , it "should be disableable" + [ assert.elementPresent ".one-disabled ui-picker[disabled]" + , assert.elementPresent ".one-disabled ui-picker" + ] + , it + "should allow preselection of dates" + [ assert.containsText { selector = ".preselected ui-picker:nth-child(1)", text = "2018-05-20" } + , assert.containsText { selector = ".preselected ui-picker:nth-child(2)", text = "2018-05-28" } + ] + , it + "should be able to set calendars to be read only" + [ assert.elementPresent ".readonly ui-picker:nth-child(1) ui-calendar[readonly]" + , assert.elementPresent ".readonly ui-picker:nth-child(2) ui-calendar[readonly]" ] ]