Implement task editing.
authorJan Schneider <jan@horde.org>
Tue, 17 Nov 2009 16:35:33 +0000 (17:35 +0100)
committerJan Schneider <jan@horde.org>
Tue, 17 Nov 2009 16:36:02 +0000 (17:36 +0100)
kronolith/ajax.php
kronolith/js/kronolith.js
kronolith/templates/index/task.inc
nag/lib/Api.php

index 9288db9..953096c 100644 (file)
@@ -370,6 +370,65 @@ try {
         $result->task = $task->toJson(true, $prefs->getValue('twentyFour') ? 'H:i' : 'h:i A');
         break;
 
+    case 'SaveTask':
+        if (!$registry->hasMethod('tasks/updateTask')) {
+            break;
+        }
+        if (is_null($id = Horde_Util::getFormData('task_id')) ||
+            is_null($list = Horde_Util::getFormData('old_tasklist'))) {
+            break;
+        }
+        $task = Horde_Util::getFormData('task');
+
+        $due = trim($task['due_date'] . ' ' . $task['due_time']);
+        if (!empty($due)) {
+            // strptime() is locale dependent, i.e. %p is not always matching
+            // AM/PM. Set the locale to C to workaround this, but grab the
+            // locale's D_FMT before that.
+            $date_format = Horde_Nls::getLangInfo(D_FMT);
+            $old_locale = setlocale(LC_TIME, 0);
+            setlocale(LC_TIME, 'C');
+            $format = $date_format . ' '
+                . ($prefs->getValue('twentyFour') ? '%H:%M' : '%I:%M %p');
+
+            // Try exact format match first.
+            if ($date_arr = strptime($due, $format)) {
+                $task['due'] = new Horde_Date(
+                    array('year'  => $date_arr['tm_year'] + 1900,
+                          'month' => $date_arr['tm_mon'] + 1,
+                          'mday'  => $date_arr['tm_mday'],
+                          'hour'  => $date_arr['tm_hour'],
+                          'min'   => $date_arr['tm_min'],
+                          'sec'   => $date_arr['tm_sec']));
+            } else {
+                $task['due'] = new Horde_Date($due);
+            }
+            setlocale(LC_TIME, $old_locale);
+        }
+
+        if ($task['alarm']['on']) {
+            $task['alarm'] = $task['alarm']['value'] * $task['alarm']['unit'];
+        } else {
+            $task['alarm'] = 0;
+        }
+
+        $result = $registry->tasks->updateTask($list, $id, $task);
+        if (is_a($result, 'PEAR_Error')) {
+            $notification->push($result, 'horde.error');
+            break;
+        }
+        $task = $registry->tasks->getTask($task['tasklist'], $id);
+        if (is_a($task, 'PEAR_Error')) {
+            $notification->push($task, 'horde.error');
+            break;
+        }
+        $result = new stdClass;
+        $result->type = $task->completed ? 'complete' : 'incomplete';
+        $result->list = $task->tasklist;
+        $result->sig = Horde_Util::getFormData('sig');
+        $result->tasks = array($id => $task->toJson(false, $prefs->getValue('twentyFour') ? 'H:i' : 'h:i A'));
+        break;
+
     case 'DeleteTask':
         if (!$registry->hasMethod('tasks/deleteTask')) {
             break;
index bbb7c5e..f8c601e 100644 (file)
@@ -1516,13 +1516,13 @@ KronolithCore = {
                     Object.isUndefined(this.tcache.get(type).get(list))) {
                     loading = true;
                     this.startLoading('tasks:' + type + list, tasktype);
-                    this._storeTasksCache($H(), type, list);
+                    this._storeTasksCache($H(), type, list, true);
                     this.doAction('ListTasks',
                                   { 'type': type,
                                     'sig' : tasktype,
                                     'list': list },
                                   function(r) {
-                                      this._loadTasksCallback(r, tasktype);
+                                      this._loadTasksCallback(r, tasktype, true);
                                   }.bind(this));
                 }
             }, this);
@@ -1538,9 +1538,15 @@ KronolithCore = {
     /**
      * Callback method for inserting tasks in the current view.
      *
-     * @param object r  The ajax response object.
+     * @param object r             The ajax response object.
+     * @param string tasktype      The (UI) task type for that the response was
+     *                             targeted.
+     * @param boolean createCache  Whether to create a cache list entry for the
+     *                             response, if none exists yet. Useful for
+     *                             adding individual tasks to the cache without
+     *                             assuming to have all tasks of the list.
      */
-    _loadTasksCallback: function(r, tasktype)
+    _loadTasksCallback: function(r, tasktype, createCache)
     {
         // Hide spinner.
         this.loading--;
@@ -1548,7 +1554,7 @@ KronolithCore = {
             $('kronolithLoading').hide();
         }
 
-        this._storeTasksCache(r.response.tasks || {}, r.response.type, r.response.list);
+        this._storeTasksCache(r.response.tasks || {}, r.response.type, r.response.list, createCache);
 
         // Check if this is the still the result of the most current request.
         if (this.view != 'tasks' ||
@@ -1671,12 +1677,6 @@ KronolithCore = {
                 }
             });
 
-            // TODO: Assuming that tasks of the same tasklist are already in
-            // order
-            if (rowTasklist == newTask.value.l) {
-                continue;
-            }
-
             if (Object.isUndefined(rowTask)) {
                 // TODO: Throw error
                 return;
@@ -1783,7 +1783,7 @@ KronolithCore = {
         } else {
             $('kronolithTaskId').clear();
             $('kronolithTaskOldList').clear();
-            $('kronolithTaskList').setValue(Kronolith.conf.default_tasklist);
+            //$('kronolithTaskList').setValue(Kronolith.conf.default_tasklist);
             $('kronolithTaskDelete').hide();
             $('kronolithTaskDueDate').setValue(d.toString(Kronolith.conf.date_format));
             $('kronolithTaskDueTime').setValue(d.toString(Kronolith.conf.time_format));
@@ -1847,7 +1847,7 @@ KronolithCore = {
         $('kronolithTaskList').update();
         $H(Kronolith.conf.calendars.tasklists).each(function(cal) {
             if (cal.value.edit) {
-                $('kronolithTaskList').insert(new Element('OPTION', { 'value': 'tasks|' + cal.key })
+                $('kronolithTaskList').insert(new Element('OPTION', { 'value': cal.key.substring(6) })
                              .setStyle({ 'backgroundColor': cal.value.bg, 'color': cal.value.fg })
                              .update(cal.value.name.escapeHTML()));
             }
@@ -1870,6 +1870,27 @@ KronolithCore = {
     },
 
     /**
+     * Submits the task edit form to create or update a task.
+     */
+    saveTask: function()
+    {
+        var tasklist = $F('kronolithTaskList'),
+            taskid = $F('kronolithTaskId');
+        this.startLoading('tasks:' + ($F('kronolithTaskCompleted') ? 'complete' : 'incomplete') + tasklist, this.tasktype);
+        this.doAction('SaveTask',
+                      $H($('kronolithTaskForm').serialize({ 'hash': true }))
+                          .merge({ 'sig': this.tasktype }),
+                      function(r) {
+                          if (r.response.tasks && taskid) {
+                              this._removeTask(taskid, tasklist);
+                              this._loadTasksCallback(r, this.tasktype, false);
+                          }
+                          this._closeRedBox();
+                          window.history.back();
+                      }.bind(this));
+    },
+
+    /**
      * Parses a date attribute string into a Date object.
      *
      * For other strings use Date.parse().
@@ -1980,13 +2001,15 @@ KronolithCore = {
     /**
      * Stores a set of tasks in the cache.
      *
-     * @param Hash tasks        The tasks to be stored.
-     * @param string tasktypes  The task type that's being stored.
-     * @param string tasklist   The task list to which the tasks belong.
+     * @param Hash tasks           The tasks to be stored.
+     * @param string tasktypes     The task type that's being stored.
+     * @param string tasklist      The task list to which the tasks belong.
+     * @param boolean createCache  Whether to create a cache list entry for the
+     *                             response, if none exists yet.
      */
-    _storeTasksCache: function(tasks, tasktypes, tasklist)
+    _storeTasksCache: function(tasks, tasktypes, tasklist, createCache)
     {
-        var taskHashes = {};
+        var taskHashes = {}, cacheExists = {};
 
         if (tasktypes == 'all' || tasktypes == 'future') {
             tasktypes = [ 'complete', 'incomplete' ];
@@ -1995,23 +2018,39 @@ KronolithCore = {
         }
 
         tasktypes.each(function(tasktype) {
+            cacheExists[tasktype] = false;
             if (!this.tcache.get(tasktype)) {
-                this.tcache.set(tasktype, $H());
+                if (createCache) {
+                    this.tcache.set(tasktype, $H());
+                } else {
+                    return;
+                }
             }
             if (!this.tcache.get(tasktype).get(tasklist)) {
-                this.tcache.get(tasktype).set(tasklist, $H());
+                if (createCache) {
+                    this.tcache.get(tasktype).set(tasklist, $H());
+                    cacheExists[tasktype] = true;
+                } else {
+                    return;
+                }
+            } else {
+                cacheExists[tasktype] = true;
             }
             taskHashes[tasktype] = this.tcache.get(tasktype).get(tasklist);
         }, this);
 
         $H(tasks).each(function(task) {
+            var tasktype = task.value.cp ? 'complete' : 'incomplete';
+            if (!cacheExists[tasktype]) {
+                return;
+            }
             if (!Object.isUndefined(task.value.s)) {
                 task.value.start = Date.parse(task.value.s);
             }
             if (!Object.isUndefined(task.value.du)) {
                 task.value.due = Date.parse(task.value.du);
             }
-            taskHashes[task.value.cp ? 'complete' : 'incomplete'].set(task.key, task.value);
+            taskHashes[tasktype].set(task.key, task.value);
         });
     },
 
@@ -2134,6 +2173,11 @@ KronolithCore = {
                     e.stop();
                     break;
 
+                case 'kronolithTaskForm':
+                    this.saveTask();
+                    e.stop();
+                    break;
+
                 case 'kronolithSearchForm':
                     this.go('search:' + $F('kronolithSearchContext') + ':' + $F('kronolithSearchTerm'))
                     e.stop();
@@ -2259,6 +2303,11 @@ KronolithCore = {
                 e.stop();
                 return;
 
+            case 'kronolithTaskSave':
+                this.saveTask();
+                e.stop();
+                return;
+
             case 'kronolithEventDelete':
                 var cal = $F('kronolithEventCalendar'),
                     eventid = $F('kronolithEventId');
@@ -2635,6 +2684,9 @@ KronolithCore = {
         }
     },
 
+    /**
+     * Submits the event edit form to create or update an event.
+     */
     saveEvent: function()
     {
         var cal = $F('kronolithEventCalendar'),
index 1f95610..ecaf9dc 100644 (file)
@@ -5,18 +5,18 @@
 
 <div>
   <label for="kronolithTaskTitle"><?php echo _("Name") ?>:</label><br />
-  <input type="text" name="name" id="kronolithTaskTitle" class="kronolithLongField" />
+  <input type="text" name="task[name]" id="kronolithTaskTitle" class="kronolithLongField" />
 </div>
 
 <div>
-  <label><input type="checkbox" name="completed" id="kronolithTaskCompleted" value="" /> <?php echo _("completed") ?></label>
+  <label><input type="checkbox" name="task[completed]" id="kronolithTaskCompleted" value="1" /> <?php echo _("completed") ?></label>
 </div>
 
 <table cellspacing="" cellpadding="0" border="0"><tbody><tr>
   <td>
     <div>
       <label for="kronolithTaskPriority"><?php echo _("Priority") ?>:</label><br />
-      <select name="priority" id="kronolithTaskPriority">
+      <select name="task[priority]" id="kronolithTaskPriority">
         <?php foreach (array(1 => '1 ' . _("(highest)"), 2 => 2, 3 => 3, 4 => 4, 5 => '5 ' . _("(lowest)")) as $prio => $label): ?>
         <option value="<?php echo $prio ?>"><?php echo $label ?></option>
         <?php endforeach; ?>
   <td>
     <div>
       <label for="kronolithTaskList"><?php echo _("Task List") ?>:</label><br />
-      <select name="tasklist_id" id="kronolithTaskList">
+      <select name="task[tasklist]" id="kronolithTaskList">
       </select>
     </div>
   </td>
   <td>
     <div>
       <label><?php echo _("Due date") ?>:</label><br />
-      <input type="text" name="due_date" id="kronolithTaskDueDate" size="10" class="kronolithDatePicker" />
+      <input type="text" name="task[due_date]" id="kronolithTaskDueDate" size="10" class="kronolithDatePicker" />
       <?php echo _("at") ?>
-      <input type="text" name="due_time" id="kronolithTaskDueTime" size="8" />
+      <input type="text" name="task[due_time]" id="kronolithTaskDueTime" size="8" />
     </div>
   </td>
 </tr></tbody></table>
 <br class="clear" />
 
 <div id="kronolithTaskTabDescription" class="kronolithTabsOption">
-  <textarea name="desc" id="kronolithTaskDescription" rows="5" cols="40" class="kronolithLongField"></textarea>
+  <textarea name="task[desc]" id="kronolithTaskDescription" rows="5" cols="40" class="kronolithLongField"></textarea>
 </div>
 
 <div id="kronolithTaskTabReminder" class="kronolithTabsOption" style="display:none">
-   <label><input type="radio" name="nag_alarm[on]" id="kronolithTaskAlarmOff" value="0" checked="checked" /> <?php echo _("don't set") ?></label>
+   <label><input type="radio" name="task[alarm][on]" id="kronolithTaskAlarmOff" value="0" checked="checked" /> <?php echo _("don't set") ?></label>
    <?php echo _("or") ?>
-   <label><input type="radio" name="nag_alarm[on]" id="kronolithTaskAlarmOn" value="1" /> <?php echo _("set") ?></label>
+   <label><input type="radio" name="task[alarm][on]" id="kronolithTaskAlarmOn" value="1" /> <?php echo _("set") ?></label>
+   <input type="text" name="task[alarm][value]" id="kronolithTaskAlarmValue" size="3" value="15" class="kronolithEventValue" />
    <label>
-     <input type="text" name="nag_alarm[value]" id="kronolithTaskAlarmValue" size="3" value="15" class="kronolithEventValue" />
-     <select name="nag_alarm[unit]" id="kronolithTaskAlarmUnit">
+     <select name="task[alarm][unit]" id="kronolithTaskAlarmUnit">
        <option value="1"><?php echo _("minutes") ?></option>
        <option value="60"><?php echo _("hours") ?></option>
        <option value="1440"><?php echo _("days") ?></option>
@@ -70,7 +70,7 @@
 </div>
 
 <div id="kronolithTaskTabUrl" class="kronolithTabsOption" style="display:none">
-  <input type="text" name="url" id="taskUrl" class="kronolithLongField" value="http://" />
+  <input type="text" name="task[url]" id="taskUrl" class="kronolithLongField" value="http://" />
 </div>
 
 <div id="kronolithTaskActions">
index 48aad71..cdbfa53 100644 (file)
@@ -1148,6 +1148,48 @@ class Nag_Api extends Horde_Registry_Api
     }
 
     /**
+     * Changes a task identified by tasklist and ID.
+     *
+     * @param string $tasklist  A tasklist id.
+     * @param string $id        A task id.
+     * @param array $task       A hash with overwriting task information.
+     */
+    public function updateTask($tasklist, $id, $task)
+    {
+        require_once dirname(__FILE__) . '/base.php';
+
+        if (!Horde_Auth::isAdmin() &&
+            !array_key_exists($tasklist,
+                              Nag::listTasklists(false, PERMS_EDIT))) {
+            return PEAR::raiseError(_("Permission Denied"));
+        }
+
+        $storage = Nag_Driver::singleton($tasklist);
+        $existing = $storage->get($id);
+        if (is_a($existing, 'PEAR_Error')) {
+            return $existing;
+        }
+
+        return $storage->modify(
+            $id,
+            isset($task['name']) ? $task['name'] : $existing->name,
+            isset($task['desc']) ? $task['desc'] : $existing->desc,
+            isset($task['start']) ? $task['start'] : $existing->start,
+            isset($task['due']) ? $task['due'] : $existing->due,
+            isset($task['priority']) ? $task['priority'] : $existing->priority,
+            isset($task['estimate']) ? $task['estimate'] : $existing->estimate,
+            isset($task['completed']) ? (int)$task['completed'] : $existing->completed,
+            isset($task['category']) ? $task['category'] : $existing->category,
+            isset($task['alarm']) ? $task['alarm'] : $existing->alarm,
+            isset($task['parent_id']) ? $task['parent_id'] : $existing->parent_id,
+            isset($task['private']) ? $task['private'] : $existing->private,
+            isset($task['owner']) ? $task['owner'] : $existing->owner,
+            isset($task['assignee']) ? $task['assignee'] : $existing->assignee,
+            $task['completed'] && !$existing->completed ? date() : $existing->completed_date,
+            isset($task['tasklist']) ? $task['tasklist'] : $existing->tasklist);
+    }
+
+    /**
      * Lists active tasks as cost objects.
      *
      * @todo Implement $criteria parameter.