Monotone-Parent: c40151185171e3ba8a257569b8ab5ec86930d393

Monotone-Revision: 0efdaccbad84f567e4d9cc3ca0176e506620aefb

Monotone-Author: wsourdeau@inverse.ca
Monotone-Date: 2010-05-05T18:49:29
Monotone-Branch: ca.inverse.sogo
maint-2.0.2
Wolfgang Sourdeau 2010-05-05 18:49:29 +00:00
parent 2bf4b7cf8b
commit 97df7c4c45
10 changed files with 422 additions and 277 deletions

View File

@ -1,5 +1,49 @@
2010-05-05 Wolfgang Sourdeau <wsourdeau@inverse.ca> 2010-05-05 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* UI/WebServerResources/UIxAttendeesEditor.js (onContactKeydown):
the code for the "enter" and "tab" has been merged, since it was
similar. We also resolve individual contacts from list entries.
(resolveListAttendees): new function that fetches the indivual
entries from contacts lists.
(resolveListAttendeesCallback): callback for the above, that
create rows and input fields corresponding to the returned
entries, and trigger a contact search on each of them.
(performSearch): take an "input" field as argument since
"attendeesEditor.currentField" is no longer used.
(performSearchCallbacks): roles and partstats are now stored in
individual attributes rather than in the element class. We also
now behave differently depending on whether the event owner is
returned or another type of user, in order to match Lightning's
behaviour.
(newAttendee): no longer a callback, and now takes an optional
argument that indicates with attendee row precedes the one being
created. Also the new row is returned.
(checkAttendee): no longer a callback. Now takes an "input"
argument.
(onInputBlur): new callback that invokes "checkAttendee" and
handle the cleanup of the entries menu.
(displayFreeBusyForNode): use DOM methods rather than "innerHTML"
to modify the cell contents.
(updateFreeBusyDataCallback): freebusy requests are no longer
sequential.
(prepareAttendees): we now modify the DOM attributes rather than
the HTML attributes.
* UI/Contacts/UIxListView.m (-propertiesAction): we now invoke
-[self responseWithStatus:andJSONRepresentation:] to build the
resulting WOResponse.
* UI/Contacts/UIxContactFoldersView.m (-allContactSearchAction):
the type of returned component can now be deduced from the value
of "c_component" rather than from the c_name extension... Also, we
avoid autoreleasing variables where it's not needed.
(-contactSearchAction): removed obsolete method.
* SoObjects/Contacts/SOGoContactGCSFolder.m (+initialize): new
method initializing "folderListingFields" as a static NSArray
rather than as macro. Added "c_component" to the list of fields to
retrieve.
* UI/Scheduler/UIxComponentEditor.m (-ownerLogin): new accessor * UI/Scheduler/UIxComponentEditor.m (-ownerLogin): new accessor
the differenciate between the type of users in the list of the differenciate between the type of users in the list of
attendees. attendees.

View File

@ -7812,16 +7812,17 @@ Index: sope-appserver/NGObjWeb/DAVPropMap.plist
=================================================================== ===================================================================
--- sope-appserver/NGObjWeb/DAVPropMap.plist (revision 1664) --- sope-appserver/NGObjWeb/DAVPropMap.plist (revision 1664)
+++ sope-appserver/NGObjWeb/DAVPropMap.plist (working copy) +++ sope-appserver/NGObjWeb/DAVPropMap.plist (working copy)
@@ -157,6 +157,8 @@ @@ -157,6 +157,9 @@
"{urn:ietf:params:xml:ns:caldav}supported-calendar-data" = "{urn:ietf:params:xml:ns:caldav}supported-calendar-data" =
davSupportedCalendarDataTypes; davSupportedCalendarDataTypes;
"{urn:ietf:params:xml:ns:caldav}calendar-description" = davDescription; "{urn:ietf:params:xml:ns:caldav}calendar-description" = davDescription;
+ "{urn:ietf:params:xml:ns:caldav}calendar-timezone" = davCalendarTimeZone; + "{urn:ietf:params:xml:ns:caldav}calendar-timezone" = davCalendarTimeZone;
+ "{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL" = davScheduleDefaultCalendarURL; + "{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL" = davScheduleDefaultCalendarURL;
+ "{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp" = davScheduleCalendarTransparency;
/* CardDAV */ /* CardDAV */
"{urn:ietf:params:xml:ns:carddav}addressbook-home-set" = davAddressbookHomeSet; "{urn:ietf:params:xml:ns:carddav}addressbook-home-set" = davAddressbookHomeSet;
@@ -168,6 +170,8 @@ @@ -168,6 +171,8 @@
"{http://calendarserver.org/ns/}dropbox-home-URL" = davDropboxHomeURL; "{http://calendarserver.org/ns/}dropbox-home-URL" = davDropboxHomeURL;
"{http://calendarserver.org/ns/}notifications-URL" = davNotificationsURL; "{http://calendarserver.org/ns/}notifications-URL" = davNotificationsURL;
"{http://calendarserver.org/ns/}getctag" = davCollectionTag; "{http://calendarserver.org/ns/}getctag" = davCollectionTag;

View File

@ -2285,6 +2285,11 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
return @""; return @"";
} }
- (NSString *) davScheduleCalendarTransparency
{
return @"coucou";
}
/* vevent UID handling */ /* vevent UID handling */
- (NSString *) resourceNameForEventUID: (NSString *) uid - (NSString *) resourceNameForEventUID: (NSString *) uid

View File

@ -46,13 +46,20 @@
#import "SOGoContactGCSFolder.h" #import "SOGoContactGCSFolder.h"
#define folderListingFields [NSArray arrayWithObjects: @"c_name", @"c_cn", \ static NSArray *folderListingFields = nil;
@"c_givenname", @"c_sn", @"c_screenname", \
@"c_o", @"c_mail", @"c_telephonenumber", \
nil]
@implementation SOGoContactGCSFolder @implementation SOGoContactGCSFolder
+ (void) initialize
{
if (!folderListingFields)
folderListingFields = [[NSArray alloc] initWithObjects: @"c_name",
@"c_cn", @"c_givenname", @"c_sn",
@"c_screenname", @"c_o",
@"c_mail", @"c_telephonenumber",
@"c_component", nil];
}
- (Class) objectClassForContent: (NSString *) content - (Class) objectClassForContent: (NSString *) content
{ {
CardGroup *cardEntry; CardGroup *cardEntry;

View File

@ -37,6 +37,45 @@ def fetchUserInfo(login):
return (displayName, email_nodes[0].childNodes[0].nodeValue) return (displayName, email_nodes[0].childNodes[0].nodeValue)
class CalDAVPropertiesTest(unittest.TestCase):
def setUp(self):
self.client = webdavlib.WebDAVClient(hostname, port,
username, password)
self.user_calendar = "/SOGo/dav/%s/Calendar/personal/" % username
def testDavScheduleCalendarTransparency(self):
"""{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp"""
propfind = webdavlib.WebDAVPROPFIND(self.user_calendar,
["{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp"],
0)
self.client.execute(propfind)
propfind.xpath_namespace = { "D": "DAV:",
"C": "urn:ietf:params:xml:ns:caldav" }
propstats = propfind.xpath_evaluate('/D:multistatus/D:response/D:propstat/D:prop/C:schedule-calendar-transp')
self.assertTrue(len(propstats) > 0,
"schedule-calendar-transp not present in response")
node = propstats[0]
status = propfind.xpath_evaluate('D:status',
node.parentNode.parentNode)[0] \
.childNodes[0].nodeValue[9:12]
self.assertEquals(status, "200",
"schedule-calendar-transp marked as 'Not Found' in response")
values = node.childNodes
nvalues = len(values)
self.assertEquals(nvalues, 1,
"expected 1 value (%d received)" % nvalues)
value = values[0]
print node.namespaceURI
self.assertEquals(value.__class__.__name__, "Element",
"schedule-calendar-transp must be an instance of" \
" %s, not %s" % ("Element", value.__class__.__name__))
self.assertEquals(value.namespaceURI, "urn:ietf:params:xml:ns:caldav",
"schedule-calendar-transp must have a value in namespace '%s', not '%s'", "urn:ietf:params:xml:ns:caldav", value.namespaceURI)
self.assertTrue(value.tagName == "opaque"
or value.tagName == "transparent",
"schedule-calendar-transp must be 'opaque' or" \
" 'transparent', not '%s'" % value.tagName)
class CalDAVITIPDelegationTest(unittest.TestCase): class CalDAVITIPDelegationTest(unittest.TestCase):
def setUp(self): def setUp(self):
self.client = webdavlib.WebDAVClient(hostname, port, self.client = webdavlib.WebDAVClient(hostname, port,
@ -159,7 +198,7 @@ class CalDAVITIPDelegationTest(unittest.TestCase):
% (email, % (email,
compared_attendees[email], attendees[email])) compared_attendees[email], attendees[email]))
def testInvitationDelegation(self): def xtestInvitationDelegation(self):
""" invitation delegation """ """ invitation delegation """
# the invitation must not exist # the invitation must not exist

View File

@ -19,8 +19,7 @@ class WebDAVTest(unittest.TestCase):
propfind = webdavlib.WebDAVPROPFIND(resource, propfind = webdavlib.WebDAVPROPFIND(resource,
["{DAV:}principal-collection-set"], ["{DAV:}principal-collection-set"],
0) 0)
propfind.xpath_namespace = { "D": "DAV:" } propfind.xpath_namespace = { "D": "DAV:" } self.client.execute(propfind)
self.client.execute(propfind)
self.assertEquals(propfind.response["status"], 207) self.assertEquals(propfind.response["status"], 207)
nodes = propfind.xpath_evaluate('/D:multistatus/D:response/D:propstat/D:prop/D:principal-collection-set/D:href', nodes = propfind.xpath_evaluate('/D:multistatus/D:response/D:propstat/D:prop/D:principal-collection-set/D:href',
None) None)

View File

@ -78,8 +78,11 @@
us = [activeUser userSettings]; us = [activeUser userSettings];
moduleSettings = [us objectForKey: module]; moduleSettings = [us objectForKey: module];
if (!moduleSettings) if (!moduleSettings)
moduleSettings = [NSMutableDictionary dictionary]; {
[us setObject: moduleSettings forKey: module]; moduleSettings = [NSMutableDictionary new];
[us setObject: moduleSettings forKey: module];
[moduleSettings release];
}
contextIsSetup = YES; contextIsSetup = YES;
} }
} }
@ -149,44 +152,6 @@
return email; return email;
} }
- (NSArray *) _responseForResults: (NSArray *) results
{
NSEnumerator *contacts;
NSString *email, *info;
NSDictionary *contact;
NSMutableArray *formattedContacts;
NSMutableDictionary *formattedContact;
formattedContacts = [NSMutableArray arrayWithCapacity: [results count]];
if ([results count] > 0)
{
contacts = [results objectEnumerator];
contact = [contacts nextObject];
while (contact)
{
email = [contact objectForKey: @"c_email"];
if ([email length])
{
formattedContact = [NSMutableDictionary dictionary];
[formattedContact setObject: [contact objectForKey: @"c_uid"]
forKey: @"uid"];
[formattedContact setObject: [contact objectForKey: @"cn"]
forKey: @"name"];
[formattedContact setObject: email
forKey: @"email"];
info = [contact objectForKey: @"c_info"];
if (info != nil)
[formattedContact setObject: info
forKey: @"contactInfo"];
[formattedContacts addObject: formattedContact];
}
contact = [contacts nextObject];
}
}
return formattedContacts;
}
- (id <WOActionResults>) allContactSearchAction - (id <WOActionResults>) allContactSearchAction
{ {
id <WOActionResults> result; id <WOActionResults> result;
@ -196,7 +161,7 @@
NSArray *folders, *contacts, *descriptors, *sortedContacts; NSArray *folders, *contacts, *descriptors, *sortedContacts;
NSMutableArray *sortedFolders; NSMutableArray *sortedFolders;
NSMutableDictionary *contact, *uniqueContacts; NSMutableDictionary *contact, *uniqueContacts;
unsigned int i, j; unsigned int i, j, max;
NSSortDescriptor *commonNameDescriptor; NSSortDescriptor *commonNameDescriptor;
BOOL excludeGroups, excludeLists; BOOL excludeGroups, excludeLists;
@ -217,9 +182,10 @@
else else
[localException raise]; [localException raise];
NS_ENDHANDLER; NS_ENDHANDLER;
sortedFolders = [NSMutableArray arrayWithCapacity: [folders count]]; max = [folders count];
sortedFolders = [NSMutableArray arrayWithCapacity: max];
uniqueContacts = [NSMutableDictionary dictionary]; uniqueContacts = [NSMutableDictionary dictionary];
for (i = 0; i < [folders count]; i++) for (i = 0; i < max; i++)
{ {
folder = [folders objectAtIndex: i]; folder = [folders objectAtIndex: i];
/* We first search in LDAP folders (in case of duplicated entries in GCS folders) */ /* We first search in LDAP folders (in case of duplicated entries in GCS folders) */
@ -228,7 +194,7 @@
else else
[sortedFolders addObject: folder]; [sortedFolders addObject: folder];
} }
for (i = 0; i < [sortedFolders count]; i++) for (i = 0; i < max; i++)
{ {
folder = [sortedFolders objectAtIndex: i]; folder = [sortedFolders objectAtIndex: i];
//NSLog(@" Address book: %@ (%@)", [folder displayName], [folder class]); //NSLog(@" Address book: %@ (%@)", [folder displayName], [folder class]);
@ -245,8 +211,8 @@
&& [uniqueContacts objectForKey: mail] == nil && [uniqueContacts objectForKey: mail] == nil
&& !(excludeGroups && [contact objectForKey: @"isGroup"])) && !(excludeGroups && [contact objectForKey: @"isGroup"]))
[uniqueContacts setObject: contact forKey: mail]; [uniqueContacts setObject: contact forKey: mail];
else if (!excludeLists else if (!excludeLists && [[contact objectForKey: @"c_component"]
&& [[contact objectForKey: @"c_name"] hasSuffix: @".vlf"]) isEqualToString: @"vlist"])
{ {
[contact setObject: [folder nameInContainer] [contact setObject: [folder nameInContainer]
forKey: @"container"]; forKey: @"container"];
@ -254,14 +220,16 @@
forKey: [contact objectForKey: @"c_name"]]; forKey: [contact objectForKey: @"c_name"]];
} }
} }
} }
if ([uniqueContacts count] > 0) if ([uniqueContacts count] > 0)
{ {
// Sort the contacts by display name // Sort the contacts by display name
commonNameDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"c_cn" commonNameDescriptor = [[NSSortDescriptor alloc] initWithKey: @"c_cn"
ascending:YES] autorelease]; ascending:YES];
descriptors = [NSArray arrayWithObjects: commonNameDescriptor, nil]; descriptors = [NSArray arrayWithObjects: commonNameDescriptor, nil];
sortedContacts = [[uniqueContacts allValues] sortedArrayUsingDescriptors: descriptors]; [commonNameDescriptor release];
sortedContacts = [[uniqueContacts allValues]
sortedArrayUsingDescriptors: descriptors];
} }
else else
sortedContacts = [NSArray array]; sortedContacts = [NSArray array];
@ -269,7 +237,7 @@
sortedContacts, @"contacts", sortedContacts, @"contacts",
nil]; nil];
result = [self responseWithStatus: 200]; result = [self responseWithStatus: 200];
[(WOResponse*)result appendContentString: [data jsonRepresentation]]; [(WOResponse*) result appendContentString: [data jsonRepresentation]];
} }
else else
result = [NSException exceptionWithHTTPStatus: 400 result = [NSException exceptionWithHTTPStatus: 400
@ -278,36 +246,6 @@
return result; return result;
} }
- (id <WOActionResults>) contactSearchAction
{
NSDictionary *data;
NSArray *contacts;
NSString *searchText, *domain;
id <WOActionResults> result;
SOGoUserManager *um;
searchText = [self queryParameterForKey: @"search"];
if ([searchText length] > 0)
{
um = [SOGoUserManager sharedUserManager];
domain = [[context activeUser] domain];
contacts
= [self _responseForResults: [um fetchContactsMatching: searchText
inDomain: domain]];
data = [NSDictionary dictionaryWithObjectsAndKeys:
searchText, @"searchText",
contacts, @"contacts",
nil];
result = [self responseWithStatus: 200];
[(WOResponse*)result appendContentString: [data jsonRepresentation]];
}
else
result = [NSException exceptionWithHTTPStatus: 400
reason: @"missing 'search' parameter"];
return result;
}
- (NSArray *) _subFoldersFromFolder: (SOGoParentFolder *) parentFolder - (NSArray *) _subFoldersFromFolder: (SOGoParentFolder *) parentFolder
{ {
NSMutableArray *folders; NSMutableArray *folders;
@ -375,8 +313,7 @@
- (NSString *) currentContactFolderId - (NSString *) currentContactFolderId
{ {
return [NSString stringWithFormat: @"/%@", return [NSString stringWithFormat: @"/%@", [currentFolder nameInContainer]];
[currentFolder nameInContainer]];
} }
- (NSString *) currentContactFolderName - (NSString *) currentContactFolderName
@ -391,7 +328,8 @@
- (NSString *) currentContactFolderClass - (NSString *) currentContactFolderClass
{ {
return ([currentFolder isKindOfClass: [SOGoContactSourceFolder class]]? @"remote" : @"local"); return ([currentFolder isKindOfClass: [SOGoContactSourceFolder class]]
? @"remote" : @"local");
} }
- (NSString *) verticalDragHandleStyle - (NSString *) verticalDragHandleStyle
@ -401,7 +339,8 @@
[self _setupContext]; [self _setupContext];
vertical = [moduleSettings objectForKey: @"DragHandleVertical"]; vertical = [moduleSettings objectForKey: @"DragHandleVertical"];
return ((vertical && [vertical intValue] > 0) ? (id)[vertical stringByAppendingFormat: @"px"] : nil); return ((vertical && [vertical intValue] > 0)
? (id)[vertical stringByAppendingFormat: @"px"] : nil);
} }
- (NSString *) horizontalDragHandleStyle - (NSString *) horizontalDragHandleStyle
@ -411,7 +350,8 @@
[self _setupContext]; [self _setupContext];
horizontal = [moduleSettings objectForKey: @"DragHandleHorizontal"]; horizontal = [moduleSettings objectForKey: @"DragHandleHorizontal"];
return ((horizontal && [horizontal intValue] > 0) ? (id)[horizontal stringByAppendingFormat: @"px"] : nil); return ((horizontal && [horizontal intValue] > 0)
? (id)[horizontal stringByAppendingFormat: @"px"] : nil);
} }
- (NSString *) contactsListContentStyle - (NSString *) contactsListContentStyle
@ -421,7 +361,8 @@
[self _setupContext]; [self _setupContext];
height = [moduleSettings objectForKey: @"DragHandleVertical"]; height = [moduleSettings objectForKey: @"DragHandleVertical"];
return ((height && [height intValue] > 0) ? [NSString stringWithFormat: @"%ipx", ([height intValue] - 27)] : nil); return ((height && [height intValue] > 0)
? [NSString stringWithFormat: @"%ipx", ([height intValue] - 27)] : nil);
} }
- (WOResponse *) saveDragHandleStateAction - (WOResponse *) saveDragHandleStateAction

View File

@ -143,29 +143,23 @@
- (WOResponse *) propertiesAction - (WOResponse *) propertiesAction
{ {
NSArray *references;
NSMutableArray *data; NSMutableArray *data;
NGVCardReference *card; NGVCardReference *card;
WOResponse *rc; int count, max;
int i, count;
data = [NSMutableArray array]; list = [[self clientObject] vList];
co = [self clientObject]; references = [list cardReferences];
list = [co vList]; max = [references count];
data = [NSMutableArray arrayWithCapacity: max];
count = [[list cardReferences] count]; for (count = 0; count < max; count++)
for (i = 0; i < count; i++)
{ {
card = [[list cardReferences] objectAtIndex: i]; card = [references objectAtIndex: count];
[data addObject: [NSArray arrayWithObjects: [card reference], [card fn], [data addObject: [NSArray arrayWithObjects: [card reference],
[card email], nil]]; [card fn], [card email], nil]];
} }
rc = [context response]; return [self responseWithStatus: 200 andJSONRepresentation: data];
[rc setHeader: @"text/plain; charset=utf-8"
forKey: @"content-type"];
[rc appendContentString: [data jsonRepresentation]];
return rc;
} }
@end @end

View File

@ -23,11 +23,6 @@
pageName = "UIxContactFoldersView"; pageName = "UIxContactFoldersView";
actionName = "mailerContacts"; actionName = "mailerContacts";
}; };
contactSearch = {
protectedBy = "<public>";
pageName = "UIxContactFoldersView";
actionName = "contactSearch";
};
allContactSearch = { allContactSearch = {
protectedBy = "<public>"; protectedBy = "<public>";
pageName = "UIxContactFoldersView"; pageName = "UIxContactFoldersView";

View File

@ -4,7 +4,6 @@ var OwnerLogin = "";
var resultsDiv; var resultsDiv;
var address; var address;
var awaitingFreeBusyRequests = new Array();
var additionalDays = 2; var additionalDays = 2;
var isAllDay = parent$("isAllDay").checked + 0; var isAllDay = parent$("isAllDay").checked + 0;
@ -13,8 +12,6 @@ var displayEndHour = 23;
var attendeesEditor = { var attendeesEditor = {
delay: 500, delay: 500,
delayedSearch: false,
currentField: null,
selectedIndex: -1 selectedIndex: -1
}; };
@ -32,67 +29,135 @@ function handleAllDay () {
/* address completion */ /* address completion */
function resolveListAttendees(input, append) {
var urlstr = (UserFolderURL
+ "Contacts/"
+ escape(input.container) + "/"
+ escape(input.cname) + "/properties");
triggerAjaxRequest(urlstr, resolveListAttendeesCallback,
{ "input": input, "append": append });
}
function resolveListAttendeesCallback(http) {
if (http.readyState == 4 && http.status == 200) {
var input = http.callbackData["input"];
var append = http.callbackData["append"];
var contacts = http.responseText.evalJSON(true);
for (var i = 0; i < contacts.length; i++) {
var contact = contacts[i];
var fullName = contact[1];
if (fullName && fullName.length > 0) {
fullName += " <" + contact[2] + ">";
}
else {
fullName = contact[2];
}
input.uid = null;
input.cname = null;
input.container = null;
input.isList = false;
input.value = contact[2];
input.confirmedValue = null;
input.hasfreebusy = false;
input.modified = true;
// input.focussed = true;
// input.activate();
input.checkAfterLookup = true;
performSearch(input);
if (i < (contacts.length - 1)) {
var nextRow = newAttendee(input.parentNode.parentNode);
input = nextRow.down("input");
} else if (append) {
var row = input.parentNode.parentNode;
var tBody = row.parentNode;
if (row.rowIndex == (tBody.rows.length - 3)) {
if (input.selectText) {
input.selectText(0, 0);
} else if (input.createTextRange) {
input.createTextRange().moveStart();
}
newAttendee();
} else {
var nextRow = tBody.rows[row.rowIndex + 1];
var input = nextRow.down("input");
input.selectText(0, input.value.length);
input.focussed = true;
}
} else {
if (input.selectText) {
input.selectText(0, 0);
} else if (input.createTextRange) {
input.createTextRange().moveStart();
}
input.blur();
}
}
}
}
function onContactKeydown(event) { function onContactKeydown(event) {
if (event.ctrlKey || event.metaKey) { if (event.ctrlKey || event.metaKey) {
this.focussed = true; this.focussed = true;
return; return;
} }
if (event.keyCode == 9) { // Tab if (event.keyCode == 9 || event.keyCode == 13) { // Tab
preventDefault(event); preventDefault(event);
if (this.confirmedValue) if (this.confirmedValue)
this.value = this.confirmedValue; this.value = this.confirmedValue;
this.hasfreebusy = false; this.hasfreebusy = false;
var row = $(this).up("tr").next(); var row = $(this).up("tr").next();
this.blur(); // triggers checkAttendee function call if (this.isList) {
var input = row.down("input"); resolveListAttendees(this, true);
if (input) { event.stop();
input.focussed = true; } else {
input.activate(); checkAttendee(this);
// this.blur(); // triggers checkAttendee function call
var input = row.down("input");
if (input) {
input.focussed = true;
input.activate();
}
else
newAttendee();
} }
else
newAttendee(null);
} }
else if (event.keyCode == 0 else if (event.keyCode == 0
|| event.keyCode == 8 // Backspace || event.keyCode == 8 // Backspace
|| event.keyCode == 32 // Space || event.keyCode == 32 // Space
|| event.keyCode > 47) { || event.keyCode > 47) {
this.setAttribute("modified", "1"); this.modified = true;
this.confirmedValue = null; this.confirmedValue = null;
this.cname = null;
this.uid = null; this.uid = null;
this.container = null;
this.hasfreebusy = false; this.hasfreebusy = false;
attendeesEditor.currentField = this; if (this.searchTimeout) {
if (this.value.length > 0 && !attendeesEditor.delayedSearch) { window.clearTimeout(this.searchTimeout);
attendeesEditor.delayedSearch = true; }
setTimeout("performSearch()", attendeesEditor.delay); if (this.value.length > 0) {
var thisInput = this;
this.searchTimeout = setTimeout(function()
{performSearch(thisInput);
thisInput = null;},
attendeesEditor.delay);
} }
else if (this.value.length == 0) { else if (this.value.length == 0) {
if (document.currentPopupMenu) if (document.currentPopupMenu)
hideMenu(document.currentPopupMenu); hideMenu(document.currentPopupMenu);
} }
} }
else if (event.keyCode == 13) {
preventDefault(event);
if (this.confirmedValue)
this.value = this.confirmedValue;
$(this).selectText(0, this.value.length);
if (document.currentPopupMenu)
hideMenu(document.currentPopupMenu);
attendeesEditor.selectedIndex = -1;
if (this.uid) {
this.hasfreebusy = false;
this.writeAttribute("modified", "1");
this.blur(); // triggers checkAttendee function call
}
}
else if ($('attendeesMenu').getStyle('visibility') == 'visible') { else if ($('attendeesMenu').getStyle('visibility') == 'visible') {
attendeesEditor.currentField = this;
if (event.keyCode == Event.KEY_UP) { // Up arrow if (event.keyCode == Event.KEY_UP) { // Up arrow
if (attendeesEditor.selectedIndex > 0) { if (attendeesEditor.selectedIndex > 0) {
var attendees = $('attendeesMenu').select("li"); var attendees = $('attendeesMenu').select("li");
attendees[attendeesEditor.selectedIndex--].removeClassName("selected"); attendees[attendeesEditor.selectedIndex--].removeClassName("selected");
attendees[attendeesEditor.selectedIndex].addClassName("selected"); var attendee = attendees[attendeesEditor.selectedIndex];
this.value = this.confirmedValue = attendees[attendeesEditor.selectedIndex].readAttribute("address"); attendee.addClassName("selected");
this.uid = attendees[attendeesEditor.selectedIndex].uid; this.value = this.confirmedValue = attendee.address;
this.uid = attendee.uid;
this.isList = attendee.isList;
this.cname = attendee.cname;
this.container = attendee.container;
} }
} }
else if (event.keyCode == Event.KEY_DOWN) { // Down arrow else if (event.keyCode == Event.KEY_DOWN) { // Down arrow
@ -101,30 +166,27 @@ function onContactKeydown(event) {
if (attendeesEditor.selectedIndex >= 0) if (attendeesEditor.selectedIndex >= 0)
attendees[attendeesEditor.selectedIndex].removeClassName("selected"); attendees[attendeesEditor.selectedIndex].removeClassName("selected");
attendeesEditor.selectedIndex++; attendeesEditor.selectedIndex++;
attendees[attendeesEditor.selectedIndex].addClassName("selected"); var attendee = attendees[attendeesEditor.selectedIndex];
this.value = this.confirmedValue = attendees[attendeesEditor.selectedIndex].readAttribute("address"); attendee.addClassName("selected");
this.uid = attendees[attendeesEditor.selectedIndex].uid; this.value = this.confirmedValue = attendee.address;
this.isList = attendee.isList;
this.uid = attendee.uid;
this.cname = attendee.cname;
this.container = attendee.container;
} }
} }
} }
} }
function performSearch() { function performSearch(input) {
// Perform address completion // Perform address completion
if (attendeesEditor.currentField) { if (input.value.trim().length > 0) {
if (document.contactLookupAjaxRequest) { var urlstr = (UserFolderURL
// Abort any pending request + "Contacts/allContactSearch?excludeGroups=1&search="
document.contactLookupAjaxRequest.aborted = true; + escape(input.value));
document.contactLookupAjaxRequest.abort(); triggerAjaxRequest(urlstr, performSearchCallback, input);
}
if (attendeesEditor.currentField.value.trim().length > 0) {
var urlstr = ( UserFolderURL + "Contacts/contactSearch?search="
+ escape(attendeesEditor.currentField.value) );
document.contactLookupAjaxRequest =
triggerAjaxRequest(urlstr, performSearchCallback, attendeesEditor.currentField);
}
} }
attendeesEditor.delayedSearch = false; input.searchTimeout = null;
} }
function performSearchCallback(http) { function performSearchCallback(http) {
@ -139,6 +201,7 @@ function performSearchCallback(http) {
var data = http.responseText.evalJSON(true); var data = http.responseText.evalJSON(true);
if (data.contacts.length > 1) { if (data.contacts.length > 1) {
list.input = input;
$(list.childNodesWithTag("li")).each(function(item) { $(list.childNodesWithTag("li")).each(function(item) {
item.remove(); item.remove();
}); });
@ -146,25 +209,42 @@ function performSearchCallback(http) {
// Populate popup menu // Populate popup menu
for (var i = 0; i < data.contacts.length; i++) { for (var i = 0; i < data.contacts.length; i++) {
var contact = data.contacts[i]; var contact = data.contacts[i];
var completeEmail = contact["name"] + " <" + contact["email"] + ">"; var isList = (contact["c_component"] &&
var node = new Element('li', { 'address': completeEmail }); contact["c_component"] == "vlist");
var completeEmail = contact["c_cn"].trim();
if (!isList) {
if (completeEmail)
completeEmail += " <" + contact["c_mail"] + ">";
else
completeEmail = contact["c_mail"];
}
var node = createElement('li');
list.appendChild(node);
node.address = completeEmail;
log("node.address: " + node.address);
node.uid = contact["c_uid"];
node.isList = isList;
if (isList) {
node.cname = contact["c_name"];
node.container = contact["container"];
}
var matchPosition = completeEmail.toLowerCase().indexOf(data.searchText.toLowerCase()); var matchPosition = completeEmail.toLowerCase().indexOf(data.searchText.toLowerCase());
var matchBefore = completeEmail.substring(0, matchPosition); var matchBefore = completeEmail.substring(0, matchPosition);
var matchText = completeEmail.substring(matchPosition, matchPosition + data.searchText.length); var matchText = completeEmail.substring(matchPosition, matchPosition + data.searchText.length);
var matchAfter = completeEmail.substring(matchPosition + data.searchText.length); var matchAfter = completeEmail.substring(matchPosition + data.searchText.length);
list.appendChild(node);
node.uid = contact["uid"];
node.appendChild(document.createTextNode(matchBefore)); node.appendChild(document.createTextNode(matchBefore));
node.appendChild(new Element('strong').update(matchText)); node.appendChild(new Element('strong').update(matchText));
node.appendChild(document.createTextNode(matchAfter)); node.appendChild(document.createTextNode(matchAfter));
if (contact["contactInfo"]) if (contact["contactInfo"])
node.appendChild(document.createTextNode(" (" + contact["contactInfo"] + ")")); node.appendChild(document.createTextNode(" (" +
$(node).observe("mousedown", onAttendeeResultClick); contact["contactInfo"] + ")"));
node.observe("mousedown",
onAttendeeResultClick.bindAsEventListener(node));
} }
// Show popup menu // Show popup menu
var offsetScroll = Element.cumulativeScrollOffset(attendeesEditor.currentField); var offsetScroll = Element.cumulativeScrollOffset(input);
var offset = Element.cumulativeOffset(attendeesEditor.currentField); var offset = Element.cumulativeOffset(input);
var top = offset[1] - offsetScroll[1] + node.offsetHeight + 3; var top = offset[1] - offsetScroll[1] + node.offsetHeight + 3;
var height = 'auto'; var height = 'auto';
var heightDiff = window.height() - offset[1]; var heightDiff = window.height() - offset[1];
@ -190,52 +270,73 @@ function performSearchCallback(http) {
if (data.contacts.length == 1) { if (data.contacts.length == 1) {
// Single result // Single result
var contact = data.contacts[0]; var contact = data.contacts[0];
if (contact["uid"].length > 0) { input.uid = contact["c_uid"];
var row = $(input.parentNode.parentNode); var row = $(input.parentNode.parentNode);
input.uid = contact["uid"]; if (input.uid == OwnerLogin) {
if (input.uid == OwnerLogin) { row.removeAttribute("role");
row.removeAttribute("role"); row.setAttribute("partstat", "accepted");
row.setAttribute("partstat", "accepted"); row.addClassName("organizer-row");
row.addClassName("organizer-row"); row.removeClassName("attendee-row");
row.removeClassName("attendee-row"); row.isOrganizer = true;
row.isOrganizer = true; } else {
} else { row.removeAttribute("partstat");
row.removeAttribute("partstat"); row.setAttribute("role", "req-participant");
row.setAttribute("role", "req-participant"); row.addClassName("attendee-row");
row.addClassName("attendee-row"); row.removeClassName("organizer-row");
row.removeClassName("organizer-row"); row.isOrganizer = false;
row.isOrganizer = false;
}
} }
var completeEmail = contact["name"] + " <" + contact["email"] + ">"; var isList = (contact["c_component"] &&
if (contact["name"].substring(0, input.value.length).toUpperCase() contact["c_component"] == "vlist");
== input.value.toUpperCase()) if (isList) {
input.cname = contact["c_name"];
input.container = contact["container"];
}
var completeEmail = contact["c_cn"].trim();
if (!isList) {
if (completeEmail)
completeEmail += " <" + contact["c_mail"] + ">";
else
completeEmail = contact["c_mail"];
}
if ((input.value == contact["c_mail"])
|| (contact["c_cn"].substring(0, input.value.length).toUpperCase()
== input.value.toUpperCase())) {
input.value = completeEmail; input.value = completeEmail;
}
else else
// The result matches email address, not user name // The result matches email address, not user name
input.value += ' >> ' + completeEmail; input.value += ' >> ' + completeEmail;
input.isList = isList;
input.confirmedValue = completeEmail; input.confirmedValue = completeEmail;
var end = input.value.length; var end = input.value.length;
$(input).selectText(start, end); $(input).selectText(start, end);
attendeesEditor.selectedIndex = -1; attendeesEditor.selectedIndex = -1;
if (input.checkAfterLookup) {
input.checkAfterLookup = false;
input.modified = true;
input.hasfreebusy = false;
checkAttendee(input);
}
} }
} }
} }
else else
if (document.currentPopupMenu) if (document.currentPopupMenu)
hideMenu(document.currentPopupMenu); hideMenu(document.currentPopupMenu);
document.contactLookupAjaxRequest = null;
} }
} }
function onAttendeeResultClick(event) { function onAttendeeResultClick(event) {
if (attendeesEditor.currentField) { var input = this.parentNode.input;
attendeesEditor.currentField.uid = this.uid; input.uid = this.uid;
attendeesEditor.currentField.value = $(this).readAttribute("address"); input.cname = this.cname;
attendeesEditor.currentField.confirmedValue = attendeesEditor.currentField.value; input.container = this.container;
attendeesEditor.currentField.blur(); // triggers checkAttendee function call input.isList = this.isList;
} input.confirmedValue = input.value = this.address;
checkAttendee(input);
this.parentNode.input = null;
} }
function resetFreeBusyZone() { function resetFreeBusyZone() {
@ -331,13 +432,23 @@ function rotateAttendeeStatus(row) {
} }
} }
function newAttendee(event) { function onNewAttendeeClick(event) {
newAttendee();
event.stop();
}
function newAttendee(previousAttendee) {
var table = $("freeBusyAttendees"); var table = $("freeBusyAttendees");
var tbody = table.tBodies[0]; var tbody = table.tBodies[0];
var model = tbody.rows[tbody.rows.length - 1]; var model = tbody.rows[tbody.rows.length - 1];
var futureRow = tbody.rows[tbody.rows.length - 2]; var nextRowIndex = tbody.rows.length - 2;
if (previousAttendee) {
nextRowIndex = previousAttendee.rowIndex + 1;
}
var nextRow = tbody.rows[nextRowIndex];
var newRow = $(model.cloneNode(true)); var newRow = $(model.cloneNode(true));
tbody.insertBefore(newRow, futureRow); tbody.insertBefore(newRow, nextRow);
var result = newRow;
var statusTD = newRow.down(".attendeeStatus"); var statusTD = newRow.down(".attendeeStatus");
if (statusTD) { if (statusTD) {
@ -348,8 +459,8 @@ function newAttendee(event) {
$(newRow).removeClassName("attendeeModel"); $(newRow).removeClassName("attendeeModel");
var input = newRow.down("input"); var input = newRow.down("input");
input.observe("keydown", onContactKeydown); input.observe("keydown", onContactKeydown.bindAsEventListener(input));
input.observe("blur", checkAttendee); input.observe("blur", onInputBlur);
input.focussed = true; input.focussed = true;
input.activate(); input.activate();
@ -357,49 +468,64 @@ function newAttendee(event) {
table = $("freeBusyData"); table = $("freeBusyData");
tbody = table.tBodies[0]; tbody = table.tBodies[0];
model = tbody.rows[tbody.rows.length - 1]; model = tbody.rows[tbody.rows.length - 1];
futureRow = tbody.rows[tbody.rows.length - 2]; nextRow = tbody.rows[nextRowIndex];
newRow = $(model.cloneNode(true)); newRow = $(model.cloneNode(true));
tbody.insertBefore(newRow, futureRow); tbody.insertBefore(newRow, nextRow);
newRow.removeClassName("dataModel"); newRow.removeClassName("dataModel");
var attendeesDiv = $$('TABLE#freeBusy TD.freeBusyAttendees DIV').first(); var attendeesDiv = $$('TABLE#freeBusy TD.freeBusyAttendees DIV').first();
var dataDiv = $$('TABLE#freeBusy TD.freeBusyData DIV').first(); var dataDiv = $$('TABLE#freeBusy TD.freeBusyData DIV').first();
dataDiv.scrollTop = attendeesDiv.scrollTop; dataDiv.scrollTop = attendeesDiv.scrollTop;
return result;
} }
function checkAttendee() { // log ("checkAttendee"); function checkAttendee(input) {
if (document.currentPopupMenu) var row = $(input.parentNode.parentNode);
hideMenu(document.currentPopupMenu);
if (document.currentPopupMenu && !this.confirmedValue) {
// Hack for IE7; blur event is triggered on input field when
// selecting a menu item
var visible = $(document.currentPopupMenu).getStyle('visibility') != 'hidden';
if (visible)
return;
}
this.focussed = false;
var row = this.parentNode.parentNode;
var tbody = row.parentNode; var tbody = row.parentNode;
if (tbody && this.value.trim().length == 0) { if (tbody && input.value.trim().length == 0) {
var dataTable = $("freeBusyData").tBodies[0]; var dataTable = $("freeBusyData").tBodies[0];
var dataRow = dataTable.rows[row.sectionRowIndex]; var dataRow = dataTable.rows[row.sectionRowIndex];
tbody.removeChild(row); tbody.removeChild(row);
dataTable.removeChild(dataRow); dataTable.removeChild(dataRow);
} }
else if (this.readAttribute("modified") == "1") { else if (input.modified) {
if (!this.hasfreebusy) { if (!row.hasClassName("needs-action")) {
if (this.uid && this.confirmedValue) row.addClassName("needs-action");
this.value = this.confirmedValue; row.removeClassName("declined");
displayFreeBusyForNode(this); row.removeClassName("accepted");
this.hasfreebusy = true;
} }
this.setAttribute("modified", "0"); if (!input.hasfreebusy) {
if (input.uid && input.confirmedValue) {
input.value = input.confirmedValue;
}
displayFreeBusyForNode(input);
input.hasfreebusy = true;
}
input.modified = false;
}
}
function onInputBlur(event) {
if (document.currentPopupMenu && !this.confirmedValue) {
// Hack for IE7; blur event is triggered on input field when
// selecting a menu item
var visible = $(document.currentPopupMenu).getStyle('visibility') != 'hidden';
if (visible) {
log("XXX we return");
return;
}
}
if (document.currentPopupMenu)
hideMenu(document.currentPopupMenu);
if (this.isList) {
resolveListAttendees(this, false);
} else {
checkAttendee(this);
} }
attendeesEditor.currentField = null;
} }
function displayFreeBusyForNode(input) { function displayFreeBusyForNode(input) {
@ -407,36 +533,33 @@ function displayFreeBusyForNode(input) {
var nodes = $("freeBusyData").tBodies[0].rows[rowIndex].cells; var nodes = $("freeBusyData").tBodies[0].rows[rowIndex].cells;
log ("displayFreeBusyForNode index " + rowIndex + " (" + nodes.length + " cells)"); log ("displayFreeBusyForNode index " + rowIndex + " (" + nodes.length + " cells)");
if (input.uid) { if (input.uid) {
if (document.contactFreeBusyAjaxRequest) { log ("busy -- delay " + rowIndex); for (var i = 0; i < nodes.length; i++) {
awaitingFreeBusyRequests.push(input); } var node = $(nodes[i]);
else { node.removeClassName("noFreeBusy");
for (var i = 0; i < nodes.length; i++) { while (node.firstChild) {
$(nodes[i]).removeClassName("noFreeBusy"); node.removeChild(node.firstChild);
$(nodes[i]).innerHTML = ('<span class="freeBusyZoneElement"></span>' }
+ '<span class="freeBusyZoneElement"></span>' for (var j = 0; j < 4; j++) {
+ '<span class="freeBusyZoneElement"></span>' createElement("span", null, "freeBusyZoneElement",
+ '<span class="freeBusyZoneElement"></span>'); null, null, node);
} }
// if (document.contactFreeBusyAjaxRequest) {
// // Abort any pending request
// document.contactFreeBusyAjaxRequest.aborted = true;
// document.contactFreeBusyAjaxRequest.abort();
// }
var sd = $('startTime_date').valueAsShortDateString();
var ed = $('endTime_date').valueAsShortDateString();
var urlstr = ( UserFolderURL + "../" + input.uid
+ "/freebusy.ifb/ajaxRead?"
+ "sday=" + sd + "&eday=" + ed + "&additional=" +
additionalDays ); log (urlstr);
document.contactFreeBusyAjaxRequest
= triggerAjaxRequest(urlstr,
updateFreeBusyDataCallback,
input);
} }
var sd = $('startTime_date').valueAsShortDateString();
var ed = $('endTime_date').valueAsShortDateString();
var urlstr = (UserFolderURL + "../" + input.uid
+ "/freebusy.ifb/ajaxRead?"
+ "sday=" + sd + "&eday=" + ed + "&additional=" +
additionalDays);
triggerAjaxRequest(urlstr,
updateFreeBusyDataCallback,
input);
} else { } else {
for (var i = 0; i < nodes.length; i++) { for (var i = 0; i < nodes.length; i++) {
$(nodes[i]).addClassName("noFreeBusy"); var node = $(nodes[i]);
$(nodes[i]).update(); node.addClassName("noFreeBusy");
while (node.firstChild) {
node.removeChild(node.firstChild);
}
} }
} }
} }
@ -467,15 +590,12 @@ function updateFreeBusyDataCallback(http) {
var slots = http.responseText.split(","); var slots = http.responseText.split(",");
var rowIndex = input.parentNode.parentNode.sectionRowIndex; var rowIndex = input.parentNode.parentNode.sectionRowIndex;
var nodes = $("freeBusyData").tBodies[0].rows[rowIndex].cells; var nodes = $("freeBusyData").tBodies[0].rows[rowIndex].cells;
log ("received " + slots.length + " slots for " + rowIndex + " with " + nodes.length + " cells"); // log ("received " + slots.length + " slots for " + rowIndex + " with " + nodes.length + " cells");
for (var i = 0; i < slots.length; i++) { for (var i = 0; i < slots.length; i++) {
if (slots[i] != '0') if (slots[i] != '0')
setSlot(nodes, i, slots[i]); setSlot(nodes, i, slots[i]);
} }
} }
document.contactFreeBusyAjaxRequest = null;
if (awaitingFreeBusyRequests.length > 0) { log ("1");
displayFreeBusyForNode(awaitingFreeBusyRequests.shift()); }
} }
} }
@ -484,8 +604,8 @@ function resetAllFreeBusys() {
var inputs = table.getElementsByTagName("input"); var inputs = table.getElementsByTagName("input");
for (var i = 0; i < inputs.length - 1; i++) { for (var i = 0; i < inputs.length - 1; i++) {
var currentInput = inputs[i]; log ("reset fb " + currentInput.uid); var currentInput = inputs[i];
currentInput.hasfreebusy = false; log ("2"); currentInput.hasfreebusy = false;
displayFreeBusyForNode(currentInput); displayFreeBusyForNode(currentInput);
} }
} }
@ -626,8 +746,8 @@ function onEditorOkClick(event) {
if (inputs[i].uid) if (inputs[i].uid)
uid = inputs[i].uid; uid = inputs[i].uid;
if (!(name && name.length > 0)) if (!(name && name.length > 0))
if (inputs[i].uid) if (uid.length > 0)
name = inputs[i].uid; name = uid;
else else
name = email; name = email;
var attendee = attendees["email"]; var attendee = attendees["email"];
@ -791,7 +911,6 @@ function prepareAttendees() {
attendees.keys().each(function(atKey) { attendees.keys().each(function(atKey) {
var attendee = attendees.get(atKey); var attendee = attendees.get(atKey);
// attendee = $H(attendee);
var row = $(modelAttendee.cloneNode(true)); var row = $(modelAttendee.cloneNode(true));
tbodyAttendees.insertBefore(row, newAttendeeRow); tbodyAttendees.insertBefore(row, newAttendeeRow);
row.removeClassName("attendeeModel"); row.removeClassName("attendeeModel");
@ -822,13 +941,14 @@ function prepareAttendees() {
value = ""; value = "";
value += "<" + attendee["email"] + ">"; value += "<" + attendee["email"] + ">";
input.value = value; input.value = value;
if (uid) input.uid = attendee["uid"];
input.uid = uid; input.cname = attendee["cname"];
input.setAttribute("name", ""); input.setAttribute("name", "");
input.setAttribute("modified", "0"); input.modified = false;
input.observe("blur", checkAttendee); input.observe("blur", onInputBlur);
input.observe("keydown", onContactKeydown); input.observe("keydown", onContactKeydown.bindAsEventListener(input)
);
row = $(modelData.cloneNode(true)); row = $(modelData.cloneNode(true));
tbodyData.insertBefore(row, newDataRow); tbodyData.insertBefore(row, newDataRow);
row.removeClassName("dataModel"); row.removeClassName("dataModel");
@ -838,7 +958,7 @@ function prepareAttendees() {
// Activate "Add attendee" button // Activate "Add attendee" button
var links = tableAttendees.select("TR.futureAttendee TD A"); var links = tableAttendees.select("TR.futureAttendee TD A");
links.first().observe("click", newAttendee); links.first().observe("click", onNewAttendeeClick);
} }
function onWindowResize(event) { function onWindowResize(event) {
@ -934,7 +1054,7 @@ function initTimeWidgets(widgets) {
function onAdjustTime(event) { function onAdjustTime(event) {
var endDate = window.getEndDate(); var endDate = window.getEndDate();
var startDate = window.getStartDate(); var startDate = window.getStartDate();
if ($(this).readAttribute("id").startsWith("start")) { if (this.id.startsWith("start")) {
// Start date was changed // Start date was changed
var delta = window.getShadowStartDate().valueOf() - var delta = window.getShadowStartDate().valueOf() -
startDate.valueOf(); startDate.valueOf();