3

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?

  • 1
    Maybe looking for something like [this](https://www.npmjs.com/package/ngx-duration-picker)? [Here](https://embed.plnkr.co/1dAIGrGqbcfrNVqs4WwW/) is an example – xDrago Nov 15 '19 at 11:19
  • @xDrago No. The link you provided gives option to select all the values individually. Looking for something that can do the same within a single control/textbox in provided format 000:00:00 or hhh:mm:ss , i.e. hours more than 24 or in 3 digits. – Gurpreet Singh Drish Nov 15 '19 at 11:27
  • see if this will help https://agranom.github.io/ngx-material-timepicker/ – Boobalan Nov 18 '19 at 04:51
  • @Boobalan. First, do add whatever solution you must be suggesting as an answer. Secondly, I have specifically mentioned that the duration must be able to input more than 24 hours. The solution you have suggested just handles the less than 24 hour cases, which could have been other wise default input type time too, but is not what is required in this case. – Gurpreet Singh Drish Nov 18 '19 at 05:06

4 Answers4

5

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/

Chif
  • 830
  • 1
  • 7
  • 20
1

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)
Allabakash
  • 1,969
  • 1
  • 9
  • 15
  • This looks worthwhile, useful but does not solves my use case. This is using multiple controls or textboxes. My requirement is for the same ability in a single control, not a group of them, so the user can just type in or increase the format. – Gurpreet Singh Drish Nov 18 '19 at 07:12
  • 2
    okay, for that you need to create your own formatter and parser functions, You may look into this - https://netbasal.com/angular-formatters-and-parsers-8388e2599a0e. Here is sample demo i have created - https://stackblitz.com/edit/hello-angular-6-yuvffz just to give an idea. you need to implement mouse key up and key down functionalities along with validations. – Allabakash Nov 18 '19 at 09:33
  • The formatters and parsers seems to be useful for my use case,a and the example you have provided seems be workable with some tweaks. More along the lines of my requirement. Could you put together an example using formatters that can handle up-down and post that as answer instead of comment. – Gurpreet Singh Drish Nov 18 '19 at 09:43
  • I have updated the answer, Hope it helps, let me know if have any doubts. – Allabakash Nov 18 '19 at 10:13
0

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/

Ronak07
  • 894
  • 5
  • 26
0

You can try with number as type :

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

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

ysk
  • 159
  • 1
  • 3
  • 12