Reputation: 169
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
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
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
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
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 :-
Upvotes: 0