(function($) { window.bookly = function(Options) { var $container = $('#bookly-form-' + Options.form_id), timeZone = typeof Intl === 'object' ? Intl.DateTimeFormat().resolvedOptions().timeZone : undefined, timeZoneOffset = new Date().getTimezoneOffset(); Options.skip_steps.service = Options.skip_steps.service_part1 && Options.skip_steps.service_part2; // initialize if (Options.status.booking == 'finished') { stepComplete(); } else if (Options.status.booking == 'cancelled') { stepPayment(); } else { stepService({new_chain : true}); } /** * Service step. */ function stepService(params) { if (Options.skip_steps.service) { if (!Options.skip_steps.extras) { stepExtras(params) } else { stepTime(params); } return; } var data = $.extend({ action : 'bookly_render_service', csrf_token : BooklyL10n.csrf_token, form_id : Options.form_id, time_zone : timeZone, time_zone_offset : timeZoneOffset }, params); $.ajax({ url : Options.ajaxurl, data : data, dataType : 'json', xhrFields : { withCredentials: true }, crossDomain : 'withCredentials' in new XMLHttpRequest(), success : function (response) { if (response.success) { BooklyL10n.csrf_token = response.csrf_token; $container.html(response.html); if (params === undefined) { // Scroll when returning to the step Service. default value {new_chain : true} scrollTo($container); } var $chain_item_draft = $('.bookly-js-chain-item.bookly-js-draft', $container), $select_location = $('.bookly-js-select-location', $container), $select_category = $('.bookly-js-select-category', $container), $select_service = $('.bookly-js-select-service', $container), $select_employee = $('.bookly-js-select-employee', $container), $select_nop = $('.bookly-js-select-number-of-persons', $container), $select_quantity = $('.bookly-js-select-quantity', $container), $date_from = $('.bookly-js-date-from', $container), $week_day = $('.bookly-js-week-day', $container), $select_time_from = $('.bookly-js-select-time-from', $container), $select_time_to = $('.bookly-js-select-time-to', $container), $next_step = $('.bookly-js-next-step', $container), $mobile_next_step = $('.bookly-js-mobile-next-step', $container), $mobile_prev_step = $('.bookly-js-mobile-prev-step', $container), locations = response.locations, categories = response.categories, services = response.services, staff = response.staff, chain = response.chain, last_chain_key = 0, category_selected = false ; // Init Pickadate. $date_from.pickadate({ formatSubmit : 'yyyy-mm-dd', format : Options.date_format, min : response.date_min || true, max : response.date_max || true, clear : false, close : false, today : BooklyL10n.today, monthsFull : BooklyL10n.months, weekdaysFull : BooklyL10n.days, weekdaysShort : BooklyL10n.daysShort, labelMonthNext : BooklyL10n.nextMonth, labelMonthPrev : BooklyL10n.prevMonth, firstDay : Options.start_of_week, onSet : function(timestamp) { if ($.isNumeric(timestamp.select)) { // Checks appropriate day of the week var date = new Date(timestamp.select); $('.bookly-js-week-day[value="' + (date.getDay() + 1) + '"]:not(:checked)', $container).attr('checked', true).trigger('change'); } } }); $('.bookly-js-go-to-cart', $container).on('click', function (e) { e.preventDefault(); ladda_start(this); stepCart({from_step : 'service'}); }); // insert data into select function setSelect($select, data, value) { // reset select $('option:not([value=""])', $select).remove(); // and fill the new data var docFragment = document.createDocumentFragment(); function valuesToArray(obj) { return Object.keys(obj).map(function (key) { return obj[key]; }); } function compare(a, b) { if (parseInt(a.pos) < parseInt(b.pos)) return -1; if (parseInt(a.pos) > parseInt(b.pos)) return 1; return 0; } // sort select by position data = valuesToArray(data).sort(compare); $.each(data, function(key, object) { var option = document.createElement('option'); option.value = object.id; option.text = object.name; docFragment.appendChild(option); }); $select.append(docFragment); // set default value of select $select.val(value); } function setSelects($chain_item, location_id, category_id, service_id, staff_id) { var _staff = {}, _services = {}, _categories = {}, _nop = {}; $.each(staff, function(id, staff_member) { if (!location_id || locations[location_id].staff.hasOwnProperty(id)) { if (!service_id) { if (!category_id) { _staff[id] = staff_member; } else { $.each(staff_member.services, function(s_id) { if (services[s_id].category_id == category_id) { _staff[id] = staff_member; return false; } }); } } else if (staff_member.services.hasOwnProperty(service_id)) { if (staff_member.services[service_id].price != null) { _staff[id] = { id : id, name : staff_member.name + ' (' + staff_member.services[service_id].price + ')', pos : staff_member.pos }; } else { _staff[id] = staff_member; } } } }); if (!location_id) { _categories = categories; $.each(services, function(id, service) { if (!category_id || service.category_id == category_id) { if (!staff_id || staff[staff_id].services.hasOwnProperty(id)) { _services[id] = service; } } }); } else { var category_ids = [], service_ids = []; $.each(locations[location_id].staff, function(st_id) { $.each(staff[st_id].services, function(s_id) { category_ids.push(services[s_id].category_id); service_ids.push(s_id); }); }); $.each(categories, function(id, category) { if ($.inArray(parseInt(id), category_ids) > -1) { _categories[id] = category; } }); $.each(services, function(id, service) { if ($.inArray(id, service_ids) > -1) { if (!category_id || service.category_id == category_id) { if (!staff_id || staff[staff_id].services.hasOwnProperty(id)) { _services[id] = service; } } } }); } var nop = $chain_item.find('.bookly-js-select-number-of-persons').val(); var max_capacity = service_id ? (staff_id ? staff[staff_id].services[service_id].max_capacity : services[service_id].max_capacity) : 1; var min_capacity = service_id ? (staff_id ? staff[staff_id].services[service_id].min_capacity : services[service_id].min_capacity) : 1; for (var i = min_capacity; i <= max_capacity; ++ i) { _nop[i] = { id: i, name: i, pos: i }; } if (nop > max_capacity) { nop = max_capacity; } if (nop < min_capacity) { nop = min_capacity; } setSelect($chain_item.find('.bookly-js-select-category'), _categories, category_id); setSelect($chain_item.find('.bookly-js-select-service'), _services, service_id); setSelect($chain_item.find('.bookly-js-select-employee'), _staff, staff_id); setSelect($chain_item.find('.bookly-js-select-number-of-persons'), _nop, nop); } $container.off('click').off('change'); // Location select change $container.on('change', '.bookly-js-select-location', function () { var $chain_item = $(this).closest('.bookly-js-chain-item'), location_id = this.value, category_id = $chain_item.find('.bookly-js-select-category').val(), service_id = $chain_item.find('.bookly-js-select-service').val(), staff_id = $chain_item.find('.bookly-js-select-employee').val() ; // Validate selected values. if (location_id) { if (staff_id && !locations[location_id].staff.hasOwnProperty(staff_id)) { staff_id = ''; } if (service_id) { var valid = false; $.each(locations[location_id].staff, function(id) { if (staff[id].services.hasOwnProperty(service_id)) { valid = true; return false; } }); if (!valid) { service_id = ''; } } if (category_id) { var valid = false; $.each(locations[location_id].staff, function(id) { $.each(staff[id].services, function(s_id) { if (services[s_id].category_id == category_id) { valid = true; return false; } }); if (valid) { return false; } }); if (!valid) { category_id = ''; } } } setSelects($chain_item, location_id, category_id, service_id, staff_id); }); // Category select change $container.on('change', '.bookly-js-select-category', function () { var $chain_item = $(this).closest('.bookly-js-chain-item'), location_id = $chain_item.find('.bookly-js-select-location').val(), category_id = this.value, service_id = $chain_item.find('.bookly-js-select-service').val(), staff_id = $chain_item.find('.bookly-js-select-employee').val() ; // Validate selected values. if (category_id) { category_selected = true; if (service_id) { if (services[service_id].category_id != category_id) { service_id = ''; } } if (staff_id) { var valid = false; $.each(staff[staff_id].services, function(id) { if (services[id].category_id == category_id) { valid = true; return false; } }); if (!valid) { staff_id = ''; } } } else { category_selected = false; } setSelects($chain_item, location_id, category_id, service_id, staff_id); }); // Service select change $container.on('change', '.bookly-js-select-service', function () { var $chain_item = $(this).closest('.bookly-js-chain-item'), location_id = $chain_item.find('.bookly-js-select-location').val(), category_id = category_selected ? $chain_item.find('.bookly-js-select-category').val() : '', service_id = this.value, staff_id = $chain_item.find('.bookly-js-select-employee').val() ; // Validate selected values. if (service_id) { if (staff_id && !staff[staff_id].services.hasOwnProperty(service_id)) { staff_id = ''; } } setSelects($chain_item, location_id, category_id, service_id, staff_id); if (service_id) { $chain_item.find('.bookly-js-select-category').val(services[service_id].category_id); } }); // Staff select change $container.on('change', '.bookly-js-select-employee', function() { var $chain_item = $(this).closest('.bookly-js-chain-item'), location_id = $chain_item.find('.bookly-js-select-location').val(), category_id = $('.bookly-js-select-category', $chain_item).val(), service_id = $chain_item.find('.bookly-js-select-service').val(), staff_id = this.value ; setSelects($chain_item, location_id, category_id, service_id, staff_id); }); // Set up draft selects. if (Options.attributes.show_service_duration) { $.each(services, function(id, service) { service.name = service.name + ' ( ' + service.duration + ' )'; }); } setSelect($select_location, locations); setSelect($select_category, categories); setSelect($select_service, services); setSelect($select_employee, staff); $select_location.closest('.bookly-form-group').toggle(!Options.attributes.hide_locations); $select_category.closest('.bookly-form-group').toggle(!Options.attributes.hide_categories); $select_service.closest('.bookly-form-group').toggle(!(Options.attributes.hide_services && Options.attributes.service_id)); $select_employee.closest('.bookly-form-group').toggle(!Options.attributes.hide_staff_members); $select_nop.closest('.bookly-form-group').toggle(Options.attributes.show_number_of_persons); $select_quantity.closest('.bookly-form-group').toggle(!Options.attributes.hide_quantity); if (Options.attributes.location_id) { $select_location.val(Options.attributes.location_id).trigger('change'); } if (Options.attributes.category_id) { $select_category.val(Options.attributes.category_id).trigger('change'); } if (Options.attributes.service_id) { $select_service.val(Options.attributes.service_id).trigger('change'); } if (Options.attributes.staff_member_id) { $select_employee.val(Options.attributes.staff_member_id).trigger('change'); } if (Options.attributes.hide_date) { $('.bookly-js-available-date', $container).hide(); } if (Options.attributes.hide_week_days) { $('.bookly-js-week-days', $container).hide(); } if (Options.attributes.hide_time_range) { $('.bookly-js-time-range', $container).hide(); } // Create chain items. $.each(chain, function(key, chain_item) { var $chain_item = $chain_item_draft .clone() .data('chain_key', key) .removeClass('bookly-js-draft') .css('display', 'table'); $chain_item_draft.find('select').each(function (i, select) { $chain_item.find('select:eq(' + i + ')').val(select.value); }); last_chain_key = key; if (key == 0) { $chain_item.find('.bookly-js-actions button[data-action="drop"]').remove(); } $('.bookly-js-chain-item:last', $container).after($chain_item); if (chain_item.location_id) { $('.bookly-js-select-location', $chain_item).val(chain_item.location_id).trigger('change'); } if (chain_item.service_id) { $('.bookly-js-select-service', $chain_item).val(chain_item.service_id).trigger('change'); } if (chain_item.staff_ids.length == 1 && chain_item.staff_ids[0]) { $('.bookly-js-select-employee', $chain_item).val(chain_item.staff_ids[0]).trigger('change'); } if (chain_item.number_of_persons > 1) { $('.bookly-js-select-number-of-persons', $chain_item).val(chain_item.number_of_persons); } if (chain_item.quantity > 1) { $('.bookly-js-select-quantity', $chain_item).val(chain_item.quantity); } }); $container.on('click', '.bookly-js-mobile-step-1 .bookly-js-actions button', function () { switch ($(this).data('action')) { case 'plus': var $new_chain = $chain_item_draft.clone(); $chain_item_draft.find('select').each(function (i, select) { $new_chain.find('select:eq(' + i + ')').val(select.value); }); $('.bookly-js-chain-item:last', $container) .after( $new_chain .data('chain_key', ++ last_chain_key) .removeClass('bookly-js-draft') .css('display', 'table') ); break; case 'drop': $(this).closest('.bookly-js-chain-item').remove(); break; } }); // change week days $week_day.on('change', function () { var $this = $(this); if ($this.is(':checked')) { $this.parent().not("[class*='active']").addClass('active'); } else { $this.parent().removeClass('active'); } }); // time from $select_time_from.on('change', function () { var start_time = $(this).val(), end_time = $select_time_to.val(), $last_time_entry = $('option:last', $select_time_from); $select_time_to.empty(); // case when we click on the not last time entry if ($select_time_from[0].selectedIndex < $last_time_entry.index()) { // clone and append all next "time_from" time entries to "time_to" list $('option', this).each(function () { if ($(this).val() > start_time) { $select_time_to.append($(this).clone()); } }); // case when we click on the last time entry } else { $select_time_to.append($last_time_entry.clone()).val($last_time_entry.val()); } var first_value = $('option:first', $select_time_to).val(); $select_time_to.val(end_time >= first_value ? end_time : first_value); }); var stepServiceValidator = function() { $('.bookly-js-select-service-error', $container).hide(); $('.bookly-js-select-employee-error', $container).hide(); $('.bookly-js-select-location-error', $container).hide(); var valid = true, $select_service = null, $select_employee = null, $select_location = null, $scroll_to = null; $('.bookly-js-chain-item:not(.bookly-js-draft)', $container).each(function () { var $chain = $(this); $select_service = $('.bookly-js-select-service', $chain); $select_employee = $('.bookly-js-select-employee', $chain); $select_location = $('.bookly-js-select-location', $chain); $select_service.removeClass('bookly-error'); $select_employee.removeClass('bookly-error'); $select_location.removeClass('bookly-error'); // service validation if (!$select_service.val()) { valid = false; $select_service.addClass('bookly-error'); $('.bookly-js-select-service-error', $chain).show(); $scroll_to = $select_service; } if (Options.required.hasOwnProperty('location') && Options.required.location && !$select_location.val()) { valid = false; $select_location.addClass('bookly-error'); $('.bookly-js-select-location-error', $chain).show(); $scroll_to = $select_location; } if (Options.required.staff && !$select_employee.val()) { valid = false; $select_employee.addClass('bookly-error'); $('.bookly-js-select-employee-error', $chain).show(); $scroll_to = $select_employee; } }); $date_from.removeClass('bookly-error'); // date validation if (!$date_from.val()) { valid = false; $date_from.addClass('bookly-error'); if ($scroll_to === null) { $scroll_to = $date_from; } } // week days if (!$('.bookly-js-week-day:checked', $container).length) { valid = false; if ($scroll_to === null) { $scroll_to = $week_day; } } if ($scroll_to !== null) { scrollTo($scroll_to); } return valid; }; // "Next" click $next_step.on('click', function (e) { e.preventDefault(); if (stepServiceValidator()) { ladda_start(this); // Prepare chain data. var chain = {}; var has_extras = 0; $('.bookly-js-chain-item:not(.bookly-js-draft)', $container).each(function () { var $chain_item = $(this); var staff_ids = []; if ($('.bookly-js-select-employee', $chain_item).val()) { staff_ids.push($('.bookly-js-select-employee', $chain_item).val()); } else { $('.bookly-js-select-employee', $chain_item).find('option').each(function () { if (this.value) { staff_ids.push(this.value); } }); } chain[$chain_item.data('chain_key')] = { location_id : $('.bookly-js-select-location', $chain_item).val(), service_id : $('.bookly-js-select-service', $chain_item).val(), staff_ids : staff_ids, number_of_persons : $('.bookly-js-select-number-of-persons', $chain_item).val(), quantity : $('.bookly-js-select-quantity', $chain_item).val() ? $('.bookly-js-select-quantity', $chain_item).val() : 1 }; has_extras += services[$('.bookly-js-select-service', $chain_item).val()].has_extras; }); // Prepare days. var days = []; $('.bookly-js-week-days .active input.bookly-js-week-day', $container).each(function() { days.push(this.value); }); $.ajax({ type : 'POST', url : Options.ajaxurl, data : { action : 'bookly_session_save', csrf_token : BooklyL10n.csrf_token, form_id : Options.form_id, chain : chain, date_from : $date_from.pickadate('picker').get('select', 'yyyy-mm-dd'), days : days, time_from : $select_time_from.val(), time_to : $select_time_to.val() }, dataType : 'json', xhrFields : { withCredentials: true }, crossDomain : 'withCredentials' in new XMLHttpRequest(), success : function (response) { if (!Options.skip_steps.extras) { if (has_extras == 0) { Options.no_extras = true; stepTime(); } else { Options.no_extras = false; stepExtras(); } } else { stepTime(); } } }); } }); // $mobile_next_step.on('click', function (e,skip_scroll) { if (stepServiceValidator()) { if (Options.skip_steps.service_part2) { ladda_start(this); $next_step.trigger('click'); } else { $('.bookly-js-mobile-step-1', $container).hide(); $('.bookly-js-mobile-step-2', $container).css('display', 'block'); if (Options.skip_steps.service) { $mobile_prev_step.remove(); } if (skip_scroll != true) { scrollTo($container); } } } return false; }); $mobile_prev_step.on('click', function () { $('.bookly-js-mobile-step-1', $container).show(); $('.bookly-js-mobile-step-2', $container).hide(); if ($select_service.val()) { $('.bookly-js-select-service', $container).parent().removeClass('bookly-error'); } return false; }); if (Options.skip_steps.service_part1) { // Skip scrolling $mobile_next_step.trigger('click', [true]); } } } // ajax success }); // ajax } /** * Extras step. */ function stepExtras(params) { var data = $.extend({ action : 'bookly_render_extras', csrf_token : BooklyL10n.csrf_token, form_id : Options.form_id }, params); if (Options.skip_steps.service) { // If Service step is skipped then we need to send time zone offset. data.time_zone = timeZone; data.time_zone_offset = timeZoneOffset; } $.ajax({ url: Options.ajaxurl, data: data, dataType: 'json', xhrFields: {withCredentials: true}, crossDomain: 'withCredentials' in new XMLHttpRequest(), success: function (response) { if (response.success) { BooklyL10n.csrf_token = response.csrf_token; $container.html(response.html); if (params === undefined) { // Scroll when returning to the step Extras. scrollTo($container); } var $next_step = $('.bookly-js-next-step', $container), $back_step = $('.bookly-js-back-step', $container), $goto_cart = $('.bookly-js-go-to-cart', $container), $extras_items = $('.bookly-js-extras-item', $container), $extras_summary = $('.bookly-js-extras-summary span', $container), currency = response.currency, $this, $input; $extras_items.each(function (index, elem) { var $this = $(this); var $input = $this.find('input'); $this.find('.bookly-js-extras-thumb').on('click', function () { extras_changed($this, $input.val() > 0 ? 0 : 1); }); $this.find('.bookly-js-count-control').on('click', function() { var count = parseInt($input.val()); count = $(this).hasClass('bookly-js-extras-increment') ? Math.min($this.data('max_quantity'), count + 1) : Math.max(0, count - 1); extras_changed($this, count); }); }); function extras_changed($extras_item, quantity) { var $input = $extras_item.find('input'); var $total = $extras_item.find('.bookly-js-extras-total-price'); var total_price = quantity * parseFloat($extras_item.data('price')); $total.text(currency.format.replace('1', total_price.toFixed(currency.precision))); $input.val(quantity); $extras_item.find('.bookly-js-extras-thumb').toggleClass('bookly-extras-selected', quantity > 0); // Updating summary var amount = 0; $extras_items.each(function (index, elem) { var $this = $(this); amount += parseFloat($this.data('price')) * $this.find('input').val(); }); if (amount) { $extras_summary.html(' + ' + currency.format.replace('1', amount.toFixed(currency.precision))); } else { $extras_summary.html(''); } } $goto_cart.on('click', function (e) { e.preventDefault(); ladda_start(this); stepCart({from_step : 'extras'}); }); $next_step.on('click', function (e) { e.preventDefault(); ladda_start(this); var extras = {}; $('.bookly-js-extras-container', $container).each(function () { var $extras_container = $(this); var chain_id = $extras_container.data('chain'); var chain_extras = {}; // Get checked extras for chain. $extras_container.find('.bookly-js-extras-item').each(function (index, elem) { $this = $(this); $input = $this.find('input'); if ($input.val() > 0) { chain_extras[$this.data('id')] = $input.val(); } }); extras[chain_id] = JSON.stringify(chain_extras); }); $.ajax({ type : 'POST', url : Options.ajaxurl, data : { action : 'bookly_session_save', csrf_token : BooklyL10n.csrf_token, form_id : Options.form_id, extras : extras }, dataType: 'json', xhrFields: {withCredentials: true}, crossDomain: 'withCredentials' in new XMLHttpRequest(), success: function (response) { stepTime(); } }); }); $back_step.on('click', function (e) { e.preventDefault(); ladda_start(this); stepService(); }).toggle(!Options.skip_steps.service); } } }); } /** * Time step. */ var xhr_render_time = null; function stepTime(params, error_message) { if (xhr_render_time != null) { xhr_render_time.abort(); xhr_render_time = null; } var data = $.extend({ action: 'bookly_render_time', csrf_token: BooklyL10n.csrf_token, form_id: Options.form_id, skip_extras: false }, params); if (Options.skip_steps.service) { // If Service step is skipped then we need to send time zone offset. data.time_zone = timeZone; data.time_zone_offset = timeZoneOffset; } xhr_render_time = $.ajax({ url : Options.ajaxurl, data : data, dataType : 'json', xhrFields : { withCredentials: true }, crossDomain : 'withCredentials' in new XMLHttpRequest(), success : function (response) { if (response.success == false) { // The session doesn't contain data. stepService(); return; } BooklyL10n.csrf_token = response.csrf_token; $container.html(response.html); var $columnizer_wrap = $('.bookly-columnizer-wrap', $container), $columnizer = $('.bookly-columnizer', $columnizer_wrap), $time_next_button = $('.bookly-time-next', $container), $time_prev_button = $('.bookly-time-prev', $container), $current_screen = null, slot_height = 36, column_width = 127, columns = 0, screen_index = 0, has_more_slots = response.has_more_slots, form_hidden = false, $screens, slots_per_column, columns_per_screen, show_day_per_column = response.day_one_column ; // 'BACK' button. $('.bookly-js-back-step', $container).on('click', function (e) { e.preventDefault(); ladda_start(this); if (!Options.skip_steps.extras && !Options.no_extras) { stepExtras(); } else { stepService(); } }).toggle(!Options.skip_steps.service || !Options.skip_steps.extras); $('.bookly-js-go-to-cart', $container).on('click', function(e) { e.preventDefault(); ladda_start(this); stepCart({from_step : 'time'}); }); if (Options.show_calendar) { // Init calendar. var $input = $('.bookly-js-selected-date', $container); $input.pickadate({ formatSubmit : 'yyyy-mm-dd', format : Options.date_format, min : response.date_min || true, max : response.date_max || true, weekdaysFull : BooklyL10n.days, weekdaysShort : BooklyL10n.daysShort, monthsFull : BooklyL10n.months, firstDay : Options.start_of_week, clear : false, close : false, today : false, disable : response.disabled_days, closeOnSelect : false, klass : { picker: 'picker picker--opened picker--focused' }, onSet: function(e) { if (e.select) { var date = this.get('select', 'yyyy-mm-dd'); if (response.slots[date]) { // Get data from response.slots. $columnizer.html(response.slots[date]).css('left', '0px'); columns = 0; screen_index = 0; $current_screen = null; initSlots(); $time_prev_button.hide(); $time_next_button.toggle($screens.length != 1); } else { // Load new data from server. stepTime({selected_date : date}); showSpinner(); } } this.open(); // Fix ultimate-member plugin }, onClose: function() { this.open(false); }, onRender: function() { var date = new Date(Date.UTC(this.get('view').year, this.get('view').month)); $('.picker__nav--next').on('click', function() { date.setUTCMonth(date.getUTCMonth() + 1); stepTime({selected_date : date.toJSON().substr(0, 10)}); showSpinner(); }); $('.picker__nav--prev').on('click', function() { date.setUTCMonth(date.getUTCMonth() - 1); stepTime({selected_date : date.toJSON().substr(0, 10)}); showSpinner(); }); } }); // Insert slots for selected day. var date = $input.pickadate('picker').get('select', 'yyyy-mm-dd'); $columnizer.html(response.slots[date]); } else { // Insert all slots. var slots = ''; $.each(response.slots, function(group, group_slots) { slots += group_slots; }); $columnizer.html(slots); } if (response.has_slots) { if (error_message) { $container.find('.bookly-holder.bookly-label-error').html(error_message); } else { $container.find('.bookly-holder.bookly-label-error').hide(); } // Calculate number of slots per column. slots_per_column = parseInt($(window).height() / slot_height, 10); if (slots_per_column < 4) { slots_per_column = 4; } else if (slots_per_column > 10) { slots_per_column = 10; } columns_per_screen = parseInt($columnizer_wrap.width() / column_width, 10); if (columns_per_screen > 10) { columns_per_screen = 10; } else if (columns_per_screen == 0) { // Bookly form display hidden. form_hidden = true; columns_per_screen = 4; } initSlots(); if (!has_more_slots && $screens.length == 1) { $time_next_button.hide(); } var hammertime = $('.bookly-time-step', $container).hammer({ swipe_velocity: 0.1 }); hammertime.on('swipeleft', function() { if ($time_next_button.is(':visible')) { $time_next_button.trigger('click'); } }); hammertime.on('swiperight', function() { if ($time_prev_button.is(':visible')) { $time_prev_button.trigger('click'); } }); $time_next_button.on('click', function (e) { $time_prev_button.show(); if ($screens.eq(screen_index + 1).length) { $columnizer.animate( { left: (Options.is_rtl ? '+' : '-') + ( screen_index + 1 ) * $current_screen.width() }, { duration: 800 } ); $current_screen = $screens.eq(++ screen_index); $columnizer_wrap.animate( { height: $current_screen.height() }, { duration: 800 } ); if (screen_index + 1 == $screens.length && !has_more_slots) { $time_next_button.hide(); } } else if (has_more_slots) { // Do ajax request when there are more slots. var $button = $('> button:last', $columnizer); if ($button.length == 0) { $button = $('.bookly-column:hidden:last > button:last', $columnizer); if ($button.length == 0) { $button = $('.bookly-column:last > button:last', $columnizer); } } // Render Next Time var data = { action : 'bookly_render_next_time', csrf_token : BooklyL10n.csrf_token, form_id : Options.form_id, last_slot : $button.val() }, ladda = ladda_start(this); $.ajax({ type : 'POST', url : Options.ajaxurl, data : data, dataType : 'json', xhrFields : { withCredentials: true }, crossDomain : 'withCredentials' in new XMLHttpRequest(), success : function (response) { if (response.success) { if (response.has_slots) { // if there are available time has_more_slots = response.has_more_slots; var $html = $(response.html); // The first slot is always a day slot. // Check if such day slot already exists (this can happen // because of time zone offset) and then remove the first slot. var $first_day = $html.eq(0); if ($('button.bookly-day[value="' + $first_day.attr('value') + '"]', $container).length) { $html = $html.not(':first'); } $columnizer.append($html); initSlots(); $time_next_button.trigger('click'); } else { // no available time $time_next_button.hide(); } } else { // no available time $time_next_button.hide(); } ladda.stop(); } }); } }); $time_prev_button.on('click', function () { $time_next_button.show(); $current_screen = $screens.eq(-- screen_index); $columnizer.animate( { left: (Options.is_rtl ? '+' : '-') + screen_index * $current_screen.width() }, { duration: 800 } ); $columnizer_wrap.animate( { height: $current_screen.height() }, { duration: 800 } ); if (screen_index === 0) { $time_prev_button.hide(); } }); } if (params === undefined) { // Scroll when returning to the step Time. scrollTo($container); } function showSpinner() { $('.bookly-time-screen,.bookly-not-time-screen', $container).addClass('bookly-spin-overlay'); var opts = { lines : 11, // The number of lines to draw length: 11, // The length of each line width : 4, // The line thickness radius: 5 // The radius of the inner circle }; if ($screens) { new Spinner(opts).spin($screens.eq(screen_index).get(0)); } else { // Calendar not available month. new Spinner(opts).spin($('.bookly-not-time-screen', $container).get(0)); } } function initSlots() { var $buttons = $('> button', $columnizer), slots_count = 0, max_slots = 0, $button, $column, $screen; if (show_day_per_column) { /** * Create columns for 'Show each day in one column' mode. */ while ($buttons.length > 0) { // Create column. if ($buttons.eq(0).hasClass('bookly-day')) { slots_count = 1; $column = $('
'); $button = $($buttons.splice(0, 1)); $button.addClass('bookly-js-first-child'); $column.append($button); } else { slots_count ++; $button = $($buttons.splice(0, 1)); // If it is last slot in the column. if (!$buttons.length || $buttons.eq(0).hasClass('bookly-day')) { $button.addClass('bookly-last-child'); $column.append($button); $columnizer.append($column); } else { $column.append($button); } } // Calculate max number of slots. if (slots_count > max_slots) { max_slots = slots_count; } } } else { /** * Create columns for normal mode. */ while (has_more_slots ? $buttons.length > slots_per_column : $buttons.length) { $column = $('
'); max_slots = slots_per_column; if (columns % columns_per_screen == 0 && !$buttons.eq(0).hasClass('bookly-day')) { // If this is the first column of a screen and the first slot in this column is not day // then put 1 slot less in this column because createScreens adds 1 more // slot to such columns. -- max_slots; } for (var i = 0; i < max_slots; ++ i) { if (i + 1 == max_slots && $buttons.eq(0).hasClass('bookly-day')) { // Skip the last slot if it is day. break; } $button = $($buttons.splice(0, 1)); if (i == 0) { $button.addClass('bookly-js-first-child'); } else if (i + 1 == max_slots) { $button.addClass('bookly-last-child'); } $column.append($button); } $columnizer.append($column); ++ columns; } } /** * Create screens. */ var $columns = $('> .bookly-column', $columnizer); while (has_more_slots ? $columns.length >= columns_per_screen : $columns.length) { $screen = $('
'); for (var i = 0; i < columns_per_screen; ++i) { $column = $($columns.splice(0, 1)); if (i == 0) { $column.addClass('bookly-js-first-column'); var $first_slot = $column.find('.bookly-js-first-child'); // In the first column the first slot is time. if (!$first_slot.hasClass('bookly-day')) { var group = $first_slot.data('group'), $group_slot = $('button.bookly-day[value="' + group + '"]:last', $container); // Copy group slot to the first column. $column.prepend($group_slot.clone()); } } $screen.append($column); } $columnizer.append($screen); } $screens = $('.bookly-time-screen', $columnizer); if ($current_screen === null) { $current_screen = $screens.eq(0); } // On click on a slot. $('button.bookly-hour', $container).off('click').on('click', function (e) { e.preventDefault(); var $this = $(this), data = { action : 'bookly_session_save', csrf_token : BooklyL10n.csrf_token, form_id : Options.form_id, slots : this.value }; $this.attr({'data-style': 'zoom-in','data-spinner-color':'#333','data-spinner-size':'40'}); ladda_start(this); $.ajax({ type : 'POST', url : Options.ajaxurl, data : data, dataType : 'json', xhrFields : { withCredentials: true }, crossDomain : 'withCredentials' in new XMLHttpRequest(), success : function (response) { if (!Options.skip_steps.repeat) { stepRepeat(); } else if (Options.cart.enabled) { stepCart({add_to_cart : true, from_step : 'time'}); } else { stepDetails({add_to_cart : true}); } } }); }); // Columnizer width & height. $('.bookly-time-step', $container).width(columns_per_screen * column_width); $columnizer_wrap.height(form_hidden ? $('.bookly-column.bookly-js-first-column button', $current_screen).length * (slot_height + 3) : $current_screen.height()); form_hidden = false; } } }); } /** * Repeat step. */ function stepRepeat(params, error) { if (Options.skip_steps.repeat) { stepCart(params, error) } else { var data = $.extend({ action : 'bookly_render_repeat', csrf_token : BooklyL10n.csrf_token, form_id : Options.form_id }, params); $.ajax({ url : Options.ajaxurl, data : data, dataType : 'json', xhrFields : { withCredentials: true }, crossDomain : 'withCredentials' in new XMLHttpRequest(), success : function (response) { if (response.success) { $container.html(response.html); scrollTo($container); var $repeat_enabled = $('.bookly-js-repeat-appointment-enabled', $container), $next_step = $('.bookly-js-next-step', $container), $repeat_container = $('.bookly-js-repeat-variants-container', $container), $variants = $('[class^="bookly-js-variant"]', $repeat_container), $repeat_variant = $('.bookly-js-repeat-variant', $repeat_container), $button_get_schedule = $('.bookly-js-get-schedule', $repeat_container), $variant_weekly = $('.bookly-js-variant-weekly', $repeat_container), $variant_monthly = $('.bookly-js-repeat-variant-monthly', $repeat_container), $date_until = $('.bookly-js-repeat-until', $repeat_container), $monthly_specific_day = $('.bookly-js-monthly-specific-day', $repeat_container), $monthly_week_day = $('.bookly-js-monthly-week-day', $repeat_container), $repeat_every_day = $('.bookly-js-repeat-daily-every', $repeat_container), $week_day = $('.bookly-js-week-day', $repeat_container), $schedule_container = $('.bookly-js-schedule-container', $container), $days_error = $('.bookly-js-days-error', $repeat_container), $schedule_slots = $('.bookly-js-schedule-slots',$schedule_container), $intersection_info = $('.bookly-js-intersection-info', $schedule_container), $info_help = $('.bookly-js-schedule-help', $schedule_container), $info_wells = $('.bookly-well', $schedule_container), $pagination = $('.bookly-pagination', $schedule_container), $schedule_row_template = $('.bookly-schedule-row-template .bookly-schedule-row', $schedule_container), pages_warning_info = response.pages_warning_info, short_date_format = response.short_date_format, bound_date = {min: response.date_min || true, max: response.date_max || true}, schedule = [] ; var repeat = { prepareButtonNextState : function () { // Disable/Enable next button var is_disabled = $next_step.prop('disabled'), new_prop_disabled = schedule.length == 0; for (var i = 0; i < schedule.length; i++) { if (is_disabled) { if (!schedule[i].deleted) { new_prop_disabled = false; break; } } else if (schedule[i].deleted) { new_prop_disabled = true; } else { new_prop_disabled = false; break; } } $next_step.prop('disabled', new_prop_disabled); }, addTimeSlotControl : function ($schedule_row, options, prefer_time, current_time ) { var $time = ''; if(options.length) { var prefer; $time = $(''), $edit_button = $(this), ladda_round = ladda_start(this); $schedule_row.find('.bookly-schedule-date').html($date); $date.pickadate({ min : bound_date.min, max : bound_date.max, formatSubmit : 'yyyy-mm-dd', format : short_date_format, clear : false, close : false, today : BooklyL10n.today, monthsFull : BooklyL10n.months, weekdaysFull : BooklyL10n.days, weekdaysShort : BooklyL10n.daysShort, labelMonthNext : BooklyL10n.nextMonth, labelMonthPrev : BooklyL10n.prevMonth, firstDay : Options.start_of_week, onSet: function() { var exclude = []; $.each(schedule, function (index, item) { if ((row_index != index) && !item.deleted) { exclude.push(item.slots); } }); $.ajax({ url : Options.ajaxurl, type: 'POST', data: { action : 'bookly_recurring_appointments_get_daily_customer_schedule', csrf_token : BooklyL10n.csrf_token, date : this.get('select', 'yyyy-mm-dd'), form_id : Options.form_id, exclude : exclude }, dataType: 'json', xhrFields: {withCredentials: true}, crossDomain: 'withCredentials' in new XMLHttpRequest(), success: function (response) { $edit_button.hide(); ladda_round.stop(); if (response.data.length) { repeat.addTimeSlotControl($schedule_row, response.data[0].options, prefer_time, schedule[row_index].display_time); $schedule_row.find('button[data-action="save"]').show(); } else { repeat.addTimeSlotControl($schedule_row, [] ); $schedule_row.find('button[data-action="save"]').hide(); } } }); } }); var slots = JSON.parse(schedule[row_index].slots); $date.pickadate('picker').set('select', new Date(slots[0][2])); break; case 'save': $(this).hide(); $schedule_row.find('button[data-action="edit"]').show(); var $date_container = $schedule_row.find('.bookly-schedule-date'), $time_container = $schedule_row.find('.bookly-schedule-time'), $select = $time_container.find('select'), option = $select.find('option:selected'); schedule[row_index].slots = $select.val(); schedule[row_index].display_date = $date_container.find('input').val(); schedule[row_index].display_time = option.text(); $date_container.html(schedule[row_index].display_date); $time_container.html(schedule[row_index].display_time); break; } }); } }; $date_until.pickadate({ formatSubmit : 'yyyy-mm-dd', format : Options.date_format, min : bound_date.min, max : bound_date.max, clear : false, close : false, today : BooklyL10n.today, monthsFull : BooklyL10n.months, weekdaysFull : BooklyL10n.days, weekdaysShort : BooklyL10n.daysShort, labelMonthNext : BooklyL10n.nextMonth, labelMonthPrev : BooklyL10n.prevMonth, firstDay : Options.start_of_week }); var open_repeat_onchange = $repeat_enabled.on('change', function () { $repeat_container.toggle($(this).prop('checked')); if ($(this).prop('checked')) { repeat.prepareButtonNextState(); } else { $next_step.prop('disabled', false); } }); if (response.repeated) { var repeat_data = response.repeat_data; var repeat_params = repeat_data.params; $repeat_enabled.prop('checked', true); $repeat_variant.val(repeat_data.repeat); var until = repeat_data.until.split('-'); $date_until.pickadate('set').set('select', new Date(until[0], until[1]-1, until[2])); switch (repeat_data.repeat) { case 'daily': $repeat_every_day.val(repeat_params.every); break; case 'weekly': //break skipped case 'biweekly': $('.bookly-js-week-days input.bookly-js-week-day', $repeat_container) .prop('checked', false) .parent() .removeClass('active'); repeat_params.on.forEach(function(val) { $('.bookly-js-week-days input.bookly-js-week-day[value='+val+']', $repeat_container) .prop('checked', true) .parent() .addClass('active'); }); break; case 'monthly': if (repeat_params.on === 'day') { $variant_monthly.val('specific'); $('.bookly-js-monthly-specific-day[value='+repeat_params.day+']', $repeat_container).prop('checked', true); } else { $variant_monthly.val(repeat_params.on); $monthly_week_day.val(repeat_params.weekday); } break; } repeat.renderFullSchedule(response.schedule); } open_repeat_onchange.trigger('change'); if (!response.could_be_repeated) { $repeat_enabled.attr('disabled', true); } $repeat_variant.on('change', function () { $variants.hide(); $repeat_container.find('.bookly-js-variant-' + this.value).show() }).trigger('change'); $variant_monthly.on('change', function () { $monthly_week_day.toggle(this.value != 'specific'); $monthly_specific_day.toggle(this.value == 'specific'); }).trigger('change'); $week_day.on('change', function () { var $this = $(this); if ($this.is(':checked')) { $this.parent().not("[class*='active']").addClass('active'); } else { $this.parent().removeClass('active'); } }); $monthly_specific_day.val(response.date_min[2]); $button_get_schedule.on('click', function () { $schedule_container.hide(); var data = { action : 'bookly_recurring_appointments_get_customer_schedule', csrf_token : BooklyL10n.csrf_token, form_id : Options.form_id, repeat : $repeat_variant.val(), until : $date_until.pickadate('picker').get('select', 'yyyy-mm-dd'), params : {} }, ladda = ladda_start(this); switch (data.repeat) { case 'daily': data.params = {every: $repeat_every_day.val()}; break; case 'weekly': case 'biweekly': data.params.on = []; $('.bookly-js-week-days input.bookly-js-week-day:checked', $variant_weekly).each(function () { data.params.on.push(this.value); }); if (data.params.on.length == 0) { $days_error.toggle(true); ladda.stop(); return false; } else { $days_error.toggle(false); } break; case 'monthly': if ($variant_monthly.val() == 'specific') { data.params = {on: 'day', day: $monthly_specific_day.val()}; } else { data.params = {on: $variant_monthly.val(), weekday: $monthly_week_day.val()}; } break; } $schedule_slots.off('click'); $.ajax({ url : Options.ajaxurl, type: 'POST', data: data, dataType: 'json', xhrFields: {withCredentials: true}, crossDomain: 'withCredentials' in new XMLHttpRequest(), success: function (response) { if (response.success) { repeat.renderFullSchedule(response.data); ladda.stop(); } } }); }); $('.bookly-js-back-step', $container).on('click', function (e) { e.preventDefault(); ladda_start(this); $.ajax({ type: 'POST', url: Options.ajaxurl, data: { action: 'bookly_session_save', csrf_token: BooklyL10n.csrf_token, form_id: Options.form_id, unrepeat: 1 }, dataType: 'json', xhrFields: {withCredentials: true}, crossDomain: 'withCredentials' in new XMLHttpRequest(), success: function (response) { stepTime(); } }); }); $('.bookly-js-go-to-cart', $container).on('click', function(e) { e.preventDefault(); ladda_start(this); stepCart({from_step : 'repeat'}); }); $('.bookly-js-next-step', $container).on('click', function (e) { ladda_start(this); if ($repeat_enabled.is(':checked')) { var slots_to_send = []; var repeat = 0; schedule.forEach(function (item) { if (!item.deleted) { var slots = JSON.parse(item.slots); slots_to_send = slots_to_send.concat(slots); repeat++; } }); $.ajax({ type: 'POST', url: Options.ajaxurl, data: { action: 'bookly_session_save', csrf_token: BooklyL10n.csrf_token, form_id: Options.form_id, slots: JSON.stringify(slots_to_send), repeat: repeat }, dataType: 'json', xhrFields: {withCredentials: true}, crossDomain: 'withCredentials' in new XMLHttpRequest(), success: function (response) { stepCart({add_to_cart : true, from_step : 'repeat'}); } }); } else { $.ajax({ type: 'POST', url: Options.ajaxurl, data: { action: 'bookly_session_save', csrf_token: BooklyL10n.csrf_token, form_id: Options.form_id, unrepeat: 1 }, dataType: 'json', xhrFields: {withCredentials: true}, crossDomain: 'withCredentials' in new XMLHttpRequest(), success: function (response) { stepCart({add_to_cart: true, from_step : 'repeat'}); } }); } }); } } }); } } /** * Cart step. */ function stepCart(params, error) { if (!Options.cart.enabled) { stepDetails(params); } else { if (params && params.from_step) { // Record previous step if it was given in params. Options.cart.prev_step = params.from_step; } var data = $.extend({ action : 'bookly_render_cart', csrf_token : BooklyL10n.csrf_token, form_id : Options.form_id }, params); $.ajax({ url: Options.ajaxurl, data: data, dataType: 'json', xhrFields: {withCredentials: true}, crossDomain: 'withCredentials' in new XMLHttpRequest(), success: function (response) { if (response.success) { $container.html(response.html); if (error){ $('.bookly-holder.bookly-label-error', $container).html(error.message); $('tr[data-cart-key="'+ error.failed_key +'"]', $container).addClass('bookly-label-error'); } else { $('.bookly-holder.bookly-label-error', $container).hide(); } scrollTo($container); $('.bookly-js-next-step', $container).on('click', function () { ladda_start(this); stepDetails(); }); $('.bookly-add-item', $container).on('click', function () { ladda_start(this); stepService({new_chain : true}); }); // 'BACK' button. $('.bookly-js-back-step', $container).on('click', function (e) { e.preventDefault(); ladda_start(this); switch (Options.cart.prev_step) { case 'service': stepService(); break; case 'extras': stepExtras(); break; case 'time': stepTime(); break; case 'repeat': stepRepeat(); break; default: stepService(); } }); $('.bookly-js-actions button', $container).on('click', function () { ladda_start(this); var $this = $(this), $cart_item = $this.closest('tr'); switch ($this.data('action')) { case 'drop': $.ajax({ url: Options.ajaxurl, data: { action : 'bookly_cart_drop_item', csrf_token : BooklyL10n.csrf_token, form_id : Options.form_id, cart_key : $cart_item.data('cart-key') }, dataType: 'json', xhrFields: {withCredentials: true}, crossDomain: 'withCredentials' in new XMLHttpRequest(), success: function (response) { if (response.success) { var remove_cart_key = $cart_item.data('cart-key'), $trs_to_remove = $('tr[data-cart-key="'+remove_cart_key+'"]', $container) ; $cart_item.delay(300).fadeOut(200, function () { $('.bookly-js-total-price', $container).html(response.data.total_price); $('.bookly-js-total-deposit-price', $container).html(response.data.total_deposit_price); $trs_to_remove.remove(); if ($('tr[data-cart-key]').length == 0) { $('.bookly-js-back-step', $container).hide(); $('.bookly-js-next-step', $container).hide(); } }); } } }); break; case 'edit': stepService({edit_cart_item : $cart_item.data('cart-key')}); break; } }); } } }); } } /** * Details step. */ function stepDetails(params) { var data = $.extend({ action : 'bookly_render_details', csrf_token : BooklyL10n.csrf_token, form_id : Options.form_id }, params); $.ajax({ url : Options.ajaxurl, data : data, dataType : 'json', xhrFields : { withCredentials: true }, crossDomain : 'withCredentials' in new XMLHttpRequest(), success : function (response) { if (response.success) { $container.html(response.html); scrollTo($container); // Init. var phone_number = '', $guest_info = $('.bookly-js-guest', $container), $phone_field = $('.bookly-js-user-phone-input', $container), $email_field = $('.bookly-js-user-email', $container), $name_field = $('.bookly-js-full-name', $container), $phone_error = $('.bookly-js-user-phone-error', $container), $email_error = $('.bookly-js-user-email-error', $container), $name_error = $('.bookly-js-full-name-error', $container), $captcha = $('.bookly-js-captcha-img', $container), $errors = $('.bookly-js-full-name-error, .bookly-js-user-phone-error, .bookly-js-user-email-error, .bookly-custom-field-error', $container), $fields = $('.bookly-js-full-name, .bookly-js-user-phone-input, .bookly-js-user-email, .bookly-custom-field', $container), $modals = $('.bookly-js-modal', $container), $login_modal = $('.bookly-js-login', $container), $cst_modal = $('.bookly-js-cst-duplicate', $container), $next_btn = $('.bookly-js-next-step', $container) ; if (Options.intlTelInput.enabled) { $phone_field.intlTelInput({ preferredCountries: [Options.intlTelInput.country], defaultCountry: Options.intlTelInput.country, geoIpLookup: function (callback) { $.get(Options.ajaxurl, {action: 'bookly_ip_info', csrf_token: BooklyL10n.csrf_token}, function () { }, 'json').always(function (resp) { var countryCode = (resp && resp.country) ? resp.country : ''; callback(countryCode); }); }, utilsScript: Options.intlTelInput.utils }); } // Init modals. $('body > .bookly-js-modal.' + Options.form_id).remove(); $modals .addClass(Options.form_id).appendTo('body') .on('click', '.bookly-js-close', function (e) { e.preventDefault(); $(e.delegateTarget).removeClass('bookly-in') .find('form').trigger('reset').end() .find('input').removeClass('bookly-error').end() .find('.bookly-label-error').html('') ; }) ; // Login modal. $('.bookly-js-login-show', $container).on('click', function(e) { e.preventDefault(); $login_modal.addClass('bookly-in'); }); $('button:submit', $login_modal).on('click', function (e) { e.preventDefault(); var ladda = Ladda.create(this); ladda.start(); $.ajax({ type : 'POST', url : Options.ajaxurl, data : { action : 'bookly_wp_user_login', csrf_token : BooklyL10n.csrf_token, form_id : Options.form_id, log : $login_modal.find('[name="log"]').val(), pwd : $login_modal.find('[name="pwd"]').val(), rememberme : $login_modal.find('[name="rememberme"]').prop('checked') ? 1 : 0 }, dataType : 'json', xhrFields : { withCredentials: true }, crossDomain : 'withCredentials' in new XMLHttpRequest(), success: function (response) { if (response.success) { BooklyL10n.csrf_token = response.data.csrf_token; $guest_info.fadeOut('slow'); $name_field.val(response.data.name).removeClass('bookly-error'); if (response.data.phone) { $phone_field.intlTelInput("setNumber", response.data.phone).removeClass('bookly-error'); } $email_field.val(response.data.email).removeClass('bookly-error'); $errors.filter(':not(.bookly-custom-field-error)').html(''); $login_modal.removeClass('bookly-in'); } else if (response.error_code == 8) { $login_modal.find('input').addClass('bookly-error'); $login_modal.find('.bookly-label-error').html(response.error); } ladda.stop(); } }) }); // Customer duplicate modal. $('button:submit', $cst_modal).on('click', function (e) { e.preventDefault(); $cst_modal.removeClass('bookly-in'); $next_btn.trigger('click', [1]); }); $next_btn.on('click', function(e, force_update_customer) { e.preventDefault(); var custom_fields = [], checkbox_values, captcha_ids = [], ladda = ladda_start(this) ; $('.bookly-custom-fields-container', $container).each(function () { var $cf_container = $(this), key = $cf_container.data('cart_key'), custom_fields_data = []; $('div.bookly-custom-field-row', $cf_container).each(function() { var $this = $(this); switch ($this.data('type')) { case 'text-field': custom_fields_data.push({ id : $this.data('id'), value : $this.find('input.bookly-custom-field').val() }); break; case 'textarea': custom_fields_data.push({ id : $this.data('id'), value : $this.find('textarea.bookly-custom-field').val() }); break; case 'checkboxes': checkbox_values = []; $this.find('input.bookly-custom-field:checked').each(function () { checkbox_values.push(this.value); }); custom_fields_data.push({ id : $this.data('id'), value : checkbox_values }); break; case 'radio-buttons': custom_fields_data.push({ id : $this.data('id'), value : $this.find('input.bookly-custom-field:checked').val() || null }); break; case 'drop-down': custom_fields_data.push({ id : $this.data('id'), value : $this.find('select.bookly-custom-field').val() }); break; case 'captcha': custom_fields_data.push({ id : $this.data('id'), value : $this.find('input.bookly-custom-field').val() }); captcha_ids.push($this.data('id')); break; } }); custom_fields[key] = {custom_fields: JSON.stringify(custom_fields_data)}; }); try { phone_number = Options.intlTelInput.enabled ? $phone_field.intlTelInput('getNumber') : $phone_field.val(); if (phone_number == '') { phone_number = $phone_field.val(); } } catch (error) { // In case when intlTelInput can't return phone number. phone_number = $phone_field.val(); } var data = { action : 'bookly_session_save', csrf_token : BooklyL10n.csrf_token, form_id : Options.form_id, name : $name_field.val(), phone : phone_number, email : $email_field.val(), cart : custom_fields, captcha_ids : JSON.stringify(captcha_ids), force_update_customer : force_update_customer }; $.ajax({ type : 'POST', url : Options.ajaxurl, data : data, dataType : 'json', xhrFields : { withCredentials: true }, crossDomain : 'withCredentials' in new XMLHttpRequest(), success : function (response) { // Error messages $errors.empty(); $fields.removeClass('bookly-error'); if (response.success) { if (Options.woocommerce.enabled) { var data = { action : 'bookly_add_to_woocommerce_cart', csrf_token : BooklyL10n.csrf_token, form_id : Options.form_id }; $.ajax({ type : 'POST', url : Options.ajaxurl, data : data, dataType : 'json', xhrFields : { withCredentials: true }, crossDomain : 'withCredentials' in new XMLHttpRequest(), success : function (response) { if (response.success) { window.location.href = Options.woocommerce.cart_url; } else { ladda.stop(); stepTime(undefined, response.error); } } }); } else { stepPayment(); } } else { ladda.stop(); var $scroll_to = null; if (response.name) { $name_error.html(response.name); $name_field.addClass('bookly-error'); $scroll_to = $name_field; } if (response.phone) { $phone_error.html(response.phone); $phone_field.addClass('bookly-error'); if ($scroll_to === null) { $scroll_to = $phone_field; } } if (response.email) { $email_error.html(response.email); $email_field.addClass('bookly-error'); if ($scroll_to === null) { $scroll_to = $email_field; } } if (response.custom_fields) { $.each(response.custom_fields, function (key, fields) { $.each(fields, function (field_id, message) { var $custom_fields_collector = $('.bookly-custom-fields-container[data-cart_key="' + key + '"]', $container); var $div = $('[data-id="' + field_id + '"]', $custom_fields_collector); $div.find('.bookly-custom-field-error').html(message); $div.find('.bookly-custom-field').addClass('bookly-error'); if ($scroll_to === null) { $scroll_to = $div.find('.bookly-custom-field'); } }); }); } if (response.customer) { $cst_modal .find('.bookly-js-modal-body').html(response.customer).end() .addClass('bookly-in') ; } if ($scroll_to !== null) { scrollTo($scroll_to); } } } }); }); $('.bookly-js-back-step', $container).on('click', function (e) { e.preventDefault(); ladda_start(this); if (Options.cart.enabled) { stepCart(); } else if (!Options.skip_steps.repeat) { stepRepeat(); } else { stepTime(); } }); $('.bookly-js-captcha-refresh', $container).on('click', function() { $captcha.css('opacity','0.5'); $.ajax({ type : 'POST', url : Options.ajaxurl, data : {action: 'bookly_captcha_refresh', form_id: Options.form_id, csrf_token : BooklyL10n.csrf_token}, dataType : 'json', xhrFields : {withCredentials: true}, crossDomain : 'withCredentials' in new XMLHttpRequest(), success : function (response) { if (response.success) { $captcha.attr('src', response.data.captcha_url).on('load', function() { $captcha.css('opacity', '1'); }); } } }); }); } } }); } /** * Payment step. */ function stepPayment() { $.ajax({ type : 'POST', url : Options.ajaxurl, data : {action: 'bookly_render_payment', csrf_token : BooklyL10n.csrf_token, form_id: Options.form_id, page_url: document.URL.split('#')[0]}, dataType : 'json', xhrFields : {withCredentials: true}, crossDomain: 'withCredentials' in new XMLHttpRequest(), success : function (response) { if (response.success) { // If payment step is disabled. if (response.disabled) { save(); return; } $container.html(response.html); scrollTo($container); if (Options.status.booking == 'cancelled') { Options.status.booking = 'ok'; } var $payments = $('.bookly-payment', $container), $apply_coupon_button = $('.bookly-js-apply-coupon', $container), $coupon_input = $('input.bookly-user-coupon', $container), $coupon_error = $('.bookly-js-coupon-error', $container), $coupon_info_text = $('.bookly-info-text-coupon', $container), $bookly_payment_nav = $('.bookly-payment-nav', $container), $buttons = $('.bookly-gateway-buttons,form.bookly-authorize-net,form.bookly-stripe', $container) ; $payments.on('click', function() { $buttons.hide(); $('.bookly-gateway-buttons.pay-' + $(this).val(), $container).show(); if ($(this).val() == 'card') { $('form.bookly-' + $(this).data('form'), $container).show(); } }); $payments.eq(0).trigger('click'); $apply_coupon_button.on('click', function (e) { var ladda = ladda_start(this); $coupon_error.text(''); $coupon_input.removeClass('bookly-error'); var data = { action : 'bookly_apply_coupon', csrf_token : BooklyL10n.csrf_token, form_id : Options.form_id, coupon : $coupon_input.val() }; $.ajax({ type : 'POST', url : Options.ajaxurl, data : data, dataType : 'json', xhrFields : {withCredentials: true}, crossDomain : 'withCredentials' in new XMLHttpRequest(), success : function (response) { if (response.success) { $coupon_info_text.html(response.text); $coupon_input.replaceWith(data.coupon); $apply_coupon_button.replaceWith('✓'); if (response.total <= 0) { $bookly_payment_nav.hide(); $buttons.hide(); $('.bookly-gateway-buttons.pay-coupon', $container).show(); $('.bookly-coupon-free', $container).attr('checked', 'checked').val(data.coupon); } else { // Set new price for payment request $('input.bookly-payment-amount', $container).val(response.total); var $payu_latam_form = $('.bookly-payu_latam-form', $container); if ($payu_latam_form.length) { $.post(Options.ajaxurl, {action: 'bookly_payu_latam_refresh_tokens', csrf_token : BooklyL10n.csrf_token, form_id: Options.form_id}) .done(function(response){ if (response.success) { $payu_latam_form.find('input[name=referenceCode]').val(response.data.referenceCode); $payu_latam_form.find('input[name=signature]').val(response.data.signature); } }, 'json' ); } } } else if (response.error_code == 6) { $coupon_error.html(response.error); $coupon_input.addClass('bookly-error'); $coupon_info_text.html(response.text); scrollTo($coupon_error); } ladda.stop(); }, error : function () { ladda.stop(); } }); }); $('.bookly-js-next-step', $container).on('click', function (e) { var ladda = ladda_start(this), $form ; if ($('.bookly-payment[value=local]', $container).is(':checked') || $(this).hasClass('bookly-js-coupon-payment')) { // handle only if was selected local payment ! e.preventDefault(); save(); } else if ($('.bookly-payment[value=card]', $container).is(':checked')) { var stripe = $('.bookly-payment[data-form=stripe]', $container).is(':checked'); var card_action = stripe ? 'bookly_stripe' : 'bookly_authorize_net_aim'; $form = $container.find(stripe ? '.bookly-stripe' : '.bookly-authorize-net'); e.preventDefault(); var data = { action: card_action, csrf_token: BooklyL10n.csrf_token, card: { number : $form.find('input[name="card_number"]').val(), cvc : $form.find('input[name="card_cvc"]').val(), exp_month: $form.find('select[name="card_exp_month"]').val(), exp_year : $form.find('select[name="card_exp_year"]').val() }, form_id: Options.form_id }; var card_payment = function (data) { $.ajax({ type : 'POST', url : Options.ajaxurl, data : data, dataType : 'json', xhrFields : {withCredentials: true}, crossDomain: 'withCredentials' in new XMLHttpRequest(), success : function (response) { if (response.success) { stepComplete(); } else if (response.error_code == 3) { handle_error_3(response); } else if (response.error_code == 7) { ladda.stop(); $form.find('.bookly-js-card-error').text(response.error); } } }); }; if (stripe && $form.find('#publishable_key').val()) { try { Stripe.setPublishableKey($form.find('#publishable_key').val()); Stripe.createToken(data.card, function (status, response) { if (response.error) { $form.find('.bookly-js-card-error').text(response.error.message); ladda.stop(); } else { // Token from stripe.js data['card'] = response['id']; card_payment(data); } }); } catch (e) { $form.find('.bookly-js-card-error').text(e.message); ladda.stop(); } } else { card_payment(data); } } else if ( $('.bookly-payment[value=paypal]', $container).is(':checked') || $('.bookly-payment[value=2checkout]', $container).is(':checked') || $('.bookly-payment[value=payu_latam]', $container).is(':checked') || $('.bookly-payment[value=payson]', $container).is(':checked') || $('.bookly-payment[value=mollie]', $container).is(':checked') ) { e.preventDefault(); $form = $(this).closest('form'); if ($form.find('input.bookly-payment-id').length > 0 ) { $.ajax({ type : 'POST', url : Options.ajaxurl, xhrFields : {withCredentials: true}, crossDomain: 'withCredentials' in new XMLHttpRequest(), data : { action: 'bookly_save_pending_appointment', csrf_token: BooklyL10n.csrf_token, form_id: Options.form_id, payment_type: $form.data('gateway') }, dataType : 'json', success : function (response) { if (response.success) { $form.find('input.bookly-payment-id').val(response.payment_id); $form.submit(); } else if (response.error_code == 3) { handle_error_3(response); } } }); } else { $.ajax({ type : 'POST', url : Options.ajaxurl, xhrFields : {withCredentials: true}, crossDomain: 'withCredentials' in new XMLHttpRequest(), data : {action: 'bookly_check_cart', csrf_token : BooklyL10n.csrf_token, form_id: Options.form_id}, dataType : 'json', success : function (response) { if (response.success) { $form.submit(); } else if (response.error_code == 3) { handle_error_3(response); } } }); } } }); $('.bookly-js-back-step', $container).on('click', function (e) { e.preventDefault(); ladda_start(this); stepDetails(); }); } } }); } /** * Complete step. */ function stepComplete() { if (Options.final_step_url) { document.location.href = Options.final_step_url; } else { $.ajax({ url: Options.ajaxurl, data: {action: 'bookly_render_complete', csrf_token: BooklyL10n.csrf_token, form_id: Options.form_id}, dataType: 'json', xhrFields: {withCredentials: true}, crossDomain: 'withCredentials' in new XMLHttpRequest(), success: function (response) { if (response.success) { $container.html(response.html); scrollTo($container); } } }); } } // =========== helpers =================== // function save() { $.ajax({ type : 'POST', url : Options.ajaxurl, xhrFields : { withCredentials: true }, crossDomain : 'withCredentials' in new XMLHttpRequest(), data : { action : 'bookly_save_appointment', csrf_token : BooklyL10n.csrf_token, form_id : Options.form_id }, dataType : 'json' }).done(function(response) { if (response.success) { stepComplete(); } else if (response.error_code == 3) { handle_error_3(response); } }); } function ladda_start(elem) { var ladda = Ladda.create(elem); ladda.start(); return ladda; } /** * Handle error with code 3 which means one of the cart item is not available anymore. * * @param response */ function handle_error_3(response) { if (Options.cart.enabled) { stepCart(undefined, { failed_key : response.failed_cart_key, message : response.error }); } else { stepTime(undefined, response.error); } } /** * Scroll to element if it is not visible. * * @param $elem */ function scrollTo($elem) { var elemTop = $elem.offset().top; var scrollTop = $(window).scrollTop(); if (elemTop < $(window).scrollTop() || elemTop > scrollTop + window.innerHeight) { $('html,body').animate({ scrollTop: (elemTop - 24) }, 500); } } }; })(jQuery);