| 1 |
/** Add Booking administrator inspector interactions. */ |
| 2 |
( function ( $ ) { |
| 3 |
'use strict'; |
| 4 |
|
| 5 |
var active_days_selection_override = ''; |
| 6 |
var days_selection_enforcement_timers = []; |
| 7 |
|
| 8 |
/** |
| 9 |
* Show the panel controlled by one compact right-sidebar tab. |
| 10 |
* |
| 11 |
* @param {jQuery} $tab Selected tab button. |
| 12 |
* @return {void} |
| 13 |
*/ |
| 14 |
function switch_right_panel( $tab ) { |
| 15 |
var panel_id = $tab.attr( 'aria-controls' ); |
| 16 |
var $tabs = $tab.closest( '.wpbc_add_booking__rightbar_tabs' ).find( '[role="tab"]' ); |
| 17 |
var $panels = $( '.wpbc_add_booking__rightbar' ).find( '[role="tabpanel"]' ); |
| 18 |
|
| 19 |
if ( ! panel_id || ! $( '#' + panel_id ).length ) { |
| 20 |
return; |
| 21 |
} |
| 22 |
|
| 23 |
$tabs.attr( 'aria-selected', 'false' ); |
| 24 |
$tab.attr( 'aria-selected', 'true' ); |
| 25 |
$panels.attr( { hidden: true, 'aria-hidden': 'true' } ); |
| 26 |
$( '#' + panel_id ).removeAttr( 'hidden' ).attr( 'aria-hidden', 'false' ); |
| 27 |
} |
| 28 |
|
| 29 |
/** |
| 30 |
* Expand an inspector group and focus one of its controls. |
| 31 |
* |
| 32 |
* The shared collapsible controller is used when available so exclusive |
| 33 |
* groups and ARIA state stay synchronized. The header click is a safe |
| 34 |
* fallback for older Booking Calendar admin bundles. |
| 35 |
* |
| 36 |
* @param {string} group_name Group data key. |
| 37 |
* @param {string} focus_selector Optional control to focus after expansion. |
| 38 |
* @return {void} |
| 39 |
*/ |
| 40 |
function open_settings_group( group_name, focus_selector ) { |
| 41 |
var $group = $( '.wpbc_add_booking__inspector_settings .wpbc_ui__collapsible_group[data-group="' + group_name + '"]' ).first(); |
| 42 |
var root; |
| 43 |
var controller; |
| 44 |
|
| 45 |
if ( ! $group.length ) { |
| 46 |
return; |
| 47 |
} |
| 48 |
|
| 49 |
root = $group.closest( '.wpbc_collapsible' ).get( 0 ); |
| 50 |
controller = root && root.__wpbc_collapsible_instance ? root.__wpbc_collapsible_instance : null; |
| 51 |
|
| 52 |
if ( controller && 'function' === typeof controller.expand ) { |
| 53 |
controller.expand( $group.get( 0 ) ); |
| 54 |
} else { |
| 55 |
$group.siblings( '.wpbc_ui__collapsible_group' ).each( function () { |
| 56 |
var $sibling = $( this ); |
| 57 |
|
| 58 |
$sibling.removeClass( 'is-open' ); |
| 59 |
$sibling.children( '.group__header' ).attr( 'aria-expanded', 'false' ); |
| 60 |
$sibling.children( '.group__fields' ).attr( { hidden: true, 'aria-hidden': 'true' } ); |
| 61 |
} ); |
| 62 |
$group.addClass( 'is-open' ); |
| 63 |
$group.children( '.group__header' ).attr( 'aria-expanded', 'true' ); |
| 64 |
$group.children( '.group__fields' ).removeAttr( 'hidden' ).attr( 'aria-hidden', 'false' ); |
| 65 |
} |
| 66 |
|
| 67 |
window.requestAnimationFrame( function () { |
| 68 |
$group.get( 0 ).scrollIntoView( { behavior: 'smooth', block: 'start' } ); |
| 69 |
if ( focus_selector ) { |
| 70 |
window.setTimeout( function () { |
| 71 |
$( focus_selector ).first().trigger( 'focus' ); |
| 72 |
}, 250 ); |
| 73 |
} |
| 74 |
} ); |
| 75 |
} |
| 76 |
|
| 77 |
/** |
| 78 |
* Synchronize Booking Tools status links with their current toggle values. |
| 79 |
* |
| 80 |
* @return {void} |
| 81 |
*/ |
| 82 |
function refresh_booking_tools_summary() { |
| 83 |
var $summary = $( '.wpbc_add_booking__setup_summary' ).first(); |
| 84 |
var enabled_label; |
| 85 |
var disabled_label; |
| 86 |
|
| 87 |
if ( ! $summary.length ) { |
| 88 |
return; |
| 89 |
} |
| 90 |
|
| 91 |
enabled_label = $summary.attr( 'data-wpbc-add-booking-enabled-label' ) || 'Enabled'; |
| 92 |
disabled_label = $summary.attr( 'data-wpbc-add-booking-disabled-label' ) || 'Disabled'; |
| 93 |
|
| 94 |
$summary.find( '[data-wpbc-add-booking-summary="emails"]' ).text( |
| 95 |
$( '#is_send_email_for_pending' ).is( ':checked' ) ? enabled_label : disabled_label |
| 96 |
); |
| 97 |
$summary.find( '[data-wpbc-add-booking-summary="allow-past"]' ).text( |
| 98 |
$( '#is_allow_bookings_in_past' ).is( ':checked' ) ? enabled_label : disabled_label |
| 99 |
); |
| 100 |
} |
| 101 |
|
| 102 |
/** |
| 103 |
* Get the Add Booking date-selection radio container. |
| 104 |
* |
| 105 |
* @return {jQuery} Date-selection container, when rendered. |
| 106 |
*/ |
| 107 |
function get_days_selection_container() { |
| 108 |
return $( '[data-wpbc-add-booking-days-selection="1"]' ).first(); |
| 109 |
} |
| 110 |
|
| 111 |
/** |
| 112 |
* Normalize calendar engine modes to the three administrator radio choices. |
| 113 |
* |
| 114 |
* @param {string} mode Calendar or radio mode. |
| 115 |
* @return {string} single, multiple, range, or an empty string. |
| 116 |
*/ |
| 117 |
function normalize_days_selection_mode( mode ) { |
| 118 |
mode = String( mode || '' ); |
| 119 |
|
| 120 |
if ( 'fixed' === mode || 'dynamic' === mode || 'range' === mode ) { |
| 121 |
return 'range'; |
| 122 |
} |
| 123 |
|
| 124 |
return 'single' === mode || 'multiple' === mode ? mode : ''; |
| 125 |
} |
| 126 |
|
| 127 |
/** |
| 128 |
* Get the current Add Booking resource ID from the radio control context. |
| 129 |
* |
| 130 |
* @return {number} Positive resource ID, or zero when unavailable. |
| 131 |
*/ |
| 132 |
function get_days_selection_resource_id() { |
| 133 |
var resource_id = parseInt( get_days_selection_container().attr( 'data-wpbc-resource-id' ), 10 ); |
| 134 |
|
| 135 |
if ( ! resource_id && window.wpbc_add_booking_component_context ) { |
| 136 |
resource_id = parseInt( window.wpbc_add_booking_component_context.resource_id, 10 ); |
| 137 |
} |
| 138 |
|
| 139 |
return resource_id > 0 ? resource_id : 0; |
| 140 |
} |
| 141 |
|
| 142 |
/** |
| 143 |
* Read one calendar parameter without exposing page state globally. |
| 144 |
* |
| 145 |
* @param {number} resource_id Booking resource ID. |
| 146 |
* @param {string} parameter_name Calendar parameter name. |
| 147 |
* @return {*} Current parameter value, or null when the calendar is unavailable. |
| 148 |
*/ |
| 149 |
function get_calendar_parameter( resource_id, parameter_name ) { |
| 150 |
if ( ! window._wpbc || ! window._wpbc.calendar || 'function' !== typeof window._wpbc.calendar__get_param_value ) { |
| 151 |
return null; |
| 152 |
} |
| 153 |
|
| 154 |
return window._wpbc.calendar__get_param_value( resource_id, parameter_name ); |
| 155 |
} |
| 156 |
|
| 157 |
/** |
| 158 |
* Normalize an array or comma-separated calendar parameter into integers. |
| 159 |
* |
| 160 |
* @param {Array|number|string} parameter_value Calendar parameter value. |
| 161 |
* @param {Array} fallback_value Value returned when no valid numbers exist. |
| 162 |
* @return {Array} Integer values. |
| 163 |
*/ |
| 164 |
function normalize_calendar_number_list( parameter_value, fallback_value ) { |
| 165 |
var values = Array.isArray( parameter_value ) ? parameter_value : String( parameter_value || '' ).split( ',' ); |
| 166 |
var normalized_values = []; |
| 167 |
|
| 168 |
$.each( values, function ( index, number_value ) { |
| 169 |
var parsed_number = parseInt( number_value, 10 ); |
| 170 |
|
| 171 |
if ( ! isNaN( parsed_number ) ) { |
| 172 |
normalized_values.push( parsed_number ); |
| 173 |
} |
| 174 |
} ); |
| 175 |
|
| 176 |
return normalized_values.length ? normalized_values : fallback_value; |
| 177 |
} |
| 178 |
|
| 179 |
/** |
| 180 |
* Get the fixed/dynamic engine to use for the Range days radio choice. |
| 181 |
* |
| 182 |
* @return {string} fixed or dynamic. |
| 183 |
*/ |
| 184 |
function get_range_engine_mode() { |
| 185 |
var range_engine_mode = String( get_days_selection_container().attr( 'data-wpbc-range-engine-mode' ) || 'dynamic' ); |
| 186 |
|
| 187 |
return 'fixed' === range_engine_mode ? 'fixed' : 'dynamic'; |
| 188 |
} |
| 189 |
|
| 190 |
/** |
| 191 |
* Update the checked radio and visible setup summary from an effective mode. |
| 192 |
* |
| 193 |
* @param {string} mode Calendar or radio mode. |
| 194 |
* @return {void} |
| 195 |
*/ |
| 196 |
function sync_days_selection_controls( mode ) { |
| 197 |
var normalized_mode = normalize_days_selection_mode( mode ); |
| 198 |
var $container = get_days_selection_container(); |
| 199 |
var $radio; |
| 200 |
var mode_label; |
| 201 |
|
| 202 |
if ( ! normalized_mode || ! $container.length ) { |
| 203 |
return; |
| 204 |
} |
| 205 |
|
| 206 |
$radio = $container.find( 'input[name="wpbc_add_booking_days_selection_mode"][value="' + normalized_mode + '"]' ); |
| 207 |
if ( 1 !== $radio.length ) { |
| 208 |
return; |
| 209 |
} |
| 210 |
|
| 211 |
$radio.prop( 'checked', true ); |
| 212 |
mode_label = $radio.attr( 'data-wpbc-days-selection-label' ) || normalized_mode; |
| 213 |
$( '[data-wpbc-add-booking-summary="days-selection"]' ).text( mode_label ); |
| 214 |
} |
| 215 |
|
| 216 |
/** |
| 217 |
* Clear selected dates before changing their selection semantics. |
| 218 |
* |
| 219 |
* Customer fields remain untouched. Existing calendar helpers are used when |
| 220 |
* available so Datepick state and the hidden selected-date field stay aligned. |
| 221 |
* |
| 222 |
* @param {number} resource_id Booking resource ID. |
| 223 |
* @return {void} |
| 224 |
*/ |
| 225 |
function clear_selected_booking_dates( resource_id ) { |
| 226 |
var $date_field = $( '#date_booking' + resource_id ); |
| 227 |
|
| 228 |
if ( ! $date_field.length || ! String( $date_field.val() || '' ).trim() ) { |
| 229 |
return; |
| 230 |
} |
| 231 |
|
| 232 |
if ( 'function' === typeof window.wpbc_calendar__unselect_all_dates ) { |
| 233 |
window.wpbc_calendar__unselect_all_dates( resource_id ); |
| 234 |
} else { |
| 235 |
$date_field.val( '' ); |
| 236 |
} |
| 237 |
|
| 238 |
if ( 'function' === typeof window.wpbc_disable_time_fields_in_booking_form ) { |
| 239 |
window.wpbc_disable_time_fields_in_booking_form( resource_id ); |
| 240 |
} |
| 241 |
} |
| 242 |
|
| 243 |
/** |
| 244 |
* Apply one radio mode through the shared immediate calendar helpers. |
| 245 |
* |
| 246 |
* Range mode retains the configured fixed/dynamic subtype and its existing |
| 247 |
* number-of-days and weekday parameters. No settings are saved. |
| 248 |
* |
| 249 |
* @param {string} mode Requested radio mode. |
| 250 |
* @param {boolean} clear_dates Whether an existing selection must be cleared. |
| 251 |
* @return {boolean} True when the requested helper was applied or already active. |
| 252 |
*/ |
| 253 |
function apply_days_selection_mode( mode, clear_dates ) { |
| 254 |
var normalized_mode = normalize_days_selection_mode( mode ); |
| 255 |
var resource_id = get_days_selection_resource_id(); |
| 256 |
var desired_engine_mode = 'range' === normalized_mode ? get_range_engine_mode() : normalized_mode; |
| 257 |
var current_engine_mode; |
| 258 |
var fixed_days_number; |
| 259 |
|
| 260 |
if ( ! normalized_mode || ! resource_id ) { |
| 261 |
return false; |
| 262 |
} |
| 263 |
|
| 264 |
current_engine_mode = String( get_calendar_parameter( resource_id, 'days_select_mode' ) || '' ); |
| 265 |
if ( desired_engine_mode === current_engine_mode ) { |
| 266 |
sync_days_selection_controls( normalized_mode ); |
| 267 |
return true; |
| 268 |
} |
| 269 |
|
| 270 |
if ( clear_dates ) { |
| 271 |
clear_selected_booking_dates( resource_id ); |
| 272 |
} |
| 273 |
|
| 274 |
if ( 'single' === normalized_mode && 'function' === typeof window.wpbc_cal_days_select__single ) { |
| 275 |
window.wpbc_cal_days_select__single( resource_id ); |
| 276 |
} else if ( 'multiple' === normalized_mode && 'function' === typeof window.wpbc_cal_days_select__multiple ) { |
| 277 |
window.wpbc_cal_days_select__multiple( resource_id ); |
| 278 |
} else if ( 'range' === normalized_mode && 'fixed' === desired_engine_mode && 'function' === typeof window.wpbc_cal_days_select__fixed ) { |
| 279 |
fixed_days_number = parseInt( get_calendar_parameter( resource_id, 'fixed__days_num' ), 10 ); |
| 280 |
window.wpbc_cal_days_select__fixed( |
| 281 |
resource_id, |
| 282 |
fixed_days_number > 0 ? fixed_days_number : 3, |
| 283 |
normalize_calendar_number_list( get_calendar_parameter( resource_id, 'fixed__week_days__start' ), [ -1 ] ) |
| 284 |
); |
| 285 |
} else if ( 'range' === normalized_mode && 'function' === typeof window.wpbc_cal_days_select__range_mode ) { |
| 286 |
window.wpbc_cal_days_select__range_mode( resource_id ); |
| 287 |
} else { |
| 288 |
return false; |
| 289 |
} |
| 290 |
|
| 291 |
sync_days_selection_controls( normalized_mode ); |
| 292 |
return true; |
| 293 |
} |
| 294 |
|
| 295 |
/** |
| 296 |
* Synchronize controls with the calendar, or enforce a user-selected override. |
| 297 |
* |
| 298 |
* @return {void} |
| 299 |
*/ |
| 300 |
function synchronize_days_selection_mode() { |
| 301 |
var resource_id = get_days_selection_resource_id(); |
| 302 |
var current_engine_mode; |
| 303 |
|
| 304 |
if ( ! resource_id ) { |
| 305 |
return; |
| 306 |
} |
| 307 |
|
| 308 |
if ( active_days_selection_override ) { |
| 309 |
apply_days_selection_mode( active_days_selection_override, false ); |
| 310 |
return; |
| 311 |
} |
| 312 |
|
| 313 |
current_engine_mode = get_calendar_parameter( resource_id, 'days_select_mode' ); |
| 314 |
sync_days_selection_controls( current_engine_mode ); |
| 315 |
} |
| 316 |
|
| 317 |
/** |
| 318 |
* Recheck mode after the selected Booking Form's legacy delayed initializer. |
| 319 |
* |
| 320 |
* Existing timers are cancelled so repeated clicks cannot accumulate work. |
| 321 |
* Reapplication occurs only when the calendar engine mode was changed by a |
| 322 |
* later initializer, avoiding unnecessary calendar renders. |
| 323 |
* |
| 324 |
* @return {void} |
| 325 |
*/ |
| 326 |
function schedule_days_selection_synchronization() { |
| 327 |
$.each( days_selection_enforcement_timers, function ( index, timer_id ) { |
| 328 |
window.clearTimeout( timer_id ); |
| 329 |
} ); |
| 330 |
days_selection_enforcement_timers = []; |
| 331 |
|
| 332 |
$.each( [ 0, 120, 1150, 2200 ], function ( index, delay ) { |
| 333 |
days_selection_enforcement_timers.push( window.setTimeout( synchronize_days_selection_mode, delay ) ); |
| 334 |
} ); |
| 335 |
} |
| 336 |
|
| 337 |
$( document ).on( 'click', '.wpbc_add_booking__rightbar_tabs [role="tab"]', function () { |
| 338 |
switch_right_panel( $( this ) ); |
| 339 |
} ); |
| 340 |
|
| 341 |
$( document ).on( 'click', '.wpbc_add_booking__setup_summary_link', function ( event ) { |
| 342 |
event.preventDefault(); |
| 343 |
open_settings_group( $( this ).attr( 'data-wpbc-add-booking-open-group' ), $( this ).attr( 'data-wpbc-add-booking-focus' ) || '' ); |
| 344 |
} ); |
| 345 |
|
| 346 |
$( document ).on( 'change', '#is_send_email_for_pending, #is_allow_bookings_in_past', refresh_booking_tools_summary ); |
| 347 |
$( document ).on( 'click', 'input[name="wpbc_add_booking_days_selection_mode"]', function () { |
| 348 |
active_days_selection_override = normalize_days_selection_mode( $( this ).val() ); |
| 349 |
apply_days_selection_mode( active_days_selection_override, true ); |
| 350 |
schedule_days_selection_synchronization(); |
| 351 |
} ); |
| 352 |
$( refresh_booking_tools_summary ); |
| 353 |
$( schedule_days_selection_synchronization ); |
| 354 |
}( jQuery ) ); |
| 355 |
|