From: Jan Schneider Date: Fri, 24 Jul 2009 10:06:02 +0000 (+0200) Subject: [jan] Integrate tasks into Ajax interface (Gonçalo Queirós X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=dc852b8b0b85c8329990980d62a40a024a93265b;p=horde.git [jan] Integrate tasks into Ajax interface (Gonçalo Queirós ). --- diff --git a/kronolith/ajax.php b/kronolith/ajax.php index e489423f7..3f273de67 100644 --- a/kronolith/ajax.php +++ b/kronolith/ajax.php @@ -124,6 +124,28 @@ try { } break; + case 'ListTasks': + if (!$registry->hasMethod('tasks/listTasks')) { + break; + } + + $taskList = Horde_Util::getFormData('list'); + $taskType = Horde_Util::getFormData('taskType'); + $tasks = $registry->call('tasks/listTasks', + array(null, null, null, $taskList, $taskType, true)); + if (is_a($tasks, 'PEAR_Error')) { + $notification->push($tasks, 'horde.error'); + break; + } + + $result = new stdClass; + $result->taskList = $taskList; + $result->taskType = $taskType; + if (count($tasks)) { + $result->tasks = $tasks; + } + break; + case 'GetEvent': if (!($kronolith_driver = getDriver(Horde_Util::getFormData('cal')))) { $result = true; @@ -310,6 +332,27 @@ try { $result->tags[] = $tag['tag_name']; } break; + + case 'ToggleCompletion': + if (!$registry->hasMethod('tasks/toggleCompletion')) { + break; + } + $taskList = Horde_Util::getFormData('taskList'); + $taskType = Horde_Util::getFormData('taskType'); + $taskId = Horde_Util::getFormData('taskId'); + $saved = $registry->call('tasks/toggleCompletion', + array($taskId, $taskList)); + if (is_a($saved, 'PEAR_Error')) { + $notification->push($saved, 'horde.error'); + break; + } + + $result = new stdClass; + $result->taskList = $taskList; + $result->taskType = $taskType; + $result->taskId = $taskId; + $result->toggled = true; + break; } } catch (Exception $e) { $notification->push($e->getMessage(), 'horde.error'); diff --git a/kronolith/docs/CHANGES b/kronolith/docs/CHANGES index b04fc3716..a64d71115 100644 --- a/kronolith/docs/CHANGES +++ b/kronolith/docs/CHANGES @@ -2,9 +2,9 @@ v3.0-git -------- -[mjr] The listTimeObjects API is now responsible for providing an optional link - for the event. It can also now provide a URL to an icon to display in the - event's title on the calendar view. +[jan] Integrate tasks into Ajax interface (Gonçalo Queirós + ). +[mjr] Extend listTimeObjects API to include optional links and icons. [jan] Allow searching of any type of calendar and improve searching of recurring events. [cjh] With only SHOW permissions, display event titles as "busy". diff --git a/kronolith/js/src/kronolith.js b/kronolith/js/src/kronolith.js index b2dbc6342..812c38cad 100644 --- a/kronolith/js/src/kronolith.js +++ b/kronolith/js/src/kronolith.js @@ -21,10 +21,12 @@ KronolithCore = { view: '', ecache: $H(), + tcache: $H(), efifo: {}, eventsLoading: $H(), loading: 0, date: new Date(), + taskType: 1, //Default to all tasks view doActionOpts: { onException: function(r, e) { KronolitCore.debug('onException', e); }, @@ -223,6 +225,19 @@ KronolithCore = { break; + case 'tasks': + if (this.view == loc) { + return; + } + this._loadTasks(this.taskType); + if ($('kronolithView' + locCap)) { + this.viewLoading = true; + $('kronolithView' + locCap).appear({ 'queue': 'end', 'afterFinish': function() { this.viewLoading = false; }.bind(this) }); + } + $('kronolithLoading' + loc).insert($('kronolithLoading').remove()); + + break; + default: if ($('kronolithView' + locCap)) { this.viewLoading = true; @@ -824,6 +839,198 @@ KronolithCore = { }, /** + * Method to load tasks, either from cache or from database + * + * @param integer taskType The tasks type, (1 = all tasks, + * 0 = incomplete tasks, 2 = complete tasks, + * 3 = future tasks, 4 = future and incomplete + * tasks) + * @param Array tasksLists The lists from where to obtain the tasks + */ + _loadTasks: function(taskType, taskLists) + { + if (typeof taskLists == 'undefined') { + taskLists = []; + //FIXME: Temporary hack to get the tasklists + $H(Kronolith.conf.calendars.external).each(function(cal) { + if (cal.value.api = "Tasks" && cal.value.show) + { + taskLists.push(cal.key.substring(6)); + } + }); + } + + taskLists.each(function(taskList) { + var list = this.tcache.get(taskList); + + if (typeof list != 'undefined') { + this._insertTasks(taskType, taskList); + return; + } + + this.startLoading('tasks:' + taskList, taskType, ''); + this._storeTasksCache($H(), taskList); + this.doAction('ListTasks', {taskType: taskType, list: taskList}, this._loadTasksCallback.bind(this)); + }, this); + }, + + /** + * Callback method for inserting tasks in the current view. + * + * @param object r The ajax response object. + */ + _loadTasksCallback: function(r) + { + // Hide spinner. + this.loading--; + if (!this.loading) { + $('kronolithLoading').hide(); + } + + this._storeTasksCache(r.response.tasks || {}, r.response.taskList); + + // Check if this is the still the result of the most current request. + if (this.view != 'tasks' || + this.eventsLoading['tasks:' + r.response.taskList] != r.response.taskType) { + return; + } + this._insertTasks(r.response.taskType, r.response.taskList); + }, + + /** + * Reads tasks from the cache and inserts them into the view. + * + * @param integer taskType The tasks type, (1 = all tasks, + * 0 = incomplete tasks, 2 = complete tasks, + * 3 = future tasks, 4 = future and incomplete + * tasks) + * @param string tasksList The task list to be drawn + */ + _insertTasks: function(taskType, taskList) + { + $('kronolithViewTasksBody').select('tr[taskList=' + taskList + ']').invoke('remove'); + var tasks = this.tcache.get(taskList); + $H(tasks).each(function(task) { + // TODO: Check for the taskType + this._insertTask(task); + }, this); + }, + + /** + * Creates the DOM node for a task and inserts it into the view. + * + * @param object task A Hash with the task to insert + */ + _insertTask: function(task) + { + var body = $('kronolithViewTasksBody'), + row = $('kronolithTasksTemplate').cloneNode(true), + col = row.down(), + div = col.down(); + + row.removeAttribute('id'); + row.writeAttribute('taskList', task.value.l); + row.writeAttribute('taskId', task.key); + col.addClassName('kronolithTask' + (task.value.cp != 0 ? 'Completed' : '')); + col.insert(task.value.n); + if (typeof task.value.du != 'undefined') { + var date = Date.parse(task.value.du), + now = new Date(); + if (now.compareTo(date) != 1) { + col.addClassName('kronolithTaskDue'); + col.insert(new Element('SPAN', { 'class': 'kronolithSep' }).update('·')); + col.insert(new Element('SPAN', { 'class': 'kronolithDate' }).update(date.toString(Kronolith.conf.date_format))); + } + } + + if (typeof task.value.sd != 'undefined') { + col.insert(new Element('SPAN', { 'class': 'kronolithSep' }).update('·')); + col.insert(new Element('SPAN', { 'class': 'kronolithInfo' }).update(task.value.sd)); + } + + row.insert(col.show()); + this._insertTaskPosition(row, task); + }, + + /** + * Inserts the task row in the correct position + * + * @param Element newRow The new row to be inserted. + * @param object newTask A Hash with the task being added. + */ + _insertTaskPosition: function(newRow, newTask) + { + var rows = $('kronolithViewTasksBody').select('tr'); + // The first row is a template one, so must be ignored + for( var i = 1; i < rows.length; i++) { + var rowTaskList = rows[i].readAttribute('taskList'); + var rowTaskId = rows[i].readAttribute('taskId'); + var rowTask = this.tcache.get(rowTaskList).get(rowTaskId); + + // TODO: Assuming that tasks of the same tasklist are already in + // order + if (rowTaskList == newTask.value.l) { + continue; + } + + if (typeof rowTask == 'undefined') { + // TODO: Throw error + return; + } + if (!this._isTaskAfter(newTask.value, rowTask)) { + break; + } + } + rows[--i].insert({ 'after': newRow.show() }); + }, + + /** + * Method that analyzes wich task showld be drawn first + * + * TODO: Very incomplete, only a dummy version + */ + _isTaskAfter: function(taskA, taskB) + { + // TODO: Make all ordering system + return (taskA.pr >= taskB.pr); + }, + + /** + * Method that completes/uncompletes a task + * + * @param string taskList The task list to which the tasks belongs + * @param string taskId The id of the task + */ + _toggleCompletion: function(taskList, taskId) + { + var task = this.tcache.get(taskList).get(taskId); + if (typeof task == 'undefined') { + this._toggleCompletionClass(taskId); + // TODO: Show some message? + return; + } + // Update the cache + task.cp = (task.cp == "1") ? "0": "1"; + }, + + /** + * Method that toggles the CSS class to show that a tasks + * is completed/uncompleted + */ + _toggleCompletionClass: function(taskId) + { + var row = $(taskId); + if (row.length == 0) { + //FIXME: Show some error? + return; + } + var col = row.down('td.kronolithTaskCol', 0), div = col.down('div.kronolithTaskCheckbox', 0); + + col.toggleClassName('kronolithTask'); + col.toggleClassName('kronolithTaskCompleted'); + }, + + /** * Reads events from the cache and inserts them into the view. * * If inserting events into day and week views, the calendar parameter is @@ -1313,6 +1520,25 @@ KronolithCore = { }, /** + * Stores the tasks on cache + * + * @param object tasks The tasks to be stored + * @param string taskList The task list to which the tasks belong + */ + _storeTasksCache: function(tasks, taskList) + { + if (!this.tcache.get(taskList)) { + this.tcache.set(taskList, $H()); + } + + var taskHash = this.tcache.get(taskList); + + $H(tasks).each(function(task) { + taskHash.set(task.key, task.value); + }); + }, + + /** * Stores a set of events in the cache. * * For dates in the specified date ranges that don't contain any events, @@ -1715,6 +1941,25 @@ KronolithCore = { this.go('day:' + elt.readAttribute('date')); e.stop(); return; + } else if (elt.hasClassName('kronolithTaskCheckbox')) { + var taskId = elt.up('tr.kronolithTaskRow',0).readAttribute('id'), + taskList = elt.up('tr.kronolithTaskRow',0).readAttribute('tasklist'); + this._toggleCompletionClass(taskId); + this.doAction('ToggleCompletion', + { taskList: taskList, taskType: this.taskType, taskId: taskId }, + function(r) { + if (r.response.toggled) { + this._toggleCompletion(taskList, taskId); + } else { + // Check if this is the still the result of the most current request. + if (this.view != 'tasks' || this.taskType != r.response.taskType) { + return; + } + this._toggleCompletionClass(taskId); + } + }.bind(this)); + e.stop(); + return; } calClass = elt.readAttribute('calendarclass'); @@ -1732,6 +1977,14 @@ KronolithCore = { elt.toggleClassName('kronolithCalOn'); elt.toggleClassName('kronolithCalOff'); if (calClass == 'remote' || calClass == 'external') { + if (calClass == 'external' && calendar.startsWith('tasks/')) { + var taskList = calendar.substr(6); + if (typeof this.tcache.get(taskList) == 'undefined' && this.view == 'tasks') { + this._loadTasks(this.taskType,[taskList]); + } else { + $('kronolithViewTasksBody').select('tr[taskList=' + taskList + ']').invoke('toggle'); + } + } calendar = calClass + '_' + calendar; } this.doAction('SaveCalPref', { toggle_calendar: calendar }); diff --git a/kronolith/templates/index/tasks.inc b/kronolith/templates/index/tasks.inc index 0141f1c67..975b9f942 100644 --- a/kronolith/templates/index/tasks.inc +++ b/kronolith/templates/index/tasks.inc @@ -4,24 +4,12 @@
- - - - - Verify IE compatibility - - · - Apr 12, 2009 - · - = 8): ?> - · - Apr 8, 2009 - · - - Probably there should be changes in IE's CSS so that it displays properly + + + +
-
diff --git a/kronolith/themes/screen.css b/kronolith/themes/screen.css index 1071ee6de..02d1757b8 100644 --- a/kronolith/themes/screen.css +++ b/kronolith/themes/screen.css @@ -1114,19 +1114,26 @@ div.kronolithView div.kronolithViewBody div.kronolithRow { margin-left: 2px; } #kronolithViewTasks tbody.kronolithViewBody td { - padding: 4px 4px 4px 20px; - background-image: url("graphics/checkbox_off.png"); - background-position: 2px 3px; - background-repeat: no-repeat; + padding: 4px 4px 4px 4px; +} +#kronolithViewTasks tbody.kronolithViewBody div.kronolithTaskCheckbox { + background: url("graphics/checkbox_off.png") no-repeat; cursor: pointer; + margin-right: 5px; + float: left; + width: 16px; + height: 16px } -#kronolithViewTasks tbody.kronolithViewBody td.kronolithTaskCompleted, #kronolithViewTasks tbody.kronolithViewBody td:hover { - background-image: url("graphics/checkbox_on.png"); +#kronolithViewTasks tbody.kronolithViewBody div.kronolithTaskCheckbox:hover { + background: url("graphics/checkbox_on.png") no-repeat; } #kronolithViewTasks tbody.kronolithViewBody td.kronolithTaskCompleted { color: #ccc; text-decoration: line-through; } +#kronolithViewTasks tbody.kronolithViewBody td.kronolithTaskCompleted div { + background: url("graphics/checkbox_on.png") no-repeat; +} #kronolithViewTasks tbody.kronolithViewBody td.kronolithTaskDue { color: #a00; font-weight: bold;