FullCalendar RefetchEvents very slow - meteor

I call calendar.refetchEvents(); inside an autorun Block to ensure the reactivity of the Scheduler (i'm using resource view) , when tested with big datasets , although i make sure to subscript to only a portion of 2 weeks worth of events , it's extremly slow .
my events arent Json based i'm using meteor and i loop over the events inside the Events function of the calendar .
are there some good fullcalendar practices i'm missing ?
calendar = $('#calendar').fullCalendar({
now: new Date(),
editable: true, // enable draggable events
droppable: true, // this allows things to be dropped onto the calendar
aspectRatio: 1.8,
disableDragging: true,
displayEventTime: false,
selectable:true,
allDaySlot:true,
slotDuration:'24:00',
lazyFetching:true,
scrollTime: '00:00', // undo default 6am scrollTime
header: {
left: 'today prev,next',
center: 'title',
right: 'timelineThreeDays'
},
defaultView: 'timelineThreeDays',
views: {
timelineThreeDays: {
type: 'timeline',
duration: { days: 14 }
}
},
eventAfterAllRender: function(){
Session.set("loading",false);
},
resourceLabelText: 'Employees',
eventRender: function (event, element) {
var originalClass = element[0].className;
if (event.id && event.id.indexOf("temp")>-1){
element[0].className = originalClass + ' dragEvent';
}
else if (event.id && event.id.indexOf("oloc")>-1){
element[0].className = originalClass + ' oloc';
}
else{
element[0].className = originalClass + ' hasmenu';
}
$(element[0]).attr('shift-id', event._id);
element.find('.fc-title').html((event.title?event.title+"<br/>":"")+(event.locationName?event.locationName+"<br/>":"")+moment(event.start).format("HH:mm")+ " "+moment(event.end).format("HH:mm"));
element.bind('mousedown', function (e) {
if (e.which == 3) {
Session.set("selectedShift",event._id);
}
});
},eventAfterRender: function(event, element, view) {
},
resourceRender: function(resourceObj, labelTds, bodyTds) {
var originalClass = labelTds[0].className;
var uid=resourceObj.id;
var resource = Meteor.users.findOne({_id:uid});
if(resource){
var img = Images.findOne({_id: resource.picture});
var imgUrl = img ? img.url() : "/images/default-avatar.png";
var styleString = "<img class='img-profil small' src='"+imgUrl+"'>" + resource.profile.firstname + " " + resource.profile.lastname+" <small style='font-size:0.6em;color:#D24D57;'>"+resource.profile.registeredTelephony+"</small>";
labelTds.find('.fc-cell-text').html("");
labelTds.find('.fc-cell-text').prepend(styleString);
labelTds[0].className = originalClass + ' hasResourceMenu';
}else{
var imgUrl = "/images/default-avatar.png";
var styleString = "<img class='img-profil small' src='"+imgUrl+"'>" + "Unassigned" + " " +" <small style='font-size:0.6em;color:#D24D57;'>"+"</small>";
labelTds.find('.fc-cell-text').html("");
labelTds.find('.fc-cell-text').prepend(styleString);
}
},
resources: function(callback) {
var users = [];
var data = Meteor.users.find({
$or:[
{"profile.showInScheduler":{$exists:false}},
{"profile.showInScheduler":true}
],
assignedTo:{$in:[Session.get("locationId")]},
'locations._id':Session.get("locationId"),
"profile.companyId":Session.get("companyId")
});
var arr = data.map(function(c) {
var employeeType = c.userSettings.employeeType;
var type = EmployeeType.findOne({_id:employeeType});
var img = Images.findOne({_id: c.picture});
var imgUrl = img ? img.url() : "/images/default-avatar.png";
c.name = c.name || "";
var totalHoursAllLocation = 0;
var totalHoursCurrentLocation = 0;
return {
id: c._id,
title: "t"
};
});
arr.push({id:"temp"+Session.get("companyId")+Session.get("locationId"),title:"<div class='img-profil small' style='background: url(/images/default-avatar.png);'></div> UnAssigned"});
callback(arr);
},
events: function(start, end, timezone, callback) {
},
drop: function(date, jsEvent, ui, resourceId) {
// retrieve the dropped element's stored Event Object
var locationId=Session.get("locationId");
var originalEventObject = $(this).data('eventObject');
var copiedEventObject = $.extend({}, originalEventObject);
// assign it the date that was reported
copiedEventObject.start = date;
//copiedEventObject.allDay = allDay;
shift = ShiftTypes.findOne({_id:copiedEventObject.id});
if(shift){
startDate = moment(date);
hour = shift.dayDuration.Start.split(":");
startDate.hours(hour[0]).minutes(hour[1]);
endDate = moment(date);
hour = shift.dayDuration.End.split(":");
endDate.hours(hour[0]).minutes(hour[1]);
if(moment(startDate).isAfter(endDate)){
endDate=endDate.add("1","days");
}
var data = {
shiftId:shift._id,
name:shift.name,
uid:resourceId,
locationId:Session.get("locationId"),
companyId:Session.get("companyId"),
day:date,start:startDate.utc().toDate(),
end:endDate.utc().toDate(),
type:"active"
};
if (SchedulesBeforeInsert(data,"dropActive")){
Schedules.insert(data,function (err,result) {});
}
}
},
eventResize: function( event, dayDelta, minuteDelta, revertFunc, jsEvent, ui, view ) {
endDate = moment.utc(new Date(event.start));
schedule = Schedules.findOne({_id:event.id});
var delta = dayDelta._days;
for(i=1;i<delta+1;i++){
Schedules.insert({start:moment.utc(event.start).add(i,"days").toDate(),end:moment.utc(schedule.end).add(i,"days").toDate(),uid:schedule.uid,locationId:Session.get("locationId"),companyId:schedule.companyId,name:schedule.name,shiftId:schedule.shiftId,attendanceCode:schedule.attendanceCode,type:schedule.type});
}
},
dayClick: function(date, jsEvent, view,res,res2) {
Session.set("selectedDay",moment(date).toDate());
Session.set("selectedRessource",res.id);
},
eventClick: function ( event, jsEvent, view ) {
toastr.success(moment(event.start).format('HH:mm') +" TO "+moment(event.endDate).format('HH:mm'))
},
eventReceive: function(event) { // called when a proper external event is dropped
console.log('eventReceive', event);
}
}).data().fullCalendar;
/********************* reactive calendar *****************/
this.autorun(function() {
console.log("autoRun")
Meteor.subscribe("schedules",Session.get("companyId"),moment($('#calendar').fullCalendar('getDate')).startOf('week').toDate(),moment($('#calendar').fullCalendar('getDate')).add(4,"weeks").endOf('week').toDate());
var events = [];
var usersInLocation = Meteor.users.find({$or:[{"profile.showInScheduler":{$exists:false}},{"profile.showInScheduler":true}],assignedTo:{$in:[Session.get("locationId")]},'locations._id':Session.get("locationId"),"profile.companyId":Session.get("companyId")}).fetch();
var userIds = _.pluck(usersInLocation,"_id");
userIds.push("temp"+Session.get("companyId")+Session.get("locationId"));
var data;
if(Session.get("displayAllLocations")===true){
reqEvents = Schedules.find({uid:{$in:userIds},companyId:Session.get("companyId"),type:{$in:["active","activeAttendance"]},start:{$gte:moment(Session.get("currentDate")).startOf('week').toDate()},end:{$lte:moment(Session.get("currentDate")).add(1,"week").endOf('week').toDate()}});
}else{
reqEvents = Schedules.find({uid:{$in:userIds},locationId:Session.get("locationId"),companyId:Session.get("companyId"),type:{$in:["active","activeAttendance"]},start:{$gte:moment(Session.get("currentDate")).startOf('week').toDate()},end:{$lte:moment(Session.get("currentDate")).add(1,"week").endOf('week').toDate()}});
}
reqEvents.forEach(function(evt){
var event = null;
color="";
if(evt.attendanceCode){
attendance =AttendanceCodes.findOne({_id:evt.attendanceCode});
color=attendance.color;
}
attendance = null;
color="";
id="";
locationName="";
if(evt.attendanceCode){
attendance =AttendanceCodes.findOne({_id:evt.attendanceCode})
if(attendance){
color=attendance.color;
}
}
else if(evt.employeeAttendanceCode){
attendance =AttendanceCodesPortal.findOne({_id:evt.employeeAttendanceCode})
if(attendance){
color=attendance.color;
}
}
id=evt._id;
if(evt.locationId!==Session.get("locationId")){
color="#EEEEEE";
id="oloc";
location =Locations.findOne({_id:evt.locationId});
if(location){
locationName=location.name;
}
}
if(evt.name != null){
event = {id:id,title:evt.name,start:evt.start,end:evt.end,color:color,resourceId:evt.uid,locationName:locationName};
}else{
event = {id:id,title:" ",start:evt.start,end:evt.end,color:color,resourceId:evt.uid,locationName:locationName};
}
events.push(event);
});
allUsersCursor =Meteor.users.find({
$or:[
{"profile.showInScheduler":{$exists:false}},
{"profile.showInScheduler":true}
],
assignedTo:{$in:[Session.get("locationId")]},
'locations._id':Session.get("locationId"),
"profile.companyId":Session.get("companyId")
}).fetch();
if(calendar){
calendar.removeEvents();
calendar.addEventSource(events);
calendar.refetchResources();
//SetUp the actions context menu
setUpContextMenu();
// Initialize the external events
$('#external-events div.external-event').each(function() {
var eventObject = {
title: $.trim($(this).text()), // use the element's text as the event title
id:$(this).attr("id")
};
// store the Event Object in the DOM element so we can get to it later
$(this).data('eventObject', eventObject);
// make the event draggable using jQuery UI
$(this).draggable({
helper: 'clone',
revert: 'invalid',
appendTo: 'body'// original position after the drag
});
});
}
});

I can see a number of non-Meteoric patterns in there that you should optimize out:
Your this.autorun will rerun anytime anything changes in your Schedules collection as well as for various user object changes (not just to the logged-in users but other users as well). Your autorun rebuilds your client-side data from scratch every time, including numerous joins. You could use a local collection to cache the joined data and then only add/change those documents when the underlying persisted document changes. This can be done with an observeChanges pattern. Or you could denormalize your data model a bit to avoid all those joins.
You're modifying the DOM by attaching events to a number of selectors using jQuery instead of using Meteor event handlers. This is also happening in your autorun which means you're attaching the same events to the same DOM objects repeatedly.
You use many Session variables and you use them repeatedly. These can be slow since they use browser local storage. You should .get() such data into a local variable once only and then refer to the local variable from then on.
Make sure that you only include fields in your Schedules publication that your code actually refers to on the client. The rest are overhead.

Related

FullCalendar - Get Background event on dayClick

I'm currently trying to get an event (type of background) when the DayClick is trigger.
I can't use the EventClick trigger because of the background type (I don't know why but nothing happen in my case, something wrong from FullCalendar ?).
This is my code for the init :
$('#calendarRoomUnavailable').fullCalendar({
height: 'auto',
header: {
left : '',
center: 'title',
right: ''
},
defaultView: 'year',
defaultDate: getDateFilterRoomAvailable(),
lang: 'fr',
firstDay: 1,
columnFormat: 'ddd D/M',
selectable : false,
weekends: false,
navLinks : false,
events: basePath + '/agenda/datalist/room_available',
viewRender: function (view, element) {
},
eventRender: function(event,element){
if(event.rendering === "background"){
element.data(event); //store the event data inside the element
}
},
dayClick: function (date, jsEvent, view) {
console.log(jsEvent)
},
editable:false,
});
Quick look
What I want:
When I click on a day, I want the get the event(background) related to the day (I've got only one event per day in my calendar).
Currently I'm working with the eventRender + dayClick :
eventRender: function(event,element){
if(event.rendering === "background"){
element.data(event); //store the event data inside the element
}
},
dayClick: function (date, jsEvent, view) {
console.log(jsEvent)
[...]
}
With the console.log JSEvent on the DayClick, I know it get me the wrong <td> :
Img
Because, when I try to get the target <td> with the class fc-bgevent, nothing happens :
dayClick: function (date, jsEvent, view) {
console.log(jsEvent)
if (jsEvent.target.classList.contains('fc-bgevent')) {
console.log($(jsEvent.target).data());
}
If I try to go to the HTML element target getted by the jsEvent, it shows me the wrong <td> and I can't do anything with that ...
HTML Debug
Does someone know how to bypass that ?
Thanks !
Code to get one event from a date :
function getEventFromDate(date) {
var allEvents = [];
allEvents = $('#calendarRoomUnavailable').fullCalendar('clientEvents');
var event = $.grep(allEvents, function (v) {
return +v.start === +date;
});
if (event.length > 0) {
return event[0];
} else {
return null;
}
}
After the #ADyson comments, my final function look like this :
function getEventFromDate(date) {
var eventToReturn = null;
$('#calendarRoomUnavailable').fullCalendar('clientEvents', function(event) {
var event_start = formatDateYmd(new Date(date),'-');
var date_formated = formatDateYmd(new Date(event.start),'-');
if(event_start === date_formated) {
eventToReturn = event;
}
});
return eventToReturn;
}
with a small function to get a formated date without library like Moment.js or Date.js:
function formatDateYmd(value,$delimiter)
{
var year = value.getFullYear();
var month = (value.getMonth()+1) < 10 ? '0'+(value.getMonth()+1) : (value.getMonth()+1);
var day = value.getDate() < 10 ? '0'+value.getDate() : value.getDate();
return year + $delimiter +
month + $delimiter +
day;
}
I hope to find something more performant, but this one divide the execution time by two.
Keep an eye on it
Thanks again.

Vue doesn't update when computed data change

Context: I have a list of posts with tags, categories from wordpress api. I display these posts with Vue and using computed with a search box to filter the result based on titre, description, tags, and categories
Problem: I am trying to update a computed list when user click on a list of tag available. I add the get and set for computed data like this:
var vm = new Vue({
el: '#blogs',
data: {
search: '',
posts: [],
filterPosts: []
},
beforeMount: function() {
// It should call the data and update
callData();
},
computed: {
filterPosts: {
get: function() {
var self = this;
return self.posts.filter(function(post){
var query = self.search.toLowerCase();
var title = post.title.toLowerCase();
var content = post.content.toLowerCase();
var date = post.date.toLowerCase();
var categories = '';
post.categories.forEach(function(category) {
categories += category.name.toLowerCase();
});
var tags = '';
post.tags.forEach(function(tag){
tags += tag.name.toLowerCase();
});
return title.indexOf(query) !== -1 ||content.indexOf(query) !== -1 || date.indexOf(query) !== -1 || categories.indexOf(query) !== -1 || tags.indexOf(query) !== -1;
});
},
set: function (newValue) {
console.log(newValue);
this.filterPosts = Object.assign({}, newValue);
}
}
},
methods: {
filterByTag: function(tag, event) {
event.preventDefault();
var self = this;
self.filterPosts = self.posts.filter(function(post){
var tags = '';
post.tags.forEach(function(tag){
tags += tag.name.toLowerCase();
});
return tags.indexOf(tag.toLowerCase()) !== -1;
});
}
}
}); // Vue instance
The console.log always output new data based on the function I wrote on methods but Vue didn't re-render the view. I think I didn't do the right way or thought like Vue. Could you please give some insight?
Edit 1
Add full code.
I tried to add filterPosts in data but I received this error from Vue: The computed property "filterPosts" is already defined in data.
Your setter is actually not setting anything, it only logs the new value. You need to store it somewhere.
For example you can store it in the component's data:
data: {
value: 'foo',
},
computed: {
otherValue: {
get() { /*...*/ },
set(newVal) { this.value = newVal },
},
},
But this is definitely not the only possibility, if you use Vuex, the setter can dispatch an action that will then make the computed value get updated. The component will eventually catch the update and show the new value.
computed: {
value: {
get() {
return this.$store.getters.externalData;
},
set(newVal) {
return this.$store.dispatch('modifyingAction', newVal);
},
},
},
The bottomline is you have to trigger a data change in the setter, otherwise your component will not be updated nor will it trigger any rerender.
EDIT (The original answer was updated with full code):
The answer is that unless you want to manually change the list filteredPosts without altering posts, you don't need a get and set function for your computed variable. The behaviour you want can be acheived with this:
const vm = new Vue({
data() {
return {
search: '',
posts: [],
// these should probably be props, or you won't be able to edit the list easily. The result is the same anyway.
};
},
computed: {
filteredPosts() {
return this.posts.filter(function(post) {
... // do the filtering
});
},
},
template: "<ul><li v-for='post in filteredPosts'>{{ post.content }}</li></ul>",
});
This way, if you change the posts or the search variable in data, filteredPosts will get recomputed, and a re-render will be triggered.
After going around and around, I found a solution, I think it may be the right way with Vue now: Update the computed data through its dependencies properties or data.
The set method didn't work for this case so I add an activeTag in data, when I click on a tag, it will change the activeTag and notify the computed filterPost recheck and re-render. Please tell me if we have another way to update the computed data.
var vm = new Vue({
el: '#blogs',
data: {
search: '',
posts: [],
tags: [],
activeTag: ''
},
beforeMount: function() {
// It should call the data and update
callData();
},
computed: {
filterPosts: {
get: function() {
var self = this;
return self.posts.filter(function(post){
var query = self.search.toLowerCase();
var title = post.title.toLowerCase();
var content = post.content.toLowerCase();
var date = post.date.toLowerCase();
var categories = '';
post.categories.forEach(function(category) {
categories += category.name.toLowerCase();
});
var tags = '';
post.tags.forEach(function(tag){
tags += tag.name.toLowerCase();
});
var activeTag = self.activeTag;
if (activeTag !== '') {
return tags.indexOf(activeTag.toLowerCase()) !== -1;
}else{
return title.indexOf(query) !== -1 ||content.indexOf(query) !== -1 || date.indexOf(query) !== -1 || categories.indexOf(query) !== -1 || tags.indexOf(query) !== -1;
}
});
},
set: function (newValue) {
console.log(newValue);
}
}
},
methods: {
filterByTag: function(tag, event) {
event.preventDefault();
var self = this;
self.activeTag = tag;
}
}
}); // Vue instance
Try something like:
data: {
myValue: 'OK'
},
computed: {
filterPosts: {
get: function () {
return this.myValue + ' is OK'
}
set: function (newValue) {
this.myValue = newValue
}
}
}
More:
https://v2.vuejs.org/v2/guide/computed.html#Computed-Setter

Filtering events with multiple filters (Checkboxes)

I am using the jQuery's full calendar with my sharepoint calendar list. I want to filter the events with multiple checkbox selection. I want to filter the events based on the Rooms Selected in the checkboxes.
For this I am using the following script.
Script :
<link rel="stylesheet" type="text/css" href="http://bisp2013-04:1000/sites/Exercise2/Style%20Library/FullCalendar/fullcalendar.css" />
<link rel="stylesheet" type="text/css" href="http://bisp2013-04:1000/sites/Exercise2/Style%20Library/FullCalendar/jquery-ui-1.8.16.custom.css" />
<script type="text/javascript" src="http://bisp2013-04:1000/sites/Exercise2/Style%20Library/FullCalendar/jquery-2.1.4.js"></script>
<script type="text/javascript" src="http://bisp2013-04:1000/sites/Exercise2/Style%20Library/FullCalendar/jquery-ui-1.8.16.custom.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery.SPServices/0.7.1a/jquery.SPServices-0.7.1a.min.js"></script>
<script type="text/javascript" src="http://bisp2013-04:1000/sites/Exercise2/Style%20Library/FullCalendar/moment.min.js"></script>
<script type="text/javascript" src="http://bisp2013-04:1000/sites/Exercise2/Style%20Library/FullCalendar/fullcalendar.js"></script>
<script type=text/javascript">
</script>
<script type="text/javascript">
// Format UTC dates as local date/time strings.
var rooms = [];
var selectedRooms = [];
var events = [];
var filteredSource = [];
var RoomSelection = [];
var date = new Date();
function closeDialog(result) {
SP.UI.ModalDialog.RefreshPage(result);
}
function formatDateToLocal( date ) {
var dateUTC;
if ( typeof date === "string" ) {
// Convert UTC string to date object
var d = new Date();
var year = date.split('-')[0];
var month = date.split('-')[1] - 1;
var day;
var hour;
var minute;
var second;
day = date.split('-')[2].split('T')[0];
hour = date.split('T')[1].split(':')[0];
minute = date.split('T')[1].split(':')[1].split(':')[0];
second = date.split('T')[1].split(':')[2].split('Z')[0];
dateUTC = new Date( Date.UTC( year, month, day, hour, minute, second ) );
}
else if ( typeof date === "object" ) {
dateUTC = date;
}
else {
alert( "Date is not a valid string or date object." );
}
// Create local date strings from UTC date object
var year = "" + dateUTC.getFullYear();
var month = "" + ( dateUTC.getMonth() + 1 ); // Add 1 to month because months are zero-indexed.
var day = "" + dateUTC.getDate();
var hour = "" + dateUTC.getHours();
var minute = "" + dateUTC.getMinutes();
var second = "" + dateUTC.getSeconds();
// Add leading zeros to single-digit months, days, hours, minutes, and seconds
if ( month.length < 2 ) {
month = "0" + month;
}
if ( day.length < 2 ) {
day = "0" + day;
}
if ( hour.length < 2 ) {
hour = "0" + hour;
}
if ( minute.length < 2 ) {
minute = "0" + minute;
}
if ( second.length < 2 ) {
second = "0" + second;
}
var localDateString = year + "-" + month + "-" + day + "T" + hour + ":" + minute + ":" + second;
return localDateString;
}
$(document).ready( function() {
$().SPServices({
operation: "GetListItems",
async: false,
listName: "Training Rooms",
completefunc: function (xData, Status) {
if(Status == 'success' )
{
$(xData.responseXML).SPFilterNode("z:row").each(function()
{
var value= $(this).attr("ows_ID");
var text = $(this).attr("ows_Title");
var roomColor = $(this).attr("ows_bifctpEventColor")
rooms.push({
title: text,
id: value,
Color: roomColor
});
$("#filterBox").append ( "<input id='chk_" + value + "' type='checkbox' value='" + value + "' style='background:"+roomColor+"' onchange='FilterCalendar(" + value + ",this)'/><label for='chk_" + value + "'>" + text + "</label>" );
});
}
}
});
$( '#calendar' ).fullCalendar({
// Assign buttons to the header of the calendar. See FullCalendar documentation for details.
header: {
left:'prev,next today',
center: 'title',
right: 'month, agendaWeek, agendaDay'
},
dayClick: function (date, jsEvent, view) {
var now = new Date();
if (date.setHours(0,0,0,0) < now.setHours(0,0,0,0))
{
alert('You Can Not add Event on this date');
}
else
{
var siteUrl = window.location.protocol + "//" + window.location.host + _spPageContextInfo.siteServerRelativeUrl;
var myLinksUrl = siteUrl + "/Lists/RoomReservation/NewForm.aspx";
var options = {
url: myLinksUrl,
title: "Room Reservation",
dialogReturnValueCallback: closeDialog
};
SP.SOD.execute('sp.ui.dialog.js', 'SP.UI.ModalDialog.showModalDialog', options);
}
},
defaultView: "month", // Set the default view to month
/*
when user select timeslot this option code will execute.
It has three arguments. Start,end and allDay.
Start means starting time of event.
End means ending time of event.
allDay means if events is for entire day or not.
*/
selectable: true,
selectHelper: true,
select: function(start, end, allDay)
{
if(start < date)
{
// Do whatever you want here.
alert('Cannot select this dates.');
return;
}
var title = prompt('Event Title:');
if (title) {
$( '#calendar' ).fullCalendar('renderEvent', {
title: title,
start: start,
end: end,
allDay: allDay
},
true // make the event "stick"
);
}
$( '#calendar' ).fullCalendar('unselect');
},
editable: true,
// Add events to the calendar.
events: function( start, end, callback ) {
// Create an array to hold the events.
events = [];
// Set the date from which to pull events based on the first visible day in the current calendar view. For a month view, this will usually be several days into the previous month. We can use FullCalendar's built-in getView method along with the formatDate utility function to create a date string in the format that SharePoint requires. It must be in the format YYYY-MM-DDTHH:MM:SSZ. Due to time zone differences, we will omit everything after the day.
var startDate = moment().format($('#calendar').fullCalendar('getView').intervalstart, "u").split("T")[0];
// Get the current view of the calendar (agendaWeek, agendaDay, month, etc.). Then set the camlView to the appropriate value to pass to the web service. This way we will only retrieve events needed by the current view (e.g. the agendaWeek view will only retrieve events during the current week rather than getting all events for the current month).
var calView = $( '#calendar' ).fullCalendar( 'getView' ).title;
var camlView = "";
switch( calView ) {
case "agendaWeek":
camlView = "<Week />";
break;
case "agendaDay":
camlView = "<Week />";
break;
default: // Default to month view
camlView = "<Month />";
}
// Set the camlFields, camlQuery, and camlOptions to the appropriate values to pass to the web service. You can add additional <ViewFields /> or adjust the CAML query if you have some custom columns that you want to filter by or display data from. The values below are the pretty much the minimum you'll want to start from to get it working.
var camlFields = "<ViewFields><FieldRef Name='Title' /><FieldRef Name='EventDate' /><FieldRef Name='EndDate' /><FieldRef Name='Location' /><FieldRef Name='Description' /><FieldRef Name='fRecurrence' /><FieldRef Name='RecurrenceData' /><FieldRef Name='RecurrenceID' /><FieldRef Name='fAllDayEvent' /></ViewFields>";
var camlQuery = "<Query><Where><And><Geq><FieldRef Name='EventDate' /><Value IncludeTimeValue='TRUE' Type='DateTime'>"+moment(start).toISOString()+"</Value></Geq><Leq><FieldRef Name='EndDate' /><Value IncludeTimeValue='TRUE' Type='DateTime'>"+moment(end).toISOString()+"</Value></Leq></And></Where></Query>";
var camlOptions = "<QueryOptions><CalendarDate>" + startDate + "</CalendarDate><RecurrencePatternXMLVersion>v3</RecurrencePatternXMLVersion><ExpandRecurrence>TRUE</ExpandRecurrence><DateInUtc>TRUE</DateInUtc></QueryOptions>";
// Make the web service call to retrieve events.
$().SPServices({
operation: "GetListItems",
async: false,
listName: "Room Reservation", // Change this to the GUID or display name of your calendar. If the calendar is on a different site, you can use the display name with the webURL option (see SPServices.CodePlex.com for more information).
CAMLViewFields: camlFields,
CAMLQuery: camlQuery,
CAMLQueryOptions: camlOptions,
completefunc: function( xData, Status ) {
$( xData.responseXML ).find( "z\\:row, row" ).each( function() {
// Check for all day events
var fADE = $( this ).attr( 'ows_fAllDayEvent' );
var thisADE = false;
var thisStart;
var thisEnd;
if ( typeof fADE !== "undefined" && fADE !== "0" ) {
thisADE = true;
// Get the start and end date/time of the event. FullCalendar will parse date strings in local time automagically, and we don't need to do any local time conversions for all day events, so we can use the UTC date strings from SharePoint without converting them to local time.
var thisStart = $( this ).attr( 'ows_EventDate' );
var thisEnd = $( this ).attr( 'ows_EndDate' );
}
else {
// Get the start and end date/time of the event. FullCalendar will parse date strings in local time automagically, so we need to convert the UTC date strings from SharePoint into local time. The formatDateToLocal() function above will take care of this. See comments in that function for more information.
var thisStart = formatDateToLocal( $( this ).attr( 'ows_EventDate' ) );
var thisEnd = formatDateToLocal( $( this ).attr( 'ows_EndDate' ) );
}
// Get the list item ID and recurrence date if present. This will be used to generate the ID query string parameter to link to the event (or the specific instance of a recurring event). The ID query string parameter must be in the format "ID.0.yyyy-MM-ddTHH:mm:ssZ" for recurring events (where "ID" is the list item ID for the event). Event ID's are returned as just a number (for non-recurring events) or several numbers separated by ";#" in 2007 or "." in 2010 to indicate individual instances of recurring events. By splitting and joining the ID this way, thisID will be set to a valid query string parameter whether an event is recurring or not for both versions of SharePoint.
var thisID = $( this ).attr( 'ows_ID' ).split( ';#' ).join( '.' );
var thisRoom = $(this).attr('ows_bifctpTrainingRoom').split(';#')[0];
// FullCalendar documentation specifies that recurring events should all have the same id value when building the events array (the id is optional, but I'm including it for completeness). We can get the list item ID (which is the same for all instances of recurring events) without the recurrence information by simply splitting thisID.
var eventID = thisID.split( '.' )[0];
// Get the event title. This is displayed on the calendar along with the start time of the event.
var thisTitle = $( this ).attr( 'ows_Title' );
// Get the event description. I don't use it in this example, but you could use it for something, perhaps as a tooltip when hovering over the event.
var thisDesc = $( this ).attr( 'ows_Description' );
// Add the event information to the events array so FullCalendar can display it.
events.push({
title: thisTitle,
id: eventID,
start: thisStart,
end: thisEnd,
allDay: thisADE,
room:thisRoom,
// Adjust this URL to link to the display form for your calendar events. You can include a Source parameter to allow users to easily return to the FullCalendar page.
url: _spPageContextInfo.siteAbsoluteUrl + '/Lists/RoomReservation/DispForm.aspx?ID=' + thisID + '&Source=' + window.location,
description: 'Check Calendar Events'
});
});
callback( events );
}
});
},
eventRender: function eventRender( event, element, view ) {
var isFound = false;
var backgroundColor = "";
for (var roomID in rooms) {
var currentRoom = rooms[roomID];
if (currentRoom.id == event.room) {
backgroundColor = currentRoom.Color;
element.find(".fc-event-time").html(event.start.format("HH:mm") + "-" + event.end.format("HH:mm"));
element.find(".fc-title").parent().attr("title", event.title);
element.css('color','black');
element.find('.fc-event-skin').css('background-color', backgroundColor);
element.css('background-color', backgroundColor);
isFound = true;
break;
}
}
return isFound;
}
});
})
</script>
<style type="text/css">
#calendar
{
width: 900px;
margin: 0 auto;
}
</style>
<div id="filterBox">
</div>
<div id="calendar">
<!-- Calendar will be added here -->
</div><!-- #calendar -->
This is how my screen looks at present:
What i want to do is on change event of the each of the checkbox. I want to filter the calendar events and show events only for the selected rooms.
You need to use event source, so you can split your events in different sources (rooms in your case).
Then, you can easily show / hide your events with add Event Source and remove Event Source.
So your code will be something like:
$('#calendar').fullCalendar({
// put your options and callbacks here
eventSources: [source1, source2]
})
And bound to your checkbox:
$('#calendar').fullCalendar( 'addEventSource', source1 ); //Show it
$('#calendar').fullCalendar( 'removeEventSource', source1 ); //Hide it
I did a plunker where you can reproduce it.

fullCalendar only display events on agendaView

I'm trying to get the fullCalendar JS application to display events only when on the agendaView view, not on the month view.
This is my current code. The problem is the events function is only called once, on the initial page load. According to documentation (and another site I have with fullCalendar) this function should call every time I change view, or date range. It does not.
$('#calendar').fullCalendar({
events: function ( start, end, timezone, callback ) {
var view = $('#calendar').fullCalendar('getView');
console.log(view.name);
if(view.name == 'agendaDay') {
$.ajax({
type: 'POST',
url: 'index.php?events=true',
data: {
start: start,
end: end
},
async: true,
success: function ( data ) {
callback(data);
}
});
}
},
dayClick: function ( date, jsEvent, view ) {
if(view.name == 'month' && jsEvent.currentTarget.className.indexOf('date-disabled') <= -1) {
$('#calendar').fullCalendar('changeView', 'agendaDay');
$('#calendar').fullCalendar('gotoDate', date);
} else if(view.name == 'agendaDay') {
var check = moment(date).format('YYYY-MM-DD');
var today = moment(new Date()).format('YYYY-MM-DD');
if(check > today) {
alert(date);
}
}
},
header: {
left: 'title',
center: '',
right: 'today month,agendaDay prev,next'
},
dayRender: function ( date, cell ) {
var check = moment(date).format('YYYY-MM-DD');
var today = moment(new Date()).format('YYYY-MM-DD');
if(check <= today) cell[0].className += ' date-disabled';
}
});
I know this for fact as the console.log only logs once, the word month. So it's only called when I refresh the page, first load it. Not when changing views which it should.
I was missing the lazyFetching option. This must be a new option in v2 as I was not expecting it.
http://arshaw.com/fullcalendar/docs/event_data/lazyFetching/
My code was correct. I simply needed to tell fullCalendar to get the events at EVERY change of the view and anything else that happened event wise.
lazyFetching: false,
This now works with my code.

Persisting dates between FullCalendar and an index view

I'm working on a website, asp.net MVC kind of thing. On the home page, I have two alternate views. One is a jquery FullCalendar, and one is an index view, both displaying events from a database.
I can currently change the month being viewed in either, but I want to be able to link them up, so that for example changing to view april 2013 in the calendar and then clicking "index view" will take me to april 2013 in the index view, rather than the default.
My "GetEventsForCalendar" method is as follows:
[HttpPost]
public virtual ActionResult GetEventsForCalendar(long start, long end)
{
var startDateTime = start.ToDateTime();
var endDateTime = end.ToDateTime();
var listOfEvents = eventRepository.List.Where(e => e.ToDate >= startDateTime
&& e.FromDate <= endDateTime).ToList() .Select(eventSerialiser.SerialiseForFullCalendarJS);
return Json(listOfEvents);
}
But I can't find anywhere in the codebase where it is given these parameters. The only place the function is specified is in the calendar partial view scrips section:
#section scripts {
#Scripts.Render(Links.Bundles.Scripts.calendar)
#Scripts.Render(Links.Bundles.Scripts.events_calendar)
<script type="text/javascript">
$(document).ready(function() {
SohoHouse.EventsCalendar.setup("#Url.Action(MVC.Events.GetEventsForCalendar())");
})
</script>
}
I think the details of the month being viewed are persisted in a cookie, but I'm not sure how to access this from my index view.
If you need any other code then please ask, I'm very new to programming and stack overflow so I'm still not sure how to ask questions well :)
Managed to get this working in the end: abandoned the idea of a seperate view for the index, and instead used the jquery to create a table with a list view of the events. Then used a button to toggle between the two. For reference, the calander.js looked something like this:
var report = $("#report");
var calendar = $("#calendar .fc-content");
report.hide();
$("#toggle-view").click(function() {
calendar.toggle();
report.toggle();
$("#calendar").fullCalendar("refetchEvents");
$("#calendar").fullCalendar("render");
updateToggleButtonText();
});
function updateToggleButtonText() {
$("#toggle-view").text(report.is(":visible") ? "View Calendar" : "View List");
}
function setUpCalendar() {
$("#calendar").fullCalendar({
events: {
url: getEventsUrl,
type: "POST",
eventDataTransform: addReportRow
},
timeFormat: "H:mm{ - H:mm}",
allDayDefault: false,
eventBorderColor: "gray",
header: {
left: "prev,next",
center: "title",
right: null
},
weekMode: "variable",
loading: function(isLoading) {
if (isLoading) {
$("table#report > tbody:last").empty();
}
},
allDaySlot: false
});
}
function addReportRow(data) {
var calendarMonth = $("#calendar").fullCalendar("getDate").getMonth();
var startDate = new Date(data.start);
var endDate = new Date(data.end);
if (startDate.getMonth() > calendarMonth || endDate.getMonth() < calendarMonth) {
return null;
}
var tableDetails = [
data.title,
$.fullCalendar.formatDate(startDate, "dd MMMM yyyy"),
$.fullCalendar.formatDate(endDate, "dd MMMM yyyy"),
data.published ? "Published" : "Not Published"
];
var row = $("<tr></tr>");
for (var i = 0; i < tableDetails.length; i++) {
row.append($("<td></td>").text(tableDetails[i]));
}
$("table#report > tbody:last").append(row);
return data;
}
function gotoDateFromCookie() {
var calendarDate = $.cookie("calendarDate");
if (calendarDate !== null && calendarDate !== "Invalid Date") {
$("#calendar").fullCalendar("gotoDate", new Date(calendarDate));
$.cookie("calendarDate", null);
}
}
function storeDateAsCookie() {
var calendarDate = $("#calendar").fullCalendar("getDate");
$.cookie("calendarDate", calendarDate);
}
window.eventDropdownChanged = function() {
location.reload();
};
};

Resources