Gurpreet Singh Drish
Gurpreet Singh Drish

Reputation: 169

How to implement duration picker with HTML5 or/with Angular8, with hours more than 24?

I am trying to implement a control, using either

 <input type="time"/>

or just with

  <input type="text"/>

and implement a duration picker control which can have hours format more than 24, something like 000:00:00 or hhh:mm:ss, and no am/pm option ( The default input type for time has formats in am/pm format, which is not useful in my case). The requirement is to be able to increase decrease the duration using up and down keys much like the default input type time of HTML.

Is there any native HTML, angular, or material component for this? Or is there a way to achieve this using regular expression/patterns or something?

Upvotes: 4

Views: 4181

Answers (4)

Chif
Chif

Reputation: 840

One way I can think of is to write your custom control (as also mentioned by @Allabakash). For Native HTML, The control can be something like this:

window.addEventListener('DOMContentLoaded', (event) => {
            document.querySelectorAll('[my-duration-picker]').forEach(picker => {
                //prevent unsupported keys
                const acceptedKeys = ['Backspace', 'ArrowLeft', 'ArrowRight', 'ArrowDown', 'ArrowUp'];
                const selectFocus = event => {
                    //get cursor position and select nearest block;
                    const cursorPosition = event.target.selectionStart;
                    "000:00:00" //this is the format used to determine cursor location
                    const hourMarker = event.target.value.indexOf(":");
                    const minuteMarker = event.target.value.lastIndexOf(":");
                    if (hourMarker < 0 || minuteMarker < 0) {
                        //something wrong with the format. just return;
                        return;
                    }
                    if (cursorPosition < hourMarker) {
                        event.target.selectionStart = 0; //hours mode
                        event.target.selectionEnd = hourMarker;
                    }
                    if (cursorPosition > hourMarker && cursorPosition < minuteMarker) {
                        event.target.selectionStart = hourMarker + 1; //minutes mode
                        event.target.selectionEnd = minuteMarker;
                    }
                    if (cursorPosition > minuteMarker) {
                        event.target.selectionStart = minuteMarker + 1; //seconds mode
                        event.target.selectionEnd = minuteMarker + 3;
                    }
                }
                const insertFormatted = (inputBox, secondsValue) => {
                    let hours = Math.floor(secondsValue / 3600);
                    secondsValue %= 3600;
                    let minutes = Math.floor(secondsValue / 60);
                    let seconds = secondsValue % 60;
                    minutes = String(minutes).padStart(2, "0");
                    hours = String(hours).padStart(3, "0");
                    seconds = String(seconds).padStart(2, "0");
                    inputBox.value = hours + ":" + minutes + ":" + seconds;
                }
                const increaseValue = inputBox => {
                    const rawValue = inputBox.value;
                    sectioned = rawValue.split(':');
                    let secondsValue = 0
                    if (sectioned.length === 3) {
                        secondsValue = Number(sectioned[2]) + Number(sectioned[1] * 60) + Number(sectioned[0] * 60 * 60);
                    }
                    secondsValue += 1;
                    insertFormatted(inputBox, secondsValue);
                }
                const decreaseValue = inputBox => {
                    const rawValue = inputBox.value;
                    sectioned = rawValue.split(':');
                    let secondsValue = 0
                    if (sectioned.length === 3) {
                        secondsValue = Number(sectioned[2]) + Number(sectioned[1] * 60) + Number(sectioned[0] * 60 * 60);
                    }
                    secondsValue -= 1;
                    if (secondsValue < 0) {
                        secondsValue = 0;
                    }
                    insertFormatted(inputBox, secondsValue);
                }
                const validateInput = event => {
                    sectioned = event.target.value.split(':');
                    if (sectioned.length !== 3) {
                        event.target.value = "000:00:00"; //fallback to default
                        return;
                    }
                    if (isNaN(sectioned[0])) {
                        sectioned[0] = "000";
                    }
                    if (isNaN(sectioned[1]) || sectioned[1] < 0) {
                        sectioned[1] = "00";
                    }
                    if (sectioned[1] > 59 || sectioned[1].length > 2) {
                        sectioned[1] = "59";
                    }
                    if (isNaN(sectioned[2]) || sectioned[2] < 0) {
                        sectioned[2] = "00";
                    }
                    if (sectioned[2] > 59 || sectioned[2].length > 2) {
                        sectioned[2] = "59";
                    }
                    event.target.value = sectioned.join(":");
                }
                const controlsDiv = document.createElement("div");
                const scrollUpBtn = document.createElement("button");
                const scrollDownBtn = document.createElement("button");
                scrollDownBtn.textContent = " - ";
                scrollUpBtn.textContent = " + ";
                scrollUpBtn.addEventListener('click', (e) => {
                    increaseValue(picker);
                });
                scrollDownBtn.addEventListener('click', (e) => {
                    decreaseValue(picker);
                });
                picker.parentNode.insertBefore(scrollDownBtn, picker.nextSibling);
                picker.parentNode.insertBefore(scrollUpBtn, picker.nextSibling);
                picker.value = "000:00:00";
                picker.style.textAlign = "right"; //align the values to the right (optional)
                picker.addEventListener('keydown', event => {
                    //use arrow keys to increase value;
                    if (event.key == 'ArrowDown' || event.key == 'ArrowUp') {
                        if(event.key == 'ArrowDown'){
                        decreaseValue(event.target);
                        }
                        if(event.key == 'ArrowUp'){
                        increaseValue(event.target);
                        }
                        event.preventDefault(); //prevent default
                    }

                    if (isNaN(event.key) && !acceptedKeys.includes(event.key)) {
                        event.preventDefault(); //prevent default
                        return false;
                    }
                });
                picker.addEventListener('focus', selectFocus); //selects a block of hours, minutes etc
                picker.addEventListener('click', selectFocus); //selects a block of hours, minutes etc
                picker.addEventListener('change', validateInput);
                picker.addEventListener('blur', validateInput);
                picker.addEventListener('keyup', validateInput);
            });
        });
<input type="text" my-duration-picker></input>

Tested and working on Google Chrome 78. I will do a Angular version later.

For the Angular version, you can write your own custom Directive and just import it to your app-module-ts declarations. See this example on stackblitz:

App Demo: https://angular-xbkeoc.stackblitz.io

Code: https://stackblitz.com/edit/angular-xbkeoc

UPDATE: I developed and improved this concept over time. You can checkout the picker here 👉 https://nadchif.github.io/html-duration-picker.js/

Upvotes: 5

ysk
ysk

Reputation: 169

You can try with number as type :

<input type="min" min="0" max="60">

demo : https://stackblitz.com/edit/angular-nz9hrn

Upvotes: 0

Allabakash
Allabakash

Reputation: 2027

checkout this solution , https://github.com/FrancescoBorzi/ngx-duration-picker. which provides options you are looking for.

here is the demo - https://embed.plnkr.co/1dAIGrGqbcfrNVqs4WwW/.

Demo shows Y:M:W:D:H:M:S format. you can hide the parameters using flags defined in docs.


Since you are looking for duration picker with single input, creating your own component will be handy. You can consider the concepts formatters and parsers.

checkout this topics which helps you in achieving that.

https://netbasal.com/angular-formatters-and-parsers-8388e2599a0e
https://stackoverflow.com/questions/39457941/parsers-and-formatters-in-angular2

here is the updated sample demo - https://stackblitz.com/edit/hello-angular-6-yuvffz

you can implement the increase/decrease functionalities using keyup/keydown event functions.

handle(event) {
    let value = event.target.value; //hhh:mm:ss
    if(event.key === 'ArrowUp') {
        console.log('increase');
    } else if (event.key === 'ArrowDown') {
        console.log('decrease');
    } else {

       //dont allow user from entering more than two digits in seconds
    }
}

Validations you need to consider ::

  - If user enters wrong input, show error message / block from entering anything other than numbers
  - allowing only unit specific digits - (Ex :: for hr - 3 digits, mm - 2 digits etc as per your requirement)

Upvotes: 1

Ronak07
Ronak07

Reputation: 894

To do something more interesting or make it look like interactive you can use the flipclock.js which is very cool in looking and to work with it is also feasible.

Here is the link :-

http://flipclockjs.com/

Upvotes: 0

Related Questions