Fixed bug #1515 Task View is very basic.

- Added sortable fields on Title, Due date, Location, Category and
  Calendar.
- Added Priority column, also sortable.
- Separated the logic of Events and Tasks.
maint-2.0.2
Luc Charland 2012-09-28 15:42:49 -04:00
parent 4038229688
commit 034f2c8745
7 changed files with 277 additions and 80 deletions

2
NEWS
View File

@ -4,6 +4,8 @@ New Features
- Microsoft Outlook compatibility layer
Enhancements
- Improved Task View, added sortable fields on Title, Due date,
Location, Category and Calendar. Added Priority column, also sortable.
- updated translations
- calendars list and mini-calendar are now always visible
- tasks list has moved to a table in a tabs view along the events list

View File

@ -48,6 +48,20 @@
#define eventErasableIndex 19
#define eventOwnerIsOrganizerIndex 20
// See [UIxCalListingActions initialize]
#define taskNameIndex 0
#define taskFolderIndex 1
#define taskCalendarNameIndex 2
#define taskStatusIndex 3
#define taskTitleIndex 4
#define taskEndDateIndex 5
#define taskClassificationIndex 6
#define taskLocationIndex 7
#define taskCategoryIndex 8
#define taskEditableIndex 9
#define taskErasableIndex 10
#define taskPriorityIndex 11
@interface NSArray (SOGoEventComparison)
- (NSComparisonResult) compareEventsStartDateAscending: (NSArray *) otherEvent;
@ -56,6 +70,12 @@
- (NSComparisonResult) compareEventsLocationAscending: (NSArray *) otherEvent;
- (NSComparisonResult) compareEventsCalendarNameAscending: (NSArray *) otherEvent;
- (NSComparisonResult) compareTasksAscending: (NSArray *) otherTask;
- (NSComparisonResult) compareTasksPriorityAscending: (NSArray *) otherTask;
- (NSComparisonResult) compareTasksTitleAscending: (NSArray *) otherTask;
- (NSComparisonResult) compareTasksEndAscending: (NSArray *) otherTask;
- (NSComparisonResult) compareTasksLocationAscending: (NSArray *) otherTask;
- (NSComparisonResult) compareTasksCategoryAscending: (NSArray *) otherTask;
- (NSComparisonResult) compareTasksCalendarNameAscending: (NSArray *) otherTask;
- (NSArray *) reversedArray;
@end

View File

@ -27,6 +27,7 @@
#import <Foundation/NSValue.h>
#import <NGExtensions/NGCalendarDateRange.h>
#import <NGExtensions/NSNull+misc.h>
#import "NSArray+Scheduler.h"
@ -119,40 +120,148 @@
NSComparisonResult result;
unsigned int selfTime, otherTime;
result = [self _compareCompletionWithStatus1: [self objectAtIndex: 2]
andStatus2: [otherTask objectAtIndex: 2]];
result = [self _compareCompletionWithStatus1: [self objectAtIndex: taskCalendarNameIndex]
andStatus2: [otherTask objectAtIndex: taskCalendarNameIndex]];
if (result == NSOrderedSame)
{
// End date
selfTime = [[self objectAtIndex: taskEndDateIndex] intValue];
otherTime = [[otherTask objectAtIndex: taskEndDateIndex] intValue];
if (selfTime && !otherTime)
result = NSOrderedAscending;
else if (!selfTime && otherTime)
result = NSOrderedDescending;
else
{
// End date
selfTime = [[self objectAtIndex: 5] intValue];
otherTime = [[otherTask objectAtIndex: 5] intValue];
if (selfTime && !otherTime)
result = NSOrderedAscending;
else if (!selfTime && otherTime)
result = NSOrderedDescending;
if (selfTime > otherTime)
result = NSOrderedDescending;
else if (selfTime < otherTime)
result = NSOrderedAscending;
else
{
if (selfTime > otherTime)
result = NSOrderedDescending;
else if (selfTime < otherTime)
result = NSOrderedAscending;
else
{
// Calendar ID
result = [[self objectAtIndex: 1]
compare: [otherTask objectAtIndex: 1]];
if (result == NSOrderedSame)
// Task name
result = [[self objectAtIndex: 4]
compare: [otherTask objectAtIndex: 4]
options: NSCaseInsensitiveSearch];
}
}
{
// Calendar ID
result = [[self objectAtIndex: taskFolderIndex]
compare: [otherTask objectAtIndex: taskFolderIndex]];
if (result == NSOrderedSame)
// Task name
result = [[self objectAtIndex: taskTitleIndex]
compare: [otherTask objectAtIndex: taskTitleIndex]
options: NSCaseInsensitiveSearch];
}
}
}
return result;
}
- (NSComparisonResult) compareTasksPriorityAscending: (NSArray *) otherTask
{
NSComparisonResult result;
int selfPriority, otherPriority;
selfPriority = [[self objectAtIndex: taskPriorityIndex] intValue];
otherPriority = [[otherTask objectAtIndex: taskPriorityIndex] intValue];
if (selfPriority && !otherPriority)
result = NSOrderedAscending;
else if (!selfPriority && otherPriority)
result = NSOrderedDescending;
else
{
if (selfPriority > otherPriority)
result = NSOrderedDescending;
else if (selfPriority < otherPriority)
result = NSOrderedAscending;
else
result = NSOrderedSame;
}
return result;
}
- (NSComparisonResult) compareTasksTitleAscending: (NSArray *) otherTask
{
NSString *selfTitle, *otherTitle;
selfTitle = [self objectAtIndex: taskTitleIndex];
otherTitle = [otherTask objectAtIndex: taskTitleIndex];
return [selfTitle caseInsensitiveCompare: otherTitle];
}
- (NSComparisonResult) compareTasksEndAscending: (NSArray *) otherTask
{
NSComparisonResult result;
unsigned int selfTime, otherTime;
// End date
selfTime = [[self objectAtIndex: taskEndDateIndex] intValue];
otherTime = [[otherTask objectAtIndex: taskEndDateIndex] intValue];
if (selfTime && !otherTime)
result = NSOrderedAscending;
else if (!selfTime && otherTime)
result = NSOrderedDescending;
else
{
if (selfTime > otherTime)
result = NSOrderedDescending;
else if (selfTime < otherTime)
result = NSOrderedAscending;
else
{
// Calendar ID
result = [[self objectAtIndex: taskFolderIndex]
compare: [otherTask objectAtIndex: taskFolderIndex]];
if (result == NSOrderedSame)
// Task name
result = [[self objectAtIndex: taskTitleIndex]
compare: [otherTask objectAtIndex: taskTitleIndex]
options: NSCaseInsensitiveSearch];
}
}
return result;
}
- (NSComparisonResult) compareTasksLocationAscending: (NSArray *) otherTask
{
NSString *selfLocation, *otherLocation;
selfLocation = [self objectAtIndex: taskLocationIndex];
otherLocation = [otherTask objectAtIndex: taskLocationIndex];
return [selfLocation caseInsensitiveCompare: otherLocation];
}
- (NSComparisonResult) compareTasksCategoryAscending: (NSArray *) otherTask
{
NSString *selfCategory, *otherCategory;
NSComparisonResult result;
selfCategory = [self objectAtIndex: taskCategoryIndex];
otherCategory = [otherTask objectAtIndex: taskCategoryIndex];
if ([selfCategory isNotNull] && [otherCategory isNotNull])
result = [selfCategory caseInsensitiveCompare: otherCategory];
else if ([selfCategory isNotNull])
result = NSOrderedAscending;
else if ([otherCategory isNotNull])
result = NSOrderedDescending;
else
result = NSOrderedSame;
return result;
}
- (NSComparisonResult) compareTasksCalendarNameAscending: (NSArray *) otherTask
{
NSString *selfCalendarName, *otherCalendarName;
selfCalendarName = [self objectAtIndex: taskCalendarNameIndex];
otherCalendarName = [otherTask objectAtIndex: taskCalendarNameIndex];
return [selfCalendarName caseInsensitiveCompare: otherCalendarName];
}
- (NSArray *) reversedArray
{
return [[self reverseObjectEnumerator] allObjects];

View File

@ -1047,14 +1047,16 @@ _computeBlocksPosition (NSArray *blocks)
- (WOResponse *) tasksListAction
{
NSMutableArray *filteredTasks, *filteredTask;
NSString *sort, *ascending;
NSString *statusFlag;
SOGoUserSettings *us;
NSEnumerator *tasks;
NSMutableArray *filteredTasks, *filteredTask;
BOOL showCompleted;
NSArray *task;
int statusCode;
unsigned int endDateStamp;
NSString *statusFlag;
BOOL showCompleted;
int statusCode;
filteredTasks = [NSMutableArray array];
@ -1087,7 +1089,25 @@ _computeBlocksPosition (NSArray *blocks)
[filteredTasks addObject: filteredTask];
}
}
[filteredTasks sortUsingSelector: @selector (compareTasksAscending:)];
sort = [[context request] formValueForKey: @"sort"];
if ([sort isEqualToString: @"title"])
[filteredTasks sortUsingSelector: @selector (compareTasksTitleAscending:)];
else if ([sort isEqualToString: @"priority"])
[filteredTasks sortUsingSelector: @selector (compareTasksPriorityAscending:)];
else if ([sort isEqualToString: @"end"])
[filteredTasks sortUsingSelector: @selector (compareTasksEndAscending:)];
else if ([sort isEqualToString: @"location"])
[filteredTasks sortUsingSelector: @selector (compareTasksLocationAscending:)];
else if ([sort isEqualToString: @"category"])
[filteredTasks sortUsingSelector: @selector (compareTasksCategoryAscending:)];
else if ([sort isEqualToString: @"calendarname"])
[filteredTasks sortUsingSelector: @selector (compareTasksCalendarNameAscending:)];
else
[filteredTasks sortUsingSelector: @selector (compareTasksAscending:)];
ascending = [[context request] formValueForKey: @"asc"];
if (![ascending boolValue])
[filteredTasks reverseArray];
return [self _responseWithData: filteredTasks];
}

View File

@ -159,10 +159,10 @@
<table id="eventsList" cellspacing="0">
<thead>
<tr>
<td id="titleHeader" class="headerCell headerTitle sortableTableHeader"><var:string label:value="Title"/></td>
<td id="startHeader" class="headerCell headerDateTime sortableTableHeader"><var:string label:value="Start"/></td>
<td id="endHeader" class="headerCell headerDateTime sortableTableHeader"><var:string label:value="End"/></td>
<td id="locationHeader" class="headerCell headerLocation sortableTableHeader"><var:string label:value="Location"/></td>
<td id="eventTitleHeader" class="headerCell headerTitle sortableTableHeader"><var:string label:value="Title"/></td>
<td id="eventStartHeader" class="headerCell headerDateTime sortableTableHeader"><var:string label:value="Start"/></td>
<td id="eventEndHeader" class="headerCell headerDateTime sortableTableHeader"><var:string label:value="End"/></td>
<td id="eventLocationHeader" class="headerCell headerLocation sortableTableHeader"><var:string label:value="Location"/></td>
<td id="calendarNameHeader" class="headerCell headerCalendarName sortableTableHeader"><var:string label:value="Calendar"/></td>
</tr>
</thead>
@ -176,12 +176,13 @@
<table id="tasksList" cellspacing="0">
<thead>
<tr>
<td id="completedHeader" class="headerCell"><entity name="nbsp"/></td>
<td class="headerCell headerTitle sortableTableHeader"><var:string label:value="Title"/></td>
<td class="headerCell headerDateTime sortableTableHeader"><var:string label:value="Due Date"/></td>
<td class="headerCell headerLocation sortableTableHeader"><var:string label:value="Location"/></td>
<td class="headerCell"><var:string label:value="Category"/></td>
<td class="headerCell headerCalendarName sortableTableHeader"><var:string label:value="Calendar"/></td>
<td id="taskCompletedHeader" class="headerCell"><entity name="nbsp"/></td>
<td id="taskPriorityHeader" class="headerCell headerPriority sortableTableHeader"><var:string label:value="Priority"/></td>
<td id="taskTitleHeader" class="headerCell headerTitle sortableTableHeader"><var:string label:value="Title"/></td>
<td id="taskEndHeader" class="headerCell headerDateTime sortableTableHeader"><var:string label:value="Due Date"/></td>
<td id="taskLocationHeader" class="headerCell headerLocation sortableTableHeader"><var:string label:value="Location"/></td>
<td id="taskCategoryHeader" class="headerCell headerLocation sortableTableHeader"><var:string label:value="Category"/></td>
<td id="taskCalendarNameHeader" class="headerCell headerCalendarName sortableTableHeader"><var:string label:value="Calendar"/></td>
</tr>
</thead>
<tbody><!-- tasks list --></tbody>

View File

@ -254,10 +254,13 @@ TABLE#tasksList
TABLE#eventsList .colorBox
{ margin-left: 2px; }
TABLE#eventsList TD.headerTitle,
TABLE#eventsList TD.headerDateTime
#eventsList TD.headerTitle,
#eventsList TD.headerDateTime
{ width: 30%; }
#tasksList .headerPriority
{ width: 80px;}
TABLE#eventsList TD,
TABLE#eventsList TH,
TABLE#tasksList TD,
@ -270,7 +273,7 @@ TABLE#eventsList TH,
TABLE#tasksList TH
{ white-space: pre; }
TABLE#tasksList TD#completedHeader
TABLE#tasksList TD#taskCompletedHeader
{ text-align: center;
width: 20px; }

View File

@ -952,7 +952,6 @@ function eventsListCallback(http) {
if (data[i][3] != null)
// Status is defined -- event is readable
row.observe("dblclick", editDoubleClickedEvent);
row.attachMenu("eventsListMenu");
var td = createElement("td");
row.appendChild(td);
@ -986,8 +985,8 @@ function eventsListCallback(http) {
td.appendChild(document.createTextNode(data[i][2])); // calendar
}
if (sorting["attribute"] && sorting["attribute"].length > 0) {
var sortHeader = $(sorting["attribute"] + "Header");
if (sorting["event-header"] && sorting["event-header"].length > 0) {
var sortHeader = $(sorting["event-header"]);
if (sortHeader) {
var sortImages = $(table.tHead).select(".sortImage");
@ -997,10 +996,10 @@ function eventsListCallback(http) {
var sortImage = createElement("img", "messageSortImage", "sortImage");
sortHeader.insertBefore(sortImage, sortHeader.firstChild);
if (sorting["ascending"])
sortImage.src = ResourcesURL + "/arrow-down.png";
else
if (sorting["event-ascending"])
sortImage.src = ResourcesURL + "/arrow-up.png";
else
sortImage.src = ResourcesURL + "/arrow-down.png";
}
}
}
@ -1074,6 +1073,10 @@ function tasksListCallback(http) {
input.setAttribute("checked", "checked");
input.observe("click", updateTaskStatus, true);
cell = createElement("td");
row.appendChild(cell);
cell.update(_("prio_" + data[i][11])); // Priority
cell = createElement("td");
row.appendChild(cell);
var colorDiv = createElement("div", false, "colorBox calendarFolder" + calendar);
@ -1103,6 +1106,23 @@ function tasksListCallback(http) {
table.scrollTop = table.previousScroll;
if (sorting["task-attribute"] && sorting["task-attribute"].length > 0) {
var sortHeader = $(sorting["task-header"]);
if (sortHeader) {
var sortImages = $(table.tHead).select(".sortImage");
$(sortImages).each(function(item) {
item.remove();
});
var sortImage = createElement("img", "messageSortImage", "sortImage");
sortHeader.insertBefore(sortImage, sortHeader.firstChild);
if (sorting["task-ascending"])
sortImage.src = ResourcesURL + "/arrow-up.png";
else
sortImage.src = ResourcesURL + "/arrow-down.png";
}
}
if (http.callbackData) {
var selectedNodesId = http.callbackData;
for (var i = 0; i < selectedNodesId.length; i++) {
@ -2109,28 +2129,37 @@ function _loadTasksHref(href) {
}
function onHeaderClick(event) {
var headerId = this.getAttribute("id");
var newSortAttribute;
if (headerId == "titleHeader")
newSortAttribute = "title";
else if (headerId == "startHeader")
newSortAttribute = "start";
else if (headerId == "endHeader")
newSortAttribute = "end";
else if (headerId == "locationHeader")
newSortAttribute = "location";
else if (headerId == "calendarNameHeader")
newSortAttribute = "calendarName";
else
newSortAttribute = "start";
var headerId;
if (sorting["attribute"] == newSortAttribute)
sorting["ascending"] = !sorting["ascending"];
else {
sorting["attribute"] = newSortAttribute;
sorting["ascending"] = true;
headerId = this.getAttribute("id");
if (headerId.startsWith('event'))
{
// This is needed to get the dom object and flip the triangle
sorting["event-header"] = headerId;
// Take away the 'events' and 'Header' and lowercase the result
newSortAttribute = headerId.sub("Header", "").sub("event", "").toLowerCase();
if (sorting["event-attribute"] == newSortAttribute)
sorting["event-ascending"] = !sorting["event-ascending"];
else
sorting["event-ascending"] = true;
sorting["event-attribute"] = newSortAttribute;
refreshEvents();
}
else // Tasks
{
// This is needed to get the dom object and flip the triangle
sorting["task-header"] = headerId;
// Take away the 'tasks' and 'Header' and lowercase the result
newSortAttribute = headerId.sub("Header", "").sub("task", "").toLowerCase();
if (sorting["task-attribute"] == newSortAttribute)
sorting["task-ascending"] = !sorting["task-ascending"];
else
sorting["task-ascending"] = true;
sorting["task-attribute"] = newSortAttribute;
refreshTasks();
}
refreshEvents();
Event.stop(event);
}
@ -2143,6 +2172,7 @@ function refreshCurrentFolder() {
function refreshEvents() {
var titleSearch;
var value = search["value"];
if (value && value.length)
titleSearch = "&search=" + escape(value.utf8encode());
else
@ -2150,23 +2180,26 @@ function refreshEvents() {
refreshAlarms();
return _loadEventHref("eventslist?asc=" + sorting["ascending"]
+ "&sort=" + sorting["attribute"]
return _loadEventHref("eventslist?asc=" + sorting["event-ascending"]
+ "&sort=" + sorting["event-attribute"]
+ "&day=" + currentDay
+ titleSearch
+ "&filterpopup=" + listFilter);
}
function refreshTasks(setUserDefault) {
var url = "taskslist?show-completed=" + showCompletedTasks;
var setud;
/* TODO: the logic behind this should be reimplemented properly:
the "taskslist" method should save the status when the 'show-completed'
is set to true and revert to the current status when that parameter is
NOT passed via the url. */
setud = "";
if (setUserDefault == 1)
url += "&setud=1";
setud = "&setud=1";
refreshAlarms();
return _loadTasksHref(url);
return _loadTasksHref("taskslist?show-completed=" + showCompletedTasks
+ "&asc=" + sorting["task-ascending"]
+ "&sort=" + sorting["task-attribute"]);
}
function refreshEventsAndDisplay() {
@ -3153,22 +3186,27 @@ function deletePersonalCalendarCallback(http) {
}
function configureLists() {
var list = $$("#tasksList tbody").first();
// TASK LIST
var list = $("tasksList");
list.multiselect = true;
list.on("mousedown", onTasksSelectionChange);
list.on("selectstart", listRowMouseDownHandler);
list.attachMenu("tasksListMenu");
configureSortableTableHeaders(list);
TableKit.Resizable.init(list, {'trueResize' : true, 'keepWidth' : true});
list.down("tbody").on("mousedown", onTasksSelectionChange);
list.down("tbody").on("selectstart", listRowMouseDownHandler);
list.down("tbody").attachMenu("tasksListMenu");
var input = $("showHideCompletedTasks");
input.observe("click", onShowCompletedTasks);
if (showCompletedTasks)
input.checked = true;
// EVENT LIST
list = $("eventsList");
list.multiselect = true;
configureSortableTableHeaders(list);
TableKit.Resizable.init(list, {'trueResize' : true, 'keepWidth' : true});
list.down("tbody").on("mousedown", onEventsSelectionChange);
list.down("tbody").attachMenu("eventsListMenu");
}
function initDateSelectorEvents() {
@ -3280,8 +3318,12 @@ function saveTabState(event) {
}
function initScheduler() {
sorting["attribute"] = "start";
sorting["ascending"] = true;
sorting["event-header"] = "";
sorting["task-header"] = "";
sorting["event-attribute"] = "start";
sorting["task-attribute"] = "end";
sorting["event-ascending"] = true;
sorting["task-ascending"] = true;
if (!$(document.body).hasClassName("popup")) {
var node = $("filterpopup");