Show free/busy times for event attendess (work-in-progress)
This commit is contained in:
parent
0ec7458eb1
commit
028f3069d6
8 changed files with 314 additions and 44 deletions
|
@ -23,8 +23,14 @@
|
|||
+-------------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
|
||||
class calendar extends rcube_plugin
|
||||
{
|
||||
const FREEBUSY_UNKNOWN = 0;
|
||||
const FREEBUSY_FREE = 1;
|
||||
const FREEBUSY_BUSY = 2;
|
||||
const FREEBUSY_OOF = 4;
|
||||
|
||||
public $task = '?(?!login|logout).*';
|
||||
public $rc;
|
||||
public $driver;
|
||||
|
@ -181,6 +187,7 @@ class calendar extends rcube_plugin
|
|||
$this->register_handler('plugin.attachments_list', array($this->ui, 'attachments_list'));
|
||||
$this->register_handler('plugin.attendees_list', array($this->ui, 'attendees_list'));
|
||||
$this->register_handler('plugin.attendees_form', array($this->ui, 'attendees_form'));
|
||||
$this->register_handler('plugin.attendees_freebusy_table', array($this->ui, 'attendees_freebusy_table'));
|
||||
$this->register_handler('plugin.edit_recurring_warning', array($this->ui, 'recurring_event_warning'));
|
||||
$this->register_handler('plugin.searchform', array($this->rc->output, 'search_form')); // use generic method from rcube_template
|
||||
|
||||
|
@ -1207,16 +1214,36 @@ 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
|
||||
|
||||
if (!$start) $start = time();
|
||||
if (!$end) $end = $start + 86400 * 30;
|
||||
if (!$end) $end = $start + 86400 * 7;
|
||||
|
||||
$fblist = $this->driver->get_freebusy_list($email, $start, $end);
|
||||
$result = array();
|
||||
$slots = array();
|
||||
|
||||
// TODO: 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++) {
|
||||
$status = self::FREEBUSY_UNKNOWN;
|
||||
$t_end = $t + $interval*60;
|
||||
|
||||
// determine attendee's status
|
||||
if (is_array($fblist)) {
|
||||
$status = self::FREEBUSY_FREE;
|
||||
foreach ($fblist as $slot) {
|
||||
list($from, $to) = $slot;
|
||||
if ($from <= $t_end && $to > $t) {
|
||||
$status = self::FREEBUSY_BUSY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$slots[$s] = $status;
|
||||
$t = $t_end;
|
||||
}
|
||||
|
||||
echo json_encode($result);
|
||||
echo json_encode(array('email' => $email, 'slots' => $slots, 'interval' => $interval));
|
||||
exit;
|
||||
}
|
||||
|
||||
|
|
|
@ -261,15 +261,17 @@ function rcube_calendar_ui(settings)
|
|||
|
||||
// list event attendees
|
||||
if (calendar.attendees && event.attendees) {
|
||||
var data, dispname, html = '';
|
||||
var data, dispname, organizer = false, html = '';
|
||||
for (var j=0; j < event.attendees.length; j++) {
|
||||
data = event.attendees[j];
|
||||
dispname = Q(data.name || data.email);
|
||||
if (data.email)
|
||||
dispname = '<a href="mailto:' + data.email + '" title="' + Q(data.email) + '" class="mailtolink">' + dispname + '</a>';
|
||||
html += '<span class="attendee ' + String(data.status).toLowerCase() + '">' + dispname + '</span> ';
|
||||
html += '<span class="attendee ' + String(data.role == 'ORGANIZER' ? 'organizer' : data.status).toLowerCase() + '">' + dispname + '</span> ';
|
||||
if (data.role == 'ORGANIZER')
|
||||
organizer = true;
|
||||
}
|
||||
if (html) {
|
||||
if (html && event.attendees.length > 1 || !organizer) {
|
||||
$('#event-attendees').show()
|
||||
.children('.event-text')
|
||||
.html(html)
|
||||
|
@ -317,26 +319,6 @@ function rcube_calendar_ui(settings)
|
|||
};
|
||||
|
||||
|
||||
|
||||
//jquery-ui dialog for printing calendars - stub
|
||||
var calendars_print_dialog = function(action, event)
|
||||
{
|
||||
var $dialog = $("#printcalendar");
|
||||
$dialog.dialog({
|
||||
modal: true,
|
||||
resizable: true,
|
||||
closeOnEscape: false,
|
||||
title: rcmail.gettext('Print', 'calendar'),
|
||||
close: function() {
|
||||
$dialog.dialog("destroy").hide();
|
||||
},
|
||||
//buttons: buttons,
|
||||
minWidth: 500,
|
||||
width: 580
|
||||
}).show();
|
||||
|
||||
}
|
||||
|
||||
// bring up the event dialog (jquery-ui popup)
|
||||
var event_edit_dialog = function(action, event)
|
||||
{
|
||||
|
@ -621,6 +603,123 @@ function rcube_calendar_ui(settings)
|
|||
title.select();
|
||||
};
|
||||
|
||||
var event_freebusy_dialog = function()
|
||||
{
|
||||
var $dialog = $('#eventfreebusy').dialog('close');
|
||||
var event = me.selected_event;
|
||||
|
||||
// set form elements
|
||||
var duration = Math.round((event.end.getTime() - event.start.getTime()) / 1000);
|
||||
var startdate = $('#schedule-startdate').val($.fullCalendar.formatDate(event.start, settings['date_format'])).data('duration', duration);
|
||||
var starttime = $('#schedule-starttime').val($.fullCalendar.formatDate(event.start, settings['time_format'])).show();
|
||||
var enddate = $('#schedule-enddate').val($.fullCalendar.formatDate(event.end, settings['date_format']));
|
||||
var endtime = $('#schedule-endtime').val($.fullCalendar.formatDate(event.end, settings['time_format'])).show();
|
||||
var allday = $('#edit-allday').get(0);
|
||||
|
||||
if (event.allDay) {
|
||||
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; // 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"> </td>';
|
||||
|
||||
t += interval * 60000;
|
||||
}
|
||||
dates_row += '</tr>';
|
||||
times_row += '</tr>';
|
||||
|
||||
// render list of attendees
|
||||
var domid, dispname, data, list_html = '', times_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 = {};
|
||||
|
||||
buttons[rcmail.gettext('cancel', 'calendar')] = function() {
|
||||
$dialog.dialog("close");
|
||||
};
|
||||
|
||||
$dialog.dialog({
|
||||
modal: true,
|
||||
resizable: true,
|
||||
closeOnEscape: true,
|
||||
title: rcmail.gettext('scheduletime', 'calendar'),
|
||||
close: function() {
|
||||
$dialog.dialog("destroy").hide();
|
||||
},
|
||||
buttons: buttons,
|
||||
minWidth: 640,
|
||||
width: 850
|
||||
|
||||
}).show();
|
||||
|
||||
|
||||
// load free-busy information for every attendee
|
||||
var domid, email
|
||||
for (var i=0; i < event_attendees.length; i++) {
|
||||
if ((email = event_attendees[i].email)) {
|
||||
domid = String(email).replace(rcmail.identifier_expr, '');
|
||||
$('#rcmli' + domid).addClass('loading');
|
||||
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
url: rcmail.url('freebusy-times'),
|
||||
data: { email:email, start:date2unixtime(range_start), end:date2unixtime(range_end), _remote: 1 },
|
||||
success: function(data){
|
||||
var status_classes = ['unknown','free','busy','tentative','out-of-office'];
|
||||
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';
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 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 event properties and attendees availability if event times have changed
|
||||
var event_times_changed = function()
|
||||
{
|
||||
|
@ -1421,6 +1520,10 @@ function rcube_calendar_ui(settings)
|
|||
if (add_attendees(input.val()))
|
||||
input.val('');
|
||||
});
|
||||
|
||||
$('#edit-attendee-schedule').click(function(){
|
||||
event_freebusy_dialog();
|
||||
});
|
||||
|
||||
// add proprietary css styles if not IE
|
||||
if (!bw.ie)
|
||||
|
|
|
@ -572,9 +572,32 @@ class calendar_ui
|
|||
function attendees_form($attrib = array())
|
||||
{
|
||||
$input = new html_inputfield(array('name' => 'participant', 'id' => 'edit-attendee-name', 'size' => 30));
|
||||
$checkbox = new html_checkbox(array('name' => 'notify', 'id' => 'edit-attendees-notify', 'value' => 1, 'disabled' => true)); // disabled for now
|
||||
|
||||
return html::div($attrib, $input->show() . " " .
|
||||
html::tag('input', array('type' => 'button', 'class' => 'button', 'id' => 'edit-attendee-add', 'value' => $this->calendar->gettext('addattendee'))));
|
||||
return html::div($attrib,
|
||||
html::div(null, $input->show() . " " .
|
||||
html::tag('input', array('type' => 'button', 'class' => 'button', 'id' => 'edit-attendee-add', 'value' => $this->calendar->gettext('addattendee'))) . " " .
|
||||
html::tag('input', array('type' => 'button', 'class' => 'button', 'id' => 'edit-attendee-schedule', 'value' => $this->calendar->gettext('scheduletime').'...'))) .
|
||||
html::p('attendees-notifybox', html::label(null, $checkbox->show(1) . $this->calendar->gettext('sendnotifications')))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function attendees_freebusy_table($attrib = array())
|
||||
{
|
||||
$table = new html_table(array('cols' => 2, 'border' => 0, 'cellspacing' => 0));
|
||||
$table->add('attendees',
|
||||
html::tag('h3', 'boxtitle', $this->calendar->gettext('tabattendees')) .
|
||||
html::div('timesheader', ' ') .
|
||||
html::div(array('id' => 'schedule-attendees-list'), '')
|
||||
);
|
||||
$table->add('times',
|
||||
html::div('scroll', html::tag('table', array('id' => 'schedule-freebusy-times', 'border' => 0, 'cellspacing' => 0), html::tag('thead') . html::tag('tbody')))
|
||||
);
|
||||
|
||||
return $table->show($attrib);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -92,6 +92,12 @@ $labels['roleorganizer'] = 'Organizer';
|
|||
$labels['rolerequired'] = 'Required';
|
||||
$labels['roleoptional'] = 'Optional';
|
||||
$labels['roleresource'] = 'Resource';
|
||||
$labels['availfree'] = 'Free';
|
||||
$labels['availbusy'] = 'Busy';
|
||||
$labels['availunknown'] = 'Unknown';
|
||||
$labels['availoutofoffice'] = 'Out of Office';
|
||||
$labels['scheduletime'] = 'Available times';
|
||||
$labels['sendnotifications'] = 'Send notifications';
|
||||
|
||||
// event dialog tabs
|
||||
$labels['tabsummary'] = 'Summary';
|
||||
|
|
|
@ -235,9 +235,7 @@ pre {
|
|||
right: 4px;
|
||||
}
|
||||
|
||||
#eventshow,
|
||||
#eventedit,
|
||||
#calendarform {
|
||||
div.uidialog {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
@ -363,6 +361,10 @@ a.miniColors-trigger {
|
|||
background-position: right -60px;
|
||||
}
|
||||
|
||||
#event-attendees span.organizer {
|
||||
background-position: right -80px;
|
||||
}
|
||||
|
||||
/* jQuery UI overrides */
|
||||
|
||||
#eventshow h1 {
|
||||
|
@ -503,12 +505,12 @@ td.topalign {
|
|||
display: table;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #ddd;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
#edit-attendees-table td {
|
||||
padding: 3px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
#edit-attendees-table td.role {
|
||||
|
@ -538,16 +540,23 @@ td.topalign {
|
|||
}
|
||||
|
||||
#edit-attendees-form {
|
||||
position: relative;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
#edit-attendees-form #edit-attendee-schedule {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#edit-attendees-table select.edit-attendee-role {
|
||||
border: 0;
|
||||
padding: 2px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
#edit-attendees-table img.availabilityicon {
|
||||
.availability img.availabilityicon {
|
||||
margin: 1px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
|
@ -555,26 +564,43 @@ td.topalign {
|
|||
-moz-border-radius: 4px;
|
||||
}
|
||||
|
||||
#edit-attendees-table img.availabilityicon.loading {
|
||||
.availability img.availabilityicon.loading {
|
||||
background: url('images/loading-small.gif') top left no-repeat;
|
||||
}
|
||||
|
||||
#edit-attendees-table img.availabilityicon.unknown {
|
||||
background: #ccc;
|
||||
#schedule-freebusy-times td.unknown,
|
||||
.availability img.availabilityicon.unknown {
|
||||
background: #ddd;
|
||||
}
|
||||
|
||||
#edit-attendees-table img.availabilityicon.free {
|
||||
#schedule-freebusy-times td.free,
|
||||
.availability img.availabilityicon.free {
|
||||
background: #0c0;
|
||||
}
|
||||
|
||||
#edit-attendees-table img.availabilityicon.busy {
|
||||
#schedule-freebusy-times td.busy,
|
||||
.availability img.availabilityicon.busy {
|
||||
background: #c00;
|
||||
}
|
||||
|
||||
#edit-attendees-table img.availabilityicon.out-of-office {
|
||||
#schedule-freebusy-times td.out-of-office,
|
||||
.availability img.availabilityicon.out-of-office {
|
||||
background: #f0b400;
|
||||
}
|
||||
|
||||
#edit-attendees-legend {
|
||||
margin-top: 3em;
|
||||
}
|
||||
|
||||
#edit-attendees-legend .legend {
|
||||
margin-right: 2em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#edit-attendees-legend img.availabilityicon {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#edit-attendees-table tbody td.confirmstate {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
|
@ -603,6 +629,67 @@ td.topalign {
|
|||
background-position: 5px -60px;
|
||||
}
|
||||
|
||||
#attendees-freebusy-table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
#attendees-freebusy-table td.attendees {
|
||||
width: 16em;
|
||||
border: 1px solid #ccc;
|
||||
vertical-align: top;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#attendees-freebusy-table td.times {
|
||||
width: auto;
|
||||
vertical-align: top;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
#attendees-freebusy-table div.scroll {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#attendees-freebusy-table h3.boxtitle {
|
||||
margin: 0;
|
||||
height: auto !important;
|
||||
border-color: #ccc;
|
||||
}
|
||||
|
||||
#attendees-freebusy-table div.timesheader {
|
||||
padding: 0.3em;
|
||||
}
|
||||
|
||||
#schedule-attendees-list div.attendee {
|
||||
padding: 3px 20px 3px 6px;
|
||||
border-top: 1px solid #ccc;
|
||||
}
|
||||
|
||||
#schedule-attendees-list div.loading {
|
||||
background: url('images/loading-small.gif') top right no-repeat;
|
||||
}
|
||||
|
||||
#schedule-freebusy-times {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
#schedule-freebusy-times td {
|
||||
padding: 3px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
#schedule-freebusy-times tr.dates th {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
#schedule-freebusy-times tr.times td {
|
||||
font-size: 9px;
|
||||
padding: 5px 2px 6px 2px;
|
||||
}
|
||||
|
||||
span.edit-alarm-set {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.6 KiB |
|
@ -36,7 +36,7 @@
|
|||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="eventshow">
|
||||
<div id="eventshow" class="uidialog">
|
||||
<h1 id="event-title">Event Title</h1>
|
||||
<div class="event-section" id="event-location">Location</div>
|
||||
<div class="event-section" id="event-date">From-To</div>
|
||||
|
@ -82,7 +82,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div id="eventedit">
|
||||
<div id="eventedit" class="uidialog">
|
||||
<form id="eventtabs" action="#" method="post">
|
||||
<ul>
|
||||
<li><a href="#event-tab-1"><roundcube:label name="calendar.tabsummary" /></a></li>
|
||||
|
@ -168,6 +168,7 @@
|
|||
<div id="event-tab-3">
|
||||
<roundcube:object name="plugin.attendees_list" id="edit-attendees-table" cellspacing="0" cellpadding="0" border="0" />
|
||||
<roundcube:object name="plugin.attendees_form" id="edit-attendees-form" />
|
||||
<roundcube:include file="/templates/freebusylegend.html" />
|
||||
</div>
|
||||
<!-- attachments list (with upload form) -->
|
||||
<div id="event-tab-4">
|
||||
|
@ -183,7 +184,24 @@
|
|||
<roundcube:object name="plugin.edit_recurring_warning" class="edit-recurring-warning" style="display:none" />
|
||||
</div>
|
||||
|
||||
<div id="calendarform">
|
||||
<div id="eventfreebusy" class="uidialog">
|
||||
<roundcube:object name="plugin.attendees_freebusy_table" id="attendees-freebusy-table" cellspacing="0" cellpadding="0" border="0" />
|
||||
|
||||
<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" />
|
||||
<input type="text" name="starttime" size="6" id="schedule-starttime" disabled="true" />
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<label for="schedule-enddate"><roundcube:label name="calendar.end" /></label>
|
||||
<input type="text" name="enddate" size="10" id="schedule-enddate" disabled="true" />
|
||||
<input type="text" name="endtime" size="6" id="schedule-endtime" disabled="true" />
|
||||
</div>
|
||||
|
||||
<roundcube:include file="/templates/freebusylegend.html" />
|
||||
</div>
|
||||
|
||||
<div id="calendarform" class="uidialog">
|
||||
<roundcube:label name="loading" />
|
||||
</div>
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<div id="edit-attendees-legend" class="availability">
|
||||
<span class="legend"><img class="availabilityicon free" src="./program/blank.gif" /> <roundcube:label name="calendar.availfree" /></span>
|
||||
<span class="legend"><img class="availabilityicon busy" src="./program/blank.gif" /> <roundcube:label name="calendar.availbusy" /></span>
|
||||
<span class="legend"><img class="availabilityicon out-of-office" src="./program/blank.gif" /> <roundcube:label name="calendar.availoutofoffice" /></span>
|
||||
<span class="legend"><img class="availabilityicon unknown" src="./program/blank.gif" /> <roundcube:label name="calendar.availunknown" /></span>
|
||||
</div>
|
Loading…
Add table
Reference in a new issue