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>
* 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
the differenciate between the type of users in the list of
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 (working copy)
@@ -157,6 +157,8 @@
@@ -157,6 +157,9 @@
"{urn:ietf:params:xml:ns:caldav}supported-calendar-data" =
davSupportedCalendarDataTypes;
"{urn:ietf:params:xml:ns:caldav}calendar-description" = davDescription;
+ "{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-calendar-transp" = davScheduleCalendarTransparency;
/* CardDAV */
"{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/}notifications-URL" = davNotificationsURL;
"{http://calendarserver.org/ns/}getctag" = davCollectionTag;

View File

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

View File

@ -46,13 +46,20 @@
#import "SOGoContactGCSFolder.h"
#define folderListingFields [NSArray arrayWithObjects: @"c_name", @"c_cn", \
@"c_givenname", @"c_sn", @"c_screenname", \
@"c_o", @"c_mail", @"c_telephonenumber", \
nil]
static NSArray *folderListingFields = nil;
@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
{
CardGroup *cardEntry;

View File

@ -37,6 +37,45 @@ def fetchUserInfo(login):
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):
def setUp(self):
self.client = webdavlib.WebDAVClient(hostname, port,
@ -159,7 +198,7 @@ class CalDAVITIPDelegationTest(unittest.TestCase):
% (email,
compared_attendees[email], attendees[email]))
def testInvitationDelegation(self):
def xtestInvitationDelegation(self):
""" invitation delegation """
# the invitation must not exist

View File

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

View File

@ -78,8 +78,11 @@
us = [activeUser userSettings];
moduleSettings = [us objectForKey: module];
if (!moduleSettings)
moduleSettings = [NSMutableDictionary dictionary];
[us setObject: moduleSettings forKey: module];
{
moduleSettings = [NSMutableDictionary new];
[us setObject: moduleSettings forKey: module];
[moduleSettings release];
}
contextIsSetup = YES;
}
}
@ -149,44 +152,6 @@
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> result;
@ -196,7 +161,7 @@
NSArray *folders, *contacts, *descriptors, *sortedContacts;
NSMutableArray *sortedFolders;
NSMutableDictionary *contact, *uniqueContacts;
unsigned int i, j;
unsigned int i, j, max;
NSSortDescriptor *commonNameDescriptor;
BOOL excludeGroups, excludeLists;
@ -217,9 +182,10 @@
else
[localException raise];
NS_ENDHANDLER;
sortedFolders = [NSMutableArray arrayWithCapacity: [folders count]];
max = [folders count];
sortedFolders = [NSMutableArray arrayWithCapacity: max];
uniqueContacts = [NSMutableDictionary dictionary];
for (i = 0; i < [folders count]; i++)
for (i = 0; i < max; i++)
{
folder = [folders objectAtIndex: i];
/* We first search in LDAP folders (in case of duplicated entries in GCS folders) */
@ -228,7 +194,7 @@
else
[sortedFolders addObject: folder];
}
for (i = 0; i < [sortedFolders count]; i++)
for (i = 0; i < max; i++)
{
folder = [sortedFolders objectAtIndex: i];
//NSLog(@" Address book: %@ (%@)", [folder displayName], [folder class]);
@ -245,8 +211,8 @@
&& [uniqueContacts objectForKey: mail] == nil
&& !(excludeGroups && [contact objectForKey: @"isGroup"]))
[uniqueContacts setObject: contact forKey: mail];
else if (!excludeLists
&& [[contact objectForKey: @"c_name"] hasSuffix: @".vlf"])
else if (!excludeLists && [[contact objectForKey: @"c_component"]
isEqualToString: @"vlist"])
{
[contact setObject: [folder nameInContainer]
forKey: @"container"];
@ -254,14 +220,16 @@
forKey: [contact objectForKey: @"c_name"]];
}
}
}
}
if ([uniqueContacts count] > 0)
{
// Sort the contacts by display name
commonNameDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"c_cn"
ascending:YES] autorelease];
commonNameDescriptor = [[NSSortDescriptor alloc] initWithKey: @"c_cn"
ascending:YES];
descriptors = [NSArray arrayWithObjects: commonNameDescriptor, nil];
sortedContacts = [[uniqueContacts allValues] sortedArrayUsingDescriptors: descriptors];
[commonNameDescriptor release];
sortedContacts = [[uniqueContacts allValues]
sortedArrayUsingDescriptors: descriptors];
}
else
sortedContacts = [NSArray array];
@ -269,7 +237,7 @@
sortedContacts, @"contacts",
nil];
result = [self responseWithStatus: 200];
[(WOResponse*)result appendContentString: [data jsonRepresentation]];
[(WOResponse*) result appendContentString: [data jsonRepresentation]];
}
else
result = [NSException exceptionWithHTTPStatus: 400
@ -278,36 +246,6 @@
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
{
NSMutableArray *folders;
@ -375,8 +313,7 @@
- (NSString *) currentContactFolderId
{
return [NSString stringWithFormat: @"/%@",
[currentFolder nameInContainer]];
return [NSString stringWithFormat: @"/%@", [currentFolder nameInContainer]];
}
- (NSString *) currentContactFolderName
@ -391,7 +328,8 @@
- (NSString *) currentContactFolderClass
{
return ([currentFolder isKindOfClass: [SOGoContactSourceFolder class]]? @"remote" : @"local");
return ([currentFolder isKindOfClass: [SOGoContactSourceFolder class]]
? @"remote" : @"local");
}
- (NSString *) verticalDragHandleStyle
@ -401,7 +339,8 @@
[self _setupContext];
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
@ -411,7 +350,8 @@
[self _setupContext];
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
@ -421,7 +361,8 @@
[self _setupContext];
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

View File

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

View File

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

View File

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