From 034f2c8745dabb71e9e420eb478b668328d6cdb3 Mon Sep 17 00:00:00 2001 From: Luc Charland Date: Fri, 28 Sep 2012 15:42:49 -0400 Subject: [PATCH] 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. --- NEWS | 2 + UI/Scheduler/NSArray+Scheduler.h | 20 +++ UI/Scheduler/NSArray+Scheduler.m | 161 ++++++++++++++++---- UI/Scheduler/UIxCalListingActions.m | 30 +++- UI/Templates/SchedulerUI/UIxCalMainView.wox | 21 +-- UI/WebServerResources/SchedulerUI.css | 9 +- UI/WebServerResources/SchedulerUI.js | 114 +++++++++----- 7 files changed, 277 insertions(+), 80 deletions(-) diff --git a/NEWS b/NEWS index 1c73c91f3..97f1cfd41 100644 --- a/NEWS +++ b/NEWS @@ -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 diff --git a/UI/Scheduler/NSArray+Scheduler.h b/UI/Scheduler/NSArray+Scheduler.h index e5308d2e4..ed3ae2434 100644 --- a/UI/Scheduler/NSArray+Scheduler.h +++ b/UI/Scheduler/NSArray+Scheduler.h @@ -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 diff --git a/UI/Scheduler/NSArray+Scheduler.m b/UI/Scheduler/NSArray+Scheduler.m index 9a3d6234b..f0ed78869 100644 --- a/UI/Scheduler/NSArray+Scheduler.m +++ b/UI/Scheduler/NSArray+Scheduler.m @@ -27,6 +27,7 @@ #import #import +#import #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]; diff --git a/UI/Scheduler/UIxCalListingActions.m b/UI/Scheduler/UIxCalListingActions.m index e3d271f95..8ef47f070 100644 --- a/UI/Scheduler/UIxCalListingActions.m +++ b/UI/Scheduler/UIxCalListingActions.m @@ -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]; } diff --git a/UI/Templates/SchedulerUI/UIxCalMainView.wox b/UI/Templates/SchedulerUI/UIxCalMainView.wox index 4965bceb4..0c6db65c1 100644 --- a/UI/Templates/SchedulerUI/UIxCalMainView.wox +++ b/UI/Templates/SchedulerUI/UIxCalMainView.wox @@ -159,10 +159,10 @@ - - - - + + + + @@ -176,12 +176,13 @@
- - - - - - + + + + + + + diff --git a/UI/WebServerResources/SchedulerUI.css b/UI/WebServerResources/SchedulerUI.css index 15e58cd28..b4295ffde 100644 --- a/UI/WebServerResources/SchedulerUI.css +++ b/UI/WebServerResources/SchedulerUI.css @@ -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; } diff --git a/UI/WebServerResources/SchedulerUI.js b/UI/WebServerResources/SchedulerUI.js index fdbb51013..08f877af8 100644 --- a/UI/WebServerResources/SchedulerUI.js +++ b/UI/WebServerResources/SchedulerUI.js @@ -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");