From: wolfgang Date: Tue, 13 Nov 2007 22:42:23 +0000 (+0000) Subject: git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1263 d1b88da0-ebda-0310... X-Git-Url: https://err.no/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4e6eeb543b90ae4551e7dbc24a82eb608696e1a7;p=scalable-opengroupware.org git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1263 d1b88da0-ebda-0310-925b-ed51d893ca5b --- diff --git a/ChangeLog b/ChangeLog index f9a1ce43..f77b4a32 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,37 @@ +2007-11-13 Wolfgang Sourdeau + + * SoObjects/SOGo/iCalEntityObject+Utilities.m ([iCalEntityObject + -findParticipant:user]): new method based on the one removed from + SOGoCalendarComponent below. + + * SoObjects/SOGo/iCalEntityObject+Utilities.[hm]: new category + module for iCalEntityObject. + + * SoObjects/Appointments/SOGoCalendarComponent.m + ([-findParticipant:user]): removed method. + + * SoObjects/SOGo/SOGoContentObject.m ([SOGoContentObject + -setContentString:newContent]): new accessor method. + ([SOGoContentObject + -saveContentString:newContentbaseVersion:newBaseVersion]): invoke + -[self setContentString:]. + + * UI/MailPartViewers/UIxMailPartICalViewer.m + ([UIxMailPartICalViewer -authorativeEvent]): returns the most + up-to-date event. + ([-isLoggedInUserTheOrganizer]): make use of -[SOGoUser + hasEmail:]. + + * UI/MailPartViewers/UIxMailPartTextViewer.m ([NSString + -stringByConvertingCRLNToHTML]): fixed crashes due to overflows in + temporary buffer we are handing. + + * UI/Scheduler/UIxComponentEditor.m ([UIxComponentEditor + -setComponent:newComponent]): check that newComponent is non-nil + before replacing the default values. + ([UIxComponentEditor -calendarList]): privacy is already an ivar. + We don't need to refetch it. + 2007-11-12 Wolfgang Sourdeau * SoObjects/Mailer/SOGoDraftObject.m ([SOGoDraftObject @@ -16,7 +50,8 @@ ([UIxMailPartICalViewer -declineLink]) ([UIxMailPartICalViewer -tentativeLink]): removed useless methods. - * UI/MailPartViewers/UIxMailPartICalAction.m ([UIxMailPartICalAction -addToCalendarAction]) + * UI/MailPartViewers/UIxMailPartICalAction.m + ([UIxMailPartICalAction -addToCalendarAction]) ([UIxMailPartICalAction -deleteFromCalendarAction]): new stub methods. diff --git a/SOPE/NGCards/ChangeLog b/SOPE/NGCards/ChangeLog index 549ad5a2..ec0c0c54 100644 --- a/SOPE/NGCards/ChangeLog +++ b/SOPE/NGCards/ChangeLog @@ -1,3 +1,10 @@ +2007-11-12 Wolfgang Sourdeau + + * iCalTimeZonePeriod.m ([iCalTimeZonePeriod + -occurenceForDate:refDate]): added support for timezone periods + which do not contain a recurrence rule, such as the one provided + by iCal. + 2007-11-02 Wolfgang Sourdeau * iCalDateHolder.m ([iCalDateHolder -awakeAfterUsingSaxDecoder:]): diff --git a/SOPE/NGCards/iCalEntityObject.h b/SOPE/NGCards/iCalEntityObject.h index e58dc1e5..516a0d16 100644 --- a/SOPE/NGCards/iCalEntityObject.h +++ b/SOPE/NGCards/iCalEntityObject.h @@ -116,6 +116,9 @@ typedef enum - (NSArray *) alarms; - (BOOL) hasAlarms; +/* comparisons */ +- (NSComparisonResult) compare: (iCalEntityObject *) otherObject; + @end #endif /* __NGCards_iCalEntityObject_H__ */ diff --git a/SOPE/NGCards/iCalEntityObject.m b/SOPE/NGCards/iCalEntityObject.m index 39f2afce..53a51c1f 100644 --- a/SOPE/NGCards/iCalEntityObject.m +++ b/SOPE/NGCards/iCalEntityObject.m @@ -425,4 +425,27 @@ return nil; /* not found */ } +- (NSComparisonResult) _compareVersions: (iCalEntityObject *) otherObject +{ + NSComparisonResult result; + + result = [[self sequence] compare: [otherObject sequence]]; + if (result == NSOrderedSame) + result = [[self lastModified] compare: [otherObject lastModified]]; + + return result; +} + +- (NSComparisonResult) compare: (iCalEntityObject *) otherObject +{ + NSComparisonResult result; + + if ([[self uid] isEqualToString: [otherObject uid]]) + result = [self _compareVersions: otherObject]; + else + result = [[self created] compare: [otherObject created]]; + + return result; +} + @end /* iCalEntityObject */ diff --git a/SOPE/NGCards/iCalTimeZonePeriod.m b/SOPE/NGCards/iCalTimeZonePeriod.m index 29bd9bb1..dc1f611f 100644 --- a/SOPE/NGCards/iCalTimeZonePeriod.m +++ b/SOPE/NGCards/iCalTimeZonePeriod.m @@ -99,35 +99,48 @@ return dayOfWeek; } -- (NSCalendarDate *) occurenceForDate: (NSCalendarDate *) refDate; +- (NSCalendarDate *) _occurenceForDate: (NSCalendarDate *) refDate + byRRule: (iCalRecurrenceRule *) rrule { NSCalendarDate *tmpDate; - iCalRecurrenceRule *rrule; NSString *byDay; int dayOfWeek, dateDayOfWeek, offset, pos; - rrule = (iCalRecurrenceRule *) [self uniqueChildWithTag: @"rrule"]; byDay = [rrule namedValue: @"byday"]; dayOfWeek = [self dayOfWeekFromRruleDay: [rrule byDayMask]]; pos = [[byDay substringToIndex: 2] intValue]; if (!pos) pos = 1; - tmpDate = [NSCalendarDate - dateWithYear: [refDate yearOfCommonEra] - month: [[rrule namedValue: @"bymonth"] intValue] - day: 1 hour: 0 minute: 0 second: 0 - timeZone: [NSTimeZone timeZoneWithName: @"GMT"]]; + tmpDate = [NSCalendarDate dateWithYear: [refDate yearOfCommonEra] + month: [[rrule namedValue: @"bymonth"] intValue] + day: 1 hour: 0 minute: 0 second: 0 + timeZone: [NSTimeZone timeZoneWithName: @"GMT"]]; tmpDate = [tmpDate addYear: 0 month: ((pos > 0) ? 0 : 1) - day: 0 hour: 0 minute: 0 - second: -[self _secondsOfOffset: @"tzoffsetfrom"]]; + day: 0 hour: 0 minute: 0 + second: -[self _secondsOfOffset: @"tzoffsetfrom"]]; dateDayOfWeek = [tmpDate dayOfWeek]; offset = (dayOfWeek - dateDayOfWeek); if (pos > 0 && offset < 0) offset += 7; offset += (pos * 7); tmpDate = [tmpDate addYear: 0 month: 0 day: offset - hour: 0 minute: 0 second: 0]; + hour: 0 minute: 0 second: 0]; + + return tmpDate; +} + +- (NSCalendarDate *) occurenceForDate: (NSCalendarDate *) refDate; +{ + NSCalendarDate *tmpDate; + iCalRecurrenceRule *rrule; + + rrule = (iCalRecurrenceRule *) [self uniqueChildWithTag: @"rrule"]; + if ([rrule isVoid]) + tmpDate + = [(iCalDateTime *) [self uniqueChildWithTag: @"dtstart"] dateTime]; + else + tmpDate = [self _occurenceForDate: refDate byRRule: rrule]; return tmpDate; } diff --git a/SOPE/sope-patchset-r1546.diff b/SOPE/sope-patchset-r1546.diff index acec966d..e26b5934 100644 --- a/SOPE/sope-patchset-r1546.diff +++ b/SOPE/sope-patchset-r1546.diff @@ -426,6 +426,23 @@ Index: sope-mime/NGImap4/NGImap4ResponseParser.m - (NSException *)exceptionForFailedMatch:(unsigned char)_match got:(unsigned char)_avail { +Index: sope-mime/NGMail/NGSmtpClient.m +=================================================================== +--- sope-mime/NGMail/NGSmtpClient.m (révision 1546) ++++ sope-mime/NGMail/NGSmtpClient.m (copie de travail) +@@ -442,10 +442,10 @@ + [self->text flush]; + + if (self->isDebuggingEnabled) +- [NGTextErr writeFormat:@"C: data(%i bytes) ..\n", [_data bytes]]; ++ [NGTextErr writeFormat:@"C: data(%i bytes) ..\n", [_data length]]; + + [self->connection safeWriteBytes:[_data bytes] count:[_data length]]; +- [self->connection safeWriteBytes:".\r\n" count:3]; ++ [self->connection safeWriteBytes:"\r\n.\r\n" count:5]; + [self->connection flush]; + + reply = [self receiveReply]; Index: sope-mime/NGMime/NGMimeRFC822DateHeaderFieldParser.m =================================================================== --- sope-mime/NGMime/NGMimeRFC822DateHeaderFieldParser.m (révision 1546) diff --git a/SoObjects/Appointments/SOGoAppointmentObject.m b/SoObjects/Appointments/SOGoAppointmentObject.m index 7cc44e23..12f97b38 100644 --- a/SoObjects/Appointments/SOGoAppointmentObject.m +++ b/SoObjects/Appointments/SOGoAppointmentObject.m @@ -31,6 +31,7 @@ #import #import +#import #import #import #import @@ -252,36 +253,32 @@ uid = [self getUIDForICalPerson: organizer]; if (!uid) uid = [self ownerInContext: nil]; - if (uid) { - if (![storeUIDs containsObject:uid]) - [storeUIDs addObject:uid]; - [removedUIDs removeObject:uid]; - } + if (uid) + { + [storeUIDs addObjectUniquely: uid]; + [removedUIDs removeObject: uid]; + } /* organizer might have changed completely */ - if (oldApt && ([props containsObject: @"organizer"])) { - uid = [self getUIDForICalPerson:[oldApt organizer]]; - if (uid) { - if (![storeUIDs containsObject:uid]) { - if (![removedUIDs containsObject:uid]) { - [removedUIDs addObject:uid]; - } - } + if (oldApt && ([props containsObject: @"organizer"])) + { + uid = [self getUIDForICalPerson: [oldApt organizer]]; + if (uid && ![storeUIDs containsObject: uid]) + [removedUIDs addObjectUniquely: uid]; } - } - [self debugWithFormat:@"UID ops:\n store: %@\n remove: %@", + [self debugWithFormat: @"UID ops:\n store: %@\n remove: %@", storeUIDs, removedUIDs]; /* if time did change, all participants have to re-decide ... * ... exception from that rule: the organizer */ - if (oldApt != nil && - ([props containsObject: @"startDate"] || - [props containsObject: @"endDate"] || - [props containsObject: @"duration"])) + if (oldApt + && ([props containsObject: @"startDate"] + || [props containsObject: @"endDate"] + || [props containsObject: @"duration"])) { NSArray *ps; unsigned i, count; @@ -312,23 +309,34 @@ if ([self sendEMailNotifications] && [self _aptIsStillRelevant: newApt]) { + iCalEvent *requestApt; + + requestApt = [newApt copy]; + [(iCalCalendar *) [requestApt parent] setMethod: @"request"]; attendees = [NSMutableArray arrayWithArray: [changes insertedAttendees]]; [attendees removePerson: organizer]; [self sendEMailUsingTemplateNamed: @"Invitation" forOldObject: nil - andNewObject: newApt + andNewObject: requestApt toAttendees: attendees]; + [requestApt release]; - if (updateForcesReconsider) { - attendees = [NSMutableArray arrayWithArray:[newApt attendees]]; - [attendees removeObjectsInArray:[changes insertedAttendees]]; - [attendees removePerson:organizer]; - [self sendEMailUsingTemplateNamed: @"Update" - forOldObject: oldApt - andNewObject: newApt - toAttendees: attendees]; - } + if (updateForcesReconsider) + { + iCalEvent *updatedApt; + + updatedApt = [newApt copy]; + [(iCalCalendar *) [updatedApt parent] setMethod: @"request"]; + attendees = [NSMutableArray arrayWithArray:[newApt attendees]]; + [attendees removeObjectsInArray:[changes insertedAttendees]]; + [attendees removePerson:organizer]; + [self sendEMailUsingTemplateNamed: @"Update" + forOldObject: oldApt + andNewObject: updatedApt + toAttendees: attendees]; + [updatedApt release]; + } attendees = [NSMutableArray arrayWithArray: [changes deletedAttendees]]; @@ -393,8 +401,8 @@ && [self _aptIsStillRelevant: apt]) { /* send notification email to attendees excluding organizer */ - attendees = [NSMutableArray arrayWithArray:[apt attendees]]; - [attendees removePerson:[apt organizer]]; + attendees = [NSMutableArray arrayWithArray: [apt attendees]]; + [attendees removePerson: [apt organizer]]; /* flag appointment as being cancelled */ [(iCalCalendar *) [apt parent] setMethod: @"cancel"]; diff --git a/SoObjects/Appointments/SOGoAptMailNotification.m b/SoObjects/Appointments/SOGoAptMailNotification.m index 912c3a0e..b08bf74d 100644 --- a/SoObjects/Appointments/SOGoAptMailNotification.m +++ b/SoObjects/Appointments/SOGoAptMailNotification.m @@ -82,6 +82,7 @@ static NSTimeZone *EST = nil; - (NSString *)homePageURL { return self->homePageURL; } + - (void)setHomePageURL:(NSString *)_homePageURL { ASSIGN(self->homePageURL, _homePageURL); } diff --git a/SoObjects/Appointments/SOGoCalendarComponent.h b/SoObjects/Appointments/SOGoCalendarComponent.h index db0d4df9..4bb746a3 100644 --- a/SoObjects/Appointments/SOGoCalendarComponent.h +++ b/SoObjects/Appointments/SOGoCalendarComponent.h @@ -57,10 +57,10 @@ forOldObject: (iCalRepeatableEntityObject *) _oldObject andNewObject: (iCalRepeatableEntityObject *) _newObject toAttendees: (NSArray *) _attendees; +- (void) sendResponseToOrganizer; // - (BOOL) isOrganizerOrOwner: (SOGoUser *) user; -- (iCalPerson *) findParticipant: (SOGoUser *) user; - (iCalPerson *) findParticipantWithUID: (NSString *) uid; - (iCalPerson *) iCalPersonWithUID: (NSString *) uid; diff --git a/SoObjects/Appointments/SOGoCalendarComponent.m b/SoObjects/Appointments/SOGoCalendarComponent.m index 805de427..72cb73c3 100644 --- a/SoObjects/Appointments/SOGoCalendarComponent.m +++ b/SoObjects/Appointments/SOGoCalendarComponent.m @@ -36,6 +36,7 @@ #import #import +#import #import #import #import @@ -161,21 +162,28 @@ static BOOL sendEMailNotifications = NO; return calContent; } -- (NSException *) saveContentString: (NSString *) contentString - baseVersion: (unsigned int) baseVersion +- (void) setContentString: (NSString *) newContent { - NSException *result; + [super setContentString: newContent]; + ASSIGN (calendar, nil); + ASSIGN (calContent, nil); +} - result = [super saveContentString: contentString - baseVersion: baseVersion]; - if (!result && calContent) - { - [calContent release]; - calContent = nil; - } +// - (NSException *) saveContentString: (NSString *) contentString +// baseVersion: (unsigned int) baseVersion +// { +// NSException *result; - return result; -} +// result = [super saveContentString: contentString +// baseVersion: baseVersion]; +// if (!result && calContent) +// { +// [calContent release]; +// calContent = nil; +// } + +// return result; +// } - (iCalCalendar *) calendar: (BOOL) create { @@ -445,6 +453,11 @@ static BOOL sendEMailNotifications = NO; } } +- (void) sendResponseToOrganizer +{ +#warning THIS IS A STUB +} + // - (BOOL) isOrganizerOrOwner: (SOGoUser *) user // { // BOOL isOrganizerOrOwner; @@ -464,33 +477,13 @@ static BOOL sendEMailNotifications = NO; - (iCalPerson *) findParticipantWithUID: (NSString *) uid { + iCalEntityObject *component; SOGoUser *user; user = [SOGoUser userWithLogin: uid roles: nil]; - - return [self findParticipant: user]; -} - -- (iCalPerson *) findParticipant: (SOGoUser *) user -{ - iCalPerson *participant, *currentParticipant; - iCalEntityObject *component; - NSEnumerator *participants; - - participant = nil; component = [self component: NO]; - if (component) - { - participants = [[component participants] objectEnumerator]; - currentParticipant = [participants nextObject]; - while (currentParticipant && !participant) - if ([user hasEmail: [currentParticipant rfc822Email]]) - participant = currentParticipant; - else - currentParticipant = [participants nextObject]; - } - return participant; + return [component findParticipant: user]; } - (iCalPerson *) iCalPersonWithUID: (NSString *) uid diff --git a/SoObjects/Mailer/SOGoDraftObject.m b/SoObjects/Mailer/SOGoDraftObject.m index ccfd9391..e4293472 100644 --- a/SoObjects/Mailer/SOGoDraftObject.m +++ b/SoObjects/Mailer/SOGoDraftObject.m @@ -1149,7 +1149,7 @@ static BOOL showTextAttachmentsInline = NO; dateString = [[NSCalendarDate date] rfc822DateString]; [map addObject: dateString forKey: @"date"]; [map addObject: @"1.0" forKey: @"MIME-Version"]; - [map addObject: userAgent forKey: @"X-Mailer"]; + [map addObject: userAgent forKey: @"User-Agent"]; /* add custom headers */ diff --git a/SoObjects/Mailer/SOGoMailBodyPart.m b/SoObjects/Mailer/SOGoMailBodyPart.m index 5c32a2ef..78be1fb6 100644 --- a/SoObjects/Mailer/SOGoMailBodyPart.m +++ b/SoObjects/Mailer/SOGoMailBodyPart.m @@ -167,7 +167,8 @@ static BOOL debugOn = NO; /* fetch */ -- (NSData *)fetchBLOB { +- (NSData *) fetchBLOB +{ // HEADER, HEADER.FIELDS, HEADER.FIELDS.NOT, MIME, TEXT NSString *enc; NSData *data; @@ -225,7 +226,8 @@ static BOOL debugOn = NO; return type; } -- (NSString *)contentTypeForPathExtension:(NSString *)pe { +- (NSString *) contentTypeForPathExtension: (NSString *) pe +{ if ([pe length] == 0) return @"application/octet-stream"; diff --git a/SoObjects/Mailer/SOGoMailObject.m b/SoObjects/Mailer/SOGoMailObject.m index 0cda214e..f6969e97 100644 --- a/SoObjects/Mailer/SOGoMailObject.m +++ b/SoObjects/Mailer/SOGoMailObject.m @@ -41,6 +41,7 @@ #import #import +#import #import #import diff --git a/SoObjects/SOGo/GNUmakefile b/SoObjects/SOGo/GNUmakefile index 1d9df45c..889b653f 100644 --- a/SoObjects/SOGo/GNUmakefile +++ b/SoObjects/SOGo/GNUmakefile @@ -34,6 +34,7 @@ libSOGo_HEADER_FILES = \ SOGoDateFormatter.h \ SOGoPermissions.h \ SOGoDAVRendererTypes.h \ + iCalEntityObject+Utilities.h \ NSArray+Utilities.h \ NSDictionary+URL.h \ NSDictionary+Utilities.h \ @@ -68,6 +69,7 @@ libSOGo_OBJC_FILES = \ LDAPSource.m \ SOGoDAVRendererTypes.m \ AgenorUserDefaults.m \ + iCalEntityObject+Utilities.m \ NSArray+Utilities.m \ NSDictionary+URL.m \ NSDictionary+Utilities.m \ diff --git a/SoObjects/SOGo/SOGoContentObject.h b/SoObjects/SOGo/SOGoContentObject.h index f23f5cce..62d3fc75 100644 --- a/SoObjects/SOGo/SOGoContentObject.h +++ b/SoObjects/SOGo/SOGoContentObject.h @@ -49,6 +49,7 @@ /* content */ - (BOOL) isNew; +- (void) setContentString: (NSString *) newContent; - (NSString *) contentAsString; - (NSException *) saveContentString: (NSString *) _str baseVersion: (unsigned int) _baseVersion; diff --git a/SoObjects/SOGo/SOGoContentObject.m b/SoObjects/SOGo/SOGoContentObject.m index 44251366..7e43dbbb 100644 --- a/SoObjects/SOGo/SOGoContentObject.m +++ b/SoObjects/SOGo/SOGoContentObject.m @@ -138,6 +138,11 @@ return content; } +- (void) setContentString: (NSString *) newContent +{ + ASSIGN (content, newContent); +} + - (NSException *) saveContentString: (NSString *) newContent baseVersion: (unsigned int) newBaseVersion { @@ -147,12 +152,11 @@ ex = nil; - ASSIGN (content, newContent); + [self setContentString: newContent]; folder = [container ocsFolder]; if (folder) { - ex = [folder writeContent: newContent - toName: nameInContainer + ex = [folder writeContent: content toName: nameInContainer baseVersion: newBaseVersion]; if (ex) [self errorWithFormat:@"write failed: %@", ex]; diff --git a/SoObjects/SOGo/SOGoUser.h b/SoObjects/SOGo/SOGoUser.h index 4530e14f..10b9f0fd 100644 --- a/SoObjects/SOGo/SOGoUser.h +++ b/SoObjects/SOGo/SOGoUser.h @@ -41,7 +41,10 @@ @class NSUserDefaults; @class NSTimeZone; @class WOContext; +@class SOGoAppointmentFolder; +@class SOGoAppointmentFolders; @class SOGoDateFormatter; +@class WOContext; extern NSString *SOGoWeekStartHideWeekNumbers; extern NSString *SOGoWeekStartJanuary1; @@ -117,7 +120,10 @@ extern NSString *SOGoWeekStartFirstFullWeek; /* folders */ - (id) homeFolderInContext: (id) _ctx; -// - (id) schedulingCalendarInContext: (id) _ctx; + +- (SOGoAppointmentFolders *) calendarsFolderInContext: (WOContext *) context; +- (SOGoAppointmentFolder *) + personalCalendarFolderInContext: (WOContext *) context; - (NSArray *) rolesForObject: (NSObject *) object inContext: (WOContext *) context; diff --git a/SoObjects/SOGo/SOGoUser.m b/SoObjects/SOGo/SOGoUser.m index 9ffdfc6e..9488c1dd 100644 --- a/SoObjects/SOGo/SOGoUser.m +++ b/SoObjects/SOGo/SOGoUser.m @@ -551,6 +551,21 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek"; return folder; } +- (SOGoAppointmentFolders *) calendarsFolderInContext: (WOContext *) context +{ + return [[self homeFolderInContext: context] lookupName: @"Calendar" + inContext: context + acquire: NO]; +} + +- (SOGoAppointmentFolder *) + personalCalendarFolderInContext: (WOContext *) context +{ + return [[self calendarsFolderInContext: context] lookupName: @"personal" + inContext: context + acquire: NO]; +} + // - (id) schedulingCalendarInContext: (id) _ctx // { // /* Note: watch out for cyclic references */ diff --git a/SoObjects/SOGo/iCalEntityObject+Utilities.h b/SoObjects/SOGo/iCalEntityObject+Utilities.h new file mode 100644 index 00000000..c9d44c7d --- /dev/null +++ b/SoObjects/SOGo/iCalEntityObject+Utilities.h @@ -0,0 +1,37 @@ +/* iCalEntityObject+Utilities.h - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef ICALENTITYOBJECT_UTILITIES_H +#define ICALENTITYOBJECT_UTILITIES_H + +#import + +@class iCalPerson; +@class SOGoUser; + +@interface iCalEntityObject (SOGoAddition) + +- (iCalPerson *) findParticipant: (SOGoUser *) user; + +@end + +#endif /* ICALENTITYOBJECT_UTILITIES_H */ diff --git a/SoObjects/SOGo/iCalEntityObject+Utilities.m b/SoObjects/SOGo/iCalEntityObject+Utilities.m new file mode 100644 index 00000000..86748093 --- /dev/null +++ b/SoObjects/SOGo/iCalEntityObject+Utilities.m @@ -0,0 +1,51 @@ +/* iCalEntityObject+Utilities.m - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import +#import + +#import + +#import "SOGoUser.h" + +#import "iCalEntityObject+Utilities.h" + +@implementation iCalEntityObject (SOGoAddition) + +- (iCalPerson *) findParticipant: (SOGoUser *) user +{ + iCalPerson *participant, *currentParticipant; + NSEnumerator *participants; + + participant = nil; + participants = [[self participants] objectEnumerator]; + currentParticipant = [participants nextObject]; + while (currentParticipant && !participant) + if ([user hasEmail: [currentParticipant rfc822Email]]) + participant = currentParticipant; + else + currentParticipant = [participants nextObject]; + + return participant; +} + +@end diff --git a/UI/MailPartViewers/GNUmakefile b/UI/MailPartViewers/GNUmakefile index ffa92eb7..a86ea456 100644 --- a/UI/MailPartViewers/GNUmakefile +++ b/UI/MailPartViewers/GNUmakefile @@ -24,7 +24,7 @@ MailPartViewers_OBJC_FILES += \ UIxMailPartMessageViewer.m \ UIxMailPartICalViewer.m \ \ - UIxMailPartICalAction.m \ + UIxMailPartICalActions.m \ \ UIxKolabPartViewer.m \ UIxKolabPartContactViewer.m \ diff --git a/UI/MailPartViewers/UIxMailPartICalAction.m b/UI/MailPartViewers/UIxMailPartICalActions.h similarity index 51% rename from UI/MailPartViewers/UIxMailPartICalAction.m rename to UI/MailPartViewers/UIxMailPartICalActions.h index f83a3381..f6a26e79 100644 --- a/UI/MailPartViewers/UIxMailPartICalAction.m +++ b/UI/MailPartViewers/UIxMailPartICalActions.h @@ -1,4 +1,4 @@ -/* UIxMailPartICalAction.m - this file is part of SOGo +/* UIxMailPartICalActions.h - this file is part of SOGo * * Copyright (C) 2007 Inverse groupe conseil * @@ -20,44 +20,20 @@ * Boston, MA 02111-1307, USA. */ -#import -#import - -#import - -@interface UIxMailPartICalAction : WODirectAction -@end +#ifndef UIXMAILPARTICALACTIONS_H +#define UIXMAILPARTICALACTIONS_H -@implementation UIxMailPartICalAction - -- (WOResponse *) _changePartStatusAction: (NSString *) _newStatus -{ - return [self responseWithStatus: 404]; -} - -- (WOResponse *) markAcceptedAction -{ - return [self _changePartStatusAction: @"ACCEPTED"]; -} +#import -- (WOResponse *) markDeclinedAction -{ - return [self _changePartStatusAction: @"DECLINED"]; -} +@class iCalCalendar; +@class SOGoMailBodyPart; +@class WOResponse; -- (WOResponse *) markTentativeAction -{ - return [self _changePartStatusAction: @"TENTATIVE"]; -} +@interface UIxMailPartICalActions : WODirectAction -- (WOResponse *) addToCalendarAction -{ - return [self responseWithStatus: 404]; -} +- (WOResponse *) acceptAction; +- (WOResponse *) declineAction; -- (WOResponse *) deleteFromCalendarAction -{ - return [self responseWithStatus: 404]; -} +@end -@end /* UIxMailPartICalAction */ +#endif /* UIXMAILPARTICALACTIONS_H */ diff --git a/UI/MailPartViewers/UIxMailPartICalActions.m b/UI/MailPartViewers/UIxMailPartICalActions.m new file mode 100644 index 00000000..c4c8cb78 --- /dev/null +++ b/UI/MailPartViewers/UIxMailPartICalActions.m @@ -0,0 +1,164 @@ +/* UIxMailPartICalActions.m - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import + +#import +#import + +#import +#import + +#import + +#import +#import +#import +#import +#import + +#import "UIxMailPartICalActions.h" + +@implementation UIxMailPartICalActions + +- (iCalEvent *) _emailEvent +{ + NSData *content; + NSString *eventString; + iCalCalendar *emailCalendar; + + content = [[self clientObject] fetchBLOB]; + eventString = [[NSString alloc] initWithData: content + encoding: NSUTF8StringEncoding]; + if (!eventString) + eventString = [[NSString alloc] initWithData: content + encoding: NSISOLatin1StringEncoding]; + emailCalendar = [iCalCalendar parseSingleFromSource: eventString]; + + return (iCalEvent *) [emailCalendar firstChildWithTag: @"vevent"]; +} + +- (SOGoAppointmentObject *) _eventObjectWithUID: (NSString *) uid +{ + SOGoAppointmentFolder *personalFolder; + SOGoAppointmentObject *eventObject; + + personalFolder + = [[context activeUser] personalCalendarFolderInContext: context]; + eventObject = [personalFolder lookupName: uid + inContext: context acquire: NO]; + if (![eventObject isKindOfClass: [SOGoAppointmentObject class]]) + eventObject = [SOGoAppointmentObject objectWithName: uid + inContainer: personalFolder]; + + return eventObject; +} + +- (iCalEvent *) + _setupChosenEventAndEventObject: (SOGoAppointmentObject **) eventObject +{ + iCalEvent *emailEvent, *calendarEvent, *chosenEvent; + + emailEvent = [self _emailEvent]; + if (emailEvent) + { + *eventObject = [self _eventObjectWithUID: [emailEvent uid]]; + if ([*eventObject isNew]) + chosenEvent = emailEvent; + else + { + calendarEvent = (iCalEvent *) [*eventObject component: NO]; + if ([calendarEvent compare: emailEvent] == NSOrderedAscending) + chosenEvent = emailEvent; + else + chosenEvent = calendarEvent; + } + } + else + chosenEvent = nil; + + return chosenEvent; +} + +- (WOResponse *) _changePartStatusAction: (NSString *) newStatus +{ + WOResponse *response; + SOGoAppointmentObject *eventObject; + iCalEvent *chosenEvent; + iCalPerson *user; + iCalCalendar *calendar; + NSString *rsvp, *method; + + chosenEvent = [self _setupChosenEventAndEventObject: &eventObject]; + if (chosenEvent) + { + user = [chosenEvent findParticipant: [context activeUser]]; + [user setPartStat: newStatus]; + calendar = [chosenEvent parent]; + method = [[calendar method] lowercaseString]; + if ([method isEqualToString: @"request"]) + { + [calendar setMethod: @""]; + rsvp = [[user rsvp] lowercaseString]; + } + else + rsvp = nil; + [eventObject saveContentString: [calendar versitString]]; + if (rsvp && [rsvp isEqualToString: @"true"]) + [eventObject sendResponseToOrganizer]; + response = [self responseWith204]; + } + else + { + response = [context response]; + [response setStatus: 409]; + } + + return response; +} + +- (WOResponse *) acceptAction +{ + return [self _changePartStatusAction: @"ACCEPTED"]; +} + +- (WOResponse *) declineAction +{ + return [self _changePartStatusAction: @"DECLINED"]; +} + +// - (WOResponse *) markTentativeAction +// { +// return [self _changePartStatusAction: @"TENTATIVE"]; +// } + +// - (WOResponse *) addToCalendarAction +// { +// return [self responseWithStatus: 404]; +// } + +// - (WOResponse *) deleteFromCalendarAction +// { +// return [self responseWithStatus: 404]; +// } + +@end diff --git a/UI/MailPartViewers/UIxMailPartICalViewer.m b/UI/MailPartViewers/UIxMailPartICalViewer.m index ec8c18af..56df0e45 100644 --- a/UI/MailPartViewers/UIxMailPartICalViewer.m +++ b/UI/MailPartViewers/UIxMailPartICalViewer.m @@ -25,6 +25,8 @@ Show plain/calendar mail parts. */ +#import + #import #import #import @@ -290,22 +292,24 @@ - (iCalEvent *) authorativeEvent { - /* DB is considered master, when in DB, ignore mail organizer */ - return [self isEventStoredInCalendar] - ? [self storedEvent] - : [self inEvent]; + iCalEvent *authorativeEvent; + + if ([[self storedEvent] compare: [self inEvent]] + == NSOrderedAscending) + authorativeEvent = inEvent; + else + authorativeEvent = storedEventObject; + + return authorativeEvent; } - (BOOL) isLoggedInUserTheOrganizer { - NSString *loginEMail; - - if ((loginEMail = [self loggedInUserEMail]) == nil) { - [self warnWithFormat:@"Could not determine email of logged in user?"]; - return NO; - } + iCalPerson *organizer; - return [[self authorativeEvent] isOrganizer:loginEMail]; + organizer = [[self authorativeEvent] organizer]; + + return [[context activeUser] hasEmail: [organizer rfc822Email]]; } - (BOOL) isLoggedInUserAnAttendee diff --git a/UI/MailPartViewers/UIxMailPartTextViewer.m b/UI/MailPartViewers/UIxMailPartTextViewer.m index 6e9b6a26..27ecea50 100644 --- a/UI/MailPartViewers/UIxMailPartTextViewer.m +++ b/UI/MailPartViewers/UIxMailPartTextViewer.m @@ -28,6 +28,8 @@ TODO: add contained link detection. */ +#import + #import #import @@ -42,35 +44,45 @@ @implementation NSString (SOGoMailUIExtension) -- (NSString *) stringByConvertingCRLNToHTML +static inline char * +convertChars (const char *oldString, unsigned int oldLength, + unsigned int *newLength) { - NSString *convertedString; - const char *oldString, *currentChar; - char *newString, *destChar; - unsigned int oldLength, length, delta; - - oldString = [self cStringUsingEncoding: NSUTF8StringEncoding]; - oldLength = [self lengthOfBytesUsingEncoding: NSUTF8StringEncoding]; + const char *currentChar, *upperLimit; + char *newString, *destChar, *reallocated; + unsigned int length, maxLength, iteration; - length = oldLength; - newString = malloc (length + 500); + maxLength = oldLength + 500; + newString = malloc (maxLength); destChar = newString; currentChar = oldString; - while (currentChar < (oldString + oldLength)) + + length = 0; + iteration = 0; + + upperLimit = oldString + oldLength; + while ((unsigned int) currentChar < (unsigned int) upperLimit) { if (*currentChar != '\r') { if (*currentChar == '\n') { - strcpy (destChar, "
"); - destChar += 6; - delta = (destChar - newString); - if (delta > length) + length = (unsigned int) destChar - (unsigned int) newString; + if ((length + (6 * iteration) + 500) > maxLength) { - length += 500; - newString = realloc (newString, length + 500); - destChar = newString + delta; + maxLength = length + (iteration * 6) + 500; + reallocated = realloc (newString, maxLength); + if (reallocated) + newString = reallocated; + else + [NSException raise: NSMallocException + format: @"reallocation failed in %s", + __PRETTY_FUNCTION__]; + destChar = newString + length; } + strcpy (destChar, "
"); + destChar += 6; + iteration++; } else { @@ -81,9 +93,23 @@ currentChar++; } *destChar = 0; + *newLength = (unsigned int) destChar - (unsigned int) newString; + + return newString; +} + +- (NSString *) stringByConvertingCRLNToHTML +{ + NSString *convertedString; + char *newString; + unsigned int newLength; + newString + = convertChars ([self cStringUsingEncoding: NSUTF8StringEncoding], + [self lengthOfBytesUsingEncoding: NSUTF8StringEncoding], + &newLength); convertedString = [[NSString alloc] initWithBytes: newString - length: (destChar - newString) + length: newLength encoding: NSUTF8StringEncoding]; [convertedString autorelease]; free (newString); diff --git a/UI/MailPartViewers/product.plist b/UI/MailPartViewers/product.plist index 145b4724..31e8b466 100644 --- a/UI/MailPartViewers/product.plist +++ b/UI/MailPartViewers/product.plist @@ -1,4 +1,4 @@ -{ +{ /* -*-java-*- */ requires = ( MAIN ); publicResources = ( @@ -12,29 +12,29 @@ methods = { accept = { protectedBy = "View"; - actionClass = "UIxMailPartICalAction"; - actionName = "markAccepted"; + actionClass = "UIxMailPartICalActions"; + actionName = "accept"; }; decline = { protectedBy = "View"; - actionClass = "UIxMailPartICalAction"; - actionName = "markDeclined"; + actionClass = "UIxMailPartICalActions"; + actionName = "decline"; }; - tentative = { + /* tentative = { protectedBy = "View"; - actionClass = "UIxMailPartICalAction"; + actionClass = "UIxMailPartICalAction"; actionName = "markTentative"; }; addToCalendar = { protectedBy = "View"; - actionClass = "UIxMailPartICalAction"; + actionClass = "UIxMailPartICalAction"; actionName = "addToCalendar"; }; deleteFromCalendar = { protectedBy = "View"; - actionClass = "UIxMailPartICalAction"; + actionClass = "UIxMailPartICalAction"; actionName = "deleteFromCalendar"; - }; + }; */ }; }; }; diff --git a/UI/Scheduler/UIxCalendarSelector.m b/UI/Scheduler/UIxCalendarSelector.m index e60fb6e1..1e49c8e2 100644 --- a/UI/Scheduler/UIxCalendarSelector.m +++ b/UI/Scheduler/UIxCalendarSelector.m @@ -25,6 +25,9 @@ #import #import + +#import + #import #import @@ -102,23 +105,26 @@ colorForNumber (unsigned int number) - (NSArray *) calendars { - NSArray *folders; + NSArray *folders, *roles; SOGoAppointmentFolders *co; SOGoAppointmentFolder *folder; NSMutableDictionary *calendar; unsigned int count, max; NSString *folderName, *fDisplayName; NSNumber *isActive; + SOGoUser *user; if (!calendars) { co = [self clientObject]; + user = [[self context] activeUser]; folders = [co subFolders]; max = [folders count]; calendars = [[NSMutableArray alloc] initWithCapacity: max]; for (count = 0; count < max; count++) { folder = [folders objectAtIndex: count]; + roles = [user rolesForObject: folder inContext: [self context]]; calendar = [NSMutableDictionary dictionary]; folderName = [folder nameInContainer]; fDisplayName = [folder displayName]; @@ -134,6 +140,8 @@ colorForNumber (unsigned int number) [calendar setObject: isActive forKey: @"active"]; [calendar setObject: [folder ownerInContext: context] forKey: @"owner"]; + [calendar setObject: [roles componentsJoinedByString: @","] + forKey: @"roles"]; [calendars addObject: calendar]; } } diff --git a/UI/Scheduler/UIxComponentEditor.m b/UI/Scheduler/UIxComponentEditor.m index 0c07730d..5f108ba8 100644 --- a/UI/Scheduler/UIxComponentEditor.m +++ b/UI/Scheduler/UIxComponentEditor.m @@ -148,18 +148,20 @@ co = [self clientObject]; componentOwner = [co ownerInContext: nil]; - - ASSIGN (title, [component summary]); - ASSIGN (location, [component location]); - ASSIGN (comment, [component comment]); - ASSIGN (url, [[component url] absoluteString]); - ASSIGN (privacy, [component accessClass]); - ASSIGN (priority, [component priority]); - ASSIGN (status, [component status]); - ASSIGN (categories, [[component categories] commaSeparatedValues]); - ASSIGN (organizer, [component organizer]); - [self _loadCategories]; - [self _loadAttendees]; + if (component) + { + ASSIGN (title, [component summary]); + ASSIGN (location, [component location]); + ASSIGN (comment, [component comment]); + ASSIGN (url, [[component url] absoluteString]); + ASSIGN (privacy, [component accessClass]); + ASSIGN (priority, [component priority]); + ASSIGN (status, [component status]); + ASSIGN (categories, [[component categories] commaSeparatedValues]); + ASSIGN (organizer, [component organizer]); + [self _loadCategories]; + [self _loadAttendees]; + } } // /* cycles */ // if ([component isRecurrent]) @@ -330,41 +332,47 @@ [NSString stringWithFormat: @"category_%@", item]]; } +- (NSString *) _permissionForEditing +{ + NSString *perm; + + if ([[self clientObject] isNew]) + perm = SoPerm_AddDocumentsImagesAndFiles; + else + { + if ([privacy isEqualToString: @"PRIVATE"]) + perm = SOGoCalendarPerm_ModifyPrivateRecords; + else if ([privacy isEqualToString: @"CONFIDENTIAL"]) + perm = SOGoCalendarPerm_ModifyConfidentialRecords; + else + perm = SOGoCalendarPerm_ModifyPublicRecords; + } + + return perm; +} + - (NSArray *) calendarList { - SOGoAppointmentFolder *calendar, *currentCalendar; + SOGoAppointmentFolder *currentCalendar; SOGoAppointmentFolders *calendarParent; NSEnumerator *allCalendars; SoSecurityManager *sm; - NSString *perm, *privacy; + NSString *perm; if (!calendarList) { - sm = [SoSecurityManager sharedSecurityManager]; - if ([[self clientObject] isNew]) - perm = SoPerm_AddDocumentsImagesAndFiles; - else { - privacy = [component accessClass]; - if ([privacy isEqualToString: @"PRIVATE"]) - perm = SOGoCalendarPerm_ModifyPrivateRecords; - else if ([privacy isEqualToString: @"CONFIDENTIAL"]) - perm = SOGoCalendarPerm_ModifyConfidentialRecords; - else - perm = SOGoCalendarPerm_ModifyPublicRecords; - } calendarList = [NSMutableArray new]; - calendar = [[self clientObject] container]; - calendarParent = [calendar container]; + + perm = [self _permissionForEditing]; + calendarParent + = [[context activeUser] calendarsFolderInContext: context]; + sm = [SoSecurityManager sharedSecurityManager]; allCalendars = [[calendarParent subFolders] objectEnumerator]; - currentCalendar = [allCalendars nextObject]; - while (currentCalendar) - { - if (![sm validatePermission: perm - onObject: currentCalendar - inContext: context]) - [calendarList addObject: currentCalendar]; - currentCalendar = [allCalendars nextObject]; - } + while ((currentCalendar = [allCalendars nextObject])) + if (![sm validatePermission: perm + onObject: currentCalendar + inContext: context]) + [calendarList addObject: currentCalendar]; } return calendarList; diff --git a/UI/Templates/ContactsUI/UIxContactsListView.wox b/UI/Templates/ContactsUI/UIxContactsListView.wox index d98db7d2..efc8c859 100644 --- a/UI/Templates/ContactsUI/UIxContactsListView.wox +++ b/UI/Templates/ContactsUI/UIxContactsListView.wox @@ -9,7 +9,7 @@ className="UIxContactsListViewContainer" selectorComponentClass="selectorComponentClass" title="name"> - +
diff --git a/UI/Templates/MailerUI/UIxMailListView.wox b/UI/Templates/MailerUI/UIxMailListView.wox index 31d5ed63..a0dec12e 100644 --- a/UI/Templates/MailerUI/UIxMailListView.wox +++ b/UI/Templates/MailerUI/UIxMailListView.wox @@ -1,6 +1,6 @@ -
-
+
diff --git a/UI/Templates/SchedulerUI/UIxCalendarSelector.wox b/UI/Templates/SchedulerUI/UIxCalendarSelector.wox index eac42da1..a02893ff 100644 --- a/UI/Templates/SchedulerUI/UIxCalendarSelector.wox +++ b/UI/Templates/SchedulerUI/UIxCalendarSelector.wox @@ -32,7 +32,7 @@ div.colorBox.calendarFolder
  • + var:owner="currentCalendar.owner" var:roles="currentCalendar.roles" > diff --git a/UI/WebServerResources/ContactsUI.js b/UI/WebServerResources/ContactsUI.js index e5c3d160..a58c509d 100644 --- a/UI/WebServerResources/ContactsUI.js +++ b/UI/WebServerResources/ContactsUI.js @@ -758,6 +758,7 @@ function initContacts(event) { var table = $("contactsList"); if (table) { // Initialize contacts table + table.multiselect = true; configureSortableTableHeaders(table); TableKit.Resizable.init(table, {'trueResize' : true, 'keepWidth' : true}); } diff --git a/UI/WebServerResources/MailerUI.js b/UI/WebServerResources/MailerUI.js index 9f8ce6a2..bce12ad7 100644 --- a/UI/WebServerResources/MailerUI.js +++ b/UI/WebServerResources/MailerUI.js @@ -785,6 +785,9 @@ function ICalendarButtonCallback(http) { loadMessage(currentMessages[currentMailbox]); } } + else { + window.alert("received code: " + http.status); + } } function resizeMailContent() { diff --git a/UI/WebServerResources/SchedulerUI.css b/UI/WebServerResources/SchedulerUI.css index 609da77c..cd70d984 100644 --- a/UI/WebServerResources/SchedulerUI.css +++ b/UI/WebServerResources/SchedulerUI.css @@ -191,23 +191,19 @@ DIV#dateSelectorView margin: 0px; border: 0px; } -#dateSelector > .header A -{ width: 1em; - padding: .4em .2em; } - #dateSelector > .header #leftArrow { float: left; } #dateSelector > .header #rightArrow -{ float: right; } +{ float: right; + margin-right: 2px; } #dateSelector > .header SPAN { cursor: default; font-size: medium; vertical-align: middle; font-weight: bold; - border: 1px solid transparent; - margin: .5em .2em; } + border: 1px solid transparent; } #dateSelector > .header SPAN:hover { border-left: 1px solid #fff; @@ -221,43 +217,32 @@ DIV#dateSelectorView TABLE#dateSelectorTable { padding: 2px; } -#dateSelector TABLE#dateSelectorTable TD -{ width: 14%; } - -#dateSelector TABLE#dateSelectorTable TD TABLE TD -{ width: 100% } - #dateSelector TABLE, #dateSelector TABLE TABLE { border-collapse: collapse; margin: 0px auto; width: 100%; } +#dateSelector TABLE#dateSelectorTable TD TABLE TD +{ width: 0px; /* temp hack */ } + +#dateSelector TABLE#dateSelectorTable TD TABLE TD.activeDay, +#dateSelector TABLE#dateSelectorTable TD TABLE TD.inactiveDay, +#dateSelector TABLE#dateSelectorTable TD TABLE TD.dayOfToday +{ width: 1em; } + #dateSelector TABLE TABLE TD { cursor: pointer; margin: 0px; padding: 0px; - width: 100%; - border: 1px solid #fff; } + border: 1px solid #fff; + text-align: center; } #dateSelector TABLE TABLE TD:hover -{ color: #f00; - border: 1px solid #deebf7; } - -#dateSelector TABLE TABLE TD:hover A -{ color: #f00; } - -#dateSelector .inactiveDay -{ color: #dedfde; } - -#dateSelector .dayOfToday -{ background-color: #deebf7; - border: 1px solid #deebf7; } +{ border: 1px solid #deebf7; } #dateSelector TD SPAN -{ width: 1.5em; - height: 1.5em; - text-align: center; +{ text-align: center; display: block; } #dateSelector TD SPAN A @@ -269,6 +254,17 @@ TABLE#dateSelectorTable { background-color: #ddd; border: 1px solid #deebf7; } +#dateSelector TD.inactiveDay A +{ color: #dedfde; } + +#dateSelector TD.dayOfToday +{ background-color: #deebf7; + border: 1px solid #deebf7; } + +#dateSelector TD._selected A +{ color: #fff; } + + TABLE#eventsList { width: 100%; } @@ -287,9 +283,9 @@ TABLE#eventsList TH ._unfocused#dateSelector TD._selected, UL._unfocused > LI._selected, TABLE._unfocused#eventsList TR._selected TD -{ - background-color: #d4d0c8 !important;; - color: #fff !important;; +{ + background-color: #d4d0c8 !important; + color: #fff !important; } SPAN.dayCellLabel diff --git a/UI/WebServerResources/SchedulerUI.js b/UI/WebServerResources/SchedulerUI.js index 22e7b72a..05ded9b2 100644 --- a/UI/WebServerResources/SchedulerUI.js +++ b/UI/WebServerResources/SchedulerUI.js @@ -6,6 +6,7 @@ var listFilter = 'view_today'; var listOfSelection = null; var selectedCalendarCell; +var calendarColorIndex = null; var showCompletedTasks = 0; @@ -30,7 +31,11 @@ function newEvent(sender, type) { var hour = sender.hour; if (!hour) hour = sender.getAttribute("hour"); - var folderID = getSelectedFolder(); + var folder = getSelectedFolder(); + var roles = folder.getAttribute("roles").split(","); + var folderID = folder.getAttribute("id"); + if ($(roles).indexOf("PublicModifier") < 0) + folderID = "/personal"; var urlstr = ApplicationBaseURL + folderID + "/new" + type; var params = new Array(); if (day) @@ -47,12 +52,12 @@ function newEvent(sender, type) { function getSelectedFolder() { var folder; - - var nodes = $("calendarList").getSelectedRows(); + var list = $("calendarList"); + var nodes = list.getSelectedRows(); if (nodes.length > 0) - folder = nodes[0].getAttribute("id"); + folder = nodes[0]; else - folder = "/personal"; + folder = list.down("li"); return folder; } @@ -1459,9 +1464,12 @@ function appendCalendar(folderName, folderPath) { li.setAttribute("owner", owner); // Generate new color + if (calendarColorIndex == null) + calendarColorIndex = lis.length; + calendarColorIndex++; var colorTable = [1, 1, 1]; var color; - var currentValue = lis.length + 1; + var currentValue = calendarColorIndex; var index = 0; while (currentValue) { if (currentValue & 1) diff --git a/UI/WebServerResources/UIxAppointmentEditor.css b/UI/WebServerResources/UIxAppointmentEditor.css index 3bef0501..b02026e5 100644 --- a/UI/WebServerResources/UIxAppointmentEditor.css +++ b/UI/WebServerResources/UIxAppointmentEditor.css @@ -90,7 +90,7 @@ TEXTAREA padding-bottom: 0em; } SELECT#calendarList -{ width: 7em; } +{ width: 18em; } A#changeUrlButton { margin-left: 1em; } diff --git a/UI/WebServerResources/UIxAttendeesEditor.js b/UI/WebServerResources/UIxAttendeesEditor.js index 0f0547d6..9cf96023 100644 --- a/UI/WebServerResources/UIxAttendeesEditor.js +++ b/UI/WebServerResources/UIxAttendeesEditor.js @@ -302,8 +302,6 @@ function resetAttendeesValue() { currentInput.setAttribute("uid", null); } currentInput.setAttribute("autocomplete", "off"); - //Event.observe(currentInput, "keydown", onContactKeydown.bindAsEventListener(currentInput)); - //Event.observe(currentInput, "blur", checkAttendee.bindAsEventListener(currentInput)); } inputs[inputs.length - 2].setAttribute("autocomplete", "off"); Event.observe(inputs[inputs.length - 2], "click", newAttendee); @@ -511,6 +509,8 @@ function prepareAttendees() { input.value = value; $(input).addClassName("textField"); input.setAttribute("modified", "0"); + input.observe("blur", checkAttendee); + input.observe("keydown", onContactKeydown); tr.appendChild(td); td.appendChild(input); displayFreeBusyForNode(input); diff --git a/UI/WebServerResources/UIxComponentEditor.js b/UI/WebServerResources/UIxComponentEditor.js index 8aa9c112..6ff4d3f0 100644 --- a/UI/WebServerResources/UIxComponentEditor.js +++ b/UI/WebServerResources/UIxComponentEditor.js @@ -149,18 +149,7 @@ function onComponentEditorLoad(event) { Event.observe(list, "mousedown", onChangeCalendar.bindAsEventListener(list), false); - if (document.createEvent) { - var onSelectionChangeEvent; - if (isSafari()) - onSelectionChangeEvent = document.createEvent("UIEvents"); - else - onSelectionChangeEvent = document.createEvent("Events"); - onSelectionChangeEvent.initEvent("mousedown", false, false); - list.dispatchEvent(onSelectionChangeEvent); - } - else { - list.fireEvent("onmousedown"); // IE - } + list.fire("mousedown"); var menuItems = $("itemPrivacyList").childNodesWithTag("li"); for (var i = 0; i < menuItems.length; i++) diff --git a/UI/WebServerResources/UIxContactEditor.css b/UI/WebServerResources/UIxContactEditor.css index d0205082..f40ce742 100644 --- a/UI/WebServerResources/UIxContactEditor.css +++ b/UI/WebServerResources/UIxContactEditor.css @@ -26,21 +26,15 @@ DIV.tab TABLE width: 100%; } SPAN.caption -{ text-align: center; +{ display: inline-block; + position: relative; + text-align: center; cursor: default; - background: #999; + background-color: #d4d0c8; width: auto; padding: .25em; margin-left: 7px; - border-top: 2px solid #fff; - border-left: 2px solid #fff; - border-bottom: 2px solid #888; - border-right: 2px solid #888; - background-color: #d4d0c8; - -moz-border-top-colors: #efebe7 #fff; - -moz-border-left-colors: #efebe7 #fff; - -moz-border-bottom-colors: #000 #9c9a94 transparent; - -moz-border-right-colors: #000 #9c9a94 transparent; } + border: 0px; } DIV#buttons { visibility: visible; diff --git a/UI/WebServerResources/UIxTaskEditor.css b/UI/WebServerResources/UIxTaskEditor.css index ec02c5e1..a6a1a1d5 100644 --- a/UI/WebServerResources/UIxTaskEditor.css +++ b/UI/WebServerResources/UIxTaskEditor.css @@ -58,7 +58,7 @@ TEXTAREA padding-bottom: 0em; } SELECT#calendarList -{ width: 7em; } +{ width: 20em; } A#changeUrlButton { margin-left: 1em; } diff --git a/UI/WebServerResources/generic.css b/UI/WebServerResources/generic.css index d5c1c529..512a750e 100644 --- a/UI/WebServerResources/generic.css +++ b/UI/WebServerResources/generic.css @@ -600,7 +600,7 @@ LI.denied LI._selected, TR._selected > TD, TD._selected -{ +{ background-color: #4b6983; color: #fff; } diff --git a/UI/WebServerResources/generic.js b/UI/WebServerResources/generic.js index 8150991e..ea96808e 100644 --- a/UI/WebServerResources/generic.js +++ b/UI/WebServerResources/generic.js @@ -301,7 +301,7 @@ function triggerAjaxRequest(url, callback, userdata) { http.url = url; http.onreadystatechange = function() { - //log ("state changed (" + http.readyState + "): " + url); +// log ("state changed (" + http.readyState + "): " + url); try { if (http.readyState == 4 && activeAjaxRequests > 0) { @@ -525,18 +525,7 @@ function onRowClick(event) { var parentNode = node.parentNode; if (parentNode.tagName == 'TBODY') parentNode = parentNode.parentNode; - if (document.createEvent) { - var onSelectionChangeEvent; - if (isSafari()) - onSelectionChangeEvent = document.createEvent("UIEvents"); - else - onSelectionChangeEvent = document.createEvent("Events"); - onSelectionChangeEvent.initEvent("mousedown", true, true); - parentNode.dispatchEvent(onSelectionChangeEvent); - } - else if (document.createEventObject) { - parentNode.fireEvent("onmousedown"); - } + parentNode.fire("mousedown"); } } lastClickedRow = rowIndex; diff --git a/UI/WebServerResources/iefixes.css b/UI/WebServerResources/iefixes.css index ff0757e1..412621b2 100644 --- a/UI/WebServerResources/iefixes.css +++ b/UI/WebServerResources/iefixes.css @@ -16,11 +16,19 @@ DIV.javascriptMessagePseudoWindow, DIV.javascriptMessagePseudoTopWindow { filter: alpha(opacity=100); } +TD.headerCell, +TD.tbtv_headercell, +TD.tbtv_navcell +{ border: 1px solid #fff; + border-right: 1px solid #666; + border-bottom: 1px solid #666; } + /* MailerUI */ IMG.dragMessage { filter: alpha(opacity=70); } /* SchedulerUI */ + DIV[class~="event"]._selected > DIV.eventInside { filter: alpha(opacity=70); } diff --git a/UI/WebServerResources/prototype.js b/UI/WebServerResources/prototype.js index a3f21ac7..5c734629 100644 --- a/UI/WebServerResources/prototype.js +++ b/UI/WebServerResources/prototype.js @@ -1,27 +1,29 @@ -/* Prototype JavaScript framework, version 1.5.1.1 +/* Prototype JavaScript framework, version 1.6.0 * (c) 2005-2007 Sam Stephenson * * Prototype is freely distributable under the terms of an MIT-style license. * For details, see the Prototype web site: http://www.prototypejs.org/ * -/*--------------------------------------------------------------------------*/ + *--------------------------------------------------------------------------*/ var Prototype = { - Version: '1.5.1.1', + Version: '1.6.0', Browser: { IE: !!(window.attachEvent && !window.opera), Opera: !!window.opera, WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1, - Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1 + Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1, + MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/) }, BrowserFeatures: { XPath: !!document.evaluate, ElementExtensions: !!window.HTMLElement, SpecificElementExtensions: - (document.createElement('div').__proto__ !== - document.createElement('form').__proto__) + document.createElement('div').__proto__ && + document.createElement('div').__proto__ !== + document.createElement('form').__proto__ }, ScriptFragment: ']*>([\\S\\s]*?)<\/script>', @@ -29,24 +31,81 @@ var Prototype = { emptyFunction: function() { }, K: function(x) { return x } -} +}; + +if (Prototype.Browser.MobileSafari) + Prototype.BrowserFeatures.SpecificElementExtensions = false; +if (Prototype.Browser.WebKit) + Prototype.BrowserFeatures.XPath = false; + +/* Based on Alex Arnell's inheritance implementation. */ var Class = { create: function() { - return function() { + var parent = null, properties = $A(arguments); + if (Object.isFunction(properties[0])) + parent = properties.shift(); + + function klass() { this.initialize.apply(this, arguments); } + + Object.extend(klass, Class.Methods); + klass.superclass = parent; + klass.subclasses = []; + + if (parent) { + var subclass = function() { }; + subclass.prototype = parent.prototype; + klass.prototype = new subclass; + parent.subclasses.push(klass); + } + + for (var i = 0; i < properties.length; i++) + klass.addMethods(properties[i]); + + if (!klass.prototype.initialize) + klass.prototype.initialize = Prototype.emptyFunction; + + klass.prototype.constructor = klass; + + return klass; } -} +}; -var Abstract = new Object(); +Class.Methods = { + addMethods: function(source) { + var ancestor = this.superclass && this.superclass.prototype; + var properties = Object.keys(source); + + if (!Object.keys({ toString: true }).length) + properties.push("toString", "valueOf"); + + for (var i = 0, length = properties.length; i < length; i++) { + var property = properties[i], value = source[property]; + if (ancestor && Object.isFunction(value) && + value.argumentNames().first() == "$super") { + var method = value, value = Object.extend((function(m) { + return function() { return ancestor[m].apply(this, arguments) }; + })(property).wrap(method), { + valueOf: function() { return method }, + toString: function() { return method.toString() } + }); + } + this.prototype[property] = value; + } + + return this; + } +}; + +var Abstract = { }; Object.extend = function(destination, source) { - for (var property in source) { + for (var property in source) destination[property] = source[property]; - } return destination; -} +}; Object.extend(Object, { inspect: function(object) { @@ -62,24 +121,35 @@ Object.extend(Object, { toJSON: function(object) { var type = typeof object; - switch(type) { + switch (type) { case 'undefined': case 'function': case 'unknown': return; case 'boolean': return object.toString(); } + if (object === null) return 'null'; if (object.toJSON) return object.toJSON(); - if (object.ownerDocument === document) return; + if (Object.isElement(object)) return; + var results = []; for (var property in object) { var value = Object.toJSON(object[property]); if (value !== undefined) results.push(property.toJSON() + ': ' + value); } + return '{' + results.join(', ') + '}'; }, + toQueryString: function(object) { + return $H(object).toQueryString(); + }, + + toHTML: function(object) { + return object && object.toHTML ? object.toHTML() : String.interpret(object); + }, + keys: function(object) { var keys = []; for (var property in object) @@ -95,55 +165,99 @@ Object.extend(Object, { }, clone: function(object) { - return Object.extend({}, object); + return Object.extend({ }, object); + }, + + isElement: function(object) { + return object && object.nodeType == 1; + }, + + isArray: function(object) { + return object && object.constructor === Array; + }, + + isHash: function(object) { + return object instanceof Hash; + }, + + isFunction: function(object) { + return typeof object == "function"; + }, + + isString: function(object) { + return typeof object == "string"; + }, + + isNumber: function(object) { + return typeof object == "number"; + }, + + isUndefined: function(object) { + return typeof object == "undefined"; } }); -Function.prototype.bind = function() { - var __method = this, args = $A(arguments), object = args.shift(); - return function() { - return __method.apply(object, args.concat($A(arguments))); - } -} +Object.extend(Function.prototype, { + argumentNames: function() { + var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip"); + return names.length == 1 && !names[0] ? [] : names; + }, -Function.prototype.bindAsEventListener = function(object) { - var __method = this, args = $A(arguments), object = args.shift(); - return function(event) { - return __method.apply(object, [event || window.event].concat(args)); - } -} + bind: function() { + if (arguments.length < 2 && arguments[0] === undefined) return this; + var __method = this, args = $A(arguments), object = args.shift(); + return function() { + return __method.apply(object, args.concat($A(arguments))); + } + }, -Object.extend(Number.prototype, { - toColorPart: function() { - return this.toPaddedString(2, 16); + bindAsEventListener: function() { + var __method = this, args = $A(arguments), object = args.shift(); + return function(event) { + return __method.apply(object, [event || window.event].concat(args)); + } }, - succ: function() { - return this + 1; + curry: function() { + if (!arguments.length) return this; + var __method = this, args = $A(arguments); + return function() { + return __method.apply(this, args.concat($A(arguments))); + } }, - times: function(iterator) { - $R(0, this, true).each(iterator); - return this; + delay: function() { + var __method = this, args = $A(arguments), timeout = args.shift() * 1000; + return window.setTimeout(function() { + return __method.apply(__method, args); + }, timeout); }, - toPaddedString: function(length, radix) { - var string = this.toString(radix || 10); - return '0'.times(length - string.length) + string; + wrap: function(wrapper) { + var __method = this; + return function() { + return wrapper.apply(this, [__method.bind(this)].concat($A(arguments))); + } }, - toJSON: function() { - return isFinite(this) ? this.toString() : 'null'; + methodize: function() { + if (this._methodized) return this._methodized; + var __method = this; + return this._methodized = function() { + return __method.apply(null, [this].concat($A(arguments))); + }; } }); +Function.prototype.defer = Function.prototype.delay.curry(0.01); + Date.prototype.toJSON = function() { - return '"' + this.getFullYear() + '-' + - (this.getMonth() + 1).toPaddedString(2) + '-' + - this.getDate().toPaddedString(2) + 'T' + - this.getHours().toPaddedString(2) + ':' + - this.getMinutes().toPaddedString(2) + ':' + - this.getSeconds().toPaddedString(2) + '"'; + return '"' + this.getUTCFullYear() + '-' + + (this.getUTCMonth() + 1).toPaddedString(2) + '-' + + this.getUTCDate().toPaddedString(2) + 'T' + + this.getUTCHours().toPaddedString(2) + ':' + + this.getUTCMinutes().toPaddedString(2) + ':' + + this.getUTCSeconds().toPaddedString(2) + 'Z"'; }; var Try = { @@ -155,17 +269,22 @@ var Try = { try { returnValue = lambda(); break; - } catch (e) {} + } catch (e) { } } return returnValue; } -} +}; + +RegExp.prototype.match = RegExp.prototype.test; + +RegExp.escape = function(str) { + return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); +}; /*--------------------------------------------------------------------------*/ -var PeriodicalExecuter = Class.create(); -PeriodicalExecuter.prototype = { +var PeriodicalExecuter = Class.create({ initialize: function(callback, frequency) { this.callback = callback; this.frequency = frequency; @@ -178,6 +297,10 @@ PeriodicalExecuter.prototype = { this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); }, + execute: function() { + this.callback(this); + }, + stop: function() { if (!this.timer) return; clearInterval(this.timer); @@ -188,13 +311,13 @@ PeriodicalExecuter.prototype = { if (!this.currentlyExecuting) { try { this.currentlyExecuting = true; - this.callback(this); + this.execute(); } finally { this.currentlyExecuting = false; } } } -} +}); Object.extend(String, { interpret: function(value) { return value == null ? '' : String(value); @@ -238,14 +361,14 @@ Object.extend(String.prototype, { scan: function(pattern, iterator) { this.gsub(pattern, iterator); - return this; + return String(this); }, truncate: function(length, truncation) { length = length || 30; truncation = truncation === undefined ? '...' : truncation; return this.length > length ? - this.slice(0, length - truncation.length) + truncation : this; + this.slice(0, length - truncation.length) + truncation : String(this); }, strip: function() { @@ -279,7 +402,7 @@ Object.extend(String.prototype, { }, unescapeHTML: function() { - var div = document.createElement('div'); + var div = new Element('div'); div.innerHTML = this.stripTags(); return div.childNodes[0] ? (div.childNodes.length > 1 ? $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) : @@ -288,16 +411,16 @@ Object.extend(String.prototype, { toQueryParams: function(separator) { var match = this.strip().match(/([^?#]*)(#.*)?$/); - if (!match) return {}; + if (!match) return { }; - return match[1].split(separator || '&').inject({}, function(hash, pair) { + return match[1].split(separator || '&').inject({ }, function(hash, pair) { if ((pair = pair.split('='))[0]) { var key = decodeURIComponent(pair.shift()); var value = pair.length > 1 ? pair.join('=') : pair[0]; if (value != undefined) value = decodeURIComponent(value); if (key in hash) { - if (hash[key].constructor != Array) hash[key] = [hash[key]]; + if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; hash[key].push(value); } else hash[key] = value; @@ -316,9 +439,7 @@ Object.extend(String.prototype, { }, times: function(count) { - var result = ''; - for (var i = 0; i < count; i++) result += this; - return result; + return count < 1 ? '' : new Array(count + 1).join(this); }, camelize: function() { @@ -396,6 +517,10 @@ Object.extend(String.prototype, { blank: function() { return /^\s*$/.test(this); + }, + + interpolate: function(object, pattern) { + return new Template(this, pattern).evaluate(object); } }); @@ -409,10 +534,10 @@ if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.proto }); String.prototype.gsub.prepareReplacement = function(replacement) { - if (typeof replacement == 'function') return replacement; + if (Object.isFunction(replacement)) return replacement; var template = new Template(replacement); return function(match) { return template.evaluate(match) }; -} +}; String.prototype.parseQuery = String.prototype.toQueryParams; @@ -423,28 +548,46 @@ Object.extend(String.prototype.escapeHTML, { with (String.prototype.escapeHTML) div.appendChild(text); -var Template = Class.create(); -Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; -Template.prototype = { +var Template = Class.create({ initialize: function(template, pattern) { this.template = template.toString(); - this.pattern = pattern || Template.Pattern; + this.pattern = pattern || Template.Pattern; }, evaluate: function(object) { + if (Object.isFunction(object.toTemplateReplacements)) + object = object.toTemplateReplacements(); + return this.template.gsub(this.pattern, function(match) { - var before = match[1]; + if (object == null) return ''; + + var before = match[1] || ''; if (before == '\\') return match[2]; - return before + String.interpret(object[match[3]]); - }); + + var ctx = object, expr = match[3]; + var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/, match = pattern.exec(expr); + if (match == null) return before; + + while (match != null) { + var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1]; + ctx = ctx[comp]; + if (null == ctx || '' == match[3]) break; + expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); + match = pattern.exec(expr); + } + + return before + String.interpret(ctx); + }.bind(this)); } -} +}); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; -var $break = {}, $continue = new Error('"throw $continue" is deprecated, use "return" instead'); +var $break = { }; var Enumerable = { - each: function(iterator) { + each: function(iterator, context) { var index = 0; + iterator = iterator.bind(context); try { this._each(function(value) { iterator(value, index++); @@ -455,40 +598,45 @@ var Enumerable = { return this; }, - eachSlice: function(number, iterator) { + eachSlice: function(number, iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; var index = -number, slices = [], array = this.toArray(); while ((index += number) < array.length) slices.push(array.slice(index, index+number)); - return slices.map(iterator); + return slices.collect(iterator, context); }, - all: function(iterator) { + all: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; var result = true; this.each(function(value, index) { - result = result && !!(iterator || Prototype.K)(value, index); + result = result && !!iterator(value, index); if (!result) throw $break; }); return result; }, - any: function(iterator) { + any: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; var result = false; this.each(function(value, index) { - if (result = !!(iterator || Prototype.K)(value, index)) + if (result = !!iterator(value, index)) throw $break; }); return result; }, - collect: function(iterator) { + collect: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; var results = []; this.each(function(value, index) { - results.push((iterator || Prototype.K)(value, index)); + results.push(iterator(value, index)); }); return results; }, - detect: function(iterator) { + detect: function(iterator, context) { + iterator = iterator.bind(context); var result; this.each(function(value, index) { if (iterator(value, index)) { @@ -499,7 +647,8 @@ var Enumerable = { return result; }, - findAll: function(iterator) { + findAll: function(iterator, context) { + iterator = iterator.bind(context); var results = []; this.each(function(value, index) { if (iterator(value, index)) @@ -508,17 +657,24 @@ var Enumerable = { return results; }, - grep: function(pattern, iterator) { + grep: function(filter, iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; var results = []; + + if (Object.isString(filter)) + filter = new RegExp(filter); + this.each(function(value, index) { - var stringValue = value.toString(); - if (stringValue.match(pattern)) - results.push((iterator || Prototype.K)(value, index)); - }) + if (filter.match(value)) + results.push(iterator(value, index)); + }); return results; }, include: function(object) { + if (Object.isFunction(this.indexOf)) + if (this.indexOf(object) != -1) return true; + var found = false; this.each(function(value) { if (value == object) { @@ -537,7 +693,8 @@ var Enumerable = { }); }, - inject: function(memo, iterator) { + inject: function(memo, iterator, context) { + iterator = iterator.bind(context); this.each(function(value, index) { memo = iterator(memo, value, index); }); @@ -551,30 +708,33 @@ var Enumerable = { }); }, - max: function(iterator) { + max: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; var result; this.each(function(value, index) { - value = (iterator || Prototype.K)(value, index); + value = iterator(value, index); if (result == undefined || value >= result) result = value; }); return result; }, - min: function(iterator) { + min: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; var result; this.each(function(value, index) { - value = (iterator || Prototype.K)(value, index); + value = iterator(value, index); if (result == undefined || value < result) result = value; }); return result; }, - partition: function(iterator) { + partition: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; var trues = [], falses = []; this.each(function(value, index) { - ((iterator || Prototype.K)(value, index) ? + (iterator(value, index) ? trues : falses).push(value); }); return [trues, falses]; @@ -582,13 +742,14 @@ var Enumerable = { pluck: function(property) { var results = []; - this.each(function(value, index) { + this.each(function(value) { results.push(value[property]); }); return results; }, - reject: function(iterator) { + reject: function(iterator, context) { + iterator = iterator.bind(context); var results = []; this.each(function(value, index) { if (!iterator(value, index)) @@ -597,7 +758,8 @@ var Enumerable = { return results; }, - sortBy: function(iterator) { + sortBy: function(iterator, context) { + iterator = iterator.bind(context); return this.map(function(value, index) { return {value: value, criteria: iterator(value, index)}; }).sort(function(left, right) { @@ -612,7 +774,7 @@ var Enumerable = { zip: function() { var iterator = Prototype.K, args = $A(arguments); - if (typeof args.last() == 'function') + if (Object.isFunction(args.last())) iterator = args.pop(); var collections = [this].concat(args).map($A); @@ -628,46 +790,42 @@ var Enumerable = { inspect: function() { return '#'; } -} +}; Object.extend(Enumerable, { map: Enumerable.collect, find: Enumerable.detect, select: Enumerable.findAll, + filter: Enumerable.findAll, member: Enumerable.include, - entries: Enumerable.toArray + entries: Enumerable.toArray, + every: Enumerable.all, + some: Enumerable.any }); -var $A = Array.from = function(iterable) { +function $A(iterable) { if (!iterable) return []; - if (iterable.toArray) { - return iterable.toArray(); - } else { - var results = []; - for (var i = 0, length = iterable.length; i < length; i++) - results.push(iterable[i]); - return results; - } + if (iterable.toArray) return iterable.toArray(); + var length = iterable.length, results = new Array(length); + while (length--) results[length] = iterable[length]; + return results; } if (Prototype.Browser.WebKit) { - $A = Array.from = function(iterable) { + function $A(iterable) { if (!iterable) return []; - if (!(typeof iterable == 'function' && iterable == '[object NodeList]') && - iterable.toArray) { - return iterable.toArray(); - } else { - var results = []; - for (var i = 0, length = iterable.length; i < length; i++) - results.push(iterable[i]); - return results; - } + if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') && + iterable.toArray) return iterable.toArray(); + var length = iterable.length, results = new Array(length); + while (length--) results[length] = iterable[length]; + return results; } } +Array.from = $A; + Object.extend(Array.prototype, Enumerable); -if (!Array.prototype._reverse) - Array.prototype._reverse = Array.prototype.reverse; +if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse; Object.extend(Array.prototype, { _each: function(iterator) { @@ -696,7 +854,7 @@ Object.extend(Array.prototype, { flatten: function() { return this.inject([], function(array, value) { - return array.concat(value && value.constructor == Array ? + return array.concat(Object.isArray(value) ? value.flatten() : [value]); }); }, @@ -708,12 +866,6 @@ Object.extend(Array.prototype, { }); }, - indexOf: function(object) { - for (var i = 0, length = this.length; i < length; i++) - if (this[i] == object) return i; - return -1; - }, - reverse: function(inline) { return (inline !== false ? this : this.toArray())._reverse(); }, @@ -730,6 +882,12 @@ Object.extend(Array.prototype, { }); }, + intersect: function(array) { + return this.uniq().findAll(function(item) { + return array.detect(function(value) { return item === value }); + }); + }, + clone: function() { return [].concat(this); }, @@ -752,9 +910,29 @@ Object.extend(Array.prototype, { } }); +// use native browser JS 1.6 implementation if available +if (Object.isFunction(Array.prototype.forEach)) + Array.prototype._each = Array.prototype.forEach; + +if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) { + i || (i = 0); + var length = this.length; + if (i < 0) i = length + i; + for (; i < length; i++) + if (this[i] === item) return i; + return -1; +}; + +if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) { + i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; + var n = this.slice(0, i).reverse().indexOf(item); + return (n < 0) ? n : i - n - 1; +}; + Array.prototype.toArray = Array.prototype.clone; function $w(string) { + if (!Object.isString(string)) return []; string = string.strip(); return string ? string.split(/\s+/) : []; } @@ -764,7 +942,7 @@ if (Prototype.Browser.Opera){ var array = []; for (var i = 0, length = this.length; i < length; i++) array.push(this[i]); for (var i = 0, length = arguments.length; i < length; i++) { - if (arguments[i].constructor == Array) { + if (Object.isArray(arguments[i])) { for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) array.push(arguments[i][j]); } else { @@ -772,136 +950,156 @@ if (Prototype.Browser.Opera){ } } return array; - } + }; } -var Hash = function(object) { - if (object instanceof Hash) this.merge(object); - else Object.extend(this, object || {}); -}; - -Object.extend(Hash, { - toQueryString: function(obj) { - var parts = []; - parts.add = arguments.callee.addPair; +Object.extend(Number.prototype, { + toColorPart: function() { + return this.toPaddedString(2, 16); + }, - this.prototype._each.call(obj, function(pair) { - if (!pair.key) return; - var value = pair.value; + succ: function() { + return this + 1; + }, - if (value && typeof value == 'object') { - if (value.constructor == Array) value.each(function(value) { - parts.add(pair.key, value); - }); - return; - } - parts.add(pair.key, value); - }); + times: function(iterator) { + $R(0, this, true).each(iterator); + return this; + }, - return parts.join('&'); + toPaddedString: function(length, radix) { + var string = this.toString(radix || 10); + return '0'.times(length - string.length) + string; }, - toJSON: function(object) { - var results = []; - this.prototype._each.call(object, function(pair) { - var value = Object.toJSON(pair.value); - if (value !== undefined) results.push(pair.key.toJSON() + ': ' + value); - }); - return '{' + results.join(', ') + '}'; + toJSON: function() { + return isFinite(this) ? this.toString() : 'null'; } }); -Hash.toQueryString.addPair = function(key, value, prefix) { - key = encodeURIComponent(key); - if (value === undefined) this.push(key); - else this.push(key + '=' + (value == null ? '' : encodeURIComponent(value))); -} - -Object.extend(Hash.prototype, Enumerable); -Object.extend(Hash.prototype, { - _each: function(iterator) { - for (var key in this) { - var value = this[key]; - if (value && value == Hash.prototype[key]) continue; +$w('abs round ceil floor').each(function(method){ + Number.prototype[method] = Math[method].methodize(); +}); +function $H(object) { + return new Hash(object); +}; - var pair = [key, value]; - pair.key = key; - pair.value = value; - iterator(pair); +var Hash = Class.create(Enumerable, (function() { + if (function() { + var i = 0, Test = function(value) { this.key = value }; + Test.prototype.key = 'foo'; + for (var property in new Test('bar')) i++; + return i > 1; + }()) { + function each(iterator) { + var cache = []; + for (var key in this._object) { + var value = this._object[key]; + if (cache.include(key)) continue; + cache.push(key); + var pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } } - }, + } else { + function each(iterator) { + for (var key in this._object) { + var value = this._object[key], pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + } + } - keys: function() { - return this.pluck('key'); - }, + function toQueryPair(key, value) { + if (Object.isUndefined(value)) return key; + return key + '=' + encodeURIComponent(String.interpret(value)); + } - values: function() { - return this.pluck('value'); - }, + return { + initialize: function(object) { + this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); + }, - merge: function(hash) { - return $H(hash).inject(this, function(mergedHash, pair) { - mergedHash[pair.key] = pair.value; - return mergedHash; - }); - }, + _each: each, - remove: function() { - var result; - for(var i = 0, length = arguments.length; i < length; i++) { - var value = this[arguments[i]]; - if (value !== undefined){ - if (result === undefined) result = value; - else { - if (result.constructor != Array) result = [result]; - result.push(value) - } - } - delete this[arguments[i]]; - } - return result; - }, + set: function(key, value) { + return this._object[key] = value; + }, - toQueryString: function() { - return Hash.toQueryString(this); - }, + get: function(key) { + return this._object[key]; + }, - inspect: function() { - return '#'; - }, + unset: function(key) { + var value = this._object[key]; + delete this._object[key]; + return value; + }, - toJSON: function() { - return Hash.toJSON(this); - } -}); + toObject: function() { + return Object.clone(this._object); + }, -function $H(object) { - if (object instanceof Hash) return object; - return new Hash(object); -}; + keys: function() { + return this.pluck('key'); + }, + + values: function() { + return this.pluck('value'); + }, + + index: function(value) { + var match = this.detect(function(pair) { + return pair.value === value; + }); + return match && match.key; + }, + + merge: function(object) { + return this.clone().update(object); + }, + + update: function(object) { + return new Hash(object).inject(this, function(result, pair) { + result.set(pair.key, pair.value); + return result; + }); + }, + + toQueryString: function() { + return this.map(function(pair) { + var key = encodeURIComponent(pair.key), values = pair.value; + + if (values && typeof values == 'object') { + if (Object.isArray(values)) + return values.map(toQueryPair.curry(key)).join('&'); + } + return toQueryPair(key, values); + }).join('&'); + }, + + inspect: function() { + return '#'; + }, -// Safari iterates over shadowed properties -if (function() { - var i = 0, Test = function(value) { this.key = value }; - Test.prototype.key = 'foo'; - for (var property in new Test('bar')) i++; - return i > 1; -}()) Hash.prototype._each = function(iterator) { - var cache = []; - for (var key in this) { - var value = this[key]; - if ((value && value == Hash.prototype[key]) || cache.include(key)) continue; - cache.push(key); - var pair = [key, value]; - pair.key = key; - pair.value = value; - iterator(pair); + toJSON: function() { + return Object.toJSON(this.toObject()); + }, + + clone: function() { + return new Hash(this); + } } -}; -ObjectRange = Class.create(); -Object.extend(ObjectRange.prototype, Enumerable); -Object.extend(ObjectRange.prototype, { +})()); + +Hash.prototype.toTemplateReplacements = Hash.prototype.toObject; +Hash.from = $H; +var ObjectRange = Class.create(Enumerable, { initialize: function(start, end, exclusive) { this.start = start; this.end = end; @@ -927,7 +1125,7 @@ Object.extend(ObjectRange.prototype, { var $R = function(start, end, exclusive) { return new ObjectRange(start, end, exclusive); -} +}; var Ajax = { getTransport: function() { @@ -939,7 +1137,7 @@ var Ajax = { }, activeRequestCount: 0 -} +}; Ajax.Responders = { responders: [], @@ -959,10 +1157,10 @@ Ajax.Responders = { dispatch: function(callback, request, transport, json) { this.each(function(responder) { - if (typeof responder[callback] == 'function') { + if (Object.isFunction(responder[callback])) { try { responder[callback].apply(responder, [request, transport, json]); - } catch (e) {} + } catch (e) { } } }); } @@ -971,42 +1169,35 @@ Ajax.Responders = { Object.extend(Ajax.Responders, Enumerable); Ajax.Responders.register({ - onCreate: function() { - Ajax.activeRequestCount++; - }, - onComplete: function() { - Ajax.activeRequestCount--; - } + onCreate: function() { Ajax.activeRequestCount++ }, + onComplete: function() { Ajax.activeRequestCount-- } }); -Ajax.Base = function() {}; -Ajax.Base.prototype = { - setOptions: function(options) { +Ajax.Base = Class.create({ + initialize: function(options) { this.options = { method: 'post', asynchronous: true, contentType: 'application/x-www-form-urlencoded', encoding: 'UTF-8', - parameters: '' - } - Object.extend(this.options, options || {}); + parameters: '', + evalJSON: true, + evalJS: true + }; + Object.extend(this.options, options || { }); this.options.method = this.options.method.toLowerCase(); - if (typeof this.options.parameters == 'string') + if (Object.isString(this.options.parameters)) this.options.parameters = this.options.parameters.toQueryParams(); } -} - -Ajax.Request = Class.create(); -Ajax.Request.Events = - ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; +}); -Ajax.Request.prototype = Object.extend(new Ajax.Base(), { +Ajax.Request = Class.create(Ajax.Base, { _complete: false, - initialize: function(url, options) { + initialize: function($super, url, options) { + $super(options); this.transport = Ajax.getTransport(); - this.setOptions(options); this.request(url); }, @@ -1023,7 +1214,7 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), { this.parameters = params; - if (params = Hash.toQueryString(params)) { + if (params = Object.toQueryString(params)) { // when GET, append parameters to URL if (this.method == 'get') this.url += (this.url.include('?') ? '&' : '?') + params; @@ -1032,14 +1223,14 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), { } try { - if (this.options.onCreate) this.options.onCreate(this.transport); - Ajax.Responders.dispatch('onCreate', this, this.transport); + var response = new Ajax.Response(this); + if (this.options.onCreate) this.options.onCreate(response); + Ajax.Responders.dispatch('onCreate', this, response); this.transport.open(this.method.toUpperCase(), this.url, this.options.asynchronous); - if (this.options.asynchronous) - setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10); + if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); this.transport.onreadystatechange = this.onStateChange.bind(this); this.setRequestHeaders(); @@ -1087,7 +1278,7 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), { if (typeof this.options.requestHeaders == 'object') { var extras = this.options.requestHeaders; - if (typeof extras.push == 'function') + if (Object.isFunction(extras.push)) for (var i = 0, length = extras.length; i < length; i += 2) headers[extras[i]] = extras[i+1]; else @@ -1099,33 +1290,39 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), { }, success: function() { - return !this.transport.status - || (this.transport.status >= 200 && this.transport.status < 300); + var status = this.getStatus(); + return !status || (status >= 200 && status < 300); + }, + + getStatus: function() { + try { + return this.transport.status || 0; + } catch (e) { return 0 } }, respondToReadyState: function(readyState) { - var state = Ajax.Request.Events[readyState]; - var transport = this.transport, json = this.evalJSON(); + var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); if (state == 'Complete') { try { this._complete = true; - (this.options['on' + this.transport.status] + (this.options['on' + response.status] || this.options['on' + (this.success() ? 'Success' : 'Failure')] - || Prototype.emptyFunction)(transport, json); + || Prototype.emptyFunction)(response, response.headerJSON); } catch (e) { this.dispatchException(e); } - var contentType = this.getHeader('Content-type'); - if (contentType && contentType.strip(). - match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i)) - this.evalResponse(); + var contentType = response.getHeader('Content-type'); + if (this.options.evalJS == 'force' + || (this.options.evalJS && contentType + && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) + this.evalResponse(); } try { - (this.options['on' + state] || Prototype.emptyFunction)(transport, json); - Ajax.Responders.dispatch('on' + state, this, transport, json); + (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); + Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); } catch (e) { this.dispatchException(e); } @@ -1142,13 +1339,6 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), { } catch (e) { return null } }, - evalJSON: function() { - try { - var json = this.getHeader('X-JSON'); - return json ? json.evalJSON() : null; - } catch (e) { return null } - }, - evalResponse: function() { try { return eval((this.transport.responseText || '').unfilterJSON()); @@ -1163,57 +1353,129 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), { } }); -Ajax.Updater = Class.create(); +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; -Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { - initialize: function(container, url, options) { - this.container = { - success: (container.success || container), - failure: (container.failure || (container.success ? null : container)) +Ajax.Response = Class.create({ + initialize: function(request){ + this.request = request; + var transport = this.transport = request.transport, + readyState = this.readyState = transport.readyState; + + if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { + this.status = this.getStatus(); + this.statusText = this.getStatusText(); + this.responseText = String.interpret(transport.responseText); + this.headerJSON = this._getHeaderJSON(); } - this.transport = Ajax.getTransport(); - this.setOptions(options); + if(readyState == 4) { + var xml = transport.responseXML; + this.responseXML = xml === undefined ? null : xml; + this.responseJSON = this._getResponseJSON(); + } + }, - var onComplete = this.options.onComplete || Prototype.emptyFunction; - this.options.onComplete = (function(transport, param) { - this.updateContent(); - onComplete(transport, param); - }).bind(this); + status: 0, + statusText: '', - this.request(url); + getStatus: Ajax.Request.prototype.getStatus, + + getStatusText: function() { + try { + return this.transport.statusText || ''; + } catch (e) { return '' } }, - updateContent: function() { - var receiver = this.container[this.success() ? 'success' : 'failure']; - var response = this.transport.responseText; + getHeader: Ajax.Request.prototype.getHeader, - if (!this.options.evalScripts) response = response.stripScripts(); + getAllHeaders: function() { + try { + return this.getAllResponseHeaders(); + } catch (e) { return null } + }, - if (receiver = $(receiver)) { - if (this.options.insertion) - new this.options.insertion(receiver, response); - else - receiver.update(response); - } + getResponseHeader: function(name) { + return this.transport.getResponseHeader(name); + }, + + getAllResponseHeaders: function() { + return this.transport.getAllResponseHeaders(); + }, + + _getHeaderJSON: function() { + var json = this.getHeader('X-JSON'); + if (!json) return null; + json = decodeURIComponent(escape(json)); + try { + return json.evalJSON(this.request.options.sanitizeJSON); + } catch (e) { + this.request.dispatchException(e); + } + }, + + _getResponseJSON: function() { + var options = this.request.options; + if (!options.evalJSON || (options.evalJSON != 'force' && + !(this.getHeader('Content-type') || '').include('application/json'))) + return null; + try { + return this.transport.responseText.evalJSON(options.sanitizeJSON); + } catch (e) { + this.request.dispatchException(e); + } + } +}); + +Ajax.Updater = Class.create(Ajax.Request, { + initialize: function($super, container, url, options) { + this.container = { + success: (container.success || container), + failure: (container.failure || (container.success ? null : container)) + }; + + options = options || { }; + var onComplete = options.onComplete; + options.onComplete = (function(response, param) { + this.updateContent(response.responseText); + if (Object.isFunction(onComplete)) onComplete(response, param); + }).bind(this); + + $super(url, options); + }, + + updateContent: function(responseText) { + var receiver = this.container[this.success() ? 'success' : 'failure'], + options = this.options; + + if (!options.evalScripts) responseText = responseText.stripScripts(); + + if (receiver = $(receiver)) { + if (options.insertion) { + if (Object.isString(options.insertion)) { + var insertion = { }; insertion[options.insertion] = responseText; + receiver.insert(insertion); + } + else options.insertion(receiver, responseText); + } + else receiver.update(responseText); + } if (this.success()) { - if (this.onComplete) - setTimeout(this.onComplete.bind(this), 10); + if (this.onComplete) this.onComplete.bind(this).defer(); } } }); -Ajax.PeriodicalUpdater = Class.create(); -Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { - initialize: function(container, url, options) { - this.setOptions(options); +Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { + initialize: function($super, container, url, options) { + $super(options); this.onComplete = this.options.onComplete; this.frequency = (this.options.frequency || 2); this.decay = (this.options.decay || 1); - this.updater = {}; + this.updater = { }; this.container = container; this.url = url; @@ -1231,15 +1493,14 @@ Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { (this.onComplete || Prototype.emptyFunction).apply(this, arguments); }, - updateComplete: function(request) { + updateComplete: function(response) { if (this.options.decay) { - this.decay = (request.responseText == this.lastText ? + this.decay = (response.responseText == this.lastText ? this.decay * this.options.decay : 1); - this.lastText = request.responseText; + this.lastText = response.responseText; } - this.timer = setTimeout(this.onTimerEvent.bind(this), - this.decay * this.frequency * 1000); + this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); }, onTimerEvent: function() { @@ -1252,7 +1513,7 @@ function $(element) { elements.push($(arguments[i])); return elements; } - if (typeof element == 'string') + if (Object.isString(element)) element = document.getElementById(element); return Element.extend(element); } @@ -1263,67 +1524,51 @@ if (Prototype.BrowserFeatures.XPath) { var query = document.evaluate(expression, $(parentElement) || document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); for (var i = 0, length = query.snapshotLength; i < length; i++) - results.push(query.snapshotItem(i)); + results.push(Element.extend(query.snapshotItem(i))); return results; }; - - document.getElementsByClassName = function(className, parentElement) { - var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]"; - return document._getElementsByXPath(q, parentElement); - } - -} else document.getElementsByClassName = function(className, parentElement) { - var children = ($(parentElement) || document.body).getElementsByTagName('*'); - var elements = [], child, pattern = new RegExp("(^|\\s)" + className + "(\\s|$)"); - for (var i = 0, length = children.length; i < length; i++) { - child = children[i]; - var elementClassName = child.className; - if (elementClassName.length == 0) continue; - if (elementClassName == className || elementClassName.match(pattern)) - elements.push(Element.extend(child)); - } - return elements; -}; +} /*--------------------------------------------------------------------------*/ -if (!window.Element) var Element = {}; - -Element.extend = function(element) { - var F = Prototype.BrowserFeatures; - if (!element || !element.tagName || element.nodeType == 3 || - element._extended || F.SpecificElementExtensions || element == window) - return element; - - var methods = {}, tagName = element.tagName, cache = Element.extend.cache, - T = Element.Methods.ByTag; - - // extend methods for all tags (Safari doesn't need this) - if (!F.ElementExtensions) { - Object.extend(methods, Element.Methods), - Object.extend(methods, Element.Methods.Simulated); - } - - // extend methods for specific tags - if (T[tagName]) Object.extend(methods, T[tagName]); - - for (var property in methods) { - var value = methods[property]; - if (typeof value == 'function' && !(property in element)) - element[property] = cache.findOrStore(value); - } - - element._extended = Prototype.emptyFunction; - return element; -}; +if (!window.Node) var Node = { }; + +if (!Node.ELEMENT_NODE) { + // DOM level 2 ECMAScript Language Binding + Object.extend(Node, { + ELEMENT_NODE: 1, + ATTRIBUTE_NODE: 2, + TEXT_NODE: 3, + CDATA_SECTION_NODE: 4, + ENTITY_REFERENCE_NODE: 5, + ENTITY_NODE: 6, + PROCESSING_INSTRUCTION_NODE: 7, + COMMENT_NODE: 8, + DOCUMENT_NODE: 9, + DOCUMENT_TYPE_NODE: 10, + DOCUMENT_FRAGMENT_NODE: 11, + NOTATION_NODE: 12 + }); +} -Element.extend.cache = { - findOrStore: function(value) { - return this[value] = this[value] || function() { - return value.apply(null, [this].concat($A(arguments))); +(function() { + var element = this.Element; + this.Element = function(tagName, attributes) { + attributes = attributes || { }; + tagName = tagName.toLowerCase(); + var cache = Element.cache; + if (Prototype.Browser.IE && attributes.name) { + tagName = '<' + tagName + ' name="' + attributes.name + '">'; + delete attributes.name; + return Element.writeAttribute(document.createElement(tagName), attributes); } - } -}; + if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); + return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); + }; + Object.extend(this.Element, element || { }); +}).call(window); + +Element.cache = { }; Element.Methods = { visible: function(element) { @@ -1352,28 +1597,74 @@ Element.Methods = { return element; }, - update: function(element, html) { - html = typeof html == 'undefined' ? '' : html.toString(); - $(element).innerHTML = html.stripScripts(); - setTimeout(function() {html.evalScripts()}, 10); + update: function(element, content) { + element = $(element); + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) return element.update().insert(content); + content = Object.toHTML(content); + element.innerHTML = content.stripScripts(); + content.evalScripts.bind(content).defer(); return element; }, - replace: function(element, html) { + replace: function(element, content) { element = $(element); - html = typeof html == 'undefined' ? '' : html.toString(); - if (element.outerHTML) { - element.outerHTML = html.stripScripts(); - } else { + if (content && content.toElement) content = content.toElement(); + else if (!Object.isElement(content)) { + content = Object.toHTML(content); var range = element.ownerDocument.createRange(); - range.selectNodeContents(element); - element.parentNode.replaceChild( - range.createContextualFragment(html.stripScripts()), element); + range.selectNode(element); + content.evalScripts.bind(content).defer(); + content = range.createContextualFragment(content.stripScripts()); + } + element.parentNode.replaceChild(content, element); + return element; + }, + + insert: function(element, insertions) { + element = $(element); + + if (Object.isString(insertions) || Object.isNumber(insertions) || + Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) + insertions = {bottom:insertions}; + + var content, t, range; + + for (position in insertions) { + content = insertions[position]; + position = position.toLowerCase(); + t = Element._insertionTranslations[position]; + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + t.insert(element, content); + continue; + } + + content = Object.toHTML(content); + + range = element.ownerDocument.createRange(); + t.initializeRange(element, range); + t.insert(element, range.createContextualFragment(content.stripScripts())); + + content.evalScripts.bind(content).defer(); } - setTimeout(function() {html.evalScripts()}, 10); + return element; }, + wrap: function(element, wrapper, attributes) { + element = $(element); + if (Object.isElement(wrapper)) + $(wrapper).writeAttribute(attributes || { }); + else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); + else wrapper = new Element('div', wrapper); + if (element.parentNode) + element.parentNode.replaceChild(wrapper, element); + wrapper.appendChild(element); + return wrapper; + }, + inspect: function(element) { element = $(element); var result = '<' + element.tagName.toLowerCase(); @@ -1429,7 +1720,7 @@ Element.Methods = { }, match: function(element, selector) { - if (typeof selector == 'string') + if (Object.isString(selector)) selector = new Selector(selector); return selector.match($(element)); }, @@ -1466,28 +1757,58 @@ Element.Methods = { nextSiblings[index || 0]; }, - getElementsBySelector: function() { + select: function() { var args = $A(arguments), element = $(args.shift()); return Selector.findChildElements(element, args); }, - getElementsByClassName: function(element, className) { - return document.getElementsByClassName(className, element); + adjacent: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element.parentNode, args).without(element); + }, + + identify: function(element) { + element = $(element); + var id = element.readAttribute('id'), self = arguments.callee; + if (id) return id; + do { id = 'anonymous_element_' + self.counter++ } while ($(id)); + element.writeAttribute('id', id); + return id; }, readAttribute: function(element, name) { element = $(element); if (Prototype.Browser.IE) { - if (!element.attributes) return null; - var t = Element._attributeTranslations; + var t = Element._attributeTranslations.read; if (t.values[name]) return t.values[name](element, name); - if (t.names[name]) name = t.names[name]; - var attribute = element.attributes[name]; - return attribute ? attribute.nodeValue : null; + if (t.names[name]) name = t.names[name]; + if (name.include(':')) { + return (!element.attributes || !element.attributes[name]) ? null : + element.attributes[name].value; + } } return element.getAttribute(name); }, + writeAttribute: function(element, name, value) { + element = $(element); + var attributes = { }, t = Element._attributeTranslations.write; + + if (typeof name == 'object') attributes = name; + else attributes[name] = value === undefined ? true : value; + + for (var attr in attributes) { + var name = t.names[attr] || attr, value = attributes[attr]; + if (t.values[attr]) name = t.values[attr](element, value); + if (value === false || value === null) + element.removeAttribute(name); + else if (value === true) + element.setAttribute(name, name); + else element.setAttribute(name, value); + } + return element; + }, + getHeight: function(element) { return $(element).getDimensions().height; }, @@ -1503,39 +1824,28 @@ Element.Methods = { hasClassName: function(element, className) { if (!(element = $(element))) return; var elementClassName = element.className; - if (elementClassName.length == 0) return false; - if (elementClassName == className || - elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) - return true; - return false; + return (elementClassName.length > 0 && (elementClassName == className || + new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); }, addClassName: function(element, className) { if (!(element = $(element))) return; - Element.classNames(element).add(className); + if (!element.hasClassName(className)) + element.className += (element.className ? ' ' : '') + className; return element; }, removeClassName: function(element, className) { if (!(element = $(element))) return; - Element.classNames(element).remove(className); + element.className = element.className.replace( + new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); return element; }, toggleClassName: function(element, className) { if (!(element = $(element))) return; - Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className); - return element; - }, - - observe: function() { - Event.observe.apply(Event, arguments); - return $A(arguments).first(); - }, - - stopObserving: function() { - Event.stopObserving.apply(Event, arguments); - return $A(arguments).first(); + return element[element.hasClassName(className) ? + 'removeClassName' : 'addClassName'](className); }, // removes whitespace-only text node children @@ -1557,6 +1867,20 @@ Element.Methods = { descendantOf: function(element, ancestor) { element = $(element), ancestor = $(ancestor); + + if (element.compareDocumentPosition) + return (element.compareDocumentPosition(ancestor) & 8) === 8; + + if (element.sourceIndex && !Prototype.Browser.Opera) { + var e = element.sourceIndex, a = ancestor.sourceIndex, + nextAncestor = ancestor.nextSibling; + if (!nextAncestor) { + do { ancestor = ancestor.parentNode; } + while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode); + } + if (nextAncestor) return (e > a && e < nextAncestor.sourceIndex); + } + while (element = element.parentNode) if (element == ancestor) return true; return false; @@ -1564,7 +1888,7 @@ Element.Methods = { scrollTo: function(element) { element = $(element); - var pos = Position.cumulativeOffset(element); + var pos = element.cumulativeOffset(); window.scrollTo(pos[0], pos[1]); return element; }, @@ -1585,16 +1909,20 @@ Element.Methods = { return $(element).getStyle('opacity'); }, - setStyle: function(element, styles, camelized) { + setStyle: function(element, styles) { element = $(element); - var elementStyle = element.style; - + var elementStyle = element.style, match; + if (Object.isString(styles)) { + element.style.cssText += ';' + styles; + return styles.include('opacity') ? + element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; + } for (var property in styles) - if (property == 'opacity') element.setOpacity(styles[property]) + if (property == 'opacity') element.setOpacity(styles[property]); else elementStyle[(property == 'float' || property == 'cssFloat') ? (elementStyle.styleFloat === undefined ? 'cssFloat' : 'styleFloat') : - (camelized ? property : property.camelize())] = styles[property]; + property] = styles[property]; return element; }, @@ -1661,8 +1989,8 @@ Element.Methods = { makeClipping: function(element) { element = $(element); if (element._overflow) return element; - element._overflow = element.style.overflow || 'auto'; - if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') + element._overflow = Element.getStyle(element, 'overflow') || 'auto'; + if (element._overflow !== 'hidden') element.style.overflow = 'hidden'; return element; }, @@ -1673,14 +2001,216 @@ Element.Methods = { element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; element._overflow = null; return element; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if (element.tagName == 'BODY') break; + var p = Element.getStyle(element, 'position'); + if (p == 'relative' || p == 'absolute') break; + } + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + absolutize: function(element) { + element = $(element); + if (element.getStyle('position') == 'absolute') return; + // Position.prepare(); // To be done manually by Scripty when it needs it. + + var offsets = element.positionedOffset(); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.width = width + 'px'; + element.style.height = height + 'px'; + return element; + }, + + relativize: function(element) { + element = $(element); + if (element.getStyle('position') == 'relative') return; + // Position.prepare(); // To be done manually by Scripty when it needs it. + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + return element; + }, + + cumulativeScrollOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + getOffsetParent: function(element) { + if (element.offsetParent) return $(element.offsetParent); + if (element == document.body) return $(element); + + while ((element = element.parentNode) && element != document.body) + if (Element.getStyle(element, 'position') != 'static') + return $(element); + + return $(document.body); + }, + + viewportOffset: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + // Safari fix + if (element.offsetParent == document.body && + Element.getStyle(element, 'position') == 'absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + if (!Prototype.Browser.Opera || element.tagName == 'BODY') { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + + return Element._returnOffset(valueL, valueT); + }, + + clonePosition: function(element, source) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || { }); + + // find page position of source + source = $(source); + var p = source.viewportOffset(); + + // find coordinate system to use + element = $(element); + var delta = [0, 0]; + var parent = null; + // delta [0,0] will do fine with position: fixed elements, + // position:absolute needs offsetParent deltas + if (Element.getStyle(element, 'position') == 'absolute') { + parent = element.getOffsetParent(); + delta = parent.viewportOffset(); + } + + // correct by body offsets (fixes Safari) + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + // set position + if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if (options.setWidth) element.style.width = source.offsetWidth + 'px'; + if (options.setHeight) element.style.height = source.offsetHeight + 'px'; + return element; } }; +Element.Methods.identify.counter = 1; + Object.extend(Element.Methods, { - childOf: Element.Methods.descendantOf, + getElementsBySelector: Element.Methods.select, childElements: Element.Methods.immediateDescendants }); +Element._attributeTranslations = { + write: { + names: { + className: 'class', + htmlFor: 'for' + }, + values: { } + } +}; + + +if (!document.createRange || Prototype.Browser.Opera) { + Element.Methods.insert = function(element, insertions) { + element = $(element); + + if (Object.isString(insertions) || Object.isNumber(insertions) || + Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) + insertions = { bottom: insertions }; + + var t = Element._insertionTranslations, content, position, pos, tagName; + + for (position in insertions) { + content = insertions[position]; + position = position.toLowerCase(); + pos = t[position]; + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + pos.insert(element, content); + continue; + } + + content = Object.toHTML(content); + tagName = ((position == 'before' || position == 'after') + ? element.parentNode : element).tagName.toUpperCase(); + + if (t.tags[tagName]) { + var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + if (position == 'top' || position == 'after') fragments.reverse(); + fragments.each(pos.insert.curry(element)); + } + else element.insertAdjacentHTML(pos.adjacency, content.stripScripts()); + + content.evalScripts.bind(content).defer(); + } + + return element; + }; +} + if (Prototype.Browser.Opera) { Element.Methods._getStyle = Element.Methods.getStyle; Element.Methods.getStyle = function(element, style) { @@ -1693,8 +2223,28 @@ if (Prototype.Browser.Opera) { default: return Element._getStyle(element, style); } }; + Element.Methods._readAttribute = Element.Methods.readAttribute; + Element.Methods.readAttribute = function(element, attribute) { + if (attribute == 'title') return element.title; + return Element._readAttribute(element, attribute); + }; } + else if (Prototype.Browser.IE) { + $w('positionedOffset getOffsetParent viewportOffset').each(function(method) { + Element.Methods[method] = Element.Methods[method].wrap( + function(proceed, element) { + element = $(element); + var position = element.getStyle('position'); + if (position != 'static') return proceed(element); + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + }); + Element.Methods.getStyle = function(element, style) { element = $(element); style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); @@ -1709,56 +2259,118 @@ else if (Prototype.Browser.IE) { if (value == 'auto') { if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) - return element['offset'+style.capitalize()] + 'px'; + return element['offset' + style.capitalize()] + 'px'; return null; } return value; }; Element.Methods.setOpacity = function(element, value) { + function stripAlpha(filter){ + return filter.replace(/alpha\([^\)]*\)/gi,''); + } element = $(element); + var currentStyle = element.currentStyle; + if ((currentStyle && !currentStyle.hasLayout) || + (!currentStyle && element.style.zoom == 'normal')) + element.style.zoom = 1; + var filter = element.getStyle('filter'), style = element.style; if (value == 1 || value === '') { - style.filter = filter.replace(/alpha\([^\)]*\)/gi,''); + (filter = stripAlpha(filter)) ? + style.filter = filter : style.removeAttribute('filter'); return element; } else if (value < 0.00001) value = 0; - style.filter = filter.replace(/alpha\([^\)]*\)/gi, '') + + style.filter = stripAlpha(filter) + 'alpha(opacity=' + (value * 100) + ')'; return element; }; - // IE is missing .innerHTML support for TABLE-related elements - Element.Methods.update = function(element, html) { - element = $(element); - html = typeof html == 'undefined' ? '' : html.toString(); - var tagName = element.tagName.toUpperCase(); - if (['THEAD','TBODY','TR','TD'].include(tagName)) { - var div = document.createElement('div'); - switch (tagName) { - case 'THEAD': - case 'TBODY': - div.innerHTML = '
' + html.stripScripts() + '
'; - depth = 2; - break; - case 'TR': - div.innerHTML = '' + html.stripScripts() + '
'; - depth = 3; - break; - case 'TD': - div.innerHTML = '
' + html.stripScripts() + '
'; - depth = 4; + Element._attributeTranslations = { + read: { + names: { + 'class': 'className', + 'for': 'htmlFor' + }, + values: { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + _getAttrNode: function(element, attribute) { + var node = element.getAttributeNode(attribute); + return node ? node.value : ""; + }, + _getEv: function(element, attribute) { + var attribute = element.getAttribute(attribute); + return attribute ? attribute.toString().slice(23, -2) : null; + }, + _flag: function(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + }, + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + title: function(element) { + return element.title; + } } - $A(element.childNodes).each(function(node) { element.removeChild(node) }); - depth.times(function() { div = div.firstChild }); - $A(div.childNodes).each(function(node) { element.appendChild(node) }); - } else { - element.innerHTML = html.stripScripts(); } - setTimeout(function() { html.evalScripts() }, 10); - return element; - } + }; + + Element._attributeTranslations.write = { + names: Object.clone(Element._attributeTranslations.read.names), + values: { + checked: function(element, value) { + element.checked = !!value; + }, + + style: function(element, value) { + element.style.cssText = value ? value : ''; + } + } + }; + + Element._attributeTranslations.has = {}; + + $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + + 'encType maxLength readOnly longDesc').each(function(attr) { + Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; + Element._attributeTranslations.has[attr.toLowerCase()] = attr; + }); + + (function(v) { + Object.extend(v, { + href: v._getAttr, + src: v._getAttr, + type: v._getAttr, + action: v._getAttrNode, + disabled: v._flag, + checked: v._flag, + readonly: v._flag, + multiple: v._flag, + onload: v._getEv, + onunload: v._getEv, + onclick: v._getEv, + ondblclick: v._getEv, + onmousedown: v._getEv, + onmouseup: v._getEv, + onmouseover: v._getEv, + onmousemove: v._getEv, + onmouseout: v._getEv, + onfocus: v._getEv, + onblur: v._getEv, + onkeypress: v._getEv, + onkeydown: v._getEv, + onkeyup: v._getEv, + onsubmit: v._getEv, + onreset: v._getEv, + onselect: v._getEv, + onchange: v._getEv + }); + })(Element._attributeTranslations.read.values); } -else if (Prototype.Browser.Gecko) { + +else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { Element.Methods.setOpacity = function(element, value) { element = $(element); element.style.opacity = (value == 1) ? 0.999999 : @@ -1767,68 +2379,219 @@ else if (Prototype.Browser.Gecko) { }; } -Element._attributeTranslations = { - names: { - colspan: "colSpan", - rowspan: "rowSpan", - valign: "vAlign", - datetime: "dateTime", - accesskey: "accessKey", - tabindex: "tabIndex", - enctype: "encType", - maxlength: "maxLength", - readonly: "readOnly", - longdesc: "longDesc" - }, - values: { - _getAttr: function(element, attribute) { - return element.getAttribute(attribute, 2); +else if (Prototype.Browser.WebKit) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + + if (value == 1) + if(element.tagName == 'IMG' && element.width) { + element.width++; element.width--; + } else try { + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch (e) { } + + return element; + }; + + // Safari returns margins on body which is incorrect if the child is absolutely + // positioned. For performance reasons, redefine Position.cumulativeOffset for + // KHTML/WebKit only. + Element.Methods.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return Element._returnOffset(valueL, valueT); + }; +} + +if (Prototype.Browser.IE || Prototype.Browser.Opera) { + // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements + Element.Methods.update = function(element, content) { + element = $(element); + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) return element.update().insert(content); + + content = Object.toHTML(content); + var tagName = element.tagName.toUpperCase(); + + if (tagName in Element._insertionTranslations.tags) { + $A(element.childNodes).each(function(node) { element.removeChild(node) }); + Element._getContentFromAnonymousElement(tagName, content.stripScripts()) + .each(function(node) { element.appendChild(node) }); + } + else element.innerHTML = content.stripScripts(); + + content.evalScripts.bind(content).defer(); + return element; + }; +} + +if (document.createElement('div').outerHTML) { + Element.Methods.replace = function(element, content) { + element = $(element); + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + element.parentNode.replaceChild(content, element); + return element; + } + + content = Object.toHTML(content); + var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); + + if (Element._insertionTranslations.tags[tagName]) { + var nextSibling = element.next(); + var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + parent.removeChild(element); + if (nextSibling) + fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); + else + fragments.each(function(node) { parent.appendChild(node) }); + } + else element.outerHTML = content.stripScripts(); + + content.evalScripts.bind(content).defer(); + return element; + }; +} + +Element._returnOffset = function(l, t) { + var result = [l, t]; + result.left = l; + result.top = t; + return result; +}; + +Element._getContentFromAnonymousElement = function(tagName, html) { + var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; + div.innerHTML = t[0] + html + t[1]; + t[2].times(function() { div = div.firstChild }); + return $A(div.childNodes); +}; + +Element._insertionTranslations = { + before: { + adjacency: 'beforeBegin', + insert: function(element, node) { + element.parentNode.insertBefore(node, element); }, - _flag: function(element, attribute) { - return $(element).hasAttribute(attribute) ? attribute : null; + initializeRange: function(element, range) { + range.setStartBefore(element); + } + }, + top: { + adjacency: 'afterBegin', + insert: function(element, node) { + element.insertBefore(node, element.firstChild); }, - style: function(element) { - return element.style.cssText.toLowerCase(); + initializeRange: function(element, range) { + range.selectNodeContents(element); + range.collapse(true); + } + }, + bottom: { + adjacency: 'beforeEnd', + insert: function(element, node) { + element.appendChild(node); + } + }, + after: { + adjacency: 'afterEnd', + insert: function(element, node) { + element.parentNode.insertBefore(node, element.nextSibling); }, - title: function(element) { - var node = element.getAttributeNode('title'); - return node.specified ? node.nodeValue : null; + initializeRange: function(element, range) { + range.setStartAfter(element); } + }, + tags: { + TABLE: ['', '
', 1], + TBODY: ['', '
', 2], + TR: ['', '
', 3], + TD: ['
', '
', 4], + SELECT: ['', 1] } }; (function() { - Object.extend(this, { - href: this._getAttr, - src: this._getAttr, - type: this._getAttr, - disabled: this._flag, - checked: this._flag, - readonly: this._flag, - multiple: this._flag + this.bottom.initializeRange = this.top.initializeRange; + Object.extend(this.tags, { + THEAD: this.tags.TBODY, + TFOOT: this.tags.TBODY, + TH: this.tags.TD }); -}).call(Element._attributeTranslations.values); +}).call(Element._insertionTranslations); Element.Methods.Simulated = { hasAttribute: function(element, attribute) { - var t = Element._attributeTranslations, node; - attribute = t.names[attribute] || attribute; - node = $(element).getAttributeNode(attribute); + attribute = Element._attributeTranslations.has[attribute] || attribute; + var node = $(element).getAttributeNode(attribute); return node && node.specified; } }; -Element.Methods.ByTag = {}; +Element.Methods.ByTag = { }; Object.extend(Element, Element.Methods); if (!Prototype.BrowserFeatures.ElementExtensions && - document.createElement('div').__proto__) { - window.HTMLElement = {}; + document.createElement('div').__proto__) { + window.HTMLElement = { }; window.HTMLElement.prototype = document.createElement('div').__proto__; Prototype.BrowserFeatures.ElementExtensions = true; } +Element.extend = (function() { + if (Prototype.BrowserFeatures.SpecificElementExtensions) + return Prototype.K; + + var Methods = { }, ByTag = Element.Methods.ByTag; + + var extend = Object.extend(function(element) { + if (!element || element._extendedByPrototype || + element.nodeType != 1 || element == window) return element; + + var methods = Object.clone(Methods), + tagName = element.tagName, property, value; + + // extend methods for specific tags + if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); + + for (property in methods) { + value = methods[property]; + if (Object.isFunction(value) && !(property in element)) + element[property] = value.methodize(); + } + + element._extendedByPrototype = Prototype.emptyFunction; + return element; + + }, { + refresh: function() { + // extend methods for all tags (Safari doesn't need this) + if (!Prototype.BrowserFeatures.ElementExtensions) { + Object.extend(Methods, Element.Methods); + Object.extend(Methods, Element.Methods.Simulated); + } + } + }); + + extend.refresh(); + return extend; +})(); + Element.hasAttribute = function(element, attribute) { if (element.hasAttribute) return element.hasAttribute(attribute); return Element.Methods.Simulated.hasAttribute(element, attribute); @@ -1853,26 +2616,26 @@ Element.addMethods = function(methods) { methods = arguments[1]; } - if (!tagName) Object.extend(Element.Methods, methods || {}); + if (!tagName) Object.extend(Element.Methods, methods || { }); else { - if (tagName.constructor == Array) tagName.each(extend); + if (Object.isArray(tagName)) tagName.each(extend); else extend(tagName); } function extend(tagName) { tagName = tagName.toUpperCase(); if (!Element.Methods.ByTag[tagName]) - Element.Methods.ByTag[tagName] = {}; + Element.Methods.ByTag[tagName] = { }; Object.extend(Element.Methods.ByTag[tagName], methods); } function copy(methods, destination, onlyIfAbsent) { onlyIfAbsent = onlyIfAbsent || false; - var cache = Element.extend.cache; for (var property in methods) { var value = methods[property]; + if (!Object.isFunction(value)) continue; if (!onlyIfAbsent || !(property in destination)) - destination[property] = cache.findOrStore(value); + destination[property] = value.methodize(); } } @@ -1896,7 +2659,7 @@ Element.addMethods = function(methods) { klass = 'HTML' + tagName.capitalize() + 'Element'; if (window[klass]) return window[klass]; - window[klass] = {}; + window[klass] = { }; window[klass].prototype = document.createElement(tagName).__proto__; return window[klass]; } @@ -1909,153 +2672,48 @@ Element.addMethods = function(methods) { if (F.SpecificElementExtensions) { for (var tag in Element.Methods.ByTag) { var klass = findDOMClass(tag); - if (typeof klass == "undefined") continue; + if (Object.isUndefined(klass)) continue; copy(T[tag], klass.prototype); } } Object.extend(Element, Element.Methods); delete Element.ByTag; -}; - -var Toggle = { display: Element.toggle }; - -/*--------------------------------------------------------------------------*/ -Abstract.Insertion = function(adjacency) { - this.adjacency = adjacency; -} - -Abstract.Insertion.prototype = { - initialize: function(element, content) { - this.element = $(element); - this.content = content.stripScripts(); - - if (this.adjacency && this.element.insertAdjacentHTML) { - try { - this.element.insertAdjacentHTML(this.adjacency, this.content); - } catch (e) { - var tagName = this.element.tagName.toUpperCase(); - if (['TBODY', 'TR'].include(tagName)) { - this.insertContent(this.contentFromAnonymousTable()); - } else { - throw e; - } - } - } else { - this.range = this.element.ownerDocument.createRange(); - if (this.initializeRange) this.initializeRange(); - this.insertContent([this.range.createContextualFragment(this.content)]); - } - - setTimeout(function() {content.evalScripts()}, 10); - }, - - contentFromAnonymousTable: function() { - var div = document.createElement('div'); - div.innerHTML = '' + this.content + '
'; - return $A(div.childNodes[0].childNodes[0].childNodes); - } -} - -var Insertion = new Object(); - -Insertion.Before = Class.create(); -Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { - initializeRange: function() { - this.range.setStartBefore(this.element); - }, - - insertContent: function(fragments) { - fragments.each((function(fragment) { - this.element.parentNode.insertBefore(fragment, this.element); - }).bind(this)); - } -}); - -Insertion.Top = Class.create(); -Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { - initializeRange: function() { - this.range.selectNodeContents(this.element); - this.range.collapse(true); - }, - - insertContent: function(fragments) { - fragments.reverse(false).each((function(fragment) { - this.element.insertBefore(fragment, this.element.firstChild); - }).bind(this)); - } -}); - -Insertion.Bottom = Class.create(); -Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { - initializeRange: function() { - this.range.selectNodeContents(this.element); - this.range.collapse(this.element); - }, - - insertContent: function(fragments) { - fragments.each((function(fragment) { - this.element.appendChild(fragment); - }).bind(this)); - } -}); - -Insertion.After = Class.create(); -Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { - initializeRange: function() { - this.range.setStartAfter(this.element); - }, - - insertContent: function(fragments) { - fragments.each((function(fragment) { - this.element.parentNode.insertBefore(fragment, - this.element.nextSibling); - }).bind(this)); - } -}); - -/*--------------------------------------------------------------------------*/ - -Element.ClassNames = Class.create(); -Element.ClassNames.prototype = { - initialize: function(element) { - this.element = $(element); - }, - - _each: function(iterator) { - this.element.className.split(/\s+/).select(function(name) { - return name.length > 0; - })._each(iterator); - }, + if (Element.extend.refresh) Element.extend.refresh(); + Element.cache = { }; +}; - set: function(className) { - this.element.className = className; +document.viewport = { + getDimensions: function() { + var dimensions = { }; + $w('width height').each(function(d) { + var D = d.capitalize(); + dimensions[d] = self['inner' + D] || + (document.documentElement['client' + D] || document.body['client' + D]); + }); + return dimensions; }, - add: function(classNameToAdd) { - if (this.include(classNameToAdd)) return; - this.set($A(this).concat(classNameToAdd).join(' ')); + getWidth: function() { + return this.getDimensions().width; }, - remove: function(classNameToRemove) { - if (!this.include(classNameToRemove)) return; - this.set($A(this).without(classNameToRemove).join(' ')); + getHeight: function() { + return this.getDimensions().height; }, - toString: function() { - return $A(this).join(' '); + getScrollOffsets: function() { + return Element._returnOffset( + window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, + window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); } }; - -Object.extend(Element.ClassNames.prototype, Enumerable); /* Portions of the Selector class are derived from Jack Slocum’s DomQuery, * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style * license. Please see http://www.yui-ext.com/ for more information. */ -var Selector = Class.create(); - -Selector.prototype = { +var Selector = Class.create({ initialize: function(expression) { this.expression = expression.strip(); this.compileMatcher(); @@ -2063,15 +2721,17 @@ Selector.prototype = { compileMatcher: function() { // Selectors with namespaced attributes can't use the XPath version - if (Prototype.BrowserFeatures.XPath && !(/\[[\w-]*?:/).test(this.expression)) + if (Prototype.BrowserFeatures.XPath && !(/(\[[\w-]*?:|:checked)/).test(this.expression)) return this.compileXPathMatcher(); var e = this.expression, ps = Selector.patterns, h = Selector.handlers, c = Selector.criteria, le, p, m; if (Selector._cache[e]) { - this.matcher = Selector._cache[e]; return; + this.matcher = Selector._cache[e]; + return; } + this.matcher = ["this.matcher = function(root) {", "var r = root, h = Selector.handlers, c = false, n;"]; @@ -2080,7 +2740,7 @@ Selector.prototype = { for (var i in ps) { p = ps[i]; if (m = e.match(p)) { - this.matcher.push(typeof c[i] == 'function' ? c[i](m) : + this.matcher.push(Object.isFunction(c[i]) ? c[i](m) : new Template(c[i]).evaluate(m)); e = e.replace(m[0], ''); break; @@ -2095,7 +2755,7 @@ Selector.prototype = { compileXPathMatcher: function() { var e = this.expression, ps = Selector.patterns, - x = Selector.xpath, le, m; + x = Selector.xpath, le, m; if (Selector._cache[e]) { this.xpath = Selector._cache[e]; return; @@ -2106,7 +2766,7 @@ Selector.prototype = { le = e; for (var i in ps) { if (m = e.match(ps[i])) { - this.matcher.push(typeof x[i] == 'function' ? x[i](m) : + this.matcher.push(Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m)); e = e.replace(m[0], ''); break; @@ -2125,7 +2785,39 @@ Selector.prototype = { }, match: function(element) { - return this.findElements(document).include(element); + this.tokens = []; + + var e = this.expression, ps = Selector.patterns, as = Selector.assertions; + var le, p, m; + + while (e && le !== e && (/\S/).test(e)) { + le = e; + for (var i in ps) { + p = ps[i]; + if (m = e.match(p)) { + // use the Selector.assertions methods unless the selector + // is too complex. + if (as[i]) { + this.tokens.push([i, Object.clone(m)]); + e = e.replace(m[0], ''); + } else { + // reluctantly do a document-wide search + // and look for a match in the array + return this.findElements(document).include(element); + } + } + } + } + + var match = true, name, matches; + for (var i = 0, token; token = this.tokens[i]; i++) { + name = token[0], matches = token[1]; + if (!Selector.assertions[name](element, matches)) { + match = false; break; + } + } + + return match; }, toString: function() { @@ -2135,10 +2827,10 @@ Selector.prototype = { inspect: function() { return "#"; } -}; +}); Object.extend(Selector, { - _cache: {}, + _cache: { }, xpath: { descendant: "//*", @@ -2160,7 +2852,7 @@ Object.extend(Selector, { pseudo: function(m) { var h = Selector.xpath.pseudos[m[1]]; if (!h) return ''; - if (typeof h === 'function') return h(m); + if (Object.isFunction(h)) return h(m); return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); }, operators: { @@ -2189,7 +2881,7 @@ Object.extend(Selector, { le = e; for (var i in p) { if (m = e.match(p[i])) { - v = typeof x[i] == 'function' ? x[i](m) : new Template(x[i]).evaluate(m); + v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m); exclusion.push("(" + v.substring(1, v.length - 1) + ")"); e = e.replace(m[0], ''); break; @@ -2247,7 +2939,7 @@ Object.extend(Selector, { m[3] = (m[5] || m[6]); return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m); }, - pseudo: function(m) { + pseudo: function(m) { if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); }, @@ -2269,9 +2961,33 @@ Object.extend(Selector, { tagName: /^\s*(\*|[\w\-]+)(\b|$)?/, id: /^#([\w\-\*]+)(\b|$)/, className: /^\.([\w\-\*]+)(\b|$)/, - pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|\s|(?=:))/, + pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s)|(?=:))/, attrPresence: /^\[([\w]+)\]/, - attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\]]*?)\4|([^'"][^\]]*?)))?\]/ + attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ + }, + + // for Selector.match and Element#match + assertions: { + tagName: function(element, matches) { + return matches[1].toUpperCase() == element.tagName.toUpperCase(); + }, + + className: function(element, matches) { + return Element.hasClassName(element, matches[1]); + }, + + id: function(element, matches) { + return element.id === matches[1]; + }, + + attrPresence: function(element, matches) { + return Element.hasAttribute(element, matches[1]); + }, + + attr: function(element, matches) { + var nodeValue = Element.readAttribute(element, matches[1]); + return Selector.operators[matches[2]](nodeValue, matches[3]); + } }, handlers: { @@ -2303,7 +3019,7 @@ Object.extend(Selector, { parentNode._counted = true; if (reverse) { for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { - node = nodes[i]; + var node = nodes[i]; if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; } } else { @@ -2390,7 +3106,8 @@ Object.extend(Selector, { id: function(nodes, root, id, combinator) { var targetNode = $(id), h = Selector.handlers; - if (!nodes && root == document) return targetNode ? [targetNode] : []; + if (!targetNode) return []; + if (!nodes && root == document) return [targetNode]; if (nodes) { if (combinator) { if (combinator == 'child') { @@ -2430,6 +3147,7 @@ Object.extend(Selector, { }, attrPresence: function(nodes, root, attr) { + if (!nodes) nodes = root.getElementsByTagName("*"); var results = []; for (var i = 0, node; node = nodes[i]; i++) if (Element.hasAttribute(node, attr)) results.push(node); @@ -2598,7 +3316,7 @@ Object.extend(Selector, { }, findElement: function(elements, expression, index) { - if (typeof expression == 'number') { + if (Object.isNumber(expression)) { index = expression; expression = false; } return Selector.matchElements(elements, expression || '*')[index || 0]; @@ -2627,13 +3345,19 @@ var Form = { return form; }, - serializeElements: function(elements, getHash) { - var data = elements.inject({}, function(result, element) { + serializeElements: function(elements, options) { + if (typeof options != 'object') options = { hash: !!options }; + else if (options.hash === undefined) options.hash = true; + var key, value, submitted = false, submit = options.submit; + + var data = elements.inject({ }, function(result, element) { if (!element.disabled && element.name) { - var key = element.name, value = $(element).getValue(); - if (value != null) { - if (key in result) { - if (result[key].constructor != Array) result[key] = [result[key]]; + key = element.name; value = $(element).getValue(); + if (value != null && (element.type != 'submit' || (!submitted && + submit !== false && (!submit || key == submit) && (submitted = true)))) { + if (key in result) { + // a key is already present; construct an array of values + if (!Object.isArray(result[key])) result[key] = [result[key]]; result[key].push(value); } else result[key] = value; @@ -2642,13 +3366,13 @@ var Form = { return result; }); - return getHash ? data : Hash.toQueryString(data); + return options.hash ? data : Object.toQueryString(data); } }; Form.Methods = { - serialize: function(form, getHash) { - return Form.serializeElements(Form.getElements(form), getHash); + serialize: function(form, options) { + return Form.serializeElements(Form.getElements(form), options); }, getElements: function(form) { @@ -2690,9 +3414,15 @@ Form.Methods = { }, findFirstElement: function(form) { - return $(form).getElements().find(function(element) { - return element.type != 'hidden' && !element.disabled && - ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + var elements = $(form).getElements().findAll(function(element) { + return 'hidden' != element.type && !element.disabled; + }); + var firstByIndex = elements.findAll(function(element) { + return element.hasAttribute('tabIndex') && element.tabIndex >= 0; + }).sortBy(function(element) { return element.tabIndex }).first(); + + return firstByIndex ? firstByIndex : elements.find(function(element) { + return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); }); }, @@ -2703,22 +3433,23 @@ Form.Methods = { }, request: function(form, options) { - form = $(form), options = Object.clone(options || {}); + form = $(form), options = Object.clone(options || { }); - var params = options.parameters; + var params = options.parameters, action = form.readAttribute('action') || ''; + if (action.blank()) action = window.location.href; options.parameters = form.serialize(true); if (params) { - if (typeof params == 'string') params = params.toQueryParams(); + if (Object.isString(params)) params = params.toQueryParams(); Object.extend(options.parameters, params); } if (form.hasAttribute('method') && !options.method) options.method = form.method; - return new Ajax.Request(form.readAttribute('action'), options); + return new Ajax.Request(action, options); } -} +}; /*--------------------------------------------------------------------------*/ @@ -2732,7 +3463,7 @@ Form.Element = { $(element).select(); return element; } -} +}; Form.Element.Methods = { serialize: function(element) { @@ -2740,9 +3471,9 @@ Form.Element.Methods = { if (!element.disabled && element.name) { var value = element.getValue(); if (value != undefined) { - var pair = {}; + var pair = { }; pair[element.name] = value; - return Hash.toQueryString(pair); + return Object.toQueryString(pair); } } return ''; @@ -2754,6 +3485,13 @@ Form.Element.Methods = { return Form.Element.Serializers[method](element); }, + setValue: function(element, value) { + element = $(element); + var method = element.tagName.toLowerCase(); + Form.Element.Serializers[method](element, value); + return element; + }, + clear: function(element) { $(element).value = ''; return element; @@ -2768,9 +3506,9 @@ Form.Element.Methods = { try { element.focus(); if (element.select && (element.tagName.toLowerCase() != 'input' || - !['button', 'reset', 'submit'].include(element.type))) + !['button', 'reset', 'submit'].include(element.type))) element.select(); - } catch (e) {} + } catch (e) { } return element; }, @@ -2786,7 +3524,7 @@ Form.Element.Methods = { element.disabled = false; return element; } -} +}; /*--------------------------------------------------------------------------*/ @@ -2796,27 +3534,44 @@ var $F = Form.Element.Methods.getValue; /*--------------------------------------------------------------------------*/ Form.Element.Serializers = { - input: function(element) { + input: function(element, value) { switch (element.type.toLowerCase()) { case 'checkbox': case 'radio': - return Form.Element.Serializers.inputSelector(element); + return Form.Element.Serializers.inputSelector(element, value); default: - return Form.Element.Serializers.textarea(element); + return Form.Element.Serializers.textarea(element, value); } }, - inputSelector: function(element) { - return element.checked ? element.value : null; + inputSelector: function(element, value) { + if (value === undefined) return element.checked ? element.value : null; + else element.checked = !!value; }, - textarea: function(element) { - return element.value; + textarea: function(element, value) { + if (value === undefined) return element.value; + else element.value = value; }, - select: function(element) { - return this[element.type == 'select-one' ? - 'selectOne' : 'selectMany'](element); + select: function(element, index) { + if (index === undefined) + return this[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + else { + var opt, value, single = !Object.isArray(index); + for (var i = 0, length = element.length; i < length; i++) { + opt = element.options[i]; + value = this.optionValue(opt); + if (single) { + if (value == index) { + opt.selected = true; + return; + } + } + else opt.selected = index.include(value); + } + } }, selectOne: function(element) { @@ -2839,45 +3594,34 @@ Form.Element.Serializers = { // extend element because hasAttribute may not be native return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; } -} +}; /*--------------------------------------------------------------------------*/ -Abstract.TimedObserver = function() {} -Abstract.TimedObserver.prototype = { - initialize: function(element, frequency, callback) { - this.frequency = frequency; +Abstract.TimedObserver = Class.create(PeriodicalExecuter, { + initialize: function($super, element, frequency, callback) { + $super(callback, frequency); this.element = $(element); - this.callback = callback; - this.lastValue = this.getValue(); - this.registerCallback(); }, - registerCallback: function() { - setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); - }, - - onTimerEvent: function() { + execute: function() { var value = this.getValue(); - var changed = ('string' == typeof this.lastValue && 'string' == typeof value - ? this.lastValue != value : String(this.lastValue) != String(value)); - if (changed) { + if (Object.isString(this.lastValue) && Object.isString(value) ? + this.lastValue != value : String(this.lastValue) != String(value)) { this.callback(this.element, value); this.lastValue = value; } } -} +}); -Form.Element.Observer = Class.create(); -Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { +Form.Element.Observer = Class.create(Abstract.TimedObserver, { getValue: function() { return Form.Element.getValue(this.element); } }); -Form.Observer = Class.create(); -Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { +Form.Observer = Class.create(Abstract.TimedObserver, { getValue: function() { return Form.serialize(this.element); } @@ -2885,8 +3629,7 @@ Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { /*--------------------------------------------------------------------------*/ -Abstract.EventObserver = function() {} -Abstract.EventObserver.prototype = { +Abstract.EventObserver = Class.create({ initialize: function(element, callback) { this.element = $(element); this.callback = callback; @@ -2907,7 +3650,7 @@ Abstract.EventObserver.prototype = { }, registerFormCallbacks: function() { - Form.getElements(this.element).each(this.registerCallback.bind(this)); + Form.getElements(this.element).each(this.registerCallback, this); }, registerCallback: function(element) { @@ -2923,24 +3666,20 @@ Abstract.EventObserver.prototype = { } } } -} +}); -Form.Element.EventObserver = Class.create(); -Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { +Form.Element.EventObserver = Class.create(Abstract.EventObserver, { getValue: function() { return Form.Element.getValue(this.element); } }); -Form.EventObserver = Class.create(); -Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { +Form.EventObserver = Class.create(Abstract.EventObserver, { getValue: function() { return Form.serialize(this.element); } }); -if (!window.Event) { - var Event = new Object(); -} +if (!window.Event) var Event = { }; Object.extend(Event, { KEY_BACKSPACE: 8, @@ -2956,100 +3695,335 @@ Object.extend(Event, { KEY_END: 35, KEY_PAGEUP: 33, KEY_PAGEDOWN: 34, + KEY_INSERT: 45, - element: function(event) { - return $(event.target || event.srcElement); - }, + cache: { }, - isLeftClick: function(event) { - return (((event.which) && (event.which == 1)) || - ((event.button) && (event.button == 1))); - }, + relatedTarget: function(event) { + var element; + switch(event.type) { + case 'mouseover': element = event.fromElement; break; + case 'mouseout': element = event.toElement; break; + default: return null; + } + return Element.extend(element); + } +}); - pointerX: function(event) { - return event.pageX || (event.clientX + - (document.documentElement.scrollLeft || document.body.scrollLeft)); - }, +Event.Methods = (function() { + var isButton; - pointerY: function(event) { - return event.pageY || (event.clientY + - (document.documentElement.scrollTop || document.body.scrollTop)); - }, + if (Prototype.Browser.IE) { + var buttonMap = { 0: 1, 1: 4, 2: 2 }; + isButton = function(event, code) { + return event.button == buttonMap[code]; + }; - stop: function(event) { - if (event.preventDefault) { + } else if (Prototype.Browser.WebKit) { + isButton = function(event, code) { + switch (code) { + case 0: return event.which == 1 && !event.metaKey; + case 1: return event.which == 1 && event.metaKey; + default: return false; + } + }; + + } else { + isButton = function(event, code) { + return event.which ? (event.which === code + 1) : (event.button === code); + }; + } + + return { + isLeftClick: function(event) { return isButton(event, 0) }, + isMiddleClick: function(event) { return isButton(event, 1) }, + isRightClick: function(event) { return isButton(event, 2) }, + + element: function(event) { + var node = Event.extend(event).target; + return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node); + }, + + findElement: function(event, expression) { + var element = Event.element(event); + return element.match(expression) ? element : element.up(expression); + }, + + pointer: function(event) { + return { + x: event.pageX || (event.clientX + + (document.documentElement.scrollLeft || document.body.scrollLeft)), + y: event.pageY || (event.clientY + + (document.documentElement.scrollTop || document.body.scrollTop)) + }; + }, + + pointerX: function(event) { return Event.pointer(event).x }, + pointerY: function(event) { return Event.pointer(event).y }, + + stop: function(event) { + Event.extend(event); event.preventDefault(); event.stopPropagation(); - } else { - event.returnValue = false; - event.cancelBubble = true; + event.stopped = true; } - }, + }; +})(); - // find the first node with the given tagName, starting from the - // node the event was triggered on; traverses the DOM upwards - findElement: function(event, tagName) { - var element = Event.element(event); - while (element.parentNode && (!element.tagName || - (element.tagName.toUpperCase() != tagName.toUpperCase()))) - element = element.parentNode; - return element; - }, +Event.extend = (function() { + var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { + m[name] = Event.Methods[name].methodize(); + return m; + }); + + if (Prototype.Browser.IE) { + Object.extend(methods, { + stopPropagation: function() { this.cancelBubble = true }, + preventDefault: function() { this.returnValue = false }, + inspect: function() { return "[object Event]" } + }); + + return function(event) { + if (!event) return false; + if (event._extendedByPrototype) return event; + + event._extendedByPrototype = Prototype.emptyFunction; + var pointer = Event.pointer(event); + Object.extend(event, { + target: event.srcElement, + relatedTarget: Event.relatedTarget(event), + pageX: pointer.x, + pageY: pointer.y + }); + return Object.extend(event, methods); + }; + + } else { + Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__; + Object.extend(Event.prototype, methods); + return Prototype.K; + } +})(); + +Object.extend(Event, (function() { + var cache = Event.cache; + + function getEventID(element) { + if (element._eventID) return element._eventID; + arguments.callee.id = arguments.callee.id || 1; + return element._eventID = ++arguments.callee.id; + } + + function getDOMEventName(eventName) { + if (eventName && eventName.include(':')) return "dataavailable"; + return eventName; + } - observers: false, + function getCacheForID(id) { + return cache[id] = cache[id] || { }; + } + + function getWrappersForEventName(id, eventName) { + var c = getCacheForID(id); + return c[eventName] = c[eventName] || []; + } + + function createWrapper(element, eventName, handler) { + var id = getEventID(element); + var c = getWrappersForEventName(id, eventName); + if (c.pluck("handler").include(handler)) return false; + + var wrapper = function(event) { + if (!Event || !Event.extend || + (event.eventName && event.eventName != eventName)) + return false; + + Event.extend(event); + handler.call(element, event) + }; + + wrapper.handler = handler; + c.push(wrapper); + return wrapper; + } + + function findWrapper(id, eventName, handler) { + var c = getWrappersForEventName(id, eventName); + return c.find(function(wrapper) { return wrapper.handler == handler }); + } + + function destroyWrapper(id, eventName, handler) { + var c = getCacheForID(id); + if (!c[eventName]) return false; + c[eventName] = c[eventName].without(findWrapper(id, eventName, handler)); + } + + function destroyCache() { + for (var id in cache) + for (var eventName in cache[id]) + cache[id][eventName] = null; + } - _observeAndCache: function(element, name, observer, useCapture) { - if (!this.observers) this.observers = []; - if (element.addEventListener) { - this.observers.push([element, name, observer, useCapture]); - element.addEventListener(name, observer, useCapture); - } else if (element.attachEvent) { - this.observers.push([element, name, observer, useCapture]); - element.attachEvent('on' + name, observer); + if (window.attachEvent) { + window.attachEvent("onunload", destroyCache); + } + + return { + observe: function(element, eventName, handler) { + element = $(element); + var name = getDOMEventName(eventName); + + var wrapper = createWrapper(element, eventName, handler); + if (!wrapper) return element; + + if (element.addEventListener) { + element.addEventListener(name, wrapper, false); + } else { + element.attachEvent("on" + name, wrapper); + } + + return element; + }, + + stopObserving: function(element, eventName, handler) { + element = $(element); + var id = getEventID(element), name = getDOMEventName(eventName); + + if (!handler && eventName) { + getWrappersForEventName(id, eventName).each(function(wrapper) { + element.stopObserving(eventName, wrapper.handler); + }); + return element; + + } else if (!eventName) { + Object.keys(getCacheForID(id)).each(function(eventName) { + element.stopObserving(eventName); + }); + return element; + } + + var wrapper = findWrapper(id, eventName, handler); + if (!wrapper) return element; + + if (element.removeEventListener) { + element.removeEventListener(name, wrapper, false); + } else { + element.detachEvent("on" + name, wrapper); + } + + destroyWrapper(id, eventName, handler); + + return element; + }, + + fire: function(element, eventName, memo) { + element = $(element); + if (element == document && document.createEvent && !element.dispatchEvent) + element = document.documentElement; + + if (document.createEvent) { + var event = document.createEvent("HTMLEvents"); + event.initEvent("dataavailable", true, true); + } else { + var event = document.createEventObject(); + event.eventType = "ondataavailable"; + } + + event.eventName = eventName; + event.memo = memo || { }; + + if (document.createEvent) { + element.dispatchEvent(event); + } else { + element.fireEvent(event.eventType, event); + } + + return event; } - }, + }; +})()); + +Object.extend(Event, Event.Methods); + +Element.addMethods({ + fire: Event.fire, + observe: Event.observe, + stopObserving: Event.stopObserving +}); + +Object.extend(document, { + fire: Element.Methods.fire.methodize(), + observe: Element.Methods.observe.methodize(), + stopObserving: Element.Methods.stopObserving.methodize() +}); + +(function() { + /* Support for the DOMContentLoaded event is based on work by Dan Webb, + Matthias Miller, Dean Edwards and John Resig. */ + + var timer, fired = false; + + function fireContentLoadedEvent() { + if (fired) return; + if (timer) window.clearInterval(timer); + document.fire("dom:loaded"); + fired = true; + } + + if (document.addEventListener) { + if (Prototype.Browser.WebKit) { + timer = window.setInterval(function() { + if (/loaded|complete/.test(document.readyState)) + fireContentLoadedEvent(); + }, 0); + + Event.observe(window, "load", fireContentLoadedEvent); - unloadCache: function() { - if (!Event.observers) return; - for (var i = 0, length = Event.observers.length; i < length; i++) { - Event.stopObserving.apply(this, Event.observers[i]); - Event.observers[i][0] = null; + } else { + document.addEventListener("DOMContentLoaded", + fireContentLoadedEvent, false); } - Event.observers = false; - }, - observe: function(element, name, observer, useCapture) { - element = $(element); - useCapture = useCapture || false; + } else { + document.write("