My favorites | Sign in
Project Home Issues
New issue   Search
for
  Advanced search   Search tips   Subscriptions
Issue 1433: Ability to show certain portion of day as available
5 people starred this issue and may be notified of changes. Back to list
Status:  WontFix
Owner:  ----
Closed:  Aug 2013


Sign in to add a comment
 
Reported by praful.z...@neuronic.in, Jun 11, 2012
We want the ability to show a schedule in different background shade, for example, 9:00AM to 12:00 PM and 2:00PM to 8:00 PM as a pre-configured available appointment slots.

How can we achieve that using fullcalendar?
Jun 25, 2012
#1 jamshe...@gmail.com
I have achieved this with some modifications to fullcalendar.js.  It is not clear to me that the solution is optimal, and it certainly could be extended, but it does work.  Please feel free to comment or make it better.  This only works for agendaDay and agendaWeek.

General idea:  pass in availability info, add a css class to every slot indicating available or unavailable.  Style those elements to indicate availability and prevent operations on unavailable slots.

1.  Add new 'availInfo' option during initialization, this is an array of objects, each with two properties:  'start' and 'end' dates.  Something like 'availInfo: [ { start: s1, end: e1}, { start: s2, end: e2 }]' where s1,e1,etc. are JavaScript Date objects.

2.  Set a default for this option for this in the very top of fullcallendar.js in the defaults object:  availInfo: [].  This will prevent any problems if you don't use that option.

3.  Update the function 'opt' to handle array-type options.  As best I can tell, this function is used to get access to the fullcalendar options when direct access is not available.  This will allow us to get the availInfo with just "opt('availInfo')".  Here is the code:

        function opt(name, viewNameOverride) {
            var v = options[name];
            if ($.isArray(v)) {
                return v;
            }
            if (v && typeof v == 'object') {
                return smartProperty(v, viewNameOverride || viewName);
            }
            return v;
        }

4.  Attach data to each slot indicating start and end time, measured in minutes.  This code is in function buildSkeleton(), which is where the actual HTML of the agenda view is built.  Add a variable for slotMinutes so you don't have to refetch the option multiple times:

        var slotMinutes = opt('slotMinutes');

Then go to towards the bottom of this function and find where the actual <td> elements of the view are built.  This is in a for loop that reads "for (i = 0; d < maxd; i++)".  The <td> element is given a class of the current contentClass variable.  This is where you need to add data attributes for start and end.  Here is the code for the for loop:

            for (i = 0; d < maxd; i++) {
                minutes = d.getMinutes();
                s +=
				"<tr class='fc-slot" + i + ' ' + (!minutes ? '' : 'fc-minor') + "'>" +
				"<th class='fc-agenda-axis " + headerClass + "'>" +
				((!slotNormal || !minutes) ? formatDate(d, opt('axisFormat')) : '&nbsp;') +
				"</th>" +
				"<td class='{class}' data-start='{start}' data-end='{end}'>"
                    .replace("{class}", contentClass)
                    .replace("{start}", (slotCnt * slotMinutes))
                    .replace("{end}", ((slotCnt + 1) * slotMinutes)) +
				"<div style='position:relative'>&nbsp;</div>" +
				"</td>" +
				"</tr>";
                addMinutes(d, slotMinutes);
                slotCnt++;
            }

Now, every slot stores its start minute and end minute (INCLUSIVE).

5.  The existing function updateCells() is used to add CSS classes to make today's date show differently.  This is where you add new classes for availability:  'fc-avail' and 'fc-unavail'.  Start by adding a variable for the list of availabilities:  "var availList = opt('availInfo');".  Then, inside the for loop that goes over every day in the current view (one for agendaDay, seven for agendaWeek--this is what I think is going on), add the code to check the slots.  Here is the full completed function:

function updateCells() {
            var i, j;
            var headCell;
            var bodyCell;
            var date;
            var availStart;
            var availEnd;
            var slotStart;
            var slotEnd;
            var availList = opt('availInfo');
            var today = clearTime(new Date());

            for (i = 0; i < colCnt; i++) {
                date = colDate(i);
                headCell = dayHeadCells.eq(i);
                headCell.html(formatDate(date, colFormat));
                bodyCell = dayBodyCells.eq(i);

                //Remove today highlight from agendaDayView
                if ((viewName != "agendaDay") && (+date == +today)) {
                    bodyCell.addClass(tm + '-state-highlight fc-today');
                } else {
                    bodyCell.removeClass(tm + '-state-highlight fc-today');
                }

                setDayID(headCell.add(bodyCell), date);

                if (availList) {
                    //set available/unavailable classes for each slot
                    $.each($(".fc-agenda-slots").find("td"), function () {
                        slotStart = $(this).data('start');
                        slotEnd = $(this).data('end');
                        for (j = 0; j < availList.length; j++) {
                            //check if right date
                            if ((availList[j].start.getDate() == date.getDate())
                            && (availList[j].start.getMonth() == date.getMonth())
                            && (availList[j].start.getFullYear() == date.getFullYear())) {

                                //convert to minutes
                                availStart = availList[j].start.getMinutes() + (60 * availList[j].start.getHours());
                                availEnd = availList[j].end.getMinutes() + (60 * availList[j].end.getHours());

                                //set availability classes if any part of a slot is unavailable
                                if ((slotStart >= availStart) && (slotEnd <= availEnd)) {
                                    $(this).addClass('fc-avail');
                                    $(this).removeClass('fc-unavail');
                                    break;
                                }
                                else {
                                    $(this).addClass('fc-unavail');
                                    $(this).removeClass('fc-avail');
                                }
                            }
                        }
                    });
                }
            }
        }

Note that this is not a supremely efficient solution, as you have to loop through each timeslot for every item in your availability array.  Please let me know if you have a better algorithm that speeds up time.

6.  Now you can style the timeslots, I chose to do so in fullcalendar.css:
.fc-agenda-slots td.fc-unavail
{
    /* copied from http://cssdeck.com/item/86/diagonal-lines-pattern */
	border-width: 1px 0 0;
	background-image: -webkit-gradient(linear, left bottom, right top, color-stop(0, #ccc), color-stop(0.25, #ccc), color-stop(0.25, #bbb), color-stop(0.5, #bbb), color-stop(0.5, #ccc), color-stop(0.75, #ccc), color-stop(0.75, #bbb));
	background-image: -webkit-linear-gradient(left bottom, #ccc 0%, #ccc 25%, #bbb 25%, #bbb 50%, #ccc 50%, #ccc 75%, #bbb 75%);
	background-image: -moz-linear-gradient(left bottom, #ccc 0%, #ccc 25%, #bbb 25%, #bbb 50%, #ccc 50%, #ccc 75%, #bbb 75%);
	background-image: -ms-linear-gradient(left bottom, #ccc 0%, #ccc 25%, #bbb 25%, #bbb 50%, #ccc 50%, #ccc 75%, #bbb 75%);
	background-image: -o-linear-gradient(left bottom, #ccc 0%, #ccc 25%, #bbb 25%, #bbb 50%, #ccc 50%, #ccc 75%, #bbb 75%);
	filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#cccccc', endColorstr='#bbbbbb',GradientType=0 ); /* IE6-8 */
	background-image: linear-gradient(left bottom, #ccc 0%, #ccc 25%, #bbb 25%, #bbb 50%, #ccc 50%, #ccc 75%, #bbb 75%);
	background-size: 0px 10px;
	width:100%;
	height:100%;
}

Note that this is not a diagonal background pattern (it's horizontal grey bars) because when each individual slot gets it, the diagonal bars don't match up.  The larger point is that it would be more interesting to have drawn a div over the ranges of available and unavailable.

Also, this solution requires you to have the data up front, it is not a trivial task to have fullcalendar take in ajax settings and do the calls itself, so I did not proceed with that.

Finally, for my requirements, I am setting selection to false and having the user double-click any open slot (single-click does nothing).  This allows me to easily augment the click events (because I don't have to get into the Selection Manager).  I basically copied dayClick but turned it into 'slotDoubleClick' and pass a parameter 'isAvail'.  When initializing the fullcalendar, it looks like:
  
       slotDoubleClick: function (date, isAvail, jsEvent, view) { 
           if (!isAvail) {
              return;
           }
           //do stuff here
       }.  

So then I can essentially cancel the double-click if it happens on an 'unavailable' slot.  Here is the modified code from fullcalendar.js:

        function slotBind(cells) {
            cells.bind('dblclick', slotDoubleClick);
            //cells.click(slotClick)
            //.mousedown(slotSelectionMousedown); //this was used for dragging to select a time
        }

        //copied from function slotClick(ev)
        function slotDoubleClick(ev) {
            if (!opt('selectable')) { // if selectable, SelectionManager will worry about clicking
                var col = Math.min(colCnt - 1, Math.floor((ev.pageX - dayTable.offset().left - axisWidth) / colWidth));
                var date = colDate(col);
                var rowMatch = this.parentNode.className.match(/fc-slot(\d+)/); // TODO: maybe use data
                if (rowMatch) {
                    var mins = parseInt(rowMatch[1]) * opt('slotMinutes');
                    var hours = Math.floor(mins / 60);
                    date.setHours(hours);
                    date.setMinutes(mins % 60 + minMinute);
                    trigger('slotDoubleClick', dayBodyCells[col], date, $(this).hasClass('fc-avail'), ev);
                }
            }
        }






I hope this helps others out there and plants the seeds for a feature update.  I can't promise that I got all my changes described correctly, but it does work for me.

--J
Jun 26, 2012
#2 jamshe...@gmail.com
I am also able to accomplish this with significantly fewer edits to the fullcalendar.js file.  If you do step 4, then you can do step 5 from the function you put into option 'viewDisplay'.  

viewDisplay: function (view) {
  if (view.name == "agendaDay"){
    //get availability data, e.g. from ajax call
    //then set classes on each slot
    $.each($(#myFullCalendar).find(".fc-agenda-slots").find("td"), function () {
       ...
    } 
  }

Check if view.name is 'agendaDay' (or 'agendaWeek' if you use that), then get your availability data, then check it against each slot as before.

This approach is MUCH less work than the previous dive into fullcalendar.
Oct 16, 2012
#3 jamshe...@gmail.com
The problem with my solution is that it only works for agendaDay view; agendaWeek view fails because each TD spans all days!  So because there is no individual element that represents the actual slot *on a single day*, my method of styling only those cells doesn't work.
Aug 13, 2013
Project Member #4 adamrs...@gmail.com
There are 2 tickets that already exist that could potentially solve your problem when implemented:
-  issue 144 
-  issue 496 
Status: WontFix
Labels: -Type-Enhancement Type-Feature
Sign in to add a comment

Powered by Google Project Hosting