From ff9f87a037687a62dbd0070a6d2582fb9ec14d3b Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Tue, 19 Aug 2014 10:37:31 +0200 Subject: [PATCH] Add button to expand members of a group attendee (#3376); prefix edit-attendees-table styles to avoid unintended style overrides --- plugins/calendar/calendar.php | 2 +- plugins/calendar/calendar_ui.js | 30 +++++++--- plugins/calendar/skins/larry/calendar.css | 41 ++++++++----- plugins/libcalendaring/libcalendaring.js | 59 +++++++++++++++++++ plugins/libcalendaring/libcalendaring.php | 56 ++++++++++++++++++ plugins/libcalendaring/localization/en_US.inc | 6 ++ plugins/tasklist/skins/larry/tasklist.css | 9 +++ plugins/tasklist/tasklist.js | 45 ++++++++------ plugins/tasklist/tasklist.php | 2 +- 9 files changed, 207 insertions(+), 43 deletions(-) diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php index 840a2ed2..4975f7c2 100644 --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -294,7 +294,7 @@ class calendar extends rcube_plugin $this->ui->init_templates(); $this->rc->output->add_label('lowest','low','normal','high','highest','delete','cancel','uploading','noemailwarning','close'); - $this->rc->output->add_label('libcalendaring.itipaccepted','libcalendaring.itiptentative','libcalendaring.itipdeclined','libcalendaring.itipdelegated'); + $this->rc->output->add_label('libcalendaring.itipaccepted','libcalendaring.itiptentative','libcalendaring.itipdeclined','libcalendaring.itipdelegated','libcalendaring.expandattendeegroup','libcalendaring.expandattendeegroupnodata'); // initialize attendees autocompletion rcube_autocomplete_init(); diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js index c41bd962..1b3547da 100644 --- a/plugins/calendar/calendar_ui.js +++ b/plugins/calendar/calendar_ui.js @@ -914,11 +914,11 @@ function rcube_calendar_ui(settings) }, buttons: buttons, minWidth: 500, - width: 580 + width: 600 }).append(editform.show()); // adding form content AFTERWARDS massively speeds up opening on IE6 // set dialog size according to form content - me.dialog_resize($dialog.get(0), editform.height() + (bw.ie ? 20 : 0), 530); + me.dialog_resize($dialog.get(0), editform.height() + (bw.ie ? 20 : 0), 550); title.select(); @@ -1877,7 +1877,7 @@ function rcube_calendar_ui(settings) }; // add the given attendee to the list - var add_attendee = function(data, readonly) + var add_attendee = function(data, readonly, before) { if (!me.selected_event) return false; @@ -1931,6 +1931,12 @@ function rcube_calendar_ui(settings) else if (data['delegated-from']) tooltip = rcmail.gettext('delegatedfrom', 'calendar') + data['delegated-from']; + // add expand button for groups + if (data.cutype == 'GROUP') { + dispname += ' ' + + rcmail.gettext('expandattendeegroup','libcalendaring') + ''; + } + var html = '' + select + '' + '' + dispname + '' + '' + @@ -1941,11 +1947,16 @@ function rcube_calendar_ui(settings) var table = rcmail.env.calendar_resources && data.cutype == 'RESOURCE' ? resources_list : attendees_list; var tr = $('') .addClass(String(data.role).toLowerCase()) - .html(html) - .appendTo(table); + .html(html); + + if (before) + tr.insertBefore(before) + else + tr.appendTo(table); tr.find('a.deletelink').click({ id:(data.email || data.name) }, function(e) { remove_attendee(this, e.data.id); return false; }); tr.find('a.mailtolink').click(event_attendee_click); + tr.find('a.expandlink').click(data, function(e) { me.expand_attendee_group(e, add_attendee, remove_attendee); }); tr.find('input.edit-attendee-reply').click(function() { var enabled = $('#edit-attendees-invite:checked').length || $('input.edit-attendee-reply:checked').length; $('p.attendees-commentbox')[enabled ? 'show' : 'hide'](); @@ -3755,12 +3766,15 @@ function rcube_calendar_ui(settings) }; } rcmail.init_address_input_events($('#edit-attendee-name'), ac_props); - rcmail.addEventListener('autocomplete_insert', function(e){ + rcmail.addEventListener('autocomplete_insert', function(e) { + var success = false; if (e.field.name == 'participant') { - $('#edit-attendee-add').click(); + success = add_attendees(e.insert, { role:'REQ-PARTICIPANT', status:'NEEDS-ACTION', cutype:(e.data && e.data.type == 'group' ? 'GROUP' : 'INDIVIDUAL') }); } else if (e.field.name == 'resource' && e.data && e.data.email) { - add_attendee($.extend(e.data, { role:'REQ-PARTICIPANT', status:'NEEDS-ACTION', cutype:'RESOURCE' })); + success = add_attendee($.extend(e.data, { role:'REQ-PARTICIPANT', status:'NEEDS-ACTION', cutype:'RESOURCE' })); + } + if (e.field && success) { e.field.value = ''; } }); diff --git a/plugins/calendar/skins/larry/calendar.css b/plugins/calendar/skins/larry/calendar.css index 85a7aa5a..0700455a 100644 --- a/plugins/calendar/skins/larry/calendar.css +++ b/plugins/calendar/skins/larry/calendar.css @@ -955,31 +955,31 @@ td.topalign { width: 95%; } -.edit-attendees-table { +#eventedit .edit-attendees-table { width: 100%; margin-top: 0.5em; } -.edit-attendees-table th.role, -.edit-attendees-table td.role { +#eventedit .edit-attendees-table th.role, +#eventedit .edit-attendees-table td.role { width: 9em; } -.edit-attendees-table th.availability, -.edit-attendees-table td.availability, -.edit-attendees-table th.confirmstate, -.edit-attendees-table td.confirmstate { +#eventedit .edit-attendees-table th.availability, +#eventedit .edit-attendees-table td.availability, +#eventedit .edit-attendees-table th.confirmstate, +#eventedit .edit-attendees-table td.confirmstate { width: 4em; } -.edit-attendees-table th.options, -.edit-attendees-table td.options { +#eventedit .edit-attendees-table th.options, +#eventedit .edit-attendees-table td.options { width: 16px; padding: 2px 4px; } -.edit-attendees-table th.sendmail, -.edit-attendees-table td.sendmail { +#eventedit .edit-attendees-table th.sendmail, +#eventedit .edit-attendees-table td.sendmail { width: 44px; padding: 2px; } @@ -998,23 +998,24 @@ td.topalign { background: url(images/sendinvitation.png) 1px 0 no-repeat; } -.edit-attendees-table tbody tr:last-child td { +#eventedit .edit-attendees-table tbody tr:last-child td { border-bottom: 0; } -.edit-attendees-table th.name, -.edit-attendees-table td.name { +#eventedit .edit-attendees-table th.name, +#eventedit .edit-attendees-table td.name { width: auto; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + position: relative; } -.edit-attendees-table td.name select { +#eventedit .edit-attendees-table td.name select { width: 100%; } -.edit-attendees-table a.deletelink { +#eventedit .edit-attendees-table a.deletelink { display: inline-block; width: 17px; height: 17px; @@ -1023,6 +1024,14 @@ td.topalign { text-indent: 1000px; } +#eventedit .edit-attendees-table a.expandlink { + position: absolute; + top: 4px; + right: 6px; + width: 16px; + height: 16px; +} + #edit-attendees-form, #edit-resources-form { position: relative; diff --git a/plugins/libcalendaring/libcalendaring.js b/plugins/libcalendaring/libcalendaring.js index 6f20ab7c..39b34ae7 100644 --- a/plugins/libcalendaring/libcalendaring.js +++ b/plugins/libcalendaring/libcalendaring.js @@ -33,6 +33,7 @@ function rcube_libcalendaring(settings) this.alarm_dialog = null; this.snooze_popup = null; this.dismiss_link = null; + this.group2expand = {}; // abort if env isn't set if (!settings || !settings.date_format) @@ -729,6 +730,64 @@ function rcube_libcalendaring(settings) $.each(listitems, function(idx, item) { mylist.append(item); }); }; + + /***** Attendee form handling *****/ + + // expand the given contact group into individual event/task attendees + this.expand_attendee_group = function(e, add, remove) + { + var id = (e.data ? e.data.email : null) || $(e.target).attr('data-email'), + role_select = $(e.target).closest('tr').find('select.edit-attendee-role option:selected'); + + this.group2expand[id] = { link: e.target, data: $.extend({}, e.data || {}), adder: add, remover: remove } + + // copy group role from the according form element + if (role_select.length) { + this.group2expand[id].data.role = role_select.val(); + } + + // register callback handler + if (!this._expand_attendee_listener) { + this._expand_attendee_listener = this.expand_attendee_callback; + rcmail.addEventListener('plugin.expand_attendee_callback', function(result) { + me._expand_attendee_listener(result); + }); + } + + rcmail.http_post('libcal/plugin.expand_attendee_group', { id: id, data: e.data || {} }, rcmail.set_busy(true, 'loading')); + }; + + // callback from server to expand an attendee group + this.expand_attendee_callback = function(result) + { + var attendee, id = result.id, + data = this.group2expand[id], + row = $(data.link).closest('tr'); + + // replace group entry with all members returned by the server + if (data && data.adder && result.members && result.members.length) { + for (var i=0; i < result.members.length; i++) { + attendee = result.members[i]; + attendee.role = data.data.role; + attendee.cutype = 'INDIVIDUAL'; + attendee.status = 'NEEDS-ACTION'; + data.adder(attendee, null, row); + } + + if (data.remover) { + data.remover(data.link, id) + } + else { + row.remove(); + } + + delete this.group2expand[id]; + } + else { + rcmail.display_message(result.error || rcmail.gettext('expandattendeegroupnodata','libcalendaring'), 'error'); + } + }; + } ////// static methods diff --git a/plugins/libcalendaring/libcalendaring.php b/plugins/libcalendaring/libcalendaring.php index 6ca548ef..52a81347 100644 --- a/plugins/libcalendaring/libcalendaring.php +++ b/plugins/libcalendaring/libcalendaring.php @@ -106,6 +106,7 @@ class libcalendaring extends rcube_plugin // add hook to display alarms $this->add_hook('refresh', array($this, 'refresh')); $this->register_action('plugin.alarms', array($this, 'alarms_action')); + $this->register_action('plugin.expand_attendee_group', array($this, 'expand_attendee_group')); } // proceed initialization in startup hook @@ -1359,6 +1360,61 @@ class libcalendaring extends rcube_plugin } + /********* Attendee handling functions *********/ + + /** + * Handler for attendee group expansion requests + */ + public function expand_attendee_group() + { + $id = rcube_utils::get_input_value('id', rcube_utils::INPUT_POST); + $data = rcube_utils::get_input_value('data', rcube_utils::INPUT_POST, true); + $result = array('id' => $id, 'members' => array()); + $maxnum = 500; + + // iterate over all autocomplete address books (we don't know the source of the group) + foreach ((array)$this->rc->config->get('autocomplete_addressbooks', 'sql') as $abook_id) { + if (($abook = $this->rc->get_address_book($abook_id)) && $abook->groups) { + foreach ($abook->list_groups($data['name'], 1) as $group) { + // this is the matching group to expand + if (in_array($data['email'], (array)$group['email'])) { + $abook->set_pagesize($maxnum); + $abook->set_group($group['ID']); + + // get all members + $res = $abook->list_records($this->rc->config->get('contactlist_fields')); + + // handle errors (e.g. sizelimit, timelimit) + if ($abook->get_error()) { + $result['error'] = $this->rc->gettext('expandattendeegrouperror', 'libcalendaring'); + $res = false; + } + // check for maximum number of members (we don't wanna bloat the UI too much) + else if ($res->count > $maxnum) { + $result['error'] = $this->rc->gettext('expandattendeegroupsizelimit', 'libcalendaring'); + $res = false; + } + + while ($res && ($member = $res->iterate())) { + $emails = (array)$abook->get_col_values('email', $member, true); + if (!empty($emails) && ($email = array_shift($emails))) { + $result['members'][] = array( + 'email' => $email, + 'name' => rcube_addressbook::compose_list_name($member), + ); + } + } + + break 2; + } + } + } + } + + $this->rc->output->command('plugin.expand_attendee_callback', $result); + } + + /********* Static utility functions *********/ /** diff --git a/plugins/libcalendaring/localization/en_US.inc b/plugins/libcalendaring/localization/en_US.inc index 5ad34ed5..82fb7a8d 100644 --- a/plugins/libcalendaring/localization/en_US.inc +++ b/plugins/libcalendaring/localization/en_US.inc @@ -140,3 +140,9 @@ $labels['declinedeleteconfirm'] = 'Do you also want to delete this declined obje $labels['savingdata'] = 'Saving data...'; +// attendees labels +$labels['expandattendeegroup'] = 'Substitute with group members'; +$labels['expandattendeegroupnodata'] = 'Unable to substitute this group. No members found.'; +$labels['expandattendeegrouperror'] = 'Unable to substitute this group. It might contain too many members.'; +$labels['expandattendeegroupsizelimit'] = 'This group contains too many members for substituting.'; + diff --git a/plugins/tasklist/skins/larry/tasklist.css b/plugins/tasklist/skins/larry/tasklist.css index 0b2ed3dd..0ffaf966 100644 --- a/plugins/tasklist/skins/larry/tasklist.css +++ b/plugins/tasklist/skins/larry/tasklist.css @@ -918,6 +918,7 @@ a.morelink:hover { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + position: relative; } #taskedit .edit-attendees-table td.name select { @@ -933,6 +934,14 @@ a.morelink:hover { text-indent: 1000px; } +#taskedit .edit-attendees-table a.expandlink { + position: absolute; + top: 4px; + right: 6px; + width: 16px; + height: 16px; +} + #edit-attendees-form { position: relative; margin-top: 15px; diff --git a/plugins/tasklist/tasklist.js b/plugins/tasklist/tasklist.js index 5e0dc8eb..4c07937c 100644 --- a/plugins/tasklist/tasklist.js +++ b/plugins/tasklist/tasklist.js @@ -615,22 +615,22 @@ function rcube_tasklist_ui(settings) }; } rcmail.init_address_input_events($('#edit-attendee-name'), ac_props); - rcmail.addEventListener('autocomplete_insert', function(e){ - if (e.field.name == 'participant') { - $('#edit-attendee-add').click(); - } -// else if (e.field.name == 'resource' && e.data && e.data.email) { -// add_attendee($.extend(e.data, { role:'REQ-PARTICIPANT', status:'NEEDS-ACTION', cutype:'RESOURCE' })); -// e.field.value = ''; -// } + rcmail.addEventListener('autocomplete_insert', function(e) { + var success = false; + if (e.field.name == 'participant') { + success = add_attendees(e.insert, { role:'REQ-PARTICIPANT', status:'NEEDS-ACTION', cutype:(e.data && e.data.type == 'group' ? 'GROUP' : 'INDIVIDUAL') }); + } + if (e.field && success) { + e.field.value = ''; + } }); - $('#edit-attendee-add').click(function(){ - var input = $('#edit-attendee-name'); - rcmail.ksearch_blur(); - if (add_attendees(input.val(), { role:'REQ-PARTICIPANT', status:'NEEDS-ACTION', cutype:'INDIVIDUAL' })) { - input.val(''); - } + $('#edit-attendee-add').click(function() { + var input = $('#edit-attendee-name'); + rcmail.ksearch_blur(); + if (add_attendees(input.val(), { role:'REQ-PARTICIPANT', status:'NEEDS-ACTION', cutype:'INDIVIDUAL' })) { + input.val(''); + } }); // handle change of "send invitations" checkbox @@ -1595,7 +1595,7 @@ function rcube_tasklist_ui(settings) }; // add the given attendee to the list - var add_attendee = function(data, readonly) + var add_attendee = function(data, readonly, before) { if (!me.selected_task) return false; @@ -1624,6 +1624,12 @@ function rcube_tasklist_ui(settings) else if (data['delegated-from']) tooltip = rcmail.gettext('delegatedfrom', 'tasklist') + data['delegated-from']; + // add expand button for groups + if (data.cutype == 'GROUP') { + dispname += ' ' + + rcmail.gettext('expandattendeegroup','libcalendaring') + ''; + } + var html = '' + dispname + '' + '' + Q(data.status || '') + '' + (data.cutype != 'RESOURCE' ? '' + (readonly || !invbox ? '' : invbox) + '' : '') + @@ -1631,11 +1637,16 @@ function rcube_tasklist_ui(settings) var tr = $('') .addClass(String(data.role).toLowerCase()) - .html(html) - .appendTo(attendees_list); + .html(html); + + if (before) + tr.insertBefore(before) + else + tr.appendTo(attendees_list); tr.find('a.deletelink').click({ id:(data.email || data.name) }, function(e) { remove_attendee(this, e.data.id); return false; }); tr.find('a.mailtolink').click(task_attendee_click); + tr.find('a.expandlink').click(data, function(e) { me.expand_attendee_group(e, add_attendee, remove_attendee); }); tr.find('input.edit-attendee-reply').click(function() { var enabled = $('#edit-attendees-invite:checked').length || $('input.edit-attendee-reply:checked').length; $('p.attendees-commentbox')[enabled ? 'show' : 'hide'](); diff --git a/plugins/tasklist/tasklist.php b/plugins/tasklist/tasklist.php index 7894b102..a5731289 100644 --- a/plugins/tasklist/tasklist.php +++ b/plugins/tasklist/tasklist.php @@ -1147,7 +1147,7 @@ class tasklist extends rcube_plugin $this->rc->output->set_env('autocomplete_threads', (int)$this->rc->config->get('autocomplete_threads', 0)); $this->rc->output->set_env('autocomplete_max', (int)$this->rc->config->get('autocomplete_max', 15)); $this->rc->output->set_env('autocomplete_min_length', $this->rc->config->get('autocomplete_min_length')); - $this->rc->output->add_label('autocompletechars', 'autocompletemore'); + $this->rc->output->add_label('autocompletechars', 'autocompletemore', 'libcalendaring.expandattendeegroup', 'libcalendaring.expandattendeegroupnodata'); $this->rc->output->set_pagetitle($this->gettext('navtitle')); $this->rc->output->send('tasklist.mainview');