File: //home/globfdxw/www/wp-content/plugins/wpforms-google-calendar/assets/js/builder.js
/* global WPForms, wpf, wpforms_builder, WPFormsBuilder, Choices */
/**
* WPForms Providers Builder Google Calendar module.
*
* @since 1.0.0
*
* @property {Object} wpforms_builder WPForms Builder i18n strings.
* @property {Object} wpforms_builder.google_calendar WPForms Google Calendar i18n strings.
* @property {string} wpforms_builder.google_calendar.duration_options List of duration options.
* @property {string} wpforms_builder.google_calendar.recurrence_options List of recurrence options.
*/
WPForms.Admin.Builder.Providers.GoogleCalendar = WPForms.Admin.Builder.Providers.GoogleCalendar || ( function( document, window, $ ) {
/**
* Public functions and properties.
*
* @since 1.0.0
*
* @type {Object}
*/
const app = {
/**
* CSS selectors.
*
* @since 1.0.0
*
* @type {Object}
*/
selectors: {
settingsPanel: '#wpforms-panel-settings',
authButton: '.wpforms-google-sign-in-button-url',
connection: '.wpforms-builder-provider-connection',
accountField: '.js-wpforms-builder-google-calendar-provider-connection-account',
accountFields: '.js-wpforms-builder-google-calendar-provider-account-fields',
calendarFieldWrapper: '.js-wpforms-builder-google-calendar-provider-connection-calendar-wrapper',
calendarField: '.js-wpforms-builder-google-calendar-provider-connection-calendar',
calendarFieldDescription: '.js-wpforms-builder-google-calendar-provider-connection-calendar-description',
calendarFieldDatetimePreview: '.js-wpforms-builder-google-calendar-provider-connection-calendar-datetime-preview',
calendarFields: '.js-wpforms-builder-google-calendar-provider-calendar-fields',
fieldWrapper: '.wpforms-builder-provider-connection-block',
startDateTimeField: '.js-wpforms-builder-google-calendar-provider-connection-start-datetime',
durationField: '.js-wpforms-builder-google-calendar-provider-connection-duration',
endDateTimeField: '.js-wpforms-builder-google-calendar-provider-connection-end-datetime',
endDateTimeFieldWrapper: '.js-wpforms-builder-google-calendar-provider-end-datetime',
alert: '.wpforms-alert',
alertContent: '.wpforms-alert-content',
optionRow: '.wpforms-field-option-row',
panelField: '.wpforms-panel-field',
seeGuestField: '.js-wpforms-builder-google-calendar-provider-connection-see-guest-list',
inviteOthersField: '.js-wpforms-builder-google-calendar-provider-connection-invite-others',
modifyEventField: '.js-wpforms-builder-google-calendar-provider-connection-modify-event',
dateFormatOption: '.wpforms-field-option-row-format select',
choiceJS: '.choicesjs-select',
},
/**
* List of used classes.
*
* @since 1.0.0
*/
classes: {
hide: 'wpforms-hidden',
required: 'wpforms-required',
disabled: 'wpforms-disabled',
guestsField: 'js-wpforms-builder-google-calendar-provider-connection-guests',
warningAlert: 'wpforms-alert-warning',
errorAlert: 'wpforms-alert-danger',
},
/**
* jQuery elements.
*
* @since 1.0.0
*
* @type {Object}
*/
$elements: {
$builder: $( '#wpforms-builder' ),
$panel: $( '#google-calendar-provider' ),
$connections: $( '#google-calendar-provider .wpforms-builder-provider-connections' ),
},
/**
* Current provider slug.
*
* @since 1.0.0
*
* @type {string}
*/
provider: 'google-calendar',
/**
* This is a shortcut to the 'WPForms.Admin.Builder.Providers' object
* that handles the parent all-providers functionality.
*
* @since 1.0.0
*
* @type {Object}
*/
Providers: {},
/**
* This is a shortcut to the 'WPForms.Admin.Builder.Templates' object
* that handles all the template management.
*
* @since 1.0.0
*
* @type {Object}
*/
Templates: {},
/**
* This is a shortcut to the 'WPForms.Admin.Builder.Providers.cache' object
* that handles all the cache management.
*
* @since 1.0.0
*
* @type {Object}
*/
Cache: {},
/**
* This is a flag for the ready state.
*
* @since 1.0.0
*
* @type {boolean}
*/
isReady: false,
/**
* Start the engine.
*
* Run initialization on the Settings panel only.
*
* @since 1.0.0
*/
init() {
// We are requesting/loading the Settings panel.
if ( wpf.getQueryString( 'view' ) === 'settings' ) {
$( app.selectors.settingsPanel ).on( 'WPForms.Admin.Builder.Providers.ready', app.ready );
}
// We have switched to the Settings panel.
$( document ).on( 'wpformsPanelSwitched', function( event, panel ) {
if ( panel === 'settings' ) {
app.ready();
}
} );
},
/**
* Initialized once the DOM and Providers are fully loaded.
*
* @since 1.0.0
*/
ready() {
if ( app.isReady ) {
return;
}
app.Providers = WPForms.Admin.Builder.Providers;
app.Templates = WPForms.Admin.Builder.Templates;
app.Cache = app.Providers.cache;
app.Templates.add( [
'wpforms-google-calendar-builder-content-connection',
'wpforms-google-calendar-builder-content-connection-calendar-field',
'wpforms-google-calendar-builder-content-connection-conditionals',
] );
// Events registration.
app.bindUIActions();
app.bindTriggers();
app.processInitial();
// Save a flag for ready state.
app.isReady = true;
},
/**
* Process various events as a response to UI interactions.
*
* @since 1.0.0
*/
bindUIActions() {
app.$elements.$panel
.on( 'connectionCreate', app.connection.create )
.on( 'connectionDelete', app.connection.delete )
.on( 'click', app.selectors.authButton, app.account.add )
.on( 'change', app.selectors.accountField, app.ui.accountField.change )
.on( 'change', app.selectors.calendarField, app.ui.calendarField.change )
.on( 'change', app.selectors.durationField, app.ui.durationField.change )
.on( 'change', app.selectors.startDateTimeField, app.ui.dateTimeFields.change )
.on( 'change', app.selectors.endDateTimeField, app.ui.dateTimeFields.change )
.on( 'change', app.selectors.modifyEventField, app.ui.modifyEventField.change );
app.$elements.$builder
.on( 'wpformsFieldSelectMapped', app.ui.guestsField.update )
.on( 'change', app.selectors.dateFormatOption, app.ui.dateTimeFields.changeFormat );
},
/**
* Fire certain events on certain actions, specifically for related connections.
* These are not directly caused by user manipulations.
*
* @since 1.0.0
*/
bindTriggers() {
app.$elements.$connections.on( 'connectionsDataLoaded', function( event, data ) {
if ( _.isEmpty( data.connections ) ) {
return;
}
for ( const connectionId in data.connections ) {
app.connection.renderConnections( {
connection: data.connections[ connectionId ],
conditional: data.conditionals[ connectionId ],
} );
}
} );
app.$elements.$connections.on( 'connectionGenerated', function( event, data ) {
const $connection = app.connection.getById( data.connection.id );
if ( _.has( data.connection, 'isNew' ) && data.connection.isNew ) {
app.connection.replaceIds( data.connection.id, $connection );
return;
}
$( app.selectors.accountField, $connection ).trigger( 'change', [ $connection ] );
$( app.selectors.startDateTimeField, $connection ).trigger( 'change', [ $connection ] );
$( app.selectors.endDateTimeField, $connection ).trigger( 'change', [ $connection ] );
$( app.selectors.modifyEventField, $connection ).trigger( 'change', [ $connection ] );
} );
},
/**
* Compile template with data if any and display them on a page.
*
* @since 1.0.0
*/
processInitial() {
const error = app.Templates.get( `wpforms-${ app.provider }-builder-content-connection-error` );
app.$elements.$connections.prepend( error() );
app.connection.dataLoad();
},
/**
* Connection property.
*
* @since 1.0.0
*/
connection: {
/**
* Sometimes we might need to get a connection DOM element by its ID.
*
* @since 1.0.0
*
* @param {string} connectionId Connection ID to search for a DOM element by.
*
* @return {jQuery} jQuery object for connection.
*/
getById( connectionId ) {
return app.$elements.$connections.find( '.wpforms-builder-provider-connection[data-connection_id="' + connectionId + '"]' );
},
/**
* Sometimes in DOM we might have placeholders or temporary connection IDs.
* We need to replace them with actual values.
*
* @since 1.0.0
*
* @param {string} connectionId New connection ID to replace to.
* @param {Object} $connection jQuery DOM connection element.
*/
replaceIds( connectionId, $connection ) {
// Replace the old temporary% connection_id% from PHP code with the new one.
$connection.find( 'input, select, label' ).each( function() {
const $this = $( this );
if ( $this.attr( 'name' ) ) {
$this.attr( 'name', $this.attr( 'name' ).replace( /%connection_id%/gi, connectionId ) );
}
if ( $this.attr( 'id' ) ) {
$this.attr( 'id', $this.attr( 'id' ).replace( /%connection_id%/gi, connectionId ) );
}
if ( $this.attr( 'for' ) ) {
$this.attr( 'for', $this.attr( 'for' ).replace( /%connection_id%/gi, connectionId ) );
}
if ( $this.attr( 'data-name' ) ) {
$this.attr( 'data-name', $this.attr( 'data-name' ).replace( /%connection_id%/gi, connectionId ) );
}
} );
},
/**
* Create a connection using the user-entered name.
*
* @since 1.0.0
*
* @param {Object} event Event object.
* @param {string} name Connection name.
*/
create( event, name ) {
const connectionId = new Date().getTime().toString( 16 ),
connection = {
id: connectionId,
name,
isNew: true,
};
app.Cache.addTo( app.provider, 'connections', connectionId, connection );
app.connection.renderConnections( {
connection,
} );
},
/**
* Connection is deleted - delete a cache as well.
*
* @since 1.0.0
*
* @param {Object} event Event object.
* @param {Object} $connection jQuery DOM element for a connection.
*/
delete( event, $connection ) {
const $eHolder = app.Providers.getProviderHolder( app.provider );
if ( ! $connection.closest( $eHolder ).length ) {
return;
}
const connectionId = $connection.data( 'connection_id' );
if ( _.isString( connectionId ) ) {
app.Cache.deleteFrom( app.provider, 'connections', connectionId );
}
app.$elements.$connections.trigger( 'connectionDeleted' );
},
/**
* Render connections.
*
* @since 1.0.0
*
* @param {Object} data Connection data.
*/
renderConnections( data ) {
const accounts = app.Cache.get( app.provider, 'accounts' );
if ( ! app.account.isExists( data.connection.account_id, accounts ) ) {
return;
}
const tmplConnection = app.Templates.get( `wpforms-${ app.provider }-builder-content-connection` );
const tmplConditional = $( `#tmpl-wpforms-${ app.provider }-builder-content-connection-conditionals` ).length ? app.Templates.get( `wpforms-${ app.provider }-builder-content-connection-conditionals` ) : app.Templates.get( 'wpforms-providers-builder-content-connection-conditionals' );
const conditional = _.has( data.connection, 'isNew' ) && data.connection.isNew ? tmplConditional() : data.conditional;
const emailFields = wpf.getFields( [ 'email' ], true, true );
const dateTimeFields = wpf.getFields( [ 'date-time' ], true );
app.$elements.$connections
.prepend(
tmplConnection( {
durationOptions: wpforms_builder.google_calendar.duration_options,
recurrenceOptions: wpforms_builder.google_calendar.recurrence_options,
emailFields,
dateTimeFields,
accounts,
connection: data.connection,
conditional,
provider: app.provider,
} ) );
// When we added a new connection with its accounts - trigger next steps.
app.$elements.$connections.trigger( 'connectionGenerated', [ data ] );
app.ui.initChoicesJS( app.connection.getById( data.connection.id ) );
app.$elements.$connections.trigger( 'connectionRendered', [ app.provider, data.connection.id ] );
},
/**
* Fire AJAX-request to retrieve the list of all saved connections.
*
* @since 1.0.0
*/
dataLoad() {
app
.Providers.ajax
.request( app.provider, {
data: {
task: 'connections_get',
},
} )
.done( function( response ) {
if (
! response.success ||
! _.has( response.data, 'connections' )
) {
return;
}
app.connection.updateCache( response.data );
app.$elements.$connections.trigger( 'connectionsDataLoaded', [ response.data ] );
} );
},
/**
* Set cache data after AJAX requests.
*
* @since 1.0.0
*
* @param {Object} data Response data.
*/
updateCache( data ) {
[
'connections',
'calendars',
'conditionals',
'accounts',
].forEach( ( dataType ) => {
app.Cache.set( app.provider, dataType, jQuery.extend( {}, data[ dataType ] ) );
} );
},
},
/**
* Account property.
*
* @since 1.0.0
*/
account: {
/**
* Check if a provided account is listed inside an account list.
*
* @since 1.0.0
*
* @param {string} accountId Connection account ID to check.
* @param {Object} accounts Array of objects, usually received from API.
*
* @return {boolean} True if an account exists.
*/
isExists( accountId, accounts ) {
if ( _.isEmpty( accounts ) ) {
return false;
}
// New connections that have not been saved don't have the account ID yet.
if ( _.isEmpty( accountId ) ) {
return true;
}
return _.has( accounts, accountId );
},
/**
* Redirect the customer to the Google Calendar authorization consent screen.
*
* @since 1.0.0
*
* @param {Event} e Event.
*/
add( e ) {
if ( WPFormsBuilder.formIsSaved() ) {
return;
}
e.preventDefault();
// eslint-disable-next-line camelcase
wpforms_builder.exit_url = $( this ).attr( 'href' );
WPFormsBuilder.formSave( true );
},
},
/**
* All methods that modify the UI of a page.
*
* @since 1.0.0
*/
ui: {
/**
* Account field methods.
*
* @since 1.0.0
*/
accountField: {
/**
* Callback-function on change event.
*
* @since 1.0.0
*/
change() {
const $this = $( this );
const accountId = $this.val();
const $connection = $this.closest( app.selectors.connection );
const $accountFields = $( app.selectors.accountFields, $connection );
const connectionId = $connection.data( 'connection_id' );
const connection = app.Cache.getById( app.provider, 'connections', connectionId );
// eslint-disable-next-line camelcase
connection.account_id = accountId;
$accountFields.toggleClass( app.classes.hide, accountId === '' );
app.ui.calendarField.render( connection );
},
},
/**
* Calendar field methods.
*
* @since 1.0.0
*/
calendarField: {
/**
* Render HTML template.
*
* @since 1.0.0
*
* @param {Object} connection Connection data.
*/
render( connection ) {
const calendars = connection.account_id ? app.Cache.getById( app.provider, 'calendars', connection.account_id ) : {};
const tmpl = app.Templates.get( `wpforms-${ app.provider }-builder-content-connection-calendar-field` );
const $connection = app.connection.getById( connection.id );
const $calendarFieldWrapper = $( app.selectors.calendarFieldWrapper, $connection );
$calendarFieldWrapper.html(
tmpl( {
calendars,
connection,
provider: app.provider,
} )
);
const $calendarField = $( app.selectors.calendarField, $connection );
$calendarField.trigger( 'change' );
},
/**
* Callback-function on change event.
*
* @since 1.0.0
*/
change() {
const $this = $( this );
const $connection = $this.closest( app.selectors.connection );
const $calendarFields = $( app.selectors.calendarFields, $connection );
const $calendarFieldDescription = $( app.selectors.calendarFieldDescription, $connection );
const isEmpty = $this.val() === '' || $this.val() === null;
$calendarFields.toggleClass( app.classes.hide, isEmpty );
$calendarFieldDescription.toggleClass( app.classes.hide, isEmpty );
if ( ! isEmpty ) {
app.ui.calendarField.updateDatetimePreview( $this );
}
},
/**
* Update datetime preview.
*
* @since 1.0.0
*
* @param {jQuery} $el Element.
*/
updateDatetimePreview( $el ) {
const $connection = $el.closest( app.selectors.connection );
const $calendarFieldDatetimePreview = $( app.selectors.calendarFieldDatetimePreview, $connection );
const timeZone = $el.find( 'option:selected' ).data( 'timezone' );
const now = new Date();
const timeString = now.toLocaleString( 'en', { timeZone, dateStyle: 'short', timeStyle: 'short' } );
$calendarFieldDatetimePreview.html( timeString );
},
},
/**
* Guests field methods.
*
* @since 1.0.0
*/
guestsField: {
/**
* Update value for choiceJS field.
*
* @since 1.0.0
*
* @param {Event} e Event.
* @param {jQuery} $select Select field.
*/
update( e, $select ) {
if ( ! $select.hasClass( app.classes.guestsField ) ) {
return;
}
let choicesObj = $select.data( 'choicesjs' );
if ( ! choicesObj ) {
return;
}
const choices = app.ui.guestsField.getSelectFieldChoices( $select );
const currentValues = choicesObj.getValue( true ).filter( ( value ) =>
choices.some( ( choice ) => choice.value === value )
);
// Workaround to display a placeholder when no activate values.
if ( currentValues.length === 0 ) {
choicesObj.destroy();
$select.removeData( 'choicesjs' );
app.ui.initChoicesJS( $select.closest( app.selectors.connection ) );
choicesObj = $select.data( 'choicesjs' );
}
// noinspection JSVoidFunctionReturnValueUsed
choicesObj
.clearChoices( true, true )
.removeActiveItems()
.setChoices( choices, 'value', 'label', true )
.setChoiceByValue( currentValues );
},
/**
* Get select field options and convert them to an array for ChoiceJS choices format.
*
* @since 1.0.0
*
* @param {jQuery} $select Select field.
*
* @return {Array} Choices.
*/
getSelectFieldChoices( $select ) {
const choices = [];
$select.find( 'option' ).each( function() {
const $option = $( this );
const value = $option.val();
if ( value === '' ) {
return;
}
choices.push(
{ value, label: $option.text() }
);
} );
return choices;
},
},
/**
* Duration field methods.
*
* @since 1.0.0
*/
durationField: {
/**
* Change field event.
*
* @since 1.0.0
*/
change() {
const val = $( this ).val();
const $connection = $( this ).closest( app.selectors.connection );
const $endDateTimeFieldWrapper = $( app.selectors.endDateTimeFieldWrapper, $connection );
const $endDateTimeField = $( app.selectors.endDateTimeField, $connection );
const isUserDefined = val === 'user_defined';
$endDateTimeFieldWrapper.toggleClass( app.classes.hide, ! isUserDefined );
$endDateTimeField.toggleClass( app.classes.required, isUserDefined );
},
},
/**
* Start/End Date/Time fields methods.
*
* @since 1.0.0
*/
dateTimeFields: {
/**
* Trigger Start/End Date/Time fields when a Date field format is changed.
*
* @since 1.0.0
*/
changeFormat() {
const fieldId = +$( this ).closest( app.selectors.optionRow ).data( 'field-id' );
$( `${ app.selectors.startDateTimeField }, ${ app.selectors.endDateTimeField }` ).each( function() {
const $this = $( this );
if ( +$this.val() === fieldId ) {
$this.trigger( 'change' );
}
} );
},
/**
* Change Start/End Date/Time fields.
*/
change() {
const $field = $( this );
const fieldId = $field.val();
const $fieldWrapper = $field.closest( app.selectors.fieldWrapper );
const fieldFormat = $( `#wpforms-field-option-${ fieldId }-format` ).val();
const $alert = $fieldWrapper.find( app.selectors.alert );
const $alertContent = $alert.find( app.selectors.alertContent );
$alert.removeClass( app.classes.warningAlert );
$alert.removeClass( app.classes.errorAlert );
if ( fieldId === '' || fieldFormat === 'date-time' ) {
$alert.addClass( app.classes.hide );
$alertContent.text( '' );
return;
}
if ( fieldFormat === 'date' ) {
$alert.addClass( app.classes.warningAlert );
$alertContent.text( wpforms_builder.google_calendar.date_format_warning );
$alert.removeClass( app.classes.hide );
return;
}
if ( fieldFormat === 'time' ) {
$alert.addClass( app.classes.errorAlert );
$alertContent.text( wpforms_builder.google_calendar.time_format_error );
$alert.removeClass( app.classes.hide );
}
},
},
/**
* Modify Event field methods.
*/
modifyEventField: {
/**
* Change field event.
*
* @since 1.0.0
*/
change() {
const $this = $( this );
const $connection = $this.closest( app.selectors.connection );
const $dependedFields = $( `${ app.selectors.seeGuestField }, ${ app.selectors.inviteOthersField }`, $connection );
const isChecked = $this.is( ':checked' );
$dependedFields.closest( app.selectors.panelField ).toggleClass( app.classes.disabled, isChecked );
if ( isChecked ) {
$dependedFields.prop( 'checked', isChecked );
}
},
},
/**
* Initialize Choices.js library.
*
* @since 1.0.0
*
* @param {Object} $connection jQuery connection selector.
*/
initChoicesJS( $connection ) {
// Load if the function exists.
if ( typeof window.Choices !== 'function' ) {
return;
}
const $choices = $( app.selectors.choiceJS, $connection );
$choices.each( function( index, element ) {
const $this = $( element );
if ( 'undefined' !== typeof $this.data( 'choicesjs' ) ) {
return;
}
$this.data( 'choicesjs', new Choices( $this[ 0 ], {
shouldSort: false,
removeItemButton: true,
fuseOptions:{
threshold: 0.1,
distance: 1000,
},
callbackOnInit() {
wpf.initMultipleSelectWithSearch( this );
wpf.showMoreButtonForChoices( this.containerOuter.element );
},
} ) );
} );
},
},
};
// Provide access to public functions/properties.
return app;
}( document, window, jQuery ) );
// Initialize.
WPForms.Admin.Builder.Providers.GoogleCalendar.init();