|
|
/*!
|
|
|
* Time picker for pickadate.js v3.6.4
|
|
|
* http://amsul.github.io/pickadate.js/time.htm
|
|
|
*/
|
|
|
|
|
|
(function (factory) {
|
|
|
|
|
|
// AMD.
|
|
|
if (typeof define == 'function' && define.amd)
|
|
|
define(['./picker', 'jquery'], factory)
|
|
|
|
|
|
// Node.js/browserify.
|
|
|
else if (typeof exports == 'object')
|
|
|
module.exports = factory(require('./picker.js'), require('jquery'))
|
|
|
|
|
|
// Browser globals.
|
|
|
else factory(Picker, jQuery)
|
|
|
|
|
|
}(function (Picker, $) {
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Globals and constants
|
|
|
*/
|
|
|
var HOURS_IN_DAY = 24,
|
|
|
MINUTES_IN_HOUR = 60,
|
|
|
HOURS_TO_NOON = 12,
|
|
|
MINUTES_IN_DAY = HOURS_IN_DAY * MINUTES_IN_HOUR,
|
|
|
_ = Picker._
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
* The time picker constructor
|
|
|
*/
|
|
|
function TimePicker(picker, settings) {
|
|
|
|
|
|
var clock = this,
|
|
|
elementValue = picker.$node[0].value,
|
|
|
elementDataValue = picker.$node.data('value'),
|
|
|
valueString = elementDataValue || elementValue,
|
|
|
formatString = elementDataValue ? settings.formatSubmit : settings.format
|
|
|
|
|
|
clock.settings = settings
|
|
|
clock.$node = picker.$node
|
|
|
|
|
|
// The queue of methods that will be used to build item objects.
|
|
|
clock.queue = {
|
|
|
interval: 'i',
|
|
|
min: 'measure create',
|
|
|
max: 'measure create',
|
|
|
now: 'now create',
|
|
|
select: 'parse create validate',
|
|
|
highlight: 'parse create validate',
|
|
|
view: 'parse create validate',
|
|
|
disable: 'deactivate',
|
|
|
enable: 'activate'
|
|
|
}
|
|
|
|
|
|
// The component's item object.
|
|
|
clock.item = {}
|
|
|
|
|
|
clock.item.clear = null
|
|
|
clock.item.interval = settings.interval || 30
|
|
|
clock.item.disable = (settings.disable || []).slice(0)
|
|
|
clock.item.enable = -(function (collectionDisabled) {
|
|
|
return collectionDisabled[0] === true ? collectionDisabled.shift() : -1
|
|
|
})(clock.item.disable)
|
|
|
|
|
|
clock.
|
|
|
set('min', settings.min).
|
|
|
set('max', settings.max).
|
|
|
set('now')
|
|
|
|
|
|
// When there’s a value, set the `select`, which in turn
|
|
|
// also sets the `highlight` and `view`.
|
|
|
if (valueString) {
|
|
|
clock.set('select', valueString, {
|
|
|
format: formatString
|
|
|
})
|
|
|
}
|
|
|
|
|
|
// If there’s no value, default to highlighting “todayâ€.
|
|
|
else {
|
|
|
clock.
|
|
|
set('select', null).
|
|
|
set('highlight', clock.item.now)
|
|
|
}
|
|
|
|
|
|
// The keycode to movement mapping.
|
|
|
clock.key = {
|
|
|
40: 1, // Down
|
|
|
38: -1, // Up
|
|
|
39: 1, // Right
|
|
|
37: -1, // Left
|
|
|
go: function (timeChange) {
|
|
|
clock.set(
|
|
|
'highlight',
|
|
|
clock.item.highlight.pick + timeChange * clock.item.interval,
|
|
|
{ interval: timeChange * clock.item.interval }
|
|
|
)
|
|
|
this.render()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
// Bind some picker events.
|
|
|
picker.
|
|
|
on('render', function () {
|
|
|
var $pickerHolder = picker.$root.children(),
|
|
|
$viewset = $pickerHolder.find('.' + settings.klass.viewset),
|
|
|
vendors = function (prop) {
|
|
|
return ['webkit', 'moz', 'ms', 'o', ''].map(function (vendor) {
|
|
|
return (vendor ? '-' + vendor + '-' : '') + prop
|
|
|
})
|
|
|
},
|
|
|
animations = function ($el, state) {
|
|
|
vendors('transform').map(function (prop) {
|
|
|
$el.css(prop, state)
|
|
|
})
|
|
|
vendors('transition').map(function (prop) {
|
|
|
$el.css(prop, state)
|
|
|
})
|
|
|
}
|
|
|
if ($viewset.length) {
|
|
|
animations($pickerHolder, 'none')
|
|
|
$pickerHolder[0].scrollTop = ~~$viewset.position().top - ($viewset[0].clientHeight * 2)
|
|
|
animations($pickerHolder, '')
|
|
|
}
|
|
|
}, 1).
|
|
|
on('open', function () {
|
|
|
picker.$root.find('button').attr('disabled', false)
|
|
|
}, 1).
|
|
|
on('close', function () {
|
|
|
picker.$root.find('button').attr('disabled', true)
|
|
|
}, 1)
|
|
|
|
|
|
} //TimePicker
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Set a timepicker item object.
|
|
|
*/
|
|
|
TimePicker.prototype.set = function (type, value, options) {
|
|
|
|
|
|
var clock = this,
|
|
|
clockItem = clock.item
|
|
|
|
|
|
// If the value is `null` just set it immediately.
|
|
|
if (value === null) {
|
|
|
if (type == 'clear') type = 'select'
|
|
|
clockItem[type] = value
|
|
|
return clock
|
|
|
}
|
|
|
|
|
|
// Otherwise go through the queue of methods, and invoke the functions.
|
|
|
// Update this as the time unit, and set the final value as this item.
|
|
|
// * In the case of `enable`, keep the queue but set `disable` instead.
|
|
|
// And in the case of `flip`, keep the queue but set `enable` instead.
|
|
|
clockItem[(type == 'enable' ? 'disable' : type == 'flip' ? 'enable' : type)] = clock.queue[type].split(' ').map(function (method) {
|
|
|
value = clock[method](type, value, options)
|
|
|
return value
|
|
|
}).pop()
|
|
|
|
|
|
// Check if we need to cascade through more updates.
|
|
|
if (type == 'select') {
|
|
|
clock.set('highlight', clockItem.select, options)
|
|
|
}
|
|
|
else if (type == 'highlight') {
|
|
|
clock.set('view', clockItem.highlight, options)
|
|
|
}
|
|
|
else if (type == 'interval') {
|
|
|
clock.
|
|
|
set('min', clockItem.min, options).
|
|
|
set('max', clockItem.max, options)
|
|
|
}
|
|
|
else if (type.match(/^(flip|min|max|disable|enable)$/)) {
|
|
|
if (clockItem.select && clock.disabled(clockItem.select)) {
|
|
|
clock.set('select', value, options)
|
|
|
}
|
|
|
if (clockItem.highlight && clock.disabled(clockItem.highlight)) {
|
|
|
clock.set('highlight', value, options)
|
|
|
}
|
|
|
if (type == 'min') {
|
|
|
clock.set('max', clockItem.max, options)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return clock
|
|
|
} //TimePicker.prototype.set
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Get a timepicker item object.
|
|
|
*/
|
|
|
TimePicker.prototype.get = function (type) {
|
|
|
return this.item[type]
|
|
|
} //TimePicker.prototype.get
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Create a picker time object.
|
|
|
*/
|
|
|
TimePicker.prototype.create = function (type, value, options) {
|
|
|
|
|
|
var clock = this
|
|
|
|
|
|
// If there’s no value, use the type as the value.
|
|
|
value = value === undefined ? type : value
|
|
|
|
|
|
// If it’s a date object, convert it into an array.
|
|
|
if (_.isDate(value)) {
|
|
|
value = [value.getHours(), value.getMinutes()]
|
|
|
}
|
|
|
|
|
|
// If it’s an object, use the “pick†value.
|
|
|
if ($.isPlainObject(value) && _.isInteger(value.pick)) {
|
|
|
value = value.pick
|
|
|
}
|
|
|
|
|
|
// If it’s an array, convert it into minutes.
|
|
|
else if ($.isArray(value)) {
|
|
|
value = +value[0] * MINUTES_IN_HOUR + (+value[1])
|
|
|
}
|
|
|
|
|
|
// If no valid value is passed, set it to “nowâ€.
|
|
|
else if (!_.isInteger(value)) {
|
|
|
value = clock.now(type, value, options)
|
|
|
}
|
|
|
|
|
|
// If we’re setting the max, make sure it’s greater than the min.
|
|
|
if (type == 'max' && value < clock.item.min.pick) {
|
|
|
value += MINUTES_IN_DAY
|
|
|
}
|
|
|
|
|
|
// If the value doesn’t fall directly on the interval,
|
|
|
// add one interval to indicate it as “passedâ€.
|
|
|
if (type != 'min' && type != 'max' && (value - clock.item.min.pick) % clock.item.interval !== 0) {
|
|
|
value += clock.item.interval
|
|
|
}
|
|
|
|
|
|
// Normalize it into a “reachable†interval.
|
|
|
value = clock.normalize(type, value, options)
|
|
|
|
|
|
// Return the compiled object.
|
|
|
return {
|
|
|
|
|
|
// Divide to get hours from minutes.
|
|
|
hour: ~~(HOURS_IN_DAY + value / MINUTES_IN_HOUR) % HOURS_IN_DAY,
|
|
|
|
|
|
// The remainder is the minutes.
|
|
|
mins: (MINUTES_IN_HOUR + value % MINUTES_IN_HOUR) % MINUTES_IN_HOUR,
|
|
|
|
|
|
// The time in total minutes.
|
|
|
time: (MINUTES_IN_DAY + value) % MINUTES_IN_DAY,
|
|
|
|
|
|
// Reference to the “relative†value to pick.
|
|
|
pick: value % MINUTES_IN_DAY
|
|
|
}
|
|
|
} //TimePicker.prototype.create
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Create a range limit object using an array, date object,
|
|
|
* literal “trueâ€, or integer relative to another time.
|
|
|
*/
|
|
|
TimePicker.prototype.createRange = function (from, to) {
|
|
|
|
|
|
var clock = this,
|
|
|
createTime = function (time) {
|
|
|
if (time === true || $.isArray(time) || _.isDate(time)) {
|
|
|
return clock.create(time)
|
|
|
}
|
|
|
return time
|
|
|
}
|
|
|
|
|
|
// Create objects if possible.
|
|
|
if (!_.isInteger(from)) {
|
|
|
from = createTime(from)
|
|
|
}
|
|
|
if (!_.isInteger(to)) {
|
|
|
to = createTime(to)
|
|
|
}
|
|
|
|
|
|
// Create relative times.
|
|
|
if (_.isInteger(from) && $.isPlainObject(to)) {
|
|
|
from = [to.hour, to.mins + (from * clock.settings.interval)];
|
|
|
}
|
|
|
else if (_.isInteger(to) && $.isPlainObject(from)) {
|
|
|
to = [from.hour, from.mins + (to * clock.settings.interval)];
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
from: createTime(from),
|
|
|
to: createTime(to)
|
|
|
}
|
|
|
} //TimePicker.prototype.createRange
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Check if a time unit falls within a time range object.
|
|
|
*/
|
|
|
TimePicker.prototype.withinRange = function (range, timeUnit) {
|
|
|
range = this.createRange(range.from, range.to)
|
|
|
return timeUnit.pick >= range.from.pick && timeUnit.pick <= range.to.pick
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Check if two time range objects overlap.
|
|
|
*/
|
|
|
TimePicker.prototype.overlapRanges = function (one, two) {
|
|
|
|
|
|
var clock = this
|
|
|
|
|
|
// Convert the ranges into comparable times.
|
|
|
one = clock.createRange(one.from, one.to)
|
|
|
two = clock.createRange(two.from, two.to)
|
|
|
|
|
|
return clock.withinRange(one, two.from) || clock.withinRange(one, two.to) ||
|
|
|
clock.withinRange(two, one.from) || clock.withinRange(two, one.to)
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Get the time relative to now.
|
|
|
*/
|
|
|
TimePicker.prototype.now = function (type, value/*, options*/) {
|
|
|
|
|
|
var interval = this.item.interval,
|
|
|
date = new Date(),
|
|
|
nowMinutes = date.getHours() * MINUTES_IN_HOUR + date.getMinutes(),
|
|
|
isValueInteger = _.isInteger(value),
|
|
|
isBelowInterval
|
|
|
|
|
|
// Make sure “now†falls within the interval range.
|
|
|
nowMinutes -= nowMinutes % interval
|
|
|
|
|
|
// Check if the difference is less than the interval itself.
|
|
|
isBelowInterval = value < 0 && interval * value + nowMinutes <= -interval
|
|
|
|
|
|
// Add an interval because the time has “passedâ€.
|
|
|
nowMinutes += type == 'min' && isBelowInterval ? 0 : interval
|
|
|
|
|
|
// If the value is a number, adjust by that many intervals.
|
|
|
if (isValueInteger) {
|
|
|
nowMinutes += interval * (
|
|
|
isBelowInterval && type != 'max' ?
|
|
|
value + 1 :
|
|
|
value
|
|
|
)
|
|
|
}
|
|
|
|
|
|
// Return the final calculation.
|
|
|
return nowMinutes
|
|
|
} //TimePicker.prototype.now
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Normalize minutes to be “reachable†based on the min and interval.
|
|
|
*/
|
|
|
TimePicker.prototype.normalize = function (type, value/*, options*/) {
|
|
|
|
|
|
var interval = this.item.interval,
|
|
|
minTime = this.item.min && this.item.min.pick || 0
|
|
|
|
|
|
// If setting min time, don’t shift anything.
|
|
|
// Otherwise get the value and min difference and then
|
|
|
// normalize the difference with the interval.
|
|
|
value -= type == 'min' ? 0 : (value - minTime) % interval
|
|
|
|
|
|
// Return the adjusted value.
|
|
|
return value
|
|
|
} //TimePicker.prototype.normalize
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Measure the range of minutes.
|
|
|
*/
|
|
|
TimePicker.prototype.measure = function (type, value, options) {
|
|
|
|
|
|
var clock = this
|
|
|
|
|
|
// If it’s anything false-y, set it to the default.
|
|
|
if (!value) {
|
|
|
value = type == 'min' ? [0, 0] : [HOURS_IN_DAY - 1, MINUTES_IN_HOUR - 1]
|
|
|
}
|
|
|
|
|
|
// If it’s a string, parse it.
|
|
|
if (typeof value == 'string') {
|
|
|
value = clock.parse(type, value)
|
|
|
}
|
|
|
|
|
|
// If it’s a literal true, or an integer, make it relative to now.
|
|
|
else if (value === true || _.isInteger(value)) {
|
|
|
value = clock.now(type, value, options)
|
|
|
}
|
|
|
|
|
|
// If it’s an object already, just normalize it.
|
|
|
else if ($.isPlainObject(value) && _.isInteger(value.pick)) {
|
|
|
value = clock.normalize(type, value.pick, options)
|
|
|
}
|
|
|
|
|
|
return value
|
|
|
} ///TimePicker.prototype.measure
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Validate an object as enabled.
|
|
|
*/
|
|
|
TimePicker.prototype.validate = function (type, timeObject, options) {
|
|
|
|
|
|
var clock = this,
|
|
|
interval = options && options.interval ? options.interval : clock.item.interval
|
|
|
|
|
|
// Check if the object is disabled.
|
|
|
if (clock.disabled(timeObject)) {
|
|
|
|
|
|
// Shift with the interval until we reach an enabled time.
|
|
|
timeObject = clock.shift(timeObject, interval)
|
|
|
}
|
|
|
|
|
|
// Scope the object into range.
|
|
|
timeObject = clock.scope(timeObject)
|
|
|
|
|
|
// Do a second check to see if we landed on a disabled min/max.
|
|
|
// In that case, shift using the opposite interval as before.
|
|
|
if (clock.disabled(timeObject)) {
|
|
|
timeObject = clock.shift(timeObject, interval * -1)
|
|
|
}
|
|
|
|
|
|
// Return the final object.
|
|
|
return timeObject
|
|
|
} //TimePicker.prototype.validate
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Check if an object is disabled.
|
|
|
*/
|
|
|
TimePicker.prototype.disabled = function (timeToVerify) {
|
|
|
|
|
|
var clock = this,
|
|
|
|
|
|
// Filter through the disabled times to check if this is one.
|
|
|
isDisabledMatch = clock.item.disable.filter(function (timeToDisable) {
|
|
|
|
|
|
// If the time is a number, match the hours.
|
|
|
if (_.isInteger(timeToDisable)) {
|
|
|
return timeToVerify.hour == timeToDisable
|
|
|
}
|
|
|
|
|
|
// If it’s an array, create the object and match the times.
|
|
|
if ($.isArray(timeToDisable) || _.isDate(timeToDisable)) {
|
|
|
return timeToVerify.pick == clock.create(timeToDisable).pick
|
|
|
}
|
|
|
|
|
|
// If it’s an object, match a time within the “from†and “to†range.
|
|
|
if ($.isPlainObject(timeToDisable)) {
|
|
|
return clock.withinRange(timeToDisable, timeToVerify)
|
|
|
}
|
|
|
})
|
|
|
|
|
|
// If this time matches a disabled time, confirm it’s not inverted.
|
|
|
isDisabledMatch = isDisabledMatch.length && !isDisabledMatch.filter(function (timeToDisable) {
|
|
|
return $.isArray(timeToDisable) && timeToDisable[2] == 'inverted' ||
|
|
|
$.isPlainObject(timeToDisable) && timeToDisable.inverted
|
|
|
}).length
|
|
|
|
|
|
// If the clock is "enabled" flag is flipped, flip the condition.
|
|
|
return clock.item.enable === -1 ? !isDisabledMatch : isDisabledMatch ||
|
|
|
timeToVerify.pick < clock.item.min.pick ||
|
|
|
timeToVerify.pick > clock.item.max.pick
|
|
|
} //TimePicker.prototype.disabled
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Shift an object by an interval until we reach an enabled object.
|
|
|
*/
|
|
|
TimePicker.prototype.shift = function (timeObject, interval) {
|
|
|
|
|
|
var clock = this,
|
|
|
minLimit = clock.item.min.pick,
|
|
|
maxLimit = clock.item.max.pick/*,
|
|
|
safety = 1000*/
|
|
|
|
|
|
interval = interval || clock.item.interval
|
|
|
|
|
|
// Keep looping as long as the time is disabled.
|
|
|
while ( /*safety &&*/ clock.disabled(timeObject)) {
|
|
|
|
|
|
/*safety -= 1
|
|
|
if ( !safety ) {
|
|
|
throw 'Fell into an infinite loop while shifting to ' + timeObject.hour + ':' + timeObject.mins + '.'
|
|
|
}*/
|
|
|
|
|
|
// Increase/decrease the time by the interval and keep looping.
|
|
|
timeObject = clock.create(timeObject.pick += interval)
|
|
|
|
|
|
// If we've looped beyond the limits, break out of the loop.
|
|
|
if (timeObject.pick <= minLimit || timeObject.pick >= maxLimit) {
|
|
|
break
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Return the final object.
|
|
|
return timeObject
|
|
|
} //TimePicker.prototype.shift
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Scope an object to be within range of min and max.
|
|
|
*/
|
|
|
TimePicker.prototype.scope = function (timeObject) {
|
|
|
var minLimit = this.item.min.pick,
|
|
|
maxLimit = this.item.max.pick
|
|
|
return this.create(timeObject.pick > maxLimit ? maxLimit : timeObject.pick < minLimit ? minLimit : timeObject)
|
|
|
} //TimePicker.prototype.scope
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Parse a string into a usable type.
|
|
|
*/
|
|
|
TimePicker.prototype.parse = function (type, value, options) {
|
|
|
|
|
|
var hour, minutes, isPM, item, parseValue,
|
|
|
clock = this,
|
|
|
parsingObject = {}
|
|
|
|
|
|
// If it’s already parsed, we’re good.
|
|
|
if (!value || typeof value != 'string') {
|
|
|
return value
|
|
|
}
|
|
|
|
|
|
// We need a `.format` to parse the value with.
|
|
|
if (!(options && options.format)) {
|
|
|
options = options || {}
|
|
|
options.format = clock.settings.format
|
|
|
}
|
|
|
|
|
|
// Convert the format into an array and then map through it.
|
|
|
clock.formats.toArray(options.format).map(function (label) {
|
|
|
|
|
|
var
|
|
|
substring,
|
|
|
|
|
|
// Grab the formatting label.
|
|
|
formattingLabel = clock.formats[label],
|
|
|
|
|
|
// The format length is from the formatting label function or the
|
|
|
// label length without the escaping exclamation (!) mark.
|
|
|
formatLength = formattingLabel ?
|
|
|
_.trigger(formattingLabel, clock, [value, parsingObject]) :
|
|
|
label.replace(/^!/, '').length
|
|
|
|
|
|
// If there's a format label, split the value up to the format length.
|
|
|
// Then add it to the parsing object with appropriate label.
|
|
|
if (formattingLabel) {
|
|
|
substring = value.substr(0, formatLength)
|
|
|
parsingObject[label] = substring.match(/^\d+$/) ? +substring : substring
|
|
|
}
|
|
|
|
|
|
// Update the time value as the substring from format length to end.
|
|
|
value = value.substr(formatLength)
|
|
|
})
|
|
|
|
|
|
// Grab the hour and minutes from the parsing object.
|
|
|
for (item in parsingObject) {
|
|
|
parseValue = parsingObject[item]
|
|
|
if (_.isInteger(parseValue)) {
|
|
|
if (item.match(/^(h|hh)$/i)) {
|
|
|
hour = parseValue
|
|
|
if (item == 'h' || item == 'hh') {
|
|
|
hour %= 12
|
|
|
}
|
|
|
}
|
|
|
else if (item == 'i') {
|
|
|
minutes = parseValue
|
|
|
}
|
|
|
}
|
|
|
else if (item.match(/^a$/i) && parseValue.match(/^p/i) && ('h' in parsingObject || 'hh' in parsingObject)) {
|
|
|
isPM = true
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Calculate it in minutes and return.
|
|
|
return (isPM ? hour + 12 : hour) * MINUTES_IN_HOUR + minutes
|
|
|
} //TimePicker.prototype.parse
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Various formats to display the object in.
|
|
|
*/
|
|
|
TimePicker.prototype.formats = {
|
|
|
|
|
|
h: function (string, timeObject) {
|
|
|
|
|
|
// If there's string, then get the digits length.
|
|
|
// Otherwise return the selected hour in "standard" format.
|
|
|
return string ? _.digits(string) : timeObject.hour % HOURS_TO_NOON || HOURS_TO_NOON
|
|
|
},
|
|
|
hh: function (string, timeObject) {
|
|
|
|
|
|
// If there's a string, then the length is always 2.
|
|
|
// Otherwise return the selected hour in "standard" format with a leading zero.
|
|
|
return string ? 2 : _.lead(timeObject.hour % HOURS_TO_NOON || HOURS_TO_NOON)
|
|
|
},
|
|
|
H: function (string, timeObject) {
|
|
|
|
|
|
// If there's string, then get the digits length.
|
|
|
// Otherwise return the selected hour in "military" format as a string.
|
|
|
return string ? _.digits(string) : '' + (timeObject.hour % 24)
|
|
|
},
|
|
|
HH: function (string, timeObject) {
|
|
|
|
|
|
// If there's string, then get the digits length.
|
|
|
// Otherwise return the selected hour in "military" format with a leading zero.
|
|
|
return string ? _.digits(string) : _.lead(timeObject.hour % 24)
|
|
|
},
|
|
|
i: function (string, timeObject) {
|
|
|
|
|
|
// If there's a string, then the length is always 2.
|
|
|
// Otherwise return the selected minutes.
|
|
|
return string ? 2 : _.lead(timeObject.mins)
|
|
|
},
|
|
|
a: function (string, timeObject) {
|
|
|
|
|
|
// If there's a string, then the length is always 4.
|
|
|
// Otherwise check if it's more than "noon" and return either am/pm.
|
|
|
return string ? 4 : MINUTES_IN_DAY / 2 > timeObject.time % MINUTES_IN_DAY ? 'a.m.' : 'p.m.'
|
|
|
},
|
|
|
A: function (string, timeObject) {
|
|
|
|
|
|
// If there's a string, then the length is always 2.
|
|
|
// Otherwise check if it's more than "noon" and return either am/pm.
|
|
|
return string ? 2 : MINUTES_IN_DAY / 2 > timeObject.time % MINUTES_IN_DAY ? 'AM' : 'PM'
|
|
|
},
|
|
|
|
|
|
// Create an array by splitting the formatting string passed.
|
|
|
toArray: function (formatString) { return formatString.split(/(h{1,2}|H{1,2}|i|a|A|!.)/g) },
|
|
|
|
|
|
// Format an object into a string using the formatting options.
|
|
|
toString: function (formatString, itemObject) {
|
|
|
var clock = this
|
|
|
return clock.formats.toArray(formatString).map(function (label) {
|
|
|
return _.trigger(clock.formats[label], clock, [0, itemObject]) || label.replace(/^!/, '')
|
|
|
}).join('')
|
|
|
}
|
|
|
} //TimePicker.prototype.formats
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Check if two time units are the exact.
|
|
|
*/
|
|
|
TimePicker.prototype.isTimeExact = function (one, two) {
|
|
|
|
|
|
var clock = this
|
|
|
|
|
|
// When we’re working with minutes, do a direct comparison.
|
|
|
if (
|
|
|
(_.isInteger(one) && _.isInteger(two)) ||
|
|
|
(typeof one == 'boolean' && typeof two == 'boolean')
|
|
|
) {
|
|
|
return one === two
|
|
|
}
|
|
|
|
|
|
// When we’re working with time representations, compare the “pick†value.
|
|
|
if (
|
|
|
(_.isDate(one) || $.isArray(one)) &&
|
|
|
(_.isDate(two) || $.isArray(two))
|
|
|
) {
|
|
|
return clock.create(one).pick === clock.create(two).pick
|
|
|
}
|
|
|
|
|
|
// When we’re working with range objects, compare the “from†and “toâ€.
|
|
|
if ($.isPlainObject(one) && $.isPlainObject(two)) {
|
|
|
return clock.isTimeExact(one.from, two.from) && clock.isTimeExact(one.to, two.to)
|
|
|
}
|
|
|
|
|
|
return false
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Check if two time units overlap.
|
|
|
*/
|
|
|
TimePicker.prototype.isTimeOverlap = function (one, two) {
|
|
|
|
|
|
var clock = this
|
|
|
|
|
|
// When we’re working with an integer, compare the hours.
|
|
|
if (_.isInteger(one) && (_.isDate(two) || $.isArray(two))) {
|
|
|
return one === clock.create(two).hour
|
|
|
}
|
|
|
if (_.isInteger(two) && (_.isDate(one) || $.isArray(one))) {
|
|
|
return two === clock.create(one).hour
|
|
|
}
|
|
|
|
|
|
// When we’re working with range objects, check if the ranges overlap.
|
|
|
if ($.isPlainObject(one) && $.isPlainObject(two)) {
|
|
|
return clock.overlapRanges(one, two)
|
|
|
}
|
|
|
|
|
|
return false
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Flip the “enabled†state.
|
|
|
*/
|
|
|
TimePicker.prototype.flipEnable = function (val) {
|
|
|
var itemObject = this.item
|
|
|
itemObject.enable = val || (itemObject.enable == -1 ? 1 : -1)
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Mark a collection of times as “disabledâ€.
|
|
|
*/
|
|
|
TimePicker.prototype.deactivate = function (type, timesToDisable) {
|
|
|
|
|
|
var clock = this,
|
|
|
disabledItems = clock.item.disable.slice(0)
|
|
|
|
|
|
|
|
|
// If we’re flipping, that’s all we need to do.
|
|
|
if (timesToDisable == 'flip') {
|
|
|
clock.flipEnable()
|
|
|
}
|
|
|
|
|
|
else if (timesToDisable === false) {
|
|
|
clock.flipEnable(1)
|
|
|
disabledItems = []
|
|
|
}
|
|
|
|
|
|
else if (timesToDisable === true) {
|
|
|
clock.flipEnable(-1)
|
|
|
disabledItems = []
|
|
|
}
|
|
|
|
|
|
// Otherwise go through the times to disable.
|
|
|
else {
|
|
|
|
|
|
timesToDisable.map(function (unitToDisable) {
|
|
|
|
|
|
var matchFound
|
|
|
|
|
|
// When we have disabled items, check for matches.
|
|
|
// If something is matched, immediately break out.
|
|
|
for (var index = 0; index < disabledItems.length; index += 1) {
|
|
|
if (clock.isTimeExact(unitToDisable, disabledItems[index])) {
|
|
|
matchFound = true
|
|
|
break
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// If nothing was found, add the validated unit to the collection.
|
|
|
if (!matchFound) {
|
|
|
if (
|
|
|
_.isInteger(unitToDisable) ||
|
|
|
_.isDate(unitToDisable) ||
|
|
|
$.isArray(unitToDisable) ||
|
|
|
($.isPlainObject(unitToDisable) && unitToDisable.from && unitToDisable.to)
|
|
|
) {
|
|
|
disabledItems.push(unitToDisable)
|
|
|
}
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
|
|
|
// Return the updated collection.
|
|
|
return disabledItems
|
|
|
} //TimePicker.prototype.deactivate
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Mark a collection of times as “enabledâ€.
|
|
|
*/
|
|
|
TimePicker.prototype.activate = function (type, timesToEnable) {
|
|
|
|
|
|
var clock = this,
|
|
|
disabledItems = clock.item.disable,
|
|
|
disabledItemsCount = disabledItems.length
|
|
|
|
|
|
// If we’re flipping, that’s all we need to do.
|
|
|
if (timesToEnable == 'flip') {
|
|
|
clock.flipEnable()
|
|
|
}
|
|
|
|
|
|
else if (timesToEnable === true) {
|
|
|
clock.flipEnable(1)
|
|
|
disabledItems = []
|
|
|
}
|
|
|
|
|
|
else if (timesToEnable === false) {
|
|
|
clock.flipEnable(-1)
|
|
|
disabledItems = []
|
|
|
}
|
|
|
|
|
|
// Otherwise go through the disabled times.
|
|
|
else {
|
|
|
|
|
|
timesToEnable.map(function (unitToEnable) {
|
|
|
|
|
|
var matchFound,
|
|
|
disabledUnit,
|
|
|
index,
|
|
|
isRangeMatched
|
|
|
|
|
|
// Go through the disabled items and try to find a match.
|
|
|
for (index = 0; index < disabledItemsCount; index += 1) {
|
|
|
|
|
|
disabledUnit = disabledItems[index]
|
|
|
|
|
|
// When an exact match is found, remove it from the collection.
|
|
|
if (clock.isTimeExact(disabledUnit, unitToEnable)) {
|
|
|
matchFound = disabledItems[index] = null
|
|
|
isRangeMatched = true
|
|
|
break
|
|
|
}
|
|
|
|
|
|
// When an overlapped match is found, add the “inverted†state to it.
|
|
|
else if (clock.isTimeOverlap(disabledUnit, unitToEnable)) {
|
|
|
if ($.isPlainObject(unitToEnable)) {
|
|
|
unitToEnable.inverted = true
|
|
|
matchFound = unitToEnable
|
|
|
}
|
|
|
else if ($.isArray(unitToEnable)) {
|
|
|
matchFound = unitToEnable
|
|
|
if (!matchFound[2]) matchFound.push('inverted')
|
|
|
}
|
|
|
else if (_.isDate(unitToEnable)) {
|
|
|
matchFound = [unitToEnable.getFullYear(), unitToEnable.getMonth(), unitToEnable.getDate(), 'inverted']
|
|
|
}
|
|
|
break
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// If a match was found, remove a previous duplicate entry.
|
|
|
if (matchFound) for (index = 0; index < disabledItemsCount; index += 1) {
|
|
|
if (clock.isTimeExact(disabledItems[index], unitToEnable)) {
|
|
|
disabledItems[index] = null
|
|
|
break
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// In the event that we’re dealing with an overlap of range times,
|
|
|
// make sure there are no “inverted†times because of it.
|
|
|
if (isRangeMatched) for (index = 0; index < disabledItemsCount; index += 1) {
|
|
|
if (clock.isTimeOverlap(disabledItems[index], unitToEnable)) {
|
|
|
disabledItems[index] = null
|
|
|
break
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// If something is still matched, add it into the collection.
|
|
|
if (matchFound) {
|
|
|
disabledItems.push(matchFound)
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
|
|
|
// Return the updated collection.
|
|
|
return disabledItems.filter(function (val) { return val != null })
|
|
|
} //TimePicker.prototype.activate
|
|
|
|
|
|
|
|
|
/**
|
|
|
* The division to use for the range intervals.
|
|
|
*/
|
|
|
TimePicker.prototype.i = function (type, value/*, options*/) {
|
|
|
return _.isInteger(value) && value > 0 ? value : this.item.interval
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Create a string for the nodes in the picker.
|
|
|
*/
|
|
|
TimePicker.prototype.nodes = function (isOpen) {
|
|
|
|
|
|
var
|
|
|
clock = this,
|
|
|
settings = clock.settings,
|
|
|
selectedObject = clock.item.select,
|
|
|
highlightedObject = clock.item.highlight,
|
|
|
viewsetObject = clock.item.view,
|
|
|
disabledCollection = clock.item.disable
|
|
|
|
|
|
return _.node(
|
|
|
'ul',
|
|
|
_.group({
|
|
|
min: clock.item.min.pick,
|
|
|
max: clock.item.max.pick,
|
|
|
i: clock.item.interval,
|
|
|
node: 'li',
|
|
|
item: function (loopedTime) {
|
|
|
loopedTime = clock.create(loopedTime)
|
|
|
var timeMinutes = loopedTime.pick,
|
|
|
isSelected = selectedObject && selectedObject.pick == timeMinutes,
|
|
|
isHighlighted = highlightedObject && highlightedObject.pick == timeMinutes,
|
|
|
isDisabled = disabledCollection && clock.disabled(loopedTime),
|
|
|
formattedTime = _.trigger(clock.formats.toString, clock, [settings.format, loopedTime])
|
|
|
return [
|
|
|
_.trigger(clock.formats.toString, clock, [_.trigger(settings.formatLabel, clock, [loopedTime]) || settings.format, loopedTime]),
|
|
|
(function (klasses) {
|
|
|
|
|
|
if (isSelected) {
|
|
|
klasses.push(settings.klass.selected)
|
|
|
}
|
|
|
|
|
|
if (isHighlighted) {
|
|
|
klasses.push(settings.klass.highlighted)
|
|
|
}
|
|
|
|
|
|
if (viewsetObject && viewsetObject.pick == timeMinutes) {
|
|
|
klasses.push(settings.klass.viewset)
|
|
|
}
|
|
|
|
|
|
if (isDisabled) {
|
|
|
klasses.push(settings.klass.disabled)
|
|
|
}
|
|
|
|
|
|
return klasses.join(' ')
|
|
|
})([settings.klass.listItem]),
|
|
|
'data-pick=' + loopedTime.pick + ' ' + _.ariaAttr({
|
|
|
role: 'option',
|
|
|
label: formattedTime,
|
|
|
selected: isSelected && clock.$node.val() === formattedTime ? true : null,
|
|
|
activedescendant: isHighlighted ? true : null,
|
|
|
disabled: isDisabled ? true : null
|
|
|
})
|
|
|
]
|
|
|
}
|
|
|
}) +
|
|
|
|
|
|
// * For Firefox forms to submit, make sure to set the button’s `type` attribute as “buttonâ€.
|
|
|
_.node(
|
|
|
'li',
|
|
|
_.node(
|
|
|
'button',
|
|
|
settings.clear,
|
|
|
settings.klass.buttonClear,
|
|
|
'type=button data-clear=1' + (isOpen ? '' : ' disabled') + ' ' +
|
|
|
_.ariaAttr({ controls: clock.$node[0].id })
|
|
|
),
|
|
|
'', _.ariaAttr({ role: 'presentation' })
|
|
|
),
|
|
|
settings.klass.list,
|
|
|
_.ariaAttr({ role: 'listbox', controls: clock.$node[0].id })
|
|
|
)
|
|
|
} //TimePicker.prototype.nodes
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Extend the picker to add the component with the defaults.
|
|
|
*/
|
|
|
TimePicker.defaults = (function (prefix) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
// Clear
|
|
|
clear: 'Clear',
|
|
|
|
|
|
// The format to show on the `input` element
|
|
|
format: 'h:i A',
|
|
|
|
|
|
// The interval between each time
|
|
|
interval: 30,
|
|
|
|
|
|
// Picker close behavior
|
|
|
closeOnSelect: true,
|
|
|
closeOnClear: true,
|
|
|
|
|
|
// Update input value on select/clear
|
|
|
updateInput: true,
|
|
|
|
|
|
// Classes
|
|
|
klass: {
|
|
|
|
|
|
picker: prefix + ' ' + prefix + '--time',
|
|
|
holder: prefix + '__holder',
|
|
|
|
|
|
list: prefix + '__list',
|
|
|
listItem: prefix + '__list-item',
|
|
|
|
|
|
disabled: prefix + '__list-item--disabled',
|
|
|
selected: prefix + '__list-item--selected',
|
|
|
highlighted: prefix + '__list-item--highlighted',
|
|
|
viewset: prefix + '__list-item--viewset',
|
|
|
now: prefix + '__list-item--now',
|
|
|
|
|
|
buttonClear: prefix + '__button--clear'
|
|
|
}
|
|
|
}
|
|
|
})(Picker.klasses().picker)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Extend the picker to add the time picker.
|
|
|
*/
|
|
|
Picker.extend('pickatime', TimePicker)
|
|
|
|
|
|
|
|
|
}));
|
|
|
|
|
|
|