PluginProbe
Booking Calendar / 11.7
Booking Calendar v11.7
11.9 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 All 205 releases
booking / includes / page-add-appointment / _src / add_appointment_page.js

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

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