vendor/assets/javascripts/pickadate/picker.date.js in pickadate-rails-1.3.2 vs vendor/assets/javascripts/pickadate/picker.date.js in pickadate-rails-1.4.0
- old
+ new
@@ -1,24 +1,15 @@
/*!
- * Date picker for pickadate.js v3.3.2
+ * Date picker for pickadate.js v3.4.0
* http://amsul.github.io/pickadate.js/date.htm
*/
-/*jshint
- debug: true,
- devel: true,
- browser: true,
- asi: true,
- unused: true,
- boss: true
- */
-
(function ( factory ) {
// Register as an anonymous module.
- if ( typeof define === 'function' && define.amd )
+ if ( typeof define == 'function' && define.amd )
define( ['picker','jquery'], factory )
// Or using browser globals.
else factory( Picker, jQuery )
@@ -27,11 +18,12 @@
/**
* Globals and constants
*/
var DAYS_IN_WEEK = 7,
- WEEKS_IN_CALENDAR = 6
+ WEEKS_IN_CALENDAR = 6,
+ _ = Picker._
/**
* The date picker constructor
@@ -46,21 +38,22 @@
isRTL = function() {
return 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: 'navigate create validate',
- view: 'create validate viewset',
- disable: 'flipItem',
- enable: 'flipItem'
+ highlight: 'parse navigate create validate',
+ view: 'parse create validate viewset',
+ disable: 'deactivate',
+ enable: 'activate'
}
// The component's item object.
calendar.item = {}
@@ -70,53 +63,64 @@
})( calendar.item.disable )
calendar.
set( 'min', settings.min ).
set( 'max', settings.max ).
- set( 'now' ).
+ set( 'now' )
- // Setting the `select` also sets the `highlight` and `view`.
- set( 'select',
+ // 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,
+ fromValue: !!elementValue
+ })
+ }
- // Use the value provided or default to selecting “today”.
- valueString || calendar.item.now,
- {
- // Use the appropriate format.
- format: formatString,
+ // If there’s no value, default to highlighting “today”.
+ else {
+ calendar.
+ set( 'select', null ).
+ set( 'highlight', calendar.item.now )
+ }
- // Set user-provided month data as true when there is a
- // “mm” or “m” used in the relative format string.
- data: (function( formatArray ) {
- return valueString && ( formatArray.indexOf( 'mm' ) > -1 || formatArray.indexOf( 'm' ) > -1 )
- })( calendar.formats.toArray( formatString ) )
- }
- )
-
// 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 ) {
- calendar.set( 'highlight', [ calendar.item.highlight.year, calendar.item.highlight.month, calendar.item.highlight.date + timeChange ], { interval: timeChange } )
+ var highlightedObject = calendar.item.highlight,
+ targetDate = new Date( highlightedObject.year, highlightedObject.month, highlightedObject.date + timeChange )
+ calendar.set(
+ 'highlight',
+ [ targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate() ],
+ { interval: timeChange }
+ )
this.render()
}
}
// Bind some picker events.
picker.
on( 'render', function() {
picker.$root.find( '.' + settings.klass.selectMonth ).on( 'change', function() {
- picker.set( 'highlight', [ picker.get( 'view' ).year, this.value, picker.get( 'highlight' ).date ] )
- picker.$root.find( '.' + settings.klass.selectMonth ).trigger( 'focus' )
+ 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() {
- picker.set( 'highlight', [ this.value, picker.get( 'view' ).month, picker.get( 'highlight' ).date ] )
- picker.$root.find( '.' + settings.klass.selectYear ).trigger( 'focus' )
+ 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' )
+ }
})
}).
on( 'open', function() {
picker.$root.find( 'button, select' ).attr( 'disabled', false )
}).
@@ -130,31 +134,42 @@
/**
* Set a datepicker item object.
*/
DatePicker.prototype.set = function( type, value, options ) {
- var calendar = this
+ var calendar = this,
+ calendarItem = calendar.item
- // Go through the queue of methods, and invoke the function. Update this
- // as the time unit, and set the final resultant as this item type.
+ // If the value is `null` just set it immediately.
+ if ( value === null ) {
+ 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.
- calendar.item[ ( type == 'enable' ? 'disable' : type == 'flip' ? 'enable' : type ) ] = calendar.queue[ type ].split( ' ' ).map( function( method ) {
- return value = calendar[ method ]( type, value, options )
+ 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', calendar.item.select, options )
+ calendar.set( 'highlight', calendarItem.select, options )
}
else if ( type == 'highlight' ) {
- calendar.set( 'view', calendar.item.highlight, options )
+ calendar.set( 'view', calendarItem.highlight, options )
}
- else if ( ( type == 'flip' || type == 'min' || type == 'max' || type == 'disable' || type == 'enable' ) && calendar.item.select && calendar.item.highlight ) {
- calendar.
- set( 'select', calendar.item.select, options ).
- set( 'highlight', calendar.item.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
@@ -183,23 +198,23 @@
if ( value == -Infinity || value == Infinity ) {
isInfiniteValue = value
}
// If it’s an object, use the native date object.
- else if ( $.isPlainObject( value ) && Picker._.isInteger( value.pick ) ) {
+ 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 = Picker._.isDate( value ) ? value : calendar.create().obj
+ value = _.isDate( value ) ? value : calendar.create().obj
}
// If it’s a number or date object, make a normalized date.
- else if ( Picker._.isInteger( value ) || Picker._.isDate( value ) ) {
+ 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 )*/ {
@@ -217,40 +232,134 @@
}
} //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 )
-} //DatePicker.prototype.now
+}
/**
* Navigate to next/prev month.
*/
DatePicker.prototype.navigate = function( type, value, options ) {
- if ( $.isPlainObject( value ) ) {
+ var targetDateObject,
+ targetYear,
+ targetMonth,
+ targetDate,
+ isTargetArray = $.isArray( value ),
+ isTargetObject = $.isPlainObject( value ),
+ viewsetObject = this.item.view/*,
+ safety = 100*/
- var targetDateObject = new Date( value.year, value.month + ( options && options.nav ? options.nav : 0 ), 1 ),
- year = targetDateObject.getFullYear(),
- month = targetDateObject.getMonth(),
- date = value.date
- // Make sure the date is valid and 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 ( Picker._.isDate( targetDateObject ) && new Date( year, month, date ).getMonth() !== month ) {
- date -= 1
+ if ( isTargetArray || isTargetObject ) {
+
+ if ( isTargetObject ) {
+ targetYear = value.year
+ targetMonth = value.month
+ targetDate = value.date
}
+ else {
+ targetYear = +value[0]
+ targetMonth = +value[1]
+ targetDate = +value[2]
+ }
- value = [ year, month, date ]
+ // 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
@@ -275,11 +384,11 @@
if ( !value ) {
value = type == 'min' ? -Infinity : Infinity
}
// If it's an integer, get a date relative to today.
- else if ( Picker._.isInteger( value ) ) {
+ else if ( _.isInteger( value ) ) {
value = calendar.now( type, value, { rel: value } )
}
return value
} ///DatePicker.prototype.measure
@@ -328,29 +437,31 @@
if ( dateTime < dateObject.pick ) hasEnabledBeforeTarget = true
else if ( dateTime > dateObject.pick ) hasEnabledAfterTarget = true
}
// Return only integers for enabled weekdays.
- return Picker._.isInteger( value )
- }).length
+ return _.isInteger( value )
+ }).length/*,
+ safety = 100*/
+
// Cases to validate for:
// [1] Not inverted and date disabled.
// [2] Inverted and some dates enabled.
- // [3] Out of range.
+ // [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.nav ) if (
+ if ( !options || !options.nav ) if (
/* 1 */ ( !isFlippedBase && calendar.disabled( dateObject ) ) ||
/* 2 */ ( isFlippedBase && calendar.disabled( dateObject ) && ( hasEnabledWeekdays || hasEnabledBeforeTarget || hasEnabledAfterTarget ) ) ||
- /* 3 */ ( dateObject.pick <= minLimitObject.pick || dateObject.pick >= maxLimitObject.pick )
+ /* 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.
@@ -358,28 +469,35 @@
interval *= -1
}
// Keep looping until we reach an enabled date.
- while ( calendar.disabled( dateObject ) ) {
+ 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, return to the original date and flatten the interval.
+
+ // 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 = Math.abs( interval ) / interval
+ interval = interval > 0 ? 1 : -1
}
- // If we’ve reached the min/max limit, reverse the direction and flatten the interval.
+ // 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 - 1 ])
}
else if ( dateObject.pick >= maxLimitObject.pick ) {
reachedMax = true
interval = -1
+ dateObject = calendar.create([ maxLimitObject.year, maxLimitObject.month, maxLimitObject.date + 1 ])
}
// If we’ve reached both limits, just break out of the loop.
if ( reachedMin && reachedMax ) {
@@ -400,70 +518,81 @@
/**
* Check if a date is disabled.
*/
-DatePicker.prototype.disabled = function( dateObject ) {
+DatePicker.prototype.disabled = function( dateToVerify ) {
- var calendar = this,
+ 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 ( Picker._.isInteger( dateToDisable ) ) {
- return dateObject.day === ( calendar.settings.firstDay ? dateToDisable : dateToDisable - 1 ) % 7
+ 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 ) || Picker._.isDate( dateToDisable ) ) {
- return dateObject.pick === calendar.create( dateToDisable ).pick
+ 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'
+ 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 ||
- dateObject.pick < calendar.item.min.pick ||
- dateObject.pick > calendar.item.max.pick
+ 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 = {}
+ parsingObject = {},
+ monthIndex
- if ( !value || Picker._.isInteger( value ) || $.isArray( value ) || Picker._.isDate( value ) || $.isPlainObject( value ) && Picker._.isInteger( value.pick ) ) {
+ if ( !value || _.isInteger( value ) || $.isArray( value ) || _.isDate( value ) || $.isPlainObject( value ) && _.isInteger( value.pick ) ) {
return value
}
- // We need a `.format` to parse the value.
+ // We need a `.format` to parse the value with.
if ( !( options && options.format ) ) {
- // should probably default to the default format.
- throw "Need a formatting option to parse this.."
+ options = options || {}
+ options.format = calendar.settings.format
}
+ // Calculate the month index to adjust with.
+ monthIndex = typeof value == 'string' && !options.fromValue ? 1 : 0
+
// 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 ? Picker._.trigger( formattingLabel, calendar, [ value, parsingObject ] ) : label.replace( /^!/, '' ).length
+ 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 )
@@ -472,11 +601,15 @@
// Update the value as the substring from format length to end.
value = value.substr( formatLength )
})
// If it’s parsing a user provided month value, compensate for month 0index.
- return [ parsingObject.yyyy || parsingObject.yy, +( parsingObject.mm || parsingObject.m ) - ( options.data ? 1 : 0 ), parsingObject.dd || parsingObject.d ]
+ return [
+ parsingObject.yyyy || parsingObject.yy,
+ +( parsingObject.mm || parsingObject.m ) - monthIndex,
+ parsingObject.dd || parsingObject.d
+ ]
} //DatePicker.prototype.parse
/**
* Various formats to display the object in.
@@ -507,17 +640,17 @@
d: function( string, dateObject ) {
// If there's string, then get the digits length.
// Otherwise return the selected date.
- return string ? Picker._.digits( string ) : dateObject.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 : Picker._.lead( dateObject.date )
+ 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.
@@ -531,17 +664,17 @@
},
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 ? Picker._.digits( string ) : dateObject.month + 1
+ 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 : Picker._.lead( dateObject.month + 1 )
+ return string ? 2 : _.lead( dateObject.month + 1 )
},
mmm: function( string, dateObject ) {
var collection = this.settings.monthsShort
@@ -575,185 +708,251 @@
// 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 Picker._.trigger( calendar.formats[ label ], calendar, [ 0, itemObject ] ) || label.replace( /^!/, '' )
+ return _.trigger( calendar.formats[ label ], calendar, [ 0, itemObject ] ) || label.replace( /^!/, '' )
}).join( '' )
}
}
})() //DatePicker.prototype.formats
+
+
/**
- * Flip an item as enabled or disabled.
+ * Check if two date units are the exact.
*/
-DatePicker.prototype.flipItem = function( type, value/*, options*/ ) {
+DatePicker.prototype.isDateExact = function( one, two ) {
- var calendar = this,
- collection = calendar.item.disable,
- isFlippedBase = calendar.item.enable === -1
+ var calendar = this
- // Flip the enabled and disabled dates.
- if ( value == 'flip' ) {
- calendar.item.enable = isFlippedBase ? 1 : -1
+ // When we’re working with weekdays, do a direct comparison.
+ if (
+ ( _.isInteger( one ) && _.isInteger( two ) ) ||
+ ( typeof one == 'boolean' && typeof two == 'boolean' )
+ ) {
+ return one === two
}
- // Reset the collection and enable the base state.
- else if ( ( type == 'enable' && value === true ) || ( type == 'disable' && value === false ) ) {
- calendar.item.enable = 1
- collection = []
+ // 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
}
- // Reset the collection and disable the base state.
- else if ( ( type == 'enable' && value === false ) || ( type == 'disable' && value === true ) ) {
- calendar.item.enable = -1
- collection = []
+ // 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 )
}
- // Make sure a collection of things was passed to add/remove.
- else if ( $.isArray( value ) ) {
+ return false
+}
- // Check if we have to add/remove from collection.
- if ( isFlippedBase && type == 'enable' || !isFlippedBase && type == 'disable' ) {
- collection = calendar.addDisabled( collection, value )
- }
- else if ( !isFlippedBase && type == 'enable' ) {
- collection = calendar.addEnabled( collection, value )
- }
- else if ( isFlippedBase && type == 'disable' ) {
- collection = calendar.removeDisabled( collection, value )
- }
- }
- return collection
-} //DatePicker.prototype.flipItem
-
-
/**
- * Add an enabled (inverted) item to the disabled collection.
+ * Check if two date units overlap.
*/
-DatePicker.prototype.addEnabled = function( collection, item ) {
+DatePicker.prototype.isDateOverlap = function( one, two ) {
var calendar = this
- // Go through each item to enable.
- item.map( function( timeUnit ) {
+ // When we’re working with a weekday index, compare the days.
+ if ( _.isInteger( one ) && ( _.isDate( two ) || $.isArray( two ) ) ) {
+ return one === calendar.create( two ).day + 1
+ }
+ if ( _.isInteger( two ) && ( _.isDate( one ) || $.isArray( one ) ) ) {
+ return two === calendar.create( one ).day + 1
+ }
- // Check if the time unit is already within the collection.
- if ( calendar.filterDisabled( collection, timeUnit, 1 ).length ) {
+ // When we’re working with range objects, check if the ranges overlap.
+ if ( $.isPlainObject( one ) && $.isPlainObject( two ) ) {
+ return calendar.overlapRanges( one, two )
+ }
- // Remove the unit directly from the collection.
- collection = calendar.removeDisabled( collection, [timeUnit] )
+ return false
+}
- // If the unit is an array and it falls within a
- // disabled weekday, invert it and then insert it.
- if (
- $.isArray( timeUnit ) &&
- collection.filter( function( disabledDate ) {
- return Picker._.isInteger( disabledDate ) && calendar.create( timeUnit ).day === disabledDate - 1
- }).length
- ) {
- timeUnit = timeUnit.slice(0)
- timeUnit.push( 'inverted' )
- collection.push( timeUnit )
- }
- }
- })
- // Return the final collection.
- return collection
-} //DatePicker.prototype.addEnabled
+/**
+ * Flip the “enabled” state.
+ */
+DatePicker.prototype.flipEnable = function(val) {
+ var itemObject = this.item
+ itemObject.enable = val || (itemObject.enable == -1 ? 1 : -1)
+}
/**
- * Add an item to the disabled collection.
+ * Mark a collection of dates as “disabled”.
*/
-DatePicker.prototype.addDisabled = function( collection, item ) {
+DatePicker.prototype.deactivate = function( type, datesToDisable ) {
- var calendar = this
+ var calendar = this,
+ disabledItems = calendar.item.disable.slice(0)
- // Go through each item to disable.
- item.map( function( timeUnit ) {
- // Add the time unit if it isn’t already within the collection.
- if ( !calendar.filterDisabled( collection, timeUnit ).length ) {
- collection.push( timeUnit )
- }
+ // If we’re flipping, that’s all we need to do.
+ if ( datesToDisable == 'flip' ) {
+ calendar.flipEnable()
+ }
- // If the time unit is an array and falls within the range, just remove it.
- else if ( $.isArray( timeUnit ) && calendar.filterDisabled( collection, timeUnit, 1 ).length ) {
- collection = calendar.removeDisabled( collection, [timeUnit] )
- }
- })
+ else if ( datesToDisable === false ) {
+ calendar.flipEnable(1)
+ disabledItems = []
+ }
- // Return the final collection.
- return collection
-} //DatePicker.prototype.addDisabled
+ else if ( datesToDisable === true ) {
+ calendar.flipEnable(-1)
+ disabledItems = []
+ }
+ // Otherwise go through the dates to disable.
+ else {
-/**
- * Remove an item from the disabled collection.
- */
-DatePicker.prototype.removeDisabled = function( collection, item ) {
+ datesToDisable.map(function( unitToDisable ) {
- var calendar = this
+ var matchFound
- // Go through each item to enable.
- item.map( function( timeUnit ) {
+ // 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
+ }
+ }
- // Filter each item out of the collection.
- collection = calendar.filterDisabled( collection, timeUnit, 1 )
- })
+ // 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 final colleciton.
- return collection
-} //DatePicker.prototype.removeDisabled
+ // Return the updated collection.
+ return disabledItems
+} //DatePicker.prototype.deactivate
/**
- * Filter through the disabled collection to find a time unit.
+ * Mark a collection of dates as “enabled”.
*/
-DatePicker.prototype.filterDisabled = function( collection, timeUnit, isRemoving ) {
+DatePicker.prototype.activate = function( type, datesToEnable ) {
var calendar = this,
+ disabledItems = calendar.item.disable,
+ disabledItemsCount = disabledItems.length
- // Check if the time unit passed is an array or date object.
- timeIsObject = $.isArray( timeUnit ) || Picker._.isDate( timeUnit ),
+ // If we’re flipping, that’s all we need to do.
+ if ( datesToEnable == 'flip' ) {
+ calendar.flipEnable()
+ }
- // Grab the comparison value if it’s an object.
- timeObjectValue = timeIsObject && calendar.create( timeUnit ).pick
+ else if ( datesToEnable === true ) {
+ calendar.flipEnable(1)
+ disabledItems = []
+ }
- // Go through the disabled collection and try to match this time unit.
- return collection.filter( function( disabledTimeUnit ) {
+ else if ( datesToEnable === false ) {
+ calendar.flipEnable(-1)
+ disabledItems = []
+ }
- // Check if it’s an object and the collection item is an object,
- // use the comparison values. Otherwise to a direct comparison.
- var isMatch = timeIsObject && ( $.isArray( disabledTimeUnit ) || Picker._.isDate( disabledTimeUnit ) ) ?
- timeObjectValue === calendar.create( disabledTimeUnit ).pick : timeUnit === disabledTimeUnit
+ // Otherwise go through the disabled dates.
+ else {
- // Invert the match if we’re removing from the collection.
- return isRemoving ? !isMatch : isMatch
- })
-} //DatePicker.prototype.filterDisabled
+ 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,
- nowObject = calendar.item.now,
- selectedObject = calendar.item.select,
- highlightedObject = calendar.item.highlight,
- viewsetObject = calendar.item.view,
- disabledCollection = calendar.item.disable,
- minLimitObject = calendar.item.min,
- maxLimitObject = calendar.item.max,
+ 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 ) {
@@ -762,33 +961,36 @@
if ( settings.firstDay ) {
collection.push( collection.shift() )
}
// Create and return the table head group.
- return Picker._.node(
+ return _.node(
'thead',
- Picker._.group({
- min: 0,
- max: DAYS_IN_WEEK - 1,
- i: 1,
- node: 'th',
- item: function( counter ) {
- return [
- collection[ counter ],
- settings.klass.weekdays
- ]
- }
- })
+ _.node(
+ 'tr',
+ _.group({
+ min: 0,
+ max: DAYS_IN_WEEK - 1,
+ i: 1,
+ node: 'th',
+ item: function( counter ) {
+ return [
+ collection[ counter ],
+ settings.klass.weekdays
+ ]
+ }
+ })
+ )
) //endreturn
})( ( settings.showWeekdaysFull ? settings.weekdaysFull : settings.weekdaysShort ).slice( 0 ) ), //tableHead
// Create the nav for next/prev month.
createMonthNav = function( next ) {
// Otherwise, return the created month tag.
- return Picker._.node(
+ return _.node(
'div',
' ',
settings.klass[ 'nav' + ( next ? 'Next' : 'Prev' ) ] + (
// If the focused month is outside the range, disabled the button.
@@ -805,11 +1007,11 @@
createMonthLabel = function( monthsCollection ) {
// If there are months to select, add a dropdown menu.
if ( settings.selectMonths ) {
- return Picker._.node( 'select', Picker._.group({
+ return _.node( 'select', _.group({
min: 0,
max: 11,
i: 1,
node: 'option',
item: function( loopedMonth ) {
@@ -833,11 +1035,11 @@
}
}), settings.klass.selectMonth, isOpen ? '' : 'disabled' )
}
// If there's a need for a month selector
- return Picker._.node( 'div', monthsCollection[ viewsetObject.month ], settings.klass.month )
+ return _.node( 'div', monthsCollection[ viewsetObject.month ], settings.klass.month )
}, //createMonthLabel
// Create the year label.
createYearLabel = function() {
@@ -874,11 +1076,11 @@
lowestYear -= availableYears > neededYears ? neededYears : availableYears
highestYear = maxYear
}
- return Picker._.node( 'select', Picker._.group({
+ return _.node( 'select', _.group({
min: lowestYear,
max: highestYear,
i: 1,
node: 'option',
item: function( loopedYear ) {
@@ -893,38 +1095,38 @@
}
}), settings.klass.selectYear, isOpen ? '' : 'disabled' )
}
// Otherwise just return the year focused
- return Picker._.node( 'div', focusedYear, settings.klass.year )
+ return _.node( 'div', focusedYear, settings.klass.year )
} //createYearLabel
// Create and return the entire calendar.
- return Picker._.node(
+ return _.node(
'div',
createMonthNav() + createMonthNav( 1 ) +
createMonthLabel( settings.showMonthsShort ? settings.monthsShort : settings.monthsFull ) +
createYearLabel(),
settings.klass.header
- ) + Picker._.node(
+ ) + _.node(
'table',
tableHead +
- Picker._.node(
+ _.node(
'tbody',
- Picker._.group({
+ _.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 [
- Picker._.group({
+ _.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,
@@ -932,12 +1134,16 @@
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
+
return [
- Picker._.node(
+ _.node(
'div',
targetDate.date,
(function( klasses ) {
// Add the `infocus` or `outfocus` classes based on month in view.
@@ -947,27 +1153,37 @@
if ( nowObject.pick == targetDate.pick ) {
klasses.push( settings.klass.now )
}
// Add the `selected` class if something's selected and the time matches.
- if ( selectedObject && selectedObject.pick == targetDate.pick ) {
+ if ( isSelected ) {
klasses.push( settings.klass.selected )
}
// Add the `highlighted` class if something's highlighted and the time matches.
- if ( highlightedObject && highlightedObject.pick == targetDate.pick ) {
+ if ( isHighlighted ) {
klasses.push( settings.klass.highlighted )
}
// Add the `disabled` class if something's disabled and the object matches.
- if ( disabledCollection && calendar.disabled( targetDate ) || targetDate.pick < minLimitObject.pick || targetDate.pick > maxLimitObject.pick ) {
+ if ( isDisabled ) {
klasses.push( settings.klass.disabled )
}
return klasses.join( ' ' )
})([ settings.klass.day ]),
- 'data-pick=' + targetDate.pick
+ 'data-pick=' + targetDate.pick + ' ' + _.ariaAttr({
+ role: 'button',
+ controls: calendar.$node[0].id,
+ checked: isSelected && calendar.$node.val() === _.trigger(
+ calendar.formats.toString,
+ calendar,
+ [ settings.format, targetDate ]
+ ) ? true : null,
+ activedescendant: isHighlighted ? true : null,
+ disabled: isDisabled ? true : null
+ })
)
] //endreturn
}
})
] //endreturn
@@ -976,13 +1192,13 @@
),
settings.klass.table
) +
// * For Firefox forms to submit, make sure to set the buttons’ `type` attributes as “button”.
- Picker._.node(
+ _.node(
'div',
- Picker._.node( 'button', settings.today, settings.klass.buttonToday, 'type=button data-pick=' + nowObject.pick + ( isOpen ? '' : ' disabled' ) ) +
- Picker._.node( 'button', settings.clear, settings.klass.buttonClear, 'type=button data-clear=1' + ( isOpen ? '' : ' disabled' ) ),
+ _.node( 'button', settings.today, settings.klass.buttonToday, 'type=button data-pick=' + nowObject.pick + ( isOpen ? '' : ' disabled' ) ) +
+ _.node( 'button', settings.clear, settings.klass.buttonClear, 'type=button data-clear=1' + ( isOpen ? '' : ' disabled' ) ),
settings.klass.footer
) //endreturn
} //DatePicker.prototype.nodes