//const faServer = 'http://localhost:7071'; // LocalTest //const faServer = 'https://vetbook.azurewebsites.net'; // JCD Vis Studio const faServer = 'https://gvgbook.azurewebsites.net'; // JCD Vis Studio var VaildityInfo = { "lastname": { length: 3, func: null }, "email": { length: 9, func: validateEmail }, "petname": { length: 2, func: null }, "phonenumber": { length: 11, func: null }, "preferreddate": { length: 3, func: null }, "apptreason": { length: 3, func: null } }; $(document).ready(function () { $("#requestslot").hide(); stethoscope(); // Clear the form to a known state $("#lastname").val(getUrlParam('lastname', '')); $("#email").val(getUrlParam('email', '')); $("#petname").val(getUrlParam('petname', '')); $("#phonenumber, #apptreason, #preferreddate").val(""); $("#PreferredTime_0").prop('checked', true); $("#PreferredTime_1, #PreferredTime_2, #PreferredTime_3, #PreferredTime_4").prop('checked', false); $("#exsistingclient_0, #exsistingclient_1").prop('checked', false); var appttype = getUrlParam('appttype', '0'); $("#appointmenttype_0").prop('checked', appttype === '0'); $("#appointmenttype_1").prop('checked', appttype === '1'); $("#appointmenttype_2").prop('checked', appttype === '2'); if (appttype !== '0') ChangeCollapseCSS("#divapptreason", HIDESECTION); else ChangeCollapseCSS("#divapptreason", SHOWSECTION); $("#chosenbranch").prop('selectedIndex', -1); // Time checkboxes $("#PreferredTime_0").change(function () { $("#PreferredTime_1, #PreferredTime_2, #PreferredTime_3, #PreferredTime_4").prop('checked', false); }); $("#PreferredTime_1, #PreferredTime_2, #PreferredTime_3, #PreferredTime_4").change(function () { $("#PreferredTime_0").prop('checked', false); }); // Validity checks $("#lastname").on("keyup", function () { markValid("lastname", false) }); $("#email").on("keyup", function () { markValid("email", false) }); $("#petname").on("keyup", function () { markValid("petname", false) }); // Registered/Not Registered bclick and locate details $("input[type=radio][name='exsistingclient']").change(function () { failedCheck = null; $("#lastname, #email, #petname").each(function () { if (markValid(this.id, true) === false && failedCheck === null) failedCheck = this.id; }); if (failedCheck !== null) { $('#' + failedCheck).focus(); ChangeCollapseCSS("#FormSection1Errors", SHOWSECTION); $("#exsistingclient_0, #exsistingclient_1").prop('checked', false); //e.preventDefault(); FCS_postHeightToParent(); return; } ChangeCollapseCSS("#FormSection1Errors", HIDESECTION); selected_value = $("input[type=radio][name='exsistingclient']:checked").val(); if (selected_value === 'newclient') { NewDetailsSection(SHOWSECTION); } else if (selected_value === 'yes') { NewDetailsSection(HIDESECTION); locateDetails(); //e.preventDefault(); } FCS_postHeightToParent(); }); $("#phonenumber").on("keyup", function () { markValid("phonenumber", false) }); $("#chosenbranch").change(function () { branch = $("#chosenbranch").val(); if (branch !== "") { failedCheck = null; $("#lastname, #email, #petname, #phonenumber").each(function () { if (markValid(this.id, true) === false && failedCheck === null) failedCheck = this.id; }); if (failedCheck !== null) { $('#' + failedCheck).focus(); ChangeCollapseCSS("#FormSection1Errors", SHOWSECTION); $("#exsistingclient_0, #exsistingclient_1").prop('checked', false); $("#chosenbranch").prop('selectedIndex', -1); FCS_postHeightToParent(); return; } ChangeCollapseCSS("#getdetails", HIDESECTION); ChangeCollapseCSS("#getdates", SHOWSECTION); ShowDetailsSummary($("#chosenbranch option:selected").text(), $("#lastname").val(), false, true); $("#requestslot").show(); $("#preferreddate").focus(); FCS_postHeightToParent(); } }); $("input[type=radio][name='appointmenttype']").change(function () { selected_value = $("input[type=radio][name='appointmenttype']:checked").val(); if (selected_value === 'Vet consultation') ChangeCollapseCSS("#divapptreason", SHOWSECTION); else { ChangeCollapseCSS("#divapptreason", HIDESECTION); $('#apptreason').val(""); resetValidityCSS('#apptreason'); } FCS_postHeightToParent(); }); $("#apptreason").on("keyup", function () { markValid("apptreason", false) }); $("#preferreddate").on("keyup", function () { markValid("preferreddate", false) }); /* Show hide the Calendar */ $('#calendar').on("click", function (e) { var today = new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate()); $('#preferreddate').datepicker( { minDate: today, modal: true, format: 'dd mmmm yyyy', close: function (e) { $('#preferreddate').datepicker().destroy(); $('#preferreddate').removeClass().addClass("form-control"); } }).open(); FCS_postHeightToParent(); }); $('#requestslot').on("click", function (e) { faulting = null; if (markValid('preferreddate', true) === false) faulting = '#preferreddate'; if ($("input[type=radio][name='appointmenttype']:checked").val() === 'Vet consultation' && markValid('apptreason', true) === false) faulting = '#apptreason'; if (faulting !== null) { $(faulting).focus(); ChangeCollapseCSS("#FormSection2Errors", SHOWSECTION) e.preventDefault(); FCS_postHeightToParent(); return; } ChangeCollapseCSS("#FormSection2Errors", HIDESECTION) hideServiceNotice(); ChangeCollapseCSS("#requestslotDiv", HIDESECTION); ChangeCollapseCSS("#getdates", HIDESECTION); $("#requestslot").hide(); ChangeCollapseCSS("#firstSpinner", SHOWSECTION); startSlotSearch(); e.preventDefault(); FCS_postHeightToParent(); }); $('#differentdates').on("click", function (e) { hideServiceNotice(); ChangeCollapseCSS("#firstSpinner", HIDESECTION); ChangeCollapseCSS("#availableSlots", HIDESECTION); ChangeCollapseCSS("#requestslotDiv", SHOWSECTION); ChangeCollapseCSS("#getdates", SHOWSECTION); $("#requestslot").show(); $("#preferreddate").focus(); e.preventDefault(); FCS_postHeightToParent(); }); /* Booking a slotoptions */ $('#bookslot').on("click", function (e) { ChangeCollapseCSS("#bookslotDiv", HIDESECTION); ChangeCollapseCSS("#secondSpinner", SHOWSECTION); bookSlot(); e.preventDefault(); FCS_postHeightToParent(); }); FCS_postHeightToParent(); }); /* * ===========================Doc Ready Ends ======================================================= */ function resetValidityCSS(id) { $(id).removeClass("is-valid"); $(id).removeClass("is-invalid"); } function markValid(controlID, markInvalid) { id = '#' + controlID; minLength = VaildityInfo[controlID].length; extraCheck = VaildityInfo[controlID].func; resetValidityCSS(id); if ($(id).val() !== "" && $(id).val().length >= minLength && (extraCheck === null || extraCheck($(id).val()) === true)) { $(id).addClass("is-valid"); return true; } if (markInvalid) $(id).addClass("is-invalid"); return false; } function validateEmail(email) { const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return re.test(email); } /* Processing for find slots */ function startSlotSearch() { // Kick off the initial request fetch(faServer + '/api/startSlotSearch', { method: 'POST', // *GET, POST, PUT, DELETE, etc. mode: 'cors', // no-cors, *cors, same-origin cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached credentials: 'same-origin', // include, *same-origin, omit headers: { 'Content-Type': 'application/json' }, redirect: 'follow', // manual, *follow, error referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url body: JSON.stringify($('form').serializeJSON()) // body data type must match "Content-Type" header }) .then(res => res.json()) .then((out) => { startSlotSearchResults(out); console.log('Output: ', out); FCS_postHeightToParent(); }).catch(err => { ChangeCollapseCSS("#firstSpinner", HIDESECTION); showServiceNotice("alert-danger", "Remote Service Not Working!", "We are sorry we cannot complete your booking, many apologies.</br>Please try again later."); FCS_postHeightToParent(); }); } function startSlotSearchResults(results) { if (results.status === 'proceed') { showServiceNotice("alert-info", "", results.msg); getSlots(results.monitorToken); } else { ChangeCollapseCSS("#firstSpinner", HIDESECTION); showServiceNotice("alert-danger", "Date Error!", "Sorry, I do not understand the dates you have specified, please try again or use the calendar."); $("#requestslotDiv").removeClass().addClass("collapse.show"); ChangeCollapseCSS("#getdates", SHOWSECTION); ChangeCollapseCSS("#requestslotDiv", SHOWSECTION); $("#requestslot").show(); $("#preferreddate").focus(); } } // Status OK - Problem, Message and ? function showServiceNotice(colour, heading, body) { $("#serviceNoticeHeading").html(heading); $("#serviceNoticeBody").html(body); $("#serviceNotice").removeClass().addClass("alert").addClass(colour).addClass("collapse.show"); } // Status OK - Problem, Message and ? function hideServiceNotice() { $("#serviceNotice").removeClass().addClass("collapse"); } function getSlots(monitorToken) { function showGetSlotsResults(result) { ChangeCollapseCSS("#firstSpinner", HIDESECTION); ChangeCollapseCSS("#availableSlots", SHOWSECTION); $("#slotresponseid").val(result.slotresponseid); // add them in $("#slotoptions").html(""); result.slots.forEach((item, index) => { $("#slotoptions").append(buildSlotOption(index, item)); }); if (result.outsidePreferred === true) ChangeCollapseCSS("#outsidePreferred", SHOWSECTION); else ChangeCollapseCSS("#outsidePreferred", HIDESECTION); ChangeCollapseCSS("#bookslotDiv", SHOWSECTION); $("#bookslot").focus(); $(document).scrollTop($(document).height()); FCS_postHeightToParent(); } async function subscribe() { let response = await fetch(faServer + '/api/getSlots/' + monitorToken, { method: 'GET', //mode: 'no-cors', cache: 'no-cache', credentials: 'same-origin', redirect: 'follow', referrerPolicy: 'no-referrer', }); if (response.status === 504) { // Timeout await subscribe(); } else if (response.status !== 200) { await new Promise(resolve => setTimeout(resolve, 1000)); await subscribe(); } else { // Got message let message = await response.json(); showGetSlotsResults(message); } } subscribe(); } function buildSlotOption(index, item) { var text = '<div class="custom-control custom-radio">' + '<input class="custom-control-input" type="radio" name="appointmentSlot" id="appointmentSlot_' + index + '" value="' + item.slotID + '" ' + (index === 0 ? "checked=checked" : "") + ' />' + '<label class="custom-control-label" for="appointmentSlot_' + index + '">' + item.slotname + '</label>' + '</div > '; return text; } /* Kick the server to wake it up */ function stethoscope() { fetch(faServer + '/api/heartbeat', { method: 'GET', // *GET, POST, PUT, DELETE, etc. mode: 'no-cors', // no-cors, *cors, same-origin cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached credentials: 'same-origin', // include, *same-origin, omit redirect: 'follow', // manual, *follow, error referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url }) .then(res => { console.log('Output: ', res.status); }).catch(err => { console.log('Error: ', err); }); } /* Helper functions */ const HIDESECTION = false; const SHOWSECTION = true; function NewDetailsSection(visibility) { if (visibility === SHOWSECTION) { ChangeCollapseCSS("#collapseBranch", SHOWSECTION); ChangeCollapseCSS("#RegisteredSpinner", HIDESECTION); ChangeCollapseCSS("#RegisteredServiceNotice", HIDESECTION); $("#chosenbranch option:selected").attr("selected", null); } else { ChangeCollapseCSS("#collapseBranch", HIDESECTION); ChangeCollapseCSS("#RegisteredSpinner", SHOWSECTION); } } function ChangeCollapseCSS(id, visibility) { if (visibility === SHOWSECTION) $(id).removeClass("collapse").addClass("collapse.show"); else $(id).removeClass("collapse.show").addClass("collapse"); } function ShowDetailsSummary(branch, clientname, newpet, newclient) { ChangeCollapseCSS("#collapseBranch", HIDESECTION); ChangeCollapseCSS("#SummaryDetails", SHOWSECTION); strNewpet = (newpet) ? " (New Pet)" : ""; strNewclient = (newclient) ? " (New Client)" : ""; strBranchLabel = (newclient) ? "Selected Branch" : "Registered Branch"; strPhoneNo = (newclient) ? " / " + $("#phonenumber").val() : ""; txt = "Your Name: <B>" + clientname + strNewclient + "</B><br>" + "Contact Details:- <B>" + $("#email").val() + strPhoneNo + "</B><br>" + "Pet's Name:- <B>" + $("#petname").val() + strNewpet + "</B><br>" + strBranchLabel + ":- <B>" + branch; $("#SummaryDetailsTA").html(txt); } function locateDetails() { subscribe(); async function subscribe() { let response = await fetch(faServer + '/api/checkreg', { method: 'POST', //mode: 'Access-Control-Allow-Origin', cache: 'no-cache', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, redirect: 'follow', // manual, *follow, error referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url body: JSON.stringify($('form').serializeJSON()) // body data type must match "Content-Type" header }).catch(err => { $("#firstSpinner").removeClass("collapse.show").addClass("collapse"); showServiceNotice("alert-danger", "Remote Service Not Working!", "We are sorry we cannot complete your booking, many apologies.</br>Please try again later."); }); if (response.status === 504) await subscribe(); else if (response.status !== 200) { await new Promise(resolve => setTimeout(resolve, 1000)); await subscribe(); } else { // Got message let message = await response.json(); if (message.linkfailed) { $("#firstSpinner").removeClass("collapse.show").addClass("collapse"); showLocateServiceNotice("alert-danger", "Remote Service Not Working!", "We are sorry we cannot complete your booking, many apologies.</br>Please try again later."); } else showLocateDetailsResults(message); } } } function bookSlot() { function showBookSlotResults(result) { ChangeCollapseCSS("#secondSpinner", HIDESECTION); switch (result) { case "Booked": $("#bookedNoticeHeading").html("All Booked"); $("#bookedNoticeBody").html("Your appointment, " + $("input[type=radio][name='appointmentSlot']:checked").next('label:first').html() + " has been booked and we look forward to seeing you. You should shortly receive an email confirming the details."); ChangeCollapseCSS("#availableSlots", HIDESECTION); ChangeCollapseCSS("#serviceNotice", HIDESECTION); ChangeCollapseCSS("#bookedNotice", SHOWSECTION); break; case "FailedToGetSlot": showServiceNotice("alert-danger", "This appointment time is currently unavailable", "We’re very sorry but another user has just booked this appointment. Please try selecting an alternative time."); ChangeCollapseCSS("#availableSlots", HIDESECTION); $("#slotoptions").html(""); ChangeCollapseCSS("#getdates", SHOWSECTION); ChangeCollapseCSS("#requestslotDiv", SHOWSECTION); $("#requestslot").show(); $("#preferreddate").focus(); break; default: } // yes all over no allow rebook $(document).scrollTop($(document).height()); } async function subscribe() { hideServiceNotice(); let response = await fetch(faServer + '/api/bookSlot', { method: 'POST', //mode: 'no-cors', cache: 'no-cache', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, redirect: 'follow', referrerPolicy: 'no-referrer', body: JSON.stringify($('form').serializeJSON()) }).catch(err => { ChangeCollapseCSS("#SecondSpinner", HIDESECTION); showServiceNotice("alert-danger", "Remote Service Not Working!", "We are sorry we cannot complete your booking, many apologies.</br>Please try again later."); }); if (response.status === 504) { // Timeout await subscribe(); } else if (response.status !== 200) { await new Promise(resolve => setTimeout(resolve, 1000)); await subscribe(); } else { // Got message let message = await response.text(); showBookSlotResults(message); } } subscribe(); } function showLocateServiceNotice(colour, heading, body) { $("#RegisteredServiceNoticeHeading").html(heading); $("#RegisteredServiceNoticeBody").html(body); ChangeCollapseCSS("#RegisteredServiceNotice", SHOWSECTION); } // Status OK - Problem, Message and ? function hideLocateServiceNotice() { ChangeCollapseCSS("#RegisteredServiceNotice", HIDESECTION); } function showLocateDetailsResults(result) { ChangeCollapseCSS("#RegisteredSpinner", HIDESECTION); if (result.response.registeredbranchshortcode !== '') $('#chosenbranch').val(result.response.registeredbranchshortcode); if (result.response.foundclient && result.response.foundpet) { ChangeCollapseCSS("#getdetails", HIDESECTION); ChangeCollapseCSS("#getdates", SHOWSECTION); ShowDetailsSummary($("#chosenbranch option:selected").text(), result.response.clientname, false, false); $("#requestslot").show(); } else if (result.response.foundclient && !result.response.foundpet) { heading = "Pet not found"; body = "We believe your details are :- <b>" + result.response.clientname + ", registered @ " + $("#chosenbranch option:selected").text() + "</b></br>" + "However, we cannot find a pet named:- <b>" + $("#petname").val() + "</b>, registered to you, perhaps this is a new pet ?</br>" + "Please select one of the options below, thank you<br>"; showLocateServiceNotice("alert-warning", heading, body) $("#RSNButton1").html("Alter pet details above").off("click").on("click", function () { hideLocateServiceNotice(); $("#exsistingclient_0").prop('checked', false); $("#petname").focus(); } ); $("#RSNButton2").html("Continue with new pet").off("click").on("click", function () { ChangeCollapseCSS("#getdetails", HIDESECTION); hideLocateServiceNotice(); ShowDetailsSummary($("#chosenbranch option:selected").text(), result.response.clientname, true, true); ChangeCollapseCSS("#getdates", SHOWSECTION); $("#requestslot").show(); $("#preferreddate").focus(); } ); } else { heading = "Client not found"; body = "I am sorry we cannot find you registered at any of our branches. Perhaps you have supplied an email or telephone number that is not the one registered with us ?<br>Please select one of the options below, thank you<br>"; showLocateServiceNotice("alert-warning", heading, body) $("#RSNButton1").html("Alter details above").off("click").on("click", function () { hideLocateServiceNotice(); $("#exsistingclient_0").prop('checked', false); $("#lastname").focus(); } ); $("#RSNButton2").html("Continue as new client").off("click").on("click", function () { hideLocateServiceNotice(); $("#exsistingclient_0").prop('checked', false); $("#exsistingclient_1").prop('checked', true); $("#chosenbranch option:selected").attr("selected", null); $("#collapseBranch").removeClass("collapse").addClass("collapse.show"); } ); $(document).scrollTop($(document).height()); } FCS_postHeightToParent(); } function FCS_postHeightToParent() { if (!inIframe) return; var height = $('#bookFormHeader').outerHeight(true) + $('#bookForm').outerHeight(true) // var targetOrigin = 'http://goddard-vets.local/book-an-appointment/'; var targetOrigin = '*'; parent.postMessage(height + 'px', targetOrigin); } function inIframe() { try { return window.self !== window.top; } catch (e) { return true; } } function getUrlVars() { var vars = {}; var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (m, key, value) { vars[key] = value; }); return vars; } function getUrlParam(parameter, defaultvalue) { var urlparameter = defaultvalue; if (window.location.href.indexOf(parameter) > -1) { urlparameter = decodeURI(getUrlVars()[parameter]); } return urlparameter; }