Refactored free-busy display

This commit is contained in:
Thomas Bruederli 2011-07-20 19:15:21 +02:00
parent 9a96600ddd
commit c80dea2330
6 changed files with 168 additions and 56 deletions

View file

@ -1235,11 +1235,15 @@ class calendar extends rcube_plugin
{
$email = get_input_value('email', RCUBE_INPUT_GPC);
$start = get_input_value('start', RCUBE_INPUT_GET);
$end = get_input_value('end', RCUBE_INPUT_GET);
$interval = 60; // in minutes
# $end = get_input_value('end', RCUBE_INPUT_GET);
$interval = 3600; // in seconds
if (!$start) $start = time();
if (!$end) $end = $start + 86400 * 7;
if (!$end) $end = $start + 86400 * 30;
// reset $start/$end to midnight
#$start = gmmktime(0, 0, 0, gmdate('n', $start), gmdate('j'), gmdate('Y'));
#$end = gmmktime(23, 59, 59, gmdate('n', $end), gmdate('j'), gmdate('Y'));
$fblist = $this->driver->get_freebusy_list($email, $start, $end);
$slots = array();
@ -1247,7 +1251,7 @@ class calendar extends rcube_plugin
// build a list from $start till $end with blocks representing the fb-status
for ($s = 0, $t = $start; $t <= $end; $s++) {
$status = self::FREEBUSY_UNKNOWN;
$t_end = $t + $interval*60;
$t_end = $t + $interval;
// determine attendee's status
if (is_array($fblist)) {
@ -1260,12 +1264,12 @@ class calendar extends rcube_plugin
}
}
}
$slots[$s] = $status;
$t = $t_end;
}
echo json_encode(array('email' => $email, 'slots' => $slots, 'interval' => $interval));
echo json_encode(array('email' => $email, 'start' => intval($start), 'interval' => $interval, 'slots' => $slots));
exit;
}

View file

@ -44,6 +44,8 @@ function rcube_calendar_ui(settings)
var ignore_click = false;
var event_attendees = null;
var attendees_list;
var freebusy_ui = {};
var freebusy_data = {};
// general datepicker settings
var datepicker_settings = {
@ -601,7 +603,7 @@ function rcube_calendar_ui(settings)
title.select();
};
var event_freebusy_dialog = function()
{
var $dialog = $('#eventfreebusy').dialog('close');
@ -615,52 +617,39 @@ function rcube_calendar_ui(settings)
var endtime = $('#schedule-endtime').val($.fullCalendar.formatDate(event.end, settings['time_format'])).show();
var allday = $('#edit-allday').get(0);
if (event.allDay) {
if (allday.checked) {
starttime.val("00:00").hide();
endtime.val("23:59").hide();
}
// render time slots
var now = new Date(), range_start = new Date(), range_end = new Date();
range_start.setTime(Math.max(now, event.start.getTime() - 86400000 * 3)); // start 3 days before event
range_start.setHours(0);
range_start.setMinutes(0);
range_start.setSeconds(0);
range_end.setTime(range_start.getTime() + 86400000 * 10); // show 10 days
var interval = 60;
var now = new Date(), fb_start = new Date(), fb_end = new Date();
fb_start.setTime(Math.max(now, event.start));
fb_start.setHours(0);
fb_start.setMinutes(0);
fb_start.setSeconds(0);
fb_start.setMilliseconds(0);
fb_end.setTime(fb_start.getTime() + 86400000);
var interval = 60; // minutes
var dayslots = 24;
var lastdate, datestr, curdate = new Date(), dates_row = '<tr class="dates">', times_row = '<tr class="times">', slots_row = '';
for (var s = 0, t = range_start.getTime(); t < range_end.getTime(); s++) {
curdate.setTime(t);
datestr = $.fullCalendar.formatDate(curdate, settings['date_format']);
if (datestr != lastdate) {
dates_row += '<th colspan="' + dayslots + '" class="boxtitle date' + $.fullCalendar.formatDate(curdate, 'ddMMyyyy') + '">' + Q(datestr) + '</th>';
lastdate = datestr;
}
times_row += '<td>' + Q($.fullCalendar.formatDate(curdate, settings['time_format'])) + '</td>';
slots_row += '<td class="unknown">&nbsp;</td>';
t += interval * 60000;
}
dates_row += '</tr>';
times_row += '</tr>';
freebusy_data = {};
freebusy_ui.loading = 1;
freebusy_ui.numdays = 1;
freebusy_ui.interval = interval;
freebusy_ui.start = fb_start;
freebusy_ui.end = new Date(freebusy_ui.start.getTime() + 86400000 * freebusy_ui.numdays);
render_freebusy_grid(0);
// render list of attendees
var domid, dispname, data, list_html = '', times_html = '';
var domid, dispname, data, list_html = '';
for (var i=0; i < event_attendees.length; i++) {
data = event_attendees[i];
dispname = Q(data.name || data.email);
domid = String(data.email).replace(rcmail.identifier_expr, '');
list_html += '<div class="attendee ' + String(data.role).toLowerCase() + '" id="rcmli' + domid + '">' + dispname + '</div>';
times_html += '<tr id="fbrow' + domid + '">' + slots_row + '</tr>';
}
$('#schedule-attendees-list').html(list_html);
$('#schedule-freebusy-times > thead').html(dates_row + times_row);
$('#schedule-freebusy-times > tbody').html(times_html);
// dialog buttons
var buttons = {};
@ -680,9 +669,71 @@ function rcube_calendar_ui(settings)
buttons: buttons,
minWidth: 640,
width: 850
}).show();
// fetch data from server
freebusy_ui.loading = 0;
load_freebusy_data(fb_start, interval);
};
// render an HTML table showing free-busy status for all the event attendees
var render_freebusy_grid = function(delta)
{
if (delta) {
freebusy_ui.start.setTime(freebusy_ui.start.getTime() + 86400000 * delta);
freebusy_ui.end = new Date(freebusy_ui.start.getTime() + 86400000 * freebusy_ui.numdays);
}
var dayslots = Math.floor(1440 / freebusy_ui.interval);
var lastdate, datestr, css, curdate = new Date(), dates_row = '<tr class="dates">', times_row = '<tr class="times">', slots_row = '';
for (var s = 0, t = freebusy_ui.start.getTime(); t < freebusy_ui.end.getTime(); s++) {
curdate.setTime(t);
datestr = $.fullCalendar.formatDate(curdate, settings['date_format']);
if (datestr != lastdate) {
dates_row += '<th colspan="' + dayslots + '" class="boxtitle date' + $.fullCalendar.formatDate(curdate, 'ddMMyyyy') + '">' + Q(datestr) + '</th>';
lastdate = datestr;
}
// TODO: define working hours by config
css = !(curdate.getHours() >= 6 && curdate.getHours() <= 18) ? 'offhours' : 'workinghours';
times_row += '<td class="' + css + '">' + Q($.fullCalendar.formatDate(curdate, settings['time_format'])) + '</td>';
slots_row += '<td class="' + css + ' unknown">&nbsp;</td>';
t += freebusy_ui.interval * 60000;
}
dates_row += '</tr>';
times_row += '</tr>';
// render list of attendees
var domid, data, list_html = '', times_html = '';
for (var i=0; i < event_attendees.length; i++) {
data = event_attendees[i];
domid = String(data.email).replace(rcmail.identifier_expr, '');
times_html += '<tr id="fbrow' + domid + '">' + slots_row + '</tr>';
}
$('#schedule-freebusy-times > thead').html(dates_row + times_row);
$('#schedule-freebusy-times > tbody').html(times_html);
// if we have loaded free-busy data, show it
if (!freebusy_ui.loading) {
if (date2unixtime(freebusy_ui.start) < freebusy_data.start || date2unixtime(freebusy_ui.end) > freebusy_data.end) {
load_freebusy_data(freebusy_ui.start, freebusy_ui.interval)
return;
}
for (var email, i=0; i < event_attendees.length; i++) {
if ((email = event_attendees[i].email))
update_freebusy_display(email);
}
}
};
// fetch free-busy information for each attendee from server
var load_freebusy_data = function(from, interval)
{
var start = new Date(from.getTime() - 86400000 * 2); // start 1 days before event
var end = new Date(start.getTime() + 86400000 * 14); // load 14 days
// load free-busy information for every attendee
var domid, email
@ -690,35 +741,56 @@ function rcube_calendar_ui(settings)
if ((email = event_attendees[i].email)) {
domid = String(email).replace(rcmail.identifier_expr, '');
$('#rcmli' + domid).addClass('loading');
freebusy_ui.loading++;
$.ajax({
type: 'GET',
dataType: 'json',
url: rcmail.url('freebusy-times'),
data: { email:email, start:date2unixtime(range_start), end:date2unixtime(range_end), _remote: 1 },
data: { email:email, start:date2unixtime(start), end:date2unixtime(end), _remote: 1 },
success: function(data){
var status_classes = ['unknown','free','busy','tentative','out-of-office'];
freebusy_ui.loading--;
// copy data to member var
var ts = data.start - 0;
freebusy_data.start = ts;
freebusy_data[data.email] = {};
for (var i=0; i < data.slots.length; i++) {
freebusy_data[data.email][ts] = data.slots[i];
ts += data.interval;
}
freebusy_data.end = ts;
// hide loading indicator
var domid = String(data.email).replace(rcmail.identifier_expr, '');
$('#rcmli' + domid).removeClass('loading');
var row = $('#fbrow' + domid);
if (data.slots && row.length) {
row.children().each(function(i, cell){
cell.className = data.slots[i] ? status_classes[data.slots[i]] : 'unknown';
});
}
// update display
update_freebusy_display(data.email);
}
});
}
}
// scroll to event date
var pos = $('#schedule-freebusy-times th.date' + $.fullCalendar.formatDate(event.start, 'ddMMyyyy')).position();
if (pos && pos.left)
$('#schedule-freebusy-times').parent().scrollLeft(pos.left);
};
// update free-busy grid with status loaded from server
var update_freebusy_display = function(email)
{
var status_classes = ['unknown','free','busy','tentative','out-of-office'];
var domid = String(email).replace(rcmail.identifier_expr, '');
var row = $('#fbrow' + domid);
var ts = date2unixtime(freebusy_ui.start);
var fbdata = freebusy_data[email];
if (fbdata && fbdata[ts] && row.length) {
row.children().each(function(i, cell){
cell.className = cell.className.replace('unknown', fbdata[ts] ? status_classes[fbdata[ts]] : 'unknown');
ts += freebusy_ui.interval * 60;
});
}
};
// update event properties and attendees availability if event times have changed
var event_times_changed = function()
{
@ -1523,6 +1595,15 @@ function rcube_calendar_ui(settings)
$('#edit-attendee-schedule').click(function(){
event_freebusy_dialog();
});
$('#shedule-freebusy-prev').button().click(function(){ render_freebusy_grid(-1); });
$('#shedule-freebusy-next').button().click(function(){ render_freebusy_grid(1); }).parent().buttonset();
$('#schedule-freebusy-wokinghours').click(function(){
$('#workinghourscss').remove();
if (this.checked)
$('<style type="text/css" id="workinghourscss"> td.offhours { display:none } </style>').appendTo('head');
});
// add proprietary css styles if not IE
if (!bw.ie)

View file

@ -198,6 +198,9 @@ class kolab_calendar
{
$this->_fetch_events();
if (!empty($search))
$search = mb_strtolower($search);
$events = array();
foreach ($this->events as $id => $event) {
// filter events by search query

View file

@ -98,6 +98,7 @@ $labels['availunknown'] = 'Unknown';
$labels['availoutofoffice'] = 'Out of Office';
$labels['scheduletime'] = 'Available times';
$labels['sendnotifications'] = 'Send notifications';
$labels['onlyworkinghours'] = 'Show only working hours';
// event dialog tabs
$labels['tabsummary'] = 'Summary';

View file

@ -634,7 +634,7 @@ td.topalign {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
margin-bottom: 2em;
margin: 0.5em 0;
}
#attendees-freebusy-table td.attendees {
@ -683,12 +683,27 @@ td.topalign {
}
#schedule-freebusy-times tr.dates th {
border-bottom: 0;
border-color: #aaa;
border-style: solid;
border-width: 0 1px 0 1px;
}
#schedule-freebusy-times tr.times td {
min-width: 30px;
font-size: 9px;
padding: 5px 2px 6px 2px;
text-align: center;
}
#eventfreebusy .schedule-options {
position: relative;
margin-bottom: 2em;
}
#eventfreebusy .schedule-buttons {
position: absolute;
top: 0;
right: 0;
}
span.edit-alarm-set {

View file

@ -186,7 +186,15 @@
<div id="eventfreebusy" class="uidialog">
<roundcube:object name="plugin.attendees_freebusy_table" id="attendees-freebusy-table" cellspacing="0" cellpadding="0" border="0" />
<div class="schedule-options">
<label><input type="checkbox" id="schedule-freebusy-wokinghours" value="1" /><roundcube:label name="calendar.onlyworkinghours" /></label>
<div class="schedule-buttons">
<button id="shedule-freebusy-prev" title="<roundcube:label name='previouspage' />">&#9668;</button>
<button id="shedule-freebusy-next" title="<roundcube:label name='nextpage' />">&#9658;</button>
</div>
</div>
<div class="form-section">
<label for="schedule-startdate"><roundcube:label name="calendar.start" /></label>
<input type="text" name="startdate" size="10" id="schedule-startdate" disabled="true" /> &nbsp;