diff --git a/assets/js/pickadate/legacy.js b/assets/js/pickadate/legacy.js index a5c8fa52..6af504e0 100644 --- a/assets/js/pickadate/legacy.js +++ b/assets/js/pickadate/legacy.js @@ -1,10 +1,133 @@ + +/*jshint + asi: true, + unused: true, + boss: true, + loopfunc: true, + eqnull: true + */ + + /*! * Legacy browser support */ -[].map||(Array.prototype.map=function(a,b){for(var c=this,d=c.length,e=new Array(d),f=0;d>f;f++)f in c&&(e[f]=a.call(b,c[f],f,c));return e}),[].filter||(Array.prototype.filter=function(a){if(null==this)throw new TypeError;var b=Object(this),c=b.length>>>0;if("function"!=typeof a)throw new TypeError;for(var d=[],e=arguments[1],f=0;c>f;f++)if(f in b){var g=b[f];a.call(e,g,f,b)&&d.push(g)}return d}),[].indexOf||(Array.prototype.indexOf=function(a){if(null==this)throw new TypeError;var b=Object(this),c=b.length>>>0;if(0===c)return-1;var d=0;if(arguments.length>1&&(d=Number(arguments[1]),d!=d?d=0:0!==d&&1/0!=d&&d!=-1/0&&(d=(d>0||-1)*Math.floor(Math.abs(d)))),d>=c)return-1;for(var e=d>=0?d:Math.max(c-Math.abs(d),0);c>e;e++)if(e in b&&b[e]===a)return e;return-1});/*! + + +// Map array support +if ( ![].map ) { + Array.prototype.map = function ( callback, self ) { + var array = this, len = array.length, newArray = new Array( len ) + for ( var i = 0; i < len; i++ ) { + if ( i in array ) { + newArray[ i ] = callback.call( self, array[ i ], i, array ) + } + } + return newArray + } +} + + +// Filter array support +if ( ![].filter ) { + Array.prototype.filter = function( callback ) { + if ( this == null ) throw new TypeError() + var t = Object( this ), len = t.length >>> 0 + if ( typeof callback != 'function' ) throw new TypeError() + var newArray = [], thisp = arguments[ 1 ] + for ( var i = 0; i < len; i++ ) { + if ( i in t ) { + var val = t[ i ] + if ( callback.call( thisp, val, i, t ) ) newArray.push( val ) + } + } + return newArray + } +} + + +// Index of array support +if ( ![].indexOf ) { + Array.prototype.indexOf = function( searchElement ) { + if ( this == null ) throw new TypeError() + var t = Object( this ), len = t.length >>> 0 + if ( len === 0 ) return -1 + var n = 0 + if ( arguments.length > 1 ) { + n = Number( arguments[ 1 ] ) + if ( n != n ) { + n = 0 + } + else if ( n !== 0 && n != Infinity && n != -Infinity ) { + n = ( n > 0 || -1 ) * Math.floor( Math.abs( n ) ) + } + } + if ( n >= len ) return -1 + var k = n >= 0 ? n : Math.max( len - Math.abs( n ), 0 ) + for ( ; k < len; k++ ) { + if ( k in t && t[ k ] === searchElement ) return k + } + return -1 + } +} + + +/*! * Cross-Browser Split 1.1.1 * Copyright 2007-2012 Steven Levithan * Available under the MIT License * http://blog.stevenlevithan.com/archives/cross-browser-split */ -var nativeSplit=String.prototype.split,compliantExecNpcg=void 0===/()??/.exec("")[1];String.prototype.split=function(a,b){var c=this;if("[object RegExp]"!==Object.prototype.toString.call(a))return nativeSplit.call(c,a,b);var d,e,f,g,h=[],i=(a.ignoreCase?"i":"")+(a.multiline?"m":"")+(a.extended?"x":"")+(a.sticky?"y":""),j=0;for(a=new RegExp(a.source,i+"g"),c+="",compliantExecNpcg||(d=new RegExp("^"+a.source+"$(?!\\s)",i)),b=void 0===b?-1>>>0:b>>>0;(e=a.exec(c))&&(f=e.index+e[0].length,!(f>j&&(h.push(c.slice(j,e.index)),!compliantExecNpcg&&e.length>1&&e[0].replace(d,function(){for(var a=1;a1&&e.index=b)));)a.lastIndex===e.index&&a.lastIndex++;return j===c.length?(g||!a.test(""))&&h.push(""):h.push(c.slice(j)),h.length>b?h.slice(0,b):h}; \ No newline at end of file +var nativeSplit = String.prototype.split, compliantExecNpcg = /()??/.exec('')[1] === undefined +String.prototype.split = function(separator, limit) { + var str = this + if (Object.prototype.toString.call(separator) !== '[object RegExp]') { + return nativeSplit.call(str, separator, limit) + } + var output = [], + flags = (separator.ignoreCase ? 'i' : '') + + (separator.multiline ? 'm' : '') + + (separator.extended ? 'x' : '') + + (separator.sticky ? 'y' : ''), + lastLastIndex = 0, + separator2, match, lastIndex, lastLength + separator = new RegExp(separator.source, flags + 'g') + str += '' + if (!compliantExecNpcg) { + separator2 = new RegExp('^' + separator.source + '$(?!\\s)', flags) + } + limit = limit === undefined ? -1 >>> 0 : limit >>> 0 + while (match = separator.exec(str)) { + lastIndex = match.index + match[0].length + if (lastIndex > lastLastIndex) { + output.push(str.slice(lastLastIndex, match.index)) + if (!compliantExecNpcg && match.length > 1) { + match[0].replace(separator2, function () { + for (var i = 1; i < arguments.length - 2; i++) { + if (arguments[i] === undefined) { + match[i] = undefined + } + } + }) + } + if (match.length > 1 && match.index < str.length) { + Array.prototype.push.apply(output, match.slice(1)) + } + lastLength = match[0].length + lastLastIndex = lastIndex + if (output.length >= limit) { + break + } + } + if (separator.lastIndex === match.index) { + separator.lastIndex++ + } + } + if (lastLastIndex === str.length) { + if (lastLength || !separator.test('')) { + output.push('') + } + } else { + output.push(str.slice(lastLastIndex)) + } + return output.length > limit ? output.slice(0, limit) : output +}; diff --git a/assets/js/pickadate/picker.date.js b/assets/js/pickadate/picker.date.js index d3fd96a6..b34e8739 100644 --- a/assets/js/pickadate/picker.date.js +++ b/assets/js/pickadate/picker.date.js @@ -1,5 +1,1354 @@ /*! - * Date picker for pickadate.js v3.5.0 + * Date picker for pickadate.js v3.5.6 * http://amsul.github.io/pickadate.js/date.htm */ -!function(a){"function"==typeof define&&define.amd?define(["picker","jquery"],a):a(Picker,jQuery)}(function(a,b){function c(a,b){var c=this,d=a.$node[0].value,e=a.$node.data("value"),f=e||d,g=e?b.formatSubmit:b.format,h=function(){return"rtl"===getComputedStyle(a.$root[0]).direction};c.settings=b,c.$node=a.$node,c.queue={min:"measure create",max:"measure create",now:"now create",select:"parse create validate",highlight:"parse navigate create validate",view:"parse create validate viewset",disable:"deactivate",enable:"activate"},c.item={},c.item.clear=null,c.item.disable=(b.disable||[]).slice(0),c.item.enable=-function(a){return a[0]===!0?a.shift():-1}(c.item.disable),c.set("min",b.min).set("max",b.max).set("now"),f?c.set("select",f,{format:g}):c.set("select",null).set("highlight",c.item.now),c.key={40:7,38:-7,39:function(){return h()?-1:1},37:function(){return h()?1:-1},go:function(a){var b=c.item.highlight,d=new Date(b.year,b.month,b.date+a);c.set("highlight",[d.getFullYear(),d.getMonth(),d.getDate()],{interval:a}),this.render()}},a.on("render",function(){a.$root.find("."+b.klass.selectMonth).on("change",function(){var c=this.value;c&&(a.set("highlight",[a.get("view").year,c,a.get("highlight").date]),a.$root.find("."+b.klass.selectMonth).trigger("focus"))}),a.$root.find("."+b.klass.selectYear).on("change",function(){var c=this.value;c&&(a.set("highlight",[c,a.get("view").month,a.get("highlight").date]),a.$root.find("."+b.klass.selectYear).trigger("focus"))})}).on("open",function(){a.$root.find("button, select").attr("disabled",!1)}).on("close",function(){a.$root.find("button, select").attr("disabled",!0)})}var d=7,e=6,f=a._;c.prototype.set=function(a,b,c){var d=this,e=d.item;return null===b?("clear"==a&&(a="select"),e[a]=b,d):(e["enable"==a?"disable":"flip"==a?"enable":a]=d.queue[a].split(" ").map(function(e){return b=d[e](a,b,c)}).pop(),"select"==a?d.set("highlight",e.select,c):"highlight"==a?d.set("view",e.highlight,c):a.match(/^(flip|min|max|disable|enable)$/)&&(e.select&&d.disabled(e.select)&&d.set("select",e.select,c),e.highlight&&d.disabled(e.highlight)&&d.set("highlight",e.highlight,c)),d)},c.prototype.get=function(a){return this.item[a]},c.prototype.create=function(a,c,d){var e,g=this;return c=void 0===c?a:c,c==-1/0||1/0==c?e=c:b.isPlainObject(c)&&f.isInteger(c.pick)?c=c.obj:b.isArray(c)?(c=new Date(c[0],c[1],c[2]),c=f.isDate(c)?c:g.create().obj):c=f.isInteger(c)||f.isDate(c)?g.normalize(new Date(c),d):g.now(a,c,d),{year:e||c.getFullYear(),month:e||c.getMonth(),date:e||c.getDate(),day:e||c.getDay(),obj:e||c,pick:e||c.getTime()}},c.prototype.createRange=function(a,c){var d=this,e=function(a){return a===!0||b.isArray(a)||f.isDate(a)?d.create(a):a};return f.isInteger(a)||(a=e(a)),f.isInteger(c)||(c=e(c)),f.isInteger(a)&&b.isPlainObject(c)?a=[c.year,c.month,c.date+a]:f.isInteger(c)&&b.isPlainObject(a)&&(c=[a.year,a.month,a.date+c]),{from:e(a),to:e(c)}},c.prototype.withinRange=function(a,b){return a=this.createRange(a.from,a.to),b.pick>=a.from.pick&&b.pick<=a.to.pick},c.prototype.overlapRanges=function(a,b){var c=this;return a=c.createRange(a.from,a.to),b=c.createRange(b.from,b.to),c.withinRange(a,b.from)||c.withinRange(a,b.to)||c.withinRange(b,a.from)||c.withinRange(b,a.to)},c.prototype.now=function(a,b,c){return b=new Date,c&&c.rel&&b.setDate(b.getDate()+c.rel),this.normalize(b,c)},c.prototype.navigate=function(a,c,d){var e,f,g,h,i=b.isArray(c),j=b.isPlainObject(c),k=this.item.view;if(i||j){for(j?(f=c.year,g=c.month,h=c.date):(f=+c[0],g=+c[1],h=+c[2]),d&&d.nav&&k&&k.month!==g&&(f=k.year,g=k.month),e=new Date(f,g+(d&&d.nav?d.nav:0),1),f=e.getFullYear(),g=e.getMonth();new Date(f,g,h).getMonth()!==g;)h-=1;c=[f,g,h]}return c},c.prototype.normalize=function(a){return a.setHours(0,0,0,0),a},c.prototype.measure=function(a,b){var c=this;return b?f.isInteger(b)&&(b=c.now(a,b,{rel:b})):b="min"==a?-1/0:1/0,b},c.prototype.viewset=function(a,b){return this.create([b.year,b.month,1])},c.prototype.validate=function(a,c,d){var e,g,h,i,j=this,k=c,l=d&&d.interval?d.interval:1,m=-1===j.item.enable,n=j.item.min,o=j.item.max,p=m&&j.item.disable.filter(function(a){if(b.isArray(a)){var d=j.create(a).pick;dc.pick&&(g=!0)}return f.isInteger(a)}).length;if((!d||!d.nav)&&(!m&&j.disabled(c)||m&&j.disabled(c)&&(p||e||g)||!m&&(c.pick<=n.pick||c.pick>=o.pick)))for(m&&!p&&(!g&&l>0||!e&&0>l)&&(l*=-1);j.disabled(c)&&(Math.abs(l)>1&&(c.monthk.month)&&(c=k,l=l>0?1:-1),c.pick<=n.pick?(h=!0,l=1,c=j.create([n.year,n.month,n.date+(c.pick===n.pick?0:-1)])):c.pick>=o.pick&&(i=!0,l=-1,c=j.create([o.year,o.month,o.date+(c.pick===o.pick?0:1)])),!h||!i);)c=j.create([c.year,c.month,c.date+l]);return c},c.prototype.disabled=function(a){var c=this,d=c.item.disable.filter(function(d){return f.isInteger(d)?a.day===(c.settings.firstDay?d:d-1)%7:b.isArray(d)||f.isDate(d)?a.pick===c.create(d).pick:b.isPlainObject(d)?c.withinRange(d,a):void 0});return d=d.length&&!d.filter(function(a){return b.isArray(a)&&"inverted"==a[3]||b.isPlainObject(a)&&a.inverted}).length,-1===c.item.enable?!d:d||a.pickc.item.max.pick},c.prototype.parse=function(a,b,c){var d=this,e={};return b&&"string"==typeof b?(c&&c.format||(c=c||{},c.format=d.settings.format),d.formats.toArray(c.format).map(function(a){var c=d.formats[a],g=c?f.trigger(c,d,[b,e]):a.replace(/^!/,"").length;c&&(e[a]=b.substr(0,g)),b=b.substr(g)}),[e.yyyy||e.yy,+(e.mm||e.m)-1,e.dd||e.d]):b},c.prototype.formats=function(){function a(a,b,c){var d=a.match(/\w+/)[0];return c.mm||c.m||(c.m=b.indexOf(d)+1),d.length}function b(a){return a.match(/\w+/)[0].length}return{d:function(a,b){return a?f.digits(a):b.date},dd:function(a,b){return a?2:f.lead(b.date)},ddd:function(a,c){return a?b(a):this.settings.weekdaysShort[c.day]},dddd:function(a,c){return a?b(a):this.settings.weekdaysFull[c.day]},m:function(a,b){return a?f.digits(a):b.month+1},mm:function(a,b){return a?2:f.lead(b.month+1)},mmm:function(b,c){var d=this.settings.monthsShort;return b?a(b,d,c):d[c.month]},mmmm:function(b,c){var d=this.settings.monthsFull;return b?a(b,d,c):d[c.month]},yy:function(a,b){return a?2:(""+b.year).slice(2)},yyyy:function(a,b){return a?4:b.year},toArray:function(a){return a.split(/(d{1,4}|m{1,4}|y{4}|yy|!.)/g)},toString:function(a,b){var c=this;return c.formats.toArray(a).map(function(a){return f.trigger(c.formats[a],c,[0,b])||a.replace(/^!/,"")}).join("")}}}(),c.prototype.isDateExact=function(a,c){var d=this;return f.isInteger(a)&&f.isInteger(c)||"boolean"==typeof a&&"boolean"==typeof c?a===c:(f.isDate(a)||b.isArray(a))&&(f.isDate(c)||b.isArray(c))?d.create(a).pick===d.create(c).pick:b.isPlainObject(a)&&b.isPlainObject(c)?d.isDateExact(a.from,c.from)&&d.isDateExact(a.to,c.to):!1},c.prototype.isDateOverlap=function(a,c){var d=this,e=d.settings.firstDay?1:0;return f.isInteger(a)&&(f.isDate(c)||b.isArray(c))?(a=a%7+e,a===d.create(c).day+1):f.isInteger(c)&&(f.isDate(a)||b.isArray(a))?(c=c%7+e,c===d.create(a).day+1):b.isPlainObject(a)&&b.isPlainObject(c)?d.overlapRanges(a,c):!1},c.prototype.flipEnable=function(a){var b=this.item;b.enable=a||(-1==b.enable?1:-1)},c.prototype.deactivate=function(a,c){var d=this,e=d.item.disable.slice(0);return"flip"==c?d.flipEnable():c===!1?(d.flipEnable(1),e=[]):c===!0?(d.flipEnable(-1),e=[]):c.map(function(a){for(var c,g=0;gi;i+=1){if(h=e[i],d.isDateExact(h,a)){c=e[i]=null,j=!0;break}if(d.isDateOverlap(h,a)){b.isPlainObject(a)?(a.inverted=!0,c=a):b.isArray(a)?(c=a,c[3]||c.push("inverted")):f.isDate(a)&&(c=[a.getFullYear(),a.getMonth(),a.getDate(),"inverted"]);break}}if(c)for(i=0;g>i;i+=1)if(d.isDateExact(e[i],a)){e[i]=null;break}if(j)for(i=0;g>i;i+=1)if(d.isDateOverlap(e[i],a)){e[i]=null;break}c&&e.push(c)}),e.filter(function(a){return null!=a})},c.prototype.nodes=function(a){var b=this,c=b.settings,g=b.item,h=g.now,i=g.select,j=g.highlight,k=g.view,l=g.disable,m=g.min,n=g.max,o=function(a,b){return c.firstDay&&(a.push(a.shift()),b.push(b.shift())),f.node("thead",f.node("tr",f.group({min:0,max:d-1,i:1,node:"th",item:function(d){return[a[d],c.klass.weekdays,'scope=col title="'+b[d]+'"']}})))}((c.showWeekdaysFull?c.weekdaysFull:c.weekdaysShort).slice(0),c.weekdaysFull.slice(0)),p=function(a){return f.node("div"," ",c.klass["nav"+(a?"Next":"Prev")]+(a&&k.year>=n.year&&k.month>=n.month||!a&&k.year<=m.year&&k.month<=m.month?" "+c.klass.navDisabled:""),"data-nav="+(a||-1)+" "+f.ariaAttr({role:"button",controls:b.$node[0].id+"_table"})+' title="'+(a?c.labelMonthNext:c.labelMonthPrev)+'"')},q=function(){var d=c.showMonthsShort?c.monthsShort:c.monthsFull;return c.selectMonths?f.node("select",f.group({min:0,max:11,i:1,node:"option",item:function(a){return[d[a],0,"value="+a+(k.month==a?" selected":"")+(k.year==m.year&&an.month?" disabled":"")]}}),c.klass.selectMonth,(a?"":"disabled")+" "+f.ariaAttr({controls:b.$node[0].id+"_table"})+' title="'+c.labelMonthSelect+'"'):f.node("div",d[k.month],c.klass.month)},r=function(){var d=k.year,e=c.selectYears===!0?5:~~(c.selectYears/2);if(e){var g=m.year,h=n.year,i=d-e,j=d+e;if(g>i&&(j+=g-i,i=g),j>h){var l=i-g,o=j-h;i-=l>o?o:l,j=h}return f.node("select",f.group({min:i,max:j,i:1,node:"option",item:function(a){return[a,0,"value="+a+(d==a?" selected":"")]}}),c.klass.selectYear,(a?"":"disabled")+" "+f.ariaAttr({controls:b.$node[0].id+"_table"})+' title="'+c.labelYearSelect+'"')}return f.node("div",d,c.klass.year)};return f.node("div",(c.selectYears?r()+q():q()+r())+p()+p(1),c.klass.header)+f.node("table",o+f.node("tbody",f.group({min:0,max:e-1,i:1,node:"tr",item:function(a){var e=c.firstDay&&0===b.create([k.year,k.month,1]).day?-7:0;return[f.group({min:d*a-k.day+e+1,max:function(){return this.min+d-1},i:1,node:"td",item:function(a){a=b.create([k.year,k.month,a+(c.firstDay?1:0)]);var d=i&&i.pick==a.pick,e=j&&j.pick==a.pick,g=l&&b.disabled(a)||a.pickn.pick;return[f.node("div",a.date,function(b){return b.push(k.month==a.month?c.klass.infocus:c.klass.outfocus),h.pick==a.pick&&b.push(c.klass.now),d&&b.push(c.klass.selected),e&&b.push(c.klass.highlighted),g&&b.push(c.klass.disabled),b.join(" ")}([c.klass.day]),"data-pick="+a.pick+" "+f.ariaAttr({role:"gridcell",selected:d&&b.$node.val()===f.trigger(b.formats.toString,b,[c.format,a])?!0:null,activedescendant:e?!0:null,disabled:g?!0:null})),"",f.ariaAttr({role:"presentation"})]}})]}})),c.klass.table,'id="'+b.$node[0].id+'_table" '+f.ariaAttr({role:"grid",controls:b.$node[0].id,readonly:!0}))+f.node("div",f.node("button",c.today,c.klass.buttonToday,"type=button data-pick="+h.pick+(a?"":" disabled")+" "+f.ariaAttr({controls:b.$node[0].id}))+f.node("button",c.clear,c.klass.buttonClear,"type=button data-clear=1"+(a?"":" disabled")+" "+f.ariaAttr({controls:b.$node[0].id})),c.klass.footer)},c.defaults=function(a){return{labelMonthNext:"Next month",labelMonthPrev:"Previous month",labelMonthSelect:"Select a month",labelYearSelect:"Select a year",monthsFull:["January","February","March","April","May","June","July","August","September","October","November","December"],monthsShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],weekdaysFull:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],weekdaysShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],today:"Today",clear:"Clear",format:"d mmmm, yyyy",klass:{table:a+"table",header:a+"header",navPrev:a+"nav--prev",navNext:a+"nav--next",navDisabled:a+"nav--disabled",month:a+"month",year:a+"year",selectMonth:a+"select--month",selectYear:a+"select--year",weekdays:a+"weekday",day:a+"day",disabled:a+"day--disabled",selected:a+"day--selected",highlighted:a+"day--highlighted",now:a+"day--today",infocus:a+"day--infocus",outfocus:a+"day--outfocus",footer:a+"footer",buttonClear:a+"button--clear",buttonToday:a+"button--today"}}}(a.klasses().picker+"__"),a.extend("pickadate",c)}); \ No newline at end of file + +(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 DAYS_IN_WEEK = 7, + WEEKS_IN_CALENDAR = 6, + _ = Picker._ + + + +/** + * The date picker constructor + */ +function DatePicker( picker, settings ) { + + var calendar = this, + element = picker.$node[ 0 ], + elementValue = element.value, + elementDataValue = picker.$node.data( 'value' ), + valueString = elementDataValue || elementValue, + formatString = elementDataValue ? settings.formatSubmit : settings.format, + isRTL = function() { + + return element.currentStyle ? + + // For IE. + element.currentStyle.direction == 'rtl' : + + // For normal browsers. + getComputedStyle( picker.$root[0] ).direction == 'rtl' + } + + calendar.settings = settings + calendar.$node = picker.$node + + // The queue of methods that will be used to build item objects. + calendar.queue = { + min: 'measure create', + max: 'measure create', + now: 'now create', + select: 'parse create validate', + highlight: 'parse navigate create validate', + view: 'parse create validate viewset', + disable: 'deactivate', + enable: 'activate' + } + + // The component's item object. + calendar.item = {} + + calendar.item.clear = null + calendar.item.disable = ( settings.disable || [] ).slice( 0 ) + calendar.item.enable = -(function( collectionDisabled ) { + return collectionDisabled[ 0 ] === true ? collectionDisabled.shift() : -1 + })( calendar.item.disable ) + + calendar. + 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 ) { + calendar.set( 'select', valueString, { + format: formatString, + defaultValue: true + }) + } + + // If there’s no value, default to highlighting “today”. + else { + calendar. + set( 'select', null ). + set( 'highlight', calendar.item.now ) + } + + + // The keycode to movement mapping. + calendar.key = { + 40: 7, // Down + 38: -7, // Up + 39: function() { return isRTL() ? -1 : 1 }, // Right + 37: function() { return isRTL() ? 1 : -1 }, // Left + go: function( timeChange ) { + var highlightedObject = calendar.item.highlight, + targetDate = new Date( highlightedObject.year, highlightedObject.month, highlightedObject.date + timeChange ) + calendar.set( + 'highlight', + targetDate, + { interval: timeChange } + ) + this.render() + } + } + + + // Bind some picker events. + picker. + on( 'render', function() { + picker.$root.find( '.' + settings.klass.selectMonth ).on( 'change', function() { + var value = this.value + if ( value ) { + picker.set( 'highlight', [ picker.get( 'view' ).year, value, picker.get( 'highlight' ).date ] ) + picker.$root.find( '.' + settings.klass.selectMonth ).trigger( 'focus' ) + } + }) + picker.$root.find( '.' + settings.klass.selectYear ).on( 'change', function() { + var value = this.value + if ( value ) { + picker.set( 'highlight', [ value, picker.get( 'view' ).month, picker.get( 'highlight' ).date ] ) + picker.$root.find( '.' + settings.klass.selectYear ).trigger( 'focus' ) + } + }) + }, 1 ). + on( 'open', function() { + var includeToday = '' + if ( calendar.disabled( calendar.get('now') ) ) { + includeToday = ':not(.' + settings.klass.buttonToday + ')' + } + picker.$root.find( 'button' + includeToday + ', select' ).attr( 'disabled', false ) + }, 1 ). + on( 'close', function() { + picker.$root.find( 'button, select' ).attr( 'disabled', true ) + }, 1 ) + +} //DatePicker + + +/** + * Set a datepicker item object. + */ +DatePicker.prototype.set = function( type, value, options ) { + + var calendar = this, + calendarItem = calendar.item + + // If the value is `null` just set it immediately. + if ( value === null ) { + if ( type == 'clear' ) type = 'select' + calendarItem[ type ] = value + return calendar + } + + // 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. + calendarItem[ ( type == 'enable' ? 'disable' : type == 'flip' ? 'enable' : type ) ] = calendar.queue[ type ].split( ' ' ).map( function( method ) { + value = calendar[ method ]( type, value, options ) + return value + }).pop() + + // Check if we need to cascade through more updates. + if ( type == 'select' ) { + calendar.set( 'highlight', calendarItem.select, options ) + } + else if ( type == 'highlight' ) { + calendar.set( 'view', calendarItem.highlight, options ) + } + else if ( type.match( /^(flip|min|max|disable|enable)$/ ) ) { + if ( calendarItem.select && calendar.disabled( calendarItem.select ) ) { + calendar.set( 'select', calendarItem.select, options ) + } + if ( calendarItem.highlight && calendar.disabled( calendarItem.highlight ) ) { + calendar.set( 'highlight', calendarItem.highlight, options ) + } + } + + return calendar +} //DatePicker.prototype.set + + +/** + * Get a datepicker item object. + */ +DatePicker.prototype.get = function( type ) { + return this.item[ type ] +} //DatePicker.prototype.get + + +/** + * Create a picker date object. + */ +DatePicker.prototype.create = function( type, value, options ) { + + var isInfiniteValue, + calendar = this + + // If there’s no value, use the type as the value. + value = value === undefined ? type : value + + + // If it’s infinity, update the value. + if ( value == -Infinity || value == Infinity ) { + isInfiniteValue = value + } + + // If it’s an object, use the native date object. + else if ( $.isPlainObject( value ) && _.isInteger( value.pick ) ) { + value = value.obj + } + + // If it’s an array, convert it into a date and make sure + // that it’s a valid date – otherwise default to today. + else if ( $.isArray( value ) ) { + value = new Date( value[ 0 ], value[ 1 ], value[ 2 ] ) + value = _.isDate( value ) ? value : calendar.create().obj + } + + // If it’s a number or date object, make a normalized date. + else if ( _.isInteger( value ) || _.isDate( value ) ) { + value = calendar.normalize( new Date( value ), options ) + } + + // If it’s a literal true or any other case, set it to now. + else /*if ( value === true )*/ { + value = calendar.now( type, value, options ) + } + + // Return the compiled object. + return { + year: isInfiniteValue || value.getFullYear(), + month: isInfiniteValue || value.getMonth(), + date: isInfiniteValue || value.getDate(), + day: isInfiniteValue || value.getDay(), + obj: isInfiniteValue || value, + pick: isInfiniteValue || value.getTime() + } +} //DatePicker.prototype.create + + +/** + * Create a range limit object using an array, date object, + * literal “true”, or integer relative to another time. + */ +DatePicker.prototype.createRange = function( from, to ) { + + var calendar = this, + createDate = function( date ) { + if ( date === true || $.isArray( date ) || _.isDate( date ) ) { + return calendar.create( date ) + } + return date + } + + // Create objects if possible. + if ( !_.isInteger( from ) ) { + from = createDate( from ) + } + if ( !_.isInteger( to ) ) { + to = createDate( to ) + } + + // Create relative dates. + if ( _.isInteger( from ) && $.isPlainObject( to ) ) { + from = [ to.year, to.month, to.date + from ]; + } + else if ( _.isInteger( to ) && $.isPlainObject( from ) ) { + to = [ from.year, from.month, from.date + to ]; + } + + return { + from: createDate( from ), + to: createDate( to ) + } +} //DatePicker.prototype.createRange + + +/** + * Check if a date unit falls within a date range object. + */ +DatePicker.prototype.withinRange = function( range, dateUnit ) { + range = this.createRange(range.from, range.to) + return dateUnit.pick >= range.from.pick && dateUnit.pick <= range.to.pick +} + + +/** + * Check if two date range objects overlap. + */ +DatePicker.prototype.overlapRanges = function( one, two ) { + + var calendar = this + + // Convert the ranges into comparable dates. + one = calendar.createRange( one.from, one.to ) + two = calendar.createRange( two.from, two.to ) + + return calendar.withinRange( one, two.from ) || calendar.withinRange( one, two.to ) || + calendar.withinRange( two, one.from ) || calendar.withinRange( two, one.to ) +} + + +/** + * Get the date today. + */ +DatePicker.prototype.now = function( type, value, options ) { + value = new Date() + if ( options && options.rel ) { + value.setDate( value.getDate() + options.rel ) + } + return this.normalize( value, options ) +} + + +/** + * Navigate to next/prev month. + */ +DatePicker.prototype.navigate = function( type, value, options ) { + + var targetDateObject, + targetYear, + targetMonth, + targetDate, + isTargetArray = $.isArray( value ), + isTargetObject = $.isPlainObject( value ), + viewsetObject = this.item.view/*, + safety = 100*/ + + + if ( isTargetArray || isTargetObject ) { + + if ( isTargetObject ) { + targetYear = value.year + targetMonth = value.month + targetDate = value.date + } + else { + targetYear = +value[0] + targetMonth = +value[1] + targetDate = +value[2] + } + + // If we’re navigating months but the view is in a different + // month, navigate to the view’s year and month. + if ( options && options.nav && viewsetObject && viewsetObject.month !== targetMonth ) { + targetYear = viewsetObject.year + targetMonth = viewsetObject.month + } + + // Figure out the expected target year and month. + targetDateObject = new Date( targetYear, targetMonth + ( options && options.nav ? options.nav : 0 ), 1 ) + targetYear = targetDateObject.getFullYear() + targetMonth = targetDateObject.getMonth() + + // If the month we’re going to doesn’t have enough days, + // keep decreasing the date until we reach the month’s last date. + while ( /*safety &&*/ new Date( targetYear, targetMonth, targetDate ).getMonth() !== targetMonth ) { + targetDate -= 1 + /*safety -= 1 + if ( !safety ) { + throw 'Fell into an infinite loop while navigating to ' + new Date( targetYear, targetMonth, targetDate ) + '.' + }*/ + } + + value = [ targetYear, targetMonth, targetDate ] + } + + return value +} //DatePicker.prototype.navigate + + +/** + * Normalize a date by setting the hours to midnight. + */ +DatePicker.prototype.normalize = function( value/*, options*/ ) { + value.setHours( 0, 0, 0, 0 ) + return value +} + + +/** + * Measure the range of dates. + */ +DatePicker.prototype.measure = function( type, value/*, options*/ ) { + + var calendar = this + + // If it’s anything false-y, remove the limits. + if ( !value ) { + value = type == 'min' ? -Infinity : Infinity + } + + // If it’s a string, parse it. + else if ( typeof value == 'string' ) { + value = calendar.parse( type, value ) + } + + // If it's an integer, get a date relative to today. + else if ( _.isInteger( value ) ) { + value = calendar.now( type, value, { rel: value } ) + } + + return value +} ///DatePicker.prototype.measure + + +/** + * Create a viewset object based on navigation. + */ +DatePicker.prototype.viewset = function( type, dateObject/*, options*/ ) { + return this.create([ dateObject.year, dateObject.month, 1 ]) +} + + +/** + * Validate a date as enabled and shift if needed. + */ +DatePicker.prototype.validate = function( type, dateObject, options ) { + + var calendar = this, + + // Keep a reference to the original date. + originalDateObject = dateObject, + + // Make sure we have an interval. + interval = options && options.interval ? options.interval : 1, + + // Check if the calendar enabled dates are inverted. + isFlippedBase = calendar.item.enable === -1, + + // Check if we have any enabled dates after/before now. + hasEnabledBeforeTarget, hasEnabledAfterTarget, + + // The min & max limits. + minLimitObject = calendar.item.min, + maxLimitObject = calendar.item.max, + + // Check if we’ve reached the limit during shifting. + reachedMin, reachedMax, + + // Check if the calendar is inverted and at least one weekday is enabled. + hasEnabledWeekdays = isFlippedBase && calendar.item.disable.filter( function( value ) { + + // If there’s a date, check where it is relative to the target. + if ( $.isArray( value ) ) { + var dateTime = calendar.create( value ).pick + if ( dateTime < dateObject.pick ) hasEnabledBeforeTarget = true + else if ( dateTime > dateObject.pick ) hasEnabledAfterTarget = true + } + + // Return only integers for enabled weekdays. + return _.isInteger( value ) + }).length/*, + + safety = 100*/ + + + + // Cases to validate for: + // [1] Not inverted and date disabled. + // [2] Inverted and some dates enabled. + // [3] Not inverted and out of range. + // + // Cases to **not** validate for: + // • Navigating months. + // • Not inverted and date enabled. + // • Inverted and all dates disabled. + // • ..and anything else. + if ( !options || (!options.nav && !options.defaultValue) ) if ( + /* 1 */ ( !isFlippedBase && calendar.disabled( dateObject ) ) || + /* 2 */ ( isFlippedBase && calendar.disabled( dateObject ) && ( hasEnabledWeekdays || hasEnabledBeforeTarget || hasEnabledAfterTarget ) ) || + /* 3 */ ( !isFlippedBase && (dateObject.pick <= minLimitObject.pick || dateObject.pick >= maxLimitObject.pick) ) + ) { + + + // When inverted, flip the direction if there aren’t any enabled weekdays + // and there are no enabled dates in the direction of the interval. + if ( isFlippedBase && !hasEnabledWeekdays && ( ( !hasEnabledAfterTarget && interval > 0 ) || ( !hasEnabledBeforeTarget && interval < 0 ) ) ) { + interval *= -1 + } + + + // Keep looping until we reach an enabled date. + while ( /*safety &&*/ calendar.disabled( dateObject ) ) { + + /*safety -= 1 + if ( !safety ) { + throw 'Fell into an infinite loop while validating ' + dateObject.obj + '.' + }*/ + + + // If we’ve looped into the next/prev month with a large interval, return to the original date and flatten the interval. + if ( Math.abs( interval ) > 1 && ( dateObject.month < originalDateObject.month || dateObject.month > originalDateObject.month ) ) { + dateObject = originalDateObject + interval = interval > 0 ? 1 : -1 + } + + + // If we’ve reached the min/max limit, reverse the direction, flatten the interval and set it to the limit. + if ( dateObject.pick <= minLimitObject.pick ) { + reachedMin = true + interval = 1 + dateObject = calendar.create([ + minLimitObject.year, + minLimitObject.month, + minLimitObject.date + (dateObject.pick === minLimitObject.pick ? 0 : -1) + ]) + } + else if ( dateObject.pick >= maxLimitObject.pick ) { + reachedMax = true + interval = -1 + dateObject = calendar.create([ + maxLimitObject.year, + maxLimitObject.month, + maxLimitObject.date + (dateObject.pick === maxLimitObject.pick ? 0 : 1) + ]) + } + + + // If we’ve reached both limits, just break out of the loop. + if ( reachedMin && reachedMax ) { + break + } + + + // Finally, create the shifted date using the interval and keep looping. + dateObject = calendar.create([ dateObject.year, dateObject.month, dateObject.date + interval ]) + } + + } //endif + + + // Return the date object settled on. + return dateObject +} //DatePicker.prototype.validate + + +/** + * Check if a date is disabled. + */ +DatePicker.prototype.disabled = function( dateToVerify ) { + + var + calendar = this, + + // Filter through the disabled dates to check if this is one. + isDisabledMatch = calendar.item.disable.filter( function( dateToDisable ) { + + // If the date is a number, match the weekday with 0index and `firstDay` check. + if ( _.isInteger( dateToDisable ) ) { + return dateToVerify.day === ( calendar.settings.firstDay ? dateToDisable : dateToDisable - 1 ) % 7 + } + + // If it’s an array or a native JS date, create and match the exact date. + if ( $.isArray( dateToDisable ) || _.isDate( dateToDisable ) ) { + return dateToVerify.pick === calendar.create( dateToDisable ).pick + } + + // If it’s an object, match a date within the “from” and “to” range. + if ( $.isPlainObject( dateToDisable ) ) { + return calendar.withinRange( dateToDisable, dateToVerify ) + } + }) + + // If this date matches a disabled date, confirm it’s not inverted. + isDisabledMatch = isDisabledMatch.length && !isDisabledMatch.filter(function( dateToDisable ) { + return $.isArray( dateToDisable ) && dateToDisable[3] == 'inverted' || + $.isPlainObject( dateToDisable ) && dateToDisable.inverted + }).length + + // Check the calendar “enabled” flag and respectively flip the + // disabled state. Then also check if it’s beyond the min/max limits. + return calendar.item.enable === -1 ? !isDisabledMatch : isDisabledMatch || + dateToVerify.pick < calendar.item.min.pick || + dateToVerify.pick > calendar.item.max.pick + +} //DatePicker.prototype.disabled + + +/** + * Parse a string into a usable type. + */ +DatePicker.prototype.parse = function( type, value, options ) { + + var calendar = 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 = calendar.settings.format + } + + // Convert the format into an array and then map through it. + calendar.formats.toArray( options.format ).map( function( label ) { + + var + // Grab the formatting label. + formattingLabel = calendar.formats[ label ], + + // The format length is from the formatting label function or the + // label length without the escaping exclamation (!) mark. + formatLength = formattingLabel ? _.trigger( formattingLabel, calendar, [ 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 ) { + parsingObject[ label ] = value.substr( 0, formatLength ) + } + + // Update the value as the substring from format length to end. + value = value.substr( formatLength ) + }) + + // Compensate for month 0index. + return [ + parsingObject.yyyy || parsingObject.yy, + +( parsingObject.mm || parsingObject.m ) - 1, + parsingObject.dd || parsingObject.d + ] +} //DatePicker.prototype.parse + + +/** + * Various formats to display the object in. + */ +DatePicker.prototype.formats = (function() { + + // Return the length of the first word in a collection. + function getWordLengthFromCollection( string, collection, dateObject ) { + + // Grab the first word from the string. + // Regex pattern from http://stackoverflow.com/q/150033 + var word = string.match( /[^\x00-\x7F]+|\w+/ )[ 0 ] + + // If there's no month index, add it to the date object + if ( !dateObject.mm && !dateObject.m ) { + dateObject.m = collection.indexOf( word ) + 1 + } + + // Return the length of the word. + return word.length + } + + // Get the length of the first word in a string. + function getFirstWordLength( string ) { + return string.match( /\w+/ )[ 0 ].length + } + + return { + + d: function( string, dateObject ) { + + // If there's string, then get the digits length. + // Otherwise return the selected date. + return string ? _.digits( string ) : dateObject.date + }, + dd: function( string, dateObject ) { + + // If there's a string, then the length is always 2. + // Otherwise return the selected date with a leading zero. + return string ? 2 : _.lead( dateObject.date ) + }, + ddd: function( string, dateObject ) { + + // If there's a string, then get the length of the first word. + // Otherwise return the short selected weekday. + return string ? getFirstWordLength( string ) : this.settings.weekdaysShort[ dateObject.day ] + }, + dddd: function( string, dateObject ) { + + // If there's a string, then get the length of the first word. + // Otherwise return the full selected weekday. + return string ? getFirstWordLength( string ) : this.settings.weekdaysFull[ dateObject.day ] + }, + m: function( string, dateObject ) { + + // If there's a string, then get the length of the digits + // Otherwise return the selected month with 0index compensation. + return string ? _.digits( string ) : dateObject.month + 1 + }, + mm: function( string, dateObject ) { + + // If there's a string, then the length is always 2. + // Otherwise return the selected month with 0index and leading zero. + return string ? 2 : _.lead( dateObject.month + 1 ) + }, + mmm: function( string, dateObject ) { + + var collection = this.settings.monthsShort + + // If there's a string, get length of the relevant month from the short + // months collection. Otherwise return the selected month from that collection. + return string ? getWordLengthFromCollection( string, collection, dateObject ) : collection[ dateObject.month ] + }, + mmmm: function( string, dateObject ) { + + var collection = this.settings.monthsFull + + // If there's a string, get length of the relevant month from the full + // months collection. Otherwise return the selected month from that collection. + return string ? getWordLengthFromCollection( string, collection, dateObject ) : collection[ dateObject.month ] + }, + yy: function( string, dateObject ) { + + // If there's a string, then the length is always 2. + // Otherwise return the selected year by slicing out the first 2 digits. + return string ? 2 : ( '' + dateObject.year ).slice( 2 ) + }, + yyyy: function( string, dateObject ) { + + // If there's a string, then the length is always 4. + // Otherwise return the selected year. + return string ? 4 : dateObject.year + }, + + // Create an array by splitting the formatting string passed. + toArray: function( formatString ) { return formatString.split( /(d{1,4}|m{1,4}|y{4}|yy|!.)/g ) }, + + // Format an object into a string using the formatting options. + toString: function ( formatString, itemObject ) { + var calendar = this + return calendar.formats.toArray( formatString ).map( function( label ) { + return _.trigger( calendar.formats[ label ], calendar, [ 0, itemObject ] ) || label.replace( /^!/, '' ) + }).join( '' ) + } + } +})() //DatePicker.prototype.formats + + + + +/** + * Check if two date units are the exact. + */ +DatePicker.prototype.isDateExact = function( one, two ) { + + var calendar = this + + // When we’re working with weekdays, do a direct comparison. + if ( + ( _.isInteger( one ) && _.isInteger( two ) ) || + ( typeof one == 'boolean' && typeof two == 'boolean' ) + ) { + return one === two + } + + // When we’re working with date representations, compare the “pick” value. + if ( + ( _.isDate( one ) || $.isArray( one ) ) && + ( _.isDate( two ) || $.isArray( two ) ) + ) { + return calendar.create( one ).pick === calendar.create( two ).pick + } + + // When we’re working with range objects, compare the “from” and “to”. + if ( $.isPlainObject( one ) && $.isPlainObject( two ) ) { + return calendar.isDateExact( one.from, two.from ) && calendar.isDateExact( one.to, two.to ) + } + + return false +} + + +/** + * Check if two date units overlap. + */ +DatePicker.prototype.isDateOverlap = function( one, two ) { + + var calendar = this, + firstDay = calendar.settings.firstDay ? 1 : 0 + + // When we’re working with a weekday index, compare the days. + if ( _.isInteger( one ) && ( _.isDate( two ) || $.isArray( two ) ) ) { + one = one % 7 + firstDay + return one === calendar.create( two ).day + 1 + } + if ( _.isInteger( two ) && ( _.isDate( one ) || $.isArray( one ) ) ) { + two = two % 7 + firstDay + return two === calendar.create( one ).day + 1 + } + + // When we’re working with range objects, check if the ranges overlap. + if ( $.isPlainObject( one ) && $.isPlainObject( two ) ) { + return calendar.overlapRanges( one, two ) + } + + return false +} + + +/** + * Flip the “enabled” state. + */ +DatePicker.prototype.flipEnable = function(val) { + var itemObject = this.item + itemObject.enable = val || (itemObject.enable == -1 ? 1 : -1) +} + + +/** + * Mark a collection of dates as “disabled”. + */ +DatePicker.prototype.deactivate = function( type, datesToDisable ) { + + var calendar = this, + disabledItems = calendar.item.disable.slice(0) + + + // If we’re flipping, that’s all we need to do. + if ( datesToDisable == 'flip' ) { + calendar.flipEnable() + } + + else if ( datesToDisable === false ) { + calendar.flipEnable(1) + disabledItems = [] + } + + else if ( datesToDisable === true ) { + calendar.flipEnable(-1) + disabledItems = [] + } + + // Otherwise go through the dates to disable. + else { + + datesToDisable.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 ( calendar.isDateExact( 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 +} //DatePicker.prototype.deactivate + + +/** + * Mark a collection of dates as “enabled”. + */ +DatePicker.prototype.activate = function( type, datesToEnable ) { + + var calendar = this, + disabledItems = calendar.item.disable, + disabledItemsCount = disabledItems.length + + // If we’re flipping, that’s all we need to do. + if ( datesToEnable == 'flip' ) { + calendar.flipEnable() + } + + else if ( datesToEnable === true ) { + calendar.flipEnable(1) + disabledItems = [] + } + + else if ( datesToEnable === false ) { + calendar.flipEnable(-1) + disabledItems = [] + } + + // Otherwise go through the disabled dates. + else { + + datesToEnable.map(function( unitToEnable ) { + + var matchFound, + disabledUnit, + index, + isExactRange + + // 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 ( calendar.isDateExact( disabledUnit, unitToEnable ) ) { + matchFound = disabledItems[index] = null + isExactRange = true + break + } + + // When an overlapped match is found, add the “inverted” state to it. + else if ( calendar.isDateOverlap( disabledUnit, unitToEnable ) ) { + if ( $.isPlainObject( unitToEnable ) ) { + unitToEnable.inverted = true + matchFound = unitToEnable + } + else if ( $.isArray( unitToEnable ) ) { + matchFound = unitToEnable + if ( !matchFound[3] ) 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 ( calendar.isDateExact( disabledItems[index], unitToEnable ) ) { + disabledItems[index] = null + break + } + } + + // In the event that we’re dealing with an exact range of dates, + // make sure there are no “inverted” dates because of it. + if ( isExactRange ) for ( index = 0; index < disabledItemsCount; index += 1 ) { + if ( calendar.isDateOverlap( 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 }) +} //DatePicker.prototype.activate + + +/** + * Create a string for the nodes in the picker. + */ +DatePicker.prototype.nodes = function( isOpen ) { + + var + calendar = this, + settings = calendar.settings, + calendarItem = calendar.item, + nowObject = calendarItem.now, + selectedObject = calendarItem.select, + highlightedObject = calendarItem.highlight, + viewsetObject = calendarItem.view, + disabledCollection = calendarItem.disable, + minLimitObject = calendarItem.min, + maxLimitObject = calendarItem.max, + + + // Create the calendar table head using a copy of weekday labels collection. + // * We do a copy so we don't mutate the original array. + tableHead = (function( collection, fullCollection ) { + + // If the first day should be Monday, move Sunday to the end. + if ( settings.firstDay ) { + collection.push( collection.shift() ) + fullCollection.push( fullCollection.shift() ) + } + + // Create and return the table head group. + return _.node( + 'thead', + _.node( + 'tr', + _.group({ + min: 0, + max: DAYS_IN_WEEK - 1, + i: 1, + node: 'th', + item: function( counter ) { + return [ + collection[ counter ], + settings.klass.weekdays, + 'scope=col title="' + fullCollection[ counter ] + '"' + ] + } + }) + ) + ) //endreturn + })( ( settings.showWeekdaysFull ? settings.weekdaysFull : settings.weekdaysShort ).slice( 0 ), settings.weekdaysFull.slice( 0 ) ), //tableHead + + + // Create the nav for next/prev month. + createMonthNav = function( next ) { + + // Otherwise, return the created month tag. + return _.node( + 'div', + ' ', + settings.klass[ 'nav' + ( next ? 'Next' : 'Prev' ) ] + ( + + // If the focused month is outside the range, disabled the button. + ( next && viewsetObject.year >= maxLimitObject.year && viewsetObject.month >= maxLimitObject.month ) || + ( !next && viewsetObject.year <= minLimitObject.year && viewsetObject.month <= minLimitObject.month ) ? + ' ' + settings.klass.navDisabled : '' + ), + 'data-nav=' + ( next || -1 ) + ' ' + + _.ariaAttr({ + role: 'button', + controls: calendar.$node[0].id + '_table' + }) + ' ' + + 'title="' + (next ? settings.labelMonthNext : settings.labelMonthPrev ) + '"' + ) //endreturn + }, //createMonthNav + + + // Create the month label. + createMonthLabel = function() { + + var monthsCollection = settings.showMonthsShort ? settings.monthsShort : settings.monthsFull + + // If there are months to select, add a dropdown menu. + if ( settings.selectMonths ) { + + return _.node( 'select', + _.group({ + min: 0, + max: 11, + i: 1, + node: 'option', + item: function( loopedMonth ) { + + return [ + + // The looped month and no classes. + monthsCollection[ loopedMonth ], 0, + + // Set the value and selected index. + 'value=' + loopedMonth + + ( viewsetObject.month == loopedMonth ? ' selected' : '' ) + + ( + ( + ( viewsetObject.year == minLimitObject.year && loopedMonth < minLimitObject.month ) || + ( viewsetObject.year == maxLimitObject.year && loopedMonth > maxLimitObject.month ) + ) ? + ' disabled' : '' + ) + ] + } + }), + settings.klass.selectMonth, + ( isOpen ? '' : 'disabled' ) + ' ' + + _.ariaAttr({ controls: calendar.$node[0].id + '_table' }) + ' ' + + 'title="' + settings.labelMonthSelect + '"' + ) + } + + // If there's a need for a month selector + return _.node( 'div', monthsCollection[ viewsetObject.month ], settings.klass.month ) + }, //createMonthLabel + + + // Create the year label. + createYearLabel = function() { + + var focusedYear = viewsetObject.year, + + // If years selector is set to a literal "true", set it to 5. Otherwise + // divide in half to get half before and half after focused year. + numberYears = settings.selectYears === true ? 5 : ~~( settings.selectYears / 2 ) + + // If there are years to select, add a dropdown menu. + if ( numberYears ) { + + var + minYear = minLimitObject.year, + maxYear = maxLimitObject.year, + lowestYear = focusedYear - numberYears, + highestYear = focusedYear + numberYears + + // If the min year is greater than the lowest year, increase the highest year + // by the difference and set the lowest year to the min year. + if ( minYear > lowestYear ) { + highestYear += minYear - lowestYear + lowestYear = minYear + } + + // If the max year is less than the highest year, decrease the lowest year + // by the lower of the two: available and needed years. Then set the + // highest year to the max year. + if ( maxYear < highestYear ) { + + var availableYears = lowestYear - minYear, + neededYears = highestYear - maxYear + + lowestYear -= availableYears > neededYears ? neededYears : availableYears + highestYear = maxYear + } + + return _.node( 'select', + _.group({ + min: lowestYear, + max: highestYear, + i: 1, + node: 'option', + item: function( loopedYear ) { + return [ + + // The looped year and no classes. + loopedYear, 0, + + // Set the value and selected index. + 'value=' + loopedYear + ( focusedYear == loopedYear ? ' selected' : '' ) + ] + } + }), + settings.klass.selectYear, + ( isOpen ? '' : 'disabled' ) + ' ' + _.ariaAttr({ controls: calendar.$node[0].id + '_table' }) + ' ' + + 'title="' + settings.labelYearSelect + '"' + ) + } + + // Otherwise just return the year focused + return _.node( 'div', focusedYear, settings.klass.year ) + } //createYearLabel + + + // Create and return the entire calendar. + return _.node( + 'div', + ( settings.selectYears ? createYearLabel() + createMonthLabel() : createMonthLabel() + createYearLabel() ) + + createMonthNav() + createMonthNav( 1 ), + settings.klass.header + ) + _.node( + 'table', + tableHead + + _.node( + 'tbody', + _.group({ + min: 0, + max: WEEKS_IN_CALENDAR - 1, + i: 1, + node: 'tr', + item: function( rowCounter ) { + + // If Monday is the first day and the month starts on Sunday, shift the date back a week. + var shiftDateBy = settings.firstDay && calendar.create([ viewsetObject.year, viewsetObject.month, 1 ]).day === 0 ? -7 : 0 + + return [ + _.group({ + min: DAYS_IN_WEEK * rowCounter - viewsetObject.day + shiftDateBy + 1, // Add 1 for weekday 0index + max: function() { + return this.min + DAYS_IN_WEEK - 1 + }, + i: 1, + node: 'td', + item: function( targetDate ) { + + // Convert the time date from a relative date to a target date. + targetDate = calendar.create([ viewsetObject.year, viewsetObject.month, targetDate + ( settings.firstDay ? 1 : 0 ) ]) + + var isSelected = selectedObject && selectedObject.pick == targetDate.pick, + isHighlighted = highlightedObject && highlightedObject.pick == targetDate.pick, + isDisabled = disabledCollection && calendar.disabled( targetDate ) || targetDate.pick < minLimitObject.pick || targetDate.pick > maxLimitObject.pick, + formattedDate = _.trigger( calendar.formats.toString, calendar, [ settings.format, targetDate ] ) + + return [ + _.node( + 'div', + targetDate.date, + (function( klasses ) { + + // Add the `infocus` or `outfocus` classes based on month in view. + klasses.push( viewsetObject.month == targetDate.month ? settings.klass.infocus : settings.klass.outfocus ) + + // Add the `today` class if needed. + if ( nowObject.pick == targetDate.pick ) { + klasses.push( settings.klass.now ) + } + + // Add the `selected` class if something's selected and the time matches. + if ( isSelected ) { + klasses.push( settings.klass.selected ) + } + + // Add the `highlighted` class if something's highlighted and the time matches. + if ( isHighlighted ) { + klasses.push( settings.klass.highlighted ) + } + + // Add the `disabled` class if something's disabled and the object matches. + if ( isDisabled ) { + klasses.push( settings.klass.disabled ) + } + + return klasses.join( ' ' ) + })([ settings.klass.day ]), + 'data-pick=' + targetDate.pick + ' ' + _.ariaAttr({ + role: 'gridcell', + label: formattedDate, + selected: isSelected && calendar.$node.val() === formattedDate ? true : null, + activedescendant: isHighlighted ? true : null, + disabled: isDisabled ? true : null + }) + ), + '', + _.ariaAttr({ role: 'presentation' }) + ] //endreturn + } + }) + ] //endreturn + } + }) + ), + settings.klass.table, + 'id="' + calendar.$node[0].id + '_table' + '" ' + _.ariaAttr({ + role: 'grid', + controls: calendar.$node[0].id, + readonly: true + }) + ) + + + // * For Firefox forms to submit, make sure to set the buttons’ `type` attributes as “button”. + _.node( + 'div', + _.node( 'button', settings.today, settings.klass.buttonToday, + 'type=button data-pick=' + nowObject.pick + + ( isOpen && !calendar.disabled(nowObject) ? '' : ' disabled' ) + ' ' + + _.ariaAttr({ controls: calendar.$node[0].id }) ) + + _.node( 'button', settings.clear, settings.klass.buttonClear, + 'type=button data-clear=1' + + ( isOpen ? '' : ' disabled' ) + ' ' + + _.ariaAttr({ controls: calendar.$node[0].id }) ) + + _.node('button', settings.close, settings.klass.buttonClose, + 'type=button data-close=true ' + + ( isOpen ? '' : ' disabled' ) + ' ' + + _.ariaAttr({ controls: calendar.$node[0].id }) ), + settings.klass.footer + ) //endreturn +} //DatePicker.prototype.nodes + + + + +/** + * The date picker defaults. + */ +DatePicker.defaults = (function( prefix ) { + + return { + + // The title label to use for the month nav buttons + labelMonthNext: 'Next month', + labelMonthPrev: 'Previous month', + + // The title label to use for the dropdown selectors + labelMonthSelect: 'Select a month', + labelYearSelect: 'Select a year', + + // Months and weekdays + monthsFull: [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ], + monthsShort: [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ], + weekdaysFull: [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ], + weekdaysShort: [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ], + + // Today and clear + today: 'Today', + clear: 'Clear', + close: 'Close', + + // Picker close behavior + closeOnSelect: true, + closeOnClear: true, + + // The format to show on the `input` element + format: 'd mmmm, yyyy', + + // Classes + klass: { + + table: prefix + 'table', + + header: prefix + 'header', + + navPrev: prefix + 'nav--prev', + navNext: prefix + 'nav--next', + navDisabled: prefix + 'nav--disabled', + + month: prefix + 'month', + year: prefix + 'year', + + selectMonth: prefix + 'select--month', + selectYear: prefix + 'select--year', + + weekdays: prefix + 'weekday', + + day: prefix + 'day', + disabled: prefix + 'day--disabled', + selected: prefix + 'day--selected', + highlighted: prefix + 'day--highlighted', + now: prefix + 'day--today', + infocus: prefix + 'day--infocus', + outfocus: prefix + 'day--outfocus', + + footer: prefix + 'footer', + + buttonClear: prefix + 'button--clear', + buttonToday: prefix + 'button--today', + buttonClose: prefix + 'button--close' + } + } +})( Picker.klasses().picker + '__' ) + + + + + +/** + * Extend the picker to add the date picker. + */ +Picker.extend( 'pickadate', DatePicker ) + + +})); + + + diff --git a/assets/js/pickadate/picker.js b/assets/js/pickadate/picker.js index f92b6187..27906617 100644 --- a/assets/js/pickadate/picker.js +++ b/assets/js/pickadate/picker.js @@ -1,7 +1,1163 @@ /*! - * pickadate.js v3.5.0, 2014/04/13 + * pickadate.js v3.5.6, 2015/04/20 * By Amsul, http://amsul.ca * Hosted on http://amsul.github.io/pickadate.js * Licensed under MIT */ -!function(a){"function"==typeof define&&define.amd?define("picker",["jquery"],a):this.Picker=a(jQuery)}(function(a){function b(f,g,h,k){function l(){return b._.node("div",b._.node("div",b._.node("div",b._.node("div",w.component.nodes(r.open),t.box),t.wrap),t.frame),t.holder)}function m(){u.data(g,w).addClass(t.input).val(u.data("value")?w.get("select",s.format):f.value).on("focus."+r.id+" click."+r.id,p),s.editable||u.on("keydown."+r.id,function(a){var b=a.keyCode,c=/^(8|46)$/.test(b);return 27==b?(w.close(),!1):void((32==b||c||!r.open&&w.component.key[b])&&(a.preventDefault(),a.stopPropagation(),c?w.clear().close():w.open()))}),e(f,{haspopup:!0,expanded:!1,readonly:!1,owns:f.id+"_root"+(w._hidden?" "+w._hidden.id:"")})}function n(){w.$root.on({focusin:function(a){w.$root.removeClass(t.focused),a.stopPropagation()},"mousedown click":function(b){var c=b.target;c!=w.$root.children()[0]&&(b.stopPropagation(),"mousedown"!=b.type||a(c).is(":input")||"OPTION"==c.nodeName||(b.preventDefault(),f.focus()))}}).on("click","[data-pick], [data-nav], [data-clear]",function(){var c=a(this),d=c.data(),e=c.hasClass(t.navDisabled)||c.hasClass(t.disabled),g=document.activeElement;g=g&&(g.type||g.href)&&g,(e||g&&!a.contains(w.$root[0],g))&&f.focus(),d.nav&&!e?w.set("highlight",w.component.item.highlight,{nav:d.nav}):b._.isInteger(d.pick)&&!e?w.set("select",d.pick).close(!0):d.clear&&w.clear().close(!0)}),e(w.$root[0],"hidden",!0)}function o(){var b,c;s.hiddenName===!0?(b=f.name+"_hidden",c=f.name,f.name=""):(c=["string"==typeof s.hiddenPrefix?s.hiddenPrefix:"","string"==typeof s.hiddenSuffix?s.hiddenSuffix:"_submit"],c=b=c[0]+f.name+c[1]),w._hidden=a('")[0],u.on("change."+r.id,function(){w._hidden.value=f.value?w.get("select",s.formatSubmit):""}).after(w._hidden)}function p(a){a.stopPropagation(),"focus"==a.type&&w.$root.addClass(t.focused),w.open()}if(!f)return b;var q=!1,r={id:f.id||"P"+Math.abs(~~(Math.random()*new Date))},s=h?a.extend(!0,{},h.defaults,k):k||{},t=a.extend({},b.klasses(),s.klass),u=a(f),v=function(){return this.start()},w=v.prototype={constructor:v,$node:u,start:function(){return r&&r.start?w:(r.methods={},r.start=!0,r.open=!1,r.type=f.type,f.autofocus=f==document.activeElement,f.type="text",f.readOnly=!s.editable,f.id=f.id||r.id,w.component=new h(w,s),w.$root=a(b._.node("div",l(),t.picker,'id="'+f.id+'_root"')),n(),s.formatSubmit&&o(),m(),s.container?a(s.container).append(w.$root):u.after(w.$root),w.on({start:w.component.onStart,render:w.component.onRender,stop:w.component.onStop,open:w.component.onOpen,close:w.component.onClose,set:w.component.onSet}).on({start:s.onStart,render:s.onRender,stop:s.onStop,open:s.onOpen,close:s.onClose,set:s.onSet}),q=c(w.$root.children()[0]),f.autofocus&&w.open(),w.trigger("start").trigger("render"))},render:function(a){return a?w.$root.html(l()):w.$root.find("."+t.box).html(w.component.nodes(r.open)),w.trigger("render")},stop:function(){return r.start?(w.close(),w._hidden&&w._hidden.parentNode.removeChild(w._hidden),w.$root.remove(),u.removeClass(t.input).removeData(g),setTimeout(function(){u.off("."+r.id)},0),f.type=r.type,f.readOnly=!1,w.trigger("stop"),r.methods={},r.start=!1,w):w},open:function(c){return r.open?w:(u.addClass(t.active),e(f,"expanded",!0),setTimeout(function(){w.$root.addClass(t.opened),e(w.$root[0],"hidden",!1)},0),c!==!1&&(r.open=!0,q&&j.css("overflow","hidden").css("padding-right","+="+d()),u.trigger("focus"),i.on("click."+r.id+" focusin."+r.id,function(a){var b=a.target;b!=f&&b!=document&&3!=a.which&&w.close(b===w.$root.children()[0])}).on("keydown."+r.id,function(c){var d=c.keyCode,e=w.component.key[d],g=c.target;27==d?w.close(!0):g!=f||!e&&13!=d?a.contains(w.$root[0],g)&&13==d&&(c.preventDefault(),g.click()):(c.preventDefault(),e?b._.trigger(w.component.key.go,w,[b._.trigger(e)]):w.$root.find("."+t.highlighted).hasClass(t.disabled)||w.set("select",w.component.item.highlight).close())})),w.trigger("open"))},close:function(a){return a&&(u.off("focus."+r.id).trigger("focus"),setTimeout(function(){u.on("focus."+r.id,p)},0)),u.removeClass(t.active),e(f,"expanded",!1),setTimeout(function(){w.$root.removeClass(t.opened+" "+t.focused),e(w.$root[0],"hidden",!0)},0),r.open?(r.open=!1,q&&j.css("overflow","").css("padding-right","-="+d()),i.off("."+r.id),w.trigger("close")):w},clear:function(){return w.set("clear")},set:function(b,c,d){var e,f,g=a.isPlainObject(b),h=g?b:{};if(d=g&&a.isPlainObject(c)?c:d||{},b){g||(h[b]=c);for(e in h)f=h[e],e in w.component.item&&(void 0===f&&(f=null),w.component.set(e,f,d)),("select"==e||"clear"==e)&&u.val("clear"==e?"":w.get(e,s.format)).trigger("change");w.render()}return d.muted?w:w.trigger("set",h)},get:function(a,c){if(a=a||"value",null!=r[a])return r[a];if("value"==a)return f.value;if(a in w.component.item){if("string"==typeof c){var d=w.component.get(a);return d?b._.trigger(w.component.formats.toString,w.component,[c,d]):""}return w.component.get(a)}},on:function(b,c){var d,e,f=a.isPlainObject(b),g=f?b:{};if(b){f||(g[b]=c);for(d in g)e=g[d],r.methods[d]=r.methods[d]||[],r.methods[d].push(e)}return w},off:function(){var a,b,c=arguments;for(a=0,namesCount=c.length;namesCount>a;a+=1)b=c[a],b in r.methods&&delete r.methods[b];return w},trigger:function(a,c){var d=r.methods[a];return d&&d.map(function(a){b._.trigger(a,w,[c])}),w}};return new v}function c(a){var b,c="position";return a.currentStyle?b=a.currentStyle[c]:window.getComputedStyle&&(b=getComputedStyle(a)[c]),"fixed"==b}function d(){if(j.height()<=h.height())return 0;var b=a('
').appendTo("body"),c=b[0].offsetWidth;b.css("overflow","scroll");var d=a('
').appendTo(b),e=d[0].offsetWidth;return b.remove(),c-e}function e(b,c,d){if(a.isPlainObject(c))for(var e in c)f(b,e,c[e]);else f(b,c,d)}function f(a,b,c){a.setAttribute(("role"==b?"":"aria-")+b,c)}function g(b,c){a.isPlainObject(b)||(b={attribute:c}),c="";for(var d in b){var e=("role"==d?"":"aria-")+d,f=b[d];c+=null==f?"":e+'="'+b[d]+'"'}return c}var h=a(window),i=a(document),j=a(document.documentElement);return b.klasses=function(a){return a=a||"picker",{picker:a,opened:a+"--opened",focused:a+"--focused",input:a+"__input",active:a+"__input--active",holder:a+"__holder",frame:a+"__frame",wrap:a+"__wrap",box:a+"__box"}},b._={group:function(a){for(var c,d="",e=b._.trigger(a.min,a);e<=b._.trigger(a.max,a,[e]);e+=a.i)c=b._.trigger(a.item,a,[e]),d+=b._.node(a.node,c[0],c[1],c[2]);return d},node:function(b,c,d,e){return c?(c=a.isArray(c)?c.join(""):c,d=d?' class="'+d+'"':"",e=e?" "+e:"","<"+b+d+e+">"+c+""):""},lead:function(a){return(10>a?"0":"")+a},trigger:function(a,b,c){return"function"==typeof a?a.apply(b,c||[]):a},digits:function(a){return/\d/.test(a[1])?2:1},isDate:function(a){return{}.toString.call(a).indexOf("Date")>-1&&this.isInteger(a.getDate())},isInteger:function(a){return{}.toString.call(a).indexOf("Number")>-1&&a%1===0},ariaAttr:g},b.extend=function(c,d){a.fn[c]=function(e,f){var g=this.data(c);return"picker"==e?g:g&&"string"==typeof e?b._.trigger(g[e],g,[f]):this.each(function(){var f=a(this);f.data(c)||new b(this,c,d,e)})},a.fn[c].defaults=d.defaults},b}); \ No newline at end of file + +(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('jquery') ) + + // Browser globals. + else this.Picker = factory( jQuery ) + +}(function( $ ) { + +var $window = $( window ) +var $document = $( document ) +var $html = $( document.documentElement ) +var supportsTransitions = document.documentElement.style.transition != null + + +/** + * The picker constructor that creates a blank picker. + */ +function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) { + + // If there’s no element, return the picker constructor. + if ( !ELEMENT ) return PickerConstructor + + + var + IS_DEFAULT_THEME = false, + + + // The state of the picker. + STATE = { + id: ELEMENT.id || 'P' + Math.abs( ~~(Math.random() * new Date()) ) + }, + + + // Merge the defaults and options passed. + SETTINGS = COMPONENT ? $.extend( true, {}, COMPONENT.defaults, OPTIONS ) : OPTIONS || {}, + + + // Merge the default classes with the settings classes. + CLASSES = $.extend( {}, PickerConstructor.klasses(), SETTINGS.klass ), + + + // The element node wrapper into a jQuery object. + $ELEMENT = $( ELEMENT ), + + + // Pseudo picker constructor. + PickerInstance = function() { + return this.start() + }, + + + // The picker prototype. + P = PickerInstance.prototype = { + + constructor: PickerInstance, + + $node: $ELEMENT, + + + /** + * Initialize everything + */ + start: function() { + + // If it’s already started, do nothing. + if ( STATE && STATE.start ) return P + + + // Update the picker states. + STATE.methods = {} + STATE.start = true + STATE.open = false + STATE.type = ELEMENT.type + + + // Confirm focus state, convert into text input to remove UA stylings, + // and set as readonly to prevent keyboard popup. + ELEMENT.autofocus = ELEMENT == getActiveElement() + ELEMENT.readOnly = !SETTINGS.editable + ELEMENT.id = ELEMENT.id || STATE.id + if ( ELEMENT.type != 'text' ) { + ELEMENT.type = 'text' + } + + + // Create a new picker component with the settings. + P.component = new COMPONENT(P, SETTINGS) + + + // Create the picker root and then prepare it. + P.$root = $( '
' ) + prepareElementRoot() + + + // Create the picker holder and then prepare it. + P.$holder = $( createWrappedComponent() ).appendTo( P.$root ) + prepareElementHolder() + + + // If there’s a format for the hidden input element, create the element. + if ( SETTINGS.formatSubmit ) { + prepareElementHidden() + } + + + // Prepare the input element. + prepareElement() + + + // Insert the hidden input as specified in the settings. + if ( SETTINGS.containerHidden ) $( SETTINGS.containerHidden ).append( P._hidden ) + else $ELEMENT.after( P._hidden ) + + + // Insert the root as specified in the settings. + if ( SETTINGS.container ) $( SETTINGS.container ).append( P.$root ) + else $ELEMENT.after( P.$root ) + + + // Bind the default component and settings events. + P.on({ + start: P.component.onStart, + render: P.component.onRender, + stop: P.component.onStop, + open: P.component.onOpen, + close: P.component.onClose, + set: P.component.onSet + }).on({ + start: SETTINGS.onStart, + render: SETTINGS.onRender, + stop: SETTINGS.onStop, + open: SETTINGS.onOpen, + close: SETTINGS.onClose, + set: SETTINGS.onSet + }) + + + // Once we’re all set, check the theme in use. + IS_DEFAULT_THEME = isUsingDefaultTheme( P.$holder[0] ) + + + // If the element has autofocus, open the picker. + if ( ELEMENT.autofocus ) { + P.open() + } + + + // Trigger queued the “start” and “render” events. + return P.trigger( 'start' ).trigger( 'render' ) + }, //start + + + /** + * Render a new picker + */ + render: function( entireComponent ) { + + // Insert a new component holder in the root or box. + if ( entireComponent ) { + P.$holder = $( createWrappedComponent() ) + prepareElementHolder() + P.$root.html( P.$holder ) + } + else P.$root.find( '.' + CLASSES.box ).html( P.component.nodes( STATE.open ) ) + + // Trigger the queued “render” events. + return P.trigger( 'render' ) + }, //render + + + /** + * Destroy everything + */ + stop: function() { + + // If it’s already stopped, do nothing. + if ( !STATE.start ) return P + + // Then close the picker. + P.close() + + // Remove the hidden field. + if ( P._hidden ) { + P._hidden.parentNode.removeChild( P._hidden ) + } + + // Remove the root. + P.$root.remove() + + // Remove the input class, remove the stored data, and unbind + // the events (after a tick for IE - see `P.close`). + $ELEMENT.removeClass( CLASSES.input ).removeData( NAME ) + setTimeout( function() { + $ELEMENT.off( '.' + STATE.id ) + }, 0) + + // Restore the element state + ELEMENT.type = STATE.type + ELEMENT.readOnly = false + + // Trigger the queued “stop” events. + P.trigger( 'stop' ) + + // Reset the picker states. + STATE.methods = {} + STATE.start = false + + return P + }, //stop + + + /** + * Open up the picker + */ + open: function( dontGiveFocus ) { + + // If it’s already open, do nothing. + if ( STATE.open ) return P + + // Add the “active” class. + $ELEMENT.addClass( CLASSES.active ) + aria( ELEMENT, 'expanded', true ) + + // * A Firefox bug, when `html` has `overflow:hidden`, results in + // killing transitions :(. So add the “opened” state on the next tick. + // Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=625289 + setTimeout( function() { + + // Add the “opened” class to the picker root. + P.$root.addClass( CLASSES.opened ) + aria( P.$root[0], 'hidden', false ) + + }, 0 ) + + // If we have to give focus, bind the element and doc events. + if ( dontGiveFocus !== false ) { + + // Set it as open. + STATE.open = true + + // Prevent the page from scrolling. + if ( IS_DEFAULT_THEME ) { + $html. + css( 'overflow', 'hidden' ). + css( 'padding-right', '+=' + getScrollbarWidth() ) + } + + // Pass focus to the root element’s jQuery object. + focusPickerOnceOpened() + + // Bind the document events. + $document.on( 'click.' + STATE.id + ' focusin.' + STATE.id, function( event ) { + + var target = event.target + + // If the target of the event is not the element, close the picker picker. + // * Don’t worry about clicks or focusins on the root because those don’t bubble up. + // Also, for Firefox, a click on an `option` element bubbles up directly + // to the doc. So make sure the target wasn't the doc. + // * In Firefox stopPropagation() doesn’t prevent right-click events from bubbling, + // which causes the picker to unexpectedly close when right-clicking it. So make + // sure the event wasn’t a right-click. + if ( target != ELEMENT && target != document && event.which != 3 ) { + + // If the target was the holder that covers the screen, + // keep the element focused to maintain tabindex. + P.close( target === P.$holder[0] ) + } + + }).on( 'keydown.' + STATE.id, function( event ) { + + var + // Get the keycode. + keycode = event.keyCode, + + // Translate that to a selection change. + keycodeToMove = P.component.key[ keycode ], + + // Grab the target. + target = event.target + + + // On escape, close the picker and give focus. + if ( keycode == 27 ) { + P.close( true ) + } + + + // Check if there is a key movement or “enter” keypress on the element. + else if ( target == P.$holder[0] && ( keycodeToMove || keycode == 13 ) ) { + + // Prevent the default action to stop page movement. + event.preventDefault() + + // Trigger the key movement action. + if ( keycodeToMove ) { + PickerConstructor._.trigger( P.component.key.go, P, [ PickerConstructor._.trigger( keycodeToMove ) ] ) + } + + // On “enter”, if the highlighted item isn’t disabled, set the value and close. + else if ( !P.$root.find( '.' + CLASSES.highlighted ).hasClass( CLASSES.disabled ) ) { + P.set( 'select', P.component.item.highlight ) + if ( SETTINGS.closeOnSelect ) { + P.close( true ) + } + } + } + + + // If the target is within the root and “enter” is pressed, + // prevent the default action and trigger a click on the target instead. + else if ( $.contains( P.$root[0], target ) && keycode == 13 ) { + event.preventDefault() + target.click() + } + }) + } + + // Trigger the queued “open” events. + return P.trigger( 'open' ) + }, //open + + + /** + * Close the picker + */ + close: function( giveFocus ) { + + // If we need to give focus, do it before changing states. + if ( giveFocus ) { + if ( SETTINGS.editable ) { + ELEMENT.focus() + } + else { + // ....ah yes! It would’ve been incomplete without a crazy workaround for IE :| + // The focus is triggered *after* the close has completed - causing it + // to open again. So unbind and rebind the event at the next tick. + P.$holder.off( 'focus.toOpen' ).focus() + setTimeout( function() { + P.$holder.on( 'focus.toOpen', handleFocusToOpenEvent ) + }, 0 ) + } + } + + // Remove the “active” class. + $ELEMENT.removeClass( CLASSES.active ) + aria( ELEMENT, 'expanded', false ) + + // * A Firefox bug, when `html` has `overflow:hidden`, results in + // killing transitions :(. So remove the “opened” state on the next tick. + // Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=625289 + setTimeout( function() { + + // Remove the “opened” and “focused” class from the picker root. + P.$root.removeClass( CLASSES.opened + ' ' + CLASSES.focused ) + aria( P.$root[0], 'hidden', true ) + + }, 0 ) + + // If it’s already closed, do nothing more. + if ( !STATE.open ) return P + + // Set it as closed. + STATE.open = false + + // Allow the page to scroll. + if ( IS_DEFAULT_THEME ) { + $html. + css( 'overflow', '' ). + css( 'padding-right', '-=' + getScrollbarWidth() ) + } + + // Unbind the document events. + $document.off( '.' + STATE.id ) + + // Trigger the queued “close” events. + return P.trigger( 'close' ) + }, //close + + + /** + * Clear the values + */ + clear: function( options ) { + return P.set( 'clear', null, options ) + }, //clear + + + /** + * Set something + */ + set: function( thing, value, options ) { + + var thingItem, thingValue, + thingIsObject = $.isPlainObject( thing ), + thingObject = thingIsObject ? thing : {} + + // Make sure we have usable options. + options = thingIsObject && $.isPlainObject( value ) ? value : options || {} + + if ( thing ) { + + // If the thing isn’t an object, make it one. + if ( !thingIsObject ) { + thingObject[ thing ] = value + } + + // Go through the things of items to set. + for ( thingItem in thingObject ) { + + // Grab the value of the thing. + thingValue = thingObject[ thingItem ] + + // First, if the item exists and there’s a value, set it. + if ( thingItem in P.component.item ) { + if ( thingValue === undefined ) thingValue = null + P.component.set( thingItem, thingValue, options ) + } + + // Then, check to update the element value and broadcast a change. + if ( thingItem == 'select' || thingItem == 'clear' ) { + $ELEMENT. + val( thingItem == 'clear' ? '' : P.get( thingItem, SETTINGS.format ) ). + trigger( 'change' ) + } + } + + // Render a new picker. + P.render() + } + + // When the method isn’t muted, trigger queued “set” events and pass the `thingObject`. + return options.muted ? P : P.trigger( 'set', thingObject ) + }, //set + + + /** + * Get something + */ + get: function( thing, format ) { + + // Make sure there’s something to get. + thing = thing || 'value' + + // If a picker state exists, return that. + if ( STATE[ thing ] != null ) { + return STATE[ thing ] + } + + // Return the submission value, if that. + if ( thing == 'valueSubmit' ) { + if ( P._hidden ) { + return P._hidden.value + } + thing = 'value' + } + + // Return the value, if that. + if ( thing == 'value' ) { + return ELEMENT.value + } + + // Check if a component item exists, return that. + if ( thing in P.component.item ) { + if ( typeof format == 'string' ) { + var thingValue = P.component.get( thing ) + return thingValue ? + PickerConstructor._.trigger( + P.component.formats.toString, + P.component, + [ format, thingValue ] + ) : '' + } + return P.component.get( thing ) + } + }, //get + + + + /** + * Bind events on the things. + */ + on: function( thing, method, internal ) { + + var thingName, thingMethod, + thingIsObject = $.isPlainObject( thing ), + thingObject = thingIsObject ? thing : {} + + if ( thing ) { + + // If the thing isn’t an object, make it one. + if ( !thingIsObject ) { + thingObject[ thing ] = method + } + + // Go through the things to bind to. + for ( thingName in thingObject ) { + + // Grab the method of the thing. + thingMethod = thingObject[ thingName ] + + // If it was an internal binding, prefix it. + if ( internal ) { + thingName = '_' + thingName + } + + // Make sure the thing methods collection exists. + STATE.methods[ thingName ] = STATE.methods[ thingName ] || [] + + // Add the method to the relative method collection. + STATE.methods[ thingName ].push( thingMethod ) + } + } + + return P + }, //on + + + + /** + * Unbind events on the things. + */ + off: function() { + var i, thingName, + names = arguments; + for ( i = 0, namesCount = names.length; i < namesCount; i += 1 ) { + thingName = names[i] + if ( thingName in STATE.methods ) { + delete STATE.methods[thingName] + } + } + return P + }, + + + /** + * Fire off method events. + */ + trigger: function( name, data ) { + var _trigger = function( name ) { + var methodList = STATE.methods[ name ] + if ( methodList ) { + methodList.map( function( method ) { + PickerConstructor._.trigger( method, P, [ data ] ) + }) + } + } + _trigger( '_' + name ) + _trigger( name ) + return P + } //trigger + } //PickerInstance.prototype + + + /** + * Wrap the picker holder components together. + */ + function createWrappedComponent() { + + // Create a picker wrapper holder + return PickerConstructor._.node( 'div', + + // Create a picker wrapper node + PickerConstructor._.node( 'div', + + // Create a picker frame + PickerConstructor._.node( 'div', + + // Create a picker box node + PickerConstructor._.node( 'div', + + // Create the components nodes. + P.component.nodes( STATE.open ), + + // The picker box class + CLASSES.box + ), + + // Picker wrap class + CLASSES.wrap + ), + + // Picker frame class + CLASSES.frame + ), + + // Picker holder class + CLASSES.holder, + + 'tabindex="-1"' + ) //endreturn + } //createWrappedComponent + + + + /** + * Prepare the input element with all bindings. + */ + function prepareElement() { + + $ELEMENT. + + // Store the picker data by component name. + data(NAME, P). + + // Add the “input” class name. + addClass(CLASSES.input). + + // If there’s a `data-value`, update the value of the element. + val( $ELEMENT.data('value') ? + P.get('select', SETTINGS.format) : + ELEMENT.value + ) + + + // Only bind keydown events if the element isn’t editable. + if ( !SETTINGS.editable ) { + + $ELEMENT. + + // On focus/click, open the picker. + on( 'focus.' + STATE.id + ' click.' + STATE.id, function(event) { + event.preventDefault() + P.open() + }). + + // Handle keyboard event based on the picker being opened or not. + on( 'keydown.' + STATE.id, handleKeydownEvent ) + } + + + // Update the aria attributes. + aria(ELEMENT, { + haspopup: true, + expanded: false, + readonly: false, + owns: ELEMENT.id + '_root' + }) + } + + + /** + * Prepare the root picker element with all bindings. + */ + function prepareElementRoot() { + aria( P.$root[0], 'hidden', true ) + } + + + /** + * Prepare the holder picker element with all bindings. + */ + function prepareElementHolder() { + + P.$holder. + + on({ + + // For iOS8. + keydown: handleKeydownEvent, + + 'focus.toOpen': handleFocusToOpenEvent, + + blur: function() { + // Remove the “target” class. + $ELEMENT.removeClass( CLASSES.target ) + }, + + // When something within the holder is focused, stop from bubbling + // to the doc and remove the “focused” state from the root. + focusin: function( event ) { + P.$root.removeClass( CLASSES.focused ) + event.stopPropagation() + }, + + // When something within the holder is clicked, stop it + // from bubbling to the doc. + 'mousedown click': function( event ) { + + var target = event.target + + // Make sure the target isn’t the root holder so it can bubble up. + if ( target != P.$holder[0] ) { + + event.stopPropagation() + + // * For mousedown events, cancel the default action in order to + // prevent cases where focus is shifted onto external elements + // when using things like jQuery mobile or MagnificPopup (ref: #249 & #120). + // Also, for Firefox, don’t prevent action on the `option` element. + if ( event.type == 'mousedown' && !$( target ).is( 'input, select, textarea, button, option' )) { + + event.preventDefault() + + // Re-focus onto the holder so that users can click away + // from elements focused within the picker. + P.$holder[0].focus() + } + } + } + + }). + + // If there’s a click on an actionable element, carry out the actions. + on( 'click', '[data-pick], [data-nav], [data-clear], [data-close]', function() { + + var $target = $( this ), + targetData = $target.data(), + targetDisabled = $target.hasClass( CLASSES.navDisabled ) || $target.hasClass( CLASSES.disabled ), + + // * For IE, non-focusable elements can be active elements as well + // (http://stackoverflow.com/a/2684561). + activeElement = getActiveElement() + activeElement = activeElement && ( activeElement.type || activeElement.href ) + + // If it’s disabled or nothing inside is actively focused, re-focus the element. + if ( targetDisabled || activeElement && !$.contains( P.$root[0], activeElement ) ) { + P.$holder[0].focus() + } + + // If something is superficially changed, update the `highlight` based on the `nav`. + if ( !targetDisabled && targetData.nav ) { + P.set( 'highlight', P.component.item.highlight, { nav: targetData.nav } ) + } + + // If something is picked, set `select` then close with focus. + else if ( !targetDisabled && 'pick' in targetData ) { + P.set( 'select', targetData.pick ) + if ( SETTINGS.closeOnSelect ) { + P.close( true ) + } + } + + // If a “clear” button is pressed, empty the values and close with focus. + else if ( targetData.clear ) { + P.clear() + if ( SETTINGS.closeOnClear ) { + P.close( true ) + } + } + + else if ( targetData.close ) { + P.close( true ) + } + + }) //P.$holder + + } + + + /** + * Prepare the hidden input element along with all bindings. + */ + function prepareElementHidden() { + + var name + + if ( SETTINGS.hiddenName === true ) { + name = ELEMENT.name + ELEMENT.name = '' + } + else { + name = [ + typeof SETTINGS.hiddenPrefix == 'string' ? SETTINGS.hiddenPrefix : '', + typeof SETTINGS.hiddenSuffix == 'string' ? SETTINGS.hiddenSuffix : '_submit' + ] + name = name[0] + ELEMENT.name + name[1] + } + + P._hidden = $( + '' + )[0] + + $ELEMENT. + + // If the value changes, update the hidden input with the correct format. + on('change.' + STATE.id, function() { + P._hidden.value = ELEMENT.value ? + P.get('select', SETTINGS.formatSubmit) : + '' + }) + } + + + // Wait for transitions to end before focusing the holder. Otherwise, while + // using the `container` option, the view jumps to the container. + function focusPickerOnceOpened() { + + if (IS_DEFAULT_THEME && supportsTransitions) { + P.$holder.find('.' + CLASSES.frame).one('transitionend', function() { + P.$holder[0].focus() + }) + } + else { + P.$holder[0].focus() + } + } + + + function handleFocusToOpenEvent(event) { + + // Stop the event from propagating to the doc. + event.stopPropagation() + + // Add the “target” class. + $ELEMENT.addClass( CLASSES.target ) + + // Add the “focused” class to the root. + P.$root.addClass( CLASSES.focused ) + + // And then finally open the picker. + P.open() + } + + + // For iOS8. + function handleKeydownEvent( event ) { + + var keycode = event.keyCode, + + // Check if one of the delete keys was pressed. + isKeycodeDelete = /^(8|46)$/.test(keycode) + + // For some reason IE clears the input value on “escape”. + if ( keycode == 27 ) { + P.close( true ) + return false + } + + // Check if `space` or `delete` was pressed or the picker is closed with a key movement. + if ( keycode == 32 || isKeycodeDelete || !STATE.open && P.component.key[keycode] ) { + + // Prevent it from moving the page and bubbling to doc. + event.preventDefault() + event.stopPropagation() + + // If `delete` was pressed, clear the values and close the picker. + // Otherwise open the picker. + if ( isKeycodeDelete ) { P.clear().close() } + else { P.open() } + } + } + + + // Return a new picker instance. + return new PickerInstance() +} //PickerConstructor + + + +/** + * The default classes and prefix to use for the HTML classes. + */ +PickerConstructor.klasses = function( prefix ) { + prefix = prefix || 'picker' + return { + + picker: prefix, + opened: prefix + '--opened', + focused: prefix + '--focused', + + input: prefix + '__input', + active: prefix + '__input--active', + target: prefix + '__input--target', + + holder: prefix + '__holder', + + frame: prefix + '__frame', + wrap: prefix + '__wrap', + + box: prefix + '__box' + } +} //PickerConstructor.klasses + + + +/** + * Check if the default theme is being used. + */ +function isUsingDefaultTheme( element ) { + + var theme, + prop = 'position' + + // For IE. + if ( element.currentStyle ) { + theme = element.currentStyle[prop] + } + + // For normal browsers. + else if ( window.getComputedStyle ) { + theme = getComputedStyle( element )[prop] + } + + return theme == 'fixed' +} + + + +/** + * Get the width of the browser’s scrollbar. + * Taken from: https://github.com/VodkaBears/Remodal/blob/master/src/jquery.remodal.js + */ +function getScrollbarWidth() { + + if ( $html.height() <= $window.height() ) { + return 0 + } + + var $outer = $( '
' ). + appendTo( 'body' ) + + // Get the width without scrollbars. + var widthWithoutScroll = $outer[0].offsetWidth + + // Force adding scrollbars. + $outer.css( 'overflow', 'scroll' ) + + // Add the inner div. + var $inner = $( '
' ).appendTo( $outer ) + + // Get the width with scrollbars. + var widthWithScroll = $inner[0].offsetWidth + + // Remove the divs. + $outer.remove() + + // Return the difference between the widths. + return widthWithoutScroll - widthWithScroll +} + + + +/** + * PickerConstructor helper methods. + */ +PickerConstructor._ = { + + /** + * Create a group of nodes. Expects: + * ` + { + min: {Integer}, + max: {Integer}, + i: {Integer}, + node: {String}, + item: {Function} + } + * ` + */ + group: function( groupObject ) { + + var + // Scope for the looped object + loopObjectScope, + + // Create the nodes list + nodesList = '', + + // The counter starts from the `min` + counter = PickerConstructor._.trigger( groupObject.min, groupObject ) + + + // Loop from the `min` to `max`, incrementing by `i` + for ( ; counter <= PickerConstructor._.trigger( groupObject.max, groupObject, [ counter ] ); counter += groupObject.i ) { + + // Trigger the `item` function within scope of the object + loopObjectScope = PickerConstructor._.trigger( groupObject.item, groupObject, [ counter ] ) + + // Splice the subgroup and create nodes out of the sub nodes + nodesList += PickerConstructor._.node( + groupObject.node, + loopObjectScope[ 0 ], // the node + loopObjectScope[ 1 ], // the classes + loopObjectScope[ 2 ] // the attributes + ) + } + + // Return the list of nodes + return nodesList + }, //group + + + /** + * Create a dom node string + */ + node: function( wrapper, item, klass, attribute ) { + + // If the item is false-y, just return an empty string + if ( !item ) return '' + + // If the item is an array, do a join + item = $.isArray( item ) ? item.join( '' ) : item + + // Check for the class + klass = klass ? ' class="' + klass + '"' : '' + + // Check for any attributes + attribute = attribute ? ' ' + attribute : '' + + // Return the wrapped item + return '<' + wrapper + klass + attribute + '>' + item + '' + }, //node + + + /** + * Lead numbers below 10 with a zero. + */ + lead: function( number ) { + return ( number < 10 ? '0': '' ) + number + }, + + + /** + * Trigger a function otherwise return the value. + */ + trigger: function( callback, scope, args ) { + return typeof callback == 'function' ? callback.apply( scope, args || [] ) : callback + }, + + + /** + * If the second character is a digit, length is 2 otherwise 1. + */ + digits: function( string ) { + return ( /\d/ ).test( string[ 1 ] ) ? 2 : 1 + }, + + + /** + * Tell if something is a date object. + */ + isDate: function( value ) { + return {}.toString.call( value ).indexOf( 'Date' ) > -1 && this.isInteger( value.getDate() ) + }, + + + /** + * Tell if something is an integer. + */ + isInteger: function( value ) { + return {}.toString.call( value ).indexOf( 'Number' ) > -1 && value % 1 === 0 + }, + + + /** + * Create ARIA attribute strings. + */ + ariaAttr: ariaAttr +} //PickerConstructor._ + + + +/** + * Extend the picker with a component and defaults. + */ +PickerConstructor.extend = function( name, Component ) { + + // Extend jQuery. + $.fn[ name ] = function( options, action ) { + + // Grab the component data. + var componentData = this.data( name ) + + // If the picker is requested, return the data object. + if ( options == 'picker' ) { + return componentData + } + + // If the component data exists and `options` is a string, carry out the action. + if ( componentData && typeof options == 'string' ) { + return PickerConstructor._.trigger( componentData[ options ], componentData, [ action ] ) + } + + // Otherwise go through each matched element and if the component + // doesn’t exist, create a new picker using `this` element + // and merging the defaults and options with a deep copy. + return this.each( function() { + var $this = $( this ) + if ( !$this.data( name ) ) { + new PickerConstructor( this, name, Component, options ) + } + }) + } + + // Set the defaults. + $.fn[ name ].defaults = Component.defaults +} //PickerConstructor.extend + + + +function aria(element, attribute, value) { + if ( $.isPlainObject(attribute) ) { + for ( var key in attribute ) { + ariaSet(element, key, attribute[key]) + } + } + else { + ariaSet(element, attribute, value) + } +} +function ariaSet(element, attribute, value) { + element.setAttribute( + (attribute == 'role' ? '' : 'aria-') + attribute, + value + ) +} +function ariaAttr(attribute, data) { + if ( !$.isPlainObject(attribute) ) { + attribute = { attribute: data } + } + data = '' + for ( var key in attribute ) { + var attr = (key == 'role' ? '' : 'aria-') + key, + attrVal = attribute[key] + data += attrVal == null ? '' : attr + '="' + attribute[key] + '"' + } + return data +} + +// IE8 bug throws an error for activeElements within iframes. +function getActiveElement() { + try { + return document.activeElement + } catch ( err ) { } +} + + + +// Expose the picker constructor. +return PickerConstructor + + +})); + + + diff --git a/assets/js/pickadate/picker.time.js b/assets/js/pickadate/picker.time.js index 2256d9e5..99f15482 100644 --- a/assets/js/pickadate/picker.time.js +++ b/assets/js/pickadate/picker.time.js @@ -1,5 +1,1013 @@ /*! - * Time picker for pickadate.js v3.5.0 + * Time picker for pickadate.js v3.5.6 * http://amsul.github.io/pickadate.js/time.htm */ -!function(a){"function"==typeof define&&define.amd?define(["picker","jquery"],a):a(Picker,jQuery)}(function(a,b){function c(a,b){var c=this,d=a.$node[0].value,e=a.$node.data("value"),f=e||d,g=e?b.formatSubmit:b.format;c.settings=b,c.$node=a.$node,c.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"},c.item={},c.item.clear=null,c.item.interval=b.interval||30,c.item.disable=(b.disable||[]).slice(0),c.item.enable=-function(a){return a[0]===!0?a.shift():-1}(c.item.disable),c.set("min",b.min).set("max",b.max).set("now"),f?c.set("select",f,{format:g,fromValue:!!d}):c.set("select",null).set("highlight",c.item.now),c.key={40:1,38:-1,39:1,37:-1,go:function(a){c.set("highlight",c.item.highlight.pick+a*c.item.interval,{interval:a*c.item.interval}),this.render()}},a.on("render",function(){var c=a.$root.children(),d=c.find("."+b.klass.viewset);d.length&&(c[0].scrollTop=~~d.position().top-2*d[0].clientHeight)}).on("open",function(){a.$root.find("button").attr("disabled",!1)}).on("close",function(){a.$root.find("button").attr("disabled",!0)})}var d=24,e=60,f=12,g=d*e,h=a._;c.prototype.set=function(a,b,c){var d=this,e=d.item;return null===b?("clear"==a&&(a="select"),e[a]=b,d):(e["enable"==a?"disable":"flip"==a?"enable":a]=d.queue[a].split(" ").map(function(e){return b=d[e](a,b,c)}).pop(),"select"==a?d.set("highlight",e.select,c):"highlight"==a?d.set("view",e.highlight,c):"interval"==a?d.set("min",e.min,c).set("max",e.max,c):a.match(/^(flip|min|max|disable|enable)$/)&&("min"==a&&d.set("max",e.max,c),e.select&&d.disabled(e.select)&&d.set("select",e.select,c),e.highlight&&d.disabled(e.highlight)&&d.set("highlight",e.highlight,c)),d)},c.prototype.get=function(a){return this.item[a]},c.prototype.create=function(a,c,f){var i=this;return c=void 0===c?a:c,h.isDate(c)&&(c=[c.getHours(),c.getMinutes()]),b.isPlainObject(c)&&h.isInteger(c.pick)?c=c.pick:b.isArray(c)?c=+c[0]*e+ +c[1]:h.isInteger(c)||(c=i.now(a,c,f)),"max"==a&&c=a.from.pick&&b.pick<=a.to.pick},c.prototype.overlapRanges=function(a,b){var c=this;return a=c.createRange(a.from,a.to),b=c.createRange(b.from,b.to),c.withinRange(a,b.from)||c.withinRange(a,b.to)||c.withinRange(b,a.from)||c.withinRange(b,a.to)},c.prototype.now=function(a,b){var c,d=this.item.interval,f=new Date,g=f.getHours()*e+f.getMinutes(),i=h.isInteger(b);return g-=g%d,c=0>b&&-d>=d*b+g,g+="min"==a&&c?0:d,i&&(g+=d*(c&&"max"!=a?b+1:b)),g},c.prototype.normalize=function(a,b){var c=this.item.interval,d=this.item.min&&this.item.min.pick||0;return b-="min"==a?0:(b-d)%c},c.prototype.measure=function(a,c,f){var g=this;return c?c===!0||h.isInteger(c)?c=g.now(a,c,f):b.isPlainObject(c)&&h.isInteger(c.pick)&&(c=g.normalize(a,c.pick,f)):c="min"==a?[0,0]:[d-1,e-1],c},c.prototype.validate=function(a,b,c){var d=this,e=c&&c.interval?c.interval:d.item.interval;return d.disabled(b)&&(b=d.shift(b,e)),b=d.scope(b),d.disabled(b)&&(b=d.shift(b,-1*e)),b},c.prototype.disabled=function(a){var c=this,d=c.item.disable.filter(function(d){return h.isInteger(d)?a.hour==d:b.isArray(d)||h.isDate(d)?a.pick==c.create(d).pick:b.isPlainObject(d)?c.withinRange(d,a):void 0});return d=d.length&&!d.filter(function(a){return b.isArray(a)&&"inverted"==a[2]||b.isPlainObject(a)&&a.inverted}).length,-1===c.item.enable?!d:d||a.pickc.item.max.pick},c.prototype.shift=function(a,b){var c=this,d=c.item.min.pick,e=c.item.max.pick;for(b=b||c.item.interval;c.disabled(a)&&(a=c.create(a.pick+=b),!(a.pick<=d||a.pick>=e)););return a},c.prototype.scope=function(a){var b=this.item.min.pick,c=this.item.max.pick;return this.create(a.pick>c?c:a.pickb.time%g?"a.m.":"p.m."},A:function(a,b){return a?2:g/2>b.time%g?"AM":"PM"},toArray:function(a){return a.split(/(h{1,2}|H{1,2}|i|a|A|!.)/g)},toString:function(a,b){var c=this;return c.formats.toArray(a).map(function(a){return h.trigger(c.formats[a],c,[0,b])||a.replace(/^!/,"")}).join("")}},c.prototype.isTimeExact=function(a,c){var d=this;return h.isInteger(a)&&h.isInteger(c)||"boolean"==typeof a&&"boolean"==typeof c?a===c:(h.isDate(a)||b.isArray(a))&&(h.isDate(c)||b.isArray(c))?d.create(a).pick===d.create(c).pick:b.isPlainObject(a)&&b.isPlainObject(c)?d.isTimeExact(a.from,c.from)&&d.isTimeExact(a.to,c.to):!1},c.prototype.isTimeOverlap=function(a,c){var d=this;return h.isInteger(a)&&(h.isDate(c)||b.isArray(c))?a===d.create(c).hour:h.isInteger(c)&&(h.isDate(a)||b.isArray(a))?c===d.create(a).hour:b.isPlainObject(a)&&b.isPlainObject(c)?d.overlapRanges(a,c):!1},c.prototype.flipEnable=function(a){var b=this.item;b.enable=a||(-1==b.enable?1:-1)},c.prototype.deactivate=function(a,c){var d=this,e=d.item.disable.slice(0);return"flip"==c?d.flipEnable():c===!1?(d.flipEnable(1),e=[]):c===!0?(d.flipEnable(-1),e=[]):c.map(function(a){for(var c,f=0;fi;i+=1){if(g=e[i],d.isTimeExact(g,a)){c=e[i]=null,j=!0;break}if(d.isTimeOverlap(g,a)){b.isPlainObject(a)?(a.inverted=!0,c=a):b.isArray(a)?(c=a,c[2]||c.push("inverted")):h.isDate(a)&&(c=[a.getFullYear(),a.getMonth(),a.getDate(),"inverted"]);break}}if(c)for(i=0;f>i;i+=1)if(d.isTimeExact(e[i],a)){e[i]=null;break}if(j)for(i=0;f>i;i+=1)if(d.isTimeOverlap(e[i],a)){e[i]=null;break}c&&e.push(c)}),e.filter(function(a){return null!=a})},c.prototype.i=function(a,b){return h.isInteger(b)&&b>0?b:this.item.interval},c.prototype.nodes=function(a){var b=this,c=b.settings,d=b.item.select,e=b.item.highlight,f=b.item.view,g=b.item.disable;return h.node("ul",h.group({min:b.item.min.pick,max:b.item.max.pick,i:b.item.interval,node:"li",item:function(a){a=b.create(a);var i=a.pick,j=d&&d.pick==i,k=e&&e.pick==i,l=g&&b.disabled(a);return[h.trigger(b.formats.toString,b,[h.trigger(c.formatLabel,b,[a])||c.format,a]),function(a){return j&&a.push(c.klass.selected),k&&a.push(c.klass.highlighted),f&&f.pick==i&&a.push(c.klass.viewset),l&&a.push(c.klass.disabled),a.join(" ")}([c.klass.listItem]),"data-pick="+a.pick+" "+h.ariaAttr({role:"option",selected:j&&b.$node.val()===h.trigger(b.formats.toString,b,[c.format,a])?!0:null,activedescendant:k?!0:null,disabled:l?!0:null})]}})+h.node("li",h.node("button",c.clear,c.klass.buttonClear,"type=button data-clear=1"+(a?"":" disabled")+" "+h.ariaAttr({controls:b.$node[0].id})),"",h.ariaAttr({role:"presentation"})),c.klass.list,h.ariaAttr({role:"listbox",controls:b.$node[0].id}))},c.defaults=function(a){return{clear:"Clear",format:"h:i A",interval:30,klass:{picker:a+" "+a+"--time",holder:a+"__holder",list:a+"__list",listItem:a+"__list-item",disabled:a+"__list-item--disabled",selected:a+"__list-item--selected",highlighted:a+"__list-item--highlighted",viewset:a+"__list-item--viewset",now:a+"__list-item--now",buttonClear:a+"__button--clear"}}}(a.klasses().picker),a.extend("pickatime",c)}); \ No newline at end of file + +(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, + + // 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 ) + + +})); + + + diff --git a/core/um-enqueue.php b/core/um-enqueue.php index e3b23412..ff5a1e1f 100644 --- a/core/um-enqueue.php +++ b/core/um-enqueue.php @@ -3,14 +3,14 @@ class UM_Enqueue { function __construct() { - + add_action('wp_head', array(&$this, 'wp_head'), 999); // high-priority - - $priority = apply_filters( 'um_core_enqueue_priority', 0 ); + + $priority = apply_filters( 'um_core_enqueue_priority', 100 ); add_action('wp_enqueue_scripts', array(&$this, 'wp_enqueue_scripts'), $priority ); - + } - + /*** *** @Enqueue inline css globally ***/ @@ -18,7 +18,7 @@ class UM_Enqueue { $css = um_get_option('custom_css'); if ( !$css ) return; ?>permalinks->get_current_url( get_option('permalink_structure') ); - + foreach( $exclude as $match ) { if ( strstr( $c_url, untrailingslashit( $match ) ) ) { return; } } - + } - + $include = um_get_option('js_css_include'); if ( is_array( $include ) ) { array_filter( $include ); @@ -80,88 +80,91 @@ class UM_Enqueue { } if ( isset($force_load) && $force_load == false ) return; - + // enqueue styles if ( um_get_option('disable_minify') ) { - + $this->load_original(); - + wp_localize_script( 'um_scripts', 'um_scripts', array( 'ajaxurl' => admin_url( 'admin-ajax.php' ), 'fileupload' => um_url . 'core/lib/upload/um-file-upload.php', 'imageupload' => um_url . 'core/lib/upload/um-image-upload.php' ) ); - + } else { wp_register_script('um_minified', um_url . 'assets/js/um.min.js', array('jquery'), ultimatemember_version, true ); wp_enqueue_script('um_minified'); - + wp_localize_script( 'um_minified', 'um_scripts', array( 'ajaxurl' => admin_url( 'admin-ajax.php' ), 'fileupload' => um_url . 'core/lib/upload/um-file-upload.php', 'imageupload' => um_url . 'core/lib/upload/um-image-upload.php' ) ); - + wp_register_style('um_minified', um_url . 'assets/css/um.min.css', '', ultimatemember_version, 'all' ); wp_enqueue_style('um_minified'); - + } - + // rtl style if ( is_rtl() ) { wp_register_style('um_rtl', um_url . 'assets/css/um.rtl.css', '', ultimatemember_version, 'all' ); wp_enqueue_style('um_rtl'); } - + // load a localized version for date/time $locale = get_locale(); if ( $locale && file_exists( um_path . 'assets/js/pickadate/translations/' . $locale . '.js' ) ) { wp_register_script('um_datetime_locale', um_url . 'assets/js/pickadate/translations/' . $locale . '.js', '', ultimatemember_version, true ); wp_enqueue_script('um_datetime_locale'); } - + + if(is_object($post) && has_shortcode($post->post_content,'ultimatemember')) { + wp_dequeue_script('jquery-form'); + } } - + /*** *** @This will load original files (not minified) ***/ function load_original() { - + $this->load_google_charts(); - + $this->load_fonticons(); - + $this->load_selectjs(); - + $this->load_modal(); - + $this->load_css(); - + $this->load_fileupload(); - + $this->load_datetimepicker(); - + $this->load_raty(); - + $this->load_scrollto(); - + $this->load_scrollbar(); $this->load_imagecrop(); - + $this->load_masonry(); - + $this->load_tipsy(); - + $this->load_functions(); - + $this->load_responsive(); - + $this->load_customjs(); - + } - + /*** *** @Include Google charts ***/ @@ -169,205 +172,205 @@ class UM_Enqueue { wp_register_script('um_gchart', 'https://www.google.com/jsapi' ); wp_enqueue_script('um_gchart'); - + } - + /*** *** @Load plugin css ***/ function load_css(){ - + wp_register_style('um_styles', um_url . 'assets/css/um-styles.css' ); wp_enqueue_style('um_styles'); - + wp_register_style('um_members', um_url . 'assets/css/um-members.css' ); wp_enqueue_style('um_members'); - + wp_register_style('um_profile', um_url . 'assets/css/um-profile.css' ); wp_enqueue_style('um_profile'); - + wp_register_style('um_account', um_url . 'assets/css/um-account.css' ); wp_enqueue_style('um_account'); - + wp_register_style('um_misc', um_url . 'assets/css/um-misc.css' ); wp_enqueue_style('um_misc'); - + } - + /*** *** @Load select-dropdowns JS ***/ function load_selectjs(){ - + wp_register_script('um_select', um_url . 'assets/js/um-select.js', array('jquery') ); wp_enqueue_script('um_select'); - + wp_register_style('um_select', um_url . 'assets/css/um-select.css' ); wp_enqueue_style('um_select'); - + } - + /*** *** @Load Fonticons ***/ function load_fonticons(){ - + wp_register_style('um_fonticons_ii', um_url . 'assets/css/um-fonticons-ii.css' ); wp_enqueue_style('um_fonticons_ii'); - + wp_register_style('um_fonticons_fa', um_url . 'assets/css/um-fonticons-fa.css' ); wp_enqueue_style('um_fonticons_fa'); - + } - + /*** *** @Load fileupload JS ***/ function load_fileupload() { - + wp_register_script('um_jquery_form', um_url . 'assets/js/um-jquery-form.js' ); wp_enqueue_script('um_jquery_form'); - + wp_register_script('um_fileupload', um_url . 'assets/js/um-fileupload.js' ); wp_enqueue_script('um_fileupload'); - + wp_register_style('um_fileupload', um_url . 'assets/css/um-fileupload.css' ); wp_enqueue_style('um_fileupload'); - + } - + /*** *** @Load JS functions ***/ function load_functions(){ - + wp_register_script('um_functions', um_url . 'assets/js/um-functions.js' ); wp_enqueue_script('um_functions'); - + } - + /*** *** @Load custom JS ***/ function load_customjs(){ - + wp_register_script('um_conditional', um_url . 'assets/js/um-conditional.js' ); wp_enqueue_script('um_conditional'); - + wp_register_script('um_scripts', um_url . 'assets/js/um-scripts.js' ); wp_enqueue_script('um_scripts'); wp_register_script('um_members', um_url . 'assets/js/um-members.js' ); wp_enqueue_script('um_members'); - + wp_register_script('um_profile', um_url . 'assets/js/um-profile.js' ); wp_enqueue_script('um_profile'); - + wp_register_script('um_account', um_url . 'assets/js/um-account.js' ); wp_enqueue_script('um_account'); - + } - + /*** *** @Load date & time picker ***/ function load_datetimepicker(){ - + wp_register_script('um_datetime', um_url . 'assets/js/pickadate/picker.js' ); wp_enqueue_script('um_datetime'); - + wp_register_script('um_datetime_date', um_url . 'assets/js/pickadate/picker.date.js' ); wp_enqueue_script('um_datetime_date'); - + wp_register_script('um_datetime_time', um_url . 'assets/js/pickadate/picker.time.js' ); wp_enqueue_script('um_datetime_time'); - + wp_register_script('um_datetime_legacy', um_url . 'assets/js/pickadate/legacy.js' ); wp_enqueue_script('um_datetime_legacy'); - + wp_register_style('um_datetime', um_url . 'assets/css/pickadate/default.css' ); wp_enqueue_style('um_datetime'); - + wp_register_style('um_datetime_date', um_url . 'assets/css/pickadate/default.date.css' ); wp_enqueue_style('um_datetime_date'); - + wp_register_style('um_datetime_time', um_url . 'assets/css/pickadate/default.time.css' ); wp_enqueue_style('um_datetime_time'); - + } - + /*** *** @Load scrollto ***/ function load_scrollto(){ - + wp_register_script('um_scrollto', um_url . 'assets/js/um-scrollto.js' ); wp_enqueue_script('um_scrollto'); } - + /*** *** @Load scrollbar ***/ function load_scrollbar(){ - + wp_register_script('um_scrollbar', um_url . 'assets/js/um-scrollbar.js' ); wp_enqueue_script('um_scrollbar'); - + wp_register_style('um_scrollbar', um_url . 'assets/css/um-scrollbar.css' ); wp_enqueue_style('um_scrollbar'); } - + /*** *** @Load rating ***/ function load_raty(){ - + wp_register_script('um_raty', um_url . 'assets/js/um-raty.js' ); wp_enqueue_script('um_raty'); - + wp_register_style('um_raty', um_url . 'assets/css/um-raty.css' ); wp_enqueue_style('um_raty'); } - + /*** *** @Load crop script ***/ function load_imagecrop(){ - + wp_register_script('um_crop', um_url . 'assets/js/um-crop.js' ); wp_enqueue_script('um_crop'); - + wp_register_style('um_crop', um_url . 'assets/css/um-crop.css' ); wp_enqueue_style('um_crop'); - + } - + /*** *** @Load masonry ***/ function load_masonry(){ - + wp_register_script('um_masonry', um_url . 'assets/js/um-masonry.js' ); wp_enqueue_script('um_masonry'); } - + /*** *** @Load tipsy ***/ function load_tipsy(){ - + wp_register_script('um_tipsy', um_url . 'assets/js/um-tipsy.js' ); wp_enqueue_script('um_tipsy'); - + wp_register_style('um_tipsy', um_url . 'assets/css/um-tipsy.css' ); wp_enqueue_style('um_tipsy'); } - + /*** *** @Load modal ***/ @@ -375,23 +378,23 @@ class UM_Enqueue { wp_register_style('um_modal', um_url . 'assets/css/um-modal.css' ); wp_enqueue_style('um_modal'); - + wp_register_script('um_modal', um_url . 'assets/js/um-modal.js' ); wp_enqueue_script('um_modal'); - + } - + /*** *** @Load responsive styles ***/ function load_responsive(){ - + wp_register_script('um_responsive', um_url . 'assets/js/um-responsive.js' ); wp_enqueue_script('um_responsive'); - + wp_register_style('um_responsive', um_url . 'assets/css/um-responsive.css' ); wp_enqueue_style('um_responsive'); - + } - -} \ No newline at end of file + +}