From 78077bcb0af8233797861f4e90b3a1b354426fba Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Sun, 29 Jul 2012 13:36:16 +0200 Subject: [PATCH] Handle start date/time; fix task list sorting --- .../tasklist/drivers/database/SQL/mysql.sql | 2 + .../database/tasklist_database_driver.php | 4 +- .../drivers/kolab/tasklist_kolab_driver.php | 14 ++- plugins/tasklist/drivers/tasklist_driver.php | 4 +- plugins/tasklist/localization/de_CH.inc | 6 +- plugins/tasklist/localization/en_US.inc | 6 +- .../skins/larry/templates/mainview.html | 17 +++- plugins/tasklist/tasklist.js | 89 ++++++++++++++----- plugins/tasklist/tasklist.php | 24 +++++ 9 files changed, 134 insertions(+), 32 deletions(-) diff --git a/plugins/tasklist/drivers/database/SQL/mysql.sql b/plugins/tasklist/drivers/database/SQL/mysql.sql index 18f130da..6906ab66 100644 --- a/plugins/tasklist/drivers/database/SQL/mysql.sql +++ b/plugins/tasklist/drivers/database/SQL/mysql.sql @@ -32,6 +32,8 @@ CREATE TABLE `tasks` ( `tags` text, `date` varchar(10) DEFAULT NULL, `time` varchar(5) DEFAULT NULL, + `startdate` varchar(10) DEFAULT NULL, + `starttime` varchar(5) DEFAULT NULL, `flagged` tinyint(4) NOT NULL DEFAULT '0', `complete` float NOT NULL DEFAULT '0', `alarms` varchar(255) NOT NULL, diff --git a/plugins/tasklist/drivers/database/tasklist_database_driver.php b/plugins/tasklist/drivers/database/tasklist_database_driver.php index b9aeba09..acc8fa09 100644 --- a/plugins/tasklist/drivers/database/tasklist_database_driver.php +++ b/plugins/tasklist/drivers/database/tasklist_database_driver.php @@ -343,7 +343,7 @@ class tasklist_database_driver extends tasklist_driver { $rec['id'] = $rec['task_id']; $rec['list'] = $rec['tasklist_id']; - $rec['changed'] = strtotime($rec['changed']); + $rec['changed'] = new DateTime($rec['changed']); $rec['tags'] = array_filter(explode(',', $rec['tags'])); if (!$rec['parent_id']) @@ -409,7 +409,7 @@ class tasklist_database_driver extends tasklist_driver if (isset($prop[$col])) $sql_set[] = $this->rc->db->quote_identifier($col) . '=' . $this->rc->db->quote($prop[$col]); } - foreach (array('parent_id', 'date', 'time') as $col) { + foreach (array('parent_id', 'date', 'time', 'startdate', 'starttime') as $col) { if (isset($prop[$col])) $sql_set[] = $this->rc->db->quote_identifier($col) . '=' . (empty($prop[$col]) ? 'NULL' : $this->rc->db->quote($prop[$col])); } diff --git a/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php b/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php index 66e4912b..7322e1f3 100644 --- a/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php +++ b/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php @@ -337,8 +337,13 @@ class tasklist_kolab_driver extends tasklist_driver $task['date'] = $record['due']->format('Y-m-d'); $task['time'] = $record['due']->format('h:i'); } + // convert from DateTime to internal date format + if (is_a($record['start'], 'DateTime')) { + $task['startdate'] = $record['start']->format('Y-m-d'); + $task['starttime'] = $record['start']->format('h:i'); + } if (is_a($record['dtstamp'], 'DateTime')) { - $task['changed'] = $record['dtstamp']->format('U'); + $task['changed'] = $record['dtstamp']; } return $task; @@ -360,6 +365,13 @@ class tasklist_kolab_driver extends tasklist_driver unset($object['date']); } + if (!empty($task['startdate'])) { + $object['start'] = new DateTime($task['startdate'].' '.$task['starttime'], $this->plugin->timezone); + if (empty($task['starttime'])) + $object['start']->_dateonly = true; + unset($object['startdate']); + } + $object['complete'] = $task['complete'] * 100; if ($task['complete'] == 1.0) $object['status'] = 'COMPLETED'; diff --git a/plugins/tasklist/drivers/tasklist_driver.php b/plugins/tasklist/drivers/tasklist_driver.php index 6b93dc15..c86eca8d 100644 --- a/plugins/tasklist/drivers/tasklist_driver.php +++ b/plugins/tasklist/drivers/tasklist_driver.php @@ -30,12 +30,14 @@ * 'parent_id' => 'ID of parent task', // null if top-level task * 'uid' => 'Unique identifier of this task', * 'list' => 'Task list identifier to add the task to or where the task is stored', - * 'changed' => , // Last modification date of record + * 'changed' => , // Last modification date/time of the record * 'title' => 'Event title/summary', * 'description' => 'Event description', * 'tags' => array(), // List of tags for this task * 'date' => 'Due date', // as string of format YYYY-MM-DD or null if no date is set * 'time' => 'Due time', // as string of format hh::ii or null if no due time is set + * 'startdate' => 'Start date' // Delay start of the task until that date + * 'starttime' => 'Start time' // ...and time * 'categories' => 'Task category', * 'flagged' => 'Boolean value whether this record is flagged', * 'complete' => 'Float value representing the completeness state (range 0..1)', diff --git a/plugins/tasklist/localization/de_CH.inc b/plugins/tasklist/localization/de_CH.inc index eaf075a8..a0b7a8ea 100644 --- a/plugins/tasklist/localization/de_CH.inc +++ b/plugins/tasklist/localization/de_CH.inc @@ -2,8 +2,8 @@ $labels = array(); $labels['navtitle'] = 'Aufgaben'; -$labels['lists'] = 'Ressourcen'; -$labels['list'] = 'Ressource'; +$labels['lists'] = 'Aufgabenlisten'; +$labels['list'] = 'Liste'; $labels['tags'] = 'Tags'; $labels['newtask'] = 'Neue Aufgabe'; @@ -15,6 +15,7 @@ $labels['delete'] = 'Löschen'; $labels['title'] = 'Titel'; $labels['description'] = 'Beschreibung'; $labels['datetime'] = 'Datum/Zeit'; +$labels['start'] = 'Beginn'; $labels['all'] = 'Alle'; $labels['flagged'] = 'Markiert'; @@ -51,3 +52,4 @@ $labels['next'] = 'nächsten'; $labels['savingdata'] = 'Daten werden gespeichert...'; $labels['errorsaving'] = 'Fehler beim Speichern.'; $labels['notasksfound'] = 'Für die aktuellen Kriterien wurden keine Aufgaben gefunden.'; +$labels['invalidstartduedates'] = 'Beginn der Aufgabe darf nicht grösser als das Enddatum sein.'; diff --git a/plugins/tasklist/localization/en_US.inc b/plugins/tasklist/localization/en_US.inc index b7d9d16f..9c1aeef6 100644 --- a/plugins/tasklist/localization/en_US.inc +++ b/plugins/tasklist/localization/en_US.inc @@ -2,8 +2,8 @@ $labels = array(); $labels['navtitle'] = 'Tasks'; -$labels['lists'] = 'Resources'; -$labels['list'] = 'Resource'; +$labels['lists'] = 'Tasklists'; +$labels['list'] = 'Tasklist'; $labels['tags'] = 'Tags'; $labels['newtask'] = 'New Task'; @@ -15,6 +15,7 @@ $labels['delete'] = 'Delete'; $labels['title'] = 'Title'; $labels['description'] = 'Description'; $labels['datetime'] = 'Date/Time'; +$labels['start'] = 'Start'; $labels['all'] = 'All'; $labels['flagged'] = 'Flagged'; @@ -51,3 +52,4 @@ $labels['next'] = 'next'; $labels['savingdata'] = 'Saving data...'; $labels['errorsaving'] = 'Failed to save data.'; $labels['notasksfound'] = 'No tasks found for the given criteria'; +$labels['invalidstartduedates'] = 'Start date must not be greater than due date.'; diff --git a/plugins/tasklist/skins/larry/templates/mainview.html b/plugins/tasklist/skins/larry/templates/mainview.html index 603e34fd..e98db775 100644 --- a/plugins/tasklist/skins/larry/templates/mainview.html +++ b/plugins/tasklist/skins/larry/templates/mainview.html @@ -107,6 +107,11 @@ +
+ + + +
@@ -137,16 +142,22 @@   - + +
+
+ +   + +
-  % +  %
- +
diff --git a/plugins/tasklist/tasklist.js b/plugins/tasklist/tasklist.js index 77a37f40..c1522d00 100644 --- a/plugins/tasklist/tasklist.js +++ b/plugins/tasklist/tasklist.js @@ -54,6 +54,7 @@ function rcube_tasklist(settings) var saving_lock; var ui_loading; var taskcounts = {}; + var listindex = []; var listdata = {}; var tags = []; var draghelper; @@ -354,12 +355,14 @@ function rcube_tasklist(settings) function data_ready(response) { listdata = {}; + listindex = []; loadstate.lists = response.lists; loadstate.filter = response.filter; loadstate.search = response.search; for (var i=0; i < response.data.length; i++) { listdata[response.data[i].id] = response.data[i]; + listindex.push(response.data[i].id); } render_tasklist(); @@ -373,12 +376,13 @@ function rcube_tasklist(settings) function render_tasklist() { // clear display - var rec, + var id, rec, count = 0, msgbox = $('#listmessagebox').hide(), list = $(rcmail.gui_objects.resultlist).html(''); - for (var id in listdata) { + for (var i=0; i < listindex.length; i++) { + id = listindex[i]; rec = listdata[id]; if (match_filter(rec)) { render_task(rec); @@ -436,9 +440,18 @@ function rcube_tasklist(settings) */ function update_taskitem(rec) { - var id = rec.id; + var id = rec.id, + oldid = rec.tempid || id; + oldindex = listindex.indexOf(oldid); + + if (oldindex >= 0) + listindex[oldindex] = id; + else + listindex.push(id); + listdata[id] = rec; - render_task(rec, rec.tempid || id); + + render_task(rec, oldid); append_tags(rec.tags || []); } @@ -525,7 +538,7 @@ function rcube_tasklist(settings) */ function resort_task(rec, li, animated) { - var dir = 0, next_li, next_id, next_rec; + var dir = 0, index, slice, next_li, next_id, next_rec; // animated moving var insert_animated = function(li, before, after) { @@ -542,6 +555,13 @@ function rcube_tasklist(settings) }); } + // remove from list index + var oldindex = listindex.indexOf(rec.id); + if (oldindex >= 0) { + slice = listindex.slice(0,oldindex); + listindex = slice.concat(listindex.slice(oldindex+1)); + } + // find the right place to insert the task item li.siblings().each(function(i, elem){ next_li = $(elem); @@ -558,17 +578,26 @@ function rcube_tasklist(settings) } else if (next_rec && next_li && task_cmp(rec, next_rec) < 0) { if (animated) insert_animated(li, next_li); - else li.insertBefore(next_li) + else li.insertBefore(next_li); next_li = null; return false; } }); + index = listindex.indexOf(next_id); + if (next_li) { if (animated) insert_animated(li, null, next_li); else li.insertAfter(next_li); + index++; + } + + // insert into list index + if (next_id && index >= 0) { + slice = listindex.slice(0,index); + slice.push(rec.id); + listindex = slice.concat(listindex.slice(index)); } - return; } /** @@ -617,15 +646,19 @@ function rcube_tasklist(settings) { var drag_id = draggable.data('id'), parent_id = $(this).data('id'), - rec = listdata[parent_id]; + drag_rec = listdata[drag_id], + drop_rec = listdata[parent_id]; - if (parent_id == listdata[drag_id].parent_id) + if (drop_rec && drop_rec.list != drag_rec.list) return false; - while (rec && rec.parent_id) { - if (rec.parent_id == drag_id) + if (parent_id == drag_rec.parent_id) + return false; + + while (drop_rec && drop_rec.parent_id) { + if (drop_rec.parent_id == drag_id) return false; - rec = listdata[rec.parent_id]; + drop_rec = listdata[drop_rec.parent_id]; } return true; @@ -672,6 +705,8 @@ function rcube_tasklist(settings) $('#task-description').html(text2html(rec.description || '', 300, 6))[(rec.description ? 'show' : 'hide')](); $('#task-date')[(rec.date ? 'show' : 'hide')]().children('.task-text').html(Q(rec.date || rcmail.gettext('nodate','tasklist'))); $('#task-time').html(Q(rec.time || '')); + $('#task-start')[(rec.startdate ? 'show' : 'hide')]().children('.task-text').html(Q(rec.startdate || '')); + $('#task-starttime').html(Q(rec.starttime || '')); $('#task-completeness .task-text').html(((rec.complete || 0) * 100) + '%'); $('#task-list .task-text').html(Q(me.tasklists[rec.list] ? me.tasklists[rec.list].name : '')); @@ -732,6 +767,8 @@ function rcube_tasklist(settings) var description = $('#edit-description').val(rec.description || ''); var recdate = $('#edit-date').val(rec.date || '').datepicker(datepicker_settings); var rectime = $('#edit-time').val(rec.time || ''); + var recstartdate = $('#edit-startdate').val(rec.startdate || '').datepicker(datepicker_settings); + var recstarttime = $('#edit-starttime').val(rec.starttime || ''); var complete = $('#edit-completeness').val((rec.complete || 0) * 100); completeness_slider.slider('value', complete.val()); var tasklist = $('#edit-tasklist').val(rec.list || 0); // .prop('disabled', rec.parent_id ? true : false); @@ -755,22 +792,31 @@ function rcube_tasklist(settings) texts: { removeLinkTitle: rcmail.gettext('removetag', 'tasklist') } }); - $('#edit-nodate').unbind('click').click(function(){ - recdate.val(''); - rectime.val(''); + $('a.edit-nodate').unbind('click').click(function(){ + var sel = $(this).attr('rel'); + if (sel) $(sel).val(''); return false; }) // define dialog buttons var buttons = {}; buttons[rcmail.gettext('save', 'tasklist')] = function() { - me.selected_task.title = title.val(); - me.selected_task.description = description.val(); - me.selected_task.date = recdate.val(); - me.selected_task.time = rectime.val(); - me.selected_task.list = tasklist.val(); + // copy form field contents into task object to save + $.each({ title:title, description:description, date:recdate, time:rectime, startdate:recstartdate, starttime:recstarttime, list:tasklist }, function(key,input){ + me.selected_task[key] = input.val(); + }); me.selected_task.tags = []; + // do some basic input validation + if (me.selected_task.startdate && me.selected_task.date) { + var startdate = $.datepicker.parseDate(datepicker_settings.dateFormat, me.selected_task.startdate, datepicker_settings); + var duedate = $.datepicker.parseDate(datepicker_settings.dateFormat, me.selected_task.date, datepicker_settings); + if (startdate > duedate) { + alert(rcmail.gettext('invalidstartduedates', 'tasklist')); + return false; + } + } + $('input[name="tags[]"]', rcmail.gui_objects.edittagline).each(function(i,elem){ if (elem.value) me.selected_task.tags.push(elem.value); @@ -812,9 +858,10 @@ function rcube_tasklist(settings) $dialog.dialog('destroy').remove(); }, buttons: buttons, + minHeight: 340, minWidth: 500, width: 580 - }).append(editform.show()); // adding form content AFTERWARDS massively speeds up opening on IE6 + }).append(editform.show()); // adding form content AFTERWARDS massively speeds up opening on IE title.select(); } diff --git a/plugins/tasklist/tasklist.php b/plugins/tasklist/tasklist.php index 0ba42602..d65a3f06 100644 --- a/plugins/tasklist/tasklist.php +++ b/plugins/tasklist/tasklist.php @@ -261,6 +261,18 @@ class tasklist extends rcube_plugin } } + if (!empty($rec['startdate'])) { + try { + $date = new DateTime($rec['startdate'] . ' ' . $rec['starttime'], $this->timezone); + $rec['startdate'] = $date->format('Y-m-d'); + if (!empty($rec['starttime'])) + $rec['starttime'] = $date->format('H:i'); + } + catch (Exception $e) { + $rec['startdate'] = $rec['starttime'] = null; + } + } + return $rec; } @@ -400,6 +412,7 @@ class tasklist extends rcube_plugin $rec['mask'] = $this->filter_mask($rec); $rec['flagged'] = intval($rec['flagged']); $rec['complete'] = floatval($rec['complete']); + $rec['changed'] = is_object($rec['changed']) ? $rec['changed']->format('U') : null; if ($rec['date']) { try { @@ -417,6 +430,17 @@ class tasklist extends rcube_plugin $rec['_hasdate'] = 0; } + if ($rec['startdate']) { + try { + $date = new DateTime($rec['startdate'] . ' ' . $rec['starttime'], $this->timezone); + $rec['startdatetime'] = intval($date->format('U')); + $rec['startdate'] = $date->format($this->rc->config->get('date_format', 'Y-m-d')); + } + catch (Exception $e) { + $rec['startdate'] = $rec['startdatetime'] = null; + } + } + if (!isset($rec['_depth'])) { $rec['_depth'] = 0; $parent_id = $this->task_tree[$rec['id']];