Giter VIP home page Giter VIP logo

elm-datetime-picker's Introduction

elm-datetime-picker

Single and duration datetime picker components written in Elm 0.19

Install

elm install mercurymedia/elm-datetime-picker

In action

Single Picker

basic

Duration Picker

duration

Usage

This package exposes two modules SingleDatePicker and DurationDatePicker. As their names imply, SingleDatePicker can be used to pick a singular datetime while DurationDatePicker is used to select a datetime range. To keep things simple, the documentation here focuses on the SingleDatePicker but both types have an example app for additional reference.

There are four steps to configure the DatePicker:

  1. Add the picker to the model and initialize it in the model init. One message needs to be defined that expects an internal DatePicker message. This is used to update the selection and view of the picker.
import SingleDatePicker as DatePicker

type alias Model =
    { ...
    , picker : DatePicker.DatePicker Msg
    }

type Msg
    = ...
    | UpdatePicker DatePicker.Msg

init : ( Model, Cmd Msg )
init =
    ( { ...
      , picker = DatePicker.init UpdatePicker
      }
    , Cmd.none
    )
  1. We call the DatePicker.view function, passing it the picker Settings and the DatePicker instance to be operated on. The minimal picker Settings only require a Time.Zone
userDefinedDatePickerSettings : Zone -> DatePicker.Settings
userDefinedDatePickerSettings timeZone =
    DatePicker.defaultSettings timeZone

view : Model -> Html Msg
view model =
    ...
    div []
        [ button [ onClick OpenPicker ] [ text "Open Me!" ]
        , DatePicker.view userDefinedDatePickerSettings model.picker
        ]

While we are on the topic of the DatePicker.view, it is worth noting that this date picker does not include an input or button to trigger the view to open, this is up to the user to define and allows the picker to be flexible across different use cases.

  1. Now it is time for the meat and potatoes: handling the DatePicker updates, including saving the time selected in the picker to the calling module's model.
type alias Model =
    { ...
    , today : Posix
    , zone : Zone
    , pickedTime : Maybe Posix
    , picker : DatePicker.DatePicker
    }

type Msg
    = ...
    | OpenPicker
    | UpdatePicker DatePicker.Msg

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        ...

        OpenPicker ->
            ( { model | picker = DatePicker.openPicker model.zone model.today model.pickedTime model.picker }, Cmd.none )

        UpdatePicker subMsg ->
            let
                ( newPicker, maybeNewTime ) =
                    SingleDatePicker.update (userDefinedDatePickerSettings model.zone model.currentTime) subMsg model.picker
            in
            ( { model | picker = newPicker, pickedTime = Maybe.map (\t -> Just t) maybeNewTime |> Maybe.withDefault model.pickedTime }, Cmd.none )

The user is responsible for defining his or her own Open picker message and placing the relevant event listener where he or she pleases. When handling this message in the update as seen above, we call DatePicker.openPicker which simply returns an updated picker instance to be stored on the model (DatePicker.closePicker is also provided and returns an updated picker instance like openPicker does). DatePicker.openPicker takes a Zone (the time zone in which to display the picker), Posix (the base time), a Maybe Posix (the picked time), and the DatePicker instance we wish to open. The base time is used to inform the picker what day it should center on in the event no datetime has been selected yet. This could be the current date or another date of the implementer's choosing.

Remember that message we passed into the DatePicker settings? Here is where it comes into play. UpdatePicker let's us know that an update of the DatePicker instance's internal state needs to happen. To process the DatePicker.Msg you can pass it to the respective DatePicker.update function along with the Settings and the current DatePicker instance. That will then return us the updated DatePicker instance, to save in the model of the calling module. Additionally, we get a Maybe Posix. In the case of Just a time, we set that on the model as the new pickedTime otherwise we default to the current pickedTime.

Automatically close the picker

In the event you want the picker to close automatically when clicking outside of it, the module uses a subscription to determine when to close (outside of a save). Wire the picker subscription like below.

subscriptions : Model -> Sub Msg
subscriptions model =
    SingleDatePicker.subscriptions model.picker

Open the picker outside the DOM hierarchy

By default, the picker is positioned relative to the nearest positioned ancestor by utilizing the CSS rule position: absolute so you need to place the Datepicker.view as a child of the trigger element within the DOM hierarchy. But sometimes, when rendering the picker somewhere deeply nested in the DOM hierarchy, the popup might interfere with its container elements' CSS rules โ€“ resulting in z-index or overflow problems.

A typical example would be to have the trigger element that opens the picker (e.g. a button) nested into a scroll container with a limited width and hiding its horizontal overflow. When rendering the picker as part of that same container sticking to the trigger element, it might exceed the container's width and gets cut off by the overflow rule. Have a look at the BasicModal example to learn more.

Since the Datepicker.view is independant from any trigger element you can render it anywhere you want in the DOM already โ€“ you just need to manually deal with the picker's position if you still want it to be attached to the trigger element.

Instead of using the default Datepicker.openPicker function, you can use the Datepicker.openPickerOutsideDomHierarchy and Datepicker.updatePickerPosition functions to handle the positioning. You simply need to make sure to pass your trigger element's id and handle updates in case of any events that might change the trigger element's position (e.g. onScroll, onResize, etc.). The trigger's and picker's positions are being calculated based on the viewport. By default it will align to the bottom right of the trigger element (as usual) but it will automatically adjust to the trigger element's top/bottom/left/right based on available space to each side.

Here's an example (also have a look at the BasicModal example):

type Msg
    = ...
    | OpenPicker
    | UpdatePicker DatePicker.Msg
    | OnViewPortChange

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        ...

        OpenPicker ->
            let
                ( newPicker, cmd ) =
                    DatePicker.openPickerOutsideHierarchy 
                        "my-button" 
                        (userDefinedDatePickerSettings model.zone model.currentTime) 
                        model.currentTime 
                        model.pickedTime 
                        model.picker
            in
            ( { model | picker = newPicker }, cmd )

        UpdatePicker subMsg ->
            ...

        OnViewportChange ->
            ( model, DatePicker.updatePickerPosition model.picker )



        
subscriptions : Model -> Sub Msg
subscriptions model =
    Sub.batch
        [ ...
        , Browser.Events.onResize (\_ _ -> OnViewportChange)
        ]
        

Additional Configuration

This is the settings type to be used when configuring the datepicker. More configuration will be available in future releases.

type alias Settings =
    { zone : Zone
    , id : String
    , formattedDay : Weekday -> String
    , formattedMonth : Month -> String
    , isDayDisabled : Zone -> Posix -> Bool
    , focusedDate : Maybe Posix
    , dateStringFn : Zone -> Posix -> String
    , timePickerVisibility : TimePickerVisibility
    , showCalendarWeekNumbers : Bool
    }

Examples

Examples can be found in the examples folder. To build the examples to view in the browser run: cd examples && make && cd .. from the root of the repository.

CSS

The CSS for the date picker is distributed separately and can be found here. The styling is based on a CSS-Variables theme that can be easily adjusted for the most important design tokens.

elm-datetime-picker's People

Contributors

dependabot-preview[bot] avatar jmpressman avatar markus-mind avatar maxhille avatar mschindlermm avatar rametta avatar t8p avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

elm-datetime-picker's Issues

Imports to Utilities don't work

These imports don't work for me:
import DatePicker.Styles
import DatePicker.Utilities
import DatePicker.Icons

This is the error message:

17| import DatePicker.Styles
           ^^^^^^^^^^^^^^^^^
I checked the "dependencies" and "source-directories" listed in your elm.json,
but I cannot find it! Maybe it is a typo for one of these names?

And I have the package installed via elm install.
This is the line in elm.json:
"mercurymedia/elm-datetime-picker": "1.1.0",

Duration date picker calendars cannot be more than 1 month apart

First of all thanks for creating this library, I really like the design and architecture of this.
It's my fav date picker library so far.

Just one thing I noticed that I would like to point out:

I see that the previous / next month buttons change both calendar views (left and right) in the duration date picker.
Same is the case for next / previous year.

In my opinion the two calendars should be independently navigable so that user can select dates between long date ranges.

Example:
Selecting a range date from June 2021 to October 2021 doesn't seem to be possible currently.

Max range and allow future

Is it possible to set a max range for the range picker?

Also, is it possible to not allow future in the range picker?

Next release

When are you planning on releasing the next version of this package? I see #15 removes the need for the confirmation button and that's exactly what we need :)

Thanks!

Time not shown for whole days

When the allowed times are set to the whole day, eg. by using

                    , allowedTimesOfDay =
                        \_ _ -> { startHour = 0, startMinute = 0, endHour = 23, endMinute = 59 }

the time is not shown and therefore not editable, even when using Toggleable or AlwaysVisible.

screenshot-2023-05-10T15:59:14+02:00

I suggest the time should always be shown when Toggleable or AlwaysVisible is configured.

Responsiveness and other thoughts

Hi again, I've been using this component for a while now and it's great.

I have some issues with the responsiveness of the daterangepicker, do you guys have any tips how to solve that? For example how to adjust it for smaller screens like a phone. I assume the best way would be to flip it vertically for smaller devices.

Disable partial day

Right now the datetime picker does not handle disabling a portion of the day. It is either totally unselectable or fully selectable.

Race condition when choosing different date

Bug description

When a date and time have been chosen using the datetime picker, clicking on a different date might trigger a race condition, which causes the datetime picker to utilize an old version of its model. This results in displaying the time of a previous selection, instead of its current selection (i.e. model).

The bug is caused because the update function is called from within the view. This happens for example on this line, but there are multiple instances. This is considered bad practice in Elm. The model should only be updated from the update function, not from within the view itself. This might cause unpredictable behavior as described above.

To summarize what happens in terms of events:

  1. View rendered with PickerModel1
  2. We receive updated PickerModel2
  3. We render our own view with PickerModel2, but before that is completed a new event from the view sends us a PickerModel2B based on PickerModel1

How to reproduce

  1. Select a date and time using the datetime picker (e.g. 2022-05-07 12:30).
  2. Quickly select another date and time using the datetime picker (e.g. 2022-05-08 11:30).
  3. Quickly select another day (e.g. 2022-05-01).
  4. Most likely the time will be 12:30 instead of 11:30.

Please note, that the race condition might not always occur. Though, it is triggered more easily when selecting another date more quickly, shortly after changing the time.

Expected behavior

The datetime picker always utilizes the latest version of its model, and the time does not change when choosing another date.

Suggested change

Rewrite the datetime picker so the model is not updated from within the view, but from the update function instead. This is a major breaking change, hence I'm starting with the creation of this issue, rather than a PR.

If necessary, I could provide a screen capture exposing the bug.

Date range presets

Hi, is there possible to implement date range presets such as these :

image

Set time to 00:00:00 disables time picker part!

When the time is set to 00:00:00 the time part is disabled.
I have zone = Zone.utc and timePickerVisibility = SingleDatePicker.AlwaysVisible SingleDatePicker.defaultTimePickerSettings.
As soon as I set the time to all zeroes, the time picker portion is not shown any more, and there is no way to display it.

Maintainer

Hello,

I would like to become a maintainer for this repo. I can help with:

  • response times, since it's a bit slow
  • ideas
  • implementation
  • reviewing pr's and issues

This is a great calendar library and I would like to see it grow and improve. I can help with that.

Thank you

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.