Multiple fullacalendar widgets - synchronized view changes - javascript

I have a dashboard, where people can put multiple fullcalendar widgets (imagine a team dashboard with a fullcalendar widget for every person in a team). The main issue with this is performance. One client has 16 such widgets (all quite full of events) and they are not limited in number.
Only the first calendar has header controls (others have them hidden using CSS).
What I need is to propagate the changes to view (switch basicWeek to month etc.), firstDay (today in basicWeek, Monday everywhere else) and the date (prev, next, today) from first calendar to other calendars.
The issue is performance. As I didn't really find a way how to do it (that wouldn't look really hacky) except in viewRender, which is called for every single change. That means, if you switch from basicWeek to agendaWeek the first calendar propagates the firstDay to other calendars (which calls viewRender) and after that propagates the view change, which basically re-renders all calendars (except the first one) twice.
Is there a way to propagate those changes and manually call render on other calendars (from what I see in the source code, there might not be one), or a better way to do it?
I am also thinking about just destroying the calendars and re-initializing them with new options, but that might cause flashing (instead of quite a lot of lag caused by multiple re-renders). One of the options I thought of was using my own buttons (or re-using default buttons just unbinding original events from them), but even then I would still have to re-render the calendars multiple times in some occasions.
Switching view with 6 almost empty calendars takes about 3 seconds, which is unacceptable.
This is how my viewRender code looks like (this is inside own fullcalendar function, so every widget has its own scope with cached variables and settings)
viewRender: function(view) {
console.log('viewRender', calendarid, lastView, view.type);
// Propagate all changes from first calendar to other calendars on the page
if ($('#' + calendarid).parents('.widgetCalendar').is(':first-child')) {
// Change the first day (basicWeek is always today, other views are always Monday)
if (view.type != 'basicWeek' && currFirstDay != 1) {
currFirstDay = 1;
$('.calendarDiv').fullCalendar('option', 'firstDay', 1);
return;
} else if (view.type == 'basicWeek' && currFirstDay != firstday) {
currFirstDay = firstday;
$('.calendarDiv').fullCalendar('option', 'firstDay', firstday);
return;
}
// Propagate the view change to other calendars
if (lastView != view.type) {
lastView = view.type;
$('.calendarDiv:not(#' + calendarid + ')').fullCalendar('changeView', view.type); // , view.intervalStart.valueOf());
}
// Propagate the date change to other calendars
if (lastDate != view.intervalStart.valueOf()) {
lastDate = view.intervalStart.valueOf();
$('.calendarDiv:not(#' + calendarid + ')').fullCalendar('gotoDate', view.intervalStart.valueOf());
}
}
},
Ps.: At first I thought this issue is mainly ajax requests to get new events, but I changed that to a function which uses single call and caches the results. The main reason I thought that were some delays between ajax and that they weren't concurrent (session locking). But changing it to new function shows that the issue are indeed the re-renders which take white a long time per calendar (about 250 - 350ms).
If there is any info missing, ask in the comments and I will update the question.
Fullcalendar version: 3.4.0

Related

ap-angular2-fullcalendar & external API

I´d like to add the ap-angular2-fullcalendar to my application. The birthdays from my users and other events should be displayed in the calendar. This is my template:
<angular2-fullcalendar *ngIf="isInitialized; else loadingTemplate"
#calendar
id="calendar"
(onDateChanged)="onDateChanged($event)"
[options]="calendarOptions">
</angular2-fullcalendar>
Then, I tried to add the events in the component like so:
ngOnInit() {
let e = this.memberService.members$;
e.subscribe((members: IMember[]) => {
this.loadBirthdays(members).then((events: ICalendarEvent[]) =>
this.setEvents(events));
});
}
loadBirthdays(members: IMember[]): Promise<ICalendarEvent[]> {
let years = [];
years.push(moment().subtract('1', 'year').format('YYYY'));
years.push(moment().format('YYYY'));
years.push(moment().add('1', 'year').format('YYYY'));
members.forEach((member: IMember) => {
if (member.mainData.birthday) {
for (let i in years) {
let event: ICalendarEvent = {
title: 'Birthday ' + member.firstName + ' ' + member.lastName,
start: moment(member.birthday).set('year', years[i]).format('YYYY-MM-DD')
};
this.events.push(event);
}
}
});
return Promise.resolve(this.events);
}
setEvents(events: ICalendarEvent[]) {
const cal = $('calendar');
if (events && events.length > 0) {
events.forEach(el => {
cal.fullCalendar('renderEvent', el);
});
cal.fullCalendar('rerenderEvents');
}
this.isInitialized = true;
}
When I now try to open the calendar, no events are displayed and no errors are show. when I do a console.log for the "el" in the foreach I get an object with the name and birthday in format YYYY-MM-DD
What is wrong with my code?
The issue you've got is quite simple - fullCalendar is not preserving your event when the view or date range is changed.
The renderEvent method (https://fullcalendar.io/docs/renderEvent) has a 3rd optional parameter "stick", which is false by default. The documentation states
Normally, the event will disappear once the calendar refetches its event sources (example: when prev/next is clicked). However, specifying stick as true will cause the event to be permanently fixed to the calendar.
So in other words, since your example event is in December, and presumably your calendar is initially displaying the current month (March at the time of writing this), when you try to navigate to December, every press of "next" causes the event source to be refreshed, and your manually inserted event is lot.
All you need to do is change your renderEvent line to this to set the "stick" option to true:
call.fullCalendar('renderEvent', el, true);
You also should not need cal.fullCalendar('rerenderEvents'); at all, this can be removed.
P.S. You may be able to make your code a little more efficient. For instance instead of looping through events, you could use "renderEvents" (https://fullcalendar.io/docs/renderEvents) to add them all to the calendar at once:
if (events && events.length > 0) {
cal.fullCalendar('renderEvents', events, true);
}
You may also want to consider whether you need to use renderEvent(s) at all - the more normal way to set events on your calendar is to use the events option in the calendar initialisation options. You can either set it as a static array of event objects (https://fullcalendar.io/docs/events-array), or a JSON or other custom feed from a URL (https://fullcalendar.io/docs/events-json-feed or https://fullcalendar.io/docs/events-function). The latter two also have the advantage that the calendar dynamically updates itself from the remote data source when navigating between views and dates.

fullcalendar event rendering performance issue

So, basically all my events(there's min. 360 of them) have team1 vs. team2 or - vs. team2 or team1 vs. - placeholders.
And on the initial render events change color depending on whether the event has one or two teams.
Orange color for the one team , and green for the two teams. Also, the event changes color on click.
But mostly, I'm interested in increasing performance with rendering events.
Rendering performance is going really bad in fullCalendar, and I couldn't find any solution to this problem.
So here's my code:
eventRender: function (event, element) {
$(element).append((event.teams[0] != null ? event.teams[0] : '-') + '</br> vs. </br>' + (event.teams[1] != null ? event.teams[1] : '-'));
if (event.teams.length === 1) {
$(element).css('background', 'orange');
}
else if (event.teams.length > 1) {
$(element).css('background', 'green');
}
}
My main issue is that when I click on event to change its color, the script automatically goes to the eventRender or eventAfterRender event, and its behavior is exactly like the for statement - it iterates over events and then it does the stuff that I want to do with the individual event, but only when the loop lands on the clicked event.
Also, in the eventClick I've called $('#myCalendar').fullcalendar('updateEvent',event) and I think there is a bug, because it automatically goes to the eventAfterRender or the eventRender, iterating over the whole events collection again.
Even tough 'updateEvent' parameter should instruct fullCalendar to update/render only the specific event.
Does anyone have any advice on this subject?
Fullcalendar now supports the renderEvents method: https://fullcalendar.io/docs/renderEvents.
Simply build your events list and send them all at once:
$("#calendar").fullCalendar('renderEvents', events, true);
I know this is an old question, but i solved the same performance problem in v5 of the fullcalendar with this configuration option:
https://fullcalendar.io/docs/rerenderDelay
It basically adds a delay after each operation that would trigger a render event.
if the framework detects another operation within that delay, it renders these events in one operation and thereby increases performance.
setting the value to 1 (so 1 millisecond delay) did the trick for me. I simply added it to the configuration in my angular component:
calendarOptions: CalendarOptions = {
...,
rerenderDelay: 1,
}
In fullcalendars source-code (at least in my version of it) there is the renderEvent-handler, that calls reportEvents -function which is the bottleneck of performance. I worked my way around this issue, by adding handling of mass-rendering events to the source-code.
I wrote a short function:
function massRenderEvents(events, stick) {
var i;
for (i = 0; i < events.length; i += 1) {
normalizeEvent(events[i]);
if (!events[i].source) {
if (stick) {
stickySource.events.push(events[i]);
events[i].source = stickySource;
}
cache.push(events[i]);
}
}
reportEvents(cache);
}
Under "EventManager" -function, and added it to EventManagers exports, like:
t.massRenderEvents = massRenderEvents;
Now, for every batch of rendered events, the heavy and slow reportEvents is called just once. Note, that massRenderEvents -function is very similar to the original renderEvent -function.
I have changed
$("#calendar").fullCalendar('renderEvent', eventData1, true);
to
$("#calendar").fullCalendar('addEventSource', eventData1, true);
and that worked for me. I have read the issue on several related website and as per their suggestion I have done this.
The main difference between renderEvent and addEventSource is that the first one tries to interact with calendar when even a single event created which take much time because of regular callback function, and the second one sends a bucket of JSON events to calendar which require only single callback function which improve the performance and take less time.

Reset depth on Kendo datepicker

My client has requested that our Kendo DatePicker reset to the original view depth each time the calendar is opened.
Example: the calendar is set to start at "year" view and the user exits from "century" view without choosing a date. The next time the calendar opens it is back at "year" view rather than still being stuck on "century".
After digging through the api this function doesn't seem to be readily exposed.
Has anyone managed to do something similar?
I know this is an old question, but I ran into the same issue and found a solution.
You can call setOptions and pass the options you want to set, such as the depth or start. This will reset the depth allowing you to reset the depth when the calendar is opened. I've added that call to the close event so that it'll occur each time the calendar is closed.
Dojo Demo
var kendoOptions = {
depth: "year",
start: "year"
};
$("#datepicker").kendoDatePicker(kendoOptions);
var datepicker = $("#datepicker").data("kendoDatePicker");
datepicker.bind('close', function() {
// add if statement for scenearios where you want to reset the depth
this.setOptions(kendoOptions);
});

KendoUI MVVM Binding manually updating the source from displayed value

The situation:
I've extended a grid, and added an onkeydown event to listen for tab or arrow, whcih will allow the user to go to the next "editable" cell. I do this using ...
var grid = $("#" + that.gridId).data('kendoMyExtension');
grid.closeCell(currentCell);
grid.editCell(desiredCell);
Current behavior:
it works as expected however the when the cell closes, it doesn't persist the data (thru the correct binding) to the ViewModel.Field to which it is bound.
... However IFF you hit enter after making your changes, you will persist the changes.
What I've tried:
Manually making the update before I make the focus change (and fire off all those other kendo editing goodies) using
{grid Context}
that.saveRow();
that.dataSource.sync();
however these do not work. and usually end up throwing a undefined error somewhere in the bowels of kendo.
What I want:
Ideally kendo would supply at least one MVVM and kendo extension example (for a grid) that has all the functionality, events, etc. being bound to .. but.. since I will probably not get that asking here I will settle for:
Where does kendo "store" the changes?
What method is used to actually call the update {for a MVVM binding NOT a dataSource}.
Did I miss a binding keyword in the spirit of {one-way, two-way}?
Is kendo based on knockout.. can i use knockout techniques to get around the issues I'm having with kendo.
there are also a lot of other modifications to the display logic on this grid.. I am using a template to determine if the cell should be editable, a template to determine what should be rendered in nonedit & edit mode, and some IOC logic to wire it into the extension... (FYI)
o.k. after stepping thru kendo I found the issues... my datasource didn't have a reader (and it shouldn't afik) so sync and saveRow will not work reliably.
You MUST make sure the row is marked as 'k-edit-row' in your edit template :
{your edit template selector}.closest('tr').addClass("k-grid-edit-row");
if you want to get the saveRow to work, however in my case it wasn't reliable( it would only work if i stepped thru the code, otherwise fail every time...I have no clue as to why even tried the ever popular 'setTimeout(...)' ....)
eventually this is what I ended up doing:
myOnKeyDown:function(e){
var cell = $(e.target).closest("td")[0];
var row = $(e.target).closest("tr")[0];
if (cell != undefined) {
that.forceSync(cell, row, e.target.value);
}
},
forceSync: function (cell, row, value){
/// this is in the extension scope /////
/// Note that this uses functions in the dataSource Scope! ///
var cellFieldName = cell.kendoBindingTarget.target.options.fields.field;
var cellRecordUid = row.getAttribute('data-uid');
var that = $("#" + this.gridId).data('kendoMyExtension').dataSource, idx, length;
var data = that._flatData(that._data);
for (idx = 0, length = data.length; idx < length; idx++) {
if(data[idx].uid ===cellRecordUid){
data[idx][cellFieldName] = value;
}
}
},
... well after the considerable amount of time spent on this I felt that someone else should benefit...
Really hope it helps someone else.. -cheers.

jQuery FullCalendar - create standard list of events from Gcal

I'm using FullCalendar in a project and the month view is great. But I also need to create a simple unordered list of the events gathered from 4 different Gcal feeds, and I haven't been able to do it. Anyone got any ideas? A quick response would be great.
Posting here the solution for adding a new view in fullcalendar.js. I needed to implement an Agenda or List view in fullcalendar.js for one of my projects. In this process, I got a chance to reverse engineer the code by Adam. I must say that this plugin uses some very good coding practices and javascript concepts.
I think that it would be useful for users if I share my findings and solution here. Included list view has following features:
Fully functional and customizable List/Agenda View
Pagination using the included arrow buttons
Click/Hover effects on the events
Dynamic calling of events for the list view using pagination
First of all, we CAN NOT do it without touching the fullcalendar sourcecode. Javascript does not allow that kind of extensibility. Howvever, I have kept things as simple as possible and I am also posting the steps to replicate it from scratch along with the sourcecode. This will be helpful in case of future updates of fullcalendar.js
First of all we need to define a new view for showing the list of events. Views are defined as objects in fullcalendar.js and can be added using constructor functions. You can find the construction function for list view on this URL https://gist.github.com/amitesh-m/5744528. This function defines and initializes a new view called "list". Inside it, renderEvents is the main member function that renders the available events on this view and attaches the click/hover event triggers.
Next we need to change the updateEvents function of Calendar object (around line# 500). This is done to unlink the default event calling behavior of fullcalendar.js for the list view. Modified function will look like this:
function updateEvents(forceRender) {
if (currentView.name == "list") {
currentView.visStart = new Date(0);
currentView.visEnd = new Date(currentView.page * 1000);
refetchEvents();
} else if (!options.lazyFetching || isFetchNeeded(currentView.visStart, currentView.visEnd)) {
refetchEvents();
}
else if (forceRender) {
rerenderEvents();
}
}
Everything will work as earlier for other views but now the calendar will send a slightly different request to the event server for the list view. Now, fullcalendar will set "start=0" and "end=1" when somebody clicks on the list view. Number of items to show on the page is to be managed by the server.
Next, we need to make a change in renderView() function of the calendar object (around line#374). This is to enable pagination on our list based on the arrow buttons which are alreaedy included in fullcalendar.js. This function should look like this:
function renderView(inc) {
if (elementVisible()) {
ignoreWindowResize++; // because renderEvents might temporarily change the height before setSize is reached
unselect();
if (suggestedViewHeight === undefined) {
calcSize();
}
var forceEventRender = false;
if (!currentView.start || inc || date < currentView.start || date >= currentView.end) {
// view must render an entire new date range (and refetch/render events)
currentView.render(date, inc || 0); // responsible for clearing events
setSize(true);
forceEventRender = true;
}
else if (currentView.sizeDirty) {
// view must resize (and rerender events)
currentView.clearEvents();
setSize();
forceEventRender = true;
}
else if (currentView.eventsDirty) {
currentView.clearEvents();
forceEventRender = true;
}
if (currentView.name == "list") {
forceEventRender = true;
if (inc == 1) {
currentView.page++;
currentView.title = "Page " + currentView.page;
} else if (inc == -1) {
currentView.page--;
currentView.title = "Page " + currentView.page;
}
}
currentView.sizeDirty = false;
currentView.eventsDirty = false;
updateEvents(forceEventRender);
elementOuterWidth = element.outerWidth();
header.updateTitle(currentView.title);
var today = new Date();
if (today >= currentView.start && today < currentView.end) {
header.disableButton('today');
} else {
header.enableButton('today');
}
ignoreWindowResize--;
currentView.trigger('viewDisplay', _element);
}
}
Now, when somebody clicks on previous or next buttons, the calendar will send a request for new event data to the server. Value of "start" will remain 0 throughout for the list view, while the value for "end" will represent the subsequent page numbers.
That's it. All you need to do now is call the list view in your full calendar options. This can be done by adding "list" to the "right" of header object as follows
header: {
left: 'prev,next today',
center: 'title',
right: 'list,month,agendaWeek,agendaDay'
}
The demo is available on this URL:
http://tas.co.in/fullcalendar-with-listview/
the clientEvents method will retrieve all the events fullCalendar has fetched & has in memory. not sure if this will help you all the way
http://arshaw.com/fullcalendar/docs/event_data/clientEvents/

Categories

Resources