The first one:
https://www.cssscript.com/simplest-time-picker-for-text-field/
A single click to select the hour and
a second click to select the minutes.
Quite minimal CSS
div.timepicker {
position: absolute;
z-index: 100;
}
div.timepicker > ul {
list-style-type: none;
margin: 0;
padding: 0;
width: 80px;
border: 1px solid rgba(0, 0, 0, 0.1);
font-size: 13px;
max-height: 290px;
overflow: scroll;
box-shadow: 0 0 3px 0 rgba(0, 0, 0, 0.1);
background: #fff;
overflow-x: hidden;
}
div.timepicker > ul > li {
height: 40px;
cursor: pointer;
text-align: center;
height: 100%;
line-height: 300%;
display: block;
text-decoration: none;
color: #363636;
}
div.timepicker > ul > li.hover {
background: #e8edeb;
}
HTML
<input type="text" name="timepicker" data-toggle="timepicker">
js
var timepicker = {
defaults: {
interval: 15,
defaultHour: null,
defaultMinute: null,
start: 0,
end: 24
},
options: null,
time: {
hour: null,
minute: null
},
active: false,
source: null,
load: function(options)
{
var self = this;
this.options = options;
self.each(function(timepicker)
{
if (self.option('defaultHour') != null)
{
timepicker.value = self.pad(self.option('defaultHour'));
self.time.hour = self.option('defaultHour');
if (self.option('defaultMinute') != null)
{
timepicker.value += ':'+ self.pad(self.option('defaultMinute'));
self.time.minute = self.option('defaultMinute');
}
else
{
timepicker.value += ':00';
self.time.minute = 0;
}
}
timepicker.onclick = function()
{
self.source = this;
if (self.active == true)
return;
self.showHour(this, 1, self.option('start'), self.option('end'), function(list, selected)
{
self.time.hour = selected;
self.source.value = self.time.hour +':00';
self.showMinute(list, self.option('interval'), 60, self.time.hour, function(selected)
{
self.time.minute = selected;
self.source.value = self.time.hour +':'+ self.time.minute;
});
});
};
});
},
option: function(option)
{
if (this.options === null)
return this.defaults[option];
if (this.options[option] === undefined)
return this.defaults[option];
return this.options[option];
},
each: function(callback)
{
var timepickers = document.querySelectorAll('[data-toggle="timepicker"]');
for (var i = 0; i < timepickers.length; i++)
{
callback(timepickers[i]);
}
},
make: function(timepicker)
{
this.active = true;
div = document.createElement('div');
ul = document.createElement('ul');
ul.setAttribute('data-value', 'null');
div.setAttribute('class', 'timepicker');
div.setAttribute('data-name', timepicker.getAttribute('name'));
div.appendChild(ul);
timepicker.parentNode.insertBefore(div, timepicker.nextSibling);
document.addEventListener('mouseup', this.onClickEvent);
document.addEventListener('keydown', this.onKeyEvents);
return ul;
},
remove: function()
{
document.removeEventListener('mouseup', this.onClickEvent);
document.removeEventListener('keydown', this.onKeyEvents);
timepicker = document.querySelector('.timepicker');
timepicker.parentNode.removeChild(timepicker);
this.active = false;
},
clear: function(list)
{
all = list.querySelectorAll('li');
for (var i = 0; i < all.length; i++)
all[i].parentNode.removeChild(all[i]);
},
showHour: function(timepicker, interval, min, max, callback)
{
var height = null;
ul = this.make(timepicker);
for (var i = min; i < max; i = i + interval)
{
li = document.createElement("li");
li.setAttribute('href', '#');
li.setAttribute('data-value', this.pad(i));
li.appendChild(document.createTextNode(this.pad(i) +':00'));
ul.appendChild(li);
if (height == null)
height = li.clientHeight;
li.onmouseover = function()
{
all = document.querySelectorAll('div.timepicker ul li');
for (var j = 0; j < all.length; j++)
all[j].classList.remove('hover');
this.classList.add('hover');
};
li.onmouseout = function()
{
this.classList.remove('hover');
};
li.onclick = function() {
callback(ul, this.getAttribute('data-value'));
};
}
if (this.time.hour == null)
ul.scrollTop = height * this.option('default');
else
ul.scrollTop = this.time.hour * height;
},
showMinute: function(list, interval, max, hour, callback)
{
self = this;
this.clear(list);
for (var i = 0; i < max; i = i + interval)
{
li = document.createElement("li");
li.setAttribute('href', '#');
li.setAttribute('data-value', this.pad(i));
li.appendChild(document.createTextNode(hour +':'+ this.pad(i)));
ul.appendChild(li);
li.onmouseover = function()
{
all = document.querySelectorAll('div.timepicker ul li');
for (var j = 0; j < all.length; j++)
all[j].classList.remove('hover');
this.classList.add('hover');
};
li.onmouseout = function()
{
this.classList.remove('hover');
};
li.onclick = function()
{
callback(this.getAttribute('data-value'));
self.remove();
};
}
},
pad: function(number)
{
var s = String(number);
while (s.length < 2)
{
s = '0' + s;
}
return s;
},
onClickEvent: function()
{
if (event.target.attributes['data-value'] === undefined)
timepicker.remove();
},
onKeyEvents: function()
{
switch(event.keyCode)
{
case 27:
timepicker.remove();
break;
case 40:
item = document.querySelector('div.timepicker ul li.hover');
list = document.querySelectorAll('div.timepicker ul li');
index = Array.prototype.indexOf.call(list, item);
if (index == -1)
{
list[0].classList.add('hover');
break;
}
if (index < list.length - 1)
{
item.classList.remove('hover');
list[index + 1].classList.add('hover');
}
break;
case 38:
item = document.querySelector('div.timepicker ul li.hover');
list = document.querySelectorAll('div.timepicker ul li');
index = Array.prototype.indexOf.call(list, item);
if (index > 0)
{
item.classList.remove('hover');
list[index - 1].classList.add('hover');
}
break;
case 13:
item = document.querySelector('div.timepicker ul li.hover');
item.click();
break;
}
}
};
The second one:
https://www.cssscript.com/demo/minimal-timer-picker-pure-javascript/
CSS
.js-t{
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
position: absolute;
z-index: 9999;
top: 200px;
top: -9999px;
left: 500px;
overflow: hidden;
width: 262px;
padding: 10px;
opacity: 0;
border: 1px solid #ccc;
border-bottom-color: #bbb;
-webkit-transition: opacity 300ms ease-out;
-moz-transition: opacity 300ms ease-out;
-ms-transition: opacity 300ms ease-out;
transition: opacity 300ms ease-out;
background: #fff;
box-shadow: 0 5px 15px -5px rgba(0,0,0,.5);
}
.js-t.visible{
overflow: visible;
opacity: 1;
}
.js-t:after{
clear: both;
}
.js-t .js-t-hour-container,
.js-t .js-t-minute-container{
position: relative;
float: left;
width: 164px;
height: 100%;
}
.js-t .js-t-minute-container{
width: 87px;
margin-left: 10px;
}
.js-t .js-t-hour,
.js-t .js-t-minute{
font-size: 14px;
line-height: 16px;
float: left;
width: 21px;
height: 20px;
padding: 3px;
cursor: pointer;
text-align: center;
}
.js-t .js-t-hour.active,
.js-t .js-t-minute.active,
.js-t .js-t-hour:hover,
.js-t .js-t-minute:hover {
color: #fff;
border-radius: 3px;
background: #ff8000;
}
HTML
<input type="text" name="picker-3" style="position: absolute; left: 800px; top: 500px;"/>
JS
(function (root) {
"use strict";
/**
* Common object params
* @type {Object}
*/
var common = {
publicMethods: ['show', 'hide', 'getTime', 'setTime', 'destroy'],
className: 'JsTimepicker'
},
/**
* Main constructor
* @return {Object} - this handle
*/
Protected = function (handle, options) {
var n;
this.name = 'js-timepicker' + Math.floor(Math.random() * 99999);
this.settings = {
hourLeadingZero: true,
hourStep: 1,
minuteLeadingZero: true,
minuteStep: 5
};
//apply options to settings
if (options) {
for (n in options) {
if (options.hasOwnProperty(n)) {
this.settings[n] = options[n];
}
}
}
this.handle = handle;
this.isVisible = false;
this.hourArray = this.getHoursArray(this.settings.hourStep, this.settings.hourLeadingZero);
this.minuteArray = this.getMinutesArray(this.settings.minuteStep, this.settings.minuteLeadingZero);
this.timepicker = this.makeTimepickerHtml();
this.setEvents();
return this;
};
/**
* Main prototype
* @type {Object}
*/
Protected.prototype = {
event_outClick: function (e) {
var self = this,
parentElem = e.target,
closeList = true;
if (this.isVisible && (parentElem !== self.timepicker) && (parentElem !== this.handle)) {
while (parentElem && parentElem.parentNode) {
if (parentElem === self.timepicker) {
closeList = false;
break;
}
parentElem = parentElem.parentNode;
}
closeList && self.hide();
}
},
event_clickOnPicker: function (e) {
if (e.target && e.target.getAttribute('data-hour')) {
this.timepicker.querySelector('[data-hour].active') && this.timepicker.querySelector('[data-hour].active').classList.remove('active');
e.target.classList.add('active');
this.setHour(e.target.getAttribute('data-hour'));
return;
}
if (e.target && e.target.getAttribute('data-minute')) {
this.timepicker.querySelector('[data-minute].active') && this.timepicker.querySelector('[data-minute].active').classList.remove('active');
e.target.classList.add('active');
this.setMinute(e.target.getAttribute('data-minute'));
return;
}
},
setEvents: function () {
var self = this;
self.show = this.show.bind(this);
self.event_outClick = this.event_outClick.bind(this);
self.event_clickOnPicker = this.event_clickOnPicker.bind(this);
Array.prototype.forEach.call(['click'], function (evt) {
self.handle.addEventListener(evt, self.show);
});
document.addEventListener('click', self.event_outClick);
this.timepicker.addEventListener('click', self.event_clickOnPicker);
},
unsetEvents: function () {
var self = this;
Array.prototype.forEach.call(['click'], function (evt) {
self.handle.removeEventListener(evt, self.show);
});
document.removeEventListener('click', self.event_outClick);
this.timepicker.removeEventListener('click', self.event_clickOnPicker);
},
getHoursArray: function (step) {
var i, h = 24, retData = [];
step = step || 1;
for (i = 0; i < h; i += step) {
retData.push(this.leadingZeroHour(i));
}
return retData;
},
getMinutesArray: function (step) {
var i, h = 60, retData = [];
step = step || 5;
for (i = 0; i < h; i += step) {
retData.push(this.leadingZeroMinute(i));
}
return retData;
},
getPosition: function (element) {
var xPosition = 0,
yPosition = 0;
while(element) {
xPosition += (element.offsetLeft - element.scrollLeft + element.clientLeft);
yPosition += (element.offsetTop - element.scrollTop + element.clientTop);
element = element.offsetParent;
}
return {
x: xPosition,
y: yPosition
};
},
getElementTime: function (asString) {
var timeObj = this.parseTime(this.handle.value);
return (asString ? timeObj.hour + ':' + timeObj.minute : timeObj);
},
parseTime: function (timeString) {
var timeObj = {
hour: timeString.split(':')[0] || '',
minute: timeString.split(':')[1] || ''
}
timeObj.hour = this.leadingZeroHour(timeObj.hour);
timeObj.minute = this.leadingZeroMinute(timeObj.minute);
return timeObj;
},
setHour: function (hour) {
this.handle.value = this.leadingZeroHour(hour) + ':' + this.getElementTime().minute;
},
setMinute: function (minute) {
this.handle.value = this.getElementTime().hour + ':' + this.leadingZeroMinute(minute);
},
leadingZeroMinute: function (num) {
return ((num === '') ? '' : (this.settings.minuteLeadingZero ? ('0' + num.toString()).substr(-2) : num.toString()));
},
leadingZeroHour: function (num) {
return ((num === '') ? '' : (this.settings.hourLeadingZero ? ('0' + num.toString()).substr(-2) : num.toString()));
},
validateTime: function (timeString) {
return new RegExp(/^\d{1,2}:\d{1,2}$/).test(timeString);
},
makeTimepickerHtml: function () {
var container = document.createElement('div'),
hourContainer = document.createElement('div'),
minuteContainer = document.createElement('div'),
elem;
container.setAttribute('class', 'js-t');
container.setAttribute('id', this.name + '-' + this.handle.name);
hourContainer.setAttribute('class', 'js-t-hour-container');
minuteContainer.setAttribute('class', 'js-t-minute-container');
Array.prototype.forEach.call(this.hourArray, function (hour) {
elem = document.createElement('div');
elem.setAttribute('class', 'js-t-hour');
elem.setAttribute('data-hour', hour.toString());
elem.innerHTML = hour;
hourContainer.appendChild(elem);
});
Array.prototype.forEach.call(this.minuteArray, function (minute) {
elem = document.createElement('div');
elem.setAttribute('class', 'js-t-minute');
elem.setAttribute('data-minute', minute.toString());
elem.innerHTML = minute;
minuteContainer.appendChild(elem);
});
container.appendChild(hourContainer);
container.appendChild(minuteContainer);
return container;
},
show: function () {
if (this.isVisible) {
return false;
}
document.body.appendChild(this.timepicker);
Array.prototype.forEach.call(this.timepicker.querySelectorAll('.active'), function (activeElem) {
activeElem.classList.remove('active');
});
var handlePos = this.getPosition(this.handle),
timeObj = this.getElementTime(),
activeHour = this.timepicker.querySelector('[data-hour="' + timeObj.hour + '"]'),
activeMinute = this.timepicker.querySelector('[data-minute="' + timeObj.minute + '"]');
activeHour && activeHour.classList.add('active');
activeMinute && activeMinute.classList.add('active');
this.timepicker.style.left = handlePos.x.toString() + 'px';
this.timepicker.style.top = (handlePos.y + this.handle.offsetHeight).toString() + 'px';
this.timepicker.classList.add('visible');
this.isVisible = true;
return true;
},
hide: function () {
if (!this.isVisible) {
return false;
}
!this.validateTime(this.getElementTime(true)) && (this.handle.value = '');
this.timepicker.classList.remove('visible');
this.timepicker.style.top = '';
this.isVisible = false;
this.timepicker.remove();
return true;
},
destroy: function () {
this.hide();
this.unsetEvents();
},
getTime: function () {
return this.getElementTime(true);
},
setTime: function (hourOrTimeString, minute) {
var timeObj = !minute ? this.parseTime(hourOrTimeString) : this.parseTime(hourOrTimeString.toString() + ':' + minute.toString());
// check to step value
if ((timeObj.minute % this.settings.minuteStep)) {
console.error('Invalid time. Number does not match the minute step');
return;
}
if ((timeObj.hour % this.settings.hourStep)) {
console.error('Invalid time. Number does not match the hour step');
return;
}
this.setHour(timeObj.hour);
this.setMinute(timeObj.minute);
this.handle.value = timeObj.hour + ':' + timeObj.minute;
this.isVisible && this.hide() && this.show();
}
};
root[common.className] = function () {
function construct(constructor, args) {
function Class() {
return constructor.apply(this, args);
}
Class.prototype = constructor.prototype;
return new Class();
}
var original = construct(Protected, arguments),
Publicly = function () {};
Publicly.prototype = {};
Array.prototype.forEach.call(common.publicMethods, function (member) {
Publicly.prototype[member] = function () {
return original[member].apply(original, arguments);
};
});
return new Publicly(arguments);
};
}(this));
The HTML and CSS are easily installed with ui_templates, one in the head and the second as Widget in group. I have found that removing most of the css initially allows one to see the input fields and move forwards from there.
I see that the second time picker has an immediate function declaration, but I cannot figure out how to adapt it.
The big problem I find is that help from outside the NR community has little utility because of the unknown constraints that are required for a widget to function in the dashboard (unknown to me and so impossible to explain to others). Within the NR community I have been directed twice to search for answers elsewhere i.e. CSS, html and general web development.
These two examples cannot be much simpler but my attempts at understanding js keep getting undermined by the horrible inconsistenses that exist within the language. Fourty years of real-time embedded C do not help at all infact it is a hinderance.
It really should not be this hard to build a ui_template time picker when you have all the working parts as a starting point.
Please, does any one know how to integrate these simple js pickers into ui_templates to finish the job ?