/**
* Makes sure we have all the required levels on the Tribe Object
*
* @since 4.9.5
*
* @type {Object}
*/
tribe.events = tribe.events || {};
tribe.events.views = tribe.events.views || {};
/**
* Configures Datepicker Object in the Global Tribe variable
*
* @since 4.9.5
*
* @type {Object}
*/
tribe.events.views.datepicker = {};
/**
* Initializes in a Strict env the code that manages the Event Views
*
* @since 4.9.5
*
* @param {Object} $ jQuery
* @param {Object} obj tribe.events.views.datepicker
*
* @return {void}
*/
( function( $, obj ) {
'use strict';
var $document = $( document );
/**
* Selectors used for configuration and setup
*
* @since 4.9.5
*
* @type {Object}
*/
obj.selectors = {
datepickerFormClass: '.tribe-events-c-top-bar__datepicker-form',
datepickerContainer: '[data-js="tribe-events-top-bar-datepicker-container"]',
datepickerDaysBody: '.datepicker-days tbody',
input: '[data-js="tribe-events-top-bar-date"]',
button: '[data-js="tribe-events-top-bar-datepicker-button"]',
buttonOpenClass: '.tribe-events-c-top-bar__datepicker-button--open',
dateInput: '[name="tribe-events-views[tribe-bar-date]"]',
prevIconTemplate: '.tribe-events-c-top-bar__datepicker-template-prev-icon',
nextIconTemplate: '.tribe-events-c-top-bar__datepicker-template-next-icon',
};
/**
* Object of state
*
* @since 4.9.5
*
* @type {Object}
*/
obj.state = {
initialized: false,
};
/**
* Object of datepicker options
*
* @since 4.9.10
*
* @type {Object}
*/
obj.options = {
container: null,
daysOfWeekDisabled: [],
maxViewMode: 'decade',
minViewMode: 'month',
orientation: 'bottom left',
showOnFocus: false,
templates: {
leftArrow: '',
rightArrow: '',
},
};
/**
* Object of key codes
*
* @since 5.0.0
*
* @type {Object}
*/
obj.keyCode = {
ENTER: 13,
};
/**
* Date object representing today
*
* @since 4.9.13
*
* @type {Date|null}
*/
obj.today = null;
/**
* Object of date format map.
* Date formats are mapped from PHP to Bootstrap Datepicker format.
*
* @since 4.9.11
*
* @type {Object}
*
* @see https://bootstrap-datepicker.readthedocs.io/en/latest/options.html#format
*/
obj.dateFormatMap = {
d: 'dd',
j: 'd',
m: 'mm',
n: 'm',
Y: 'yyyy',
};
/**
* Mutation observer to watch for mutations
*
* @since 4.9.10
*
* @type {MutationObserver}
*/
obj.observer = null;
/**
* Pads number with extra 0 if needed to make it double digit
*
* @since 4.9.5
*
* @param {integer} number number to pad with extra 0
*
* @return {string} string representation of padded number
*/
obj.padNumber = function( number ) {
var numStr = number + '';
var padding = numStr.length > 1 ? '' : '0';
return padding + numStr;
};
/**
* Performs an AJAX request using manager.js request method
*
* @since 4.9.5
*
* @param {object} viewData object of view data
* @param {jQuery} $container jQuery object of view container
*
* @return {void}
*/
obj.request = function( viewData, $container ) {
var data = {
view_data: viewData,
};
tribe.events.views.manager.request( data, $container );
};
/**
* Create the Date input that will be preprended on the form created.
*
* @since 4.9.11
*
* @param {string} value string representation of the date value
*
* @return {jQuery}
*/
obj.createDateInputObj = function( value ) {
var $input = $( '' );
$input.attr( {
type: 'hidden',
name: 'tribe-events-views[tribe-bar-date]',
value: value,
} );
return $input;
};
/**
* Submits request after date change from datepicker based on live refresh setting.
*
* @since 4.9.11
*
* @param {jQuery} $container jQuery object of view container
* @param {string} value string representation of the date value
*
* @return {void}
*/
obj.submitRequest = function( $container, value ) {
var viewData = {};
viewData[ 'tribe-bar-date' ] = value,
obj.request( viewData, $container );
};
/**
* Handle datepicker changeDate event
*
* @since 4.9.5
*
* @param {Event} event event object for 'changeDate' event
*
* @return {void}
*/
obj.handleChangeDate = function( event ) {
var $container = event.data.container;
var date = event.date.getDate();
var month = event.date.getMonth() + 1;
var year = event.date.getFullYear();
var paddedDate = obj.padNumber( date );
var paddedMonth = obj.padNumber( month );
var dateValue = [ year, paddedMonth, paddedDate ].join( '-' );
obj.submitRequest( $container, dateValue );
};
/**
* Handle datepicker changeMonth event
*
* @since 4.9.5
*
* @param {Event} event event object for 'changeMonth' event
*
* @return {void}
*/
obj.handleChangeMonth = function( event ) {
var $container = event.data.container;
var month, year;
if ( event.date ) {
month = event.date.getMonth() + 1;
year = event.date.getFullYear();
} else {
var date = $container
.find( obj.selectors.input )
.bootstrapDatepicker( 'getDate' );
month = date.getMonth() + 1;
year = date.getFullYear();
}
var paddedMonth = obj.padNumber( month );
var dateValue = [ year, paddedMonth ].join( '-' );
obj.submitRequest( $container, dateValue );
};
/**
* Handle datepicker keydown event
*
* @since 5.0.0
*
* @param {Event} event event object for 'keydown' event
*
* @return {void}
*/
obj.handleKeyDown = function(event) {
if ( event.keyCode !== obj.keyCode.ENTER ) {
return;
}
event.data.input.bootstrapDatepicker().trigger( 'changeMonth' );
}
/**
* Handle datepicker show event
*
* @since 4.9.13
*
* @param {Event} event event object for 'show' event
*
* @return {void}
*/
obj.handleShow = function( event ) {
event.data.datepickerButton.addClass( obj.selectors.buttonOpenClass.className() );
};
/**
* Handle datepicker hide event
*
* @since 4.9.8
*
* @param {Event} event event object for 'hide' event
*
* @return {void}
*/
obj.handleHide = function( event ) {
var $datepickerButton = event.data.datepickerButton;
var state = $datepickerButton.data( 'tribeEventsState' );
event.data.observer.disconnect();
if ( state.isTarget ) {
event.data.input.bootstrapDatepicker( 'show' );
return;
}
$datepickerButton
.removeClass( obj.selectors.buttonOpenClass.className() )
.trigger( 'focus' );
};
/**
* Handle datepicker button mousedown
*
* @since 4.9.8
*
* @param {Event} event event object for 'mousedown' event
*
* @return {void}
*/
obj.handleMousedown = function( event ) {
var $datepickerButton = event.data.target;
var state = $datepickerButton.data( 'tribeEventsState' );
if ( 'touchstart' === event.type ) {
var method = $datepickerButton.hasClass( obj.selectors.buttonOpenClass.className() )
? 'hide'
: 'show';
var tapHide = 'hide' === method;
state.isTarget = false;
$datepickerButton
.data( 'tribeTapHide', tapHide )
.data( 'tribeEventsState', state )
.off( 'mousedown', obj.handleMousedown );
return;
}
state.isTarget = true;
$datepickerButton.data( 'tribeEventsState', state );
};
/**
* Handle datepicker button click
*
* @since 4.9.8
*
* @param {Event} event event object for 'click' event
*
* @return {void}
*/
obj.handleClick = function( event ) {
var $input = event.data.input;
var $datepickerButton = event.data.target;
var state = $datepickerButton.data( 'tribeEventsState' );
var method = $datepickerButton.hasClass( obj.selectors.buttonOpenClass.className() )
? 'hide'
: 'show';
var tapHide = $datepickerButton.data( 'tribeTapHide' );
if ( tapHide ) {
return;
}
state.isTarget = false;
$datepickerButton.data( 'tribeEventsState', state );
$input.bootstrapDatepicker( method );
if ( 'show' === method ) {
$input.trigger( 'focus' );
}
};
/**
* Curry function to handle mutations
* Used to pass in `data`
*
* @since 4.9.7
*
* @param {Object} data data object to be passed for use in handler
*
* @return {function}
*/
obj.handleMutation = function( data ) {
var $container = data.container;
/**
* Handle mutations from mutation observer
*
* @since 4.9.7
*
* @param {array} mutationsList list of mutations that have occurred
* @param {MutationObserver} observer mutation observer instance
*
* @return {void}
*/
return function( mutationsList, observer ) { // eslint-disable-line no-unused-vars
mutationsList.forEach( function( mutation ) {
// if datepicker switches months via prev/next arrows or by selecting a month on month picker
if (
'childList' === mutation.type &&
$container.find( obj.selectors.datepickerDaysBody ).is( mutation.target ) &&
mutation.addedNodes.length
) {
$container.trigger( 'handleMutationMonthChange.tribeEvents' );
}
} );
};
};
/**
* Set today to date object representing today
*
* @since 4.9.13
*
* @param {string} today string representation of today's date according to website time
*
* @return {void}
*/
obj.setToday = function( today ) {
var date = today;
if ( today.indexOf( ' ' ) >= 0 ) {
date = today.split( ' ' )[0];
}
obj.today = new Date( date );
};
/**
* Determine whether or not date is the same as today.
* The function uses UTC values to maintain consistency with website date.
* Function will return false if proper unit is not provided.
*
* @since 4.9.13
*
* @param {Date} date Date object representing the date being compared
* @param {string} unit Unit to compare dates to
*
* @return {bool}
*/
obj.isSameAsToday = function( date, unit ) {
switch ( unit ) {
case 'year':
return date.getFullYear() === obj.today.getUTCFullYear();
case 'month':
return obj.isSameAsToday( date, 'year' ) && date.getMonth() === obj.today.getUTCMonth();
case 'day':
return obj.isSameAsToday( date, 'month' ) && date.getDate() === obj.today.getUTCDate();
default:
return false;
}
}
/**
* Determine whether or not date is before today.
* The function uses UTC values to maintain consistency with website date.
* Function will return false if proper unit is not provided.
*
* @since 4.9.13
*
* @param {Date} date Date object representing the date being compared
* @param {string} unit Unit to compare dates to
*
* @return {bool}
*/
obj.isBeforeToday = function( date, unit ) {
switch ( unit ) {
case 'year':
return date.getFullYear() < obj.today.getUTCFullYear();
case 'month':
return obj.isBeforeToday( date, 'year' )
|| ( obj.isSameAsToday( date, 'year' ) && date.getMonth() < obj.today.getUTCMonth() );
case 'day':
return obj.isBeforeToday( date, 'month' )
|| ( obj.isSameAsToday( date, 'month' ) && date.getDate() < obj.today.getUTCDate() );
default:
return false;
}
};
/**
* Filter datepicker day cells
*
* @since 4.9.13
*
* @return {string|void}
*/
obj.filterDayCells = function( date ) {
if ( obj.isBeforeToday( date, 'day' ) ) {
return 'past';
} else if ( obj.isSameAsToday( date, 'day' ) ) {
return 'current';
}
};
/**
* Filter datepicker month cells
*
* @since 4.9.13
*
* @return {string|void}
*/
obj.filterMonthCells = function( date ) {
if ( obj.isBeforeToday( date, 'month' ) ) {
return 'past';
} else if ( obj.isSameAsToday( date, 'month' ) ) {
return 'current';
}
};
/**
* Filter datepicker year cells
*
* @since 4.9.13
*
* @return {string|void}
*/
obj.filterYearCells = function( date ) {
if ( obj.isBeforeToday( date, 'year' ) ) {
return 'past';
} else if ( obj.isSameAsToday( date, 'year' ) ) {
return 'current';
}
};
/**
* Convert date format from PHP to Bootstrap datepicker format.
*
* @since 4.9.11
*
* @param {string} dateFormat datepicker date format in PHP format.
*
* @return {string}
*/
obj.convertDateFormat = function( dateFormat ) {
var convertedDateFormat = dateFormat;
Object.keys( obj.dateFormatMap ).forEach( function( key ) {
convertedDateFormat = convertedDateFormat.replace( key, obj.dateFormatMap[ key ] );
} );
return convertedDateFormat;
};
/**
* Initialize datepicker date format.
*
* @since 4.9.11
*
* @param {object} data data object passed from 'afterSetup.tribeEvents' event
*
* @return {void}
*/
obj.initDateFormat = function( data ) {
var dateFormats = data.date_formats || {};
var dateFormat = dateFormats.compact;
var convertedDateFormat = obj.convertDateFormat( dateFormat );
obj.options.format = convertedDateFormat;
};
/**
* Deinitialize datepicker JS
*
* @since 4.9.5
*
* @param {Event} event event object for 'beforeAjaxSuccess.tribeEvents' event
* @param {jqXHR} jqXHR Request object
* @param {Object} settings Settings that this request was made with
*
* @return {void}
*/
obj.deinit = function( event, jqXHR, settings ) {
var $container = event.data.container;
$container.trigger( 'beforeDatepickerDeinit.tribeEvents', [ jqXHR, settings ] );
var $input = $container.find( obj.selectors.input );
var $datepickerButton = $container.find( obj.selectors.button );
$input.bootstrapDatepicker( 'destroy' ).off();
$datepickerButton.off();
$container.off( 'beforeAjaxSuccess.tribeEvents', obj.deinit );
$container.trigger( 'afterDatepickerDeinit.tribeEvents', [ jqXHR, settings ] );
};
/**
* Initialize datepicker JS
*
* @since 4.9.8
*
* @param {Event} event event object for 'afterSetup.tribeEvents' event
* @param {integer} index jQuery.each index param from 'afterSetup.tribeEvents' event
* @param {jQuery} $container jQuery object of view container
* @param {object} data data object passed from 'afterSetup.tribeEvents' event
*
* @return {void}
*/
obj.init = function( event, index, $container, data ) {
$container.trigger( 'beforeDatepickerInit.tribeEvents', [ index, $container, data ] );
var $input = $container.find( obj.selectors.input );
var $datepickerButton = $container.find( obj.selectors.button );
var $prevIcon = $container.find( obj.selectors.prevIconTemplate ).html();
var $nextIcon = $container.find( obj.selectors.nextIconTemplate ).html();
var viewSlug = data.slug;
var isMonthView = 'month' === viewSlug;
// set up datepicker change event and handler
var changeEvent = isMonthView ? 'changeMonth' : 'changeDate';
var changeHandler = isMonthView ? obj.handleChangeMonth : obj.handleChangeDate;
// set up datepicker button state
var state = {
isTarget: false,
};
// set up mutation observer
obj.observer = new MutationObserver( obj.handleMutation( { container: $container } ) );
// set up today's date
obj.setToday( data.today );
// set options for datepicker
obj.initDateFormat( data );
obj.options.weekStart = data.start_of_week;
obj.options.container = $container.find( obj.selectors.datepickerContainer );
obj.options.minViewMode = isMonthView ? 'year' : 'month';
var tribeL10nDatatables = window.tribe_l10n_datatables || {};
var datepickerI18n = tribeL10nDatatables.datepicker || {};
var nextText = datepickerI18n.nextText || 'Next';
var prevText = datepickerI18n.prevText || 'Prev';
obj.options.templates.leftArrow = $prevIcon
+ ''
+ prevText
+ '';
obj.options.templates.rightArrow = $nextIcon
+ ''
+ nextText
+ '';
obj.options.beforeShowDay = obj.filterDayCells;
obj.options.beforeShowMonth = obj.filterMonthCells;
obj.options.beforeShowYear = obj.filterYearCells;
// A tweak for RTL languages.
if ( document.dir && document.dir === 'rtl' ) {
obj.options.rtl = true;
}
// ...and for document language.
if ( document.lang ) {
obj.options.language = document.lang;
}
$input
.bootstrapDatepicker( obj.options )
.on( changeEvent, { container: $container }, changeHandler )
.on( 'show', { datepickerButton: $datepickerButton }, obj.handleShow )
.on(
'hide',
{ datepickerButton: $datepickerButton, input: $input, observer: obj.observer },
obj.handleHide
);
if ( isMonthView ) {
$input
.bootstrapDatepicker()
.on( 'keydown', { input: $input }, obj.handleKeyDown );
}
$datepickerButton
.on( 'touchstart mousedown', { target: $datepickerButton }, obj.handleMousedown )
.on( 'click', { target: $datepickerButton, input: $input }, obj.handleClick )
.data( 'tribeEventsState', state );
// deinit datepicker and event handlers before success
$container.on(
'beforeAjaxSuccess.tribeEvents',
{ container: $container, viewSlug: viewSlug },
obj.deinit
);
$container.trigger( 'afterDatepickerInit.tribeEvents', [ index, $container, data ] );
};
/**
* Initialize datepicker i18n
*
* @since 4.9.5
*
* @return {void}
*/
obj.initDatepickerI18n = function() {
var tribeL10nDatatables = window.tribe_l10n_datatables || {};
var datepickerI18n = tribeL10nDatatables.datepicker || {};
datepickerI18n.dayNames &&
( $.fn.bootstrapDatepicker.dates.en.days = datepickerI18n.dayNames );
datepickerI18n.dayNamesShort &&
( $.fn.bootstrapDatepicker.dates.en.daysShort = datepickerI18n.dayNamesShort );
datepickerI18n.dayNamesMin &&
( $.fn.bootstrapDatepicker.dates.en.daysMin = datepickerI18n.dayNamesMin );
datepickerI18n.monthNames &&
( $.fn.bootstrapDatepicker.dates.en.months = datepickerI18n.monthNames );
datepickerI18n.monthNamesMin &&
( $.fn.bootstrapDatepicker.dates.en.monthsShort = datepickerI18n.monthNamesMin );
datepickerI18n.today &&
( $.fn.bootstrapDatepicker.dates.en.today = datepickerI18n.today );
datepickerI18n.clear &&
( $.fn.bootstrapDatepicker.dates.en.clear = datepickerI18n.clear );
};
/**
* Initialize datepicker to jQuery object
*
* @since 4.9.5
*
* @return {void}
*/
obj.initDatepicker = function() {
obj.initDatepickerI18n();
obj.state.initialized = true;
};
/**
* Handles the initialization of the Datepicker when Document is ready
*
* @since 4.9.5
*
* @return {void}
*/
obj.ready = function() {
obj.initDatepicker();
if ( obj.state.initialized ) {
$document.on(
'afterSetup.tribeEvents',
tribe.events.views.manager.selectors.container,
obj.init
);
}
};
// Configure on document ready
$( obj.ready );
} )( jQuery, tribe.events.views.datepicker );