Id like to show the number of available appointment slots next to the times on the Appointment Booking Widget, does anyone know how to do this?

Jeffrey Siegel
Mega Sage

Im trying to customize the UI for the Walkup Experience, Appointment Booking Widget.  Ive been able to show the window on the button by editing the HTML, however id also like to display the number of available slots for each window.  i cant figure out how to get this information.  has anyone done this???

this is what it looks like now:

find_real_file.png

Id like it to look something like this:

find_real_file.png

 

1 ACCEPTED SOLUTION

Jeffrey Siegel
Mega Sage

 This took a while to figure out but was actually pretty simple...

I edited the Widget Appointment Booking

For Server Script:

so first i had to grab from the config the number of appts allowed per time slot and pass to the data variable to access within client script (data.bookingSlotsTotal).  I added these two scripts to the top of the server script area around line 12

//get max number of appts per window from config table,  table has only 1 record, so query and grab the data
	var bookingSlots = new GlideRecord('sn_apptmnt_booking_service_config');
	bookingSlots.query();
	while(bookingSlots.next()){
		data.bookingSlotsTotal = parseInt(bookingSlots.appointments_per_bookable_slot);
	}

Then i had to query the appointment table to grab a list of all the appts made, i dont care who or where or what it is for, just care about the actual slots, so i built an array of these slots and passed them to the client script throught data.apps.  I added this at around line 19

//get an array of the appt slots currently taken for current location id from url, pass to client script so they can be evaluated against the calendar. 
	var location_id = $sp.getParameter('location_id'); //grab location from URL
	data.appts = []; //start my array
		var queryText = 'stateNOT IN3,4,7^window_startRELATIVEGT@minute@ago@0^wu_location_queue='+location_id; //grabs all records that are not cancelled, Closed Complete or Closed Incomplete, that the start date is in the future, and the location is for the location from URL.
		var avail = new GlideRecord('wu_appointment');
		avail.addEncodedQuery(queryText);
	
		avail.query();
	while(avail.next()){
		var apptsArr = "";
		apptsArr = avail.getDisplayValue('window_start');
		data.appts.push(apptsArr); //push the value into my declared array and have access to it in client since its a data object.
	}

 

For Client Script:

i added the following script to the top of the client script around line 10.  This grabs the data from the server script to use lower in the client script.

//grab the array of appts scheduled from server script
	var appts = c.data.appts;
	//gets the number of appts currently booked for the available booking window by looking at the length of the booked appts array
	var length = appts.length;
	//gets the total booking slots allowed from server script
	var totalApptsAllowed = c.data.bookingSlotsTotal;

at around line 350 youll see the actualWindow being declared from window values this is the bottom of the main client script, below here are supporting functions to help work with the data, here, above the //TODO which categorizes the slots into morning, day and night categories,  i created a new value called actualWindow.slots which i will be referencing in my HTML.  I created two functions to evaluate the date selected vs the dates being passed for selected appointments.

actualWindow.slots = findAppt(reformatDate(window.start_date),appts);

my two functions go right below the closure of the main client script at around line 358

This first function reformatDate, reformats the order of the date field so that when i go to see if the appointment slot matches a booked appointment, they are the same format:

function reformatDate(date){
		var dateTimePieces = date.split(" ");
		var datePiece = dateTimePieces[0];
		var dateSplit = datePiece.split("-");
		var newDate = dateSplit[1]+"-"+dateSplit[2]+"-"+dateSplit[0]+" "+dateTimePieces[1];
		newDate = newDate.slice(0,newDate.length-3);
	//	alert(newDate);
		return newDate;
	}

This second function actually performs the comparison using the reformatted date above:

it sets a counter to 0, then ensures theres appointments booked within the window, if theres none, it skips the comparison since its not needed and returns the number of available slots for each window

	function findAppt(date, apptsObj){
	var count = 0
		if(length > 0){
			for(var aa=0; aa < length; aa++){
				if(apptsObj[aa] == date){
					count++
				}
			}
		}
		var availAppts = totalApptsAllowed - count;
		return availAppts;
	}
	

 

now that i have my slots i need to put them into my html code.  i see two spots for these at the time of this posting, one for mobile and one for regular.

your looking for the div class="content" block to inject your variables.

where you see the {{appointmentWindow.start}} variables (i see them 2x in this block) i added

({{appointmentWindow.slots}})&nbsp;

which will show (2) next to the start time. or whatever number of slots are available.

 

if you are able to follow this, then it should be working!

let me know below how it fared for you!!!

View solution in original post

4 REPLIES 4

Jeffrey Siegel
Mega Sage

 This took a while to figure out but was actually pretty simple...

I edited the Widget Appointment Booking

For Server Script:

so first i had to grab from the config the number of appts allowed per time slot and pass to the data variable to access within client script (data.bookingSlotsTotal).  I added these two scripts to the top of the server script area around line 12

//get max number of appts per window from config table,  table has only 1 record, so query and grab the data
	var bookingSlots = new GlideRecord('sn_apptmnt_booking_service_config');
	bookingSlots.query();
	while(bookingSlots.next()){
		data.bookingSlotsTotal = parseInt(bookingSlots.appointments_per_bookable_slot);
	}

Then i had to query the appointment table to grab a list of all the appts made, i dont care who or where or what it is for, just care about the actual slots, so i built an array of these slots and passed them to the client script throught data.apps.  I added this at around line 19

//get an array of the appt slots currently taken for current location id from url, pass to client script so they can be evaluated against the calendar. 
	var location_id = $sp.getParameter('location_id'); //grab location from URL
	data.appts = []; //start my array
		var queryText = 'stateNOT IN3,4,7^window_startRELATIVEGT@minute@ago@0^wu_location_queue='+location_id; //grabs all records that are not cancelled, Closed Complete or Closed Incomplete, that the start date is in the future, and the location is for the location from URL.
		var avail = new GlideRecord('wu_appointment');
		avail.addEncodedQuery(queryText);
	
		avail.query();
	while(avail.next()){
		var apptsArr = "";
		apptsArr = avail.getDisplayValue('window_start');
		data.appts.push(apptsArr); //push the value into my declared array and have access to it in client since its a data object.
	}

 

For Client Script:

i added the following script to the top of the client script around line 10.  This grabs the data from the server script to use lower in the client script.

//grab the array of appts scheduled from server script
	var appts = c.data.appts;
	//gets the number of appts currently booked for the available booking window by looking at the length of the booked appts array
	var length = appts.length;
	//gets the total booking slots allowed from server script
	var totalApptsAllowed = c.data.bookingSlotsTotal;

at around line 350 youll see the actualWindow being declared from window values this is the bottom of the main client script, below here are supporting functions to help work with the data, here, above the //TODO which categorizes the slots into morning, day and night categories,  i created a new value called actualWindow.slots which i will be referencing in my HTML.  I created two functions to evaluate the date selected vs the dates being passed for selected appointments.

actualWindow.slots = findAppt(reformatDate(window.start_date),appts);

my two functions go right below the closure of the main client script at around line 358

This first function reformatDate, reformats the order of the date field so that when i go to see if the appointment slot matches a booked appointment, they are the same format:

function reformatDate(date){
		var dateTimePieces = date.split(" ");
		var datePiece = dateTimePieces[0];
		var dateSplit = datePiece.split("-");
		var newDate = dateSplit[1]+"-"+dateSplit[2]+"-"+dateSplit[0]+" "+dateTimePieces[1];
		newDate = newDate.slice(0,newDate.length-3);
	//	alert(newDate);
		return newDate;
	}

This second function actually performs the comparison using the reformatted date above:

it sets a counter to 0, then ensures theres appointments booked within the window, if theres none, it skips the comparison since its not needed and returns the number of available slots for each window

	function findAppt(date, apptsObj){
	var count = 0
		if(length > 0){
			for(var aa=0; aa < length; aa++){
				if(apptsObj[aa] == date){
					count++
				}
			}
		}
		var availAppts = totalApptsAllowed - count;
		return availAppts;
	}
	

 

now that i have my slots i need to put them into my html code.  i see two spots for these at the time of this posting, one for mobile and one for regular.

your looking for the div class="content" block to inject your variables.

where you see the {{appointmentWindow.start}} variables (i see them 2x in this block) i added

({{appointmentWindow.slots}})&nbsp;

which will show (2) next to the start time. or whatever number of slots are available.

 

if you are able to follow this, then it should be working!

let me know below how it fared for you!!!

Ok so my requirement changed, we are now using multiple different time slots per location so i had to start over from scratch since my original method only allowed one set of window times per location,  this new method lets you setup for a location between these dates/times to have its specific window and slots per window,  you can grow this to many different slots per day/week/month/season etc...

the result will look something like this:

JeffreySiegel_0-1666107577221.png

to allow for this, the location needs to have the enable day level configuration activated...

you can do this by going to Appointment Booking -> Appointment Booking Configuration - Walk-up Experience and from the related list Appointment Booking Service Configuration, add/edit a location.

check the box for Enable Day level configuration (if you dont see this, ensure your view is set to default).

Once enabled a new related list will display allowing you to setup the custom schedule you want to create.  (note, my script does not use the bookable days function as of yet,  if i add this functionality, i will add a new post here)

Widget: Appointment Booking

For server script i adjusted the original code to allow for the original intent or the new (this is to replace the code originally indicated it should be inserted around line 12:

//get max number of appts per window from config table,  table has only 1 record, so query and grab the data
	var bookingSlots = new GlideRecord('wu_location_queue');
	bookingSlots.addQuery('sys_id', location_id);
	bookingSlots.query();
	while(bookingSlots.next()){
		data.daySchedule = bookingSlots.appointment_booking.enable_day_level_config;
//if no day schedule is enabled just take the one slot for the location
		if(!data.daySchedule){
		data.bookingSlotsTotal = parseInt(bookingSlots.appointment_booking.appointments_per_bookable_slot);
		}
//if a day schedule has been configured grab it and store each record as a JSON within an array to be passed to client to be evaluated
		else{
			data.bookingSchedule = [];
			data.dayLevelSysId = bookingSlots.appointment_booking.sys_id;
			var gr = new GlideRecord('sn_apptmnt_booking_day_configuration');
			gr.addQuery('service_configuration', data.dayLevelSysId); 
			gr.addQuery('active',true);
			gr.query();
			var j = 0;
			var str2 = "{";
			var length = gr.getRowCount();
			while(gr.next()){
				var str = {"name" : gr.name.getDisplayValue(), "start_date" : gr.start_date.getDisplayValue(), "end_date" : gr.end_date.getDisplayValue(), "daily_start_time": gr.daily_start_time.getDisplayValue(), "daily_end_time" : gr.daily_end_time.getDisplayValue(), "appointments_per_bookable_slot" : gr.appointments_per_bookable_slot.getDisplayValue()};
			var parser = new global.JSON();
				var JSONObject = parser.encode(str);
				data.bookingSchedule.push(JSONObject);
				}
			data.bookingSlotsTotal = "";
		}
	}

 

Adjust the client script:

for the entry around line 10, change it to:

	var appts = c.data.appts;
	var availAppts ;
	var length = appts.length;
	var dayLevelID = c.data.dayLevelSysId;
	var totalApptsAllowed = c.data.bookingSlotsTotal;
        var bookingSchedule = c.data.bookingSchedule;

around line 350 update the actualWindow.slots to be:

actualWindow.slots = findAppt(reformatDate(window.start_date),appts, actualWindow.start);

replace the findAppt function with my new function:

function findAppt(date, apptsObj, startTime){
	var count = 0
		if(length > 0){
			
			for(var aa=0; aa < length; aa++){
		if(apptsObj[aa] == date){
			count++
		}}}
		if(!c.data.daySchedule){
		availAppts = totalApptsAllowed - count;}
		else{
			availAppts = 10;
			var lookupsysid = c.data.dayLevelSysId;
			var windowD = date.split(" ");
			var windowDate = windowD[0];
	
			var a = 0
			while(a<bookingSchedule.length){
				var value = bookingSchedule[a];
				var test = JSON.parse(value);
				if((windowDate < test.end_date && windowDate > test.start_date)){
					if(test.daily_start_time <= startTime && test.daily_end_time >= startTime)
					availAppts = test.appointments_per_bookable_slot - count;				
			}
				a++;
		}
			}
		return availAppts;
	}

For the html, i wanted to see the window start/end times so i modified the appointmentWindow.start from my original post where i see div class="content" to:

{{appointmentWindow.start}}&nbsp;-&nbsp;{{appointmentWindow.end}}<br>{{appointmentWindow.slots}}&nbsp;Available

 good luck, and if anyone takes the time to develop the day of the week level configuration let me know, otherwise itll be my next stab at this project.

Ok i got the day of the week level configuration to work...
For Server Script i had to add the day of the week to the JSON object, 

replace:

 

var str = {"name" : gr.name.getDisplayValue(), "start_date" : gr.start_date.getDisplayValue(), "end_date" : gr.end_date.getDisplayValue(), "daily_start_time": gr.daily_start_time.getDisplayValue(), "daily_end_time" : gr.daily_end_time.getDisplayValue(), "appointments_per_bookable_slot" : gr.appointments_per_bookable_slot.getDisplayValue()};

 

with:

 

var str = {"name" : gr.name.getDisplayValue(), "start_date" : gr.start_date.getDisplayValue(), "end_date" : gr.end_date.getDisplayValue(), "daily_start_time": gr.daily_start_time.getDisplayValue(), "daily_end_time" : gr.daily_end_time.getDisplayValue(), "appointments_per_bookable_slot" : gr.appointments_per_bookable_slot.getDisplayValue(), "bookable_days" : gr.bookable_days.getDisplayValue()};

 

then in client script replace:

 

actualWindow.slots = findAppt(reformatDate(window.start_date),appts, actualWindow.start);

 

with:

 

actualWindow.slots = findAppt(reformatDate(window.start_date),appts, actualWindow.start, actualWindow.dayName);

 

and finally
 replace the function findAppt with:

 

function findAppt(date, apptsObj, startTime, dayName){
	var count = 0
		if(length > 0){
			
			for(var aa=0; aa < length; aa++){

		if(apptsObj[aa] == date){
			count++

		}}}
		var dayInt;
		if (dayName == "Monday")				{dayInt = "1";}
		else if (dayName == "Tuesday")	{dayInt = "2";}
		else if (dayName == "Wednesday"){dayInt = "3";}
		else if (dayName == "Thursday") {dayInt = "4";}
		else if (dayName == "Friday")		{dayInt = "5";}
		else if (dayName == "Saturday") {dayInt = "6";}
		else dayInt = "7";
		if(!c.data.daySchedule){
		availAppts = totalApptsAllowed - count;}
		else{
			availAppts = 10;
			var lookupsysid = c.data.dayLevelSysId;
			var windowD = date.split(" ");
			var windowDate = windowD[0];
			var a = 0
			while(a<bookingSchedule.length){
				var value = bookingSchedule[a];
				var test = JSON.parse(value);
				if((windowDate < test.end_date && windowDate > test.start_date)){
					if(test.bookable_days.indexOf(dayInt) >= 0 && (test.daily_start_time <= startTime && test.daily_end_time >= startTime))
					availAppts = test.appointments_per_bookable_slot - count;
				}
				a++;
			}
		}
		return availAppts;
	}

 

and it should now take the day of the week into consideration based on the config in the instance.

Enjoy!!

Hello,

I am facing a similar requirement.
I read the comments and edited the Appointment Booking widget as instructed, but the changes are not reflected.

 

 var appointmentWindow = {
            actualStart: '09:00',
            actualEnd: '09:30',
            available: true,
            scheduled: false,
            remainingSlots: 3 
        };

 


To verify the functionality, I added the above script to the server script and included the HTML as advised, but it is not working properly.

Here is the content of the HTML:

 

 <div class="content">
            <div ng-class="{'scheduledSlotContainer': c.checkIfMobileDevice() &&appointmentWindow.scheduled == true}" ng-if="c.slotsByDays[c.activeDate][key]" ng-repeat="appointmentWindow in c.getSlicedSlots(key)">
              <button ng-class="{'appointmentSlot': appointmentWindow.actualStart != c.selectedWindow.actualStart, 'scheduledSlot': appointmentWindow.scheduled == true }" class="disabledOverlay" ng-attr-uib-tooltip="{{appointmentWindow.scheduled == true && !c.checkIfMobileDevice() ? c.scheduledSlotTextMessage : null}}" ng-show="appointmentWindow.scheduled == true" tabIndex=0>
              	<span ng-class="{'slotContent': appointmentWindow.actualStart != c.selectedWindow.actualStart }">{{appointmentWindow.start}}</span>
              </button>                
            	<button aria-label="{{c.appointmentWIndowAriaLabelStartText}}: {{appointmentWindow.reformatedSelectedDate}} - {{c.appWindowBtnTextStartTime}}:{{appointmentWindow.start}} - {{c.appWindowBtnTextEndTime}}:{{appointmentWindow.end}}" class="btn appointmentSlot"
                    ng-disabled="!appointmentWindow.available || appointmentWindow.scheduled == true" ng-class="{'appointmentSlot appointmentSlotSelected': appointmentWindow.actualStart === c.selectedWindow.actualStart, 'appointmentSlot': appointmentWindow.actualStart != c.selectedWindow.actualStart, 'disabledSlot': !appointmentWindow.available && appointmentWindow.scheduled != true, 'scheduledSlot': appointmentWindow.scheduled == true }" ng-click = "c.selectActiveSlot(appointmentWindow, $index)" aria-pressed="{{appointmentWindow.actualStart === c.selectedWindow.actualStart? true:false }}" tabIndex="0">
              <span ng-class="{'slotContent slotContentSelected': appointmentWindow.actualStart === c.selectedWindow.actualStart, 'slotContent': appointmentWindow.actualStart != c.selectedWindow.actualStart }">{{appointmentWindow.start}}({{appointmentWindow.slots}})&nbsp;</span>
            </button>
              <span ng-show="c.checkIfMobileDevice() && appointmentWindow.scheduled == true" class="scheduledSlotMsg">{{c.scheduleSlotMsg}}</span>
            </div>

 

I would greatly appreciate your advice.

 

If there is any specific information you need, please let me know.
I will provide it.