From a8579090e7b0477286c6b025ac66fdc1f09525d5 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Sun, 19 Jun 2011 17:56:23 -0600 Subject: [PATCH] Make basic calendar functions available in every Roundcube step. Currently used to display alarm notifications --- plugins/calendar/calendar.php | 55 ++++-- plugins/calendar/calendar_base.js | 176 ++++++++++++++++++ .../calendar/{calendar.js => calendar_ui.js} | 136 ++------------ .../drivers/database/database_driver.php | 3 +- plugins/calendar/lib/calendar_ui.php | 9 +- 5 files changed, 238 insertions(+), 141 deletions(-) create mode 100644 plugins/calendar/calendar_base.js rename plugins/calendar/{calendar.js => calendar_ui.js} (90%) diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php index 6a999785..bdb6996f 100644 --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -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)); + } } /** diff --git a/plugins/calendar/calendar_base.js b/plugins/calendar/calendar_base.js new file mode 100644 index 00000000..2696d2c4 --- /dev/null +++ b/plugins/calendar/calendar_base.js @@ -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 | + | Thomas Bruederli | + +-------------------------------------------------------------------------+ +*/ + +// 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, '>').replace(/"/g, '"'); + }; + + // 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() ? ' — ' + $.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']) + ' — ' + + $.fullCalendar.formatDate(event.end, settings['time_format']); + else + fromto = $.fullCalendar.formatDate(event.start, settings['date_format']) + ' ' + $.fullCalendar.formatDate(event.start, settings['time_format']) + ' — ' + + $.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 = $('
').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 = '

' + Q(alarm.title) + '

'; + html += '
' + Q(alarm.location) + '
'; + html += '
' + Q(this.event_date_text(alarm)) + '
'; + + adismiss = $('').html(rcmail.gettext('dismiss','calendar')).click(function(){ + me.dismiss_link = $(this); + me.dismiss_alarm(me.dismiss_link.data('id'), 0); + }); + asnooze = $('').html(rcmail.gettext('snooze','calendar')).click(function(){ + me.snooze_dropdown($(this)); + }); + actions = $('
').addClass('alarm-actions').append(adismiss.data('id', alarm.id)).append(asnooze.data('id', alarm.id)); + + $('
').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: '' + 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 = $('
').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); }); + } +}); + diff --git a/plugins/calendar/calendar.js b/plugins/calendar/calendar_ui.js similarity index 90% rename from plugins/calendar/calendar.js rename to plugins/calendar/calendar_ui.js index 881be661..e937c346 100644 --- a/plugins/calendar/calendar.js +++ b/plugins/calendar/calendar_ui.js @@ -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, '>').replace(/"/g, '"'); - }; + + 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 = $('
').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 = '

' + Q(alarm.title) + '

'; - html += '
' + Q(alarm.location) + '
'; - html += '
' + Q(event_date_text(alarm)) + '
'; - - adismiss = $('').html(rcmail.gettext('dismiss','calendar')).click(function(){ - me.dismiss_link = $(this); - me.dismiss_alarm(me.dismiss_link.data('id'), 0); - }); - asnooze = $('').html(rcmail.gettext('snooze','calendar')).click(function(){ - me.snooze_dropdown($(this)); - }); - actions = $('
').addClass('alarm-actions').append(adismiss.data('id', alarm.id)).append(asnooze.data('id', alarm.id)); - - $('
').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: '' + 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()); diff --git a/plugins/calendar/drivers/database/database_driver.php b/plugins/calendar/drivers/database/database_driver.php index 52b6cf2d..9b2ae3a3 100644 --- a/plugins/calendar/drivers/database/database_driver.php +++ b/plugins/calendar/drivers/database/database_driver.php @@ -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); diff --git a/plugins/calendar/lib/calendar_ui.php b/plugins/calendar/lib/calendar_ui.php index 9bf831ac..8d7e01f4 100644 --- a/plugins/calendar/lib/calendar_ui.php +++ b/plugins/calendar/lib/calendar_ui.php @@ -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'); } /**