I am struggling displaying the correct data from my database with FullCalendar. The database structure I've adopted is to store rrules as ReccurringEvent database objects, and individual deviations from those rrules as separate IndividualEvent objects, all of which have a "Done" property. The goal is to display each event's "Done" status individually even if they're all part of the same RRule.
ReccurringEvents are set up in FullCalendar as:
calendar.addEvent({
title: item['title'],
rrule: {
dtstart: item['startDate'],
freq: item['frequency']
},
allDay: item['allDay'],
backgroundColor: backgroundRgb,
textColor: computeTextColor(backgroundRgb),
extendedProps: {
recurringEventID: item['recurringEventID'],
description: item['description']
},
});
Then a separate javascript bit modifies those individual occurrences as "done" and attaches the appropriate IndividualEventID to the occurrence.
var events = calendar.getEvents();
individualEventList.forEach(function(item){
events.forEach(function(event){
var indivdate = new Date(item['startDate']).toLocaleDateString('en-CA');
var eventdate = event.start.toLocaleDateString('en-CA');
//if the event is on the correct date and is a part of the rrule...
if(event.extendedProps.recurringEventID == item['recurringID'] && eventdate == indivdate)
{
console.log("match!");
event.setExtendedProp('singleID',item['individualEventID'])
//then modify its 'done' state
event.setExtendedProp('done',item['done']);
}
})
})
The "match!" console log fires only once, as I am expecting, when I modify a single event instance to be done and then load my database in. However all events in the rrule show up as done when I click on them/investigate their extendedProps.
It seems that modifying one event's extendedProps will modify all events' extendedProps if they're in the same rrule. How do I control this behavior? Can I break an event out of its rrule? Do I need to just do this computation in a different way?
I had to completely avoid dealing with extendedProps that are per single event. What I ended up doing:
When a user clicks on the event and I want to render it as done or not, I will check that events recurringEventID extendedProp, then check all IndividualEventList events to see if there's one that came from the same recurring event, and then check that the dates match. Then I'll check an HTML checkbox based off of the IndividualEventList event's done state.
In other words, I'm completely bypassing using extendedProps for anything other than "tell me which recurring event the event that was clicked belongs to".
Since I am assuming that all events will be all-day events this will work for me, but it's not a very general solution.
Related
In CKEditor 4 I want to fire some action on key and paste events. I've got working code for single event:
$('#some_id').ckeditor({
some: config
}).ckeditor().editor.on('key', function(evt) {
//some action here
});
And I don't want to repeat all code for next event. I've searched ckeditor docs - and it says that on method takes only string, so give it an array of events isn't possible. I've tried pass multiple events as string key, paste - it wasn't best idea. Another way could be made an array of events and iterate it with code above - this solution seems to be not ideal, but the best I can figure out for now. Have You any better ideas for this problem?
Since nobody have any idea in this matter, I finished with best solution I could figure out on this moment: provide array of events and iterate it. I paste here my solution for others facing same dillema:
var editor = $('#textarea').ckeditor({
//some:config
}).ckeditor().editor;
var events = ['event1', 'event2'];
for (event of events) {
editor.on(event, function(evt) {
//Yours actions
}
}
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.
I am working on a form builder application. I need to store the events for the particular element into the database at design level and retrieve the events when the form is in published state.
I am storing the events as follows and this is example with more than one event for a control:
Structure of saving events in Database for one element:
{
Event:
{
EventType: "click",
EventHandler: "var i = 0; alert(i);"
},
Event:
{
EventType: "change",
EventHandler: "var i = 0; alert(i);"
}
}
Example HTML Code:
I am giving an example for textbox control in a form builder.
<input type="text" id="text1" />
On publishing the built form, I need to bind the above events for the built form which contains only one textbox.
Current Approach:
What I did is, I used eval() to execute the code and my code is as follows:
function BindEventsForControl(controlId,eventType,eventHandlerValue){
//Contruct the event string
var scriptString = "$(document).on('"+ eventType +"',"'+ controlId +"',function(){"+ eventHandlerValue +"});"
//Evaluate the above expression
eval(scriptString);
}
The above method works perfectly.
My concern:
I need to call BindEventsForControl() for each control in a form and need to use eval() many times. Is this feasible ?
My question:
Is my approach feasible ? Is there any better approach out there? Again, I am mentioning I want to execute the events stored as string.
If you know anyother new method, then kindly help me with that.
Also, I am using Knockout.js in my application. Is there any way to approach with knockout.js for these kind of scenario ?
I have a web application that uses fullcalendar.io. I only have one event per day maximum. My problem is that everytime the events are fetched, it fetches all events, and this results into re-rendering of all the events in the month (at least). I don't want to refetch existing (in client already) events!
Why is this bad? Well, it's bad because the FullCalendar is programmed so that it will first hide/delete the clientside events from showing, then waits until the fetch is done, and then re-renders all the events. This results into a situation where for almost a second of time, the calendar month shows empty, before it renders the events. How I would like it to behave is: The calendar should fetch only the events that are not currently showed in that visible month. Then when it returns, it only re-renders those events that are new.
How I tried to achieve it is this:
events: {
data: function () {
return {
dynamic_value: JSON.stringify({myarray:$('#calendar').fullCalendar('clientEvents')})
};
},
url: '/calendar/events',
}
What I tried to do was to set a dynamic parameter, where I should put all the client side events. And then in the server backend, I would only send back events that are not included in that parameter. The problem is, that if I call the fullCalendar('clientEvents') in this place (inside the events object), it results in an empty array. So how could I give the events object a parameter to indicate that it should only fetch new events? Or am I approaching this the wrong way from the beginning?
Even if I would have found a way (I actually did) to pass old events to the dynamic_value (parameter), that wouldn't have solved the real problem.
And why is that? It is because I was using refetching for displaying the events. I looked under the hood of FullCalendar refetching function, and what it does is that it first deletes all the events from the calendar, and after that, it starts fetching new events. And during that ajax fetch, the calendar will stay blank. So it wouldn't helped me even if I had set the old events as a paremeter, and then applied serverside filtering.
But, luckily, I was able to solve the real problem. I continued fetching all events (I might change it in the future though), but I managed to avoid the problem. What I did was that I (again) looked under the hood. I realised that in FullCalendar there exists many un-documented functions that are usable through the api. One of them is a fetchEvents function (used by the refetchEvents function). The fetchEvents function only does the fetching, and it doesn't remove any old events. So what I did was that instead of using the reFetchEvents, I used:
$('#calendar').fullCalendar(
'fetchEvents',
$('#calendar').fullCalendar('getView').start,
$('#calendar').fullCalendar('getView').end
);
The fetchEvents function just needs the start and end as paramaters. I was able to get those by following how the fetchEvents function is used by refetchEvents function, and there they got the start and end from getView function. I was able to use the getView function as seen above.
But this is only half of the solution. Surely, I must somehow delete the old calendar events in a proper place. Well, for that, I was lucky to find also a function that could be used through the api (even not documented). I had to change the events part configuration in my fullCalendar. Instead of simple json feed URL, I changed it to ajax specification with success function, so that I could, in proper place, do the destroying of old events. So now the destroying part happens only after the ajax response, which will make the destroy to render process quick:
events: function(start, end, timezone, callback) {
$.ajax({
url: '/calendar/events',
dataType: 'json',
data: {
start: start.unix(),
end: end.unix()
},
success: function(doc) {
var events = [];
doc.forEach(function(eventObject) {
events.push({
title: eventObject.title,
start: eventObject.start,
user: eventObject.user,
allDay: eventObject.allDay,
overlap: eventObject.overlap,
created: eventObject.created
});
});
$('#calendar').fullCalendar('destroyEvents');
callback(events);
}
});
}
As you can see above, I use the fullCalendar destroyEvents before sending the new events for the callback to be rendered. And this solved the blank calendar during ajax call.
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.