PluginProbe
Booking Calendar / 11.8.4
Booking Calendar v11.8.4
11.8.4 11.8.3 11.8.2 11.8.1 11.8 11.7 11.6.1 11.6 11.5 11.4.3 11.4.2 11.4.1 11.4 11.3 11.2.1 11.2 11.1 11.0 10.15.7 10.15.6 10.1.3 10.10 10.10.1 10.10.2 10.11 All 204 releases
booking / includes / page-add-appointment / _src / add_appointment_page.js

add_appointment_page.js in Booking Calendar 11.8.4, at includes/page-add-appointment/_src/add_appointment_page.js

343 lines 13.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /** Add Appointment administrator inspector and non-submitting helper tools. */
2 ( function ( $ ) {
3 'use strict';
4
5 var summary_timer = 0;
6 var labels = window.wpbc_add_appointment_page_config || {};
7
8 /** Apply the administrator page context before any Provider calendar loads. */
9 function apply_booking_context() {
10 if ( 'undefined' === typeof window._wpbc ) {
11 return;
12 }
13
14 window._wpbc.set_other_param( 'this_page_booking_hash', '' );
15 window._wpbc.set_other_param( 'this_page_allow_past', labels.allowPast ? 1 : 0 );
16 window._wpbc.set_other_param( 'this_page_allow_past_arr', labels.allowPastDateArr || [] );
17 window._wpbc.set_other_param( 'this_page_admin_booking_nonce', String( labels.adminBookingNonce || '' ) );
18 }
19
20 /** Switch one shared right-sidebar panel. */
21 function switch_right_panel( $tab ) {
22 var panel_id = $tab.attr( 'aria-controls' );
23 var $tabs = $tab.closest( '.wpbc_add_appointment__rightbar_tabs' ).find( '[role="tab"]' );
24 var $panels = $( '.wpbc_add_appointment__rightbar' ).find( '[role="tabpanel"]' );
25
26 $tabs.attr( 'aria-selected', 'false' );
27 $tab.attr( 'aria-selected', 'true' );
28 $panels.attr( { hidden: true, 'aria-hidden': 'true' } );
29 $( '#' + panel_id ).removeAttr( 'hidden' ).attr( 'aria-hidden', 'false' );
30 }
31
32 /** Return trimmed visible text from the first matching element. */
33 function get_text( $root, selector ) {
34 return String( $root.find( selector ).first().text() || '' ).replace( /\s+/g, ' ' ).trim();
35 }
36
37 /** Return the display value of one native Booking Form field. */
38 function get_field_value( $field ) {
39 var value;
40
41 if ( ! $field.length ) {
42 return '';
43 }
44 if ( $field.is( 'select' ) ) {
45 value = $field.find( 'option:selected' ).text();
46 } else {
47 value = $field.val();
48 }
49
50 return String( value || '' ).replace( /\s+/g, ' ' ).trim();
51 }
52
53 /** Update one summary value and its empty-state presentation. */
54 function set_summary_value( key, value ) {
55 var $target = $( '[data-wpbc-add-appointment-summary="' + key + '"]' );
56 var is_empty = '' === String( value || '' ).trim();
57
58 $target.text( is_empty ? ( labels.emptyLabel || 'Not selected' ) : value );
59 $target.toggleClass( 'is-empty', is_empty );
60 }
61
62 /**
63 * Expand one Settings inspector group through the shared collapsible API.
64 *
65 * @param {string} group_name Group data key.
66 * @param {string} focus_selector Optional control to focus after expansion.
67 * @return {void}
68 */
69 function open_inspector_group( group_name, focus_selector ) {
70 var $group = $( '.wpbc_add_appointment__inspector_overview .wpbc_ui__collapsible_group[data-group="' + group_name + '"]' ).first();
71 var root;
72 var api;
73
74 if ( ! $group.length ) {
75 return;
76 }
77
78 root = $group.closest( '.wpbc_collapsible' ).get( 0 );
79 api = root && root.__wpbc_collapsible_instance;
80
81 if ( api && 'function' === typeof api.expand ) {
82 api.expand( $group.get( 0 ) );
83 } else {
84 $group.siblings( '.wpbc_ui__collapsible_group' ).each( function () {
85 var $sibling = $( this );
86 $sibling.removeClass( 'is-open' );
87 $sibling.children( '.group__header' ).attr( 'aria-expanded', 'false' );
88 $sibling.children( '.group__fields' ).attr( { hidden: true, 'aria-hidden': 'true' } );
89 } );
90 $group.addClass( 'is-open' );
91 $group.children( '.group__header' ).attr( 'aria-expanded', 'true' );
92 $group.children( '.group__fields' ).removeAttr( 'hidden' ).attr( 'aria-hidden', 'false' );
93 }
94
95 window.requestAnimationFrame( function () {
96 $group.get( 0 ).scrollIntoView( { behavior: 'smooth', block: 'start' } );
97 if ( focus_selector ) {
98 window.setTimeout( function () {
99 $( focus_selector ).first().trigger( 'focus' );
100 }, 250 );
101 }
102 } );
103 }
104
105 /** Format the effective before/after Service buffers. */
106 function get_buffer_summary( $source ) {
107 var before = $source.attr( 'data-buffer-before' );
108 var after = $source.attr( 'data-buffer-after' );
109
110 if ( 'undefined' === typeof before ) {
111 before = $source.attr( 'data-summary-buffer-before' );
112 after = $source.attr( 'data-summary-buffer-after' );
113 }
114 if ( 'undefined' === typeof before || 'undefined' === typeof after ) {
115 return '';
116 }
117
118 return String( before ) + ' / ' + String( after ) + ' ' + ( labels.minutesLabel || 'min' );
119 }
120
121 /** Read the most useful customer identity from the loaded Booking Form. */
122 function get_customer_summary( $form ) {
123 var first = get_field_value( $form.find( '[name^="firstname"]' ).first() );
124 var last = get_field_value( $form.find( '[name^="secondname"], [name^="lastname"]' ).first() );
125 var email = get_field_value( $form.find( '[name^="email"]' ).first() );
126 var name = String( first + ' ' + last ).trim();
127
128 return name || email;
129 }
130
131 /** Mirror live Appointment and native Booking Form values into the inspector. */
132 function refresh_summary() {
133 var $root = $( '.wpbc_add_appointment__canvas .wpbc_booking_appointment' ).first();
134 var $native = $root.find( '.wpbc_booking_appointment__native_form' ).first();
135 var $form = $native.find( 'form' ).first();
136 var $service = $root.find( 'input[name="wpbc_appointment_service"]:checked' ).closest( '.wpbc_booking_appointment__choice' );
137 var $provider = $root.find( 'input[name="wpbc_appointment_provider"]:checked' ).closest( '.wpbc_booking_appointment__choice' );
138 var $selected = $root.find( '.wpbc_booking_appointment__selected' ).first();
139 var $detail_source = $provider.length ? $provider : ( $service.length ? $service : $selected );
140 var stage = $root.attr( 'data-appointment-stage' ) || 'service';
141 var step = get_text( $root, '.wpbc_booking_appointment__progress_step.is-active .wpbc_booking_appointment__progress_label' );
142 var service = $service.attr( 'data-summary-service' ) || $selected.attr( 'data-summary-service' ) || '';
143 var provider = $provider.attr( 'data-summary-provider' ) || '';
144 var duration = $detail_source.attr( 'data-summary-duration' ) || '';
145 var price = $detail_source.attr( 'data-summary-price' ) || '';
146
147 if ( $native.length ) {
148 service = $native.attr( 'data-service-title' ) || service;
149 provider = $native.attr( 'data-provider-title' ) || provider;
150 duration = $native.attr( 'data-duration-label' ) || duration;
151 price = $native.attr( 'data-service-cost-label' ) || price;
152 $detail_source = $native;
153 }
154
155 set_summary_value( 'step', step || stage );
156 set_summary_value( 'service', service );
157 set_summary_value( 'provider', provider );
158 set_summary_value( 'duration', duration );
159 set_summary_value( 'buffers', get_buffer_summary( $detail_source ) );
160 set_summary_value( 'price', price );
161 set_summary_value( 'date', get_field_value( $form.find( '[id^="date_booking"]' ).first() ) );
162 set_summary_value( 'time', get_field_value( $form.find( '[name^="starttime"]' ).first() ) );
163 set_summary_value( 'customer', get_customer_summary( $form ) );
164 set_summary_value( 'form', $native.attr( 'data-form-slug' ) || '' );
165 set_summary_value( 'emails', $( '#is_send_email_for_pending' ).is( ':checked' ) ? ( labels.enabledLabel || 'Enabled' ) : ( labels.disabledLabel || 'Disabled' ) );
166 $( '[data-wpbc-add-appointment-start-over]' ).prop( 'hidden', 'service' === stage );
167 $( '[data-wpbc-add-appointment-autofill]' ).prop( 'disabled', ! $form.length );
168 }
169
170 /** Debounce summary work during large AJAX Booking Form DOM insertions. */
171 function schedule_summary_refresh() {
172 window.clearTimeout( summary_timer );
173 summary_timer = window.setTimeout( refresh_summary, 20 );
174 }
175
176 /** Fill sample customer fields in the current form without submitting it. */
177 function auto_fill_booking_form() {
178 var $form = $( '.wpbc_add_appointment__canvas .wpbc_booking_appointment__native_form form' ).first();
179
180 if ( ! $form.length ) {
181 return;
182 }
183
184 $form.find( 'input, textarea, select' ).each( function () {
185 var $field = $( this );
186 var name = String( $field.attr( 'name' ) || '' ).toLowerCase();
187 var type = String( $field.attr( 'type' ) || '' ).toLowerCase();
188 var ignored = /date_booking|starttime|endtime|durationtime|rangetime|captcha|coupon|service_id|appointment_/.test( name );
189 var value = '';
190
191 if ( ignored || $field.is( ':disabled' ) || 'hidden' === type || 'button' === type || 'submit' === type || 'radio' === type ) {
192 return;
193 }
194 if ( 'checkbox' === type ) {
195 if ( $field.prop( 'required' ) ) {
196 $field.prop( 'checked', true ).trigger( 'change' );
197 }
198 return;
199 }
200 if ( $field.is( 'select' ) ) {
201 if ( ! $field.val() ) {
202 var $option = $field.find( 'option:not(:disabled)' ).filter( function () {
203 return '' !== String( $( this ).val() || '' );
204 } ).first();
205 if ( $option.length ) {
206 $field.val( $option.val() ).trigger( 'change' );
207 }
208 }
209 return;
210 }
211 if ( /^firstname/.test( name ) ) {
212 value = 'John';
213 } else if ( /^(secondname|lastname)/.test( name ) ) {
214 value = 'Smith';
215 } else if ( /^email/.test( name ) || 'email' === type ) {
216 value = '[email protected]';
217 } else if ( /^phone/.test( name ) || 'tel' === type ) {
218 value = '0000000000';
219 } else if ( $field.is( 'textarea' ) ) {
220 value = '---';
221 }
222
223 if ( value && ! $field.val() ) {
224 $field.val( value ).trigger( 'input' ).trigger( 'change' );
225 }
226 } );
227
228 schedule_summary_refresh();
229 }
230
231 /** Copy the authoritative Appointment correction into its convenience slider. */
232 function synchronize_cost_correction_range() {
233 var $number_field = $( '#wpbc_add_appointment_cost_correction' );
234 var $range = $( '[data-wpbc-admin-cost-correction-range="1"]' ).first();
235 var number_value;
236
237 if ( ! $number_field.length || ! $range.length ) {
238 return;
239 }
240
241 number_value = Number( $number_field.val() );
242 if ( '' === String( $number_field.val() || '' ).trim() || ! isFinite( number_value ) ) {
243 $range.val( '0' );
244 return;
245 }
246
247 $range.val( String( number_value ) );
248 }
249
250 /** Copy the convenience slider into the authoritative Appointment number input. */
251 function synchronize_cost_correction_number() {
252 var $number_field = $( '#wpbc_add_appointment_cost_correction' );
253 var $range = $( '[data-wpbc-admin-cost-correction-range="1"]' ).first();
254
255 if ( ! $number_field.length || ! $range.length ) {
256 return;
257 }
258
259 $number_field.val( $range.val() ).trigger( 'input' );
260 }
261
262 /** Clear an unsaved or successfully submitted Appointment correction draft. */
263 function clear_cost_correction() {
264 var $number_field = $( '#wpbc_add_appointment_cost_correction' );
265
266 if ( ! $number_field.length ) {
267 return;
268 }
269
270 $number_field.val( '' );
271 synchronize_cost_correction_range();
272 }
273
274 /**
275 * Add an explicitly entered correction to the active Appointment request.
276 *
277 * The inspector is outside the AJAX-inserted native Booking Form. Requiring
278 * the submitted Provider to match the active Appointment form prevents this
279 * page-only draft from affecting an unrelated booking-create event.
280 *
281 * @param {Event} event jQuery event.
282 * @param {number} resource_id Submitted Provider resource ID.
283 * @param {Object} params Mutable booking-create request parameters.
284 * @return {void}
285 */
286 function add_cost_correction_to_appointment_request( event, resource_id, params ) {
287 var number_field = document.getElementById( 'wpbc_add_appointment_cost_correction' );
288 var $native_form = $( '.wpbc_add_appointment__canvas .wpbc_booking_appointment__native_form[data-provider-id="' + Number( resource_id || 0 ) + '"]' ).first();
289 var raw_cost;
290
291 if ( ! params || ! number_field || ! $native_form.length ) {
292 return;
293 }
294
295 delete params.wpbc_admin_cost_correction;
296 raw_cost = String( number_field.value || '' ).trim();
297 if ( '' === raw_cost || ! number_field.checkValidity() ) {
298 return;
299 }
300
301 params.wpbc_admin_cost_correction = raw_cost;
302 }
303
304 $( document ).on( 'click', '.wpbc_add_appointment__rightbar_tabs [role="tab"]', function ( event ) {
305 event.preventDefault();
306 switch_right_panel( $( this ) );
307 } );
308
309 $( document ).on( 'click', '[data-wpbc-add-appointment-start-over]', function () {
310 clear_cost_correction();
311 var $action = $( '.wpbc_add_appointment__canvas [data-wpbc-appointment-action="start-over"]' ).first();
312 if ( ! $action.length ) {
313 $action = $( '.wpbc_add_appointment__canvas [data-appointment-back="service"]' ).first();
314 }
315 if ( $action.length ) {
316 $action.trigger( 'click' );
317 }
318 } );
319 $( document ).on( 'click', '.wpbc_add_appointment__canvas [data-wpbc-appointment-action="start-over"]', clear_cost_correction );
320
321 $( document ).on( 'click', '[data-wpbc-add-appointment-open-group]', function ( event ) {
322 event.preventDefault();
323 open_inspector_group( $( this ).attr( 'data-wpbc-add-appointment-open-group' ), $( this ).attr( 'data-wpbc-add-appointment-focus' ) || '' );
324 } );
325
326 $( document ).on( 'click', '[data-wpbc-add-appointment-autofill]', auto_fill_booking_form );
327 $( document ).on( 'input change wpbc_booking_date_or_option_selected', '.wpbc_add_appointment__canvas, #is_send_email_for_pending', schedule_summary_refresh );
328 $( document ).on( 'input', '#wpbc_add_appointment_cost_correction', synchronize_cost_correction_range );
329 $( document ).on( 'input', '.wpbc_add_appointment__cost_correction [data-wpbc-admin-cost-correction-range="1"]', synchronize_cost_correction_number );
330 $( 'body' ).on( 'wpbc_before_booking_create.wpbc_add_appointment_cost_correction', add_cost_correction_to_appointment_request );
331 $( 'body' ).on( 'wpbc_booking_form_submit_success.wpbc_add_appointment_cost_correction', clear_cost_correction );
332
333 $( function () {
334 var stage = document.querySelector( '.wpbc_add_appointment__canvas .wpbc_booking_appointment__stage' );
335 apply_booking_context();
336 refresh_summary();
337 synchronize_cost_correction_range();
338 if ( stage && window.MutationObserver ) {
339 new MutationObserver( schedule_summary_refresh ).observe( stage, { childList: true, subtree: true } );
340 }
341 } );
342 } )( jQuery );
343