diff --git a/ChangeLog b/ChangeLog index 7f7adc58e..a988d2183 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,20 @@ +2011-04-14 Francis Lachapelle + + * UI/Contacts/UIxContactsListActions.m: was + UIxContactsListView.m. The new method contactsListAction now + returns a JSON representation of the addressbook. + + * UI/Contacts/UIxContactFoldersView.m (-personalContactInfos): new + method used to populate the wox template with the personal + addressbook. The addressbook module is now pre-loaded with it, + eliminating an Ajax query. + + * UI/WebServerResources/ContactsUI.js (initContacts): delegate + all events to the table instead of each row. + (contactsListCallback): we now receive a JSON representation of + the contacts and reuse the existing table rows instead of + overwriting the table. + 2011-04-13 Wolfgang Sourdeau * OpenChange/MAPIStoreObject.m: make use of the IMP cache methods diff --git a/UI/Contacts/GNUmakefile b/UI/Contacts/GNUmakefile index 7df81e254..acc321a97 100644 --- a/UI/Contacts/GNUmakefile +++ b/UI/Contacts/GNUmakefile @@ -9,7 +9,7 @@ ContactsUI_PRINCIPAL_CLASS = ContactsUIProduct ContactsUI_LANGUAGES = BrazilianPortuguese Catalan Czech Dutch English French German Hungarian Italian Norwegian Polish Russian Spanish Swedish Ukrainian Welsh ContactsUI_OBJC_FILES = \ - UIxContactsUserFolders.m \ + UIxContactsUserFolders.m \ UIxContactsMailerSelection.m \ UIxContactsUserRightsEditor.m \ \ @@ -18,10 +18,10 @@ ContactsUI_OBJC_FILES = \ UIxContactActions.m \ UIxContactView.m \ UIxContactEditor.m \ - UIxListView.m \ - UIxListEditor.m \ - UIxContactsListView.m \ - UIxContactFoldersView.m \ + UIxListView.m \ + UIxListEditor.m \ + UIxContactsListActions.m \ + UIxContactFoldersView.m \ UIxContactFolderActions.m ContactsUI_RESOURCE_FILES += \ diff --git a/UI/Contacts/UIxContactFoldersView.h b/UI/Contacts/UIxContactFoldersView.h index d385c1adf..f300cc6e5 100644 --- a/UI/Contacts/UIxContactFoldersView.h +++ b/UI/Contacts/UIxContactFoldersView.h @@ -29,6 +29,7 @@ @interface UIxContactFoldersView : UIxComponent { SOGoUserSettings *us; + NSDictionary *currentContact; NSString *selectorComponentClass; NSMutableDictionary *moduleSettings; id currentFolder; @@ -37,6 +38,8 @@ - (NSArray *) contactFolders; +- (NSArray *) personalContactInfos; + - (NSString *) currentContactFolderId; - (NSString *) currentContactFolderOwner; - (NSString *) currentContactFolderName; diff --git a/UI/Contacts/UIxContactFoldersView.m b/UI/Contacts/UIxContactFoldersView.m index c59ae0cfc..a9d56ac3e 100644 --- a/UI/Contacts/UIxContactFoldersView.m +++ b/UI/Contacts/UIxContactFoldersView.m @@ -87,6 +87,38 @@ } } +- (void) setCurrentContact: (NSDictionary *) _contact +{ + currentContact = _contact; +} + +- (NSDictionary *) currentContact +{ + return currentContact; +} + +- (NSString *) currentContactClasses +{ + return [[currentContact objectForKey: @"c_component"] lowercaseString]; +} + +- (NSArray *) personalContactInfos +{ + SOGoContactFolders *folders; + id folder; + NSArray *contactInfos; + + folders = [self clientObject]; + folder = [folders lookupPersonalFolder: @"personal" ignoringRights: YES]; + + contactInfos = [folder lookupContactsWithFilter: nil + onCriteria: nil + sortBy: @"c_cn" + ordering: NSOrderedAscending]; + + return contactInfos; +} + - (id ) mailerContactsAction { selectorComponentClass = @"UIxContactsMailerSelection"; diff --git a/UI/Contacts/UIxContactsListView.h b/UI/Contacts/UIxContactsListActions.h similarity index 81% rename from UI/Contacts/UIxContactsListView.h rename to UI/Contacts/UIxContactsListActions.h index 2f6e0bf5e..875fed8f4 100644 --- a/UI/Contacts/UIxContactsListView.h +++ b/UI/Contacts/UIxContactsListActions.h @@ -19,17 +19,17 @@ 02111-1307, USA. */ -#ifndef __UIxContactsListView_H__ -#define __UIxContactsListView_H__ +#ifndef __UIxContactsListActions_H__ +#define __UIxContactsListActions_H__ -#import +#import @class NSDictionary; @class NSString; @protocol SOGoContactObject; -@interface UIxContactsListView : UIxComponent +@interface UIxContactsListActions : WODirectAction { NSDictionary *currentContact; NSArray *contactInfos; @@ -37,4 +37,4 @@ @end -#endif /* __UIxContactsListView_H__ */ +#endif /* __UIxContactsListActions_H__ */ diff --git a/UI/Contacts/UIxContactsListView.m b/UI/Contacts/UIxContactsListActions.m similarity index 81% rename from UI/Contacts/UIxContactsListView.m rename to UI/Contacts/UIxContactsListActions.m index bc2f07656..cd4ddf4ea 100644 --- a/UI/Contacts/UIxContactsListView.m +++ b/UI/Contacts/UIxContactsListActions.m @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2010 Inverse inc. + Copyright (C) 2006-2011 Inverse inc. Copyright (C) 2004-2005 SKYRIX Software AG This file is part of OpenGroupware.org. @@ -32,6 +32,8 @@ #import #import +#import + #import #import #import @@ -44,9 +46,9 @@ #import #import -#import "UIxContactsListView.h" +#import "UIxContactsListActions.h" -@implementation UIxContactsListView +@implementation UIxContactsListActions - (id) init { @@ -64,16 +66,6 @@ /* accessors */ -- (void) setCurrentContact: (NSDictionary *) _contact -{ - currentContact = _contact; -} - -- (NSDictionary *) currentContact -{ - return currentContact; -} - - (NSString *) defaultSortKey { return @"c_cn"; @@ -82,8 +74,10 @@ - (NSString *) sortKey { NSString *s; + WORequest *rq; - s = [self queryParameterForKey: @"sort"]; + rq = [context request]; + s = [rq formValueForKey: @"sort"]; if (![s length]) s = [self defaultSortKey]; @@ -95,18 +89,20 @@ id folder; NSString *ascending, *searchText, *valueText; NSComparisonResult ordering; + WORequest *rq; if (!contactInfos) { folder = [self clientObject]; + rq = [context request]; - ascending = [self queryParameterForKey: @"asc"]; + ascending = [rq formValueForKey: @"asc"]; ordering = ((![ascending length] || [ascending boolValue]) ? NSOrderedAscending : NSOrderedDescending); - searchText = [self queryParameterForKey: @"search"]; + searchText = [rq formValueForKey: @"search"]; if ([searchText length] > 0) - valueText = [self queryParameterForKey: @"value"]; + valueText = [rq formValueForKey: @"value"]; else valueText = nil; @@ -121,9 +117,21 @@ return contactInfos; } -- (NSString *) currentContactClasses +/** + * Retrieve the addressbook contacts with respect to the sort and + * search criteria. + * @return a JSON array of dictionaries representing the contacts. + */ +- (id ) contactsListAction { - return [[currentContact objectForKey: @"c_component"] lowercaseString]; + id result; + NSArray *contactsList; + + contactsList = [self contactInfos]; + result = [self responseWithStatus: 200 + andString: [contactsList jsonRepresentation]]; + + return result; } - (id ) contactSearchAction @@ -136,11 +144,12 @@ NSMutableDictionary *uniqueContacts; unsigned int i; NSSortDescriptor *commonNameDescriptor; + WORequest *rq; - searchText = [self queryParameterForKey: @"search"]; + rq = [context request]; + searchText = [rq formValueForKey: @"search"]; if ([searchText length] > 0) { - folder = nil; NS_DURING folder = [self clientObject]; NS_HANDLER @@ -176,9 +185,8 @@ data = [NSDictionary dictionaryWithObjectsAndKeys: searchText, @"searchText", sortedContacts, @"contacts", nil]; - result = [self responseWithStatus: 200]; - - [(WOResponse*)result appendContentString: [data jsonRepresentation]]; + result = [self responseWithStatus: 200 + andString: [data jsonRepresentation]]; } else result = [NSException exceptionWithHTTPStatus: 400 @@ -187,13 +195,4 @@ return result; } - -/* actions */ - -- (BOOL) shouldTakeValuesFromRequest: (WORequest *) _rq - inContext: (WOContext*) _c -{ - return YES; -} - -@end /* UIxContactsListView */ +@end /* UIxContactsListActions */ diff --git a/UI/Contacts/product.plist b/UI/Contacts/product.plist index 2136af2bf..e88d39ca5 100644 --- a/UI/Contacts/product.plist +++ b/UI/Contacts/product.plist @@ -71,11 +71,12 @@ methods = { view = { protectedBy = "View"; - pageName = "UIxContactsListView"; + actionClass = "UIxContactsListActions"; + actionName = "contactsList"; }; contactSearch = { protectedBy = ""; - pageName = "UIxContactsListView"; + actionClass = "UIxContactsListActions"; actionName = "contactSearch"; }; newcontact = { @@ -90,7 +91,7 @@ }; mailer-contacts = { protectedBy = "View"; - pageName = "UIxContactsListView"; + pageName = "UIxContactFoldersView"; actionName = "mailerContacts"; }; export = { @@ -130,7 +131,8 @@ methods = { view = { protectedBy = ""; - pageName = "UIxContactsListView"; + actionClass = "UIxContactsListActions"; + actionName = "contactsList"; }; newcontact = { protectedBy = ""; @@ -139,7 +141,7 @@ }; mailer-contacts = { protectedBy = ""; - pageName = "UIxContactsListView"; + pageName = "UIxContactFoldersView"; actionName = "mailerContacts"; }; canAccessContent = { diff --git a/UI/Templates/ContactsUI/UIxContactFoldersView.wox b/UI/Templates/ContactsUI/UIxContactFoldersView.wox index 2bd9210cc..e8f304862 100644 --- a/UI/Templates/ContactsUI/UIxContactFoldersView.wox +++ b/UI/Templates/ContactsUI/UIxContactFoldersView.wox @@ -138,11 +138,47 @@
-
+
+ + + + + + + + + + + + + + + + + + + +
+
-
+
diff --git a/UI/WebServerResources/ContactsUI.js b/UI/WebServerResources/ContactsUI.js index 44e40725e..8f600b8ec 100644 --- a/UI/WebServerResources/ContactsUI.js +++ b/UI/WebServerResources/ContactsUI.js @@ -7,7 +7,7 @@ var usersRightsWindowHeight = 179; var usersRightsWindowWidth = 450; var Contact = { - currentAddressBook: null, + currentAddressBook: "/personal", currentContact: null, deleteContactsRequestCount: null }; @@ -55,8 +55,6 @@ function openContactsFolder(contactsFolder, reload, idx) { var contactsList = $("contactsList"); if (contactsList) selection = contactsList.getSelectedRowsId(); - // else - // window.alert("no contactsList"); } else selection = null; @@ -70,18 +68,6 @@ function openContactsFolder(contactsFolder, reload, idx) { } } -function openContactsFolderAtIndex(element) { - var idx = element.getAttribute("idx"); - var url = URLForFolderID(Contact.currentAddressBook) + "/view?noframe=1&idx=" + idx; - - if (document.contactsListAjaxRequest) { - document.contactsListAjaxRequest.aborted = true; - document.contactsListAjaxRequest.abort(); - } - document.contactsListAjaxRequest - = triggerAjaxRequest(url, contactsListCallback); -} - function contactsListCallback(http) { if (http.readyState == 4) { if (http.status == 200) { @@ -89,33 +75,76 @@ function contactsListCallback(http) { var div = $("contactsListContent"); var table = $("contactsList"); - if (table) { - // Update table - var data = http.responseText; - var html = data.replace(/^(.*\n)*.*( 0) + data = http.responseText.evalJSON(true); + + tbody.deselectAll(); + + div.scrollTop = 0; + if (data.length > 0) { + // Replace existing rows + for (var i = 0; i < data.length && i < rows.length; i++) { + var contact = data[i]; + var row = rows[i]; + row.className = contact["c_component"]; + row.setAttribute("id", contact["c_name"]); + row.setAttribute("categories", contact["c_categories"]); + row.setAttribute("contactname", contact["c_cn"]); + var cells = row.getElementsByTagName("TD"); + $(cells[0]).update(contact["c_cn"]); + $(cells[1]).update(contact["c_mail"]); + $(cells[2]).update(contact["c_screenname"]); + $(cells[3]).update(contact["c_o"]); + $(cells[4]).update(contact["c_telephonenumber"]); + } + + // Add extra rows + for (var j = i; j < data.length; j++) { + var contact = data[j]; + var row = createElement("tr", + contact["c_name"], + contact["c_component"], + null, + { categories: contact["c_categories"], + contactname: contact["c_cn"] }, + tbody); + + var cell = createElement("td", + null, + ( "displayName" ), + null, + null, + row); + cell.appendChild(document.createTextNode(contact["c_cn"])); + + cell = document.createElement("td"); + row.appendChild(cell); + if (contact["c_mail"]) + cell.appendChild(document.createTextNode(contact["c_mail"])); + + cell = document.createElement("td"); + row.appendChild(cell); + if (contact["c_screenname"]) + cell.appendChild(document.createTextNode(contact["c_screenname"])); + + cell = document.createElement("td"); + row.appendChild(cell); + if (contact["c_o"]) + cell.appendChild(document.createTextNode(contact["c_o"])); + + cell = document.createElement("td"); + row.appendChild(cell); + if (contact["c_telephonenumber"]) + cell.appendChild(document.createTextNode(contact["c_telephonenumber"])); + } } - else { - // Add table - div.update(http.responseText); - table = $("contactsList"); - table.multiselect = true; - table.observe("mousedown", onContactSelectionChange); - configureSortableTableHeaders(table); - TableKit.Resizable.init(table, {'trueResize' : true, 'keepWidth' : true}); - configureDraggables(); - resetCategoriesMenu(); - } - var rows = table.tBodies[0].rows; - for (var i = 0; i < rows.length; i++) { - var row = $(rows[i]); - row.observe("mousedown", onRowClick); - row.observe("dblclick", onContactRowDblClick); - row.observe("selectstart", listRowMouseDownHandler); - row.observe("contextmenu", onContactContextMenu); + + // Remove unnecessary rows + for (i = rows.length - 1; i >= data.length; i--) { + tbody.removeChild(rows[i]); } if (sorting["attribute"] && sorting["attribute"].length > 0) { @@ -142,26 +171,30 @@ function contactsListCallback(http) { var sortImage = createElement("img", "messageSortImage", "sortImage"); sortHeader.insertBefore(sortImage, sortHeader.firstChild); if (sorting["ascending"]) - sortImage.src = ResourcesURL + "/arrow-down.png"; - else sortImage.src = ResourcesURL + "/arrow-up.png"; + else + sortImage.src = ResourcesURL + "/arrow-down.png"; } } - - var selected = http.callbackData; - if (selected) { - for (var i = 0; i < selected.length; i++) { - var row = $(selected[i]); + + // Restore selection and scroll to first selected node + tbody.refreshSelectionByIds(); + var selection = http.callbackData; + if (selection) { + for (var i = 0; i < selection.length; i++) { + var row = $(selection[i]); if (row) { var rowPosition = row.rowIndex * row.getHeight(); if (div.getHeight() < rowPosition) div.scrollTop = rowPosition; // scroll to selected contact - row.selectElement(); + break; } } } + } else { + // No more access to this address book; empty the list var table = $("contactsList"); if (table) { var sortImages = $(table.tHead).select(".sortImage"); @@ -349,7 +382,8 @@ function moveTo(uri) { /* contact menu entries */ function onContactRowDblClick(event) { - var cname = this.getAttribute('id'); + var t = getTarget(event); + var cname = t.parentNode.getAttribute('id'); openContactWindow(URLForFolderID(Contact.currentAddressBook) + "/" + cname + "/edit", cname); @@ -358,7 +392,13 @@ function onContactRowDblClick(event) { } function onContactSelectionChange(event) { - var rows = this.getSelectedRowsId(); + if (event) { + // Update rows selection + var t = getTarget(event); + onRowClick(event, t); + } + + var rows = this.parentNode.getSelectedRowsId(); if (rows.length == 1) { var node = $(rows[0]); @@ -446,8 +486,9 @@ function onToolbarDeleteSelectedContactsConfirm(dialogId) { var contactsList = $('contactsList'); var rows = contactsList.getSelectedRowsId(); for (var i = 0; i < rows.length; i++) { - // hide row? - $(rows[i]).hide(); + var row = $(rows[i]); + row.deselect(); + row.hide(); delete cachedContacts[Contact.currentAddressBook + "/" + rows[i]]; var urlstr = (URLForFolderID(Contact.currentAddressBook) + "/" + rows[i] + "/delete"); @@ -478,7 +519,6 @@ function onContactDeleteEventCallback(http) { loadContact(Contact.currentContact); } } - row.deselect(); row.parentNode.removeChild(row); } else if (parseInt(http.status) == 403) { @@ -587,7 +627,7 @@ function onConfirmContactSelection(event) { currentAddressBookName, cid); } else { - var cname = '' + rows[i].getAttribute("contactname"); + var cname = '' + rows[i].readAttribute("contactname"); var email = '' + rows[i].cells[1].innerHTML; window.opener.addContact(tag, currentAddressBookName + '/' + cname, @@ -901,9 +941,8 @@ function configureAddressBooks() { lookupDeniedFolders(); configureDroppables(); - var personalFolder = $("/personal"); - personalFolder.selectElement(); - openContactsFolder("/personal"); + // Select initial addressbook + $(Contact.currentAddressBook).selectElement(); } } @@ -1207,7 +1246,7 @@ function onDocumentKeydown(event) { viewPort.scrollTop += rowBottom - divBottom; else if (rowScrollOffset.top > rowPosition.top) viewPort.scrollTop -= rowScrollOffset.top - rowPosition.top; - + // Select and load the next message nextRow.selectElement(); loadContact(nextRow.readAttribute("id")); @@ -1254,15 +1293,19 @@ function initContacts(event) { var table = $("contactsList"); if (table) { - // Initialize contacts table + // Initialize event delegation on contacts table table.multiselect = true; - table.observe("mousedown", onContactSelectionChange); + var tbody = table.tBodies[0]; + tbody.on("mousedown", onContactSelectionChange); + tbody.on("dblclick", onContactRowDblClick); + tbody.on("selectstart", listRowMouseDownHandler); + tbody.on("contextmenu", onContactContextMenu); configureSortableTableHeaders(table); TableKit.Resizable.init(table, {'trueResize' : true, 'keepWidth' : true}); - + resetCategoriesMenu(); } - + onWindowResize.defer(); Event.observe(window, "resize", onWindowResize); @@ -1287,8 +1330,7 @@ function resetCategoriesMenu() { var catName = UserDefaults["SOGoContactsCategories"][i]; if (catName.length > 0) { var menuLI = createElement("li"); - var bound = onCategoriesMenuItemClick.bindAsEventListener(menuLI); - menuLI.observe("click", bound); + menuLI.observe("click", onCategoriesMenuItemClick); menuLI.category = catName; menuLI.appendChild(document.createTextNode(catName)); menuUL.appendChild(menuLI); @@ -1307,7 +1349,7 @@ function onCategoriesMenuPrepareVisibility() { if (contactsList) { var rows = contactsList.getSelectedRows(); if (rows.length > 0) { - var catList = rows[0].getAttribute("categories"); + var catList = rows[0].readAttribute("categories"); var catsArray; if (catList && catList.length > 0) { catsArray = catList.split(","); @@ -1315,7 +1357,6 @@ function onCategoriesMenuPrepareVisibility() { else { catsArray = []; } - var menu = $("categoriesMenu"); var ul = menu.down("ul"); var listElements = ul.select("li"); @@ -1365,7 +1406,7 @@ function onCategoriesMenuItemCallback(http) { && contact.id == Contact.currentContact) loadContact(Contact.currentContact); } - else { + else if (parseInt(http.status) == 403) { log("onCategoriesMenuItemCallback failed: error " + http.status + " (" + http.responseText + ")"); } }