]> err.no Git - scalable-opengroupware.org/commitdiff
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1263 d1b88da0-ebda-0310...
authorwolfgang <wolfgang@d1b88da0-ebda-0310-925b-ed51d893ca5b>
Tue, 13 Nov 2007 22:42:23 +0000 (22:42 +0000)
committerwolfgang <wolfgang@d1b88da0-ebda-0310-925b-ed51d893ca5b>
Tue, 13 Nov 2007 22:42:23 +0000 (22:42 +0000)
45 files changed:
ChangeLog
SOPE/NGCards/ChangeLog
SOPE/NGCards/iCalEntityObject.h
SOPE/NGCards/iCalEntityObject.m
SOPE/NGCards/iCalTimeZonePeriod.m
SOPE/sope-patchset-r1546.diff
SoObjects/Appointments/SOGoAppointmentObject.m
SoObjects/Appointments/SOGoAptMailNotification.m
SoObjects/Appointments/SOGoCalendarComponent.h
SoObjects/Appointments/SOGoCalendarComponent.m
SoObjects/Mailer/SOGoDraftObject.m
SoObjects/Mailer/SOGoMailBodyPart.m
SoObjects/Mailer/SOGoMailObject.m
SoObjects/SOGo/GNUmakefile
SoObjects/SOGo/SOGoContentObject.h
SoObjects/SOGo/SOGoContentObject.m
SoObjects/SOGo/SOGoUser.h
SoObjects/SOGo/SOGoUser.m
SoObjects/SOGo/iCalEntityObject+Utilities.h [new file with mode: 0644]
SoObjects/SOGo/iCalEntityObject+Utilities.m [new file with mode: 0644]
UI/MailPartViewers/GNUmakefile
UI/MailPartViewers/UIxMailPartICalActions.h [moved from UI/MailPartViewers/UIxMailPartICalAction.m with 51% similarity]
UI/MailPartViewers/UIxMailPartICalActions.m [new file with mode: 0644]
UI/MailPartViewers/UIxMailPartICalViewer.m
UI/MailPartViewers/UIxMailPartTextViewer.m
UI/MailPartViewers/product.plist
UI/Scheduler/UIxCalendarSelector.m
UI/Scheduler/UIxComponentEditor.m
UI/Templates/ContactsUI/UIxContactsListView.wox
UI/Templates/MailerUI/UIxMailListView.wox
UI/Templates/SchedulerUI/UIxCalMainView.wox
UI/Templates/SchedulerUI/UIxCalendarSelector.wox
UI/WebServerResources/ContactsUI.js
UI/WebServerResources/MailerUI.js
UI/WebServerResources/SchedulerUI.css
UI/WebServerResources/SchedulerUI.js
UI/WebServerResources/UIxAppointmentEditor.css
UI/WebServerResources/UIxAttendeesEditor.js
UI/WebServerResources/UIxComponentEditor.js
UI/WebServerResources/UIxContactEditor.css
UI/WebServerResources/UIxTaskEditor.css
UI/WebServerResources/generic.css
UI/WebServerResources/generic.js
UI/WebServerResources/iefixes.css
UI/WebServerResources/prototype.js

index f9a1ce43493f59dc9725382ae9ea01b48800542c..f77b4a32c9f915f22b66cfbdea81de779c9db249 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,37 @@
+2007-11-13  Wolfgang Sourdeau  <wsourdeau@inverse.ca>
+
+       * 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  <wsourdeau@inverse.ca>
 
        * 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.
 
index 549ad5a29bf580b9680636f83dcd4bd6e8637d5a..ec0c0c5427e77f09170df467184e84ce022bd361 100644 (file)
@@ -1,3 +1,10 @@
+2007-11-12  Wolfgang Sourdeau  <wsourdeau@inverse.ca>
+
+       * 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  <wsourdeau@inverse.ca>
 
        * iCalDateHolder.m ([iCalDateHolder -awakeAfterUsingSaxDecoder:]):
index e58dc1e570bd84190392917d87c852e38c9d7fd5..516a0d16da7d94bc7b0e3429c39af3e1780bc08f 100644 (file)
@@ -116,6 +116,9 @@ typedef enum
 - (NSArray *) alarms;
 - (BOOL) hasAlarms;
 
+/* comparisons */
+- (NSComparisonResult) compare: (iCalEntityObject *) otherObject;
+
 @end
 
 #endif /* __NGCards_iCalEntityObject_H__ */
index 39f2afce2944f3831e13137285ad863ad1ce1038..53a51c1f625a4c8b118f54a80ae9bfa36d9a8451 100644 (file)
   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 */
index 29bd9bb17b88820bde63884bc9b7a6615d079d22..dc1f611fddf59508c5c95cbaef9135c34d2a1eaa 100644 (file)
   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;
 }
index acec966d317d7dd8220ff97b5a720d0c9335d9b1..e26b5934b4b8313e1226152527779e008694160c 100644 (file)
@@ -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)
index 7cc44e230b1961cd1b2b6977d928108a91bd8727..12f97b38a0e13f33b7df263f62e9f49f95c719c8 100644 (file)
@@ -31,6 +31,7 @@
 #import <NGCards/iCalPerson.h>
 
 #import <SoObjects/SOGo/LDAPUserManager.h>
+#import <SoObjects/SOGo/NSArray+Utilities.h>
 #import <SoObjects/SOGo/SOGoObject.h>
 #import <SoObjects/SOGo/SOGoPermissions.h>
 #import <SoObjects/SOGo/WORequest+SOGo.h>
       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;
       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]];
          && [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"];
index 912c3a0e22f32a0eb1f484bb98a833194f7fdf87..b08bf74dfac1ddc4ea7ca6eca35b871c2a46c7fd 100644 (file)
@@ -82,6 +82,7 @@ static NSTimeZone     *EST = nil;
 - (NSString *)homePageURL {
   return self->homePageURL;
 }
+
 - (void)setHomePageURL:(NSString *)_homePageURL {
   ASSIGN(self->homePageURL, _homePageURL);
 }
index db0d4df998efd18d46736f683d02fa851e8158b9..4bb746a3ac2b2261b8998a59bf20285d95008fc6 100644 (file)
                         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;
index 805de4276ed30b145847ce915861d77bfc1a4278..72cb73c3b8da8ce6b8cc974b9348855b32fed441 100644 (file)
@@ -36,6 +36,7 @@
 #import <NGMime/NGMimeMultipartBody.h>
 #import <NGMail/NGMimeMessage.h>
 
+#import <SoObjects/SOGo/iCalEntityObject+Utilities.h>
 #import <SoObjects/SOGo/LDAPUserManager.h>
 #import <SoObjects/SOGo/NSCalendarDate+SOGo.h>
 #import <SoObjects/SOGo/SOGoMailer.h>
@@ -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
index ccfd9391965a24bfab783a8088db3519618dde38..e4293472e8619fc1a1921c2055ed8fa8da711da3 100644 (file)
@@ -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 */
   
index 5c32a2ef22c77e0309c2eaf220379d49c1f07b49..78be1fb677d67e34a65b660b106bfb017ede49b3 100644 (file)
@@ -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";
   
index 0cda214ec520d4af68db69a6d0854328d0b4309f..f6969e9721fb02db61eebcf26b1bb92f0dc26260 100644 (file)
@@ -41,6 +41,7 @@
 #import <NGMail/NGMimeMessageParser.h>
 
 #import <SoObjects/SOGo/NSArray+Utilities.h>
+#import <SoObjects/SOGo/NSDictionary+Utilities.h>
 #import <SoObjects/SOGo/SOGoPermissions.h>
 #import <SoObjects/SOGo/SOGoUser.h>
 
index 1d9df45cfed3f7aae4eaa3f8a7047f089f4c3d6b..889b653fd764227a3e398ea44a46d0e30e70e22c 100644 (file)
@@ -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        \
index f23f5cce076b0fc7a0c56190649912be52eded98..62d3fc756a317738a3a011e1192b5c02b5c8a1ce 100644 (file)
@@ -49,6 +49,7 @@
 /* content */
 
 - (BOOL) isNew;
+- (void) setContentString: (NSString *) newContent;
 - (NSString *) contentAsString;
 - (NSException *) saveContentString: (NSString *) _str
                         baseVersion: (unsigned int) _baseVersion;
index 442513663da7e606aa89c09b9c3df8a566fb2056..7e43dbbbbf72022a85158696b3d744d5294c4b17 100644 (file)
   return content;
 }
 
+- (void) setContentString: (NSString *) newContent
+{
+  ASSIGN (content, newContent);
+}
+
 - (NSException *) saveContentString: (NSString *) newContent
                         baseVersion: (unsigned int) newBaseVersion
 {
 
   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];
index 4530e14f687925da87215a92d6581d223d157c36..10b9f0fd45f23a918e23a6219c89b5145813c936 100644 (file)
 @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;
index 9ffdfc6eca446774f89d98c215118f1b931d2de2..9488c1dd35e59bb3b5e8d81fb30ee80b8b56c81b 100644 (file)
@@ -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 (file)
index 0000000..c9d44c7
--- /dev/null
@@ -0,0 +1,37 @@
+/* iCalEntityObject+Utilities.h - this file is part of SOGo
+ *
+ * Copyright (C) 2007 Inverse groupe conseil
+ *
+ * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
+ *
+ * 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 <NGCards/iCalEntityObject.h>
+
+@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 (file)
index 0000000..8674809
--- /dev/null
@@ -0,0 +1,51 @@
+/* iCalEntityObject+Utilities.m - this file is part of SOGo
+ *
+ * Copyright (C) 2007 Inverse groupe conseil
+ *
+ * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
+ *
+ * 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 <Foundation/NSArray.h>
+#import <Foundation/NSEnumerator.h>
+
+#import <NGCards/iCalPerson.h>
+
+#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
index ffa92eb7eb2bcf230f8f7292e58339afc5ebbda7..a86ea45604b1d4f6a388390c81831464b8e73d16 100644 (file)
@@ -24,7 +24,7 @@ MailPartViewers_OBJC_FILES += \
        UIxMailPartMessageViewer.m      \
        UIxMailPartICalViewer.m         \
        \
-       UIxMailPartICalAction.m         \
+       UIxMailPartICalActions.m        \
        \
        UIxKolabPartViewer.m            \
        UIxKolabPartContactViewer.m     \
similarity index 51%
rename from UI/MailPartViewers/UIxMailPartICalAction.m
rename to UI/MailPartViewers/UIxMailPartICalActions.h
index f83a338198df9c06aa5eb39da2c790b4b5b6bde1..f6a26e79a5cecfc4f0d98c71dd47b8174c34ea54 100644 (file)
@@ -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
  *
  * Boston, MA 02111-1307, USA.
  */
 
-#import <Foundation/NSString.h>
-#import <NGObjWeb/WODirectAction.h>
-
-#import <UI/Common/WODirectAction+SOGo.h>
-
-@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 <NGObjWeb/WODirectAction.h>
 
-- (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 (file)
index 0000000..c4c8cb7
--- /dev/null
@@ -0,0 +1,164 @@
+/* UIxMailPartICalActions.m - this file is part of SOGo
+ *
+ * Copyright (C) 2007 Inverse groupe conseil
+ *
+ * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
+ *
+ * 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 <Foundation/NSCalendarDate.h>
+
+#import <NGObjWeb/WOContext+SoObjects.h>
+#import <NGObjWeb/WOResponse.h>
+
+#import <NGCards/iCalCalendar.h>
+#import <NGCards/iCalPerson.h>
+
+#import <UI/Common/WODirectAction+SOGo.h>
+
+#import <SoObjects/Appointments/SOGoAppointmentObject.h>
+#import <SoObjects/Appointments/SOGoAppointmentFolder.h>
+#import <SoObjects/SOGo/SOGoUser.h>
+#import <SoObjects/SOGo/iCalEntityObject+Utilities.h>
+#import <SoObjects/Mailer/SOGoMailBodyPart.h>
+
+#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
index ec8c18af87cf1f085a1f4791a31584ee6d86077b..56df0e45e7182d55c9ab43eaa0aaf62d82ca532c 100644 (file)
@@ -25,6 +25,8 @@
   Show plain/calendar mail parts.
 */
 
+#import <NGObjWeb/WOResponse.h>
+
 #import <NGExtensions/NSCalendarDate+misc.h>
 #import <NGExtensions/NSNull+misc.h>
 #import <NGExtensions/NSObject+Logs.h>
 
 - (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
index 6e9b6a26a1f16a8046a0211ed866c2055dca6ae1..27ecea50761b9fe3c3d3afe8ed6fd996d5985a37 100644 (file)
@@ -28,6 +28,8 @@
   TODO: add contained link detection.
 */
 
+#import <Foundation/NSException.h>
+
 #import <NGExtensions/NSString+misc.h>
 
 #import <SoObjects/SOGo/NSString+Utilities.h>
 
 @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, "<br />");
-             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, "<br />");
+             destChar += 6;
+             iteration++;
            }
          else
            {
       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);
index 145b47245752c3ab70d71fed96994ec7a2f40b22..31e8b466baccd63eef6f66283569155dccf2afb6 100644 (file)
@@ -1,4 +1,4 @@
-{
+{ /* -*-java-*- */
   requires = ( MAIN );
 
   publicResources = (
       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";
-        };
+         }; */
       };
     };
   };
index e60fb6e134e4daa13e1291efa74e63ea3bf76e7d..1e49c8e247c12303c553623ff12f8a42f0ef130e 100644 (file)
@@ -25,6 +25,9 @@
 #import <Foundation/NSValue.h>
 
 #import <SOGo/NSDictionary+Utilities.h>
+
+#import <SoObjects/SOGo/SOGoUser.h>
+
 #import <Appointments/SOGoAppointmentFolder.h>
 #import <Appointments/SOGoAppointmentFolders.h>
 
@@ -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];
        }
     }
index 0c07730d89c485abc33798e1b96d51e0689341be..5f108ba8cd49a1c635119dbae392211737ddad4b 100644 (file)
 
       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])
                 [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;
index d98db7d2557246e5e14dd4ced6f6b55236378626..efc8c859fe14d56d7efd93f0a3a7f601aa21467b 100644 (file)
@@ -9,7 +9,7 @@
     className="UIxContactsListViewContainer"
     selectorComponentClass="selectorComponentClass"
     title="name">
-    <table id="contactsList" multiselect="yes">
+    <table id="contactsList" cellspacing="0">
       <thead>
         <tr class="tableview">
           <!-- localize -->
index 31d5ed63f3765bca7a131e5e8a4c1e84718b28ce..a0dec12e61414ddb739544cbeba52cb1bb8dec25 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version='1.0' standalone='yes'?>
 <!DOCTYPE table>
-<table id="messageList"
+<table id="messageList" cellspacing="0"
   xmlns="http://www.w3.org/1999/xhtml"
   xmlns:var="http://www.skyrix.com/od/binding"
   xmlns:const="http://www.skyrix.com/od/constant"
index d39e8e7d3bef1f76e0c6a273ac6dc9224991ce96..0431db72f4f8062084a9b4dcc4bde8f0ee474ebd 100644 (file)
@@ -89,7 +89,7 @@
   <div id="rightPanel">
     <var:component className="UIxCalFilterPanel" />
     <div id="eventsListView">
-      <table id="eventsList">
+      <table id="eventsList" cellspacing="0">
         <thead>
          <tr>
            <td class="headerCell headerTitle sortableTableHeader"><var:string label:value="Title"/></td>
index eac42da1c3b7572d8e8c46d80982580b3c315e13..a02893ffa8dbe442324e6fdcc3f71d8ec763c2eb 100644 (file)
@@ -32,7 +32,7 @@ div.colorBox.calendarFolder<var:string value="currentCalendar.folder" />
     <ul id="calendarList">
       <var:foreach list="calendars" item="currentCalendar"
        ><li class="denied" var:id="currentCalendar.id"
-         var:owner="currentCalendar.owner">
+         var:owner="currentCalendar.owner" var:roles="currentCalendar.roles" >
          <input type="checkbox" class="checkBox"
            const:disabled="disabled"
            var:checked="currentCalendar.active" />
index e5c3d160cf5a2ae14c4e6b6385da844d1b762636..a58c509d59fadf36741c4245d7251b3361d409f2 100644 (file)
@@ -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});
    }
index 9f8ce6a2882d81d6de6fea65e16c103ab9babb56..bce12ad7fda881e35e01ea180a485bdcf6182d96 100644 (file)
@@ -785,6 +785,9 @@ function ICalendarButtonCallback(http) {
        loadMessage(currentMessages[currentMailbox]);
       }
     }
+    else {
+      window.alert("received code: " + http.status);
+    }
 }
 
 function resizeMailContent() {
index 609da77c4bef98cca0682f69423320fed3751b70..cd70d984e70029935ea5a980a4af3e0e9fac11e4 100644 (file)
@@ -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
index 22e7b72abbf63322f37a22be1a63adefd6fa9ba4..05ded9b279cdee640609bea6512f50d56893ce54 100644 (file)
@@ -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)
index 3bef0501006de58d5f601619c6a4d847daab23bf..b02026e5883f3ba6d0342d777f89459eb0ef12de 100644 (file)
@@ -90,7 +90,7 @@ TEXTAREA
   padding-bottom: 0em; }
 
 SELECT#calendarList
-{ width: 7em; }
+{ width: 18em; }
 
 A#changeUrlButton
 { margin-left: 1em; }
index 0f0547d661d48a351e0fa1f81542fdb05637e0db..9cf96023750dc527b7698c471fdf189f55972c37 100644 (file)
@@ -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);
index 8aa9c1124bb95c85430e2cdc59f6eedfe3b45527..6ff4d3f008bcb716f7fa21e0d39738ee53e06c0c 100644 (file)
@@ -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++)
index d0205082fbd5edc9ee4a7f428e1c920a37857fe5..f40ce74292fdcfaa7b8a945eeb9e90c3b87b3a0a 100644 (file)
@@ -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;
index ec02c5e111e32b6b53050a24ba293cfb8d61b108..a6a1a1d58a0e0f1f3da3f9cf32646e2556a50475 100644 (file)
@@ -58,7 +58,7 @@ TEXTAREA
   padding-bottom: 0em; }
 
 SELECT#calendarList
-{ width: 7em; }
+{ width: 20em; }
 
 A#changeUrlButton
 { margin-left: 1em; }
index d5c1c529166e6a4bd1586003b62f39a0e8c8f862..512a750e347cb1525a25c3143c19dc8a6ffc0ee4 100644 (file)
@@ -600,7 +600,7 @@ LI.denied
 LI._selected,
 TR._selected > TD,
 TD._selected
-{ 
+{
   background-color: #4b6983;
   color: #fff;
 }
index 8150991ee6a3dc24cab4da4c4a146dd608b40242..ea96808e2c4679ec7de815d64dcecb10c73ab9e0 100644 (file)
@@ -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;
index ff0757e13726e3ece076e24573840025785d8034..412621b2603462bd048a92cd3f6ada189077ac46 100644 (file)
@@ -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); }
 
index a3f21ac790995f83f0d7e1dfc8a448d7580d035b..5c73462946b96cd5f44fa74aae64895b99e14eb6 100644 (file)
@@ -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: '<script[^>]*>([\\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 '#<Enumerable:' + this.toArray().inspect() + '>';
   }
-}
+};
 
 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 '#<Hash:{' + this.map(function(pair) {
-      return pair.map(Object.inspect).join(': ');
-    }).join(', ') + '}>';
-  },
+    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 '#<Hash:{' + this.map(function(pair) {
+        return pair.map(Object.inspect).join(': ');
+      }).join(', ') + '}>';
+    },
 
-// 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 = '<table><tbody>' +  html.stripScripts() + '</tbody></table>';
-          depth = 2;
-          break;
-        case 'TR':
-          div.innerHTML = '<table><tbody><tr>' +  html.stripScripts() + '</tr></tbody></table>';
-          depth = 3;
-          break;
-        case 'TD':
-          div.innerHTML = '<table><tbody><tr><td>' +  html.stripScripts() + '</td></tr></tbody></table>';
-          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:  ['<table>',                '</table>',                   1],
+    TBODY:  ['<table><tbody>',         '</tbody></table>',           2],
+    TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],
+    TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
+    SELECT: ['<select>',               '</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 = '<table><tbody>' + this.content + '</tbody></table>';
-    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 "#<Selector:" + this.expression.inspect() + ">";
   }
-};
+});
 
 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("<script id=__onDOMContentLoaded defer src=//:><\/script>");
+    $("__onDOMContentLoaded").onreadystatechange = function() {
+      if (this.readyState == "complete") {
+        this.onreadystatechange = null;
+        fireContentLoadedEvent();
+      }
+    };
+  }
+})();
+/*------------------------------- DEPRECATED -------------------------------*/
 
-    if (name == 'keypress' &&
-      (Prototype.Browser.WebKit || element.attachEvent))
-      name = 'keydown';
+Hash.toQueryString = Object.toQueryString;
 
-    Event._observeAndCache(element, name, observer, useCapture);
+var Toggle = { display: Element.toggle };
+
+Element.Methods.childOf = Element.Methods.descendantOf;
+
+var Insertion = {
+  Before: function(element, content) {
+    return Element.insert(element, {before:content});
   },
 
-  stopObserving: function(element, name, observer, useCapture) {
-    element = $(element);
-    useCapture = useCapture || false;
+  Top: function(element, content) {
+    return Element.insert(element, {top:content});
+  },
 
-    if (name == 'keypress' &&
-        (Prototype.Browser.WebKit || element.attachEvent))
-      name = 'keydown';
+  Bottom: function(element, content) {
+    return Element.insert(element, {bottom:content});
+  },
 
-    if (element.removeEventListener) {
-      element.removeEventListener(name, observer, useCapture);
-    } else if (element.detachEvent) {
-      try {
-        element.detachEvent('on' + name, observer);
-      } catch (e) {}
-    }
+  After: function(element, content) {
+    return Element.insert(element, {after:content});
   }
-});
+};
 
-/* prevent memory leaks in IE */
-if (Prototype.Browser.IE)
-  Event.observe(window, 'unload', Event.unloadCache, false);
+var $continue = new Error('"throw $continue" is deprecated, use "return" instead');
+
+// This should be moved to script.aculo.us; notice the deprecated methods
+// further below, that map to the newer Element methods.
 var Position = {
   // set to true if needed, warning: firefox performance problems
   // NOT neeeded for page scrolling, only if draggable contained in
@@ -3069,59 +4043,13 @@ var Position = {
                 || 0;
   },
 
-  realOffset: function(element) {
-    var valueT = 0, valueL = 0;
-    do {
-      valueT += element.scrollTop  || 0;
-      valueL += element.scrollLeft || 0;
-      element = element.parentNode;
-    } while (element);
-    return [valueL, valueT];
-  },
-
-  cumulativeOffset: function(element) {
-    var valueT = 0, valueL = 0;
-    do {
-      valueT += element.offsetTop  || 0;
-      valueL += element.offsetLeft || 0;
-      element = element.offsetParent;
-    } while (element);
-    return [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 [valueL, valueT];
-  },
-
-  offsetParent: 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;
-  },
-
   // caches x/y coordinate pair to use with overlap
   within: function(element, x, y) {
     if (this.includeScrollOffsets)
       return this.withinIncludingScrolloffsets(element, x, y);
     this.xcomp = x;
     this.ycomp = y;
-    this.offset = this.cumulativeOffset(element);
+    this.offset = Element.cumulativeOffset(element);
 
     return (y >= this.offset[1] &&
             y <  this.offset[1] + element.offsetHeight &&
@@ -3130,11 +4058,11 @@ var Position = {
   },
 
   withinIncludingScrolloffsets: function(element, x, y) {
-    var offsetcache = this.realOffset(element);
+    var offsetcache = Element.cumulativeScrollOffset(element);
 
     this.xcomp = x + offsetcache[0] - this.deltaX;
     this.ycomp = y + offsetcache[1] - this.deltaY;
-    this.offset = this.cumulativeOffset(element);
+    this.offset = Element.cumulativeOffset(element);
 
     return (this.ycomp >= this.offset[1] &&
             this.ycomp <  this.offset[1] + element.offsetHeight &&
@@ -3153,125 +4081,104 @@ var Position = {
         element.offsetWidth;
   },
 
-  page: function(forElement) {
-    var valueT = 0, valueL = 0;
-
-    var element = forElement;
-    do {
-      valueT += element.offsetTop  || 0;
-      valueL += element.offsetLeft || 0;
+  // Deprecation layer -- use newer Element methods now (1.5.2).
 
-      // Safari fix
-      if (element.offsetParent == document.body)
-        if (Element.getStyle(element,'position')=='absolute') break;
+  cumulativeOffset: Element.Methods.cumulativeOffset,
 
-    } while (element = element.offsetParent);
+  positionedOffset: Element.Methods.positionedOffset,
 
-    element = forElement;
-    do {
-      if (!window.opera || element.tagName=='BODY') {
-        valueT -= element.scrollTop  || 0;
-        valueL -= element.scrollLeft || 0;
-      }
-    } while (element = element.parentNode);
+  absolutize: function(element) {
+    Position.prepare();
+    return Element.absolutize(element);
+  },
 
-    return [valueL, valueT];
+  relativize: function(element) {
+    Position.prepare();
+    return Element.relativize(element);
   },
 
-  clone: function(source, target) {
-    var options = Object.extend({
-      setLeft:    true,
-      setTop:     true,
-      setWidth:   true,
-      setHeight:  true,
-      offsetTop:  0,
-      offsetLeft: 0
-    }, arguments[2] || {})
+  realOffset: Element.Methods.cumulativeScrollOffset,
 
-    // find page position of source
-    source = $(source);
-    var p = Position.page(source);
+  offsetParent: Element.Methods.getOffsetParent,
 
-    // find coordinate system to use
-    target = $(target);
-    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(target,'position') == 'absolute') {
-      parent = Position.offsetParent(target);
-      delta = Position.page(parent);
-    }
+  page: Element.Methods.viewportOffset,
 
-    // correct by body offsets (fixes Safari)
-    if (parent == document.body) {
-      delta[0] -= document.body.offsetLeft;
-      delta[1] -= document.body.offsetTop;
+  clone: function(source, target, options) {
+    options = options || { };
+    return Element.clonePosition(target, source, options);
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
+  function iter(name) {
+    return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
+  }
+
+  instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
+  function(element, className) {
+    className = className.toString().strip();
+    var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
+    return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
+  } : function(element, className) {
+    className = className.toString().strip();
+    var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
+    if (!classNames && !className) return elements;
+
+    var nodes = $(element).getElementsByTagName('*');
+    className = ' ' + className + ' ';
+
+    for (var i = 0, child, cn; child = nodes[i]; i++) {
+      if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
+          (classNames && classNames.all(function(name) {
+            return !name.toString().blank() && cn.include(' ' + name + ' ');
+          }))))
+        elements.push(Element.extend(child));
     }
+    return elements;
+  };
 
-    // set position
-    if(options.setLeft)   target.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
-    if(options.setTop)    target.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
-    if(options.setWidth)  target.style.width = source.offsetWidth + 'px';
-    if(options.setHeight) target.style.height = source.offsetHeight + 'px';
-  },
+  return function(className, parentElement) {
+    return $(parentElement || document.body).getElementsByClassName(className);
+  };
+}(Element.Methods);
 
-  absolutize: function(element) {
-    element = $(element);
-    if (element.style.position == 'absolute') return;
-    Position.prepare();
+/*--------------------------------------------------------------------------*/
 
-    var offsets = Position.positionedOffset(element);
-    var top     = offsets[1];
-    var left    = offsets[0];
-    var width   = element.clientWidth;
-    var height  = element.clientHeight;
+Element.ClassNames = Class.create();
+Element.ClassNames.prototype = {
+  initialize: function(element) {
+    this.element = $(element);
+  },
 
-    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;
+  _each: function(iterator) {
+    this.element.className.split(/\s+/).select(function(name) {
+      return name.length > 0;
+    })._each(iterator);
+  },
 
-    element.style.position = 'absolute';
-    element.style.top    = top + 'px';
-    element.style.left   = left + 'px';
-    element.style.width  = width + 'px';
-    element.style.height = height + 'px';
+  set: function(className) {
+    this.element.className = className;
   },
 
-  relativize: function(element) {
-    element = $(element);
-    if (element.style.position == 'relative') return;
-    Position.prepare();
+  add: function(classNameToAdd) {
+    if (this.include(classNameToAdd)) return;
+    this.set($A(this).concat(classNameToAdd).join(' '));
+  },
 
-    element.style.position = 'relative';
-    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
-    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
+  remove: function(classNameToRemove) {
+    if (!this.include(classNameToRemove)) return;
+    this.set($A(this).without(classNameToRemove).join(' '));
+  },
 
-    element.style.top    = top + 'px';
-    element.style.left   = left + 'px';
-    element.style.height = element._originalHeight;
-    element.style.width  = element._originalWidth;
+  toString: function() {
+    return $A(this).join(' ');
   }
-}
-
-// Safari returns margins on body which is incorrect if the child is absolutely
-// positioned.  For performance reasons, redefine Position.cumulativeOffset for
-// KHTML/WebKit only.
-if (Prototype.Browser.WebKit) {
-  Position.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);
+Object.extend(Element.ClassNames.prototype, Enumerable);
 
-    return [valueL, valueT];
-  }
-}
+/*--------------------------------------------------------------------------*/
 
 Element.addMethods();
\ No newline at end of file