Make basic calendar functions available in every Roundcube step. Currently used to display alarm notifications

This commit is contained in:
Thomas Bruederli 2011-06-19 17:56:23 -06:00
parent 0a3738a0f9
commit a8579090e7
5 changed files with 238 additions and 141 deletions

View file

@ -54,17 +54,23 @@ class calendar extends rcube_plugin
$this->load_config();
// load localizations
$this->add_texts('localization/', !$this->rc->action || $this->rc->task != 'calendar');
// load Calendar user interface which includes jquery-ui
$this->require_plugin('jqueryui');
$this->add_texts('localization/', $this->rc->task == 'calendar' && !$this->rc->action);
require($this->home . '/lib/calendar_ui.php');
$this->ui = new calendar_ui($this);
$this->ui->init();
$skin = $this->rc->config->get('skin');
$this->include_stylesheet('skins/' . $skin . '/calendar.css');
// load Calendar user interface which includes jquery-ui
if (!$this->rc->output->ajax_call && !$this->rc->output->env['framed']) {
$this->require_plugin('jqueryui');
$this->ui->init();
// settings are required in every GUI step
$this->rc->output->set_env('calendar_settings', $this->load_settings());
$skin = $this->rc->config->get('skin');
$this->include_stylesheet('skins/' . $skin . '/calendar.css');
}
if ($this->rc->task == 'calendar') {
$this->load_driver();
@ -81,15 +87,6 @@ class calendar extends rcube_plugin
$this->register_action('search_events', array($this, 'search_events'));
$this->register_action('export_events', array($this, 'export_events'));
$this->register_action('randomdata', array($this, 'generate_randomdata'));
$this->add_hook('keep_alive', array($this, 'keep_alive'));
// set user's timezone
if ($this->rc->config->get('timezone') === 'auto')
$this->timezone = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z');
else
$this->timezone = ($this->rc->config->get('timezone') + intval($this->rc->config->get('dst_active')));
$this->gmt_offset = $this->timezone * 3600;
}
else if ($this->rc->task == 'settings') {
$this->load_driver();
@ -99,10 +96,27 @@ class calendar extends rcube_plugin
$this->add_hook('preferences_list', array($this, 'preferences_list'));
$this->add_hook('preferences_save', array($this, 'preferences_save'));
}
// add hook to display alarms
$this->add_hook('keep_alive', array($this, 'keep_alive'));
// set user's timezone
if ($this->rc->config->get('timezone') === 'auto')
$this->timezone = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z');
else
$this->timezone = ($this->rc->config->get('timezone') + intval($this->rc->config->get('dst_active')));
$this->gmt_offset = $this->timezone * 3600;
}
/**
* Helper method to load the backend driver according to local config
*/
private function load_driver()
{
if (is_object($this->driver))
return;
$driver_name = $this->rc->config->get('calendar_driver', 'database');
$driver_class = $driver_name . '_driver';
@ -480,9 +494,16 @@ class calendar extends rcube_plugin
*/
function keep_alive($attr)
{
$this->load_driver();
$alarms = $this->driver->pending_alarms(time());
if ($alarms)
if ($alarms) {
// make sure texts and env vars are available on client
if ($this->rc->task != 'calendar') {
$this->add_texts('localization/', true);
$this->rc->output->set_env('snooze_select', $this->ui->snooze_select());
}
$this->rc->output->command('plugin.display_alarms', $this->_alarms_output($alarms));
}
}
/**

View file

@ -0,0 +1,176 @@
/*
+-------------------------------------------------------------------------+
| Base Javascript class for the Calendar Plugin |
| Version 0.3 beta |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License version 2 |
| as published by the Free Software Foundation. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License along |
| with this program; if not, write to the Free Software Foundation, Inc., |
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| |
+-------------------------------------------------------------------------+
| Author: Lazlo Westerhof <hello@lazlo.me> |
| Thomas Bruederli <roundcube@gmail.com> |
+-------------------------------------------------------------------------+
*/
// Basic setup for Roundcube calendar client class
function rcube_calendar(settings)
{
// member vars
this.settings = settings;
this.alarm_ids = [];
this.alarm_dialog = null;
this.snooze_popup = null;
this.dismiss_link = null;
// private vars
var me = this;
// quote html entities
var Q = this.quote_html = function(str)
{
return String(str).replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
};
// create a nice human-readable string for the date/time range
this.event_date_text = function(event)
{
var fromto, duration = event.end.getTime() / 1000 - event.start.getTime() / 1000;
if (event.allDay)
fromto = $.fullCalendar.formatDate(event.start, settings['date_format']) + (duration > 86400 || event.start.getDay() != event.end.getDay() ? ' &mdash; ' + $.fullCalendar.formatDate(event.end, settings['date_format']) : '');
else if (duration < 86400 && event.start.getDay() == event.end.getDay())
fromto = $.fullCalendar.formatDate(event.start, settings['date_format']) + ' ' + $.fullCalendar.formatDate(event.start, settings['time_format']) + ' &mdash; '
+ $.fullCalendar.formatDate(event.end, settings['time_format']);
else
fromto = $.fullCalendar.formatDate(event.start, settings['date_format']) + ' ' + $.fullCalendar.formatDate(event.start, settings['time_format']) + ' &mdash; '
+ $.fullCalendar.formatDate(event.end, settings['date_format']) + ' ' + $.fullCalendar.formatDate(event.end, settings['time_format']);
return fromto;
};
// display a notification for the given pending alarms
this.display_alarms = function(alarms) {
// clear old alert first
if (this.alarm_dialog)
this.alarm_dialog.dialog('destroy');
this.alarm_dialog = $('<div>').attr('id', 'alarm-display');
var actions, adismiss, asnooze, alarm, html, event_ids = [];
for (var actions, html, alarm, i=0; i < alarms.length; i++) {
alarm = alarms[i];
alarm.start = $.fullCalendar.parseISO8601(alarm.start, true);
alarm.end = $.fullCalendar.parseISO8601(alarm.end, true);
event_ids.push(alarm.id);
html = '<h3 class="event-title">' + Q(alarm.title) + '</h3>';
html += '<div class="event-section">' + Q(alarm.location) + '</div>';
html += '<div class="event-section">' + Q(this.event_date_text(alarm)) + '</div>';
adismiss = $('<a href="#" class="alarm-action-dismiss"></a>').html(rcmail.gettext('dismiss','calendar')).click(function(){
me.dismiss_link = $(this);
me.dismiss_alarm(me.dismiss_link.data('id'), 0);
});
asnooze = $('<a href="#" class="alarm-action-snooze"></a>').html(rcmail.gettext('snooze','calendar')).click(function(){
me.snooze_dropdown($(this));
});
actions = $('<div>').addClass('alarm-actions').append(adismiss.data('id', alarm.id)).append(asnooze.data('id', alarm.id));
$('<div>').addClass('alarm-item').html(html).append(actions).appendTo(this.alarm_dialog);
}
var buttons = {};
buttons[rcmail.gettext('dismissall','calendar')] = function() {
// submit dismissed event_ids to server
me.dismiss_alarm(me.alarm_ids.join(','), 0);
$(this).dialog('close');
};
this.alarm_dialog.appendTo(document.body).dialog({
modal: false,
resizable: true,
closeOnEscape: false,
dialogClass: 'alarm',
title: '<span class="ui-icon ui-icon-alert" style="float:left; margin:0 4px 0 0"></span>' + rcmail.gettext('alarmtitle', 'calendar'),
buttons: buttons,
close: function() {
$('#alarm-snooze-dropdown').hide();
$(this).dialog('destroy').remove();
me.alarm_dialog = null;
me.alarm_ids = null;
},
drag: function(event, ui) {
$('#alarm-snooze-dropdown').hide();
}
});
this.alarm_ids = event_ids;
};
// show a drop-down menu with a selection of snooze times
this.snooze_dropdown = function(link)
{
if (!this.snooze_popup) {
this.snooze_popup = $('#alarm-snooze-dropdown');
// create popup if not found
if (!this.snooze_popup.length) {
this.snooze_popup = $('<div>').attr('id', 'alarm-snooze-dropdown').addClass('popupmenu').appendTo(document.body);
this.snooze_popup.html(rcmail.env.snooze_select)
}
$('#alarm-snooze-dropdown a').click(function(e){
var time = String(this.href).replace(/.+#/, '');
me.dismiss_alarm($('#alarm-snooze-dropdown').data('id'), time);
return false;
});
}
// hide visible popup
if (this.snooze_popup.is(':visible') && this.snooze_popup.data('id') == link.data('id')) {
this.snooze_popup.hide();
this.dismiss_link = null;
}
else { // open popup below the clicked link
var pos = link.offset();
pos.top += link.height() + 2;
this.snooze_popup.data('id', link.data('id')).css({ top:Math.floor(pos.top)+'px', left:Math.floor(pos.left)+'px' }).show();
this.dismiss_link = link;
}
};
// dismiss or snooze alarms for the given event
this.dismiss_alarm = function(id, snooze)
{
$('#alarm-snooze-dropdown').hide();
rcmail.http_post('calendar/event', { action:'dismiss', e:{ id:id, snooze:snooze } });
// remove dismissed alarm from list
if (this.dismiss_link) {
this.dismiss_link.closest('div.alarm-item').hide();
var new_ids = jQuery.grep(this.alarm_ids, function(v){ return v != id; });
if (new_ids.length)
this.alarm_ids = new_ids;
else
this.alarm_dialog.dialog('close');
}
this.dismiss_link = null;
};
}
/* calendar plugin initialization (for non-calendar tasks) */
window.rcmail && rcmail.addEventListener('init', function(evt) {
if (rcmail.task != 'calendar') {
var cal = new rcube_calendar(rcmail.env.calendar_settings);
rcmail.addEventListener('plugin.display_alarms', function(alarms){ cal.display_alarms(alarms); });
}
});

View file

@ -22,15 +22,13 @@
+-------------------------------------------------------------------------+
*/
// Roundcube calendar client class
function rcube_calendar(settings)
// Roundcube calendar UI client class
function rcube_calendar_ui(settings)
{
// extend base class
rcube_calendar.call(this, settings);
/*** member vars ***/
this.settings = settings;
this.alarm_ids = [];
this.alarm_dialog = null;
this.snooze_popup = null;
this.dismiss_link = null;
this.selected_event = null;
this.selected_calendar = null;
this.search_request = null;
@ -59,12 +57,9 @@ function rcube_calendar(settings)
/*** private methods ***/
// quote html entities
var Q = function(str)
{
return String(str).replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
};
var Q = this.quote_html;
// php equivalent
var nl2br = function(str)
{
@ -72,7 +67,8 @@ function rcube_calendar(settings)
};
// from time and date strings to a real date object
var parse_datetime = function(time, date) {
var parse_datetime = function(time, date)
{
// we use the utility function from datepicker to parse dates
var date = $.datepicker.parseDate(datepicker_settings.dateFormat, date, datepicker_settings);
var time_arr = time.split(/[:.]/);
@ -82,11 +78,13 @@ function rcube_calendar(settings)
};
// convert the given Date object into a unix timestamp respecting browser's and user's timezone settings
var date2unixtime = function(date) {
var date2unixtime = function(date)
{
return date.getTime()/1000 + gmt_offset * 3600;
};
var fromunixtime = function(ts) {
var fromunixtime = function(ts)
{
ts -= gmt_offset * 3600;
return new Date(ts * 1000);
}
@ -122,7 +120,7 @@ function rcube_calendar(settings)
$('#event-description').show().children('.event-text').html(nl2br(Q(event.description))); // TODO: format HTML with clickable links and stuff
// render from-to in a nice human-readable way
$('#event-date').html(Q(event_date_text(event))).show();
$('#event-date').html(Q(me.event_date_text(event))).show();
if (event.recurrence && event.recurrence_text)
$('#event-repeat').show().children('.event-text').html(Q(event.recurrence_text));
@ -516,108 +514,6 @@ function rcube_calendar(settings)
return false;
};
// display a notification for the given pending alarms
this.display_alarms = function(alarms) {
// clear old alert first
if (this.alarm_dialog)
this.alarm_dialog.dialog('destroy');
this.alarm_dialog = $('<div>').attr('id', 'alarm-display');
var actions, adismiss, asnooze, alarm, html, event_ids = [];
for (var actions, html, alarm, i=0; i < alarms.length; i++) {
alarm = alarms[i];
alarm.start = $.fullCalendar.parseISO8601(alarm.start, true);
alarm.end = $.fullCalendar.parseISO8601(alarm.end, true);
event_ids.push(alarm.id);
html = '<h3 class="event-title">' + Q(alarm.title) + '</h3>';
html += '<div class="event-section">' + Q(alarm.location) + '</div>';
html += '<div class="event-section">' + Q(event_date_text(alarm)) + '</div>';
adismiss = $('<a href="#" class="alarm-action-dismiss"></a>').html(rcmail.gettext('dismiss','calendar')).click(function(){
me.dismiss_link = $(this);
me.dismiss_alarm(me.dismiss_link.data('id'), 0);
});
asnooze = $('<a href="#" class="alarm-action-snooze"></a>').html(rcmail.gettext('snooze','calendar')).click(function(){
me.snooze_dropdown($(this));
});
actions = $('<div>').addClass('alarm-actions').append(adismiss.data('id', alarm.id)).append(asnooze.data('id', alarm.id));
$('<div>').addClass('alarm-item').html(html).append(actions).appendTo(this.alarm_dialog);
}
var buttons = {};
buttons[rcmail.gettext('dismissall','calendar')] = function() {
// submit dismissed event_ids to server
me.dismiss_alarm(me.alarm_ids.join(','), 0);
$(this).dialog('close');
};
this.alarm_dialog.appendTo(document.body).dialog({
modal: false,
resizable: true,
closeOnEscape: false,
dialogClass: 'alarm',
title: '<span class="ui-icon ui-icon-alert" style="float:left; margin:0 4px 0 0"></span>' + rcmail.gettext('alarmtitle', 'calendar'),
buttons: buttons,
close: function() {
$('#alarm-snooze-dropdown').hide();
$(this).dialog('destroy').remove();
me.alarm_dialog = null;
me.alarm_ids = null;
},
drag: function(event, ui) {
$('#alarm-snooze-dropdown').hide();
}
});
this.alarm_ids = event_ids;
};
// show a drop-down menu with a selection of snooze times
this.snooze_dropdown = function(link)
{
if (!this.snooze_popup) {
this.snooze_popup = $('#alarm-snooze-dropdown');
$('#alarm-snooze-dropdown a').click(function(e){
var time = String(this.href).replace(/.+#/, '');
me.dismiss_alarm($('#alarm-snooze-dropdown').data('id'), time);
return false;
});
}
// hide visible popup
if (this.snooze_popup.is(':visible') && this.snooze_popup.data('id') == link.data('id')) {
this.snooze_popup.hide();
this.dismiss_link = null;
}
else { // open popup below the clicked link
var pos = link.offset();
pos.top += link.height() + 2;
this.snooze_popup.data('id', link.data('id')).css({ top:Math.floor(pos.top)+'px', left:Math.floor(pos.left)+'px' }).show();
this.dismiss_link = link;
}
};
// dismiss or snooze alarms for the given event
this.dismiss_alarm = function(id, snooze)
{
$('#alarm-snooze-dropdown').hide();
rcmail.http_post('event', { action:'dismiss', e:{ id:id, snooze:snooze } });
// remove dismissed alarm from list
if (this.dismiss_link) {
this.dismiss_link.closest('div.alarm-item').hide();
var new_ids = jQuery.grep(this.alarm_ids, function(v){ return v != id; });
if (new_ids.length)
this.alarm_ids = new_ids;
else
this.alarm_dialog.dialog('close');
}
this.dismiss_link = null;
};
// opens a jquery UI dialog with event properties (or empty for creating a new calendar)
this.calendar_edit_dialog = function(calendar)
{
@ -1167,7 +1063,7 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
// let's go
var cal = new rcube_calendar(rcmail.env.calendar_settings);
var cal = new rcube_calendar_ui(rcmail.env.calendar_settings);
$(window).resize(function() {
$('#calendar').fullCalendar('option', 'height', $('#main').height());

View file

@ -331,8 +331,9 @@ class database_driver extends calendar_driver
$event['recurrence'] = rtrim($rrule, ';');
$event['free_busy'] = intval($this->free_busy_map[strtolower($event['free_busy'])]);
if (isset($event['allday']))
if (isset($event['allday'])) {
$event['all_day'] = $event['allday'] ? 1 : 0;
}
// compute absolute time to notify the user
$event['notifyat'] = $this->_get_notification($event);

View file

@ -45,6 +45,10 @@ class calendar_ui
'label' => 'calendar.calendar',
'href' => './?_task=calendar',
), 'taskbar');
// load basic client script (which - unfortunately - requires fullcalendar)
$this->calendar->include_script('lib/js/fullcalendar.js');
$this->calendar->include_script('calendar_base.js');
}
/**
@ -62,9 +66,8 @@ class calendar_ui
*/
public function addJS()
{
$this->calendar->include_script('lib/js/fullcalendar.js');
$this->calendar->include_script('lib/js/jquery.miniColors.min.js');
$this->calendar->include_script('calendar.js');
$this->calendar->include_script('calendar_ui.js');
$this->calendar->include_script('lib/js/jquery.miniColors.min.js');
}
/**