Putting Your Google Calendar Analog Not in 30 Lines

When developing a project for a transport company engaged in passenger transportation, the task arose to implement its Google Calendar analogue for embedding into the system.
For some reasons (deep integration into the project, communication with a bunch of different entities, full control over all parts of the code, etc.), using a solution from Google was irrational from many points of view.
So, the conditions of the problem:
- The interface should be as close as possible to the interface from Google (because before that they used it)
- Normal implementation of RFC 2445 , parts of it regarding RRULE (repetition patterns)
- Fast speed of calculating the dates of events (in this case, flights) and their rendering in the browser
- Maximize the use of existing libraries to reduce time spent.
If the topic is interesting or you have something to say, because work is still underway and this post affects only a small part - I ask for a cut, I will be glad to meaningful advice.
Initially, before detailing, it was decided to determine the overall strategy for the work of this solution. An attempt to use ruby and gem ice_cube failed because already quite a large amount of code was written in php and part of the project to transfer to another programming language is not very kosher. Well, performance issues (or my inexperience with RoR).
As a result, after reflection, the following was born:
- For visualization in the form of a calendar, a slightly modified jQuery plugin FullCalendar will be responsible
- The creation of recurring flights is entrusted to Scheduler.js from the FuelUX suite
- The repetition patterns will be stored in the database in the form “FREQ = DAILY; INTERVAL = 2; UNTIL = 20130130T230000Z;” to reduce the size of the database (because if you store each flight separately and the end of the repetitions is not assigned, the number of individual flights goes to infinity)
- Conversion of repetition patterns to a set of flight dates will be implemented on the client side for unloading server capacities
The first thing that will need to be implemented is getting RRULE, converting to a set of dates and rendering using the FullCalendar plugin. After a short search, the following conversion solution was found - rrule.js working both in the browser and as an application on node.js, which further provides the ability to transfer to the server from the client.
The approximate path is clear - let's get started. I warn you right away, a prototype is being developed and the code is written based on speed indicators, not high-quality ones.
Suppose we have a json array of RRULE rules with the fields name, length and the repetition pattern itself. We will omit its receipt from the backend.
[{
"name": "Reccurence Event #1",
"length": "120",
"rrule": "DTSTART=20020201T083000Z;FREQ=WEEKLY;WKST=MO;BYDAY=WE,FR"
},
{
"name": "Reccurence Event #2",
"length": "120",
"rrule": "FREQ=MONTHLY;DTSTART=20000201T083000Z;WKST=MO;BYDAY=TU"
},
{
"name": "Reccurence Event #3",
"length": "120",
"rrule": "FREQ=DAILY;DTSTART=20000201T063000Z;WKST=MO;BYDAY=MO,FR"
}
]
We initialize arrays and create rrule.js plugin objects from RRULE strings:
var data = private_env.get_data();
var rules = new Array();
var occurs = new Array();
for (var k in data){
rules.push(
{
name: data[k].name,
length: data[k].length,
rrule: RRule.fromString(data[k].rrule)
});
}
We get from our objects a list of dates for each flight, where DATE_START and DATE_END correspond to the beginning and end of the distance for which we need to get them:
for (var k in rules){
occurs.push(
{
name: rules[k].name,
length: rules[k].length,
occurs: rules[k]['rrule'].beetween(DATE_START,DATE_END)
});
}
We clear the calendar before rendering:
$calendar.fullCalendar('removeEvents', function (event){
return true;
});
And we display our flights to the screen:
for (var k in occurs){
for (var i in occurs[k].occurs){
var event = {
id: k,
title: occurs[k].name,
start: occurs[k].occurs[i],
/* Не элегантно высчитываем конец рейса по длине в минутах, но пусть */
end: new Date(occurs[k].occurs[i].getTime()+(1000*60*occurs[k].length)),
allDay: false
};
/* Фикс renderEvent, чтобы не запускал рендер каждое добавление рейса, т.к. регрессия производительности */
$calendar.fullCalendar('renderEvent', event, 1, 0);
}
}
/* Рендерим весь стек рейсов, отвечает последняя 1 за это */
this.fullCalendar('renderEvent',{allDay: false}, 0, 1);
We wrap all the above in one function, for example, render (DATE_START, DATE_END) and call the FullCalendar plugin during the viewRender event:
...
viewRender: function(view, element){
$(private_env.env_self).service('render', 'between', view.visStart, view.visEnd);
}
...
At the moment, we got about the following picture:

UPD : Example
Turning a post into sheets from code does not see the point, I think the general idea is clear. Appearance and so forth is not difficult to modify.
A few words about creating and editing.
A large number of events in FullCalendar allows us to implement editing functionality. Required Functionality:
- When clicked, the flight editing form opens
- When dragging and dropping, we issue a request for action options. Editing a single flight, editing the entire repetition chain, editing future repetitions
The task is trivial and boils down to hanging functions on events eventClick, eventDrop, eventResize. By the way, the last two have the ability to cancel actions:
...
revertFunc();
...
The only caveat. When choosing “Only change a single flight” we cut “RRULE FREQ = MONTHLY; DTSTART = 20000201T083000Z; WKST = MO; BYDAY = TU” into three different patterns.
What happened before this flight, the current flight itself and what will happen after. This is solved by changing the options in the DTSTART and UNTIL rules.
As for the creation of flights, Scheduler.js can output a string of repetitions according to RFC 2445, which is what we need:
...
$('#myScheduler').scheduler('value')
...
Modifying this hodgepodge of plugins has yet to take quite a while, but the direction where to move is clear.
And thank you for your attention, if there is criticism or suggestions for improvement, I ask you in the studio and I will only be glad.