Refactored free-busy display
This commit is contained in:
parent
9a96600ddd
commit
c80dea2330
6 changed files with 168 additions and 56 deletions
|
@ -1235,11 +1235,15 @@ class calendar extends rcube_plugin
|
||||||
{
|
{
|
||||||
$email = get_input_value('email', RCUBE_INPUT_GPC);
|
$email = get_input_value('email', RCUBE_INPUT_GPC);
|
||||||
$start = get_input_value('start', RCUBE_INPUT_GET);
|
$start = get_input_value('start', RCUBE_INPUT_GET);
|
||||||
$end = get_input_value('end', RCUBE_INPUT_GET);
|
# $end = get_input_value('end', RCUBE_INPUT_GET);
|
||||||
$interval = 60; // in minutes
|
$interval = 3600; // in seconds
|
||||||
|
|
||||||
if (!$start) $start = time();
|
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);
|
$fblist = $this->driver->get_freebusy_list($email, $start, $end);
|
||||||
$slots = array();
|
$slots = array();
|
||||||
|
@ -1247,7 +1251,7 @@ class calendar extends rcube_plugin
|
||||||
// build a list from $start till $end with blocks representing the fb-status
|
// build a list from $start till $end with blocks representing the fb-status
|
||||||
for ($s = 0, $t = $start; $t <= $end; $s++) {
|
for ($s = 0, $t = $start; $t <= $end; $s++) {
|
||||||
$status = self::FREEBUSY_UNKNOWN;
|
$status = self::FREEBUSY_UNKNOWN;
|
||||||
$t_end = $t + $interval*60;
|
$t_end = $t + $interval;
|
||||||
|
|
||||||
// determine attendee's status
|
// determine attendee's status
|
||||||
if (is_array($fblist)) {
|
if (is_array($fblist)) {
|
||||||
|
@ -1265,7 +1269,7 @@ class calendar extends rcube_plugin
|
||||||
$t = $t_end;
|
$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;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,8 @@ function rcube_calendar_ui(settings)
|
||||||
var ignore_click = false;
|
var ignore_click = false;
|
||||||
var event_attendees = null;
|
var event_attendees = null;
|
||||||
var attendees_list;
|
var attendees_list;
|
||||||
|
var freebusy_ui = {};
|
||||||
|
var freebusy_data = {};
|
||||||
|
|
||||||
// general datepicker settings
|
// general datepicker settings
|
||||||
var datepicker_settings = {
|
var datepicker_settings = {
|
||||||
|
@ -615,52 +617,39 @@ function rcube_calendar_ui(settings)
|
||||||
var endtime = $('#schedule-endtime').val($.fullCalendar.formatDate(event.end, settings['time_format'])).show();
|
var endtime = $('#schedule-endtime').val($.fullCalendar.formatDate(event.end, settings['time_format'])).show();
|
||||||
var allday = $('#edit-allday').get(0);
|
var allday = $('#edit-allday').get(0);
|
||||||
|
|
||||||
if (event.allDay) {
|
if (allday.checked) {
|
||||||
starttime.val("00:00").hide();
|
starttime.val("00:00").hide();
|
||||||
endtime.val("23:59").hide();
|
endtime.val("23:59").hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
// render time slots
|
// render time slots
|
||||||
var now = new Date(), range_start = new Date(), range_end = new Date();
|
var interval = 60;
|
||||||
range_start.setTime(Math.max(now, event.start.getTime() - 86400000 * 3)); // start 3 days before event
|
var now = new Date(), fb_start = new Date(), fb_end = new Date();
|
||||||
range_start.setHours(0);
|
fb_start.setTime(Math.max(now, event.start));
|
||||||
range_start.setMinutes(0);
|
fb_start.setHours(0);
|
||||||
range_start.setSeconds(0);
|
fb_start.setMinutes(0);
|
||||||
range_end.setTime(range_start.getTime() + 86400000 * 10); // show 10 days
|
fb_start.setSeconds(0);
|
||||||
|
fb_start.setMilliseconds(0);
|
||||||
|
fb_end.setTime(fb_start.getTime() + 86400000);
|
||||||
|
|
||||||
var interval = 60; // minutes
|
freebusy_data = {};
|
||||||
var dayslots = 24;
|
freebusy_ui.loading = 1;
|
||||||
var lastdate, datestr, curdate = new Date(), dates_row = '<tr class="dates">', times_row = '<tr class="times">', slots_row = '';
|
freebusy_ui.numdays = 1;
|
||||||
for (var s = 0, t = range_start.getTime(); t < range_end.getTime(); s++) {
|
freebusy_ui.interval = interval;
|
||||||
curdate.setTime(t);
|
freebusy_ui.start = fb_start;
|
||||||
datestr = $.fullCalendar.formatDate(curdate, settings['date_format']);
|
freebusy_ui.end = new Date(freebusy_ui.start.getTime() + 86400000 * freebusy_ui.numdays);
|
||||||
if (datestr != lastdate) {
|
render_freebusy_grid(0);
|
||||||
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"> </td>';
|
|
||||||
|
|
||||||
t += interval * 60000;
|
|
||||||
}
|
|
||||||
dates_row += '</tr>';
|
|
||||||
times_row += '</tr>';
|
|
||||||
|
|
||||||
// render list of attendees
|
// 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++) {
|
for (var i=0; i < event_attendees.length; i++) {
|
||||||
data = event_attendees[i];
|
data = event_attendees[i];
|
||||||
dispname = Q(data.name || data.email);
|
dispname = Q(data.name || data.email);
|
||||||
domid = String(data.email).replace(rcmail.identifier_expr, '');
|
domid = String(data.email).replace(rcmail.identifier_expr, '');
|
||||||
|
|
||||||
list_html += '<div class="attendee ' + String(data.role).toLowerCase() + '" id="rcmli' + domid + '">' + dispname + '</div>';
|
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-attendees-list').html(list_html);
|
||||||
$('#schedule-freebusy-times > thead').html(dates_row + times_row);
|
|
||||||
$('#schedule-freebusy-times > tbody').html(times_html);
|
|
||||||
|
|
||||||
// dialog buttons
|
// dialog buttons
|
||||||
var buttons = {};
|
var buttons = {};
|
||||||
|
@ -680,9 +669,71 @@ function rcube_calendar_ui(settings)
|
||||||
buttons: buttons,
|
buttons: buttons,
|
||||||
minWidth: 640,
|
minWidth: 640,
|
||||||
width: 850
|
width: 850
|
||||||
|
|
||||||
}).show();
|
}).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"> </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
|
// load free-busy information for every attendee
|
||||||
var domid, email
|
var domid, email
|
||||||
|
@ -690,35 +741,56 @@ function rcube_calendar_ui(settings)
|
||||||
if ((email = event_attendees[i].email)) {
|
if ((email = event_attendees[i].email)) {
|
||||||
domid = String(email).replace(rcmail.identifier_expr, '');
|
domid = String(email).replace(rcmail.identifier_expr, '');
|
||||||
$('#rcmli' + domid).addClass('loading');
|
$('#rcmli' + domid).addClass('loading');
|
||||||
|
freebusy_ui.loading++;
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
url: rcmail.url('freebusy-times'),
|
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){
|
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, '');
|
var domid = String(data.email).replace(rcmail.identifier_expr, '');
|
||||||
$('#rcmli' + domid).removeClass('loading');
|
$('#rcmli' + domid).removeClass('loading');
|
||||||
|
|
||||||
var row = $('#fbrow' + domid);
|
// update display
|
||||||
if (data.slots && row.length) {
|
update_freebusy_display(data.email);
|
||||||
row.children().each(function(i, cell){
|
|
||||||
cell.className = data.slots[i] ? status_classes[data.slots[i]] : 'unknown';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
// update event properties and attendees availability if event times have changed
|
||||||
var event_times_changed = function()
|
var event_times_changed = function()
|
||||||
{
|
{
|
||||||
|
@ -1524,6 +1596,15 @@ function rcube_calendar_ui(settings)
|
||||||
event_freebusy_dialog();
|
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
|
// add proprietary css styles if not IE
|
||||||
if (!bw.ie)
|
if (!bw.ie)
|
||||||
$('div.fc-content').addClass('rcube-fc-content');
|
$('div.fc-content').addClass('rcube-fc-content');
|
||||||
|
|
|
@ -198,6 +198,9 @@ class kolab_calendar
|
||||||
{
|
{
|
||||||
$this->_fetch_events();
|
$this->_fetch_events();
|
||||||
|
|
||||||
|
if (!empty($search))
|
||||||
|
$search = mb_strtolower($search);
|
||||||
|
|
||||||
$events = array();
|
$events = array();
|
||||||
foreach ($this->events as $id => $event) {
|
foreach ($this->events as $id => $event) {
|
||||||
// filter events by search query
|
// filter events by search query
|
||||||
|
|
|
@ -98,6 +98,7 @@ $labels['availunknown'] = 'Unknown';
|
||||||
$labels['availoutofoffice'] = 'Out of Office';
|
$labels['availoutofoffice'] = 'Out of Office';
|
||||||
$labels['scheduletime'] = 'Available times';
|
$labels['scheduletime'] = 'Available times';
|
||||||
$labels['sendnotifications'] = 'Send notifications';
|
$labels['sendnotifications'] = 'Send notifications';
|
||||||
|
$labels['onlyworkinghours'] = 'Show only working hours';
|
||||||
|
|
||||||
// event dialog tabs
|
// event dialog tabs
|
||||||
$labels['tabsummary'] = 'Summary';
|
$labels['tabsummary'] = 'Summary';
|
||||||
|
|
|
@ -634,7 +634,7 @@ td.topalign {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
margin-bottom: 2em;
|
margin: 0.5em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#attendees-freebusy-table td.attendees {
|
#attendees-freebusy-table td.attendees {
|
||||||
|
@ -683,12 +683,27 @@ td.topalign {
|
||||||
}
|
}
|
||||||
|
|
||||||
#schedule-freebusy-times tr.dates th {
|
#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 {
|
#schedule-freebusy-times tr.times td {
|
||||||
|
min-width: 30px;
|
||||||
font-size: 9px;
|
font-size: 9px;
|
||||||
padding: 5px 2px 6px 2px;
|
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 {
|
span.edit-alarm-set {
|
||||||
|
|
|
@ -187,6 +187,14 @@
|
||||||
<div id="eventfreebusy" class="uidialog">
|
<div id="eventfreebusy" class="uidialog">
|
||||||
<roundcube:object name="plugin.attendees_freebusy_table" id="attendees-freebusy-table" cellspacing="0" cellpadding="0" border="0" />
|
<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' />">◄</button>
|
||||||
|
<button id="shedule-freebusy-next" title="<roundcube:label name='nextpage' />">►</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-section">
|
<div class="form-section">
|
||||||
<label for="schedule-startdate"><roundcube:label name="calendar.start" /></label>
|
<label for="schedule-startdate"><roundcube:label name="calendar.start" /></label>
|
||||||
<input type="text" name="startdate" size="10" id="schedule-startdate" disabled="true" />
|
<input type="text" name="startdate" size="10" id="schedule-startdate" disabled="true" />
|
||||||
|
|
Loading…
Add table
Reference in a new issue