* added matts date picker widget
authorWilfried Göesgens <willi@citadel.org>
Thu, 1 May 2008 21:32:45 +0000 (21:32 +0000)
committerWilfried Göesgens <willi@citadel.org>
Thu, 1 May 2008 21:32:45 +0000 (21:32 +0000)
* added matts vcard inline picture display
* added matts html fixups

* corrected BSTR to bstr
* removed unneded modifications (blanks)
* corrected coding style

24 files changed:
webcit/calendar.c
webcit/calendar_tools.c
webcit/calendar_view.c
webcit/cookie_conversion.c
webcit/iconbar.c
webcit/messages.c
webcit/roomops.c
webcit/static/builder.js [new file with mode: 0644]
webcit/static/controls.js
webcit/static/datepicker-dev.js [new file with mode: 0644]
webcit/static/datepicker.css [new file with mode: 0644]
webcit/static/dragdrop.js
webcit/static/effects.js
webcit/static/head.html
webcit/static/prototype.js
webcit/static/roombanner.html
webcit/static/scriptaculous.js
webcit/static/slider.js [new file with mode: 0644]
webcit/static/sound.js [new file with mode: 0644]
webcit/static/table.js [new file with mode: 0644]
webcit/static/unittest.js
webcit/static/wclib.js
webcit/static/webcit.css
webcit/webcit.c

index 7e81620e428b8bd8ee72773bac99b4110aede83d..fadadf6c18ec8710bf01d498d208d99704f53c8a 100644 (file)
@@ -19,8 +19,8 @@
 void cal_process_object(icalcomponent *cal,
                        int recursion_level,
                        long msgnum,
-                       char *cal_partnum
-{
+                       char *cal_partnum
+{
        icalcomponent *c;
        icalproperty *method = NULL;
        icalproperty_method the_method = ICAL_METHOD_NONE;
@@ -53,16 +53,16 @@ void cal_process_object(icalcomponent *cal,
                wprintf("<img src=\"static/calarea_48x.gif\">");
                wprintf("<span>");
                switch(the_method) {
-                   case ICAL_METHOD_REQUEST:
+               case ICAL_METHOD_REQUEST:
                        title = _("Meeting invitation");
                        break;
-                   case ICAL_METHOD_REPLY:
+               case ICAL_METHOD_REPLY:
                        title = _("Attendee's reply to your invitation");
                        break;
-                   case ICAL_METHOD_PUBLISH:
+               case ICAL_METHOD_PUBLISH:
                        title = _("Published event");
                        break;
-                   default:
+               default:
                        title = _("This is an unknown type of calendar item.");
                        break;
                }
@@ -98,7 +98,7 @@ void cal_process_object(icalcomponent *cal,
        if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
 
                p = icalcomponent_get_first_property(cal,
-                                               ICAL_DTSTART_PROPERTY);
+                                                    ICAL_DTSTART_PROPERTY);
                if (p != NULL) {
                        t = icalproperty_get_dtstart(p);
 
@@ -145,7 +145,9 @@ void cal_process_object(icalcomponent *cal,
        }
 
        /** If the component has attendees, iterate through them. */
-       for (p = icalcomponent_get_first_property(cal, ICAL_ATTENDEE_PROPERTY); (p != NULL); p = icalcomponent_get_next_property(cal, ICAL_ATTENDEE_PROPERTY)) {
+       for (p = icalcomponent_get_first_property(cal, ICAL_ATTENDEE_PROPERTY); 
+            (p != NULL); 
+            p = icalcomponent_get_next_property(cal, ICAL_ATTENDEE_PROPERTY)) {
                wprintf("<dt>");
                wprintf(_("Attendee:"));
                wprintf("</dt><dd>");
@@ -167,8 +169,8 @@ void cal_process_object(icalcomponent *cal,
 
        /** If the component has subcomponents, recurse through them. */
        for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
-           (c != 0);
-           c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
+            (c != 0);
+            c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
                /* Recursively process subcomponent */
                cal_process_object(c, recursion_level+1, msgnum, cal_partnum);
        }
@@ -187,19 +189,19 @@ void cal_process_object(icalcomponent *cal,
 
                                if (is_update) {
                                        snprintf(conflict_message, sizeof conflict_message,
-                                               _("This is an update of '%s' which is already in your calendar."), conflict_name);
+                                                _("This is an update of '%s' which is already in your calendar."), conflict_name);
                                }
                                else {
                                        snprintf(conflict_message, sizeof conflict_message,
-                                               _("This event would conflict with '%s' which is already in your calendar."), conflict_name);
+                                                _("This event would conflict with '%s' which is already in your calendar."), conflict_name);
                                }
 
                                wprintf("<dt>%s",
                                        (is_update ?
-                                               _("Update:") :
-                                               _("CONFLICT:")
-                                       )
-                               );
+                                        _("Update:") :
+                                        _("CONFLICT:")
+                                               )
+                                       );
                                wprintf("</dt><dd>");
                                escputs(conflict_message);
                                wprintf("</dd>\n");
@@ -224,7 +226,7 @@ void cal_process_object(icalcomponent *cal,
                        divname, divname, msgnum, cal_partnum, _("Accept"),
                        divname, divname, msgnum, cal_partnum, _("Tentative"),
                        divname, divname, msgnum, cal_partnum, _("Decline")
-               );
+                       );
 
        }
 
@@ -233,10 +235,10 @@ void cal_process_object(icalcomponent *cal,
 
                /** \todo  In the future, if we want to validate this object before \
                 * continuing, we can do it this way:
-               serv_printf("ICAL whatever|%ld|%s|", msgnum, cal_partnum);
-               serv_getln(buf, sizeof buf);
-               }
-                ***********/
+                serv_printf("ICAL whatever|%ld|%s|", msgnum, cal_partnum);
+                serv_getln(buf, sizeof buf);
+                }
+               ***********/
 
                /** Display the update buttons */
                wprintf("<p id=\"%s_question\" >"
@@ -250,10 +252,10 @@ void cal_process_object(icalcomponent *cal,
                        _("Click <i>Update</i> to accept this reply and update your calendar."),
                        divname, divname, msgnum, cal_partnum, _("Update"),
                        divname, divname, msgnum, cal_partnum, _("Ignore")
-               );
-
+                       );
+       
        }
-
+       
        /** Trailing HTML for the display of this object */
        if (recursion_level == 0) {
                wprintf("<p>&nbsp;</p></div>\n");
@@ -269,7 +271,8 @@ void cal_process_object(icalcomponent *cal,
  * \param msgnum number of the mesage in our db
  * \param cal_partnum the number of the calendar item
  */
-void cal_process_attachment(char *part_source, long msgnum, char *cal_partnum) {
+void cal_process_attachment(char *part_source, long msgnum, char *cal_partnum) 
+{
        icalcomponent *cal;
 
        cal = icalcomponent_new_from_string(part_source);
@@ -294,7 +297,8 @@ void cal_process_attachment(char *part_source, long msgnum, char *cal_partnum) {
  * \brief accept/decline meeting
  * Respond to a meeting request
  */
-void respond_to_request(void) {
+void respond_to_request(void) 
+{
        char buf[1024];
 
        begin_ajax_response();
@@ -318,8 +322,8 @@ void respond_to_request(void) {
                        );
                } else if (!strcasecmp(bstr("sc"), "decline")) {
                        wprintf(_("You have declined this meeting invitation.  "
-                               "It has <b>not</b> been entered into your calendar.")
-                       );
+                                 "It has <b>not</b> been entered into your calendar.")
+                               );
                }
                wprintf(" ");
                wprintf(_("A reply has been sent to the meeting organizer."));
@@ -338,7 +342,8 @@ void respond_to_request(void) {
 /**
  * \brief Handle an incoming RSVP
  */
-void handle_rsvp(void) {
+void handle_rsvp(void) 
+{
        char buf[1024];
 
        begin_ajax_response();
@@ -356,8 +361,8 @@ void handle_rsvp(void) {
                        wprintf(_("Your calendar has been updated to reflect this RSVP."));
                } else if (!strcasecmp(bstr("sc"), "ignore")) {
                        wprintf(_("You have chosen to ignore this RSVP. "
-                               "Your calendar has <b>not</b> been updated.")
-                       );
+                                 "Your calendar has <b>not</b> been updated.")
+                               );
                }
                wprintf("</span>");
        } else {
@@ -366,7 +371,6 @@ void handle_rsvp(void) {
        }
 
        end_ajax_response();
-
 }
 
 
@@ -408,13 +412,14 @@ void display_individual_cal(icalcomponent *cal, long msgnum, char *from, int unr
 
 
 
-/*
+/**
  * \brief edit a task
  * Display a task by itself (for editing)
  * \param supplied_vtodo the todo item we want to edit
  * \param msgnum number of the mesage in our db
  */
-void display_edit_individual_task(icalcomponent *supplied_vtodo, long msgnum, char *from, int unread) {
+void display_edit_individual_task(icalcomponent *supplied_vtodo, long msgnum, char *from, int unread) 
+{
        icalcomponent *vtodo;
        icalproperty *p;
        struct icaltimetype t;
@@ -441,7 +446,7 @@ void display_edit_individual_task(icalcomponent *supplied_vtodo, long msgnum, ch
                                        ), 
                                msgnum,
                                from, unread
-                       );
+                               );
                        return;
                }
        }
@@ -449,26 +454,30 @@ void display_edit_individual_task(icalcomponent *supplied_vtodo, long msgnum, ch
                vtodo = icalcomponent_new(ICAL_VTODO_COMPONENT);
                created_new_vtodo = 1;
        }
-
-       output_headers(1, 1, 2, 0, 0, 0);
-       wprintf("<div id=\"banner\">\n");
-       wprintf("<img src=\"static/taskmanag_48x.gif\">");
-       wprintf("<h1>");
+       
+       // TODO: Can we take all this and move it into a template?      
+       output_headers(1, 1, 1, 0, 0, 0);
+       wprintf("<!-- start task edit form -->");
+       p = icalcomponent_get_first_property(vtodo, ICAL_SUMMARY_PROPERTY);
+       // Get summary early for title
+       wprintf("<div class=\"box\">\n");
+       wprintf("<div class=\"boxlabel\">");
        wprintf(_("Edit task"));
-       wprintf("</h1>");
-       wprintf("</div>\n");
-
-       wprintf("<div id=\"content\" class=\"service\">\n");
-
-       wprintf("<div class=\"fix_scrollbar_bug\">"
-               "<table class=\"calendar_background\"><tr><td>");
+       wprintf("- ");
+       if (p != NULL) {
+               escputs((char *)icalproperty_get_comment(p));
+       }
+       wprintf("</div>");
        
+       wprintf("<div class=\"boxcontent\">\n");
        wprintf("<FORM METHOD=\"POST\" action=\"save_task\">\n");
+       wprintf("<div style=\"display: none;\">\n       ");
        wprintf("<input type=\"hidden\" name=\"nonce\" value=\"%ld\">\n", WC->nonce);
        wprintf("<INPUT TYPE=\"hidden\" NAME=\"msgnum\" VALUE=\"%ld\">\n",
                msgnum);
-
-       wprintf("<TABLE border=0>\n");
+       wprintf("</div>");
+       wprintf("<table class=\"calendar_background\"><tr><td>");
+       wprintf("<TABLE STYLE=\"border: none;\">\n");
 
        wprintf("<TR><TD>");
        wprintf(_("Summary:"));
@@ -485,12 +494,19 @@ void display_edit_individual_task(icalcomponent *supplied_vtodo, long msgnum, ch
        wprintf(_("Start date:"));
        wprintf("</TD><TD>");
        p = icalcomponent_get_first_property(vtodo, ICAL_DTSTART_PROPERTY);
+       wprintf("<INPUT TYPE=\"CHECKBOX\" NAME=\"nodtstart\" ID=\"nodtstart\" VALUE=\"NODTSTART\" ");
+       if (p == NULL) {
+               wprintf("CHECKED=\"CHECKED\"");
+       }
+       wprintf(">");
+       wprintf(_("No date"));
+       
+       wprintf(" ");
+       wprintf(_("or"));
+       wprintf(" ");
        if (p != NULL) {
                t = icalproperty_get_dtstart(p);
        }
-       else {
-               t = icaltime_from_timet(now, 0);
-       }
        display_icaltimetype_as_webform(&t, "dtstart");
        wprintf("</TD></TR>\n");
 
@@ -498,41 +514,70 @@ void display_edit_individual_task(icalcomponent *supplied_vtodo, long msgnum, ch
        wprintf(_("Due date:"));
        wprintf("</TD><TD>");
        p = icalcomponent_get_first_property(vtodo, ICAL_DUE_PROPERTY);
+       wprintf("<INPUT TYPE=\"CHECKBOX\" NAME=\"nodue\" ID=\"nodue\" VALUE=\"NODUE\"");
+       if (p == NULL) {
+               wprintf("CHECKED=\"CHECKED\"");
+       }
+       wprintf(">");
+       wprintf(_("No date"));
+       wprintf(" ");
+       wprintf(_("or"));
+       wprintf(" ");
        if (p != NULL) {
                t = icalproperty_get_due(p);
        }
-       else {
-               t = icaltime_from_timet(now, 0);
-       }
        display_icaltimetype_as_webform(&t, "due");
+               
        wprintf("</TD></TR>\n");
+       icalproperty_status todoStatus = icalcomponent_get_status(vtodo);
+       wprintf("<TR><TD>\n");
+       wprintf(_("Completed:"));
+       wprintf("</TD><TD>");
+       wprintf("<INPUT TYPE=\"CHECKBOX\" NAME=\"STATUS\" VALUE=\"COMPLETED\"");
+       if (todoStatus == ICAL_STATUS_COMPLETED) {
+               wprintf(" CHECKED=\"CHECKED\"");
+       } 
+       wprintf(" >");
+       wprintf("</TD></TR>");
+       // start category field
+       p = icalcomponent_get_first_property(vtodo, ICAL_CATEGORIES_PROPERTY);
+       wprintf("<TR><TD>");
+       wprintf(_("Category:"));
+       wprintf("</TD><TD>");
+       wprintf("<INPUT TYPE=\"text\" NAME=\"category\" MAXLENGTH=\"32\" SIZE=\"32\" VALUE=\"");
+       if (p != NULL) {
+               escputs((char *)icalproperty_get_categories(p));
+       }
+       wprintf("\">");
+       wprintf("</TD></TR>\n   ");
+       // end category field
        wprintf("<TR><TD>");
        wprintf(_("Description:"));
        wprintf("</TD><TD>");
-       wprintf("<TEXTAREA NAME=\"description\" wrap=soft "
-               "ROWS=10 COLS=80 WIDTH=80>\n"
-       );
+       wprintf("<TEXTAREA NAME=\"description\" "
+               "ROWS=\"10\" COLS=\"80\">\n"
+               );
        p = icalcomponent_get_first_property(vtodo, ICAL_DESCRIPTION_PROPERTY);
        if (p != NULL) {
                escputs((char *)icalproperty_get_comment(p));
        }
        wprintf("</TEXTAREA></TD></TR></TABLE>\n");
 
-       wprintf("<CENTER>"
+       wprintf("<SPAN STYLE=\"text-align: center;\">"
                "<INPUT TYPE=\"submit\" NAME=\"save_button\" VALUE=\"%s\">"
                "&nbsp;&nbsp;"
                "<INPUT TYPE=\"submit\" NAME=\"delete_button\" VALUE=\"%s\">\n"
                "&nbsp;&nbsp;"
                "<INPUT TYPE=\"submit\" NAME=\"cancel_button\" VALUE=\"%s\">\n"
-               "</CENTER>\n",
+               "</SPAN>\n",
                _("Save"),
                _("Delete"),
                _("Cancel")
-       );
-
+               );
+       wprintf("</td></tr></table>");
        wprintf("</FORM>\n");
-
-       wprintf("</td></tr></table></div>\n");
+       wprintf("</div></div></div>\n");
+       wprintf("<!-- end task edit form -->");
        wDumpContent(1);
 
        if (created_new_vtodo) {
@@ -545,7 +590,8 @@ void display_edit_individual_task(icalcomponent *supplied_vtodo, long msgnum, ch
  * \param supplied_vtodo the task to save
  * \param msgnum number of the mesage in our db
  */
-void save_individual_task(icalcomponent *supplied_vtodo, long msgnum, char* from, int unread) {
+void save_individual_task(icalcomponent *supplied_vtodo, long msgnum, char* from, int unread) 
+{
        char buf[SIZ];
        int delete_existing = 0;
        icalproperty *prop;
@@ -570,7 +616,7 @@ void save_individual_task(icalcomponent *supplied_vtodo, long msgnum, char* from
                                icalcomponent_get_first_component(
                                        vtodo, ICAL_VTODO_COMPONENT), 
                                msgnum, from, unread
-                       );
+                               );
                        return;
                }
        }
@@ -584,61 +630,84 @@ void save_individual_task(icalcomponent *supplied_vtodo, long msgnum, char* from
                /** Replace values in the component with ones from the form */
 
                while (prop = icalcomponent_get_first_property(vtodo,
-                     ICAL_SUMMARY_PROPERTY), prop != NULL) {
+                                                              ICAL_SUMMARY_PROPERTY), prop != NULL) {
                        icalcomponent_remove_property(vtodo, prop);
                        icalproperty_free(prop);
                }
-               if (havebstr("summary")) {
-       
-                       icalcomponent_add_property(vtodo,
-                                       icalproperty_new_summary(bstr("summary")));
-               } else {
-                       icalcomponent_add_property(vtodo,
-                                       icalproperty_new_summary("Untitled Task"));
-               }
+               if (havebstr("summary")) {
+
+                       icalcomponent_add_property(vtodo,
+                                                  icalproperty_new_summary(bstr("summary")));
+               } else {
+                       icalcomponent_add_property(vtodo,
+                                                  icalproperty_new_summary("Untitled Task"));
+               }
        
                while (prop = icalcomponent_get_first_property(vtodo,
-                     ICAL_DESCRIPTION_PROPERTY), prop != NULL) {
+                                                              ICAL_DESCRIPTION_PROPERTY), prop != NULL) {
                        icalcomponent_remove_property(vtodo, prop);
                        icalproperty_free(prop);
                }
-               icalcomponent_add_property(vtodo,
-                       icalproperty_new_description(bstr("description")));
+               if (!IsEmptyStr(bstr("description"))) {
+                       icalcomponent_add_property(vtodo,
+                                                  icalproperty_new_description(bstr("description")));
+               }
        
                while (prop = icalcomponent_get_first_property(vtodo,
-                     ICAL_DTSTART_PROPERTY), prop != NULL) {
+                                                              ICAL_DTSTART_PROPERTY), prop != NULL) {
                        icalcomponent_remove_property(vtodo, prop);
                        icalproperty_free(prop);
                }
-               icaltime_from_webform(&t, "dtstart");
-               icalcomponent_add_property(vtodo,
-                       icalproperty_new_dtstart(t)
-               );
-       
+               if (IsEmptyStr(bstr("nodtstart"))) {
+                       icaltime_from_webform(&t, "dtstart");
+                       icalcomponent_add_property(vtodo,
+                                                  icalproperty_new_dtstart(t)
+                               );
+               }
+               while(prop = icalcomponent_get_first_property(vtodo,
+                                                             ICAL_STATUS_PROPERTY), prop != NULL) {
+                       icalcomponent_remove_property(vtodo,prop);
+                       icalproperty_free(prop);
+               }
+               if (!IsEmptyStr(bstr("status"))) {
+                       icalproperty_status taskStatus = icalproperty_string_to_status(
+                               bstr("status"));
+                       icalcomponent_set_status(vtodo, taskStatus);
+               }
                while (prop = icalcomponent_get_first_property(vtodo,
-                     ICAL_DUE_PROPERTY), prop != NULL) {
+                                                              ICAL_CATEGORIES_PROPERTY), prop != NULL) {
+                       icalcomponent_remove_property(vtodo,prop);
+                       icalproperty_free(prop);
+               }
+               if (!IsEmptyStr(bstr("category"))) {
+                       prop = icalproperty_new_categories(bstr("category"));
+                       icalcomponent_add_property(vtodo,prop);
+               }
+               while (prop = icalcomponent_get_first_property(vtodo,
+                                                              ICAL_DUE_PROPERTY), prop != NULL) {
                        icalcomponent_remove_property(vtodo, prop);
                        icalproperty_free(prop);
                }
-               icaltime_from_webform(&t, "due");
-               icalcomponent_add_property(vtodo,
-                       icalproperty_new_due(t)
-               );
-
+               if (IsEmptyStr(bstr("nodue"))) {
+                       icaltime_from_webform(&t, "due");
+                       icalcomponent_add_property(vtodo,
+                                                  icalproperty_new_due(t)
+                               );
+               }
                /** Give this task a UID if it doesn't have one. */
                lprintf(9, "Give this task a UID if it doesn't have one.\n");
                if (icalcomponent_get_first_property(vtodo,
-                  ICAL_UID_PROPERTY) == NULL) {
+                                                    ICAL_UID_PROPERTY) == NULL) {
                        generate_uuid(buf);
                        icalcomponent_add_property(vtodo,
-                               icalproperty_new_uid(buf)
-                       );
+                                                  icalproperty_new_uid(buf)
+                               );
                }
 
                /** Increment the sequence ID */
                lprintf(9, "Increment the sequence ID\n");
                while (prop = icalcomponent_get_first_property(vtodo,
-                     ICAL_SEQUENCE_PROPERTY), (prop != NULL) ) {
+                                                              ICAL_SEQUENCE_PROPERTY), (prop != NULL) ) {
                        i = icalproperty_get_sequence(prop);
                        lprintf(9, "Sequence was %d\n", i);
                        if (i > sequence) sequence = i;
@@ -648,8 +717,8 @@ void save_individual_task(icalcomponent *supplied_vtodo, long msgnum, char* from
                ++sequence;
                lprintf(9, "New sequence is %d.  Adding...\n", sequence);
                icalcomponent_add_property(vtodo,
-                       icalproperty_new_sequence(sequence)
-               );
+                                          icalproperty_new_sequence(sequence)
+                       );
 
                /**
                 * Encapsulate event into full VCALENDAR component.  Clone it first,
@@ -717,7 +786,8 @@ void save_individual_task(icalcomponent *supplied_vtodo, long msgnum, char* from
 void display_using_handler(long msgnum, int unread,
                           icalcomponent_kind which_kind,
                           void (*callback)(icalcomponent *, long, char*, int)
-       ) {
+       ) 
+{
        char buf[1024];
        char from[128] = "";
        char mime_partnum[256];
@@ -744,9 +814,9 @@ void display_using_handler(long msgnum, int unread,
                        mime_length = extract_int(&buf[5], 5);
 
                        if (  (!strcasecmp(mime_content_type, "text/calendar"))
-                          || (!strcasecmp(mime_content_type, "application/ics"))
-                          || (!strcasecmp(mime_content_type, "text/vtodo"))
-                       ) {
+                             || (!strcasecmp(mime_content_type, "application/ics"))
+                             || (!strcasecmp(mime_content_type, "text/vtodo"))
+                               ) {
                                strcpy(relevant_partnum, mime_partnum);
                        }
                }
@@ -771,10 +841,10 @@ void display_using_handler(long msgnum, int unread,
 
                                /** Subcomponents of desired type */
                                for (c = icalcomponent_get_first_component(cal,
-                                   which_kind);
-                                   (c != 0);
-                                   c = icalcomponent_get_next_component(cal,
-                                   which_kind)) {
+                                                                          which_kind);
+                                    (c != 0);
+                                    c = icalcomponent_get_next_component(cal,
+                                                                         which_kind)) {
                                        callback(c, msgnum, from, unread);
                                }
                                icalcomponent_free(cal);
@@ -791,8 +861,8 @@ void display_using_handler(long msgnum, int unread,
  */
 void display_calendar(long msgnum, int unread) {
        display_using_handler(msgnum, unread,
-                               ICAL_VEVENT_COMPONENT,
-                               display_individual_cal);
+                             ICAL_VEVENT_COMPONENT,
+                             display_individual_cal);
 }
 
 /**
@@ -801,8 +871,8 @@ void display_calendar(long msgnum, int unread) {
  */
 void display_task(long msgnum, int unread) {
        display_using_handler(msgnum, unread,
-                               ICAL_VTODO_COMPONENT,
-                               display_individual_cal);
+                             ICAL_VTODO_COMPONENT,
+                             display_individual_cal);
 }
 
 /**
@@ -810,18 +880,18 @@ void display_task(long msgnum, int unread) {
  */
 void display_edit_task(void) {
        long msgnum = 0L;
-
+                       
        /** Force change the room if we have to */
        if (havebstr("taskrm")) {
-               gotoroom(bstr("taskrm"));
+               gotoroom((char *)bstr("taskrm"));
        }
 
        msgnum = lbstr("msgnum");
        if (msgnum > 0L) {
                /** existing task */
                display_using_handler(msgnum, 0,
-                               ICAL_VTODO_COMPONENT,
-                               display_edit_individual_task);
+                                     ICAL_VTODO_COMPONENT,
+                                     display_edit_individual_task);
        }
        else {
                /** new task */
@@ -838,8 +908,8 @@ void save_task(void) {
        msgnum = lbstr("msgnum");
        if (msgnum > 0L) {
                display_using_handler(msgnum, 0,
-                               ICAL_VTODO_COMPONENT,
-                               save_individual_task);
+                                     ICAL_VTODO_COMPONENT,
+                                     save_individual_task);
        }
        else {
                save_individual_task(NULL, 0L, "", 0);
@@ -856,8 +926,8 @@ void display_edit_event(void) {
        if (msgnum > 0L) {
                /* existing event */
                display_using_handler(msgnum, 0,
-                               ICAL_VEVENT_COMPONENT,
-                               display_edit_individual_event);
+                                     ICAL_VEVENT_COMPONENT,
+                                     display_edit_individual_event);
        }
        else {
                /* new event */
@@ -875,8 +945,8 @@ void save_event(void) {
 
        if (msgnum > 0L) {
                display_using_handler(msgnum, 0,
-                               ICAL_VEVENT_COMPONENT,
-                               save_individual_event);
+                                     ICAL_VEVENT_COMPONENT,
+                                     save_individual_event);
        }
        else {
                save_individual_event(NULL, 0L, "", 0);
@@ -905,8 +975,8 @@ void do_freebusy(char *req) {
 
        len = strlen(who);
        if ( (!strcasecmp(&who[len-4], ".vcf"))
-          || (!strcasecmp(&who[len-4], ".ifb"))
-          || (!strcasecmp(&who[len-4], ".vfb")) ) {
+            || (!strcasecmp(&who[len-4], ".ifb"))
+            || (!strcasecmp(&who[len-4], ".vfb")) ) {
                who[len-4] = 0;
        }
 
@@ -928,4 +998,3 @@ void do_freebusy(char *req) {
        free(fb);
 }
 
-
index d07fd6f38f18f8e673d577770dea7297e456a775..f06b8e09fd6f7401a3301942646226db28127e19 100644 (file)
@@ -6,6 +6,7 @@
 
 #include "webcit.h"
 #include "webserver.h"
+#include "time.h"
 
 /** Hour strings */
 char *hourname[] = {
@@ -15,7 +16,7 @@ char *hourname[] = {
        "7pm", "8pm", "9pm", "10pm", "11pm"
 };
 
-/*
+/**
  * \brief display and edit date/time
  * The display_icaltimetype_as_webform() and icaltime_from_webform() functions
  * handle the display and editing of date/time properties in web pages.  The
@@ -43,12 +44,9 @@ void display_icaltimetype_as_webform(struct icaltimetype *t, char *prefix) {
        int this_year;
        time_t tt;
        struct tm tm;
-       const int span = 10;
        int all_day_event = 0;
-       time_t monthselect_time;
-       struct tm monthselect_tm;
-       char monthselect_str[32];
        int time_format;
+       char timebuf[32];
        
        time_format = get_time_format_cached ();
 
@@ -66,63 +64,35 @@ void display_icaltimetype_as_webform(struct icaltimetype *t, char *prefix) {
                localtime_r(&tt, &tm);
        }
 
-       wprintf(_("Month: "));
-       wprintf("<SELECT NAME=\"%s_month\" SIZE=\"1\">\n", prefix);
-       for (i=0; i<=11; ++i) {
-               monthselect_time = 1137997451 + (i * 2592000);
-               localtime_r(&monthselect_time, &monthselect_tm);
-               wc_strftime(monthselect_str, sizeof monthselect_str, "%B", &monthselect_tm);
-               wprintf("<OPTION %s VALUE=\"%d\">%s</OPTION>\n",
-                       ((tm.tm_mon == i) ? "SELECTED" : ""),
-                       i+1,
-                       monthselect_str
-               );
-       }
-       wprintf("</SELECT>\n");
-
-       wprintf(_("Day: "));
-       wprintf("<SELECT NAME=\"%s_day\" SIZE=\"1\">\n", prefix);
-       for (i=1; i<=31; ++i) {
-               wprintf("<OPTION %s VALUE=\"%d\">%d</OPTION>\n",
-                       ((tm.tm_mday == i) ? "SELECTED" : ""),
-                       i, i
-               );
-       }
-       wprintf("</SELECT>\n");
-
-       wprintf(_("Year: "));
-       wprintf("<SELECT NAME=\"%s_year\" SIZE=\"1\">\n", prefix);
-       if ((this_year - t->year) > span) {
-               wprintf("<OPTION SELECTED VALUE=\"%d\">%d</OPTION>\n",
-                       t->year, t->year);
-       }
-       for (i=(this_year-span); i<=(this_year+span); ++i) {
-               wprintf("<OPTION %s VALUE=\"%d\">%d</OPTION>\n",
-                       ((t->year == i) ? "SELECTED" : ""),
-                       i, i
-               );
-       }
-       if ((t->year - this_year) > span) {
-               wprintf("<OPTION SELECTED VALUE=\"%d\">%d</OPTION>\n",
-                       t->year, t->year);
-       }
-       wprintf("</SELECT>\n");
-
+       wprintf("<input type=\"text\" name=\"");
+       wprintf(prefix);
+       wprintf("\" id=\"");
+       wprintf(prefix);
+       wprintf("\" value=\"");
+       wc_strftime(timebuf, 32, "%d/%m/%Y", &tm);
+       wprintf(timebuf);
+       wprintf("\">");
+       wprintf("<script type=\"text/javascript\">\n");
+       wprintf("       var dpck = new DatePicker({\n           ");
+       wprintf("relative: '");
+       wprintf(prefix);
+       wprintf("',\n   language: 'en',\n");
+       wprintf("disableFutureDate:     false\n});</script>");
        wprintf(_("Hour: "));
        wprintf("<SELECT NAME=\"%s_hour\" SIZE=\"1\">\n", prefix);
        for (i=0; i<=23; ++i) {
 
-       if (time_format == WC_TIMEFORMAT_24) {
+               if (time_format == WC_TIMEFORMAT_24) {
                        wprintf("<OPTION %s VALUE=\"%d\">%d</OPTION>\n",
                                ((tm.tm_hour == i) ? "SELECTED" : ""),
                                i, i
-                       );
+                               );
                }
                else {
                        wprintf("<OPTION %s VALUE=\"%d\">%s</OPTION>\n",
                                ((tm.tm_hour == i) ? "SELECTED" : ""),
                                i, hourname[i]
-                       );
+                               );
                }
 
        }
@@ -135,7 +105,7 @@ void display_icaltimetype_as_webform(struct icaltimetype *t, char *prefix) {
                        wprintf("<OPTION %s VALUE=\"%d\">:%02d</OPTION>\n",
                                ((tm.tm_min == i) ? "SELECTED" : ""),
                                i, i
-                       );
+                               );
                }
        }
        wprintf("</SELECT>\n");
@@ -148,20 +118,29 @@ void display_icaltimetype_as_webform(struct icaltimetype *t, char *prefix) {
  * \param prefix whats that\todo ????
  */
 void icaltime_from_webform(struct icaltimetype *t, char *prefix) {
-       char vname[32];
-       struct icaltimetype t2;
-       char timestr[32];
-       int month, mday, year, hour, minute;
-
-        sprintf(vname, "%s_month", prefix);     month = atoi(BSTR(vname));
-        sprintf(vname, "%s_day", prefix);       mday = atoi(BSTR(vname));
-        sprintf(vname, "%s_year", prefix);      year = atoi(BSTR(vname));
-        sprintf(vname, "%s_hour", prefix);      hour = atoi(BSTR(vname));
-        sprintf(vname, "%s_minute", prefix);    minute = atoi(BSTR(vname));
-
-       sprintf(timestr, "%04d%02d%02dT%02d%02d00", year, month, mday, hour, minute);
-        t2 = icaltime_from_string(timestr);
-       memcpy(t, &t2, sizeof(struct icaltimetype));
+       char datebuf[32];
+       char vname[32];
+       struct tm tm;
+       /* Stuff tm with some zero values */
+       tm.tm_year = 0;
+       tm.tm_sec = 0;
+       tm.tm_min = 0;
+       tm.tm_hour = 0;
+       tm.tm_mday = 0;
+       tm.tm_mon = 0;
+       int hour = 0;
+       int minute = 0;
+       struct icaltimetype t2;
+       
+       
+       strptime((char*)BSTR(prefix), "%d/%m/%Y", &tm);
+       sprintf(vname, "%s_hour", prefix);      hour = IBSTR(vname);
+       sprintf(vname, "%s_minute", prefix);    minute = IBSTR(vname);
+       tm.tm_hour = hour;
+       tm.tm_min = minute;
+       strftime(&datebuf[0], 32, "%Y%m%dT%H%M%S", &tm);
+       t2 = icaltime_from_string(datebuf);
+       memcpy(t, &t2, sizeof(struct icaltimetype));
 }
 
 
@@ -173,15 +152,19 @@ void icaltime_from_webform(struct icaltimetype *t, char *prefix) {
  */
 
 void icaltime_from_webform_dateonly(struct icaltimetype *t, char *prefix) {
-       char vname[32];
-
-       memset(t, 0, sizeof(struct icaltimetype));
-
-        sprintf(vname, "%s_month", prefix);     t->month = atoi(BSTR(vname));
-        sprintf(vname, "%s_day", prefix);       t->day = atoi(BSTR(vname));
-        sprintf(vname, "%s_year", prefix);      t->year = atoi(BSTR(vname));
-       t->is_utc = 1;
-       t->is_date = 1;
+       struct tm tm;
+       /* Stuff tm with some zero values */
+       tm.tm_sec = 0;
+       tm.tm_min = 0;
+       tm.tm_hour = 0;
+       tm.tm_mday = 0;
+       tm.tm_mon = 0;
+       time_t tm_t;
+       struct icaltimetype t2;         
+       strptime((char *)BSTR(prefix), "%d/%m/%Y", &tm);
+       tm_t = mktime(&tm);
+       t2 = icaltime_from_timet(tm_t, 1);
+       memcpy(t, &t2, sizeof(struct icaltimetype));
 }
 
 
@@ -198,42 +181,42 @@ void partstat_as_string(char *buf, icalproperty *attendee) {
        strcpy(buf, _("(status unknown)"));
 
        partstat_param = icalproperty_get_first_parameter(
-                               attendee,
-                               ICAL_PARTSTAT_PARAMETER
-       );
+               attendee,
+               ICAL_PARTSTAT_PARAMETER
+               );
        if (partstat_param == NULL) {
                return;
        }
 
        partstat = icalparameter_get_partstat(partstat_param);
        switch(partstat) {
-               case ICAL_PARTSTAT_X:
-                       strcpy(buf, "(x)");
-                       break;
-               case ICAL_PARTSTAT_NEEDSACTION:
-                       strcpy(buf, _("(needs action)"));
-                       break;
-               case ICAL_PARTSTAT_ACCEPTED:
-                       strcpy(buf, _("(accepted)"));
-                       break;
-               case ICAL_PARTSTAT_DECLINED:
-                       strcpy(buf, _("(declined)"));
-                       break;
-               case ICAL_PARTSTAT_TENTATIVE:
-                       strcpy(buf, _("(tenative)"));
-                       break;
-               case ICAL_PARTSTAT_DELEGATED:
-                       strcpy(buf, _("(delegated)"));
-                       break;
-               case ICAL_PARTSTAT_COMPLETED:
-                       strcpy(buf, _("(completed)"));
-                       break;
-               case ICAL_PARTSTAT_INPROCESS:
-                       strcpy(buf, _("(in process)"));
-                       break;
-               case ICAL_PARTSTAT_NONE:
-                       strcpy(buf, _("(none)"));
-                       break;
+       case ICAL_PARTSTAT_X:
+               strcpy(buf, "(x)");
+               break;
+       case ICAL_PARTSTAT_NEEDSACTION:
+               strcpy(buf, _("(needs action)"));
+               break;
+       case ICAL_PARTSTAT_ACCEPTED:
+               strcpy(buf, _("(accepted)"));
+               break;
+       case ICAL_PARTSTAT_DECLINED:
+               strcpy(buf, _("(declined)"));
+               break;
+       case ICAL_PARTSTAT_TENTATIVE:
+               strcpy(buf, _("(tenative)"));
+               break;
+       case ICAL_PARTSTAT_DELEGATED:
+               strcpy(buf, _("(delegated)"));
+               break;
+       case ICAL_PARTSTAT_COMPLETED:
+               strcpy(buf, _("(completed)"));
+               break;
+       case ICAL_PARTSTAT_INPROCESS:
+               strcpy(buf, _("(in process)"));
+               break;
+       case ICAL_PARTSTAT_NONE:
+               strcpy(buf, _("(none)"));
+               break;
        }
 }
 
index 6ecfa5cd65696b2323131099f45280e05e97350b..0cb0273d19a429edaf1094482cc4854e688f7fd1 100644 (file)
@@ -28,13 +28,13 @@ void embeddable_mini_calendar(int year, int month, char *urlformat)
        snprintf(div_id, sizeof div_id, "mini_calendar_%d", rand() );
 
        /* Determine what day to start.
-        */
+       */
        get_preference("weekstart", weekstart_buf, sizeof weekstart_buf);
        weekstart = atoi(weekstart_buf);
 
        /*
-        * Now back up to the 1st of the month...
-        */
+       * Now back up to the 1st of the month...
+       */
        memset(&starting_tm, 0, sizeof(struct tm));
 
        starting_tm.tm_year = year - 1900;
@@ -140,7 +140,7 @@ void embeddable_mini_calendar(int year, int month, char *urlformat)
                "</script>\n"
                ,
                escaped_urlformat, div_id
-       );
+               );
 
 }
 
@@ -259,7 +259,7 @@ void calendar_month_view_display_events(int year, int month, int day)
                                        (Cal->unread)?"_unread":"_read",
                                        WC->disp_cal[i].cal_msgnum,
                                        year, month, day
-                               );
+                                       );
 
                                wprintf("<i>%s: %s</i><br />", _("From"), Cal->from);
                                wprintf("<i>%s</i> ",          _("Summary:"));
@@ -273,7 +273,7 @@ void calendar_month_view_display_events(int year, int month, int day)
                                        wprintf("<i>%s</i> ", _("Location:"));
                                        escputs((char *)icalproperty_get_comment(q));
                                        wprintf("<br />");
-                                       }
+                               }
                                
                                /**
                                 * Only show start/end times if we're actually looking at the VEVENT
@@ -378,7 +378,7 @@ void calendar_month_view_brief_events(time_t thetime, const char *daycolor) {
        for (i=0; i<(WC->num_cal); ++i) {
                Cal = &WCC->disp_cal[i];
                p = icalcomponent_get_first_property(Cal->cal,
-                                               ICAL_DTSTART_PROPERTY);
+                       ICAL_DTSTART_PROPERTY);
                if (p != NULL) {
                        t = icalproperty_get_dtstart(p);
                        event_tt = icaltime_as_timet(t);
@@ -394,58 +394,58 @@ void calendar_month_view_brief_events(time_t thetime, const char *daycolor) {
                        }
                        /** \todo epoch &! daymask */
                        if ((event_tms.tm_year == today_tm.tm_year)
-                          && (event_tms.tm_mon == today_tm.tm_mon)
-                          && (event_tms.tm_mday == today_tm.tm_mday)) {
+                               && (event_tms.tm_mon == today_tm.tm_mon)
+                       && (event_tms.tm_mday == today_tm.tm_mday)) {
+                       
+                       
+                       char sbuf[255];
+                       char ebuf[255];
+                       
+                       p = icalcomponent_get_first_property(
+                               WC->disp_cal[i].cal,
+                               ICAL_SUMMARY_PROPERTY);
+                       e = icalcomponent_get_first_property(
+                               WC->disp_cal[i].cal, 
+                               ICAL_DTEND_PROPERTY);
+                       if ((p != NULL) && (e != NULL)) {
+                               time_t difftime;
+                               int hours, minutes;
+                               t = icalproperty_get_dtend(e);
+                               event_tte = icaltime_as_timet(t);
+                               localtime_r(&event_tte, &event_tme);
+                               difftime=(event_tte-event_tts)/60;
+                               hours=(int)(difftime / 60);
+                               minutes=difftime % 60;
+                               wprintf("<tr><td bgcolor='%s'>%i:%2i</td><td bgcolor='%s'>"
+                                       "<font size=-1>"
+                                       "<a class=\"event%s\" href=\"display_edit_event?msgnum=%ld&calview=calbrief&year=%s&month=%s&day=%s\">",
+                                       daycolor,
+                                       hours, minutes,
+                                       (Cal->unread)?"_unread":"_read",                                                
+                                       daycolor,
+                                       WC->disp_cal[i].cal_msgnum,
+                                       bstr("year"),
+                                       bstr("month"),
+                                       bstr("day")
+                                       );
                                
+                               escputs((char *)
+                                       icalproperty_get_comment(p));
+                               /** \todo: allso ammitime format */
+                               wc_strftime(&sbuf[0], sizeof(sbuf), timeformat, &event_tms);
+                               wc_strftime(&ebuf[0], sizeof(sbuf), timeformat, &event_tme);
                                
-                               char sbuf[255];
-                               char ebuf[255];
-
-                               p = icalcomponent_get_first_property(
-                                                       WC->disp_cal[i].cal,
-                                                       ICAL_SUMMARY_PROPERTY);
-                               e = icalcomponent_get_first_property(
-                                                       WC->disp_cal[i].cal, 
-                                                       ICAL_DTEND_PROPERTY);
-                               if ((p != NULL) && (e != NULL)) {
-                                       time_t difftime;
-                                       int hours, minutes;
-                                       t = icalproperty_get_dtend(e);
-                                       event_tte = icaltime_as_timet(t);
-                                       localtime_r(&event_tte, &event_tme);
-                                       difftime=(event_tte-event_tts)/60;
-                                       hours=(int)(difftime / 60);
-                                       minutes=difftime % 60;
-                                       wprintf("<tr><td bgcolor='%s'>%i:%2i</td><td bgcolor='%s'>"
-                                               "<font size=-1>"
-                                               "<a class=\"event%s\" href=\"display_edit_event?msgnum=%ld&calview=calbrief&year=%s&month=%s&day=%s\">",
-                                               daycolor,
-                                               hours, minutes,
-                                               (Cal->unread)?"_unread":"_read",                                                
-                                               daycolor,
-                                               WC->disp_cal[i].cal_msgnum,
-                                               bstr("year"),
-                                               bstr("month"),
-                                               bstr("day")
-                                               );
-
-                                       escputs((char *)
-                                                       icalproperty_get_comment(p));
-                                       /** \todo: allso ammitime format */
-                                       wc_strftime(&sbuf[0], sizeof(sbuf), timeformat, &event_tms);
-                                       wc_strftime(&ebuf[0], sizeof(sbuf), timeformat, &event_tme);
-
-                                       wprintf("</a></font></td>"
-                                                       "<td bgcolor='%s'>%s</td><td bgcolor='%s'>%s</td></tr>",
-                                                       daycolor,
-                                                       sbuf,
-                                                       daycolor,
-                                                       ebuf);
-                                       
-                               }
+                               wprintf("</a></font></td>"
+                                       "<td bgcolor='%s'>%s</td><td bgcolor='%s'>%s</td></tr>",
+                                       daycolor,
+                                       sbuf,
+                                       daycolor,
+                                       ebuf);
                                
                        }
                        
+                       }
+                       
                        
                }
        }
@@ -566,9 +566,9 @@ void calendar_month_view(int year, int month, int day) {
 
                        wprintf("<td class=\"cal%s\"><div class=\"day\">",
                                ((tm.tm_mon != month-1) ? "out" :
-                               ((tm.tm_wday==0 || tm.tm_wday==6) ? "weekend" :
-                               "day"))
-                       );
+                                       ((tm.tm_wday==0 || tm.tm_wday==6) ? "weekend" :
+                                               "day"))
+                               );
                        if ((i==0) || (tm.tm_mday == 1)) {
                                wc_strftime(colheader_label, sizeof colheader_label, "%B", &tm);
                                wprintf("%s ", colheader_label);
@@ -585,7 +585,7 @@ void calendar_month_view(int year, int month, int day) {
                                tm.tm_year + 1900,
                                tm.tm_mon + 1,
                                tm.tm_mday
-                       );
+                               );
 
                        wprintf("</td>");
 
@@ -707,27 +707,27 @@ void calendar_brief_month_view(int year, int month, int day) {
                if ((i % 7) == 0) {
                        wc_strftime(&weeknumber[0], sizeof(weeknumber), "%U", &tm);
                        wprintf("<table border='0' bgcolor=\"#EEEECC\" width='100%'> <tr><th colspan='4'>%s %s</th></tr>"
-                                       "   <tr><td>%s</td><td width=70%%>%s</td><td>%s</td><td>%s</td></tr>\n",
-                                       _("Week"), 
-                                       weeknumber,
-                                       _("Hours"),
-                                       _("Subject"),
-                                       _("Start"),
-                                       _("End")
-                                       );
+                               "   <tr><td>%s</td><td width=70%%>%s</td><td>%s</td><td>%s</td></tr>\n",
+                               _("Week"), 
+                               weeknumber,
+                               _("Hours"),
+                               _("Subject"),
+                               _("Start"),
+                               _("End")
+                               );
                }
                
                daycolor=((tm.tm_mon != month-1) ? "DDDDDD" :
-                                 ((tm.tm_wday==0 || tm.tm_wday==6) ? "EEEECC" :
-                                  "FFFFFF"));
+                       ((tm.tm_wday==0 || tm.tm_wday==6) ? "EEEECC" :
+                               "FFFFFF"));
                
                /** Day Header */
                wc_strftime(weekday_name, sizeof weekday_name, "%A", &tm);
                wprintf("<tr><td bgcolor='%s' colspan='1' align='left'> %s,%i."
-                               "</td><td bgcolor='%s' colspan='3'><hr></td></tr>\n",
-                               daycolor,
-                               weekday_name,tm.tm_mday,
-                               daycolor);
+                       "</td><td bgcolor='%s' colspan='3'><hr></td></tr>\n",
+                       daycolor,
+                       weekday_name,tm.tm_mday,
+                       daycolor);
 
                /** put the data of one day  here, stupid */
                calendar_month_view_brief_events(thetime, daycolor);
@@ -771,12 +771,12 @@ void calendar_week_view(int year, int month, int day) {
  * \param dend dayend 
  */
 void calendar_day_view_display_events(time_t thetime,
-                               int year,
-                               int month,
-                               int day,
-                               int notime_events,
-                               int dstart,
-                               int dend)
+       int year,
+       int month,
+       int day,
+       int notime_events,
+       int dstart,
+       int dend)
 {
        int i;
        icalproperty *p = NULL;
@@ -880,12 +880,12 @@ void calendar_day_view_display_events(time_t thetime,
 
                        if (all_day_event && notime_events)
                        {
-                              wprintf("<li class=\"event_framed%s\"> "
-                                "<a href=\"display_edit_event?"
-                                "msgnum=%ld&calview=day&year=%d&month=%d&day=%d\" "
-                                " class=\"event_title\" "
-                                " btt_tooltext=\"",
-                                     (Cal->unread)?"_unread":"_read",
+                               wprintf("<li class=\"event_framed%s\"> "
+                                       "<a href=\"display_edit_event?"
+                                       "msgnum=%ld&calview=day&year=%d&month=%d&day=%d\" "
+                                       " class=\"event_title\" "
+                                       " btt_tooltext=\"",
+                                       (Cal->unread)?"_unread":"_read",
                                         Cal->cal_msgnum, year, month, day);
                                 wprintf("<i>%s</i><br />",      _("All day event"));
                                wprintf("<i>%s: %s</i><br />",  _("From"), Cal->from);
@@ -897,7 +897,7 @@ void calendar_day_view_display_events(time_t thetime,
                                         wprintf("<i>%s</i> ", _("Location:"));
                                         escputs((char *)icalproperty_get_comment(q));
                                         wprintf("<br />");
-                                        }
+                                                               }
                                 memset(&d_tm, 0, sizeof d_tm);
                                 d_tm.tm_year = t.year - 1900;
                                 d_tm.tm_mon = t.month - 1;
@@ -919,12 +919,12 @@ void calendar_day_view_display_events(time_t thetime,
                        else if (ongoing_event && notime_events) 
                        {
                                wprintf("<li class=\"event_framed%s\"> "
-                               "<a href=\"display_edit_event?"
-                               "msgnum=%ld&calview=day&year=%d&month=%d&day=%d\" "
-                               " class=\"event_title\" " 
-                                "btt_tooltext=\"",
+                                       "<a href=\"display_edit_event?"
+                                       "msgnum=%ld&calview=day&year=%d&month=%d&day=%d\" "
+                                       " class=\"event_title\" " 
+                                       "btt_tooltext=\"",
                                        (Cal->unread)?"_unread":"_read",
-                               Cal->cal_msgnum, year, month, day);
+                                       Cal->cal_msgnum, year, month, day);
                                 wprintf("<i>%s</i><br />",     _("Ongoing event"));
                                wprintf("<i>%s: %s</i><br />", _("From"), Cal->from);
                                 wprintf("<i>%s</i> ",          _("Summary:"));
@@ -935,7 +935,7 @@ void calendar_day_view_display_events(time_t thetime,
                                         wprintf("<i>%s</i> ", _("Location:"));
                                         escputs((char *)icalproperty_get_comment(q));
                                         wprintf("<br />");
-                                        }
+                                                               }
                                 webcit_fmt_date(buf, event_tt, 1);
                                 wprintf("<i>%s</i> %s<br>", _("Starting date/time:"), buf);
                                 webcit_fmt_date(buf, event_tte, 1);
@@ -1014,7 +1014,7 @@ void calendar_day_view_display_events(time_t thetime,
                                         wprintf("<i>%s</i> ", _("Location:"));
                                         escputs((char *)icalproperty_get_comment(q));
                                         wprintf("<br />");
-                                        }
+                                                               }
                                 webcit_fmt_date(buf, event_tt, 1);
                                 wprintf("<i>%s</i> %s<br>", _("Starting date/time:"), buf);
                                 webcit_fmt_date(buf, event_tte, 1);
@@ -1107,7 +1107,7 @@ void calendar_day_view(int year, int month, int day) {
                        "&calview=day&year=%d&month=%d&day=%d&hour=%d&minute=0\">",
                        (hour * extratimeline ), extratimeline, 
                        year, month, day, hour
-               );
+                       );
 
                if (time_format == WC_TIMEFORMAT_24) {
                        wprintf("%2d:00</a> ", hour);
@@ -1116,7 +1116,7 @@ void calendar_day_view(int year, int month, int day) {
                        wprintf("%d:00%s</a> ",
                                (hour <= 12 ? hour : hour-12),
                                (hour < 12 ? "am" : "pm")
-                       );
+                               );
                }
 
                wprintf("</dt>");
@@ -1135,7 +1135,7 @@ void calendar_day_view(int year, int month, int day) {
                         "&year=%d&month=%d&day=%d&hour=%d&minute=0\">",
                         gap + ((hour - daystart) * timeline ), timeline,
                         year, month, day, hour
-                );
+                       );
 
                 if (time_format == WC_TIMEFORMAT_24) {
                         wprintf("%2d:00</a> ", hour);
@@ -1144,7 +1144,7 @@ void calendar_day_view(int year, int month, int day) {
                         wprintf("%d:00%s</a> ",
                                 (hour <= 12 ? hour : hour-12),
                                 (hour < 12 ? "am" : "pm")
-                        );
+                                               );
                 }
 
                 wprintf("</dt>");
@@ -1217,7 +1217,7 @@ void calendar_day_view(int year, int month, int day) {
                "<font size=+2>%Y</font><br />"
                "</td>",
                &d_tm
-       );
+               );
        wprintf("%s", d_str);
 
        /** Right arrow */
@@ -1292,19 +1292,19 @@ void calendar_summary_view(void) {
                        }
 
                        if ( (event_tm.tm_year == today_tm.tm_year)
-                          && (event_tm.tm_mon == today_tm.tm_mon)
-                          && (event_tm.tm_mday == today_tm.tm_mday)
-                          ) {
-
-
-                               p = icalcomponent_get_first_property(
-                                                       WC->disp_cal[i].cal,
-                                                       ICAL_SUMMARY_PROPERTY);
-                               if (p != NULL) {
-                                       escputs((char *)
-                                               icalproperty_get_comment(p));
-                                       wprintf(" (%s)<br />\n", timestring);
-                               }
+                               && (event_tm.tm_mon == today_tm.tm_mon)
+                       && (event_tm.tm_mday == today_tm.tm_mday)
+                       ) {
+
+
+                       p = icalcomponent_get_first_property(
+                               WC->disp_cal[i].cal,
+                               ICAL_SUMMARY_PROPERTY);
+                       if (p != NULL) {
+                               escputs((char *)
+                                       icalproperty_get_comment(p));
+                               wprintf(" (%s)<br />\n", timestring);
+                       }
                        }
                }
        }
@@ -1403,8 +1403,8 @@ time_t get_task_due_date(icalcomponent *vtodo) {
                return get_task_due_date(
                        icalcomponent_get_first_component(
                                vtodo, ICAL_VTODO_COMPONENT
-                       )
-               );
+                               )
+                       );
        }
 
        p = icalcomponent_get_first_property(vtodo, ICAL_DUE_PROPERTY);
@@ -1428,12 +1428,22 @@ int task_due_cmp(const void *task1, const void *task2) {
 
        t1 =  get_task_due_date(((struct disp_cal *)task1)->cal);
        t2 =  get_task_due_date(((struct disp_cal *)task2)->cal);
-
        if (t1 < t2) return(-1);
        if (t1 > t2) return(1);
        return(0);
 }
 
+/**
+ * \brief qsort filter to move completed tasks to bottom of task list
+ */
+int task_completed_cmp(const void *task1, const void *task2) {
+       icalproperty_status t1 = icalcomponent_get_status(((struct disp_cal *)task1)->cal);
+       // icalproperty_status t2 = icalcomponent_get_status(((struct disp_cal *)task2)->cal);
+       
+       if (t1 == ICAL_STATUS_COMPLETED) 
+               return 1;
+       return 0;
+}
 
 
 
@@ -1443,18 +1453,20 @@ int task_due_cmp(const void *task1, const void *task2) {
 void do_tasks_view(void) {
        int i;
        time_t due;
-       int bg = 0;
        char buf[SIZ];
        icalproperty *p;
-
        wprintf("<div class=\"fix_scrollbar_bug\">"
-               "<table class=\"calendar_view_background\">\n<tr>\n"
+               "<table class=\"calendar_view_background\"><tbody id=\"taskview\">\n<tr>\n"
                "<th>");
+       wprintf(_("Completed?"));
+       wprintf("</th><th>");
        wprintf(_("Name of task"));
        wprintf("</th><th>");
        wprintf(_("Date due"));
-       wprintf("</th></tr>\n"
-       );
+       wprintf("</th><th>");
+       wprintf(_("Category"));
+       wprintf(" (<select id=\"selectcategory\"><option value=\"showall\">%s</option></select>)</th></tr>\n",
+               _("Show All"));
 
        /** Sort them if necessary */
        if (WC->num_cal > 1) {
@@ -1462,24 +1474,33 @@ void do_tasks_view(void) {
                        WC->num_cal,
                        sizeof(struct disp_cal),
                        task_due_cmp
-               );
+                       );
+       }
+       /** And then again, by completed */
+       if (WC->num_cal > 1) {
+               qsort(WC->disp_cal,
+                       WC->num_cal,
+                       sizeof(struct disp_cal),
+                       task_completed_cmp
+                       );
        }
 
        if (WC->num_cal) for (i=0; i<(WC->num_cal); ++i) {
-
-               bg = 1 - bg;
-               wprintf("<tr bgcolor=\"#%s\"><td>",
-                       (bg ? "DDDDDD" : "FFFFFF")
-               );
-
+               wprintf("<tr><td>");
+               icalproperty_status todoStatus = icalcomponent_get_status(WC->disp_cal[i].cal);
+               wprintf("<input style=\"text-align: center;\" type=\"checkbox\" name=\"completed\" value=\"completed\" ");
+               if (todoStatus == ICAL_STATUS_COMPLETED) {
+                       wprintf("checked=\"checked\" ");
+               }
+               wprintf("disabled=\"disabled\">\n</td><td>");
                p = icalcomponent_get_first_property(WC->disp_cal[i].cal,
-                                                       ICAL_SUMMARY_PROPERTY);
-               wprintf("<a href=\"display_edit_task?msgnum=%ld&taskrm=",
+                       ICAL_SUMMARY_PROPERTY);
+               wprintf("<a href=\"display_edit_task?msgnum=%ld&amp;taskrm=",
                        WC->disp_cal[i].cal_msgnum );
                urlescputs(WC->wc_roomname);
                wprintf("\">");
-               wprintf("<img align=middle "
-                       "src=\"static/taskmanag_16x.gif\" border=0>&nbsp;");
+               /* wprintf("<img align=middle "
+               "src=\"static/taskmanag_16x.gif\" border=0>&nbsp;"); */
                if (p != NULL) {
                        escputs((char *)icalproperty_get_comment(p));
                }
@@ -1487,15 +1508,26 @@ void do_tasks_view(void) {
                wprintf("</td>\n");
 
                due = get_task_due_date(WC->disp_cal[i].cal);
-               webcit_fmt_date(buf, due, 0);
-               wprintf("<td><font");
-               if (due < time(NULL)) {
-                       wprintf(" color=\"#FF0000\"");
+               wprintf("<td><span");
+               if (due > 0) {
+                       webcit_fmt_date(buf, due, 0);
+                       wprintf(">%s",buf);
+               }
+               else {
+                       wprintf(">");
+               }
+               wprintf("</span></td>");
+               wprintf("<td>");
+               p = icalcomponent_get_first_property(WC->disp_cal[i].cal,
+                       ICAL_CATEGORIES_PROPERTY);
+               if (p != NULL) {
+                       escputs((char *)icalproperty_get_categories(p));
                }
-               wprintf(">%s</font></td></tr>\n", buf);
+               wprintf("</td>");
+               wprintf("</tr>");
        }
 
-       wprintf("</table></div>\n");
+       wprintf("</tbody></table></div>\n");
 
        /** Free the list */
        free_calendar_buffer();
index 8b0adab6d752c3bf16b3ab49399dfc1852ac44b3..328d72fe1b7de149e141555cd61991a08e02d31c 100644 (file)
@@ -33,9 +33,8 @@ void stuff_to_cookie(char *cookie, size_t clen, int session,
        int i;
        int len;
 
-       snprintf(buf, SIZ, "%d|%s|%s|%s|", session, user, pass, room);
+       len = snprintf(buf, SIZ, "%d|%s|%s|%s|", session, user, pass, room);
        strcpy(cookie, "");
-       len = strlen(buf);
        for (i=0; i<len; ++i) {
                snprintf(&cookie[i*2], clen - i * 2, "%02X", buf[i]);
        }
index 25c1b9e2e431dd314cedb47f94f40939de99062f..0561d09ba235d71b48fa5dbf88e26b1c6078fedc 100644 (file)
@@ -92,7 +92,7 @@ void do_iconbar(void) {
         if (ib_citadel) if (ib_displayas != IB_TEXTONLY) wprintf(
                 "<div class=\"logo_citadel\"> "
                 "<a href=\"http://www.citadel.org\" "
-                "title=\"%s\" target=\"aboutcit\"> "
+                "title=\"%s\"> "
                 "<img "
                 "src=\"static/citadel-logo.gif\" alt=\"%s\"></a> "
                 "</div>\n",
@@ -112,7 +112,7 @@ void do_iconbar(void) {
                        ">", _("Your summary page")
                );
                if (ib_displayas != IB_TEXTONLY) {
-                       wprintf("<img border=\"0\" width=\"32\" height=\"32\" "
+                       wprintf("<img alt=\"\" "
                                "src=\"static/summscreen_32x.gif\">");
                }
                if (ib_displayas != IB_PICONLY) {
@@ -129,7 +129,7 @@ void do_iconbar(void) {
                        _("Go to your email inbox")
                );
                if (ib_displayas != IB_TEXTONLY) {
-                       wprintf("<img border=\"0\" width=\"32\" height=\"32\" "
+                       wprintf("<img alt=\"\" "
                                "src=\"static/privatemess_32x.gif\">");
                }
                if (ib_displayas != IB_PICONLY) {
@@ -154,7 +154,7 @@ void do_iconbar(void) {
                        _("Go to your personal calendar")
                );
                if (ib_displayas != IB_TEXTONLY) {
-                       wprintf("<img border=\"0\" width=\"32\" height=\"32\" "
+                       wprintf("<img alt=\"\" "
                        "src=\"static/calarea_32x.gif\">");
                }
                if (ib_displayas != IB_PICONLY) {
@@ -171,7 +171,7 @@ void do_iconbar(void) {
                        _("Go to your personal address book")
                );
                if (ib_displayas != IB_TEXTONLY) {
-                       wprintf("<img border=\"0\" width=\"32\" height=\"32\" "
+                       wprintf("<img alt=\"\" "
                        "src=\"static/viewcontacts_32x.gif\">");
                }
                if (ib_displayas != IB_PICONLY) {
@@ -188,7 +188,7 @@ void do_iconbar(void) {
                        _("Go to your personal notes")
                );
                if (ib_displayas != IB_TEXTONLY) {
-                       wprintf("<img border=\"0\" width=\"32\" height=\"32\" "
+                       wprintf("<img alt=\"\" "
                        "src=\"static/storenotes_32x.gif\">");
                }
                if (ib_displayas != IB_PICONLY) {
@@ -205,7 +205,7 @@ void do_iconbar(void) {
                        _("Go to your personal task list")
                );
                if (ib_displayas != IB_TEXTONLY) {
-                       wprintf("<img border=\"0\" width=\"32\" height=\"32\" "
+                       wprintf("<img alt=\"\" "
                        "src=\"static/taskmanag_32x.gif\">");
                }
                if (ib_displayas != IB_PICONLY) {
@@ -220,7 +220,7 @@ void do_iconbar(void) {
                        _("List all of your accessible rooms")
                );
                if (ib_displayas != IB_TEXTONLY) {
-                       wprintf("<img border=\"0\" width=\"32\" height=\"32\" "
+                       wprintf("<img alt=\"\" "
                        "src=\"static/chatrooms_32x.gif\">");
                }
                if (ib_displayas != IB_PICONLY) {
@@ -236,7 +236,7 @@ void do_iconbar(void) {
                        _("See who is online right now")
                );
                if (ib_displayas != IB_TEXTONLY) {
-                       wprintf("<img border=\"0\" width=\"32\" height=\"32\" "
+                       wprintf("<img alt=\"\" "
                        "src=\"static/usermanag_32x.gif\">");
                }
                if (ib_displayas != IB_PICONLY) {
@@ -260,7 +260,7 @@ void do_iconbar(void) {
                        ">"
                );
                if (ib_displayas != IB_TEXTONLY) {
-                       wprintf("<img border=\"0\" width=\"32\" height=\"32\" "
+                       wprintf("<img alt=\"\" "
                        "src=\"static/citadelchat_32x.gif\">");
                }
                if (ib_displayas != IB_PICONLY) {
@@ -277,7 +277,7 @@ void do_iconbar(void) {
                        _("Advanced Options Menu: Advanced Room commands, Account Info, and Chat")
                );
                if (ib_displayas != IB_TEXTONLY) {
-                       wprintf("<img border=\"0\" width=\"32\" height=\"32\" "
+                       wprintf("<img alt=\"\" "
                        "src=\"static/advanpage2_32x.gif\">");
                }
                if (ib_displayas != IB_PICONLY) {
@@ -294,7 +294,7 @@ void do_iconbar(void) {
                        _("Room and system administration functions")
                );
                if (ib_displayas != IB_TEXTONLY) {
-                       wprintf("<img border=\"0\" width=\"32\" height=\"32\" "
+                       wprintf("<img alt=\"\" "
                        "src=\"static/advanpage2_32x.gif\">");
                }
                if (ib_displayas != IB_PICONLY) {
@@ -311,7 +311,7 @@ void do_iconbar(void) {
                
        );
        if (ib_displayas != IB_TEXTONLY) {
-       wprintf("<img border=\"0\" width=\"32\" height=\"32\" "
+       wprintf("<img alt=\"\" "
                "src=\"static/logoff_32x.gif\">");
        }
        if (ib_displayas != IB_PICONLY) {
@@ -390,7 +390,7 @@ void do_iconbar_roomlist(void) {
         if (ib_citadel) if (ib_displayas != IB_TEXTONLY) wprintf(
                 "<div class=\"logo_citadel\"> "
                 "<a href=\"http://www.citadel.org\" "
-                "title=\"%s\" target=\"aboutcit\"> "
+                "title=\"%s\"> "
                 "<img "
                 "src=\"static/citadel-logo.gif\" alt=\"%s\"></a> "
                 "</div>\n",
@@ -412,7 +412,7 @@ void do_iconbar_roomlist(void) {
                
        );
        if (ib_displayas != IB_TEXTONLY) {
-       wprintf("<img border=\"0\" width=\"32\" height=\"32\" "
+       wprintf("<img alt=\"\" "
                "src=\"static/logoff_32x.gif\">");
        }
        if (ib_displayas != IB_PICONLY) {
index 1f6d21abe5e1ebd9ca4e24a3217bf533442a23d0..63d8cef69d27df0206635cb3126101104d73a159 100644 (file)
@@ -489,8 +489,9 @@ void fetchname_parsed_vcard(struct vCard *v, char *storename) {
  * understand in a simple two-column name/value format.
  * \param v the vCard to display
  * \param full display all items of the vcard?
+ * \param msgnum Citadel message pointer
  */
-void display_parsed_vcard(struct vCard *v, int full) {
+void display_parsed_vcard(struct vCard *v, int full, long msgnum) {
        int i, j;
        char buf[SIZ];
        char *name;
@@ -659,6 +660,14 @@ void display_parsed_vcard(struct vCard *v, int full) {
                                        wprintf("</TD></TR>\n");
                                }
                        }
+                       else if (!strcasecmp(firsttoken, "photo") && full && pass == 2) { 
+                               // Only output on second pass
+                               wprintf("<tr><td>");
+                               wprintf(_("Photo:"));
+                               wprintf("</td><td>");
+                               wprintf("<img src=\"/vcardphoto/%d/\" alt=\"Contact photo\"/>",msgnum);
+                               wprintf("</td></tr>\n");
+                       }
                        else if (!strcasecmp(firsttoken, "version")) {
                                /* ignore */
                        }
@@ -733,8 +742,10 @@ void display_parsed_vcard(struct vCard *v, int full) {
  * \param alpha what???
  * \param full should we usse all lines?
  * \param storename where to store???
+ * \param msgnum Citadel message pointer
  */
-void display_vcard(char *vcard_source, char alpha, int full, char *storename) {
+void display_vcard(char *vcard_source, char alpha, int full, char *storename, 
+       long msgnum) {
        struct vCard *v;
        char *name;
        char buf[SIZ];
@@ -757,7 +768,7 @@ void display_vcard(char *vcard_source, char alpha, int full, char *storename) {
                        || ((isalpha(alpha)) && (tolower(alpha) == tolower(this_alpha)) )
                        || ((!isalpha(alpha)) && (!isalpha(this_alpha)))
                ) {
-               display_parsed_vcard(v, full);
+               display_parsed_vcard(v, full,msgnum);
        }
 
        vcard_free(v);
@@ -1336,7 +1347,7 @@ ENDBODY:  /* If there are attached submessages, display them now... */
                        }
 
                        /** In all cases, display the full card */
-                       display_vcard(part_source, 0, 1, NULL);
+                       display_vcard(part_source, 0, 1, NULL,msgnum);
                }
        }
 
@@ -1859,7 +1870,7 @@ void display_addressbook(long msgnum, char alpha) {
                if (vcard_source != NULL) {
 
                        /** Display the summary line */
-                       display_vcard(vcard_source, alpha, 0, NULL);
+                       display_vcard(vcard_source, alpha, 0, NULL,msgnum);
 
                        /** If it's my vCard I can edit it */
                        if (    (!strcasecmp(WC->wc_roomname, USERCONFIGROOM))
@@ -1950,7 +1961,7 @@ void fetch_ab_name(long msgnum, char *namebuf) {
                if (vcard_source != NULL) {
 
                        /* Grab the name off the card */
-                       display_vcard(vcard_source, 0, 0, namebuf);
+                       display_vcard(vcard_source, 0, 0, namebuf,msgnum);
 
                        free(vcard_source);
                }
index d2bc437ea8ef25ae615faeb60915237e3b098a40..73a9b90da2352a24888e81b2c13e421e1525c4db 100644 (file)
@@ -333,37 +333,37 @@ void embed_room_graphic(void) {
                serv_getln(buf, sizeof buf);
        }
        else if (WC->wc_view == VIEW_ADDRESSBOOK) {
-               wprintf("<img height=48 width=48 src=\""
+               wprintf("<img class=\"roompic\" alt=\"\" src=\""
                        "static/viewcontacts_48x.gif"
                        "\">"
                );
        }
        else if ( (WC->wc_view == VIEW_CALENDAR) || (WC->wc_view == VIEW_CALBRIEF) ) {
-               wprintf("<img height=48 width=48 src=\""
+               wprintf("<img class=\"roompic\" alt=\"\" src=\""
                        "static/calarea_48x.gif"
                        "\">"
                );
        }
        else if (WC->wc_view == VIEW_TASKS) {
-               wprintf("<img height=48 width=48 src=\""
+               wprintf("<img class=\"roompic\" alt=\"\" src=\""
                        "static/taskmanag_48x.gif"
                        "\">"
                );
        }
        else if (WC->wc_view == VIEW_NOTES) {
-               wprintf("<img height=48 width=48 src=\""
+               wprintf("<img class=\"roompic\" alt=\"\" src=\""
                        "static/storenotes_48x.gif"
                        "\">"
                );
        }
        else if (WC->wc_view == VIEW_MAILBOX) {
-               wprintf("<img height=48 width=48 src=\""
+               wprintf("<img class=\"roompic\" alt=\"\" src=\""
                        "static/privatemess_48x.gif"
                        "\">"
                );
        }
        else {
-               wprintf("<img height=48 width=48 src=\""
+               wprintf("<img class=\"roompic\" alt=\"\" src=\""
                        "static/chatrooms_48x.gif"
                        "\">"
                );
@@ -380,7 +380,7 @@ void embed_view_o_matic(void) {
        int i;
 
        wprintf("<form name=\"viewomatic\" action=\"changeview\">\n");
-       wprintf("<input type=\"hidden\" name=\"nonce\" value=\"%ld\">\n", WC->nonce);
+       wprintf("\t<div style=\"display: inline;\">\n\t<input type=\"hidden\" name=\"nonce\" value=\"%ld\">\n", WC->nonce);
        wprintf("<label for=\"view_name\">");
        wprintf(_("View as:"));
        wprintf("</label> "
@@ -410,7 +410,7 @@ void embed_view_o_matic(void) {
                        wprintf("</option>\n");
                }
        }
-       wprintf("</select></form>\n");
+       wprintf("</select></div></form>\n");
 }
 
 
@@ -419,15 +419,15 @@ void embed_view_o_matic(void) {
  */
 void embed_search_o_matic(void) {
        wprintf("<form name=\"searchomatic\" action=\"do_search\">\n");
-       wprintf("<input type=\"hidden\" name=\"nonce\" value=\"%ld\">\n", WC->nonce);
+       wprintf("<div style=\"display: inline;\"><input type=\"hidden\" name=\"nonce\" value=\"%ld\">\n", WC->nonce);
        wprintf("<label for=\"search_name\">");
        wprintf(_("Search: "));
-       wprintf("</label> <input ");
+       wprintf("</label><input ");
        wprintf("%s", serv_info.serv_fulltext_enabled ? "" : "disabled ");
        wprintf("type=\"text\" name=\"query\" size=\"15\" maxlength=\"128\" "
                "id=\"search_name\" class=\"inputbox\">\n"
        );
-       wprintf("</form>\n");
+       wprintf("</div></form>\n");
 }
 
 
@@ -512,7 +512,7 @@ void embed_room_banner(char *got, int navbar_style) {
                if (navbar_style == navbar_default) wprintf(
                        "<li class=\"ungoto\">"
                        "<a href=\"ungoto\">"
-                       "<img align=\"middle\" src=\"static/ungoto2_24x.gif\" border=\"0\">"
+                       "<img src=\"static/ungoto2_24x.gif\" alt=\"\">"
                        "<span class=\"navbar_link\">%s</span></A>"
                        "</li>\n", _("Ungoto")
                );
@@ -521,7 +521,7 @@ void embed_room_banner(char *got, int navbar_style) {
                        wprintf(
                                "<li class=\"newmess\">"
                                "<a href=\"readnew\">"
-                               "<img align=\"middle\" src=\"static/newmess2_24x.gif\" border=\"0\">"
+                               "<img src=\"static/newmess2_24x.gif\" alt=\"\">"
                                "<span class=\"navbar_link\">%s</span></A>"
                                "</li>\n", _("Read new messages")
                        );
@@ -533,8 +533,8 @@ void embed_room_banner(char *got, int navbar_style) {
                                        wprintf(
                                                "<li class=\"viewcontacts\">"
                                                "<a href=\"readfwd\">"
-                                               "<img align=\"middle\" src=\"static/viewcontacts_24x.gif\" "
-                                               "border=\"0\">"
+                                               "<img src=\"static/viewcontacts_24x.gif\" "
+                                               "alt=\"\">"
                                                "<span class=\"navbar_link\">"
                                                "%s"
                                                "</span></a></li>\n", _("View contacts")
@@ -544,8 +544,8 @@ void embed_room_banner(char *got, int navbar_style) {
                                        wprintf(
                                                "<li class=\"staskday\">"
                                                "<a href=\"readfwd?calview=day\">"
-                                               "<img align=\"middle\" src=\"static/taskday2_24x.gif\" "
-                                               "border=\"0\">"
+                                               "<img src=\"static/taskday2_24x.gif\" "
+                                               "alt=\"\">"
                                                "<span class=\"navbar_link\">"
                                                "%s"
                                                "</span></a></li>\n", _("Day view")
@@ -553,8 +553,8 @@ void embed_room_banner(char *got, int navbar_style) {
                                        wprintf(
                                                "<li class=\"monthview\">"
                                                "<a href=\"readfwd?calview=month\">"
-                                               "<img align=\"middle\" src=\"static/monthview2_24x.gif\" "
-                                               "border=\"0\">"
+                                               "<img src=\"static/monthview2_24x.gif\" "
+                                               "alt=\"\">"
                                                "<span class=\"navbar_link\">"
                                                "%s"
                                                "</span></a></li>\n", _("Month view")
@@ -564,8 +564,8 @@ void embed_room_banner(char *got, int navbar_style) {
                                        wprintf(
                                                "<li class=\"monthview\">"
                                                "<a href=\"readfwd?calview=month\">"
-                                               "<img align=\"middle\" src=\"static/monthview2_24x.gif\" "
-                                               "border=\"0\">"
+                                               "<img src=\"static/monthview2_24x.gif\" "
+                                               "alt=\"\">"
                                                "<span class=\"navbar_link\">"
                                                "%s"
                                                "</span></a></li>\n", _("Calendar list")
@@ -575,8 +575,8 @@ void embed_room_banner(char *got, int navbar_style) {
                                        wprintf(
                                                "<li class=\"taskmanag\">"
                                                "<a href=\"readfwd\">"
-                                               "<img align=\"middle\" src=\"static/taskmanag_24x.gif\" "
-                                               "border=\"0\">"
+                                               "<img src=\"static/taskmanag_24x.gif\" "
+                                               "alt=\"\">"
                                                "<span class=\"navbar_link\">"
                                                "%s"
                                                "</span></a></li>\n", _("View tasks")
@@ -586,8 +586,8 @@ void embed_room_banner(char *got, int navbar_style) {
                                        wprintf(
                                                "<li class=\"viewnotes\">"
                                                "<a href=\"readfwd\">"
-                                               "<img align=\"middle\" src=\"static/viewnotes_24x.gif\" "
-                                               "border=\"0\">"
+                                               "<img src=\"static/viewnotes_24x.gif\" "
+                                               "alt=\"\">"
                                                "<span class=\"navbar_link\">"
                                                "%s"
                                                "</span></a></li>\n", _("View notes")
@@ -597,8 +597,8 @@ void embed_room_banner(char *got, int navbar_style) {
                                        wprintf(
                                                "<li class=\"readallmess\">"
                                                "<a href=\"readfwd\">"
-                                               "<img align=\"middle\" src=\"static/readallmess3_24x.gif\" "
-                                               "border=\"0\">"
+                                               "<img src=\"static/readallmess3_24x.gif\" "
+                                               "alt=\"\">"
                                                "<span class=\"navbar_link\">"
                                                "%s"
                                                "</span></a></li>\n", _("View message list")
@@ -608,8 +608,8 @@ void embed_room_banner(char *got, int navbar_style) {
                                        wprintf(
                                                "<li class=\"readallmess\">"
                                                "<a href=\"readfwd\">"
-                                               "<img align=\"middle\" src=\"static/readallmess3_24x.gif\" "
-                                               "border=\"0\">"
+                                               "<img src=\"static/readallmess3_24x.gif\" "
+                                               "alt=\"\">"
                                                "<span class=\"navbar_link\">"
                                                "%s"
                                                "</span></a></li>\n", _("Wiki home")
@@ -619,8 +619,8 @@ void embed_room_banner(char *got, int navbar_style) {
                                        wprintf(
                                                "<li class=\"readallmess\">"
                                                "<a href=\"readfwd\">"
-                                               "<img align=\"middle\" src=\"static/readallmess3_24x.gif\" "
-                                               "border=\"0\">"
+                                               "<img src=\"static/readallmess3_24x.gif\" "
+                                               "alt=\"\">"
                                                "<span class=\"navbar_link\">"
                                                "%s"
                                                "</span></a></li>\n", _("Read all messages")
@@ -635,8 +635,8 @@ void embed_room_banner(char *got, int navbar_style) {
                                        wprintf(
                                                "<li class=\"addnewcontact\">"
                                                "<a href=\"display_enter\">"
-                                               "<img align=\"middle\" src=\"static/addnewcontact_24x.gif\" "
-                                               "border=\"0\"><span class=\"navbar_link\">"
+                                               "<img src=\"static/addnewcontact_24x.gif\" "
+                                               "alt=\"\"><span class=\"navbar_link\">"
                                                "%s"
                                                "</span></a></li>\n", _("Add new contact")
                                        );
@@ -648,8 +648,8 @@ void embed_room_banner(char *got, int navbar_style) {
                                        if (havebstr("month")) wprintf("?month=%s", bstr("month"));
                                        if (havebstr("day"  )) wprintf("?day=%s", bstr("day"));
                                        wprintf("\">"
-                                               "<img align=\"middle\" src=\"static/addevent_24x.gif\" "
-                                               "border=\"0\"><span class=\"navbar_link\">"
+                                               "<img  src=\"static/addevent_24x.gif\" "
+                                               "alt=\"\"><span class=\"navbar_link\">"
                                                "%s"
                                                "</span></a></li>\n", _("Add new event")
                                        );
@@ -658,8 +658,8 @@ void embed_room_banner(char *got, int navbar_style) {
                                        wprintf(
                                                "<li class=\"newmess\">"
                                                "<a href=\"display_enter\">"
-                                               "<img align=\"middle\" src=\"static/newmess3_24x.gif\" "
-                                               "border=\"0\"><span class=\"navbar_link\">"
+                                               "<img  src=\"static/newmess3_24x.gif\" "
+                                               "alt=\"\"><span class=\"navbar_link\">"
                                                "%s"
                                                "</span></a></li>\n", _("Add new task")
                                        );
@@ -668,8 +668,8 @@ void embed_room_banner(char *got, int navbar_style) {
                                        wprintf(
                                                "<li class=\"enternewnote\">"
                                                "<a href=\"javascript:add_new_note();\">"
-                                               "<img align=\"middle\" src=\"static/enternewnote_24x.gif\" "
-                                               "border=\"0\"><span class=\"navbar_link\">"
+                                               "<img  src=\"static/enternewnote_24x.gif\" "
+                                               "alt=\"\"><span class=\"navbar_link\">"
                                                "%s"
                                                "</span></a></li>\n", _("Add new note")
                                        );
@@ -680,8 +680,8 @@ void embed_room_banner(char *got, int navbar_style) {
                                        wprintf(
                                                "<li class=\"newmess\">"
                                                "<a href=\"display_enter?wikipage=%s\">"
-                                               "<img align=\"middle\" src=\"static/newmess3_24x.gif\" "
-                                               "border=\"0\"><span class=\"navbar_link\">"
+                                               "<img  src=\"static/newmess3_24x.gif\" "
+                                               "alt=\"\"><span class=\"navbar_link\">"
                                                "%s"
                                                "</span></a></li>\n", buf, _("Edit this page")
                                        );
@@ -690,8 +690,8 @@ void embed_room_banner(char *got, int navbar_style) {
                                        wprintf(
                                                "<li class=\"newmess\">"
                                                "<a href=\"display_enter\">"
-                                               "<img align=\"middle\" src=\"static/newmess3_24x.gif\" "
-                                               "border=\"0\"><span class=\"navbar_link\">"
+                                               "<img  src=\"static/newmess3_24x.gif\" "
+                                               "alt=\"\"><span class=\"navbar_link\">"
                                                "%s"
                                                "</span></a></li>\n", _("Write mail")
                                        );
@@ -700,8 +700,8 @@ void embed_room_banner(char *got, int navbar_style) {
                                        wprintf(
                                                "<li class=\"newmess\">"
                                                "<a href=\"display_enter\">"
-                                               "<img align=\"middle\" src=\"static/newmess3_24x.gif\" "
-                                               "border=\"0\"><span class=\"navbar_link\">"
+                                               "<img  src=\"static/newmess3_24x.gif\" "
+                                               "alt=\"\"><span class=\"navbar_link\">"
                                                "%s"
                                                "</span></a></li>\n", _("Enter a message")
                                        );
@@ -713,7 +713,7 @@ void embed_room_banner(char *got, int navbar_style) {
                        "<li class=\"skipthisroom\">"
                        "<a href=\"skip\" "
                        "title=\"%s\">"
-                       "<img align=\"middle\" src=\"static/skipthisroom_24x.gif\" border=\"0\">"
+                       "<img  src=\"static/skipthisroom_24x.gif\" alt=\"\">"
                        "<span class=\"navbar_link\">%s</span></a>"
                        "</li>\n",
                        _("Leave all messages marked as unread, go to next room with unread messages"),
@@ -724,7 +724,7 @@ void embed_room_banner(char *got, int navbar_style) {
                        "<li class=\"markngo\">"
                        "<a href=\"gotonext\" "
                        "title=\"%s\">"
-                       "<img align=\"middle\" src=\"static/markngo_24x.gif\" border=\"0\">"
+                       "<img  src=\"static/markngo_24x.gif\" alt=\"\">"
                        "<span class=\"navbar_link\">%s</span></a>"
                        "</li>\n",
                        _("Mark all messages as read, go to next room with unread messages"),
@@ -3260,7 +3260,7 @@ void do_iconbar_view(struct folder *fold, int max_folders, int num_floors) {
                                wprintf("<a href=\"dotgoto?room=");
                                urlescputs(fold[i].room);
                                wprintf("\">");
-                               wprintf("<img align=\"middle\" border=0 src=\"static/%s\" alt=\"\"> ", icon);
+                               wprintf("<img  border=0 src=\"static/%s\" alt=\"\"> ", icon);
                        }
                        else {
                                wprintf("<i>");
diff --git a/webcit/static/builder.js b/webcit/static/builder.js
new file mode 100644 (file)
index 0000000..8301999
--- /dev/null
@@ -0,0 +1,136 @@
+// script.aculo.us builder.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
+
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+var Builder = {
+  NODEMAP: {
+    AREA: 'map',
+    CAPTION: 'table',
+    COL: 'table',
+    COLGROUP: 'table',
+    LEGEND: 'fieldset',
+    OPTGROUP: 'select',
+    OPTION: 'select',
+    PARAM: 'object',
+    TBODY: 'table',
+    TD: 'table',
+    TFOOT: 'table',
+    TH: 'table',
+    THEAD: 'table',
+    TR: 'table'
+  },
+  // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
+  //       due to a Firefox bug
+  node: function(elementName) {
+    elementName = elementName.toUpperCase();
+    
+    // try innerHTML approach
+    var parentTag = this.NODEMAP[elementName] || 'div';
+    var parentElement = document.createElement(parentTag);
+    try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
+      parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
+    } catch(e) {}
+    var element = parentElement.firstChild || null;
+      
+    // see if browser added wrapping tags
+    if(element && (element.tagName.toUpperCase() != elementName))
+      element = element.getElementsByTagName(elementName)[0];
+    
+    // fallback to createElement approach
+    if(!element) element = document.createElement(elementName);
+    
+    // abort if nothing could be created
+    if(!element) return;
+
+    // attributes (or text)
+    if(arguments[1])
+      if(this._isStringOrNumber(arguments[1]) ||
+        (arguments[1] instanceof Array) ||
+        arguments[1].tagName) {
+          this._children(element, arguments[1]);
+        } else {
+          var attrs = this._attributes(arguments[1]);
+          if(attrs.length) {
+            try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
+              parentElement.innerHTML = "<" +elementName + " " +
+                attrs + "></" + elementName + ">";
+            } catch(e) {}
+            element = parentElement.firstChild || null;
+            // workaround firefox 1.0.X bug
+            if(!element) {
+              element = document.createElement(elementName);
+              for(attr in arguments[1]) 
+                element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
+            }
+            if(element.tagName.toUpperCase() != elementName)
+              element = parentElement.getElementsByTagName(elementName)[0];
+          }
+        } 
+
+    // text, or array of children
+    if(arguments[2])
+      this._children(element, arguments[2]);
+
+     return element;
+  },
+  _text: function(text) {
+     return document.createTextNode(text);
+  },
+
+  ATTR_MAP: {
+    'className': 'class',
+    'htmlFor': 'for'
+  },
+
+  _attributes: function(attributes) {
+    var attrs = [];
+    for(attribute in attributes)
+      attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
+          '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'&quot;') + '"');
+    return attrs.join(" ");
+  },
+  _children: function(element, children) {
+    if(children.tagName) {
+      element.appendChild(children);
+      return;
+    }
+    if(typeof children=='object') { // array can hold nodes and text
+      children.flatten().each( function(e) {
+        if(typeof e=='object')
+          element.appendChild(e)
+        else
+          if(Builder._isStringOrNumber(e))
+            element.appendChild(Builder._text(e));
+      });
+    } else
+      if(Builder._isStringOrNumber(children))
+        element.appendChild(Builder._text(children));
+  },
+  _isStringOrNumber: function(param) {
+    return(typeof param=='string' || typeof param=='number');
+  },
+  build: function(html) {
+    var element = this.node('div');
+    $(element).update(html.strip());
+    return element.down();
+  },
+  dump: function(scope) { 
+    if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope 
+  
+    var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " +
+      "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " +
+      "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+
+      "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+
+      "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+
+      "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);
+  
+    tags.each( function(tag){ 
+      scope[tag] = function() { 
+        return Builder.node.apply(Builder, [tag].concat($A(arguments)));  
+      } 
+    });
+  }
+}
index 43cdb3eaaacb435c9dc85d3b45c5a06f1a0179ac..5012cb812abb97a874b6b2d79d437e3a9c807634 100644 (file)
@@ -1,28 +1,15 @@
-// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
-//           (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
-//           (c) 2005 Jon Tirsen (http://www.tirsen.com)
+// script.aculo.us controls.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
+
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//           (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
+//           (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
 // Contributors:
 //  Richard Livsey
 //  Rahul Bhargava
+//  Rob Wills
 // 
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-// 
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-// 
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
 
 // Autocompleter.Base handles all the autocompletion functionality 
 // that's independent of the data source for autocompletion. This
 // useful when one of the tokens is \n (a newline), as it 
 // allows smart autocompletion after linebreaks.
 
-var Autocompleter = {}
-Autocompleter.Base = function() {};
-Autocompleter.Base.prototype = {
+if(typeof Effect == 'undefined')
+  throw("controls.js requires including script.aculo.us' effects.js library");
+
+var Autocompleter = { }
+Autocompleter.Base = Class.create({
   baseInitialize: function(element, update, options) {
-    this.element     = $(element); 
+    element          = $(element)
+    this.element     = element; 
     this.update      = $(update);  
     this.hasFocus    = false; 
     this.changed     = false; 
     this.active      = false; 
     this.index       = 0;     
     this.entryCount  = 0;
+    this.oldElementValue = this.element.value;
 
-    if (this.setOptions)
+    if(this.setOptions)
       this.setOptions(options);
     else
-      this.options = options || {};
+      this.options = options || { };
 
     this.options.paramName    = this.options.paramName || this.element.name;
     this.options.tokens       = this.options.tokens || [];
     this.options.frequency    = this.options.frequency || 0.4;
     this.options.minChars     = this.options.minChars || 1;
     this.options.onShow       = this.options.onShow || 
-    function(element, update){ 
-      if(!update.style.position || update.style.position=='absolute') {
-        update.style.position = 'absolute';
-        Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight});
-      }
-      new Effect.Appear(update,{duration:0.15});
-    };
+      function(element, update){ 
+        if(!update.style.position || update.style.position=='absolute') {
+          update.style.position = 'absolute';
+          Position.clone(element, update, {
+            setHeight: false, 
+            offsetTop: element.offsetHeight
+          });
+        }
+        Effect.Appear(update,{duration:0.15});
+      };
     this.options.onHide = this.options.onHide || 
-    function(element, update){ new Effect.Fade(update,{duration:0.15}) };
+      function(element, update){ new Effect.Fade(update,{duration:0.15}) };
 
-    if (typeof(this.options.tokens) == 'string') 
+    if(typeof(this.options.tokens) == 'string') 
       this.options.tokens = new Array(this.options.tokens);
+    // Force carriage returns as token delimiters anyway
+    if (!this.options.tokens.include('\n'))
+      this.options.tokens.push('\n');
 
     this.observer = null;
     
@@ -90,28 +87,33 @@ Autocompleter.Base.prototype = {
 
     Element.hide(this.update);
 
-    Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
-    Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
+    Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
+    Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
   },
 
   show: function() {
     if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
-    if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && (Element.getStyle(this.update, 'position')=='absolute')) {
+    if(!this.iefix && 
+      (Prototype.Browser.IE) &&
+      (Element.getStyle(this.update, 'position')=='absolute')) {
       new Insertion.After(this.update, 
        '<iframe id="' + this.update.id + '_iefix" '+
        'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
        'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
       this.iefix = $(this.update.id+'_iefix');
     }
-    if(this.iefix) {
-      Position.clone(this.update, this.iefix);
-      this.iefix.style.zIndex = 1;
-      this.update.style.zIndex = 2;
-      Element.show(this.iefix);
-    }
+    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
+  },
+  
+  fixIEOverlapping: function() {
+    Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
+    this.iefix.style.zIndex = 1;
+    this.update.style.zIndex = 2;
+    Element.show(this.iefix);
   },
 
   hide: function() {
+    this.stopIndicator();
     if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
     if(this.iefix) Element.hide(this.iefix);
   },
@@ -142,17 +144,17 @@ Autocompleter.Base.prototype = {
        case Event.KEY_UP:
          this.markPrevious();
          this.render();
-         if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
+         Event.stop(event);
          return;
        case Event.KEY_DOWN:
          this.markNext();
          this.render();
-         if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
+         Event.stop(event);
          return;
       }
      else 
-      if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN) 
-        return;
+       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || 
+         (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
 
     this.changed = true;
     this.hasFocus = true;
@@ -162,6 +164,12 @@ Autocompleter.Base.prototype = {
         setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
   },
 
+  activate: function() {
+    this.changed = false;
+    this.hasFocus = true;
+    this.getUpdatedChoices();
+  },
+
   onHover: function(event) {
     var element = Event.findElement(event, 'LI');
     if(this.index != element.autocompleteIndex) 
@@ -192,22 +200,26 @@ Autocompleter.Base.prototype = {
         this.index==i ? 
           Element.addClassName(this.getEntry(i),"selected") : 
           Element.removeClassName(this.getEntry(i),"selected");
-        
       if(this.hasFocus) { 
         this.show();
         this.active = true;
       }
-    } else this.hide();
+    } else {
+      this.active = false;
+      this.hide();
+    }
   },
   
   markPrevious: function() {
     if(this.index > 0) this.index--
-      else this.index = this.entryCcount-1;
+      else this.index = this.entryCount-1;
+    this.getEntry(this.index).scrollIntoView(true);
   },
   
   markNext: function() {
     if(this.index < this.entryCount-1) this.index++
       else this.index = 0;
+    this.getEntry(this.index).scrollIntoView(false);
   },
   
   getEntry: function(index) {
@@ -228,30 +240,39 @@ Autocompleter.Base.prototype = {
       this.options.updateElement(selectedElement);
       return;
     }
-
-    var value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
-    var lastTokenPos = this.findLastToken();
-    if (lastTokenPos != -1) {
-      var newValue = this.element.value.substr(0, lastTokenPos + 1);
-      var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
+    var value = '';
+    if (this.options.select) {
+      var nodes = $(selectedElement).select('.' + this.options.select) || [];
+      if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
+    } else
+      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
+    
+    var bounds = this.getTokenBounds();
+    if (bounds[0] != -1) {
+      var newValue = this.element.value.substr(0, bounds[0]);
+      var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
       if (whitespace)
         newValue += whitespace[0];
-      this.element.value = newValue + value;
+      this.element.value = newValue + value + this.element.value.substr(bounds[1]);
     } else {
       this.element.value = value;
     }
-    this.element.focus(); 
+    this.oldElementValue = this.element.value;
+    this.element.focus();
+    
+    if (this.options.afterUpdateElement)
+      this.options.afterUpdateElement(this.element, selectedElement);
   },
 
   updateChoices: function(choices) {
     if(!this.changed && this.hasFocus) {
       this.update.innerHTML = choices;
       Element.cleanWhitespace(this.update);
-      Element.cleanWhitespace(this.update.firstChild);
+      Element.cleanWhitespace(this.update.down());
 
-      if(this.update.firstChild && this.update.firstChild.childNodes) {
+      if(this.update.firstChild && this.update.down().childNodes) {
         this.entryCount = 
-          this.update.firstChild.childNodes.length;
+          this.update.down().childNodes.length;
         for (var i = 0; i < this.entryCount; i++) {
           var entry = this.getEntry(i);
           entry.autocompleteIndex = i;
@@ -262,9 +283,14 @@ Autocompleter.Base.prototype = {
       }
 
       this.stopIndicator();
-
       this.index = 0;
-      this.render();
+      
+      if(this.entryCount==1 && this.options.autoSelect) {
+        this.selectEntry();
+        this.hide();
+      } else {
+        this.render();
+      }
     }
   },
 
@@ -275,41 +301,50 @@ Autocompleter.Base.prototype = {
 
   onObserverEvent: function() {
     this.changed = false;   
+    this.tokenBounds = null;
     if(this.getToken().length>=this.options.minChars) {
-      this.startIndicator();
       this.getUpdatedChoices();
     } else {
       this.active = false;
       this.hide();
     }
+    this.oldElementValue = this.element.value;
   },
 
   getToken: function() {
-    var tokenPos = this.findLastToken();
-    if (tokenPos != -1)
-      var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
-    else
-      var ret = this.element.value;
-
-    return /\n/.test(ret) ? '' : ret;
-  },
-
-  findLastToken: function() {
-    var lastTokenPos = -1;
-
-    for (var i=0; i<this.options.tokens.length; i++) {
-      var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
-      if (thisTokenPos > lastTokenPos)
-        lastTokenPos = thisTokenPos;
+    var bounds = this.getTokenBounds();
+    return this.element.value.substring(bounds[0], bounds[1]).strip();
+  },
+
+  getTokenBounds: function() {
+    if (null != this.tokenBounds) return this.tokenBounds;
+    var value = this.element.value;
+    if (value.strip().empty()) return [-1, 0];
+    var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
+    var offset = (diff == this.oldElementValue.length ? 1 : 0);
+    var prevTokenPos = -1, nextTokenPos = value.length;
+    var tp;
+    for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
+      tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
+      if (tp > prevTokenPos) prevTokenPos = tp;
+      tp = value.indexOf(this.options.tokens[index], diff + offset);
+      if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
     }
-    return lastTokenPos;
+    return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
   }
-}
+});
+
+Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
+  var boundary = Math.min(newS.length, oldS.length);
+  for (var index = 0; index < boundary; ++index)
+    if (newS[index] != oldS[index])
+      return index;
+  return boundary;
+};
 
-Ajax.Autocompleter = Class.create();
-Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
+Ajax.Autocompleter = Class.create(Autocompleter.Base, {
   initialize: function(element, update, url, options) {
-         this.baseInitialize(element, update, options);
+    this.baseInitialize(element, update, options);
     this.options.asynchronous  = true;
     this.options.onComplete    = this.onComplete.bind(this);
     this.options.defaultParams = this.options.parameters || null;
@@ -317,7 +352,9 @@ Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.pro
   },
 
   getUpdatedChoices: function() {
-    entry = encodeURIComponent(this.options.paramName) + '=' + 
+    this.startIndicator();
+    
+    var entry = encodeURIComponent(this.options.paramName) + '=' + 
       encodeURIComponent(this.getToken());
 
     this.options.parameters = this.options.callback ?
@@ -325,14 +362,13 @@ Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.pro
 
     if(this.options.defaultParams) 
       this.options.parameters += '&' + this.options.defaultParams;
-
+    
     new Ajax.Request(this.url, this.options);
   },
 
   onComplete: function(request) {
     this.updateChoices(request.responseText);
   }
-
 });
 
 // The local array autocompleter. Used when you'd prefer to
@@ -370,8 +406,7 @@ Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.pro
 // In that case, the other options above will not apply unless
 // you support them.
 
-Autocompleter.Local = Class.create();
-Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
+Autocompleter.Local = Class.create(Autocompleter.Base, {
   initialize: function(element, update, array, options) {
     this.baseInitialize(element, update, options);
     this.options.array = array;
@@ -427,273 +462,504 @@ Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
           ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
         return "<ul>" + ret.join('') + "</ul>";
       }
-    }, options || {});
+    }, options || { });
   }
 });
 
-// AJAX in-place editor
-//
-// The constructor takes three parameters. The first is the element
-// that should support in-place editing. The second is the url to submit
-// the changed value to. The server should respond with the updated
-// value (the server might have post-processed it or validation might
-// have prevented it from changing). The third is a hash of options.
-//
-// Supported options are (all are optional and have sensible defaults):
-// - okText - The text of the submit button that submits the changed value
-//            to the server (default: "ok")
-// - cancelText - The text of the link that cancels editing (default: "cancel")
-// - savingText - The text being displayed as the AJAX engine communicates
-//                with the server (default: "Saving...")
-// - formId - The id given to the <form> element
-//            (default: the id of the element to edit plus '-inplaceeditor')
-
-Ajax.InPlaceEditor = Class.create();
-Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
-Ajax.InPlaceEditor.prototype = {
+// AJAX in-place editor and collection editor
+// Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).
+
+// Use this if you notice weird scrolling problems on some browsers,
+// the DOM might be a bit confused when this gets called so do this
+// waits 1 ms (with setTimeout) until it does the activation
+Field.scrollFreeActivate = function(field) {
+  setTimeout(function() {
+    Field.activate(field);
+  }, 1);
+}
+
+Ajax.InPlaceEditor = Class.create({
   initialize: function(element, url, options) {
     this.url = url;
-    this.element = $(element);
-
-    this.options = Object.extend({
-      okText: "ok",
-      cancelText: "cancel",
-      savingText: "Saving...",
-      clickToEditText: "Click to edit",
-      okText: "ok",
-      rows: 1,
-      onComplete: function(transport, element) {
-        new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
-      },
-      onFailure: function(transport) {
-        alert("Error communicating with the server: " + transport.responseText.stripTags());
-      },
-      callback: function(form) {
-        return Form.serialize(form);
-      },
-      loadingText: 'Loading...',
-      savingClassName: 'inplaceeditor-saving',
-      formClassName: 'inplaceeditor-form',
-      highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
-      highlightendcolor: "#FFFFFF",
-      externalControl: null,
-      ajaxOptions: {}
-    }, options || {});
-
-    if(!this.options.formId && this.element.id) {
-      this.options.formId = this.element.id + "-inplaceeditor";
-      if ($(this.options.formId)) {
-        // there's already a form with that name, don't specify an id
-        this.options.formId = null;
-      }
+    this.element = element = $(element);
+    this.prepareOptions();
+    this._controls = { };
+    arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
+    Object.extend(this.options, options || { });
+    if (!this.options.formId && this.element.id) {
+      this.options.formId = this.element.id + '-inplaceeditor';
+      if ($(this.options.formId))
+        this.options.formId = '';
     }
-    
-    if (this.options.externalControl) {
+    if (this.options.externalControl)
       this.options.externalControl = $(this.options.externalControl);
-    }
-    
-    this.originalBackground = Element.getStyle(this.element, 'background-color');
-    if (!this.originalBackground) {
-      this.originalBackground = "transparent";
-    }
-    
+    if (!this.options.externalControl)
+      this.options.externalControlOnly = false;
+    this._originalBackground = this.element.getStyle('background-color') || 'transparent';
     this.element.title = this.options.clickToEditText;
-    
-    this.onclickListener = this.enterEditMode.bindAsEventListener(this);
-    this.mouseoverListener = this.enterHover.bindAsEventListener(this);
-    this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
-    Event.observe(this.element, 'click', this.onclickListener);
-    Event.observe(this.element, 'mouseover', this.mouseoverListener);
-    Event.observe(this.element, 'mouseout', this.mouseoutListener);
-    if (this.options.externalControl) {
-      Event.observe(this.options.externalControl, 'click', this.onclickListener);
-      Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
-      Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
-    }
-  },
-  enterEditMode: function() {
-    if (this.saving) return;
-    if (this.editing) return;
-    this.editing = true;
-    this.onEnterEditMode();
-    if (this.options.externalControl) {
-      Element.hide(this.options.externalControl);
+    this._boundCancelHandler = this.handleFormCancellation.bind(this);
+    this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
+    this._boundFailureHandler = this.handleAJAXFailure.bind(this);
+    this._boundSubmitHandler = this.handleFormSubmission.bind(this);
+    this._boundWrapperHandler = this.wrapUp.bind(this);
+    this.registerListeners();
+  },
+  checkForEscapeOrReturn: function(e) {
+    if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
+    if (Event.KEY_ESC == e.keyCode)
+      this.handleFormCancellation(e);
+    else if (Event.KEY_RETURN == e.keyCode)
+      this.handleFormSubmission(e);
+  },
+  createControl: function(mode, handler, extraClasses) {
+    var control = this.options[mode + 'Control'];
+    var text = this.options[mode + 'Text'];
+    if ('button' == control) {
+      var btn = document.createElement('input');
+      btn.type = 'submit';
+      btn.value = text;
+      btn.className = 'editor_' + mode + '_button';
+      if ('cancel' == mode)
+        btn.onclick = this._boundCancelHandler;
+      this._form.appendChild(btn);
+      this._controls[mode] = btn;
+    } else if ('link' == control) {
+      var link = document.createElement('a');
+      link.href = '#';
+      link.appendChild(document.createTextNode(text));
+      link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
+      link.className = 'editor_' + mode + '_link';
+      if (extraClasses)
+        link.className += ' ' + extraClasses;
+      this._form.appendChild(link);
+      this._controls[mode] = link;
     }
-    Element.hide(this.element);
-    this.form = this.getForm();
-    this.element.parentNode.insertBefore(this.form, this.element);
   },
-  getForm: function() {
-    form = document.createElement("form");
-    form.id = this.options.formId;
-    Element.addClassName(form, this.options.formClassName)
-    form.onsubmit = this.onSubmit.bind(this);
-
-    this.createEditField(form);
-
-    if (this.options.textarea) {
-      var br = document.createElement("br");
-      form.appendChild(br);
-    }
-
-    okButton = document.createElement("input");
-    okButton.type = "submit";
-    okButton.value = this.options.okText;
-    form.appendChild(okButton);
-
-    cancelLink = document.createElement("a");
-    cancelLink.href = "#";
-    cancelLink.appendChild(document.createTextNode(this.options.cancelText));
-    cancelLink.onclick = this.onclickCancel.bind(this);
-    form.appendChild(cancelLink);
-    return form;
-  },
-  createEditField: function(form) {
-    if (this.options.rows == 1) {
-      this.options.textarea = false;
-      var textField = document.createElement("input");
-      textField.type = "text";
-      textField.name = "value";
-      textField.value = this.getText();
-      textField.style.backgroundColor = this.options.highlightcolor;
+  createEditField: function() {
+    var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
+    var fld;
+    if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
+      fld = document.createElement('input');
+      fld.type = 'text';
       var size = this.options.size || this.options.cols || 0;
-      if (size != 0)
-        textField.size = size;
-      form.appendChild(textField);
-      this.editField = textField;
+      if (0 < size) fld.size = size;
     } else {
-      this.options.textarea = true;
-      var textArea = document.createElement("textarea");
-      textArea.name = "value";
-      textArea.value = this.getText();
-      textArea.rows = this.options.rows;
-      textArea.cols = this.options.cols || 40;
-      form.appendChild(textArea);
-      this.editField = textArea;
+      fld = document.createElement('textarea');
+      fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
+      fld.cols = this.options.cols || 40;
     }
+    fld.name = this.options.paramName;
+    fld.value = text; // No HTML breaks conversion anymore
+    fld.className = 'editor_field';
+    if (this.options.submitOnBlur)
+      fld.onblur = this._boundSubmitHandler;
+    this._controls.editor = fld;
+    if (this.options.loadTextURL)
+      this.loadExternalText();
+    this._form.appendChild(this._controls.editor);
+  },
+  createForm: function() {
+    var ipe = this;
+    function addText(mode, condition) {
+      var text = ipe.options['text' + mode + 'Controls'];
+      if (!text || condition === false) return;
+      ipe._form.appendChild(document.createTextNode(text));
+    };
+    this._form = $(document.createElement('form'));
+    this._form.id = this.options.formId;
+    this._form.addClassName(this.options.formClassName);
+    this._form.onsubmit = this._boundSubmitHandler;
+    this.createEditField();
+    if ('textarea' == this._controls.editor.tagName.toLowerCase())
+      this._form.appendChild(document.createElement('br'));
+    if (this.options.onFormCustomization)
+      this.options.onFormCustomization(this, this._form);
+    addText('Before', this.options.okControl || this.options.cancelControl);
+    this.createControl('ok', this._boundSubmitHandler);
+    addText('Between', this.options.okControl && this.options.cancelControl);
+    this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
+    addText('After', this.options.okControl || this.options.cancelControl);
+  },
+  destroy: function() {
+    if (this._oldInnerHTML)
+      this.element.innerHTML = this._oldInnerHTML;
+    this.leaveEditMode();
+    this.unregisterListeners();
+  },
+  enterEditMode: function(e) {
+    if (this._saving || this._editing) return;
+    this._editing = true;
+    this.triggerCallback('onEnterEditMode');
+    if (this.options.externalControl)
+      this.options.externalControl.hide();
+    this.element.hide();
+    this.createForm();
+    this.element.parentNode.insertBefore(this._form, this.element);
+    if (!this.options.loadTextURL)
+      this.postProcessEditField();
+    if (e) Event.stop(e);
+  },
+  enterHover: function(e) {
+    if (this.options.hoverClassName)
+      this.element.addClassName(this.options.hoverClassName);
+    if (this._saving) return;
+    this.triggerCallback('onEnterHover');
   },
   getText: function() {
-    if (this.options.loadTextURL) {
-      this.loadExternalText();
-      return this.options.loadingText;
-    } else {
-      return this.element.innerHTML;
-    }
+    return this.element.innerHTML;
   },
-  loadExternalText: function() {
-    new Ajax.Request(
-      this.options.loadTextURL,
-      {
-        asynchronous: true,
-        onComplete: this.onLoadedExternalText.bind(this)
-      }
-    );
+  handleAJAXFailure: function(transport) {
+    this.triggerCallback('onFailure', transport);
+    if (this._oldInnerHTML) {
+      this.element.innerHTML = this._oldInnerHTML;
+      this._oldInnerHTML = null;
+    }
   },
-  onLoadedExternalText: function(transport) {
-    this.form.value.value = transport.responseText.stripTags();
+  handleFormCancellation: function(e) {
+    this.wrapUp();
+    if (e) Event.stop(e);
+  },
+  handleFormSubmission: function(e) {
+    var form = this._form;
+    var value = $F(this._controls.editor);
+    this.prepareSubmission();
+    var params = this.options.callback(form, value) || '';
+    if (Object.isString(params))
+      params = params.toQueryParams();
+    params.editorId = this.element.id;
+    if (this.options.htmlResponse) {
+      var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
+      Object.extend(options, {
+        parameters: params,
+        onComplete: this._boundWrapperHandler,
+        onFailure: this._boundFailureHandler
+      });
+      new Ajax.Updater({ success: this.element }, this.url, options);
+    } else {
+      var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+      Object.extend(options, {
+        parameters: params,
+        onComplete: this._boundWrapperHandler,
+        onFailure: this._boundFailureHandler
+      });
+      new Ajax.Request(this.url, options);
+    }
+    if (e) Event.stop(e);
   },
-  onclickCancel: function() {
-    this.onComplete();
-    this.leaveEditMode();
-    return false;
+  leaveEditMode: function() {
+    this.element.removeClassName(this.options.savingClassName);
+    this.removeForm();
+    this.leaveHover();
+    this.element.style.backgroundColor = this._originalBackground;
+    this.element.show();
+    if (this.options.externalControl)
+      this.options.externalControl.show();
+    this._saving = false;
+    this._editing = false;
+    this._oldInnerHTML = null;
+    this.triggerCallback('onLeaveEditMode');
+  },
+  leaveHover: function(e) {
+    if (this.options.hoverClassName)
+      this.element.removeClassName(this.options.hoverClassName);
+    if (this._saving) return;
+    this.triggerCallback('onLeaveHover');
   },
-  onFailure: function(transport) {
-    this.options.onFailure(transport);
-    if (this.oldInnerHTML) {
-      this.element.innerHTML = this.oldInnerHTML;
-      this.oldInnerHTML = null;
-    }
-    return false;
-  },
-  onSubmit: function() {
-    this.saving = true;
-    new Ajax.Updater(
-      { 
-        success: this.element,
-         // don't update on failure (this could be an option)
-        failure: null
-      },
-      this.url,
-      Object.extend({
-        parameters: this.options.callback(this.form, this.editField.value),
-        onComplete: this.onComplete.bind(this),
-        onFailure: this.onFailure.bind(this)
-      }, this.options.ajaxOptions)
-    );
-    this.onLoading();
-    return false;
-  },
-  onLoading: function() {
-    this.saving = true;
+  loadExternalText: function() {
+    this._form.addClassName(this.options.loadingClassName);
+    this._controls.editor.disabled = true;
+    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+    Object.extend(options, {
+      parameters: 'editorId=' + encodeURIComponent(this.element.id),
+      onComplete: Prototype.emptyFunction,
+      onSuccess: function(transport) {
+        this._form.removeClassName(this.options.loadingClassName);
+        var text = transport.responseText;
+        if (this.options.stripLoadedTextTags)
+          text = text.stripTags();
+        this._controls.editor.value = text;
+        this._controls.editor.disabled = false;
+        this.postProcessEditField();
+      }.bind(this),
+      onFailure: this._boundFailureHandler
+    });
+    new Ajax.Request(this.options.loadTextURL, options);
+  },
+  postProcessEditField: function() {
+    var fpc = this.options.fieldPostCreation;
+    if (fpc)
+      $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
+  },
+  prepareOptions: function() {
+    this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
+    Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
+    [this._extraDefaultOptions].flatten().compact().each(function(defs) {
+      Object.extend(this.options, defs);
+    }.bind(this));
+  },
+  prepareSubmission: function() {
+    this._saving = true;
     this.removeForm();
     this.leaveHover();
     this.showSaving();
   },
+  registerListeners: function() {
+    this._listeners = { };
+    var listener;
+    $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
+      listener = this[pair.value].bind(this);
+      this._listeners[pair.key] = listener;
+      if (!this.options.externalControlOnly)
+        this.element.observe(pair.key, listener);
+      if (this.options.externalControl)
+        this.options.externalControl.observe(pair.key, listener);
+    }.bind(this));
+  },
+  removeForm: function() {
+    if (!this._form) return;
+    this._form.remove();
+    this._form = null;
+    this._controls = { };
+  },
   showSaving: function() {
-    this.oldInnerHTML = this.element.innerHTML;
+    this._oldInnerHTML = this.element.innerHTML;
     this.element.innerHTML = this.options.savingText;
-    Element.addClassName(this.element, this.options.savingClassName);
-    this.element.style.backgroundColor = this.originalBackground;
-    Element.show(this.element);
+    this.element.addClassName(this.options.savingClassName);
+    this.element.style.backgroundColor = this._originalBackground;
+    this.element.show();
   },
-  removeForm: function() {
-    if(this.form) {
-      Element.remove(this.form);
-      this.form = null;
+  triggerCallback: function(cbName, arg) {
+    if ('function' == typeof this.options[cbName]) {
+      this.options[cbName](this, arg);
     }
   },
-  enterHover: function() {
-    if (this.saving) return;
-    this.element.style.backgroundColor = this.options.highlightcolor;
-    if (this.effect) {
-      this.effect.cancel();
-    }
-    Element.addClassName(this.element, this.options.hoverClassName)
+  unregisterListeners: function() {
+    $H(this._listeners).each(function(pair) {
+      if (!this.options.externalControlOnly)
+        this.element.stopObserving(pair.key, pair.value);
+      if (this.options.externalControl)
+        this.options.externalControl.stopObserving(pair.key, pair.value);
+    }.bind(this));
   },
-  leaveHover: function() {
-    if (this.options.backgroundColor) {
-      this.element.style.backgroundColor = this.oldBackground;
-    }
-    Element.removeClassName(this.element, this.options.hoverClassName)
-    if (this.saving) return;
-    this.effect = new Effect.Highlight(this.element, {
-      startcolor: this.options.highlightcolor,
-      endcolor: this.options.highlightendcolor,
-      restorecolor: this.originalBackground
+  wrapUp: function(transport) {
+    this.leaveEditMode();
+    // Can't use triggerCallback due to backward compatibility: requires
+    // binding + direct element
+    this._boundComplete(transport, this.element);
+  }
+});
+
+Object.extend(Ajax.InPlaceEditor.prototype, {
+  dispose: Ajax.InPlaceEditor.prototype.destroy
+});
+
+Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
+  initialize: function($super, element, url, options) {
+    this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
+    $super(element, url, options);
+  },
+
+  createEditField: function() {
+    var list = document.createElement('select');
+    list.name = this.options.paramName;
+    list.size = 1;
+    this._controls.editor = list;
+    this._collection = this.options.collection || [];
+    if (this.options.loadCollectionURL)
+      this.loadCollection();
+    else
+      this.checkForExternalText();
+    this._form.appendChild(this._controls.editor);
+  },
+
+  loadCollection: function() {
+    this._form.addClassName(this.options.loadingClassName);
+    this.showLoadingText(this.options.loadingCollectionText);
+    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+    Object.extend(options, {
+      parameters: 'editorId=' + encodeURIComponent(this.element.id),
+      onComplete: Prototype.emptyFunction,
+      onSuccess: function(transport) {
+        var js = transport.responseText.strip();
+        if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
+          throw 'Server returned an invalid collection representation.';
+        this._collection = eval(js);
+        this.checkForExternalText();
+      }.bind(this),
+      onFailure: this.onFailure
     });
+    new Ajax.Request(this.options.loadCollectionURL, options);
   },
-  leaveEditMode: function() {
-    Element.removeClassName(this.element, this.options.savingClassName);
-    this.removeForm();
-    this.leaveHover();
-    this.element.style.backgroundColor = this.originalBackground;
-    Element.show(this.element);
-    if (this.options.externalControl) {
-      Element.show(this.options.externalControl);
+
+  showLoadingText: function(text) {
+    this._controls.editor.disabled = true;
+    var tempOption = this._controls.editor.firstChild;
+    if (!tempOption) {
+      tempOption = document.createElement('option');
+      tempOption.value = '';
+      this._controls.editor.appendChild(tempOption);
+      tempOption.selected = true;
     }
-    this.editing = false;
-    this.saving = false;
-    this.oldInnerHTML = null;
-    this.onLeaveEditMode();
+    tempOption.update((text || '').stripScripts().stripTags());
   },
-  onComplete: function(transport) {
-    this.leaveEditMode();
-    this.options.onComplete.bind(this)(transport, this.element);
+
+  checkForExternalText: function() {
+    this._text = this.getText();
+    if (this.options.loadTextURL)
+      this.loadExternalText();
+    else
+      this.buildOptionList();
   },
-  onEnterEditMode: function() {},
-  onLeaveEditMode: function() {},
-  dispose: function() {
-    if (this.oldInnerHTML) {
-      this.element.innerHTML = this.oldInnerHTML;
-    }
-    this.leaveEditMode();
-    Event.stopObserving(this.element, 'click', this.onclickListener);
-    Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
-    Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
-    if (this.options.externalControl) {
-      Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
-      Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
-      Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
+
+  loadExternalText: function() {
+    this.showLoadingText(this.options.loadingText);
+    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+    Object.extend(options, {
+      parameters: 'editorId=' + encodeURIComponent(this.element.id),
+      onComplete: Prototype.emptyFunction,
+      onSuccess: function(transport) {
+        this._text = transport.responseText.strip();
+        this.buildOptionList();
+      }.bind(this),
+      onFailure: this.onFailure
+    });
+    new Ajax.Request(this.options.loadTextURL, options);
+  },
+
+  buildOptionList: function() {
+    this._form.removeClassName(this.options.loadingClassName);
+    this._collection = this._collection.map(function(entry) {
+      return 2 === entry.length ? entry : [entry, entry].flatten();
+    });
+    var marker = ('value' in this.options) ? this.options.value : this._text;
+    var textFound = this._collection.any(function(entry) {
+      return entry[0] == marker;
+    }.bind(this));
+    this._controls.editor.update('');
+    var option;
+    this._collection.each(function(entry, index) {
+      option = document.createElement('option');
+      option.value = entry[0];
+      option.selected = textFound ? entry[0] == marker : 0 == index;
+      option.appendChild(document.createTextNode(entry[1]));
+      this._controls.editor.appendChild(option);
+    }.bind(this));
+    this._controls.editor.disabled = false;
+    Field.scrollFreeActivate(this._controls.editor);
+  }
+});
+
+//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
+//**** This only  exists for a while,  in order to  let ****
+//**** users adapt to  the new API.  Read up on the new ****
+//**** API and convert your code to it ASAP!            ****
+
+Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
+  if (!options) return;
+  function fallback(name, expr) {
+    if (name in options || expr === undefined) return;
+    options[name] = expr;
+  };
+  fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
+    options.cancelLink == options.cancelButton == false ? false : undefined)));
+  fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
+    options.okLink == options.okButton == false ? false : undefined)));
+  fallback('highlightColor', options.highlightcolor);
+  fallback('highlightEndColor', options.highlightendcolor);
+};
+
+Object.extend(Ajax.InPlaceEditor, {
+  DefaultOptions: {
+    ajaxOptions: { },
+    autoRows: 3,                                // Use when multi-line w/ rows == 1
+    cancelControl: 'link',                      // 'link'|'button'|false
+    cancelText: 'cancel',
+    clickToEditText: 'Click to edit',
+    externalControl: null,                      // id|elt
+    externalControlOnly: false,
+    fieldPostCreation: 'activate',              // 'activate'|'focus'|false
+    formClassName: 'inplaceeditor-form',
+    formId: null,                               // id|elt
+    highlightColor: '#ffff99',
+    highlightEndColor: '#ffffff',
+    hoverClassName: '',
+    htmlResponse: true,
+    loadingClassName: 'inplaceeditor-loading',
+    loadingText: 'Loading...',
+    okControl: 'button',                        // 'link'|'button'|false
+    okText: 'ok',
+    paramName: 'value',
+    rows: 1,                                    // If 1 and multi-line, uses autoRows
+    savingClassName: 'inplaceeditor-saving',
+    savingText: 'Saving...',
+    size: 0,
+    stripLoadedTextTags: false,
+    submitOnBlur: false,
+    textAfterControls: '',
+    textBeforeControls: '',
+    textBetweenControls: ''
+  },
+  DefaultCallbacks: {
+    callback: function(form) {
+      return Form.serialize(form);
+    },
+    onComplete: function(transport, element) {
+      // For backward compatibility, this one is bound to the IPE, and passes
+      // the element directly.  It was too often customized, so we don't break it.
+      new Effect.Highlight(element, {
+        startcolor: this.options.highlightColor, keepBackgroundImage: true });
+    },
+    onEnterEditMode: null,
+    onEnterHover: function(ipe) {
+      ipe.element.style.backgroundColor = ipe.options.highlightColor;
+      if (ipe._effect)
+        ipe._effect.cancel();
+    },
+    onFailure: function(transport, ipe) {
+      alert('Error communication with the server: ' + transport.responseText.stripTags());
+    },
+    onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
+    onLeaveEditMode: null,
+    onLeaveHover: function(ipe) {
+      ipe._effect = new Effect.Highlight(ipe.element, {
+        startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
+        restorecolor: ipe._originalBackground, keepBackgroundImage: true
+      });
     }
+  },
+  Listeners: {
+    click: 'enterEditMode',
+    keydown: 'checkForEscapeOrReturn',
+    mouseover: 'enterHover',
+    mouseout: 'leaveHover'
   }
-};
\ No newline at end of file
+});
+
+Ajax.InPlaceCollectionEditor.DefaultOptions = {
+  loadingCollectionText: 'Loading options...'
+};
+
+// Delayed observer, like Form.Element.Observer, 
+// but waits for delay after last key input
+// Ideal for live-search fields
+
+Form.Element.DelayedObserver = Class.create({
+  initialize: function(element, delay, callback) {
+    this.delay     = delay || 0.5;
+    this.element   = $(element);
+    this.callback  = callback;
+    this.timer     = null;
+    this.lastValue = $F(this.element); 
+    Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
+  },
+  delayedListener: function(event) {
+    if(this.lastValue == $F(this.element)) return;
+    if(this.timer) clearTimeout(this.timer);
+    this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
+    this.lastValue = $F(this.element);
+  },
+  onTimerEvent: function() {
+    this.timer = null;
+    this.callback(this.element, $F(this.element));
+  }
+});
diff --git a/webcit/static/datepicker-dev.js b/webcit/static/datepicker-dev.js
new file mode 100644 (file)
index 0000000..c558ff8
--- /dev/null
@@ -0,0 +1,773 @@
+/**
+ * DatePicker widget using Prototype and Scriptaculous.
+ * (c) 2007 Mathieu Jondet <mathieu@eulerian.com>
+ * Eulerian Technologies
+ *
+ * DatePicker is freely distributable under the same terms as Prototype.
+ *
+ */
+
+/**
+ * DatePickerFormatter class for matching and stringifying dates.
+ *
+ * By Arturas Slajus <x11@arturaz.net>.
+ */
+var DatePickerFormatter = Class.create();
+DatePickerFormatter.prototype = {
+    /**
+     * Create a DatePickerFormatter.
+     *
+     * format: specify a format by passing 3 value array consisting of
+     *   "yyyy", "mm", "dd". Default: ["yyyy", "mm", "dd"].
+     *
+     * separator: string for splitting the values. Default: "-".
+     *
+     * Use it like this:
+     *   var df = new DatePickerFormatter(["dd", "mm", "yyyy"], "/");
+     *   df.current_date();
+     *   df.match("7/7/2007");
+     */
+    initialize: function(format, separator) {
+        if (Object.isUndefined(format))
+        format = ["yyyy", "mm", "dd"];
+        if (Object.isUndefined(separator))
+        separator = "-";
+
+        this._format   = format;
+        this.separator = separator;
+                
+        this._format_year_index        = format.indexOf("yyyy");
+        this._format_month_index= format.indexOf("mm");
+        this._format_day_index = format.indexOf("dd");
+                
+        this._year_regexp      = /^\d{4}$/;
+        this._month_regexp     = /^0\d|1[012]|\d$/;
+        this._day_regexp       = /^0\d|[12]\d|3[01]|\d$/;
+    },
+    
+    /**
+     * Match a string against date format.
+     * Returns: [year, month, day]
+     */
+    match: function(str) {
+        var d = str.split(this.separator);
+        
+        if (d.length < 3)
+        return false;
+        
+        var year = d[this._format_year_index].match(this._year_regexp);
+        if (year) { year = year[0] } else { return false }
+        var month = d[this._format_month_index].match(this._month_regexp);
+        if (month) { month = month[0] } else { return false }
+        var day = d[this._format_day_index].match(this._day_regexp);
+        if (day) { day = day[0] } else { return false }
+        
+        return [year, month, day];
+    },
+    
+    /**
+     * Return current date according to format.
+     */
+    current_date: function() {
+        var d = new Date;
+        return this.date_to_string(
+            d.getFullYear(),
+            d.getMonth() + 1,
+            d.getDate()
+       );
+    },
+    
+    /**
+     * Return a stringified date accordint to format.
+     */
+    date_to_string: function(year, month, day, separator) {
+        if (Object.isUndefined(separator))
+        separator = this.separator;
+
+        var a = [0, 0, 0];
+        a[this._format_year_index]     = year;
+        a[this._format_month_index]    = month.toPaddedString(2);
+        a[this._format_day_index]      = day.toPaddedString(2);
+        
+        return a.join(separator);
+    }
+}; 
+
+
+/**
+ * DatePicker
+ */
+
+var DatePicker = Class.create();
+
+DatePicker.prototype   = {
+ Version       : '0.9.4',
+ _relative     : null,
+ _div          : null,
+ _zindex       : 1,
+ _keepFieldEmpty: false,
+ _daysInMonth  : [31,28,31,30,31,30,31,31,30,31,30,31],
+ _dateFormat   : [ ["dd", "mm", "yyyy"], "/" ],
+ /* language */
+ _language     : 'fr',
+ _language_month       : $H({
+  'fr' : [ 'Janvier', 'F&#233;vrier', 'Mars', 'Avril', 'Mai', 'Juin', 
+   'Juillet', 'Aout', 'Septembre', 'Octobre', 'Novembre', 'D&#233;cembre' ],
+  'en' : [ 'January', 'February', 'March', 'April', 'May',
+   'June', 'July', 'August', 'September', 'October', 'November', 'December' ],
+  'sp' : [ 'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 
+   'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre' ],
+  'it' : [ 'Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno',
+   'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre' ],
+  'de' : [ 'Januar', 'Februar', 'M&#228;rz', 'April', 'Mai', 'Juni',
+   'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember' ],
+  'pt' : [ 'Janeiro', 'Fevereiro', 'Mar&#231;o', 'Abril', 'Maio', 'Junho',
+   'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro' ],
+  'hu' : [ 'Janu&#225;r', 'Febru&#225;r', 'M&#225;rcius', '&#193;prilis', 
+   'M&#225;jus', 'J&#250;nius', 'J&#250;lius', 'Augusztus', 'Szeptember', 
+   'Okt&#243;ber', 'November', 'December' ],
+  'lt'  : [ 'Sausis', 'Vasaris', 'Kovas', 'Balandis', 'Gegu&#382;&#279;',
+   'Bir&#382;elis', 'Liepa', 'Rugj&#363;tis', 'Rus&#279;jis', 'Spalis', 
+   'Lapkritis', 'Gruodis' ],
+  'nl' : [ 'januari', 'februari', 'maart', 'april', 'mei', 'juni',
+   'juli', 'augustus', 'september', 'oktober', 'november', 'december' ],
+  'dk' : [ 'Januar', 'Februar', 'Marts', 'April', 'Maj',
+   'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'December' ],
+  'no' : [ 'Januar', 'Februar', 'Mars', 'April', 'Mai', 'Juni',
+   'Juli', 'August', 'September', 'Oktober', 'November', 'Desember' ],
+  'lv' : [ 'Janv&#257;ris', 'Febru&#257;ris', 'Marts', 'Apr&#299;lis', 'Maijs',
+   'J&#363;nijs', 'J&#363;lijs', 'Augusts', 'Septembris', 'Oktobris', 
+   'Novembris', 'Decemberis' ],
+  'ja' : [ '1&#26376;', '2&#26376;', '3&#26376;', '4&#26376;', '5&#26376;',
+   '6&#26376;', '7&#26376;', '8&#26376;', '9&#26376;', '10&#26376;', 
+   '11&#26376;', '12&#26376;' ],
+  'fi' : [ 'Tammikuu', 'Helmikuu', 'Maaliskuu', 'Huhtikuu', 'Toukokuu',
+   'Kes&#228;kuu', 'Hein&#228;kuu', 'Elokuu', 'Syyskuu', 'Lokakuu', 
+   'Marraskuu', 'Joulukuu' ],
+  'ro' : [ 'Ianuarie', 'Februarie', 'Martie', 'Aprilie', 'Mai', 'Junie',
+   'Julie', 'August', 'Septembrie', 'Octombrie', 'Noiembrie', 'Decembrie' ],
+  'zh' : [ '1&#32;&#26376;', '2&#32;&#26376;', '3&#32;&#26376;', 
+   '4&#32;&#26376;', '5&#32;&#26376;', '6&#32;&#26376;', '7&#32;&#26376;', 
+   '8&#32;&#26376;', '9&#32;&#26376;', '10&#26376;', '11&#26376;', '12&#26376;'],
+  'sv' : [ 'Januari', 'Februari', 'Mars', 'April', 'Maj', 'Juni',
+   'Juli', 'Augusti', 'September', 'Oktober', 'November', 'December' ]
+ }),
+ _language_day : $H({
+  'fr' : [ 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim' ],
+  'en' : [ 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun' ],
+  'sp' : [ 'Lun', 'Mar', 'Mie', 'Jue', 'Vie', 'S&#225;b', 'Dom' ],
+  'it' : [ 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab', 'Dom' ],
+  'de' : [ 'Mon', 'Die', 'Mit', 'Don', 'Fre', 'Sam', 'Son' ],
+  'pt' : [ 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'S&#225;b', 'Dom' ],
+  'hu' : [ 'H&#233;', 'Ke', 'Sze', 'Cs&#252;', 'P&#233;', 'Szo', 'Vas' ],
+  'lt'  : [ 'Pir', 'Ant', 'Tre', 'Ket', 'Pen', '&Scaron;e&scaron;', 'Sek' ],
+  'nl' : [ 'ma', 'di', 'wo', 'do', 'vr', 'za', 'zo' ],
+  'dk' : [ 'Man', 'Tir', 'Ons', 'Tor', 'Fre', 'L&#248;r', 'S&#248;n' ],
+  'no' : [ 'Man', 'Tir', 'Ons', 'Tor', 'Fre', 'L&#248;r', 'Sun' ],
+  'lv' : [ 'P', 'O', 'T', 'C', 'Pk', 'S', 'Sv' ],
+  'ja' : [ '&#26376;', '&#28779;', '&#27700;', '&#26408;', '&#37329;', 
+   '&#22303;', '&#26085;' ],
+  'fi' : [ 'Ma', 'Ti', 'Ke', 'To', 'Pe', 'La', 'Su' ],
+  'ro' : [ 'Lun', 'Mar', 'Mie', 'Joi', 'Vin', 'Sam', 'Dum' ],
+  'zh' : [ '&#21608;&#19968;', '&#21608;&#20108;', '&#21608;&#19977;', 
+   '&#21608;&#22235;', '&#21608;&#20116;', '&#21608;&#20845;', 
+   '&#21608;&#26085;' ],
+  'sv' : [ 'M&#229;n', 'Tis', 'Ons', 'Tor', 'Fre', 'L&#246;r', 
+   'S&#246;n' ]
+ }),
+ _language_close       : $H({
+  'fr' : 'fermer',
+  'en' : 'close',
+  'sp' : 'cerrar',
+  'it' : 'fine',
+  'de' : 'schliessen',
+  'pt' : 'fim',
+  'hu' : 'bez&#225;r',
+  'lt'  : 'udaryti',
+  'nl' : 'sluiten',
+  'dk' : 'luk',
+  'no' : 'lukk',
+  'lv' : 'aizv&#275;rt',
+  'ja' : '&#38281;&#12376;&#12427;',
+  'fi' : 'sulje',
+  'ro' : 'inchide',
+  'zh' : '&#20851;&#32;&#38381',
+  'sv' : 'st&#228;ng'
+ }),
+ /* date manipulation */
+ _todayDate            : new Date(),
+ _current_date         : null,
+ _clickCallback                : Prototype.emptyFunction,
+ _cellCallback         : Prototype.emptyFunction,
+ _id_datepicker                : null,
+ _disablePastDate      : false,
+ _disableFutureDate    : true,
+ _enableYearBrowse     : false,
+ _oneDayInMs           : 24 * 3600 * 1000,
+ /* positionning */
+ _topOffset            : 30,
+ _leftOffset           : 0,
+ _isPositionned                : false,
+ _relativePosition     : true,
+ _setPositionTop       : 0,
+ _setPositionLeft      : 0,
+ _bodyAppend           : false,
+ _contentAppend                : '',
+ _showEvent            : 'click',
+ /* Effects Adjustment */
+ _showEffect           : "appear", 
+ _showDuration         : 1,
+ _enableShowEffect     : true,
+ _closeEffect          : "fade", 
+ _closeEffectDuration  : 0.3,
+ _enableCloseEffect    : true,
+ _closeTimer           : null,
+ _enableCloseOnBlur    : false,
+ /* afterClose : called when the close function is executed */
+ _afterClose   : Prototype.emptyFunction,
+ /* return the name of current month in appropriate language */
+ getMonthLocale        : function ( month ) {
+  return       this._language_month.get(this._language)[month];
+ },
+ getLocaleClose        : function () {
+  return       this._language_close.get(this._language);
+ },
+ _initCurrentDate : function () {
+  /* Create the DateFormatter */
+  this._df = new DatePickerFormatter(this._dateFormat[0], this._dateFormat[1]);
+  /* check if value in field is proper, if not set to today */
+  this._current_date = $F(this._relative);
+  if (! this._df.match(this._current_date)) {
+    this._current_date = this._df.current_date();
+   /* set the field value ? */
+   if (!this._keepFieldEmpty)
+    $(this._relative).value = this._current_date;
+  }
+  var a_date = this._df.match(this._current_date);
+  this._current_year   = Number(a_date[0]);
+  this._current_mon    = Number(a_date[1]) - 1;
+  this._current_day    = Number(a_date[2]);
+ },
+ /* init */
+ initialize    : function ( h_p ) {
+  /* arguments */
+  this._relative= h_p["relative"];
+  if (h_p["language"])
+   this._language = h_p["language"];
+  this._zindex = ( h_p["zindex"] ) ? parseInt(Number(h_p["zindex"])) : 1;
+  if (!Object.isUndefined(h_p["keepFieldEmpty"]))
+   this._keepFieldEmpty        = h_p["keepFieldEmpty"];
+  if (Object.isFunction(h_p["clickCallback"])) 
+   this._clickCallback = h_p["clickCallback"];
+  if (!Object.isUndefined(h_p["leftOffset"]))
+   this._leftOffset    = parseInt(h_p["leftOffset"]);
+  if (!Object.isUndefined(h_p["topOffset"]))
+   this._topOffset     = parseInt(h_p["topOffset"]);
+  if (!Object.isUndefined(h_p["relativePosition"]))
+   this._relativePosition = h_p["relativePosition"];
+  if (!Object.isUndefined(h_p["showEvent"]))
+   this._showEvent     = h_p["showEvent"];
+  if (!Object.isUndefined(h_p["showEffect"]))
+   this._showEffect    = h_p["showEffect"];
+  if (!Object.isUndefined(h_p["contentAppend"]))
+   this._contentAppend = h_p["contentAppend"];
+  if (!Object.isUndefined(h_p["enableShowEffect"]))
+   this._enableShowEffect      = h_p["enableShowEffect"];
+  if (!Object.isUndefined(h_p["showDuration"]))
+   this._showDuration  = h_p["showDuration"];
+  if (!Object.isUndefined(h_p["closeEffect"]))
+   this._closeEffect   = h_p["closeEffect"];
+  if (!Object.isUndefined(h_p["enableCloseEffect"]))
+   this._enableCloseEffect     = h_p["enableCloseEffect"];
+  if (!Object.isUndefined(h_p["closeEffectDuration"]))
+   this._closeEffectDuration = h_p["closeEffectDuration"];
+  if (Object.isFunction(h_p["afterClose"]))
+   this._afterClose    = h_p["afterClose"];
+  if (!Object.isUndefined(h_p["externalControl"]))
+   this._externalControl= h_p["externalControl"];
+  if (!Object.isUndefined(h_p["dateFormat"])) 
+   this._dateFormat    = h_p["dateFormat"];
+  if (Object.isFunction(h_p["cellCallback"]))
+   this._cellCallback  = h_p["cellCallback"];
+  this._setPositionTop = ( h_p["setPositionTop"] ) ? 
+   parseInt(Number(h_p["setPositionTop"])) : 0;
+  this._setPositionLeft        = ( h_p["setPositionLeft"] ) ? 
+   parseInt(Number(h_p["setPositionLeft"])) : 0;
+  if (!Object.isUndefined(h_p["enableCloseOnBlur"]) && h_p["enableCloseOnBlur"])
+   this._enableCloseOnBlur     = true;
+  if (!Object.isUndefined(h_p["disablePastDate"]) && h_p["disablePastDate"])
+   this._disablePastDate       = true;
+  if (!Object.isUndefined(h_p["disableFutureDate"]) && 
+   !h_p["disableFutureDate"])
+   this._disableFutureDate     = false;
+  if (!Object.isUndefined(h_p["enableYearBrowse"]))
+   this._enableYearBrowse      = true;
+  this._id_datepicker          = 'datepicker-'+this._relative;
+  this._id_datepicker_prev     = this._id_datepicker+'-prev';
+  this._id_datepicker_next     = this._id_datepicker+'-next';
+  this._id_datepicker_prev_year        = this._id_datepicker_prev+'-year';
+  this._id_datepicker_next_year        = this._id_datepicker_next+'-year';
+  this._id_datepicker_hdr      = this._id_datepicker+'-header';
+  this._id_datepicker_ftr      = this._id_datepicker+'-footer';
+
+  /* build up calendar skel */
+  this._div = new Element('div', { 
+   id : this._id_datepicker,
+   className : 'datepicker',
+   style : 'display: none; z-index:'+this._zindex });
+  this._div.innerHTML = '<table><thead><tr>'+((this._enableYearBrowse) ? '<th width="10px" id="'+this._id_datepicker_prev_year+'" style="cursor: pointer;">&nbsp;&lt;&nbsp;</th>' : '')+'<th width="10px" id="'+this._id_datepicker_prev+'" style="cursor: pointer;">&nbsp;&lt;&lt;&nbsp;</th><th id="'+this._id_datepicker_hdr+'" colspan="'+((this._enableYearBrowse) ? 3 : 5 )+'"></th><th width="10px" id="'+this._id_datepicker_next+'" style="cursor: pointer;">&nbsp;&gt;&gt;&nbsp;</th>'+((this._enableYearBrowse) ? '<th width="10px" id="'+this._id_datepicker_next_year+'" style="cursor: pointer;">&nbsp;&gt;&nbsp;</th>' : '')+'</tr></thead><tbody id="'+this._id_datepicker+'-tbody"></tbody><tfoot><td colspan="7" id="'+this._id_datepicker_ftr+'"></td></tfoot></table>';
+  /* finally declare the event listener on input field */
+  Event.observe(this._relative, 
+    this._showEvent, this.click.bindAsEventListener(this), false);
+  /* need to append on body when doc is loaded for IE */
+  document.observe('dom:loaded', this.load.bindAsEventListener(this), false);
+  /* automatically close when blur event is triggered */
+  if ( this._enableCloseOnBlur ) {
+   Event.observe(this._relative, 'blur', function (e) { 
+    this._closeTimer = this.close.bind(this).delay(2); 
+   }.bindAsEventListener(this));
+   Event.observe(this._div, 'click', function (e) { 
+    if (this._closeTimer) { 
+     window.clearTimeout(this._closeTimer); 
+     this._closeTimer = null; 
+    } 
+   });
+  }
+ },
+ /**
+  * load       : called when document is fully-loaded to append datepicker
+  *              to main object.
+  */
+ load          : function () {
+  /* if externalControl defined set the observer on it */
+  if (this._externalControl) 
+   Event.observe(this._externalControl, 'click',
+    this.click.bindAsEventListener(this), false);
+  /* append to page */
+  if (this._relativeAppend) {
+   /* append to parent node */
+   if ($(this._relative).parentNode) {
+    this._div.innerHTML = this._wrap_in_iframe(this._div.innerHTML);
+    $(this._relative).parentNode.appendChild( this._div );
+   }
+  } else {
+   /* append to body tag or to provided contentAppend id */
+   var body    = ( this._contentAppend ) ?
+    $(this._contentAppend) : document.getElementsByTagName("body").item(0);
+   if (body) {
+    this._div.innerHTML = this._wrap_in_iframe(this._div.innerHTML);
+    body.appendChild(this._div);
+   }
+   if ( this._relativePosition ) {
+     var a_pos = Element.cumulativeOffset($(this._relative));
+     this.setPosition(a_pos[1], a_pos[0]);
+   } else {
+    if (this._setPositionTop || this._setPositionLeft)
+     this.setPosition(this._setPositionTop, this._setPositionLeft);
+   }
+  }
+  /* init the date in field if needed */
+  this._initCurrentDate();
+  /* set the close locale content */
+  $(this._id_datepicker_ftr).innerHTML = this.getLocaleClose();
+  /* declare the observers for UI control */
+  Event.observe($(this._id_datepicker_prev), 
+    'click', this.prevMonth.bindAsEventListener(this), false);
+  Event.observe($(this._id_datepicker_next), 
+    'click', this.nextMonth.bindAsEventListener(this), false);
+  if ( this._enableYearBrowse ) {
+   Event.observe($(this._id_datepicker_prev_year), 
+     'click', this.prevYear.bindAsEventListener(this), false);
+   Event.observe($(this._id_datepicker_next_year), 
+     'click', this.nextYear.bindAsEventListener(this), false);
+  }
+  Event.observe($(this._id_datepicker_ftr), 
+    'click', this.close.bindAsEventListener(this), false);
+ },
+ /* hack for buggy form elements layering in IE */
+ _wrap_in_iframe       : function ( content ) {
+  var _iframe_src      = 'javascript:false';
+  return       ( Prototype.Browser.IE ) ?
+   "<div style='height:167px;width:185px;background-color:white;align:left'><iframe width='100%' height='100%' marginwidth='0' marginheight='0' frameborder='0' src='"+ _iframe_src +"' style='filter:alpha(Opacity=50);'></iframe><div style='position:absolute;background-color:white;top:2px;left:2px;width:180px'>" + content + "</div></div>" : content;
+ },
+ /**
+  * visible    : return the visibility status of the datepicker.
+  */
+ visible       : function () {
+  return       ( $(this._id_datepicker) ) ? 
+   $(this._id_datepicker).visible() : false;
+ },
+ /**
+  * click      : called when input element is clicked
+  */
+ click         : function () {
+  /* init the datepicker if it doesn't exists */
+  if ( $(this._id_datepicker) == null ) this.load();
+  if (!this._isPositionned && this._relativePosition) {
+   /* position the datepicker relatively to element */
+   var a_lt = Element.positionedOffset($(this._relative));
+   $(this._id_datepicker).setStyle({
+    'left'     : Number(a_lt[0]+this._leftOffset)+'px',
+    'top'      : Number(a_lt[1]+this._topOffset)+'px'
+   });
+   this._isPositionned = true;
+  }
+  if (!this.visible()) {
+   this._initCurrentDate();
+   this._redrawCalendar();
+  }
+  /* eval the clickCallback function */
+  eval(this._clickCallback());
+  /* Effect toggle to fade-in / fade-out the datepicker */
+  if ( this._enableShowEffect ) {
+   new Effect.toggle(this._id_datepicker, 
+     this._showEffect, { duration: this._showDuration });
+  } else {
+   $(this._id_datepicker).show();
+  }
+  /* clean timer */
+  if (this._closeTimer) { 
+   window.clearTimeout(this._closeTimer); 
+   this._closeTimer = null; 
+  } 
+ },
+ /**
+  * close      : called when the datepicker is closed
+  */
+ close         : function () {
+  if ( this._enableCloseEffect ) {
+   switch(this._closeEffect) {
+    case 'puff': 
+     new Effect.Puff(this._id_datepicker, { 
+      duration : this._closeEffectDuration });
+     break;
+    case 'blindUp': 
+     new Effect.BlindUp(this._id_datepicker, { 
+      duration : this._closeEffectDuration });
+     break;
+    case 'dropOut': 
+     new Effect.DropOut(this._id_datepicker, { 
+      duration : this._closeEffectDuration }); 
+     break;
+    case 'switchOff': 
+     new Effect.SwitchOff(this._id_datepicker, { 
+      duration : this._closeEffectDuration }); 
+     break;
+    case 'squish': 
+     new Effect.Squish(this._id_datepicker, { 
+      duration : this._closeEffectDuration });
+     break;
+    case 'fold': 
+     new Effect.Fold(this._id_datepicker, { 
+      duration : this._closeEffectDuration });
+     break;
+    case 'shrink': 
+     new Effect.Shrink(this._id_datepicker, { 
+      duration : this._closeEffectDuration });
+     break;
+    default: 
+     new Effect.Fade(this._id_datepicker, { 
+      duration : this._closeEffectDuration });
+     break;
+   };
+  } else {
+   $(this._id_datepicker).hide();
+  }
+  eval(this._afterClose());
+ },
+ /**
+  * setDateFormat
+  */
+ setDateFormat : function ( format, separator ) {
+  if (Object.isUndefined(format))
+   format      = this._dateFormat[0];
+  if (Object.isUndefined(separator))
+   separator   = this._dateFormat[1];
+  this._dateFormat     = [ format, separator ];
+ },
+ /**
+  * setPosition        : set the position of the datepicker.
+  *  param : t=top | l=left
+  */
+ setPosition   : function ( t, l ) {
+  var h_pos    = { 'top' : '0px', 'left' : '0px' };
+  if (!Object.isUndefined(t))
+   h_pos['top']        = Number(t)+this._topOffset+'px';
+  if (!Object.isUndefined(l))
+   h_pos['left']= Number(l)+this._leftOffset+'px';
+  $(this._id_datepicker).setStyle(h_pos);
+  this._isPositionned  = true;
+ },
+ /**
+  * _getMonthDays : given the year and month find the number of days.
+  */
+ _getMonthDays : function ( year, month ) {
+  if (((0 == (year%4)) && 
+   ( (0 != (year%100)) || (0 == (year%400)))) && (month == 1))
+   return 29;
+  return this._daysInMonth[month];
+ },
+ /**
+  * _buildCalendar     : draw the days array for current date
+  */
+ _buildCalendar                : function () {
+  var _self    = this;
+  var tbody    = $(this._id_datepicker+'-tbody');
+  try {
+   while ( tbody.hasChildNodes() )
+    tbody.removeChild(tbody.childNodes[0]);
+  } catch ( e ) {};
+  /* generate day headers */
+  var trDay    = new Element('tr');
+  this._language_day.get(this._language).each( function ( item ) {
+   var td      = new Element('td');
+   td.innerHTML        = item;
+   td.className        = 'wday';
+   trDay.appendChild( td );
+  });
+  tbody.appendChild( trDay );
+  /* generate the content of days */
+  
+  /* build-up days matrix */
+  var a_d      = [ [ 0, 0, 0, 0, 0, 0, 0 ] ,[ 0, 0, 0, 0, 0, 0, 0 ]
+   ,[ 0, 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0, 0 ]
+   ,[ 0, 0, 0, 0, 0, 0, 0 ]
+  ];
+  /* set date at beginning of month to display */
+  var d                = new Date(this._current_year, this._current_mon, 1, 12);
+  /* start the day list on monday */
+  var startIndex       = ( !d.getDay() ) ? 6 : d.getDay() - 1;
+  var nbDaysInMonth    = this._getMonthDays(
+    this._current_year, this._current_mon);
+  var daysIndex                = 1;
+  for ( var j = startIndex; j < 7; j++ ) {
+   a_d[0][j]   = { 
+     d : daysIndex
+    ,m : this._current_mon
+    ,y : this._current_year 
+   };
+   daysIndex++;
+  }
+  var a_prevMY = this._prevMonthYear();
+  var nbDaysInMonthPrev        = this._getMonthDays(a_prevMY[1], a_prevMY[0]);
+  for ( var j = 0; j < startIndex; j++ ) {
+   a_d[0][j]   = { 
+     d : Number(nbDaysInMonthPrev - startIndex + j + 1) 
+    ,m : Number(a_prevMY[0])
+    ,y : a_prevMY[1]
+    ,c : 'outbound'
+   };
+  }
+  var switchNextMonth  = false;
+  var currentMonth     = this._current_mon;
+  var currentYear      = this._current_year;
+  for ( var i = 1; i < 6; i++ ) {
+   for ( var j = 0; j < 7; j++ ) {
+    a_d[i][j]  = { 
+      d : daysIndex
+     ,m : currentMonth
+     ,y : currentYear
+     ,c : ( switchNextMonth ) ? 'outbound' : ( 
+      ((daysIndex == this._todayDate.getDate()) &&
+        (this._current_mon  == this._todayDate.getMonth()) &&
+        (this._current_year == this._todayDate.getFullYear())) ? 'today' : null)
+    };
+    daysIndex++;
+    /* if at the end of the month : reset counter */
+    if (daysIndex > nbDaysInMonth ) {
+     daysIndex = 1;
+     switchNextMonth = true;
+     if (this._current_mon + 1 > 11 ) {
+      currentMonth = 0;
+      currentYear += 1;
+     } else {
+      currentMonth += 1;
+     }
+    }
+   }
+  }
+  /* generate days for current date */
+  for ( var i = 0; i < 6; i++ ) {
+   var tr      = new Element('tr');
+   for ( var j = 0; j < 7; j++ ) {
+    var h_ij   = a_d[i][j];
+    var td     = new Element('td');
+    /* id is : datepicker-day-mon-year or depending on language other way */
+    /* don't forget to add 1 on month for proper formmatting */
+    var id     = $A([
+     this._relative,
+     this._df.date_to_string(h_ij["y"], h_ij["m"]+1, h_ij["d"], '-')
+    ]).join('-');
+    /* set id and classname for cell if exists */
+    td.setAttribute('id', id);
+    if (h_ij["c"])
+     td.className      = h_ij["c"];
+    /* on onclick : rebuild date value from id of current cell */
+    var _curDate       = new Date();
+    _curDate.setFullYear(h_ij["y"], h_ij["m"], h_ij["d"]);
+    if ( this._disablePastDate || this._disableFutureDate ) {
+     if ( this._disablePastDate ) {
+      var _res = ( _curDate >= this._todayDate ) ? true : false;
+      this._bindCellOnClick( td, true, _res, h_ij["c"] );
+     }
+     if ( this._disableFutureDate ) {
+      var _res = ( this._todayDate.getTime() + this._oneDayInMs > _curDate.getTime() ) ? true : false;
+      this._bindCellOnClick( td, true, _res,  h_ij["c"] );
+     }
+    } else {
+     this._bindCellOnClick( td, false );
+    }
+    td.innerHTML= h_ij["d"];
+    tr.appendChild( td );
+   }
+   tbody.appendChild( tr );
+  }
+  return       tbody;
+ },
+ /**
+  * _bindCellOnClick   : bind the cell onclick depending on status.
+  */
+ _bindCellOnClick      : function ( td, wcompare, compareresult, h_ij_c ) {
+  var doBind   = false;
+  if ( wcompare ) {
+   if ( compareresult ) {
+    doBind     = true;
+   } else {
+    td.className= ( h_ij_c ) ? 'nclick_outbound' : 'nclick';
+   }
+  } else {
+   doBind      = true;
+  }
+  if ( doBind ) {
+   var _self   = this;
+   td.onclick  = function () { 
+    $(_self._relative).value = String($(this).readAttribute('id')
+      ).replace(_self._relative+'-','').replace(/-/g, _self._df.separator);
+    /* if we have a cellCallback defined call it and pass it the cell */
+    if (_self._cellCallback)
+     _self._cellCallback(this);
+    _self.close(); 
+   };
+  }
+ },
+ /**
+  * nextMonth  : redraw the calendar content for next month.
+  */
+ _nextMonthYear        : function () {
+  var c_mon    = this._current_mon;
+  var c_year   = this._current_year;
+  if (c_mon + 1 > 11) {
+   c_mon       = 0;
+   c_year      += 1;
+  } else {
+   c_mon       += 1;
+  }
+  return       [ c_mon, c_year ];
+ },
+ nextMonth     : function () {
+  var a_next   = this._nextMonthYear();
+  var _nextMon = a_next[0];
+  var _nextYear        = a_next[1];
+  var _curDate = new Date(); _curDate.setFullYear(_nextYear, _nextMon, 1);
+  var _res     = ( this._todayDate.getTime() + this._oneDayInMs > _curDate.getTime() ) ? true : false;
+  if ( this._disableFutureDate && !_res )
+   return;
+  this._current_mon    = _nextMon;
+  this._current_year   = _nextYear;
+  this._redrawCalendar();
+ },
+ /**
+  * prevMonth  : redraw the calendar content for previous month.
+  */
+ _prevMonthYear        : function () {
+  var c_mon    = this._current_mon;
+  var c_year   = this._current_year;
+  if (c_mon - 1 < 0) {
+   c_mon       = 11;
+   c_year      -= 1;
+  } else {
+   c_mon       -= 1;
+  }
+  return       [ c_mon, c_year ];
+ },
+ prevMonth     : function () {
+  var a_prev   = this._prevMonthYear();
+  var _prevMon = a_prev[0];
+  var _prevYear        = a_prev[1];
+  var _curDate = new Date(); _curDate.setFullYear(_prevYear, _prevMon, 1);
+  var _res     = ( _curDate >= this._todayDate ) ? true : false;
+  if ( this._disablePastDate && !_res && (_prevMon!=this._todayDate.getMonth()))
+   return;
+  this._current_mon    = _prevMon;
+  this._current_year   = _prevYear;
+  this._redrawCalendar();
+ },
+ /**
+  * prevYear   : redraw the calendar content for prev year.
+  */
+ _prevYear     : function () {
+  var c_mon    = this._current_mon;
+  var c_year   = (this._current_year - 1);
+  
+  return       [ c_mon, c_year ];
+ },
+ prevYear      : function () {
+  var a_next   = this._prevYear();
+  var _nextMon = a_next[0];
+  var _nextYear        = a_next[1];
+  var _curDate = new Date(); _curDate.setFullYear(_nextYear, _nextMon, 1);
+  var _res     = ( this._todayDate.getTime() + this._oneDayInMs > _curDate.getTime() ) ? true : false;
+  if ( this._disableFutureDate && !_res )
+   return;
+  this._current_mon    = _nextMon;
+  this._current_year   = _nextYear;
+  this._redrawCalendar();
+ },
+ /**
+  * nextYear   : redraw the calendar content for next year.
+  */
+ _nextYear     : function () {
+  var c_mon    = this._current_mon;
+  var c_year   = (this._current_year + 1);
+  
+  return       [ c_mon, c_year ];
+ },
+ nextYear      : function () {
+  var a_next   = this._nextYear();
+  var _nextMon = a_next[0];
+  var _nextYear        = a_next[1];
+  var _curDate = new Date(); _curDate.setFullYear(_nextYear, _nextMon, 1);
+  var _res     = ( this._todayDate.getTime() + this._oneDayInMs > _curDate.getTime() ) ? true : false;
+  if ( this._disableFutureDate && !_res )
+   return;
+  this._current_mon    = _nextMon;
+  this._current_year   = _nextYear;
+  this._redrawCalendar();
+ },
+
+ _redrawCalendar       : function () {
+  this._setLocaleHdr(); this._buildCalendar();
+ },
+ _setLocaleHdr : function () {
+  /* next link */
+  var a_next   = this._nextMonthYear();
+  $(this._id_datepicker_next).setAttribute('title',
+   this.getMonthLocale(a_next[0])+' '+a_next[1]);
+  /* prev link */
+  var a_prev   = this._prevMonthYear();
+  $(this._id_datepicker_prev).setAttribute('title',
+   this.getMonthLocale(a_prev[0])+' '+a_prev[1]);
+  /* year browse */
+  if ( this._enableYearBrowse ) {
+   var a_next_y        = this._nextYear();
+   $(this._id_datepicker_next_year).setAttribute('title',
+     this.getMonthLocale(a_next_y[0])+' '+a_next_y[1]);
+   var a_prev_y        = this._prevYear();
+   $(this._id_datepicker_prev_year).setAttribute('title',
+     this.getMonthLocale(a_prev_y[0])+' '+a_prev_y[1]);
+  }
+  /* header */
+  $(this._id_datepicker_hdr).update('&nbsp;&nbsp;&nbsp;'+this.getMonthLocale(this._current_mon)+'&nbsp;'+this._current_year+'&nbsp;&nbsp;&nbsp;');
+ }
+};
diff --git a/webcit/static/datepicker.css b/webcit/static/datepicker.css
new file mode 100644 (file)
index 0000000..4325951
--- /dev/null
@@ -0,0 +1,78 @@
+div.datepicker {
+ position: absolute;
+ text-align: center;
+ border: 1px #C4D5E3 solid;
+ font-family: arial;
+ background: #FFFFFF;
+ font-size: 10px;
+ padding: 0px;
+}
+div.datepicker table {
+ font-size: 10px;
+ margin: 0px;
+ padding: 0px;
+ text-align: center;
+ width: 180px;
+}
+div.datepicker table thead tr th {
+ font-size: 12px;
+ font-weight: bold;
+ background: #e9eff4;
+ border-bottom:1px solid #c4d5e3;
+ padding: 0px;
+ margin: 0px;
+}
+div.datepicker table tbody tr {
+ border: 1px white solid;
+ margin: 0px;
+ padding: 0px;
+}
+div.datepicker table tbody tr td {
+ border: 1px #eaeaea solid;
+ margin: 0px;
+ padding: 0px;
+ text-align: center;
+}
+div.datepicker table tbody tr td:hover,
+div.datepicker table tbody tr td.outbound:hover,
+div.datepicker table tbody tr td.today:hover {
+ border: 1px #c4d5e3 solid;
+ background: #e9eff4;
+ cursor: pointer;
+}
+div.datepicker table tbody tr td.wday {
+ border: 1px #ffffff solid;
+ background: #ffffff;
+ cursor: text;
+}
+div.datepicker table tbody tr td.outbound {
+ background: #e8e4e4;
+}
+div.datepicker table tbody tr td.today {
+ border: 1px #16518e solid;
+ background: #c4d5e3;
+}
+div.datepicker table tbody tr td.nclick,
+div.datepicker table tbody tr td.nclick_outbound {
+ cursor:default; color:#aaa;
+}
+div.datepicker table tbody tr td.nclick_outbound {
+ background:#E8E4E4;
+}
+div.datepicker table tbody tr td.nclick:hover,
+div.datepicker table tbody tr td.nclick_outbound:hover {
+ border: 1px #eaeaea solid;
+ background: #FFF;
+}
+div.datepicker table tbody tr td.nclick_outbound:hover {
+ background:#E8E4E4;
+}
+div.datepicker table tfoot {
+ font-size: 10px;
+ background: #e9eff4;
+ border-top:1px solid #c4d5e3;
+ cursor: pointer;
+ text-align: center;
+ padding: 0px;
+}
+
index 55461c1b93dda8d680c31271869512ac65cdf8a7..bf429c2616580d857bfa551c7de706534cdf5d70 100644 (file)
-// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
-// 
-// Element.Class part Copyright (c) 2005 by Rick Olson
-// 
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-// 
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
+// script.aculo.us dragdrop.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
+
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//           (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
 // 
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
 
-
-/*--------------------------------------------------------------------------*/
+if(Object.isUndefined(Effect))
+  throw("dragdrop.js requires including script.aculo.us' effects.js library");
 
 var Droppables = {
-  drops: false,
+  drops: [],
 
   remove: function(element) {
-    for(var i = 0; i < this.drops.length; i++)
-      if(this.drops[i].element == element)
-        this.drops.splice(i,1);
+    this.drops = this.drops.reject(function(d) { return d.element==$(element) });
   },
 
   add: function(element) {
     element = $(element);
     var options = Object.extend({
       greedy:     true,
-      hoverclass: null  
-    }, arguments[1] || {});
+      hoverclass: null,
+      tree:       false
+    }, arguments[1] || { });
 
     // cache containers
     if(options.containment) {
-      options._containers = new Array();
+      options._containers = [];
       var containment = options.containment;
-      if((typeof containment == 'object') && 
-        (containment.constructor == Array)) {
-        for(var i=0; i<containment.length; i++)
-          options._containers.push($(containment[i]));
+      if(Object.isArray(containment)) {
+        containment.each( function(c) { options._containers.push($(c)) });
       } else {
         options._containers.push($(containment));
       }
-      options._containers_length = 
-        options._containers.length-1;
     }
+    
+    if(options.accept) options.accept = [options.accept].flatten();
 
     Element.makePositioned(element); // fix IE
-
     options.element = element;
 
-    // activate the droppable    
-    if(!this.drops) this.drops = [];
     this.drops.push(options);
   },
+  
+  findDeepestChild: function(drops) {
+    deepest = drops[0];
+      
+    for (i = 1; i < drops.length; ++i)
+      if (Element.isParent(drops[i].element, deepest.element))
+        deepest = drops[i];
+    
+    return deepest;
+  },
 
   isContained: function(element, drop) {
-    var containers = drop._containers;
-    var parentNode = element.parentNode;
-    var i = drop._containers_length;
-    do { if(parentNode==containers[i]) return true; } while (i--);
-    return false;
+    var containmentNode;
+    if(drop.tree) {
+      containmentNode = element.treeNode; 
+    } else {
+      containmentNode = element.parentNode;
+    }
+    return drop._containers.detect(function(c) { return containmentNode == c });
   },
-
-  isAffected: function(pX, pY, element, drop) {
+  
+  isAffected: function(point, element, drop) {
     return (
       (drop.element!=element) &&
       ((!drop._containers) ||
         this.isContained(element, drop)) &&
       ((!drop.accept) ||
-        (Element.Class.has_any(element, drop.accept))) &&
-      Position.within(drop.element, pX, pY) );
+        (Element.classNames(element).detect( 
+          function(v) { return drop.accept.include(v) } ) )) &&
+      Position.within(drop.element, point[0], point[1]) );
   },
 
   deactivate: function(drop) {
-    Element.Class.remove(drop.element, drop.hoverclass);
+    if(drop.hoverclass)
+      Element.removeClassName(drop.element, drop.hoverclass);
     this.last_active = null;
   },
 
   activate: function(drop) {
-    if(this.last_active) this.deactivate(this.last_active);
     if(drop.hoverclass)
-      Element.Class.add(drop.element, drop.hoverclass);
+      Element.addClassName(drop.element, drop.hoverclass);
     this.last_active = drop;
   },
 
-  show: function(event, element) {
-    if(!this.drops) return;
-    var pX = Event.pointerX(event);
-    var pY = Event.pointerY(event);
-    Position.prepare();
-
-    var i = this.drops.length-1; do {
-      var drop = this.drops[i];
-      if(this.isAffected(pX, pY, element, drop)) {
-        if(drop.onHover)
-           drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
-        if(drop.greedy) { 
-          this.activate(drop);
-          return;
-        }
-      }
-    } while (i--);
+  show: function(point, element) {
+    if(!this.drops.length) return;
+    var drop, affected = [];
+    
+    this.drops.each( function(drop) {
+      if(Droppables.isAffected(point, element, drop))
+        affected.push(drop);
+    });
+        
+    if(affected.length>0)
+      drop = Droppables.findDeepestChild(affected);
+
+    if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
+    if (drop) {
+      Position.within(drop.element, point[0], point[1]);
+      if(drop.onHover)
+        drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
+      
+      if (drop != this.last_active) Droppables.activate(drop);
+    }
   },
 
   fire: function(event, element) {
     if(!this.last_active) return;
     Position.prepare();
 
-    if (this.isAffected(Event.pointerX(event), Event.pointerY(event), element, this.last_active))
-      if (this.last_active.onDrop) 
-        this.last_active.onDrop(element, this.last_active.element);
-
+    if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
+      if (this.last_active.onDrop) {
+        this.last_active.onDrop(element, this.last_active.element, event); 
+        return true; 
+      }
   },
 
   reset: function() {
@@ -130,252 +126,531 @@ var Droppables = {
 }
 
 var Draggables = {
-  observers: new Array(),
+  drags: [],
+  observers: [],
+  
+  register: function(draggable) {
+    if(this.drags.length == 0) {
+      this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
+      this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
+      this.eventKeypress  = this.keyPress.bindAsEventListener(this);
+      
+      Event.observe(document, "mouseup", this.eventMouseUp);
+      Event.observe(document, "mousemove", this.eventMouseMove);
+      Event.observe(document, "keypress", this.eventKeypress);
+    }
+    this.drags.push(draggable);
+  },
+  
+  unregister: function(draggable) {
+    this.drags = this.drags.reject(function(d) { return d==draggable });
+    if(this.drags.length == 0) {
+      Event.stopObserving(document, "mouseup", this.eventMouseUp);
+      Event.stopObserving(document, "mousemove", this.eventMouseMove);
+      Event.stopObserving(document, "keypress", this.eventKeypress);
+    }
+  },
+  
+  activate: function(draggable) {
+    if(draggable.options.delay) { 
+      this._timeout = setTimeout(function() { 
+        Draggables._timeout = null; 
+        window.focus(); 
+        Draggables.activeDraggable = draggable; 
+      }.bind(this), draggable.options.delay); 
+    } else {
+      window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
+      this.activeDraggable = draggable;
+    }
+  },
+  
+  deactivate: function() {
+    this.activeDraggable = null;
+  },
+  
+  updateDrag: function(event) {
+    if(!this.activeDraggable) return;
+    var pointer = [Event.pointerX(event), Event.pointerY(event)];
+    // Mozilla-based browsers fire successive mousemove events with
+    // the same coordinates, prevent needless redrawing (moz bug?)
+    if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
+    this._lastPointer = pointer;
+    
+    this.activeDraggable.updateDrag(event, pointer);
+  },
+  
+  endDrag: function(event) {
+    if(this._timeout) { 
+      clearTimeout(this._timeout); 
+      this._timeout = null; 
+    }
+    if(!this.activeDraggable) return;
+    this._lastPointer = null;
+    this.activeDraggable.endDrag(event);
+    this.activeDraggable = null;
+  },
+  
+  keyPress: function(event) {
+    if(this.activeDraggable)
+      this.activeDraggable.keyPress(event);
+  },
+  
   addObserver: function(observer) {
-    this.observers.push(observer);    
+    this.observers.push(observer);
+    this._cacheObserverCallbacks();
   },
-  removeObserver: function(element) {  // element instead of obsever fixes mem leaks
-    for(var i = 0; i < this.observers.length; i++)
-      if(this.observers[i].element && (this.observers[i].element == element))
-        this.observers.splice(i,1);
+  
+  removeObserver: function(element) {  // element instead of observer fixes mem leaks
+    this.observers = this.observers.reject( function(o) { return o.element==element });
+    this._cacheObserverCallbacks();
   },
-  notify: function(eventName, draggable) {  // 'onStart', 'onEnd'
-    for(var i = 0; i < this.observers.length; i++)
-      this.observers[i][eventName](draggable);
+  
+  notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag'
+    if(this[eventName+'Count'] > 0)
+      this.observers.each( function(o) {
+        if(o[eventName]) o[eventName](eventName, draggable, event);
+      });
+    if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
+  },
+  
+  _cacheObserverCallbacks: function() {
+    ['onStart','onEnd','onDrag'].each( function(eventName) {
+      Draggables[eventName+'Count'] = Draggables.observers.select(
+        function(o) { return o[eventName]; }
+      ).length;
+    });
   }
 }
 
 /*--------------------------------------------------------------------------*/
 
-var Draggable = Class.create();
-Draggable.prototype = {
+var Draggable = Class.create({
   initialize: function(element) {
-    var options = Object.extend({
+    var defaults = {
       handle: false,
-      starteffect: function(element) { 
-        new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7}); 
-      },
       reverteffect: function(element, top_offset, left_offset) {
         var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
-        new Effect.MoveBy(element, -top_offset, -left_offset, {duration:dur});
+        new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
+          queue: {scope:'_draggable', position:'end'}
+        });
       },
-      endeffect: function(element) { 
-         new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0}); 
+      endeffect: function(element) {
+        var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
+        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, 
+          queue: {scope:'_draggable', position:'end'},
+          afterFinish: function(){ 
+            Draggable._dragging[element] = false 
+          }
+        }); 
       },
       zindex: 1000,
-      revert: false
-    }, arguments[1] || {});
-
-    this.element      = $(element);
-    this.handle       = options.handle ? $(options.handle) : this.element;
+      revert: false,
+      quiet: false,
+      scroll: false,
+      scrollSensitivity: 20,
+      scrollSpeed: 15,
+      snap: false,  // false, or xy or [x,y] or function(x,y){ return [x,y] }
+      delay: 0
+    };
+    
+    if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
+      Object.extend(defaults, {
+        starteffect: function(element) {
+          element._opacity = Element.getOpacity(element);
+          Draggable._dragging[element] = true;
+          new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); 
+        }
+      });
+    
+    var options = Object.extend(defaults, arguments[1] || { });
+
+    this.element = $(element);
+    
+    if(options.handle && Object.isString(options.handle))
+      this.handle = this.element.down('.'+options.handle, 0);
+    
+    if(!this.handle) this.handle = $(options.handle);
+    if(!this.handle) this.handle = this.element;
+    
+    if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
+      options.scroll = $(options.scroll);
+      this._isScrollChild = Element.childOf(this.element, options.scroll);
+    }
 
     Element.makePositioned(this.element); // fix IE    
 
-    this.offsetX      = 0;
-    this.offsetY      = 0;
-    this.originalLeft = this.currentLeft();
-    this.originalTop  = this.currentTop();
-    this.originalX    = this.element.offsetLeft;
-    this.originalY    = this.element.offsetTop;
-    this.originalZ    = parseInt(this.element.style.zIndex || "0");
-
-    this.options      = options;
-
-    this.active       = false;
-    this.dragging     = false;   
-
-    this.eventMouseDown = this.startDrag.bindAsEventListener(this);
-    this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
-    this.eventMouseMove = this.update.bindAsEventListener(this);
-    this.eventKeypress  = this.keyPress.bindAsEventListener(this);
+    this.options  = options;
+    this.dragging = false;   
 
+    this.eventMouseDown = this.initDrag.bindAsEventListener(this);
     Event.observe(this.handle, "mousedown", this.eventMouseDown);
+    
+    Draggables.register(this);
   },
+  
   destroy: function() {
     Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
-    this.unregisterEvents();
-  },
-  registerEvents: function() {
-    if(this.active) return;
-    Event.observe(document, "mouseup", this.eventMouseUp);
-    Event.observe(document, "mousemove", this.eventMouseMove);
-    Event.observe(document, "keypress", this.eventKeypress);
-  },
-  unregisterEvents: function() {
-    if(!this.active) return;
-    Event.stopObserving(document, "mouseup", this.eventMouseUp);
-    Event.stopObserving(document, "mousemove", this.eventMouseMove);
-    Event.stopObserving(document, "keypress", this.eventKeypress);
+    Draggables.unregister(this);
   },
-  currentLeft: function() {
-    return parseInt(this.element.style.left || '0');
+  
+  currentDelta: function() {
+    return([
+      parseInt(Element.getStyle(this.element,'left') || '0'),
+      parseInt(Element.getStyle(this.element,'top') || '0')]);
   },
-  currentTop: function() {
-    return parseInt(this.element.style.top || '0')
-  },
-  startDrag: function(event) {
-    if(Event.isLeftClick(event)) {
-      this.registerEvents();
-      this.active = true;
+  
+  initDrag: function(event) {
+    if(!Object.isUndefined(Draggable._dragging[this.element]) &&
+      Draggable._dragging[this.element]) return;
+    if(Event.isLeftClick(event)) {    
+      // abort on form elements, fixes a Firefox issue
+      var src = Event.element(event);
+      if((tag_name = src.tagName.toUpperCase()) && (
+        tag_name=='INPUT' ||
+        tag_name=='SELECT' ||
+        tag_name=='OPTION' ||
+        tag_name=='BUTTON' ||
+        tag_name=='TEXTAREA')) return;
+        
       var pointer = [Event.pointerX(event), Event.pointerY(event)];
-      var offsets = Position.cumulativeOffset(this.element);
-      this.offsetX =  (pointer[0] - offsets[0]);
-      this.offsetY =  (pointer[1] - offsets[1]);
+      var pos     = Position.cumulativeOffset(this.element);
+      this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
+      
+      Draggables.activate(this);
       Event.stop(event);
     }
   },
+  
+  startDrag: function(event) {
+    this.dragging = true;
+    if(!this.delta)
+      this.delta = this.currentDelta();
+    
+    if(this.options.zindex) {
+      this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
+      this.element.style.zIndex = this.options.zindex;
+    }
+    
+    if(this.options.ghosting) {
+      this._clone = this.element.cloneNode(true);
+      this.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
+      if (!this.element._originallyAbsolute)
+        Position.absolutize(this.element);
+      this.element.parentNode.insertBefore(this._clone, this.element);
+    }
+    
+    if(this.options.scroll) {
+      if (this.options.scroll == window) {
+        var where = this._getWindowScroll(this.options.scroll);
+        this.originalScrollLeft = where.left;
+        this.originalScrollTop = where.top;
+      } else {
+        this.originalScrollLeft = this.options.scroll.scrollLeft;
+        this.originalScrollTop = this.options.scroll.scrollTop;
+      }
+    }
+    
+    Draggables.notify('onStart', this, event);
+        
+    if(this.options.starteffect) this.options.starteffect(this.element);
+  },
+  
+  updateDrag: function(event, pointer) {
+    if(!this.dragging) this.startDrag(event);
+    
+    if(!this.options.quiet){
+      Position.prepare();
+      Droppables.show(pointer, this.element);
+    }
+    
+    Draggables.notify('onDrag', this, event);
+    
+    this.draw(pointer);
+    if(this.options.change) this.options.change(this);
+    
+    if(this.options.scroll) {
+      this.stopScrolling();
+      
+      var p;
+      if (this.options.scroll == window) {
+        with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
+      } else {
+        p = Position.page(this.options.scroll);
+        p[0] += this.options.scroll.scrollLeft + Position.deltaX;
+        p[1] += this.options.scroll.scrollTop + Position.deltaY;
+        p.push(p[0]+this.options.scroll.offsetWidth);
+        p.push(p[1]+this.options.scroll.offsetHeight);
+      }
+      var speed = [0,0];
+      if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
+      if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
+      if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
+      if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
+      this.startScrolling(speed);
+    }
+    
+    // fix AppleWebKit rendering
+    if(Prototype.Browser.WebKit) window.scrollBy(0,0);
+    
+    Event.stop(event);
+  },
+  
   finishDrag: function(event, success) {
-    this.unregisterEvents();
-
-    this.active = false;
     this.dragging = false;
+    
+    if(this.options.quiet){
+      Position.prepare();
+      var pointer = [Event.pointerX(event), Event.pointerY(event)];
+      Droppables.show(pointer, this.element);
+    }
 
     if(this.options.ghosting) {
-      Position.relativize(this.element);
+      if (!this.element._originallyAbsolute)
+        Position.relativize(this.element);
+      delete this.element._originallyAbsolute;
       Element.remove(this._clone);
       this._clone = null;
     }
 
-    if(success) Droppables.fire(event, this.element);
-    Draggables.notify('onEnd', this);
+    var dropped = false; 
+    if(success) { 
+      dropped = Droppables.fire(event, this.element); 
+      if (!dropped) dropped = false; 
+    }
+    if(dropped && this.options.onDropped) this.options.onDropped(this.element);
+    Draggables.notify('onEnd', this, event);
 
     var revert = this.options.revert;
-    if(revert && typeof revert == 'function') revert = revert(this.element);
-
+    if(revert && Object.isFunction(revert)) revert = revert(this.element);
+    
+    var d = this.currentDelta();
     if(revert && this.options.reverteffect) {
-      this.options.reverteffect(this.element, 
-      this.currentTop()-this.originalTop,
-      this.currentLeft()-this.originalLeft);
+      if (dropped == 0 || revert != 'failure')
+        this.options.reverteffect(this.element,
+          d[1]-this.delta[1], d[0]-this.delta[0]);
     } else {
-      this.originalLeft = this.currentLeft();
-      this.originalTop  = this.currentTop();
+      this.delta = d;
     }
 
-    this.element.style.zIndex = this.originalZ;
+    if(this.options.zindex)
+      this.element.style.zIndex = this.originalZ;
 
     if(this.options.endeffect) 
       this.options.endeffect(this.element);
-
-
+      
+    Draggables.deactivate(this);
     Droppables.reset();
   },
+  
   keyPress: function(event) {
-    if(this.active) {
-      if(event.keyCode==Event.KEY_ESC) {
-        this.finishDrag(event, false);
-        Event.stop(event);
-      }
-    }
+    if(event.keyCode!=Event.KEY_ESC) return;
+    this.finishDrag(event, false);
+    Event.stop(event);
   },
+  
   endDrag: function(event) {
-    if(this.active && this.dragging) {
-      this.finishDrag(event, true);
-      Event.stop(event);
-    }
-    this.active = false;
-    this.dragging = false;
+    if(!this.dragging) return;
+    this.stopScrolling();
+    this.finishDrag(event, true);
+    Event.stop(event);
   },
-  draw: function(event) {
-    var pointer = [Event.pointerX(event), Event.pointerY(event)];
-    var offsets = Position.cumulativeOffset(this.element);
-    offsets[0] -= this.currentLeft();
-    offsets[1] -= this.currentTop();
+  
+  draw: function(point) {
+    var pos = Position.cumulativeOffset(this.element);
+    if(this.options.ghosting) {
+      var r   = Position.realOffset(this.element);
+      pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
+    }
+    
+    var d = this.currentDelta();
+    pos[0] -= d[0]; pos[1] -= d[1];
+    
+    if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
+      pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
+      pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
+    }
+    
+    var p = [0,1].map(function(i){ 
+      return (point[i]-pos[i]-this.offset[i]) 
+    }.bind(this));
+    
+    if(this.options.snap) {
+      if(Object.isFunction(this.options.snap)) {
+        p = this.options.snap(p[0],p[1],this);
+      } else {
+      if(Object.isArray(this.options.snap)) {
+        p = p.map( function(v, i) {
+          return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this))
+      } else {
+        p = p.map( function(v) {
+          return (v/this.options.snap).round()*this.options.snap }.bind(this))
+      }
+    }}
+    
     var style = this.element.style;
     if((!this.options.constraint) || (this.options.constraint=='horizontal'))
-      style.left = (pointer[0] - offsets[0] - this.offsetX) + "px";
+      style.left = p[0] + "px";
     if((!this.options.constraint) || (this.options.constraint=='vertical'))
-      style.top  = (pointer[1] - offsets[1] - this.offsetY) + "px";
+      style.top  = p[1] + "px";
+    
     if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
   },
-  update: function(event) {
-   if(this.active) {
-      if(!this.dragging) {
-        var style = this.element.style;
-        this.dragging = true;
-        if(style.position=="") style.position = "relative";
-        style.zIndex = this.options.zindex;
-
-        if(this.options.ghosting) {
-          this._clone = this.element.cloneNode(true);
-          Position.absolutize(this.element);
-          this.element.parentNode.insertBefore(this._clone, this.element);
+  
+  stopScrolling: function() {
+    if(this.scrollInterval) {
+      clearInterval(this.scrollInterval);
+      this.scrollInterval = null;
+      Draggables._lastScrollPointer = null;
+    }
+  },
+  
+  startScrolling: function(speed) {
+    if(!(speed[0] || speed[1])) return;
+    this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
+    this.lastScrolled = new Date();
+    this.scrollInterval = setInterval(this.scroll.bind(this), 10);
+  },
+  
+  scroll: function() {
+    var current = new Date();
+    var delta = current - this.lastScrolled;
+    this.lastScrolled = current;
+    if(this.options.scroll == window) {
+      with (this._getWindowScroll(this.options.scroll)) {
+        if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
+          var d = delta / 1000;
+          this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
         }
-
-        Draggables.notify('onStart', this);
-        if(this.options.starteffect) this.options.starteffect(this.element);
       }
-
-      Droppables.show(event, this.element);
-      this.draw(event);
-      if(this.options.change) this.options.change(this);
-
-      // fix AppleWebKit rendering
-      if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); 
-
-      Event.stop(event);
-   }
+    } else {
+      this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
+      this.options.scroll.scrollTop  += this.scrollSpeed[1] * delta / 1000;
+    }
+    
+    Position.prepare();
+    Droppables.show(Draggables._lastPointer, this.element);
+    Draggables.notify('onDrag', this);
+    if (this._isScrollChild) {
+      Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
+      Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
+      Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
+      if (Draggables._lastScrollPointer[0] < 0)
+        Draggables._lastScrollPointer[0] = 0;
+      if (Draggables._lastScrollPointer[1] < 0)
+        Draggables._lastScrollPointer[1] = 0;
+      this.draw(Draggables._lastScrollPointer);
+    }
+    
+    if(this.options.change) this.options.change(this);
+  },
+  
+  _getWindowScroll: function(w) {
+    var T, L, W, H;
+    with (w.document) {
+      if (w.document.documentElement && documentElement.scrollTop) {
+        T = documentElement.scrollTop;
+        L = documentElement.scrollLeft;
+      } else if (w.document.body) {
+        T = body.scrollTop;
+        L = body.scrollLeft;
+      }
+      if (w.innerWidth) {
+        W = w.innerWidth;
+        H = w.innerHeight;
+      } else if (w.document.documentElement && documentElement.clientWidth) {
+        W = documentElement.clientWidth;
+        H = documentElement.clientHeight;
+      } else {
+        W = body.offsetWidth;
+        H = body.offsetHeight
+      }
+    }
+    return { top: T, left: L, width: W, height: H };
   }
-}
+});
+
+Draggable._dragging = { };
 
 /*--------------------------------------------------------------------------*/
 
-var SortableObserver = Class.create();
-SortableObserver.prototype = {
+var SortableObserver = Class.create({
   initialize: function(element, observer) {
     this.element   = $(element);
     this.observer  = observer;
     this.lastValue = Sortable.serialize(this.element);
   },
+  
   onStart: function() {
     this.lastValue = Sortable.serialize(this.element);
   },
+  
   onEnd: function() {
     Sortable.unmark();
     if(this.lastValue != Sortable.serialize(this.element))
       this.observer(this.element)
   }
-}
+});
 
 var Sortable = {
-  sortables: new Array(),
-  options: function(element){
-    element = $(element);
-    for(var i=0;i<this.sortables.length;i++)
-      if(this.sortables[i].element == element)
-        return this.sortables[i];
-    return null;        
+  SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
+  
+  sortables: { },
+  
+  _findRootElement: function(element) {
+    while (element.tagName.toUpperCase() != "BODY") {  
+      if(element.id && Sortable.sortables[element.id]) return element;
+      element = element.parentNode;
+    }
   },
+
+  options: function(element) {
+    element = Sortable._findRootElement($(element));
+    if(!element) return;
+    return Sortable.sortables[element.id];
+  },
+  
   destroy: function(element){
-    element = $(element);
-    for(var i=0;i<this.sortables.length;i++) {
-      if(this.sortables[i].element == element) {
-        var s = this.sortables[i];
-        Draggables.removeObserver(s.element);
-        for(var j=0;j<s.droppables.length;j++)
-          Droppables.remove(s.droppables[j]);
-        for(j=0;j<s.draggables.length;j++)
-          s.draggables[j].destroy();
-        this.sortables.splice(i,1);
-      }
+    var s = Sortable.options(element);
+    
+    if(s) {
+      Draggables.removeObserver(s.element);
+      s.droppables.each(function(d){ Droppables.remove(d) });
+      s.draggables.invoke('destroy');
+      
+      delete Sortable.sortables[s.element.id];
     }
   },
+
   create: function(element) {
     element = $(element);
     var options = Object.extend({ 
       element:     element,
       tag:         'li',       // assumes li children, override with tag: 'tagname'
       dropOnEmpty: false,
-      tree:        false,      // fixme: unimplemented
+      tree:        false,
+      treeTag:     'ul',
       overlap:     'vertical', // one of 'vertical', 'horizontal'
       constraint:  'vertical', // one of 'vertical', 'horizontal', false
       containment: element,    // also takes array of elements (or id's); or false
       handle:      false,      // or a CSS class
       only:        false,
+      delay:       0,
       hoverclass:  null,
       ghosting:    false,
-      onChange:    function() {},
-      onUpdate:    function() {}
-    }, arguments[1] || {});
+      quiet:       false, 
+      scroll:      false,
+      scrollSensitivity: 20,
+      scrollSpeed: 15,
+      format:      this.SERIALIZE_RULE,
+      
+      // these take arrays of elements or ids and can be 
+      // used for better initialization performance
+      elements:    false,
+      handles:     false,
+      
+      onChange:    Prototype.emptyFunction,
+      onUpdate:    Prototype.emptyFunction
+    }, arguments[1] || { });
 
     // clear any old sortable with same element
     this.destroy(element);
@@ -383,9 +658,14 @@ var Sortable = {
     // build options for the draggables
     var options_for_draggable = {
       revert:      true,
+      quiet:       options.quiet,
+      scroll:      options.scroll,
+      scrollSpeed: options.scrollSpeed,
+      scrollSensitivity: options.scrollSensitivity,
+      delay:       options.delay,
       ghosting:    options.ghosting,
       constraint:  options.constraint,
-      handle:      handle };
+      handle:      options.handle };
 
     if(options.starteffect)
       options_for_draggable.starteffect = options.starteffect;
@@ -408,9 +688,16 @@ var Sortable = {
     var options_for_droppable = {
       overlap:     options.overlap,
       containment: options.containment,
+      tree:        options.tree,
       hoverclass:  options.hoverclass,
-      onHover:     Sortable.onHover,
-      greedy:      !options.dropOnEmpty
+      onHover:     Sortable.onHover
+    }
+    
+    var options_for_tree = {
+      onHover:      Sortable.onEmptyHover,
+      overlap:      options.overlap,
+      containment:  options.containment,
+      hoverclass:   options.hoverclass
     }
 
     // fix for gecko engine
@@ -419,30 +706,32 @@ var Sortable = {
     options.draggables = [];
     options.droppables = [];
 
-    // make it so
-
     // drop on empty handling
-    if(options.dropOnEmpty) {
-      Droppables.add(element,
-        {containment: options.containment, onHover: Sortable.onEmptyHover, greedy: false});
+    if(options.dropOnEmpty || options.tree) {
+      Droppables.add(element, options_for_tree);
       options.droppables.push(element);
     }
 
-    var elements = this.findElements(element, options);
-    if(elements) {
-      for (var i = 0; i < elements.length; i++) {
-        // handles are per-draggable
-        var handle = options.handle ? 
-          Element.Class.childrenWith(elements[i], options.handle)[0] : elements[i];
-            options.draggables.push(new Draggable(elements[i], Object.extend(options_for_draggable, { handle: handle })));
-            Droppables.add(elements[i], options_for_droppable);
-
-        options.droppables.push(elements[i]);
-      }
+    (options.elements || this.findElements(element, options) || []).each( function(e,i) {
+      var handle = options.handles ? $(options.handles[i]) :
+        (options.handle ? $(e).select('.' + options.handle)[0] : e); 
+      options.draggables.push(
+        new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
+      Droppables.add(e, options_for_droppable);
+      if(options.tree) e.treeNode = element;
+      options.droppables.push(e);      
+    });
+    
+    if(options.tree) {
+      (Sortable.findTreeElements(element, options) || []).each( function(e) {
+        Droppables.add(e, options_for_tree);
+        e.treeNode = element;
+        options.droppables.push(e);
+      });
     }
 
     // keep reference
-    this.sortables.push(options);
+    this.sortables[element.id] = options;
 
     // for onupdate
     Draggables.addObserver(new SortableObserver(element, options.onUpdate));
@@ -451,24 +740,21 @@ var Sortable = {
 
   // return all suitable-for-sortable elements in a guaranteed order
   findElements: function(element, options) {
-    if(!element.hasChildNodes()) return null;
-    var elements = [];
-    var children = element.childNodes;
-    for(var i = 0; i<children.length; i++) {
-      if(children[i].tagName && children[i].tagName==options.tag.toUpperCase() &&
-        (!options.only || (Element.Class.has(children[i], options.only))))
-          elements.push(children[i]);
-      if(options.tree) {
-        var grandchildren = this.findElements(children[i], options);
-        if(grandchildren) elements.push(grandchildren);
-      }
-    }
-
-    return (elements.length>0 ? elements.flatten() : null);
+    return Element.findChildren(
+      element, options.only, options.tree ? true : false, options.tag);
+  },
+  
+  findTreeElements: function(element, options) {
+    return Element.findChildren(
+      element, options.only, options.tree ? true : false, options.treeTag);
   },
 
   onHover: function(element, dropon, overlap) {
-    if(overlap>0.5) {
+    if(Element.isParent(dropon, element)) return;
+
+    if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
+      return;
+    } else if(overlap>0.5) {
       Sortable.mark(dropon, 'before');
       if(dropon.previousSibling != element) {
         var oldParentNode = element.parentNode;
@@ -491,15 +777,42 @@ var Sortable = {
       }
     }
   },
-
-  onEmptyHover: function(element, dropon) {
-    if(element.parentNode!=dropon) {
-      dropon.appendChild(element);
+  
+  onEmptyHover: function(element, dropon, overlap) {
+    var oldParentNode = element.parentNode;
+    var droponOptions = Sortable.options(dropon);
+        
+    if(!Element.isParent(dropon, element)) {
+      var index;
+      
+      var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
+      var child = null;
+            
+      if(children) {
+        var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
+        
+        for (index = 0; index < children.length; index += 1) {
+          if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
+            offset -= Element.offsetSize (children[index], droponOptions.overlap);
+          } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
+            child = index + 1 < children.length ? children[index + 1] : null;
+            break;
+          } else {
+            child = children[index];
+            break;
+          }
+        }
+      }
+      
+      dropon.insertBefore(element, child);
+      
+      Sortable.options(oldParentNode).onChange(element);
+      droponOptions.onChange(element);
     }
   },
 
   unmark: function() {
-    if(Sortable._marker) Element.hide(Sortable._marker);
+    if(Sortable._marker) Sortable._marker.hide();
   },
 
   mark: function(dropon, position) {
@@ -508,38 +821,154 @@ var Sortable = {
     if(sortable && !sortable.ghosting) return; 
 
     if(!Sortable._marker) {
-      Sortable._marker = $('dropmarker') || document.createElement('DIV');
-      Element.hide(Sortable._marker);
-      Element.Class.add(Sortable._marker, 'dropmarker');
-      Sortable._marker.style.position = 'absolute';
+      Sortable._marker = 
+        ($('dropmarker') || Element.extend(document.createElement('DIV'))).
+          hide().addClassName('dropmarker').setStyle({position:'absolute'});
       document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
     }    
     var offsets = Position.cumulativeOffset(dropon);
-    Sortable._marker.style.top  = offsets[1] + 'px';
-    if(position=='after') Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px';
-    Sortable._marker.style.left = offsets[0] + 'px';
-    Element.show(Sortable._marker);
+    Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
+    
+    if(position=='after')
+      if(sortable.overlap == 'horizontal') 
+        Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
+      else
+        Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
+    
+    Sortable._marker.show();
   },
+  
+  _tree: function(element, options, parent) {
+    var children = Sortable.findElements(element, options) || [];
+  
+    for (var i = 0; i < children.length; ++i) {
+      var match = children[i].id.match(options.format);
+
+      if (!match) continue;
+      
+      var child = {
+        id: encodeURIComponent(match ? match[1] : null),
+        element: element,
+        parent: parent,
+        children: [],
+        position: parent.children.length,
+        container: $(children[i]).down(options.treeTag)
+      }
+      
+      /* Get the element containing the children and recurse over it */
+      if (child.container)
+        this._tree(child.container, options, child)
+      
+      parent.children.push (child);
+    }
 
-  serialize: function(element) {
+    return parent; 
+  },
+
+  tree: function(element) {
     element = $(element);
     var sortableOptions = this.options(element);
     var options = Object.extend({
-      tag:  sortableOptions.tag,
+      tag: sortableOptions.tag,
+      treeTag: sortableOptions.treeTag,
       only: sortableOptions.only,
-      name: element.id
-    }, arguments[1] || {});
+      name: element.id,
+      format: sortableOptions.format
+    }, arguments[1] || { });
+    
+    var root = {
+      id: null,
+      parent: null,
+      children: [],
+      container: element,
+      position: 0
+    }
+    
+    return Sortable._tree(element, options, root);
+  },
 
-    var items = $(element).childNodes;
-    var queryComponents = new Array();
+  /* Construct a [i] index for a particular node */
+  _constructIndex: function(node) {
+    var index = '';
+    do {
+      if (node.id) index = '[' + node.position + ']' + index;
+    } while ((node = node.parent) != null);
+    return index;
+  },
 
-    for(var i=0; i<items.length; i++)
-      if(items[i].tagName && items[i].tagName==options.tag.toUpperCase() &&
-        (!options.only || (Element.Class.has(items[i], options.only))))
-        queryComponents.push(
-          encodeURIComponent(options.name) + "[]=" + 
-          encodeURIComponent(items[i].id.split("_")[1]));
+  sequence: function(element) {
+    element = $(element);
+    var options = Object.extend(this.options(element), arguments[1] || { });
+    
+    return $(this.findElements(element, options) || []).map( function(item) {
+      return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
+    });
+  },
 
-    return queryComponents.join("&");
+  setSequence: function(element, new_sequence) {
+    element = $(element);
+    var options = Object.extend(this.options(element), arguments[2] || { });
+    
+    var nodeMap = { };
+    this.findElements(element, options).each( function(n) {
+        if (n.id.match(options.format))
+            nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
+        n.parentNode.removeChild(n);
+    });
+   
+    new_sequence.each(function(ident) {
+      var n = nodeMap[ident];
+      if (n) {
+        n[1].appendChild(n[0]);
+        delete nodeMap[ident];
+      }
+    });
+  },
+  
+  serialize: function(element) {
+    element = $(element);
+    var options = Object.extend(Sortable.options(element), arguments[1] || { });
+    var name = encodeURIComponent(
+      (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
+    
+    if (options.tree) {
+      return Sortable.tree(element, arguments[1]).children.map( function (item) {
+        return [name + Sortable._constructIndex(item) + "[id]=" + 
+                encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
+      }).flatten().join('&');
+    } else {
+      return Sortable.sequence(element, arguments[1]).map( function(item) {
+        return name + "[]=" + encodeURIComponent(item);
+      }).join('&');
+    }
   }
-} 
\ No newline at end of file
+}
+
+// Returns true if child is contained within element
+Element.isParent = function(child, element) {
+  if (!child.parentNode || child == element) return false;
+  if (child.parentNode == element) return true;
+  return Element.isParent(child.parentNode, element);
+}
+
+Element.findChildren = function(element, only, recursive, tagName) {   
+  if(!element.hasChildNodes()) return null;
+  tagName = tagName.toUpperCase();
+  if(only) only = [only].flatten();
+  var elements = [];
+  $A(element.childNodes).each( function(e) {
+    if(e.tagName && e.tagName.toUpperCase()==tagName &&
+      (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
+        elements.push(e);
+    if(recursive) {
+      var grandchildren = Element.findChildren(e, only, recursive, tagName);
+      if(grandchildren) elements.push(grandchildren);
+    }
+  });
+
+  return (elements.length>0 ? elements.flatten() : []);
+}
+
+Element.offsetSize = function (element, type) {
+  return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
+}
index 71de14c477a68accb34e00286ca003c5727ca561..b8c0259f55c43d1ecf514f5eec00c4b9e35f1e66 100644 (file)
-// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
-//
-// Parts (c) 2005 Justin Palmer (http://encytemedia.com/)
-// Parts (c) 2005 Mark Pilgrim (http://diveintomark.org/)
-// 
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-// 
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
+// script.aculo.us effects.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
+
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// Contributors:
+//  Justin Palmer (http://encytemedia.com/)
+//  Mark Pilgrim (http://diveintomark.org/)
+//  Martin Bialasinki
 // 
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/ 
+
+// converts rgb() and #xxx to #xxxxxx format,  
+// returns self (or first argument) if not convertable  
+String.prototype.parseColor = function() {  
+  var color = '#';
+  if (this.slice(0,4) == 'rgb(') {  
+    var cols = this.slice(4,this.length-1).split(',');  
+    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);  
+  } else {  
+    if (this.slice(0,1) == '#') {  
+      if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();  
+      if (this.length==7) color = this.toLowerCase();  
+    }  
+  }  
+  return (color.length==7 ? color : (arguments[0] || this));  
+};
+
+/*--------------------------------------------------------------------------*/
+
+Element.collectTextNodes = function(element) {  
+  return $A($(element).childNodes).collect( function(node) {
+    return (node.nodeType==3 ? node.nodeValue : 
+      (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
+  }).flatten().join('');
+};
+
+Element.collectTextNodesIgnoreClass = function(element, className) {  
+  return $A($(element).childNodes).collect( function(node) {
+    return (node.nodeType==3 ? node.nodeValue : 
+      ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? 
+        Element.collectTextNodesIgnoreClass(node, className) : ''));
+  }).flatten().join('');
+};
+
+Element.setContentZoom = function(element, percent) {
+  element = $(element);  
+  element.setStyle({fontSize: (percent/100) + 'em'});   
+  if (Prototype.Browser.WebKit) window.scrollBy(0,0);
+  return element;
+};
+
+Element.getInlineOpacity = function(element){
+  return $(element).style.opacity || '';
+};
+
+Element.forceRerendering = function(element) {
+  try {
+    element = $(element);
+    var n = document.createTextNode(' ');
+    element.appendChild(n);
+    element.removeChild(n);
+  } catch(e) { }
+};
+
+/*--------------------------------------------------------------------------*/
 
 var Effect = {
+  _elementDoesNotExistError: {
+    name: 'ElementDoesNotExistError',
+    message: 'The specified DOM element does not exist, but is required for this effect to operate'
+  },
+  Transitions: {
+    linear: Prototype.K,
+    sinoidal: function(pos) {
+      return (-Math.cos(pos*Math.PI)/2) + 0.5;
+    },
+    reverse: function(pos) {
+      return 1-pos;
+    },
+    flicker: function(pos) {
+      var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
+      return pos > 1 ? 1 : pos;
+    },
+    wobble: function(pos) {
+      return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
+    },
+    pulse: function(pos, pulses) { 
+      pulses = pulses || 5; 
+      return (
+        ((pos % (1/pulses)) * pulses).round() == 0 ? 
+              ((pos * pulses * 2) - (pos * pulses * 2).floor()) : 
+          1 - ((pos * pulses * 2) - (pos * pulses * 2).floor())
+        );
+    },
+    spring: function(pos) { 
+      return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); 
+    },
+    none: function(pos) {
+      return 0;
+    },
+    full: function(pos) {
+      return 1;
+    }
+  },
+  DefaultOptions: {
+    duration:   1.0,   // seconds
+    fps:        100,   // 100= assume 66fps max.
+    sync:       false, // true for combining
+    from:       0.0,
+    to:         1.0,
+    delay:      0.0,
+    queue:      'parallel'
+  },
   tagifyText: function(element) {
-    var tagifyStyle = "position:relative";
-    if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ";zoom:1"; 
+    var tagifyStyle = 'position:relative';
+    if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';
+    
     element = $(element);
-    var children = element.childNodes;
-    for (var i = 0; i < children.length; i++)
-      if(children[i].nodeType==3) {
-        var child = children[i];
-        for (var j = 0; j < child.nodeValue.length; j++)
+    $A(element.childNodes).each( function(child) {
+      if (child.nodeType==3) {
+        child.nodeValue.toArray().each( function(character) {
           element.insertBefore(
-            Builder.node('span',{style: tagifyStyle},
-              child.nodeValue.substr(j,1) == " " ? String.fromCharCode(160) : 
-              child.nodeValue.substr(j,1)), child);
+            new Element('span', {style: tagifyStyle}).update(
+              character == ' ' ? String.fromCharCode(160) : character), 
+              child);
+        });
         Element.remove(child);
       }
+    });
   },
   multiple: function(element, effect) {
-    if(((typeof element == 'object') || 
-        (typeof element == 'function')) && 
+    var elements;
+    if (((typeof element == 'object') || 
+        Object.isFunction(element)) && 
        (element.length))
-      var elements = element;
+      elements = element;
     else
-      var elements = $(element).childNodes;
+      elements = $(element).childNodes;
       
     var options = Object.extend({
       speed: 0.1,
       delay: 0.0
-    }, arguments[2] || {});
-    var speed = options.speed;
-    var delay = options.delay;
+    }, arguments[2] || { });
+    var masterDelay = options.delay;
 
-    for(var i = 0; i < elements.length; i++)
-      new effect(elements[i], 
-        Object.extend(options, { delay: delay + i*speed }));
+    $A(elements).each( function(element, index) {
+      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
+    });
+  },
+  PAIRS: {
+    'slide':  ['SlideDown','SlideUp'],
+    'blind':  ['BlindDown','BlindUp'],
+    'appear': ['Appear','Fade']
+  },
+  toggle: function(element, effect) {
+    element = $(element);
+    effect = (effect || 'appear').toLowerCase();
+    var options = Object.extend({
+      queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
+    }, arguments[2] || { });
+    Effect[element.visible() ? 
+      Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
   }
 };
 
-var Effect2 = Effect; // deprecated
-
-/* ------------- transitions ------------- */
-
-Effect.Transitions = {}
-
-Effect.Transitions.linear = function(pos) {
-  return pos;
-}
-Effect.Transitions.sinoidal = function(pos) {
-  return (-Math.cos(pos*Math.PI)/2) + 0.5;
-}
-Effect.Transitions.reverse  = function(pos) {
-  return 1-pos;
-}
-Effect.Transitions.flicker = function(pos) {
-  return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random(0.25);
-}
-Effect.Transitions.wobble = function(pos) {
-  return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
-}
-Effect.Transitions.pulse = function(pos) {
-  return (Math.floor(pos*10) % 2 == 0 ? 
-    (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10)));
-}
-Effect.Transitions.none = function(pos) {
-  return 0;
-}
-Effect.Transitions.full = function(pos) {
-  return 1;
-}
+Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;
 
 /* ------------- core effects ------------- */
 
-Effect.Queue = {
-  effects:  [],
-  interval: null,
-  findLast: function() {
-    var timestamp = false;
-    for(var i = 0; i < this.effects.length; i++)
-      if(!timestamp || (this.effects[i].finishOn>timestamp))
-        timestamp = this.effects[i].finishOn;
-    return timestamp;
+Effect.ScopedQueue = Class.create(Enumerable, {
+  initialize: function() {
+    this.effects  = [];
+    this.interval = null;    
+  },
+  _each: function(iterator) {
+    this.effects._each(iterator);
   },
   add: function(effect) {
     var timestamp = new Date().getTime();
     
-    switch(effect.options.queue) {
+    var position = Object.isString(effect.options.queue) ? 
+      effect.options.queue : effect.options.queue.position;
+    
+    switch(position) {
       case 'front':
         // move unstarted effects after this effect  
-        for(var i = 0; i < this.effects.length; i++)
-          if(this.effects[i].state == 'idle') {
-            this.effects[i].startOn  += effect.finishOn;
-            this.effects[i].finishOn += effect.finishOn;
-          }
+        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
+            e.startOn  += effect.finishOn;
+            e.finishOn += effect.finishOn;
+          });
+        break;
+      case 'with-last':
+        timestamp = this.effects.pluck('startOn').max() || timestamp;
         break;
       case 'end':
         // start effect after last queued effect has finished
-        timestamp = this.findLast() || timestamp;
+        timestamp = this.effects.pluck('finishOn').max() || timestamp;
         break;
     }
     
     effect.startOn  += timestamp;
     effect.finishOn += timestamp;
+
+    if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
+      this.effects.push(effect);
     
-    this.effects.push(effect);
-    
-    if(!this.interval) 
-      this.interval = setInterval(this.loop.bind(this), 40);
+    if (!this.interval)
+      this.interval = setInterval(this.loop.bind(this), 15);
   },
   remove: function(effect) {
-    for(var i = 0; i < this.effects.length; i++)
-      if(this.effects[i]==effect) this.effects.splice(i,1);
-    if(this.effects.length == 0) {
+    this.effects = this.effects.reject(function(e) { return e==effect });
+    if (this.effects.length == 0) {
       clearInterval(this.interval);
       this.interval = null;
     }
   },
   loop: function() {
     var timePos = new Date().getTime();
-    for(var i = 0; i < this.effects.length; i++) {
-      this.effects[i].loop(timePos);
-    }
+    for(var i=0, len=this.effects.length;i<len;i++) 
+      this.effects[i] && this.effects[i].loop(timePos);
   }
-}
-
-Effect.Base = function() {};
-Effect.Base.prototype = {
-  setOptions: function(options) {
-    this.options = Object.extend({
-      transition: Effect.Transitions.sinoidal,
-      duration:   1.0,   // seconds
-      fps:        25.0,  // max. 25fps due to Effect.Queue implementation
-      sync:       false, // true for combining
-      from:       0.0,
-      to:         1.0,
-      delay:      0.0,
-      queue:      'parallel'
-    }, options || {});
-  },
+});
+
+Effect.Queues = {
+  instances: $H(),
+  get: function(queueName) {
+    if (!Object.isString(queueName)) return queueName;
+    
+    return this.instances.get(queueName) ||
+      this.instances.set(queueName, new Effect.ScopedQueue());
+  }
+};
+Effect.Queue = Effect.Queues.get('global');
+
+Effect.Base = Class.create({
+  position: null,
   start: function(options) {
-    this.setOptions(options || {});
+    function codeForEvent(options,eventName){
+      return (
+        (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
+        (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
+      );
+    }
+    if (options && options.transition === false) options.transition = Effect.Transitions.linear;
+    this.options      = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
     this.currentFrame = 0;
     this.state        = 'idle';
     this.startOn      = this.options.delay*1000;
-    this.finishOn     = this.startOn + (this.options.duration*1000);
-    if(this.options.beforeStart) this.options.beforeStart(this);
-    if(!this.options.sync) Effect.Queue.add(this);
+    this.finishOn     = this.startOn+(this.options.duration*1000);
+    this.fromToDelta  = this.options.to-this.options.from;
+    this.totalTime    = this.finishOn-this.startOn;
+    this.totalFrames  = this.options.fps*this.options.duration;
+    
+    eval('this.render = function(pos){ '+
+      'if (this.state=="idle"){this.state="running";'+
+      codeForEvent(this.options,'beforeSetup')+
+      (this.setup ? 'this.setup();':'')+ 
+      codeForEvent(this.options,'afterSetup')+
+      '};if (this.state=="running"){'+
+      'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+
+      'this.position=pos;'+
+      codeForEvent(this.options,'beforeUpdate')+
+      (this.update ? 'this.update(pos);':'')+
+      codeForEvent(this.options,'afterUpdate')+
+      '}}');
+    
+    this.event('beforeStart');
+    if (!this.options.sync)
+      Effect.Queues.get(Object.isString(this.options.queue) ? 
+        'global' : this.options.queue.scope).add(this);
   },
   loop: function(timePos) {
-    if(timePos >= this.startOn) {
-      if(timePos >= this.finishOn) {
+    if (timePos >= this.startOn) {
+      if (timePos >= this.finishOn) {
         this.render(1.0);
         this.cancel();
-        if(this.finish) this.finish(); 
-        if(this.options.afterFinish) this.options.afterFinish(this);
+        this.event('beforeFinish');
+        if (this.finish) this.finish(); 
+        this.event('afterFinish');
         return;  
       }
-      var pos   = (timePos - this.startOn) / (this.finishOn - this.startOn);
-      var frame = Math.round(pos * this.options.fps * this.options.duration);
-      if(frame > this.currentFrame) {
+      var pos   = (timePos - this.startOn) / this.totalTime,
+          frame = (pos * this.totalFrames).round();
+      if (frame > this.currentFrame) {
         this.render(pos);
         this.currentFrame = frame;
       }
     }
   },
-  render: function(pos) {
-    if(this.state == 'idle') {
-      this.state = 'running';
-      if(this.setup) this.setup();
-    }
-    if(this.options.transition) pos = this.options.transition(pos);
-    pos *= (this.options.to-this.options.from);
-    pos += this.options.from; 
-    if(this.options.beforeUpdate) this.options.beforeUpdate(this);
-    if(this.update) this.update(pos);
-    if(this.options.afterUpdate) this.options.afterUpdate(this);  
-  },
   cancel: function() {
-    if(!this.options.sync) Effect.Queue.remove(this);
+    if (!this.options.sync)
+      Effect.Queues.get(Object.isString(this.options.queue) ? 
+        'global' : this.options.queue.scope).remove(this);
     this.state = 'finished';
+  },
+  event: function(eventName) {
+    if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
+    if (this.options[eventName]) this.options[eventName](this);
+  },
+  inspect: function() {
+    var data = $H();
+    for(property in this)
+      if (!Object.isFunction(this[property])) data.set(property, this[property]);
+    return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
   }
-}
+});
 
-Effect.Parallel = Class.create();
-Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
+Effect.Parallel = Class.create(Effect.Base, {
   initialize: function(effects) {
     this.effects = effects || [];
     this.start(arguments[1]);
   },
   update: function(position) {
-    for (var i = 0; i < this.effects.length; i++)
-      this.effects[i].render(position);
+    this.effects.invoke('render', position);
   },
   finish: function(position) {
-    for (var i = 0; i < this.effects.length; i++) {
-      this.effects[i].cancel();
-      if(this.effects[i].finish) this.effects[i].finish(position);
-    }
+    this.effects.each( function(effect) {
+      effect.render(1.0);
+      effect.cancel();
+      effect.event('beforeFinish');
+      if (effect.finish) effect.finish(position);
+      effect.event('afterFinish');
+    });
   }
 });
 
-// Internet Explorer caveat: works only on elements that have
-// a 'layout', meaning having a given width or height. 
-// There is no way to safely set this automatically.
-Effect.Opacity = Class.create();
-Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
+Effect.Tween = Class.create(Effect.Base, {
+  initialize: function(object, from, to) {
+    object = Object.isString(object) ? $(object) : object;
+    var args = $A(arguments), method = args.last(), 
+      options = args.length == 5 ? args[3] : null;
+    this.method = Object.isFunction(method) ? method.bind(object) :
+      Object.isFunction(object[method]) ? object[method].bind(object) : 
+      function(value) { object[method] = value };
+    this.start(Object.extend({ from: from, to: to }, options || { }));
+  },
+  update: function(position) {
+    this.method(position);
+  }
+});
+
+Effect.Event = Class.create(Effect.Base, {
+  initialize: function() {
+    this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
+  },
+  update: Prototype.emptyFunction
+});
+
+Effect.Opacity = Class.create(Effect.Base, {
   initialize: function(element) {
     this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    // make this work on IE on elements without 'layout'
+    if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
+      this.element.setStyle({zoom: 1});
     var options = Object.extend({
-      from: 0.0,
+      from: this.element.getOpacity() || 0.0,
       to:   1.0
-    }, arguments[1] || {});
+    }, arguments[1] || { });
     this.start(options);
   },
   update: function(position) {
-    this.setOpacity(position);
-  }, 
-  setOpacity: function(opacity) {
-    if(opacity<0.0001) opacity = 0; // fix errors with things like 6.152242992829571e-8
-    if(opacity==1.0) {
-      this.element.style.opacity = '0.999999';
-      this.element.style.filter  = null;
-    } else {
-      this.element.style.opacity = opacity;
-      this.element.style.filter  = "alpha(opacity:"+opacity*100+")";
-    }
+    this.element.setOpacity(position);
   }
 });
 
-Effect.MoveBy = Class.create();
-Object.extend(Object.extend(Effect.MoveBy.prototype, Effect.Base.prototype), {
-  initialize: function(element, toTop, toLeft) {
-    this.element      = $(element);
-    this.toTop        = toTop;
-    this.toLeft       = toLeft;
-    this.start(arguments[3]);
+Effect.Move = Class.create(Effect.Base, {
+  initialize: function(element) {
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    var options = Object.extend({
+      x:    0,
+      y:    0,
+      mode: 'relative'
+    }, arguments[1] || { });
+    this.start(options);
   },
   setup: function() {
-    this.originalTop  = parseFloat(Element.getStyle(this.element,'top')  || '0');
-    this.originalLeft = parseFloat(Element.getStyle(this.element,'left') || '0');
-    Element.makePositioned(this.element);
+    this.element.makePositioned();
+    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
+    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');
+    if (this.options.mode == 'absolute') {
+      this.options.x = this.options.x - this.originalLeft;
+      this.options.y = this.options.y - this.originalTop;
+    }
   },
   update: function(position) {
-    topd  = this.toTop  * position + this.originalTop;
-    leftd = this.toLeft * position + this.originalLeft;
-    this.setPosition(topd, leftd);
-  },
-  setPosition: function(topd, leftd) {
-    this.element.style.top  = topd  + "px";
-    this.element.style.left = leftd + "px";
+    this.element.setStyle({
+      left: (this.options.x  * position + this.originalLeft).round() + 'px',
+      top:  (this.options.y  * position + this.originalTop).round()  + 'px'
+    });
   }
 });
 
-Effect.Scale = Class.create();
-Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
+// for backwards compatibility
+Effect.MoveBy = function(element, toTop, toLeft) {
+  return new Effect.Move(element, 
+    Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
+};
+
+Effect.Scale = Class.create(Effect.Base, {
   initialize: function(element, percent) {
-    this.element = $(element)
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
     var options = Object.extend({
       scaleX: true,
       scaleY: true,
       scaleContent: true,
       scaleFromCenter: false,
-      scaleMode: 'box',        // 'box' or 'contents' or {} with provided values
+      scaleMode: 'box',        // 'box' or 'contents' or { } with provided values
       scaleFrom: 100.0,
       scaleTo:   percent
-    }, arguments[2] || {});
+    }, arguments[2] || { });
     this.start(options);
   },
   setup: function() {
-    this.originalTop    = this.element.offsetTop;
-    this.originalLeft   = this.element.offsetLeft;
-    if(Element.getStyle(this.element,'font-size')=="") this.sizeEm = 1.0;
-    if(Element.getStyle(this.element,'font-size') && Element.getStyle(this.element,'font-size').indexOf("em")>0)
-      this.sizeEm = parseFloat(Element.getStyle(this.element,'font-size'));
-    this.factor = (this.options.scaleTo/100.0) - (this.options.scaleFrom/100.0);
-    if(this.options.scaleMode=='box') {
-      this.originalHeight = this.element.clientHeight;
-      this.originalWidth  = this.element.clientWidth; 
-    } else 
-    if(this.options.scaleMode=='contents') {
-      this.originalHeight = this.element.scrollHeight;
-      this.originalWidth  = this.element.scrollWidth;
-    } else {
-      this.originalHeight = this.options.scaleMode.originalHeight;
-      this.originalWidth  = this.options.scaleMode.originalWidth;
-    }
+    this.restoreAfterFinish = this.options.restoreAfterFinish || false;
+    this.elementPositioning = this.element.getStyle('position');
+    
+    this.originalStyle = { };
+    ['top','left','width','height','fontSize'].each( function(k) {
+      this.originalStyle[k] = this.element.style[k];
+    }.bind(this));
+      
+    this.originalTop  = this.element.offsetTop;
+    this.originalLeft = this.element.offsetLeft;
+    
+    var fontSize = this.element.getStyle('font-size') || '100%';
+    ['em','px','%','pt'].each( function(fontSizeType) {
+      if (fontSize.indexOf(fontSizeType)>0) {
+        this.fontSize     = parseFloat(fontSize);
+        this.fontSizeType = fontSizeType;
+      }
+    }.bind(this));
+    
+    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
+    
+    this.dims = null;
+    if (this.options.scaleMode=='box')
+      this.dims = [this.element.offsetHeight, this.element.offsetWidth];
+    if (/^content/.test(this.options.scaleMode))
+      this.dims = [this.element.scrollHeight, this.element.scrollWidth];
+    if (!this.dims)
+      this.dims = [this.options.scaleMode.originalHeight,
+                   this.options.scaleMode.originalWidth];
   },
   update: function(position) {
     var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
-    if(this.options.scaleContent && this.sizeEm) 
-      this.element.style.fontSize = this.sizeEm*currentScale + "em";
-    this.setDimensions(
-      this.originalWidth * currentScale, 
-      this.originalHeight * currentScale);
-  },
-  setDimensions: function(width, height) {
-    if(this.options.scaleX) this.element.style.width = width + 'px';
-    if(this.options.scaleY) this.element.style.height = height + 'px';
-    if(this.options.scaleFromCenter) {
-      var topd  = (height - this.originalHeight)/2;
-      var leftd = (width  - this.originalWidth)/2;
-      if(Element.getStyle(this.element,'position')=='absolute') {
-        if(this.options.scaleY) this.element.style.top = this.originalTop-topd + "px";
-        if(this.options.scaleX) this.element.style.left = this.originalLeft-leftd + "px";
+    if (this.options.scaleContent && this.fontSize)
+      this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
+    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
+  },
+  finish: function(position) {
+    if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
+  },
+  setDimensions: function(height, width) {
+    var d = { };
+    if (this.options.scaleX) d.width = width.round() + 'px';
+    if (this.options.scaleY) d.height = height.round() + 'px';
+    if (this.options.scaleFromCenter) {
+      var topd  = (height - this.dims[0])/2;
+      var leftd = (width  - this.dims[1])/2;
+      if (this.elementPositioning == 'absolute') {
+        if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
+        if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
       } else {
-        if(this.options.scaleY) this.element.style.top = -topd + "px";
-        if(this.options.scaleX) this.element.style.left = -leftd + "px";
+        if (this.options.scaleY) d.top = -topd + 'px';
+        if (this.options.scaleX) d.left = -leftd + 'px';
       }
     }
+    this.element.setStyle(d);
   }
 });
 
-Effect.Highlight = Class.create();
-Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
+Effect.Highlight = Class.create(Effect.Base, {
   initialize: function(element) {
     this.element = $(element);
-    var options = Object.extend({
-      startcolor:   "#ffff99"
-    }, arguments[1] || {});
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
     this.start(options);
   },
   setup: function() {
-    // try to parse current background color as default for endcolor
-    // browser stores this as: "rgb(255, 255, 255)", convert to "#ffffff" format
-    if(!this.options.endcolor) {
-      var endcolor = "#ffffff";
-      var current = Element.getStyle(this.element, 'background-color');
-      if(current && current.slice(0,4) == "rgb(") {
-        endcolor = "#";
-        var cols = current.slice(4,current.length-1).split(',');
-        var i=0; do { endcolor += parseInt(cols[i]).toColorPart() } while (++i<3);
-      }
-      this.options.endcolor = endcolor;
-    }    
+    // Prevent executing on elements not in the layout flow
+    if (this.element.getStyle('display')=='none') { this.cancel(); return; }
+    // Disable background image during the effect
+    this.oldStyle = { };
+    if (!this.options.keepBackgroundImage) {
+      this.oldStyle.backgroundImage = this.element.getStyle('background-image');
+      this.element.setStyle({backgroundImage: 'none'});
+    }
+    if (!this.options.endcolor)
+      this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
+    if (!this.options.restorecolor)
+      this.options.restorecolor = this.element.getStyle('background-color');
     // init color calculations
-    this.colors_base = [
-      parseInt(this.options.startcolor.slice(1,3),16),
-      parseInt(this.options.startcolor.slice(3,5),16),
-      parseInt(this.options.startcolor.slice(5),16) ];
-    this.colors_delta = [
-      parseInt(this.options.endcolor.slice(1,3),16)-this.colors_base[0],
-      parseInt(this.options.endcolor.slice(3,5),16)-this.colors_base[1],
-      parseInt(this.options.endcolor.slice(5),16)-this.colors_base[2]];
+    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
+    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
   },
   update: function(position) {
-    var colors = [
-      Math.round(this.colors_base[0]+(this.colors_delta[0]*position)),
-      Math.round(this.colors_base[1]+(this.colors_delta[1]*position)),
-      Math.round(this.colors_base[2]+(this.colors_delta[2]*position)) ];
-    this.element.style.backgroundColor = "#" +
-      colors[0].toColorPart() + colors[1].toColorPart() + colors[2].toColorPart();
+    this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
+      return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
   },
   finish: function() {
-    this.element.style.backgroundColor = this.options.restorecolor;
+    this.element.setStyle(Object.extend(this.oldStyle, {
+      backgroundColor: this.options.restorecolor
+    }));
   }
 });
 
-Effect.ScrollTo = Class.create();
-Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
-  initialize: function(element) {
-    this.element = $(element);
-    this.start(arguments[1] || {});
-  },
-  setup: function() {
-    Position.prepare();
-    var offsets = Position.cumulativeOffset(this.element);
-    var max = window.innerHeight ? 
-      window.height - window.innerHeight :
-      document.body.scrollHeight - 
-        (document.documentElement.clientHeight ? 
-          document.documentElement.clientHeight : document.body.clientHeight);
-    this.scrollStart = Position.deltaY;
-    this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
-  },
-  update: function(position) {
-    Position.prepare();
-    window.scrollTo(Position.deltaX, 
-      this.scrollStart + (position*this.delta));
-  }
-});
+Effect.ScrollTo = function(element) {
+  var options = arguments[1] || { },
+    scrollOffsets = document.viewport.getScrollOffsets(),
+    elementOffsets = $(element).cumulativeOffset(),
+    max = (window.height || document.body.scrollHeight) - document.viewport.getHeight();  
+
+  if (options.offset) elementOffsets[1] += options.offset;
+
+  return new Effect.Tween(null,
+    scrollOffsets.top,
+    elementOffsets[1] > max ? max : elementOffsets[1],
+    options,
+    function(p){ scrollTo(scrollOffsets.left, p.round()) }
+  );
+};
 
 /* ------------- combination effects ------------- */
 
 Effect.Fade = function(element) {
+  element = $(element);
+  var oldOpacity = element.getInlineOpacity();
   var options = Object.extend({
-  from: 1.0,
-  to:   0.0,
-  afterFinish: function(effect) 
-    { Element.hide(effect.element);
-      effect.setOpacity(1); } 
-  }, arguments[1] || {});
+    from: element.getOpacity() || 1.0,
+    to:   0.0,
+    afterFinishInternal: function(effect) { 
+      if (effect.options.to!=0) return;
+      effect.element.hide().setStyle({opacity: oldOpacity}); 
+    }
+  }, arguments[1] || { });
   return new Effect.Opacity(element,options);
-}
+};
 
 Effect.Appear = function(element) {
+  element = $(element);
   var options = Object.extend({
-  from: 0.0,
+  from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
   to:   1.0,
-  beforeStart: function(effect)  
-    { effect.setOpacity(0);
-      Element.show(effect.element); },
-  afterUpdate: function(effect)  
-    { Element.show(effect.element); }
-  }, arguments[1] || {});
+  // force Safari to render floated elements properly
+  afterFinishInternal: function(effect) {
+    effect.element.forceRerendering();
+  },
+  beforeSetup: function(effect) {
+    effect.element.setOpacity(effect.options.from).show(); 
+  }}, arguments[1] || { });
   return new Effect.Opacity(element,options);
-}
+};
 
 Effect.Puff = function(element) {
+  element = $(element);
+  var oldStyle = { 
+    opacity: element.getInlineOpacity(), 
+    position: element.getStyle('position'),
+    top:  element.style.top,
+    left: element.style.left,
+    width: element.style.width,
+    height: element.style.height
+  };
   return new Effect.Parallel(
-   [ new Effect.Scale(element, 200, { sync: true, scaleFromCenter: true }), 
-     new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ], 
+   [ new Effect.Scale(element, 200, 
+      { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), 
+     new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], 
      Object.extend({ duration: 1.0, 
-      beforeUpdate: function(effect) 
-       { effect.effects[0].element.style.position = 'absolute'; },
-      afterFinish: function(effect)
-       { Element.hide(effect.effects[0].element); }
-     }, arguments[1] || {})
+      beforeSetupInternal: function(effect) {
+        Position.absolutize(effect.effects[0].element)
+      },
+      afterFinishInternal: function(effect) {
+         effect.effects[0].element.hide().setStyle(oldStyle); }
+     }, arguments[1] || { })
    );
-}
+};
 
 Effect.BlindUp = function(element) {
   element = $(element);
-  Element.makeClipping(element);
-  return new Effect.Scale(element, 0, 
+  element.makeClipping();
+  return new Effect.Scale(element, 0,
     Object.extend({ scaleContent: false, 
       scaleX: false, 
-      afterFinish: function(effect) 
-        { 
-          Element.hide(effect.element);
-          Element.undoClipping(effect.element);
-        } 
-    }, arguments[1] || {})
+      restoreAfterFinish: true,
+      afterFinishInternal: function(effect) {
+        effect.element.hide().undoClipping();
+      } 
+    }, arguments[1] || { })
   );
-}
+};
 
 Effect.BlindDown = function(element) {
   element = $(element);
-  element.style.height = '0px';
-  Element.makeClipping(element);
-  Element.show(element);
-  return new Effect.Scale(element, 100, 
-    Object.extend({ scaleContent: false, 
-      scaleX: false, 
-      scaleMode: 'contents',
-      scaleFrom: 0,
-      afterFinish: function(effect) {
-        Element.undoClipping(effect.element);
-      }
-    }, arguments[1] || {})
-  );
-}
+  var elementDimensions = element.getDimensions();
+  return new Effect.Scale(element, 100, Object.extend({ 
+    scaleContent: false, 
+    scaleX: false,
+    scaleFrom: 0,
+    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+    restoreAfterFinish: true,
+    afterSetup: function(effect) {
+      effect.element.makeClipping().setStyle({height: '0px'}).show(); 
+    },  
+    afterFinishInternal: function(effect) {
+      effect.element.undoClipping();
+    }
+  }, arguments[1] || { }));
+};
 
 Effect.SwitchOff = function(element) {
-  return new Effect.Appear(element,
-    { duration: 0.4,
-     transition: Effect.Transitions.flicker,
-     afterFinish: function(effect)
-      { effect.element.style.overflow = 'hidden';
-        new Effect.Scale(effect.element, 1, 
-         { duration: 0.3, scaleFromCenter: true,
-          scaleX: false, scaleContent: false,
-          afterUpdate: function(effect) { 
-           if(effect.element.style.position=="")
-             effect.element.style.position = 'relative'; },
-          afterFinish: function(effect) { Element.hide(effect.element); }
-         } )
-      }
-    } );
-}
+  element = $(element);
+  var oldOpacity = element.getInlineOpacity();
+  return new Effect.Appear(element, Object.extend({
+    duration: 0.4,
+    from: 0,
+    transition: Effect.Transitions.flicker,
+    afterFinishInternal: function(effect) {
+      new Effect.Scale(effect.element, 1, { 
+        duration: 0.3, scaleFromCenter: true,
+        scaleX: false, scaleContent: false, restoreAfterFinish: true,
+        beforeSetup: function(effect) { 
+          effect.element.makePositioned().makeClipping();
+        },
+        afterFinishInternal: function(effect) {
+          effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
+        }
+      })
+    }
+  }, arguments[1] || { }));
+};
 
 Effect.DropOut = function(element) {
+  element = $(element);
+  var oldStyle = {
+    top: element.getStyle('top'),
+    left: element.getStyle('left'),
+    opacity: element.getInlineOpacity() };
   return new Effect.Parallel(
-    [ new Effect.MoveBy(element, 100, 0, { sync: true }), 
-      new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ], 
+    [ new Effect.Move(element, {x: 0, y: 100, sync: true }), 
+      new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
     Object.extend(
-      { duration: 0.5, 
-        afterFinish: function(effect)
-          { Element.hide(effect.effects[0].element); } 
-      }, arguments[1] || {}));
-}
+      { duration: 0.5,
+        beforeSetup: function(effect) {
+          effect.effects[0].element.makePositioned(); 
+        },
+        afterFinishInternal: function(effect) {
+          effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
+        } 
+      }, arguments[1] || { }));
+};
 
 Effect.Shake = function(element) {
-  return new Effect.MoveBy(element, 0, 20, 
-    { duration: 0.05, afterFinish: function(effect) {
-  new Effect.MoveBy(effect.element, 0, -40, 
-    { duration: 0.1, afterFinish: function(effect) { 
-  new Effect.MoveBy(effect.element, 0, 40, 
-    { duration: 0.1, afterFinish: function(effect) {  
-  new Effect.MoveBy(effect.element, 0, -40, 
-    { duration: 0.1, afterFinish: function(effect) {  
-  new Effect.MoveBy(effect.element, 0, 40, 
-    { duration: 0.1, afterFinish: function(effect) {  
-  new Effect.MoveBy(effect.element, 0, -20, 
-    { duration: 0.05, afterFinish: function(effect) {  
+  element = $(element);
+  var options = Object.extend({
+    distance: 20,
+    duration: 0.5
+  }, arguments[1] || {});
+  var distance = parseFloat(options.distance);
+  var split = parseFloat(options.duration) / 10.0;
+  var oldStyle = {
+    top: element.getStyle('top'),
+    left: element.getStyle('left') };
+    return new Effect.Move(element,
+      { x:  distance, y: 0, duration: split, afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
+        effect.element.undoPositioned().setStyle(oldStyle);
   }}) }}) }}) }}) }}) }});
-}
+};
 
 Effect.SlideDown = function(element) {
-  element = $(element);
-  element.style.height   = '0px';
-  Element.makeClipping(element);
-  Element.cleanWhitespace(element);
-  Element.makePositioned(element.firstChild);
-  Element.show(element);
-  return new Effect.Scale(element, 100, 
-   Object.extend({ scaleContent: false, 
+  element = $(element).cleanWhitespace();
+  // SlideDown need to have the content of the element wrapped in a container element with fixed height!
+  var oldInnerBottom = element.down().getStyle('bottom');
+  var elementDimensions = element.getDimensions();
+  return new Effect.Scale(element, 100, Object.extend({ 
+    scaleContent: false, 
     scaleX: false, 
-    scaleMode: 'contents',
-    scaleFrom: 0,
-    afterUpdate: function(effect) 
-      { effect.element.firstChild.style.bottom = 
-          (effect.originalHeight - effect.element.clientHeight) + 'px'; },
-    afterFinish: function(effect) 
-      {  Element.undoClipping(effect.element); }
-    }, arguments[1] || {})
+    scaleFrom: window.opera ? 0 : 1,
+    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+    restoreAfterFinish: true,
+    afterSetup: function(effect) {
+      effect.element.makePositioned();
+      effect.element.down().makePositioned();
+      if (window.opera) effect.element.setStyle({top: ''});
+      effect.element.makeClipping().setStyle({height: '0px'}).show(); 
+    },
+    afterUpdateInternal: function(effect) {
+      effect.element.down().setStyle({bottom:
+        (effect.dims[0] - effect.element.clientHeight) + 'px' }); 
+    },
+    afterFinishInternal: function(effect) {
+      effect.element.undoClipping().undoPositioned();
+      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
+    }, arguments[1] || { })
   );
-}
-  
+};
+
 Effect.SlideUp = function(element) {
-  element = $(element);
-  Element.makeClipping(element);
-  Element.cleanWhitespace(element);
-  Element.makePositioned(element.firstChild);
-  Element.show(element);
-  return new Effect.Scale(element, 0, 
+  element = $(element).cleanWhitespace();
+  var oldInnerBottom = element.down().getStyle('bottom');
+  var elementDimensions = element.getDimensions();
+  return new Effect.Scale(element, window.opera ? 0 : 1,
    Object.extend({ scaleContent: false, 
     scaleX: false, 
-    afterUpdate: function(effect) 
-      { effect.element.firstChild.style.bottom = 
-          (effect.originalHeight - effect.element.clientHeight) + 'px'; },
-    afterFinish: function(effect)
-      { 
-        Element.hide(effect.element);
-        Element.undoClipping(effect.element);
-      }
-   }, arguments[1] || {})
+    scaleMode: 'box',
+    scaleFrom: 100,
+    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+    restoreAfterFinish: true,
+    afterSetup: function(effect) {
+      effect.element.makePositioned();
+      effect.element.down().makePositioned();
+      if (window.opera) effect.element.setStyle({top: ''});
+      effect.element.makeClipping().show();
+    },  
+    afterUpdateInternal: function(effect) {
+      effect.element.down().setStyle({bottom:
+        (effect.dims[0] - effect.element.clientHeight) + 'px' });
+    },
+    afterFinishInternal: function(effect) {
+      effect.element.hide().undoClipping().undoPositioned();
+      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
+    }
+   }, arguments[1] || { })
   );
-}
+};
 
+// Bug in opera makes the TD containing this element expand for a instance after finish 
 Effect.Squish = function(element) {
- return new Effect.Scale(element, 0, 
-   { afterFinish: function(effect) { Element.hide(effect.element); } });
-}
+  return new Effect.Scale(element, window.opera ? 1 : 0, { 
+    restoreAfterFinish: true,
+    beforeSetup: function(effect) {
+      effect.element.makeClipping(); 
+    },  
+    afterFinishInternal: function(effect) {
+      effect.element.hide().undoClipping(); 
+    }
+  });
+};
 
 Effect.Grow = function(element) {
   element = $(element);
-  var options = arguments[1] || {};
-  
-  var originalWidth = element.clientWidth;
-  var originalHeight = element.clientHeight;
-  element.style.overflow = 'hidden';
-  Element.show(element);
-  
-  var direction = options.direction || 'center';
-  var moveTransition = options.moveTransition || Effect.Transitions.sinoidal;
-  var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal;
-  var opacityTransition = options.opacityTransition || Effect.Transitions.full;
-  
+  var options = Object.extend({
+    direction: 'center',
+    moveTransition: Effect.Transitions.sinoidal,
+    scaleTransition: Effect.Transitions.sinoidal,
+    opacityTransition: Effect.Transitions.full
+  }, arguments[1] || { });
+  var oldStyle = {
+    top: element.style.top,
+    left: element.style.left,
+    height: element.style.height,
+    width: element.style.width,
+    opacity: element.getInlineOpacity() };
+
+  var dims = element.getDimensions();    
   var initialMoveX, initialMoveY;
   var moveX, moveY;
   
-  switch (direction) {
+  switch (options.direction) {
     case 'top-left':
       initialMoveX = initialMoveY = moveX = moveY = 0; 
       break;
     case 'top-right':
-      initialMoveX = originalWidth;
+      initialMoveX = dims.width;
       initialMoveY = moveY = 0;
-      moveX = -originalWidth;
+      moveX = -dims.width;
       break;
     case 'bottom-left':
       initialMoveX = moveX = 0;
-      initialMoveY = originalHeight;
-      moveY = -originalHeight;
+      initialMoveY = dims.height;
+      moveY = -dims.height;
       break;
     case 'bottom-right':
-      initialMoveX = originalWidth;
-      initialMoveY = originalHeight;
-      moveX = -originalWidth;
-      moveY = -originalHeight;
+      initialMoveX = dims.width;
+      initialMoveY = dims.height;
+      moveX = -dims.width;
+      moveY = -dims.height;
       break;
     case 'center':
-      initialMoveX = originalWidth / 2;
-      initialMoveY = originalHeight / 2;
-      moveX = -originalWidth / 2;
-      moveY = -originalHeight / 2;
+      initialMoveX = dims.width / 2;
+      initialMoveY = dims.height / 2;
+      moveX = -dims.width / 2;
+      moveY = -dims.height / 2;
       break;
   }
   
-  return new Effect.MoveBy(element, initialMoveY, initialMoveX, { 
+  return new Effect.Move(element, {
+    x: initialMoveX,
+    y: initialMoveY,
     duration: 0.01, 
-    beforeUpdate: function(effect) { $(element).style.height = '0px'; },
-    afterFinish: function(effect) {
+    beforeSetup: function(effect) {
+      effect.element.hide().makeClipping().makePositioned();
+    },
+    afterFinishInternal: function(effect) {
       new Effect.Parallel(
-        [ new Effect.Opacity(element, { sync: true, to: 1.0, from: 0.0, transition: opacityTransition }),
-          new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: moveTransition }),
-          new Effect.Scale(element, 100, { 
-            scaleMode: { originalHeight: originalHeight, originalWidth: originalWidth }, 
-            sync: true, scaleFrom: 0, scaleTo: 100, transition: scaleTransition })],
-        options); }
-    });
-}
+        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
+          new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
+          new Effect.Scale(effect.element, 100, {
+            scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, 
+            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
+        ], Object.extend({
+             beforeSetup: function(effect) {
+               effect.effects[0].element.setStyle({height: '0px'}).show(); 
+             },
+             afterFinishInternal: function(effect) {
+               effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); 
+             }
+           }, options)
+      )
+    }
+  });
+};
 
 Effect.Shrink = function(element) {
   element = $(element);
-  var options = arguments[1] || {};
-  
-  var originalWidth = element.clientWidth;
-  var originalHeight = element.clientHeight;
-  element.style.overflow = 'hidden';
-  Element.show(element);
-
-  var direction = options.direction || 'center';
-  var moveTransition = options.moveTransition || Effect.Transitions.sinoidal;
-  var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal;
-  var opacityTransition = options.opacityTransition || Effect.Transitions.none;
-  
+  var options = Object.extend({
+    direction: 'center',
+    moveTransition: Effect.Transitions.sinoidal,
+    scaleTransition: Effect.Transitions.sinoidal,
+    opacityTransition: Effect.Transitions.none
+  }, arguments[1] || { });
+  var oldStyle = {
+    top: element.style.top,
+    left: element.style.left,
+    height: element.style.height,
+    width: element.style.width,
+    opacity: element.getInlineOpacity() };
+
+  var dims = element.getDimensions();
   var moveX, moveY;
   
-  switch (direction) {
+  switch (options.direction) {
     case 'top-left':
       moveX = moveY = 0;
       break;
     case 'top-right':
-      moveX = originalWidth;
+      moveX = dims.width;
       moveY = 0;
       break;
     case 'bottom-left':
       moveX = 0;
-      moveY = originalHeight;
+      moveY = dims.height;
       break;
     case 'bottom-right':
-      moveX = originalWidth;
-      moveY = originalHeight;
+      moveX = dims.width;
+      moveY = dims.height;
       break;
     case 'center':  
-      moveX = originalWidth / 2;
-      moveY = originalHeight / 2;
+      moveX = dims.width / 2;
+      moveY = dims.height / 2;
       break;
   }
   
   return new Effect.Parallel(
-    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: opacityTransition }),
-      new Effect.Scale(element, 0, { sync: true, transition: moveTransition }),
-      new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: scaleTransition }) ],
-    options);
-}
+    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
+      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
+      new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
+    ], Object.extend({            
+         beforeStartInternal: function(effect) {
+           effect.effects[0].element.makePositioned().makeClipping(); 
+         },
+         afterFinishInternal: function(effect) {
+           effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
+       }, options)
+  );
+};
 
 Effect.Pulsate = function(element) {
   element = $(element);
-  var options    = arguments[1] || {};
+  var options    = arguments[1] || { };
+  var oldOpacity = element.getInlineOpacity();
   var transition = options.transition || Effect.Transitions.sinoidal;
-  var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) };
+  var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
   reverser.bind(transition);
   return new Effect.Opacity(element, 
-    Object.extend(Object.extend({  duration: 3.0,
-       afterFinish: function(effect) { Element.show(effect.element); }
+    Object.extend(Object.extend({  duration: 2.0, from: 0,
+      afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
     }, options), {transition: reverser}));
-}
+};
 
 Effect.Fold = function(element) {
- element = $(element);
- element.style.overflow = 'hidden';
- return new Effect.Scale(element, 5, Object.extend({   
-   scaleContent: false,
-   scaleTo: 100,
-   scaleX: false,
-   afterFinish: function(effect) {
-   new Effect.Scale(element, 1, { 
-     scaleContent: false, 
-     scaleTo: 0,
-     scaleY: false,
-     afterFinish: function(effect) { Element.hide(effect.element) } });
- }}, arguments[1] || {}));
-}
-
-// old: new Effect.ContentZoom(element, percent)
-// new: Element.setContentZoom(element, percent) 
-
-Element.setContentZoom = function(element, percent) {
   element = $(element);
-  element.style.fontSize = (percent/100) + "em";  
-  if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
-}
+  var oldStyle = {
+    top: element.style.top,
+    left: element.style.left,
+    width: element.style.width,
+    height: element.style.height };
+  element.makeClipping();
+  return new Effect.Scale(element, 5, Object.extend({   
+    scaleContent: false,
+    scaleX: false,
+    afterFinishInternal: function(effect) {
+    new Effect.Scale(element, 1, { 
+      scaleContent: false, 
+      scaleY: false,
+      afterFinishInternal: function(effect) {
+        effect.element.hide().undoClipping().setStyle(oldStyle);
+      } });
+  }}, arguments[1] || { }));
+};
+
+Effect.Morph = Class.create(Effect.Base, {
+  initialize: function(element) {
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    var options = Object.extend({
+      style: { }
+    }, arguments[1] || { });
+    
+    if (!Object.isString(options.style)) this.style = $H(options.style);
+    else {
+      if (options.style.include(':'))
+        this.style = options.style.parseStyle();
+      else {
+        this.element.addClassName(options.style);
+        this.style = $H(this.element.getStyles());
+        this.element.removeClassName(options.style);
+        var css = this.element.getStyles();
+        this.style = this.style.reject(function(style) {
+          return style.value == css[style.key];
+        });
+        options.afterFinishInternal = function(effect) {
+          effect.element.addClassName(effect.options.style);
+          effect.transforms.each(function(transform) {
+            effect.element.style[transform.style] = '';
+          });
+        }
+      }
+    }
+    this.start(options);
+  },
+  
+  setup: function(){
+    function parseColor(color){
+      if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
+      color = color.parseColor();
+      return $R(0,2).map(function(i){
+        return parseInt( color.slice(i*2+1,i*2+3), 16 ) 
+      });
+    }
+    this.transforms = this.style.map(function(pair){
+      var property = pair[0], value = pair[1], unit = null;
+
+      if (value.parseColor('#zzzzzz') != '#zzzzzz') {
+        value = value.parseColor();
+        unit  = 'color';
+      } else if (property == 'opacity') {
+        value = parseFloat(value);
+        if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
+          this.element.setStyle({zoom: 1});
+      } else if (Element.CSS_LENGTH.test(value)) {
+          var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
+          value = parseFloat(components[1]);
+          unit = (components.length == 3) ? components[2] : null;
+      }
+
+      var originalValue = this.element.getStyle(property);
+      return { 
+        style: property.camelize(), 
+        originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), 
+        targetValue: unit=='color' ? parseColor(value) : value,
+        unit: unit
+      };
+    }.bind(this)).reject(function(transform){
+      return (
+        (transform.originalValue == transform.targetValue) ||
+        (
+          transform.unit != 'color' &&
+          (isNaN(transform.originalValue) || isNaN(transform.targetValue))
+        )
+      )
+    });
+  },
+  update: function(position) {
+    var style = { }, transform, i = this.transforms.length;
+    while(i--)
+      style[(transform = this.transforms[i]).style] = 
+        transform.unit=='color' ? '#'+
+          (Math.round(transform.originalValue[0]+
+            (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
+          (Math.round(transform.originalValue[1]+
+            (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
+          (Math.round(transform.originalValue[2]+
+            (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
+        (transform.originalValue +
+          (transform.targetValue - transform.originalValue) * position).toFixed(3) + 
+            (transform.unit === null ? '' : transform.unit);
+    this.element.setStyle(style, true);
+  }
+});
+
+Effect.Transform = Class.create({
+  initialize: function(tracks){
+    this.tracks  = [];
+    this.options = arguments[1] || { };
+    this.addTracks(tracks);
+  },
+  addTracks: function(tracks){
+    tracks.each(function(track){
+      track = $H(track);
+      var data = track.values().first();
+      this.tracks.push($H({
+        ids:     track.keys().first(),
+        effect:  Effect.Morph,
+        options: { style: data }
+      }));
+    }.bind(this));
+    return this;
+  },
+  play: function(){
+    return new Effect.Parallel(
+      this.tracks.map(function(track){
+        var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
+        var elements = [$(ids) || $$(ids)].flatten();
+        return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });
+      }).flatten(),
+      this.options
+    );
+  }
+});
+
+Element.CSS_PROPERTIES = $w(
+  'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + 
+  'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
+  'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
+  'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
+  'fontSize fontWeight height left letterSpacing lineHeight ' +
+  'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
+  'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
+  'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
+  'right textIndent top width wordSpacing zIndex');
+  
+Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
+
+String.__parseStyleElement = document.createElement('div');
+String.prototype.parseStyle = function(){
+  var style, styleRules = $H();
+  if (Prototype.Browser.WebKit)
+    style = new Element('div',{style:this}).style;
+  else {
+    String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
+    style = String.__parseStyleElement.childNodes[0].style;
+  }
+  
+  Element.CSS_PROPERTIES.each(function(property){
+    if (style[property]) styleRules.set(property, style[property]); 
+  });
+  
+  if (Prototype.Browser.IE && this.include('opacity'))
+    styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);
+
+  return styleRules;
+};
+
+if (document.defaultView && document.defaultView.getComputedStyle) {
+  Element.getStyles = function(element) {
+    var css = document.defaultView.getComputedStyle($(element), null);
+    return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
+      styles[property] = css[property];
+      return styles;
+    });
+  };
+} else {
+  Element.getStyles = function(element) {
+    element = $(element);
+    var css = element.currentStyle, styles;
+    styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {
+      results[property] = css[property];
+      return results;
+    });
+    if (!styles.opacity) styles.opacity = element.getOpacity();
+    return styles;
+  };
+};
+
+Effect.Methods = {
+  morph: function(element, style) {
+    element = $(element);
+    new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
+    return element;
+  },
+  visualEffect: function(element, effect, options) {
+    element = $(element)
+    var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
+    new Effect[klass](element, options);
+    return element;
+  },
+  highlight: function(element, options) {
+    element = $(element);
+    new Effect.Highlight(element, options);
+    return element;
+  }
+};
+
+$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
+  'pulsate shake puff squish switchOff dropOut').each(
+  function(effect) { 
+    Effect.Methods[effect] = function(element, options){
+      element = $(element);
+      Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
+      return element;
+    }
+  }
+);
+
+$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( 
+  function(f) { Effect.Methods[f] = Element[f]; }
+);
+
+Element.addMethods(Effect.Methods);
index 63c09e56cc45030332c320ef131379c65ba5b758..ab569039902d5ced1a0af693241648a7799f3b1e 100644 (file)
@@ -7,13 +7,14 @@
 <meta name="keywords" content="groupware messaging collaboration" >
 <link href="static/webcit.css" rel="stylesheet" type="text/css">
 <?CSSLOCAL>
-<script type="text/javascript" src="static/wclib.js"></script>
 <script type="text/javascript" src="static/prototype.js"></script>
 <script type="text/javascript" src="static/scriptaculous.js"></script>
 <script type="text/javascript" src="static/niftycube.js"></script>
 <script type="text/javascript" src="static/BubbleTooltips.js"></script>
-
+<script type="text/javascript" src="static/datepicker-dev.js"></script>
+<script type="text/javascript" src="static/table.js"></script>
+<script type="text/javascript" src="static/wclib.js"></script>
 </head>
 <body>
 <div id="global">
-<! end head.html>
+<!-- end head.html -->
index a3f21ac790995f83f0d7e1dfc8a448d7580d035b..6385503a1c526d4d13ccc770bce74200826ecd6a 100644 (file)
@@ -1,27 +1,29 @@
-/*  Prototype JavaScript framework, version 1.5.1.1
- *  (c) 2005-2007 Sam Stephenson
+/*  Prototype JavaScript framework, version 1.6.0.2
+ *  (c) 2005-2008 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.2',
 
   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,31 +31,86 @@ var Prototype = {
 
   emptyFunction: function() { },
   K: function(x) { return x }
-}
+};
+
+if (Prototype.Browser.MobileSafari)
+  Prototype.BrowserFeatures.SpecificElementExtensions = 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) {
     try {
-      if (object === undefined) return 'undefined';
+      if (Object.isUndefined(object)) return 'undefined';
       if (object === null) return 'null';
-      return object.inspect ? object.inspect() : object.toString();
+      return object.inspect ? object.inspect() : String(object);
     } catch (e) {
       if (e instanceof RangeError) return '...';
       throw e;
@@ -62,24 +119,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)
+      if (!Object.isUndefined(value))
         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 +163,100 @@ 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 != null && typeof object == "object" &&
+      'splice' in object && 'join' in object;
+  },
+
+  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 && Object.isUndefined(arguments[0])) 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 +268,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 +296,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 +310,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);
@@ -228,7 +350,7 @@ Object.extend(String.prototype, {
 
   sub: function(pattern, replacement, count) {
     replacement = this.gsub.prepareReplacement(replacement);
-    count = count === undefined ? 1 : count;
+    count = Object.isUndefined(count) ? 1 : count;
 
     return this.gsub(pattern, function(match) {
       if (--count < 0) return match[0];
@@ -238,14 +360,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;
+    truncation = Object.isUndefined(truncation) ? '...' : 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 +401,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 +410,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 +438,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() {
@@ -365,7 +485,9 @@ Object.extend(String.prototype, {
   },
 
   isJSON: function() {
-    var str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
+    var str = this;
+    if (str.blank()) return false;
+    str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
     return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
   },
 
@@ -396,6 +518,10 @@ Object.extend(String.prototype, {
 
   blank: function() {
     return /^\s*$/.test(this);
+  },
+
+  interpolate: function(object, pattern) {
+    return new Template(this, pattern).evaluate(object);
   }
 });
 
@@ -409,10 +535,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 +549,47 @@ 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);
     });
   }
-}
+});
+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 +600,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 +649,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 +659,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) {
@@ -530,14 +688,15 @@ var Enumerable = {
   },
 
   inGroupsOf: function(number, fillWith) {
-    fillWith = fillWith === undefined ? null : fillWith;
+    fillWith = Object.isUndefined(fillWith) ? null : fillWith;
     return this.eachSlice(number, function(slice) {
       while(slice.length < number) slice.push(fillWith);
       return slice;
     });
   },
 
-  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 +710,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);
-      if (result == undefined || value >= result)
+      value = iterator(value, index);
+      if (result == null || 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);
-      if (result == undefined || value < result)
+      value = iterator(value, index);
+      if (result == null || 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 +744,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 +760,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 +776,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 +792,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 || 0, results = new Array(length);
+  while (length--) results[length] = iterable[length];
+  return results;
 }
 
 if (Prototype.Browser.WebKit) {
-  $A = Array.from = function(iterable) {
+  $A = function(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 || 0, 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 +856,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 +868,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 +884,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);
   },
@@ -746,15 +906,35 @@ Object.extend(Array.prototype, {
     var results = [];
     this.each(function(object) {
       var value = Object.toJSON(object);
-      if (value !== undefined) results.push(value);
+      if (!Object.isUndefined(value)) results.push(value);
     });
     return '[' + results.join(', ') + ']';
   }
 });
 
+// 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 +944,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 +952,135 @@ 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)));
-}
+$w('abs round ceil floor').each(function(method){
+  Number.prototype[method] = Math[method].methodize();
+});
+function $H(object) {
+  return new Hash(object);
+};
 
-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;
+var Hash = Class.create(Enumerable, (function() {
 
-      var pair = [key, value];
-      pair.key = key;
-      pair.value = value;
-      iterator(pair);
-    }
-  },
+  function toQueryPair(key, value) {
+    if (Object.isUndefined(value)) return key;
+    return key + '=' + encodeURIComponent(String.interpret(value));
+  }
 
-  keys: function() {
-    return this.pluck('key');
-  },
+  return {
+    initialize: function(object) {
+      this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
+    },
 
-  values: function() {
-    return this.pluck('value');
-  },
+    _each: function(iterator) {
+      for (var key in this._object) {
+        var value = this._object[key], pair = [key, value];
+        pair.key = key;
+        pair.value = value;
+        iterator(pair);
+      }
+    },
 
-  merge: function(hash) {
-    return $H(hash).inject(this, function(mergedHash, pair) {
-      mergedHash[pair.key] = pair.value;
-      return mergedHash;
-    });
-  },
+    set: function(key, value) {
+      return this._object[key] = value;
+    },
 
-  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;
-  },
+    get: function(key) {
+      return this._object[key];
+    },
 
-  toQueryString: function() {
-    return Hash.toQueryString(this);
-  },
+    unset: function(key) {
+      var value = this._object[key];
+      delete this._object[key];
+      return value;
+    },
 
-  inspect: function() {
-    return '#<Hash:{' + this.map(function(pair) {
-      return pair.map(Object.inspect).join(': ');
-    }).join(', ') + '}>';
-  },
+    toObject: function() {
+      return Object.clone(this._object);
+    },
 
-  toJSON: function() {
-    return Hash.toJSON(this);
-  }
-});
+    keys: function() {
+      return this.pluck('key');
+    },
 
-function $H(object) {
-  if (object instanceof Hash) return object;
-  return new Hash(object);
-};
+    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 +1106,7 @@ Object.extend(ObjectRange.prototype, {
 
 var $R = function(start, end, exclusive) {
   return new ObjectRange(start, end, exclusive);
-}
+};
 
 var Ajax = {
   getTransport: function() {
@@ -939,7 +1118,7 @@ var Ajax = {
   },
 
   activeRequestCount: 0
-}
+};
 
 Ajax.Responders = {
   responders: [],
@@ -959,10 +1138,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 +1150,38 @@ 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();
+    else if (Object.isHash(this.options.parameters))
+      this.options.parameters = this.options.parameters.toObject();
   }
-}
-
-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 +1198,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 +1207,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 +1262,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 +1274,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 && this.isSameOrigin() && 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);
     }
@@ -1136,16 +1317,18 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
     }
   },
 
-  getHeader: function(name) {
-    try {
-      return this.transport.getResponseHeader(name);
-    } catch (e) { return null }
+  isSameOrigin: function() {
+    var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
+    return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
+      protocol: location.protocol,
+      domain: document.domain,
+      port: location.port ? ':' + location.port : ''
+    }));
   },
 
-  evalJSON: function() {
+  getHeader: function(name) {
     try {
-      var json = this.getHeader('X-JSON');
-      return json ? json.evalJSON() : null;
+      return this.transport.getResponseHeader(name) || null;
     } catch (e) { return null }
   },
 
@@ -1163,57 +1346,128 @@ 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  = Object.isUndefined(xml) ? 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);
+  },
 
-    if (this.success()) {
-      if (this.onComplete)
-        setTimeout(this.onComplete.bind(this), 10);
-    }
-  }
-});
+  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 ||
+        !this.request.isSameOrigin());
+    } 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')) ||
+        this.responseText.blank())
+          return null;
+    try {
+      return this.responseText.evalJSON(options.sanitizeJSON ||
+        !this.request.isSameOrigin());
+    } 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 = Object.clone(options);
+    var onComplete = options.onComplete;
+    options.onComplete = (function(response, json) {
+      this.updateContent(response.responseText);
+      if (Object.isFunction(onComplete)) onComplete(response, json);
+    }).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);
+    }
+  }
+});
 
-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 +1485,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 +1505,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 +1516,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 +1589,78 @@ 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, insert, tagName, childNodes;
+
+    for (var position in insertions) {
+      content  = insertions[position];
+      position = position.toLowerCase();
+      insert = Element._insertionTranslations[position];
+
+      if (content && content.toElement) content = content.toElement();
+      if (Object.isElement(content)) {
+        insert(element, content);
+        continue;
+      }
+
+      content = Object.toHTML(content);
+
+      tagName = ((position == 'before' || position == 'after')
+        ? element.parentNode : element).tagName.toUpperCase();
+
+      childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+
+      if (position == 'top' || position == 'after') childNodes.reverse();
+      childNodes.each(insert.curry(element));
+
+      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();
@@ -1399,7 +1686,7 @@ Element.Methods = {
   },
 
   descendants: function(element) {
-    return $A($(element).getElementsByTagName('*')).each(Element.extend);
+    return $(element).select("*");
   },
 
   firstDescendant: function(element) {
@@ -1429,7 +1716,7 @@ Element.Methods = {
   },
 
   match: function(element, selector) {
-    if (typeof selector == 'string')
+    if (Object.isString(selector))
       selector = new Selector(selector);
     return selector.match($(element));
   },
@@ -1438,56 +1725,86 @@ Element.Methods = {
     element = $(element);
     if (arguments.length == 1) return $(element.parentNode);
     var ancestors = element.ancestors();
-    return expression ? Selector.findElement(ancestors, expression, index) :
-      ancestors[index || 0];
+    return Object.isNumber(expression) ? ancestors[expression] :
+      Selector.findElement(ancestors, expression, index);
   },
 
   down: function(element, expression, index) {
     element = $(element);
     if (arguments.length == 1) return element.firstDescendant();
-    var descendants = element.descendants();
-    return expression ? Selector.findElement(descendants, expression, index) :
-      descendants[index || 0];
+    return Object.isNumber(expression) ? element.descendants()[expression] :
+      element.select(expression)[index || 0];
   },
 
   previous: function(element, expression, index) {
     element = $(element);
     if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
     var previousSiblings = element.previousSiblings();
-    return expression ? Selector.findElement(previousSiblings, expression, index) :
-      previousSiblings[index || 0];
+    return Object.isNumber(expression) ? previousSiblings[expression] :
+      Selector.findElement(previousSiblings, expression, index);
   },
 
   next: function(element, expression, index) {
     element = $(element);
     if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
     var nextSiblings = element.nextSiblings();
-    return expression ? Selector.findElement(nextSiblings, expression, index) :
-      nextSiblings[index || 0];
+    return Object.isNumber(expression) ? nextSiblings[expression] :
+      Selector.findElement(nextSiblings, expression, index);
   },
 
-  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] = Object.isUndefined(value) ? true : value;
+
+    for (var attr in attributes) {
+      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 +1820,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,14 +1863,30 @@ Element.Methods = {
 
   descendantOf: function(element, ancestor) {
     element = $(element), ancestor = $(ancestor);
+    var originalAncestor = 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 && nextAncestor.sourceIndex)
+       return (e > a && e < nextAncestor.sourceIndex);
+    }
+
     while (element = element.parentNode)
-      if (element == ancestor) return true;
+      if (element == originalAncestor) return true;
     return false;
   },
 
   scrollTo: function(element) {
     element = $(element);
-    var pos = Position.cumulativeOffset(element);
+    var pos = element.cumulativeOffset();
     window.scrollTo(pos[0], pos[1]);
     return element;
   },
@@ -1585,16 +1907,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];
+          (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :
+            property] = styles[property];
 
     return element;
   },
@@ -1661,8 +1987,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,28 +1999,253 @@ 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 !== 'static') 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 (Prototype.Browser.Opera) {
-  Element.Methods._getStyle = Element.Methods.getStyle;
-  Element.Methods.getStyle = function(element, style) {
-    switch(style) {
-      case 'left':
-      case 'top':
-      case 'right':
-      case 'bottom':
-        if (Element._getStyle(element, 'position') == 'static') return null;
-      default: return Element._getStyle(element, style);
+  Element.Methods.getStyle = Element.Methods.getStyle.wrap(
+    function(proceed, element, style) {
+      switch (style) {
+        case 'left': case 'top': case 'right': case 'bottom':
+          if (proceed(element, 'position') === 'static') return null;
+        case 'height': case 'width':
+          // returns '0px' for hidden elements; we want it to return null
+          if (!Element.visible(element)) return null;
+
+          // returns the border-box dimensions rather than the content-box
+          // dimensions, so we subtract padding and borders from the value
+          var dim = parseInt(proceed(element, style), 10);
+
+          if (dim !== element['offset' + style.capitalize()])
+            return dim + 'px';
+
+          var properties;
+          if (style === 'height') {
+            properties = ['border-top-width', 'padding-top',
+             'padding-bottom', 'border-bottom-width'];
+          }
+          else {
+            properties = ['border-left-width', 'padding-left',
+             'padding-right', 'border-right-width'];
+          }
+          return properties.inject(dim, function(memo, property) {
+            var val = proceed(element, property);
+            return val === null ? memo : memo - parseInt(val, 10);
+          }) + 'px';
+        default: return proceed(element, style);
+      }
     }
-  };
+  );
+
+  Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
+    function(proceed, element, attribute) {
+      if (attribute === 'title') return element.title;
+      return proceed(element, attribute);
+    }
+  );
 }
+
 else if (Prototype.Browser.IE) {
+  // IE doesn't report offsets correctly for static elements, so we change them
+  // to "relative" to get the values, then change them back.
+  Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.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;
+    }
+  );
+
+  $w('positionedOffset 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);
+        // Trigger hasLayout on the offset parent so that IE6 reports
+        // accurate offsetTop and offsetLeft values for position: fixed.
+        var offsetParent = element.getOffsetParent();
+        if (offsetParent && offsetParent.getStyle('position') === 'fixed')
+          offsetParent.setStyle({ zoom: 1 });
+        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,126 +2260,321 @@ 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) {
+          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;
-  }
-}
-else if (Prototype.Browser.Gecko) {
-  Element.Methods.setOpacity = function(element, value) {
-    element = $(element);
-    element.style.opacity = (value == 1) ? 0.999999 :
-      (value === '') ? '' : (value < 0.00001) ? 0 : value;
-    return element;
   };
-}
 
-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);
-    },
-    _flag: function(element, attribute) {
-      return $(element).hasAttribute(attribute) ? attribute : null;
-    },
-    style: function(element) {
-      return element.style.cssText.toLowerCase();
-    },
-    title: function(element) {
-      var node = element.getAttributeNode('title');
-      return node.specified ? node.nodeValue : null;
+  Element._attributeTranslations.write = {
+    names: Object.extend({
+      cellpadding: 'cellPadding',
+      cellspacing: 'cellSpacing'
+    }, 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 && /rv:1\.8\.0/.test(navigator.userAgent)) {
+  Element.Methods.setOpacity = function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1) ? 0.999999 :
+      (value === '') ? '' : (value < 0.00001) ? 0 : value;
+    return element;
+  };
+}
+
+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 Element#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 ('outerHTML' in document.createElement('div')) {
+  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];
+  if (t) {
+    div.innerHTML = t[0] + html + t[1];
+    t[2].times(function() { div = div.firstChild });
+  } else div.innerHTML = html;
+  return $A(div.childNodes);
+};
+
+Element._insertionTranslations = {
+  before: function(element, node) {
+    element.parentNode.insertBefore(node, element);
+  },
+  top: function(element, node) {
+    element.insertBefore(node, element.firstChild);
+  },
+  bottom: function(element, node) {
+    element.appendChild(node);
+  },
+  after: function(element, node) {
+    element.parentNode.insertBefore(node, element.nextSibling);
+  },
+  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
+  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 +2599,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 +2642,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,169 +2655,84 @@ 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 = { };
+    var B = Prototype.Browser;
+    $w('width height').each(function(d) {
+      var D = d.capitalize();
+      dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] :
+        (B.Opera) ? document.body['client' + D] : document.documentElement['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();
   },
 
+  shouldUseXPath: function() {
+    if (!Prototype.BrowserFeatures.XPath) return false;
+
+    var e = this.expression;
+
+    // Safari 3 chokes on :*-of-type and :empty
+    if (Prototype.Browser.WebKit &&
+     (e.include("-of-type") || e.include(":empty")))
+      return false;
+
+    // XPath can't do namespaced attributes, nor can it read
+    // the "checked" property from DOM nodes
+    if ((/(\[[\w-]*?:|:checked)/).test(this.expression))
+      return false;
+
+    return true;
+  },
+
   compileMatcher: function() {
-    // Selectors with namespaced attributes can't use the XPath version
-    if (Prototype.BrowserFeatures.XPath && !(/\[[\w-]*?:/).test(this.expression))
+    if (this.shouldUseXPath())
       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 +2741,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 +2756,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 +2767,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 +2786,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 +2828,10 @@ Selector.prototype = {
   inspect: function() {
     return "#<Selector:" + this.expression.inspect() + ">";
   }
-};
+});
 
 Object.extend(Selector, {
-  _cache: {},
+  _cache: { },
 
   xpath: {
     descendant:   "//*",
@@ -2152,15 +2845,19 @@ Object.extend(Selector, {
     },
     className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]",
     id:           "[@id='#{1}']",
-    attrPresence: "[@#{1}]",
+    attrPresence: function(m) {
+      m[1] = m[1].toLowerCase();
+      return new Template("[@#{1}]").evaluate(m);
+    },
     attr: function(m) {
+      m[1] = m[1].toLowerCase();
       m[3] = m[5] || m[6];
       return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
     },
     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: {
@@ -2182,14 +2879,14 @@ Object.extend(Selector, {
       'enabled':     "[not(@disabled)]",
       'not': function(m) {
         var e = m[6], p = Selector.patterns,
-            x = Selector.xpath, le, m, v;
+            x = Selector.xpath, le, v;
 
         var exclusion = [];
         while (e && le != e && (/\S/).test(e)) {
           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;
@@ -2239,15 +2936,15 @@ Object.extend(Selector, {
   },
 
   criteria: {
-    tagName:      'n = h.tagName(n, r, "#{1}", c);   c = false;',
-    className:    'n = h.className(n, r, "#{1}", c); c = false;',
-    id:           'n = h.id(n, r, "#{1}", c);        c = false;',
-    attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;',
+    tagName:      'n = h.tagName(n, r, "#{1}", c);      c = false;',
+    className:    'n = h.className(n, r, "#{1}", c);    c = false;',
+    id:           'n = h.id(n, r, "#{1}", c);           c = false;',
+    attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
     attr: function(m) {
       m[3] = (m[5] || m[6]);
-      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m);
+      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); 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 +2966,34 @@ 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 nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
+    }
   },
 
   handlers: {
@@ -2285,14 +3007,15 @@ Object.extend(Selector, {
 
     // marks an array of nodes for counting
     mark: function(nodes) {
+      var _true = Prototype.emptyFunction;
       for (var i = 0, node; node = nodes[i]; i++)
-        node._counted = true;
+        node._countedByPrototype = _true;
       return nodes;
     },
 
     unmark: function(nodes) {
       for (var i = 0, node; node = nodes[i]; i++)
-        node._counted = undefined;
+        node._countedByPrototype = undefined;
       return nodes;
     },
 
@@ -2300,15 +3023,15 @@ Object.extend(Selector, {
     // "ofType" flag indicates whether we're indexing for nth-of-type
     // rather than nth-child
     index: function(parentNode, reverse, ofType) {
-      parentNode._counted = true;
+      parentNode._countedByPrototype = Prototype.emptyFunction;
       if (reverse) {
         for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
-          node = nodes[i];
-          if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
+          var node = nodes[i];
+          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
         }
       } else {
         for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
-          if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
+          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
       }
     },
 
@@ -2317,8 +3040,8 @@ Object.extend(Selector, {
       if (nodes.length == 0) return nodes;
       var results = [], n;
       for (var i = 0, l = nodes.length; i < l; i++)
-        if (!(n = nodes[i])._counted) {
-          n._counted = true;
+        if (!(n = nodes[i])._countedByPrototype) {
+          n._countedByPrototype = Prototype.emptyFunction;
           results.push(Element.extend(n));
         }
       return Selector.handlers.unmark(results);
@@ -2335,7 +3058,7 @@ Object.extend(Selector, {
     child: function(nodes) {
       var h = Selector.handlers;
       for (var i = 0, results = [], node; node = nodes[i]; i++) {
-        for (var j = 0, children = [], child; child = node.childNodes[j]; j++)
+        for (var j = 0, child; child = node.childNodes[j]; j++)
           if (child.nodeType == 1 && child.tagName != '!') results.push(child);
       }
       return results;
@@ -2370,7 +3093,7 @@ Object.extend(Selector, {
 
     // TOKEN FUNCTIONS
     tagName: function(nodes, root, tagName, combinator) {
-      tagName = tagName.toUpperCase();
+      var uTagName = tagName.toUpperCase();
       var results = [], h = Selector.handlers;
       if (nodes) {
         if (combinator) {
@@ -2383,14 +3106,15 @@ Object.extend(Selector, {
           if (tagName == "*") return nodes;
         }
         for (var i = 0, node; node = nodes[i]; i++)
-          if (node.tagName.toUpperCase() == tagName) results.push(node);
+          if (node.tagName.toUpperCase() === uTagName) results.push(node);
         return results;
       } else return root.getElementsByTagName(tagName);
     },
 
     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') {
@@ -2429,15 +3153,18 @@ Object.extend(Selector, {
       return results;
     },
 
-    attrPresence: function(nodes, root, attr) {
+    attrPresence: function(nodes, root, attr, combinator) {
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      if (nodes && combinator) nodes = this[combinator](nodes);
       var results = [];
       for (var i = 0, node; node = nodes[i]; i++)
         if (Element.hasAttribute(node, attr)) results.push(node);
       return results;
     },
 
-    attr: function(nodes, root, attr, value, operator) {
+    attr: function(nodes, root, attr, value, operator, combinator) {
       if (!nodes) nodes = root.getElementsByTagName("*");
+      if (nodes && combinator) nodes = this[combinator](nodes);
       var handler = Selector.operators[operator], results = [];
       for (var i = 0, node; node = nodes[i]; i++) {
         var nodeValue = Element.readAttribute(node, attr);
@@ -2516,7 +3243,7 @@ Object.extend(Selector, {
       var h = Selector.handlers, results = [], indexed = [], m;
       h.mark(nodes);
       for (var i = 0, node; node = nodes[i]; i++) {
-        if (!node.parentNode._counted) {
+        if (!node.parentNode._countedByPrototype) {
           h.index(node.parentNode, reverse, ofType);
           indexed.push(node.parentNode);
         }
@@ -2554,7 +3281,7 @@ Object.extend(Selector, {
       var exclusions = new Selector(selector).findElements(root);
       h.mark(exclusions);
       for (var i = 0, results = [], node; node = nodes[i]; i++)
-        if (!node._counted) results.push(node);
+        if (!node._countedByPrototype) results.push(node);
       h.unmark(exclusions);
       return results;
     },
@@ -2588,27 +3315,32 @@ Object.extend(Selector, {
     '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
   },
 
+  split: function(expression) {
+    var expressions = [];
+    expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
+      expressions.push(m[1].strip());
+    });
+    return expressions;
+  },
+
   matchElements: function(elements, expression) {
-    var matches = new Selector(expression).findElements(), h = Selector.handlers;
+    var matches = $$(expression), h = Selector.handlers;
     h.mark(matches);
     for (var i = 0, results = [], element; element = elements[i]; i++)
-      if (element._counted) results.push(element);
+      if (element._countedByPrototype) results.push(element);
     h.unmark(matches);
     return results;
   },
 
   findElement: function(elements, expression, index) {
-    if (typeof expression == 'number') {
+    if (Object.isNumber(expression)) {
       index = expression; expression = false;
     }
     return Selector.matchElements(elements, expression || '*')[index || 0];
   },
 
   findChildElements: function(element, expressions) {
-    var exprs = expressions.join(','), expressions = [];
-    exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
-      expressions.push(m[1].strip());
-    });
+    expressions = Selector.split(expressions.join(','));
     var results = [], h = Selector.handlers;
     for (var i = 0, l = expressions.length, selector; i < l; i++) {
       selector = new Selector(expressions[i].strip());
@@ -2618,6 +3350,25 @@ Object.extend(Selector, {
   }
 });
 
+if (Prototype.Browser.IE) {
+  Object.extend(Selector.handlers, {
+    // IE returns comment nodes on getElementsByTagName("*").
+    // Filter them out.
+    concat: function(a, b) {
+      for (var i = 0, node; node = b[i]; i++)
+        if (node.tagName !== "!") a.push(node);
+      return a;
+    },
+
+    // IE improperly serializes _countedByPrototype in (inner|outer)HTML.
+    unmark: function(nodes) {
+      for (var i = 0, node; node = nodes[i]; i++)
+        node.removeAttribute('_countedByPrototype');
+      return nodes;
+    }
+  });
+}
+
 function $$() {
   return Selector.findChildElements(document, $A(arguments));
 }
@@ -2627,13 +3378,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 (Object.isUndefined(options.hash)) 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 +3399,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 +3447,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 +3466,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 +3496,7 @@ Form.Element = {
     $(element).select();
     return element;
   }
-}
+};
 
 Form.Element.Methods = {
   serialize: function(element) {
@@ -2740,9 +3504,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 +3518,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 +3539,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 +3557,7 @@ Form.Element.Methods = {
     element.disabled = false;
     return element;
   }
-}
+};
 
 /*--------------------------------------------------------------------------*/
 
@@ -2796,27 +3567,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 (Object.isUndefined(value)) return element.checked ? element.value : null;
+    else element.checked = !!value;
   },
 
-  textarea: function(element) {
-    return element.value;
+  textarea: function(element, value) {
+    if (Object.isUndefined(value)) return element.value;
+    else element.value = value;
   },
 
-  select: function(element) {
-    return this[element.type == 'select-one' ?
-      'selectOne' : 'selectMany'](element);
+  select: function(element, index) {
+    if (Object.isUndefined(index))
+      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 +3627,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 +3662,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 +3683,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 +3699,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 +3728,339 @@ 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];
+    };
+
+  } 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);
+    };
+  }
 
-  stop: function(event) {
-    if (event.preventDefault) {
+  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);
+      if (!expression) return element;
+      var elements = [element].concat(element.ancestors());
+      return Selector.findElement(elements, expression, 0);
+    },
+
+    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._prototypeEventID) return element._prototypeEventID[0];
+    arguments.callee.id = arguments.callee.id || 1;
+    return element._prototypeEventID = [++arguments.callee.id];
+  }
+
+  function getDOMEventName(eventName) {
+    if (eventName && eventName.include(':')) return "dataavailable";
+    return eventName;
+  }
+
+  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;
+  }
+
+  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);
 
-  observers: false,
+      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;
+
+      var event;
+      if (document.createEvent) {
+        event = document.createEvent("HTMLEvents");
+        event.initEvent("dataavailable", true, true);
+      } else {
+        event = document.createEventObject();
+        event.eventType = "ondataavailable";
+      }
 
-  _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);
+      event.eventName = eventName;
+      event.memo = memo || { };
+
+      if (document.createEvent) {
+        element.dispatchEvent(event);
+      } else {
+        element.fireEvent(event.eventType, event);
+      }
+
+      return Event.extend(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(),
+  loaded:        false
+});
+
+(function() {
+  /* Support for the DOMContentLoaded event is based on work by Dan Webb,
+     Matthias Miller, Dean Edwards and John Resig. */
+
+  var timer;
+
+  function fireContentLoadedEvent() {
+    if (document.loaded) return;
+    if (timer) window.clearInterval(timer);
+    document.fire("dom:loaded");
+    document.loaded = true;
+  }
+
+  if (document.addEventListener) {
+    if (Prototype.Browser.WebKit) {
+      timer = window.setInterval(function() {
+        if (/loaded|complete/.test(document.readyState))
+          fireContentLoadedEvent();
+      }, 0);
 
-  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;
+      Event.observe(window, "load", fireContentLoadedEvent);
+
+    } 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});
   }
-});
+};
+
+var $continue = new Error('"throw $continue" is deprecated, use "return" instead');
 
-/* prevent memory leaks in IE */
-if (Prototype.Browser.IE)
-  Event.observe(window, 'unload', Event.unloadCache, false);
+// 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 +4080,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 +4095,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 +4118,104 @@ var Position = {
         element.offsetWidth;
   },
 
-  page: function(forElement) {
-    var valueT = 0, valueL = 0;
+  // Deprecation layer -- use newer Element methods now (1.5.2).
 
-    var element = forElement;
-    do {
-      valueT += element.offsetTop  || 0;
-      valueL += element.offsetLeft || 0;
+  cumulativeOffset: Element.Methods.cumulativeOffset,
 
-      // Safari fix
-      if (element.offsetParent == document.body)
-        if (Element.getStyle(element,'position')=='absolute') break;
-
-    } 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
index 2fd4e00a82e8d21f7c18825d80ee4981c53d1f51..2fcfb2edf432531039bec3f977594d1a429c314b 100644 (file)
@@ -1,4 +1,4 @@
-<! start roombanner.html>
+<!-- start roombanner.html -->
 <div class="room_banner" >
 <?ROOMPIC>
 <h1><?ROOMNAME></h1>
@@ -10,4 +10,4 @@
 <li class="search"><?SEARCHOMATIC></li>
 <li class="view"><?VIEWOMATIC></li>
 </ul>
-<! end roombanner.html>
+<!-- end roombanner.html -->
index ea1ad40ae52a01c31c1b3f6befe6e18c7e209e23..6cfe36e8d5240f20d161cda8648fe4ee5bf22ad3 100644 (file)
@@ -1,25 +1,57 @@
+// script.aculo.us scriptaculous.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
+
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// 
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
 var Scriptaculous = {
-  Version: '1.5_pre4',
+  Version: '1.8.1',
   require: function(libraryName) {
     // inserting via DOM fails in Safari 2.0, so brute force approach
-    document.write('<script type="text/javascript" src="'+libraryName+'"></script>');
+    document.write('<script type="text/javascript" src="'+libraryName+'"><\/script>');
   },
+  REQUIRED_PROTOTYPE: '1.6.0',
   load: function() {
-    if((typeof Prototype=='undefined') ||
-      parseFloat(Prototype.Version.split(".")[0] + "." +
-                 Prototype.Version.split(".")[1]) < 1.4)
-      throw("script.aculo.us requires the Prototype JavaScript framework >= 1.4.0");
-    var scriptTags = document.getElementsByTagName("script");
-    for(var i=0;i<scriptTags.length;i++) {
-      if(scriptTags[i].src && scriptTags[i].src.match(/scriptaculous\.js$/)) {
-        var path = scriptTags[i].src.replace(/scriptaculous\.js$/,'');
-        this.require(path + 'util.js');
-        this.require(path + 'effects.js');
-        this.require(path + 'dragdrop.js');
-        this.require(path + 'controls.js');
-        break;
-      }
+    function convertVersionString(versionString){
+      var r = versionString.split('.');
+      return parseInt(r[0])*100000 + parseInt(r[1])*1000 + parseInt(r[2]);
     }
+    if((typeof Prototype=='undefined') || 
+       (typeof Element == 'undefined') || 
+       (typeof Element.Methods=='undefined') ||
+       (convertVersionString(Prototype.Version) < 
+        convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE)))
+       throw("script.aculo.us requires the Prototype JavaScript framework >= " +
+        Scriptaculous.REQUIRED_PROTOTYPE);
+    
+    $A(document.getElementsByTagName("script")).findAll( function(s) {
+      return (s.src && s.src.match(/scriptaculous\.js(\?.*)?$/))
+    }).each( function(s) {
+      var path = s.src.replace(/scriptaculous\.js(\?.*)?$/,'');
+      var includes = s.src.match(/\?.*load=([a-z,]*)/);
+      (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each(
+       function(include) { Scriptaculous.require(path+include+'.js') });
+    });
   }
 }
 
diff --git a/webcit/static/slider.js b/webcit/static/slider.js
new file mode 100644 (file)
index 0000000..cc46fe3
--- /dev/null
@@ -0,0 +1,275 @@
+// script.aculo.us slider.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
+
+// Copyright (c) 2005-2007 Marty Haught, Thomas Fuchs 
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+if (!Control) var Control = { };
+
+// options:
+//  axis: 'vertical', or 'horizontal' (default)
+//
+// callbacks:
+//  onChange(value)
+//  onSlide(value)
+Control.Slider = Class.create({
+  initialize: function(handle, track, options) {
+    var slider = this;
+    
+    if (Object.isArray(handle)) {
+      this.handles = handle.collect( function(e) { return $(e) });
+    } else {
+      this.handles = [$(handle)];
+    }
+    
+    this.track   = $(track);
+    this.options = options || { };
+
+    this.axis      = this.options.axis || 'horizontal';
+    this.increment = this.options.increment || 1;
+    this.step      = parseInt(this.options.step || '1');
+    this.range     = this.options.range || $R(0,1);
+    
+    this.value     = 0; // assure backwards compat
+    this.values    = this.handles.map( function() { return 0 });
+    this.spans     = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false;
+    this.options.startSpan = $(this.options.startSpan || null);
+    this.options.endSpan   = $(this.options.endSpan || null);
+
+    this.restricted = this.options.restricted || false;
+
+    this.maximum   = this.options.maximum || this.range.end;
+    this.minimum   = this.options.minimum || this.range.start;
+
+    // Will be used to align the handle onto the track, if necessary
+    this.alignX = parseInt(this.options.alignX || '0');
+    this.alignY = parseInt(this.options.alignY || '0');
+    
+    this.trackLength = this.maximumOffset() - this.minimumOffset();
+
+    this.handleLength = this.isVertical() ? 
+      (this.handles[0].offsetHeight != 0 ? 
+        this.handles[0].offsetHeight : this.handles[0].style.height.replace(/px$/,"")) : 
+      (this.handles[0].offsetWidth != 0 ? this.handles[0].offsetWidth : 
+        this.handles[0].style.width.replace(/px$/,""));
+
+    this.active   = false;
+    this.dragging = false;
+    this.disabled = false;
+
+    if (this.options.disabled) this.setDisabled();
+
+    // Allowed values array
+    this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false;
+    if (this.allowedValues) {
+      this.minimum = this.allowedValues.min();
+      this.maximum = this.allowedValues.max();
+    }
+
+    this.eventMouseDown = this.startDrag.bindAsEventListener(this);
+    this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
+    this.eventMouseMove = this.update.bindAsEventListener(this);
+
+    // Initialize handles in reverse (make sure first handle is active)
+    this.handles.each( function(h,i) {
+      i = slider.handles.length-1-i;
+      slider.setValue(parseFloat(
+        (Object.isArray(slider.options.sliderValue) ? 
+          slider.options.sliderValue[i] : slider.options.sliderValue) || 
+         slider.range.start), i);
+      h.makePositioned().observe("mousedown", slider.eventMouseDown);
+    });
+    
+    this.track.observe("mousedown", this.eventMouseDown);
+    document.observe("mouseup", this.eventMouseUp);
+    document.observe("mousemove", this.eventMouseMove);
+    
+    this.initialized = true;
+  },
+  dispose: function() {
+    var slider = this;    
+    Event.stopObserving(this.track, "mousedown", this.eventMouseDown);
+    Event.stopObserving(document, "mouseup", this.eventMouseUp);
+    Event.stopObserving(document, "mousemove", this.eventMouseMove);
+    this.handles.each( function(h) {
+      Event.stopObserving(h, "mousedown", slider.eventMouseDown);
+    });
+  },
+  setDisabled: function(){
+    this.disabled = true;
+  },
+  setEnabled: function(){
+    this.disabled = false;
+  },  
+  getNearestValue: function(value){
+    if (this.allowedValues){
+      if (value >= this.allowedValues.max()) return(this.allowedValues.max());
+      if (value <= this.allowedValues.min()) return(this.allowedValues.min());
+      
+      var offset = Math.abs(this.allowedValues[0] - value);
+      var newValue = this.allowedValues[0];
+      this.allowedValues.each( function(v) {
+        var currentOffset = Math.abs(v - value);
+        if (currentOffset <= offset){
+          newValue = v;
+          offset = currentOffset;
+        } 
+      });
+      return newValue;
+    }
+    if (value > this.range.end) return this.range.end;
+    if (value < this.range.start) return this.range.start;
+    return value;
+  },
+  setValue: function(sliderValue, handleIdx){
+    if (!this.active) {
+      this.activeHandleIdx = handleIdx || 0;
+      this.activeHandle    = this.handles[this.activeHandleIdx];
+      this.updateStyles();
+    }
+    handleIdx = handleIdx || this.activeHandleIdx || 0;
+    if (this.initialized && this.restricted) {
+      if ((handleIdx>0) && (sliderValue<this.values[handleIdx-1]))
+        sliderValue = this.values[handleIdx-1];
+      if ((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1]))
+        sliderValue = this.values[handleIdx+1];
+    }
+    sliderValue = this.getNearestValue(sliderValue);
+    this.values[handleIdx] = sliderValue;
+    this.value = this.values[0]; // assure backwards compat
+    
+    this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] = 
+      this.translateToPx(sliderValue);
+    
+    this.drawSpans();
+    if (!this.dragging || !this.event) this.updateFinished();
+  },
+  setValueBy: function(delta, handleIdx) {
+    this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta, 
+      handleIdx || this.activeHandleIdx || 0);
+  },
+  translateToPx: function(value) {
+    return Math.round(
+      ((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) * 
+      (value - this.range.start)) + "px";
+  },
+  translateToValue: function(offset) {
+    return ((offset/(this.trackLength-this.handleLength) * 
+      (this.range.end-this.range.start)) + this.range.start);
+  },
+  getRange: function(range) {
+    var v = this.values.sortBy(Prototype.K); 
+    range = range || 0;
+    return $R(v[range],v[range+1]);
+  },
+  minimumOffset: function(){
+    return(this.isVertical() ? this.alignY : this.alignX);
+  },
+  maximumOffset: function(){
+    return(this.isVertical() ? 
+      (this.track.offsetHeight != 0 ? this.track.offsetHeight :
+        this.track.style.height.replace(/px$/,"")) - this.alignY : 
+      (this.track.offsetWidth != 0 ? this.track.offsetWidth : 
+        this.track.style.width.replace(/px$/,"")) - this.alignX);
+  },  
+  isVertical:  function(){
+    return (this.axis == 'vertical');
+  },
+  drawSpans: function() {
+    var slider = this;
+    if (this.spans)
+      $R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) });
+    if (this.options.startSpan)
+      this.setSpan(this.options.startSpan,
+        $R(0, this.values.length>1 ? this.getRange(0).min() : this.value ));
+    if (this.options.endSpan)
+      this.setSpan(this.options.endSpan, 
+        $R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum));
+  },
+  setSpan: function(span, range) {
+    if (this.isVertical()) {
+      span.style.top = this.translateToPx(range.start);
+      span.style.height = this.translateToPx(range.end - range.start + this.range.start);
+    } else {
+      span.style.left = this.translateToPx(range.start);
+      span.style.width = this.translateToPx(range.end - range.start + this.range.start);
+    }
+  },
+  updateStyles: function() {
+    this.handles.each( function(h){ Element.removeClassName(h, 'selected') });
+    Element.addClassName(this.activeHandle, 'selected');
+  },
+  startDrag: function(event) {
+    if (Event.isLeftClick(event)) {
+      if (!this.disabled){
+        this.active = true;
+        
+        var handle = Event.element(event);
+        var pointer  = [Event.pointerX(event), Event.pointerY(event)];
+        var track = handle;
+        if (track==this.track) {
+          var offsets  = Position.cumulativeOffset(this.track); 
+          this.event = event;
+          this.setValue(this.translateToValue( 
+           (this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2)
+          ));
+          var offsets  = Position.cumulativeOffset(this.activeHandle);
+          this.offsetX = (pointer[0] - offsets[0]);
+          this.offsetY = (pointer[1] - offsets[1]);
+        } else {
+          // find the handle (prevents issues with Safari)
+          while((this.handles.indexOf(handle) == -1) && handle.parentNode) 
+            handle = handle.parentNode;
+            
+          if (this.handles.indexOf(handle)!=-1) {
+            this.activeHandle    = handle;
+            this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
+            this.updateStyles();
+            
+            var offsets  = Position.cumulativeOffset(this.activeHandle);
+            this.offsetX = (pointer[0] - offsets[0]);
+            this.offsetY = (pointer[1] - offsets[1]);
+          }
+        }
+      }
+      Event.stop(event);
+    }
+  },
+  update: function(event) {
+   if (this.active) {
+      if (!this.dragging) this.dragging = true;
+      this.draw(event);
+      if (Prototype.Browser.WebKit) window.scrollBy(0,0);
+      Event.stop(event);
+   }
+  },
+  draw: function(event) {
+    var pointer = [Event.pointerX(event), Event.pointerY(event)];
+    var offsets = Position.cumulativeOffset(this.track);
+    pointer[0] -= this.offsetX + offsets[0];
+    pointer[1] -= this.offsetY + offsets[1];
+    this.event = event;
+    this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
+    if (this.initialized && this.options.onSlide)
+      this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
+  },
+  endDrag: function(event) {
+    if (this.active && this.dragging) {
+      this.finishDrag(event, true);
+      Event.stop(event);
+    }
+    this.active = false;
+    this.dragging = false;
+  },  
+  finishDrag: function(event, success) {
+    this.active = false;
+    this.dragging = false;
+    this.updateFinished();
+  },
+  updateFinished: function() {
+    if (this.initialized && this.options.onChange) 
+      this.options.onChange(this.values.length>1 ? this.values : this.value, this);
+    this.event = null;
+  }
+});
diff --git a/webcit/static/sound.js b/webcit/static/sound.js
new file mode 100644 (file)
index 0000000..eba3843
--- /dev/null
@@ -0,0 +1,55 @@
+// script.aculo.us sound.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
+
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//
+// Based on code created by Jules Gravinese (http://www.webveteran.com/)
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+Sound = {
+  tracks: {},
+  _enabled: true,
+  template:
+    new Template('<embed style="height:0" id="sound_#{track}_#{id}" src="#{url}" loop="false" autostart="true" hidden="true"/>'),
+  enable: function(){
+    Sound._enabled = true;
+  },
+  disable: function(){
+    Sound._enabled = false;
+  },
+  play: function(url){
+    if(!Sound._enabled) return;
+    var options = Object.extend({
+      track: 'global', url: url, replace: false
+    }, arguments[1] || {});
+    
+    if(options.replace && this.tracks[options.track]) {
+      $R(0, this.tracks[options.track].id).each(function(id){
+        var sound = $('sound_'+options.track+'_'+id);
+        sound.Stop && sound.Stop();
+        sound.remove();
+      })
+      this.tracks[options.track] = null;
+    }
+      
+    if(!this.tracks[options.track])
+      this.tracks[options.track] = { id: 0 }
+    else
+      this.tracks[options.track].id++;
+      
+    options.id = this.tracks[options.track].id;
+    $$('body')[0].insert( 
+      Prototype.Browser.IE ? new Element('bgsound',{
+        id: 'sound_'+options.track+'_'+options.id,
+        src: options.url, loop: 1, autostart: true
+      }) : Sound.template.evaluate(options));
+  }
+};
+
+if(Prototype.Browser.Gecko && navigator.userAgent.indexOf("Win") > 0){
+  if(navigator.plugins && $A(navigator.plugins).detect(function(p){ return p.name.indexOf('QuickTime') != -1 }))
+    Sound.template = new Template('<object id="sound_#{track}_#{id}" width="0" height="0" type="audio/mpeg" data="#{url}"/>')
+  else
+    Sound.play = function(){}
+}
diff --git a/webcit/static/table.js b/webcit/static/table.js
new file mode 100644 (file)
index 0000000..98321d9
--- /dev/null
@@ -0,0 +1,111 @@
+var categories;
+/** Task view table sorter
+ * Written by Mathew McBride <matt@mcbridematt.dhs.org>
+ */
+function gatherCategoriesFromTable() {
+       var tbody = document.getElementById("taskview");
+       var childNodes = tbody.childNodes;
+       for (i=0; i<=childNodes.length; i++) {
+               var child = childNodes[i]; // Should be TR
+               if (child != undefined && child.nodeName == "TR") {
+                       var childTds = child.getElementsByTagName("TD");
+                       if (childTds.length == 4) {
+                       var categoryTd = childTds[3];
+                       if (categoryTd != undefined) {
+                               // Get text child
+                               if (categoryTd.childNodes.length > 0 &&
+                                       categoryTd.childNodes[0].nodeType == 3) {
+                                               categories[categoryTd.childNodes[0].nodeValue]
+                                                       = categoryTd.childNodes[0].nodeValue;
+                                       }
+                       }
+               }
+       }
+}
+}
+function addCategoriesToSelector() {
+       var selector = document.getElementById("selectcategory");
+       for (description in categories) {
+               var newOptionElement = document.createElement("option");
+               newOptionElement.setAttribute("value", categories[description]);
+               var text = document.createTextNode(categories[description]);
+               newOptionElement.appendChild(text);
+               selector.appendChild(newOptionElement);
+       }
+}
+function filterCategories(event) {
+       hideAllExistingRows();
+       var selector = document.getElementById("selectcategory");
+       var selected = selector.selectedIndex;
+       var selectedCategory = selector.options[selected];
+               var tbody = document.getElementById("taskview");
+       var cat = selectedCategory.getAttribute("value");
+       var nodesToUnhide = new Array();
+       var curIndex = 0;
+       // Hunt down all the rows with this category using XPath
+       if (document.evaluate) { // Only if we can do so, of course 
+               var debugText = "";
+               var toEvaluate = null;
+               if (cat != 'showall') {
+               toEvaluate = "//tr[td='"+cat+"']";
+               } else {
+                       toEvaluate = "//tr[td]";
+               }
+               var trNodes = document.evaluate(toEvaluate,
+               document,
+               null,
+               XPathResult.ANY_TYPE,
+               null);
+       var trNode = trNodes.iterateNext();
+       while(trNode) {
+               debugText += "<br>"+trNode.nodeName;
+               nodesToUnhide[curIndex++] = trNode;
+               trNode = trNodes.iterateNext();
+       } 
+               
+       }
+       for (i=0;i<curIndex;i++) {
+               nodesToUnhide[i].style.display = "table-row";
+               if (((i-1) % 2) == 0) {
+                       nodesToUnhide[i].setAttribute("class","table-alt-row");
+               }
+       }
+}
+function hideAllExistingRows() {
+       var nodes = new Array();
+       var curIndex = 0;
+       if (document.evaluate) { // Only if we can do so, of course 
+               var debugText = "";
+               var toEvaluate = "//tr/td";
+               var tdNodes = document.evaluate(toEvaluate,
+               document,
+               null,
+               XPathResult.ANY_TYPE,
+               null);
+       var tdNode = tdNodes.iterateNext();
+       while(tdNode) {
+               // Get parent
+               var parent = tdNode.parentNode;
+               nodes[curIndex++] = parent;
+               tdNode = tdNodes.iterateNext();
+       } 
+       }
+       for(i=0;i<curIndex;i++) {
+               nodes[i].style.display = "none";
+               nodes[i].removeAttribute("class");
+       }
+}
+
+function taskViewActivate(event ) {
+       // Do not run if not tasks, do not run without XPath3
+       if (document.getElementById("taskview") != null && document.evaluate != null) {
+       // var count = countRowsInTaskView();
+       categories = new Object();
+       gatherCategoriesFromTable();
+       addCategoriesToSelector();
+       
+       $('selectcategory').observe('change', filterCategories);
+       filterCategories(null); // color the rows first
+       }
+}
+
index b53d65ed7d5fe91d340b32e9556fae9753ed7e8d..7eed8a5490e75e03b147934b21e6160d28e86fc6 100644 (file)
@@ -1,38 +1,27 @@
-// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
-//           (c) 2005 Jon Tirsen (http://www.tirsen.com)
-//           (c) 2005 Michael Schuerig (http://www.schuerig.de/michael/)
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-// 
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-// 
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+// script.aculo.us unittest.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
 
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//           (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
+//           (c) 2005-2007 Michael Schuerig (http://www.schuerig.de/michael/)
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
 
 // experimental, Firefox-only
 Event.simulateMouse = function(element, eventName) {
   var options = Object.extend({
     pointerX: 0,
     pointerY: 0,
-    buttons: 0
+    buttons:  0,
+    ctrlKey:  false,
+    altKey:   false,
+    shiftKey: false,
+    metaKey:  false
   }, arguments[2] || {});
   var oEvent = document.createEvent("MouseEvents");
   oEvent.initMouseEvent(eventName, true, true, document.defaultView, 
     options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY, 
-    false, false, false, false, 0, $(element));
+    options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 0, $(element));
   
   if(this.mark) Element.remove(this.mark);
   this.mark = document.createElement('div');
@@ -82,24 +71,7 @@ var Test = {}
 Test.Unit = {};
 
 // security exception workaround
-Test.Unit.inspect = function(obj) {
-  var info = [];
-
-  if(typeof obj=="string" || 
-     typeof obj=="number") {
-    return obj;
-  } else {
-    for(property in obj)
-      if(typeof obj[property]!="function")
-        info.push(property + ' => ' + 
-          (typeof obj[property] == "string" ?
-            '"' + obj[property] + '"' :
-            obj[property]));
-  }
-
-  return ("'" + obj + "' #" + typeof obj + 
-    ": {" + info.join(", ") + "}");
-}
+Test.Unit.inspect = Object.inspect;
 
 Test.Unit.Logger = Class.create();
 Test.Unit.Logger.prototype = {
@@ -115,6 +87,7 @@ Test.Unit.Logger.prototype = {
     this.lastLogLine = document.createElement('tr');
     this.statusCell = document.createElement('td');
     this.nameCell = document.createElement('td');
+    this.nameCell.className = "nameCell";
     this.nameCell.appendChild(document.createTextNode(testName));
     this.messageCell = document.createElement('td');
     this.lastLogLine.appendChild(this.statusCell);
@@ -127,6 +100,7 @@ Test.Unit.Logger.prototype = {
     this.lastLogLine.className = status;
     this.statusCell.innerHTML = status;
     this.messageCell.innerHTML = this._toHTML(summary);
+    this.addLinksToResults();
   },
   message: function(message) {
     if (!this.log) return;
@@ -148,6 +122,16 @@ Test.Unit.Logger.prototype = {
   },
   _toHTML: function(txt) {
     return txt.escapeHTML().replace(/\n/g,"<br/>");
+  },
+  addLinksToResults: function(){ 
+    $$("tr.failed .nameCell").each( function(td){ // todo: limit to children of this.log
+      td.title = "Run only this test"
+      Event.observe(td, 'click', function(){ window.location.search = "?tests=" + td.innerHTML;});
+    });
+    $$("tr.passed .nameCell").each( function(td){ // todo: limit to children of this.log
+      td.title = "Run all tests"
+      Event.observe(td, 'click', function(){ window.location.search = "";});
+    });
   }
 }
 
@@ -158,6 +142,7 @@ Test.Unit.Runner.prototype = {
       testLog: 'testlog'
     }, arguments[1] || {});
     this.options.resultsURL = this.parseResultsURLQueryParameter();
+    this.options.tests      = this.parseTestsQueryParameter();
     if (this.options.testLog) {
       this.options.testLog = $(this.options.testLog) || null;
     }
@@ -175,7 +160,11 @@ Test.Unit.Runner.prototype = {
         this.tests = [];
         for(var testcase in testcases) {
           if(/^test/.test(testcase)) {
-            this.tests.push(new Test.Unit.Testcase(testcase, testcases[testcase], testcases["setup"], testcases["teardown"]));
+            this.tests.push(
+               new Test.Unit.Testcase(
+                 this.options.context ? ' -> ' + this.options.titles[testcase] : testcase, 
+                 testcases[testcase], testcases["setup"], testcases["teardown"]
+               ));
           }
         }
       }
@@ -187,6 +176,11 @@ Test.Unit.Runner.prototype = {
   parseResultsURLQueryParameter: function() {
     return window.location.search.parseQuery()["resultsURL"];
   },
+  parseTestsQueryParameter: function(){
+    if (window.location.search.parseQuery()["tests"]){
+        return window.location.search.parseQuery()["tests"].split(',');
+    };
+  },
   // Returns:
   //  "ERROR" if there was an error,
   //  "FAILURE" if there was a failure, or
@@ -246,6 +240,7 @@ Test.Unit.Runner.prototype = {
       errors     +=   this.tests[i].errors;
     }
     return (
+      (this.options.context ? this.options.context + ': ': '') + 
       this.tests.length + " tests, " + 
       assertions + " assertions, " + 
       failures   + " failures, " +
@@ -275,6 +270,9 @@ Test.Unit.Assertions.prototype = {
     this.failures++;
     this.messages.push("Failure: " + message);
   },
+  info: function(message) {
+    this.messages.push("Info: " + message);
+  },
   error: function(error) {
     this.errors++;
     this.messages.push(error.name + ": "+ error.message + "(" + Test.Unit.inspect(error) +")");
@@ -297,18 +295,54 @@ Test.Unit.Assertions.prototype = {
         '", actual "' + Test.Unit.inspect(actual) + '"'); }
     catch(e) { this.error(e); }
   },
+  assertInspect: function(expected, actual) {
+    var message = arguments[2] || "assertInspect";
+    try { (expected == actual.inspect()) ? this.pass() :
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 
+        '", actual "' + Test.Unit.inspect(actual) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertEnumEqual: function(expected, actual) {
+    var message = arguments[2] || "assertEnumEqual";
+    try { $A(expected).length == $A(actual).length && 
+      expected.zip(actual).all(function(pair) { return pair[0] == pair[1] }) ?
+        this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) + 
+          ', actual ' + Test.Unit.inspect(actual)); }
+    catch(e) { this.error(e); }
+  },
   assertNotEqual: function(expected, actual) {
     var message = arguments[2] || "assertNotEqual";
     try { (expected != actual) ? this.pass() : 
       this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); }
     catch(e) { this.error(e); }
   },
+  assertIdentical: function(expected, actual) { 
+    var message = arguments[2] || "assertIdentical"; 
+    try { (expected === actual) ? this.pass() : 
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
+        '", actual "' + Test.Unit.inspect(actual) + '"'); } 
+    catch(e) { this.error(e); } 
+  },
+  assertNotIdentical: function(expected, actual) { 
+    var message = arguments[2] || "assertNotIdentical"; 
+    try { !(expected === actual) ? this.pass() : 
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
+        '", actual "' + Test.Unit.inspect(actual) + '"'); } 
+    catch(e) { this.error(e); } 
+  },
   assertNull: function(obj) {
     var message = arguments[1] || 'assertNull'
     try { (obj==null) ? this.pass() : 
       this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); }
     catch(e) { this.error(e); }
   },
+  assertMatch: function(expected, actual) {
+    var message = arguments[2] || 'assertMatch';
+    var regex = new RegExp(expected);
+    try { (regex.exec(actual)) ? this.pass() :
+      this.fail(message + ' : regex: "' +  Test.Unit.inspect(expected) + ' did not match: ' + Test.Unit.inspect(actual) + '"'); }
+    catch(e) { this.error(e); }
+  },
   assertHidden: function(element) {
     var message = arguments[1] || 'assertHidden';
     this.assertEqual("none", element.style.display, message);
@@ -317,6 +351,22 @@ Test.Unit.Assertions.prototype = {
     var message = arguments[1] || 'assertNotNull';
     this.assert(object != null, message);
   },
+  assertType: function(expected, actual) {
+    var message = arguments[2] || 'assertType';
+    try { 
+      (actual.constructor == expected) ? this.pass() : 
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
+        '", actual "' + (actual.constructor) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertNotOfType: function(expected, actual) {
+    var message = arguments[2] || 'assertNotOfType';
+    try { 
+      (actual.constructor != expected) ? this.pass() : 
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
+        '", actual "' + (actual.constructor) + '"'); }
+    catch(e) { this.error(e); }
+  },
   assertInstanceOf: function(expected, actual) {
     var message = arguments[2] || 'assertInstanceOf';
     try { 
@@ -331,6 +381,63 @@ Test.Unit.Assertions.prototype = {
       this.fail(message + ": object was an instance of the not expected type"); }
     catch(e) { this.error(e); } 
   },
+  assertRespondsTo: function(method, obj) {
+    var message = arguments[2] || 'assertRespondsTo';
+    try {
+      (obj[method] && typeof obj[method] == 'function') ? this.pass() : 
+      this.fail(message + ": object doesn't respond to [" + method + "]"); }
+    catch(e) { this.error(e); }
+  },
+  assertReturnsTrue: function(method, obj) {
+    var message = arguments[2] || 'assertReturnsTrue';
+    try {
+      var m = obj[method];
+      if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
+      m() ? this.pass() : 
+      this.fail(message + ": method returned false"); }
+    catch(e) { this.error(e); }
+  },
+  assertReturnsFalse: function(method, obj) {
+    var message = arguments[2] || 'assertReturnsFalse';
+    try {
+      var m = obj[method];
+      if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
+      !m() ? this.pass() : 
+      this.fail(message + ": method returned true"); }
+    catch(e) { this.error(e); }
+  },
+  assertRaise: function(exceptionName, method) {
+    var message = arguments[2] || 'assertRaise';
+    try { 
+      method();
+      this.fail(message + ": exception expected but none was raised"); }
+    catch(e) {
+      ((exceptionName == null) || (e.name==exceptionName)) ? this.pass() : this.error(e); 
+    }
+  },
+  assertElementsMatch: function() {
+    var expressions = $A(arguments), elements = $A(expressions.shift());
+    if (elements.length != expressions.length) {
+      this.fail('assertElementsMatch: size mismatch: ' + elements.length + ' elements, ' + expressions.length + ' expressions');
+      return false;
+    }
+    elements.zip(expressions).all(function(pair, index) {
+      var element = $(pair.first()), expression = pair.last();
+      if (element.match(expression)) return true;
+      this.fail('assertElementsMatch: (in index ' + index + ') expected ' + expression.inspect() + ' but got ' + element.inspect());
+    }.bind(this)) && this.pass();
+  },
+  assertElementMatches: function(element, expression) {
+    this.assertElementsMatch([element], expression);
+  },
+  benchmark: function(operation, iterations) {
+    var startAt = new Date();
+    (iterations || 1).times(operation);
+    var timeTaken = ((new Date())-startAt);
+    this.info((arguments[2] || 'Operation') + ' finished ' + 
+       iterations + ' iterations in ' + (timeTaken/1000)+'s' );
+    return timeTaken;
+  },
   _isVisible: function(element) {
     element = $(element);
     if(!element.parentNode) return true;
@@ -345,6 +452,14 @@ Test.Unit.Assertions.prototype = {
   },
   assertVisible: function(element) {
     this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1]));
+  },
+  benchmark: function(operation, iterations) {
+    var startAt = new Date();
+    (iterations || 1).times(operation);
+    var timeTaken = ((new Date())-startAt);
+    this.info((arguments[2] || 'Operation') + ' finished ' + 
+       iterations + ' iterations in ' + (timeTaken/1000)+'s' );
+    return timeTaken;
   }
 }
 
@@ -353,7 +468,17 @@ Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.p
   initialize: function(name, test, setup, teardown) {
     Test.Unit.Assertions.prototype.initialize.bind(this)();
     this.name           = name;
-    this.test           = test || function() {};
+    
+    if(typeof test == 'string') {
+      test = test.gsub(/(\.should[^\(]+\()/,'#{0}this,');
+      test = test.gsub(/(\.should[^\(]+)\(this,\)/,'#{1}(this)');
+      this.test = function() {
+        eval('with(this){'+test+'}');
+      }
+    } else {
+      this.test = test || function() {};
+    }
+    
     this.setup          = setup || function() {};
     this.teardown       = teardown || function() {};
     this.isWaiting      = false;
@@ -378,4 +503,66 @@ Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.p
     }
     catch(e) { this.error(e); }
   }
-});
\ No newline at end of file
+});
+
+// *EXPERIMENTAL* BDD-style testing to please non-technical folk
+// This draws many ideas from RSpec http://rspec.rubyforge.org/
+
+Test.setupBDDExtensionMethods = function(){
+  var METHODMAP = {
+    shouldEqual:     'assertEqual',
+    shouldNotEqual:  'assertNotEqual',
+    shouldEqualEnum: 'assertEnumEqual',
+    shouldBeA:       'assertType',
+    shouldNotBeA:    'assertNotOfType',
+    shouldBeAn:      'assertType',
+    shouldNotBeAn:   'assertNotOfType',
+    shouldBeNull:    'assertNull',
+    shouldNotBeNull: 'assertNotNull',
+    
+    shouldBe:        'assertReturnsTrue',
+    shouldNotBe:     'assertReturnsFalse',
+    shouldRespondTo: 'assertRespondsTo'
+  };
+  var makeAssertion = function(assertion, args, object) { 
+       this[assertion].apply(this,(args || []).concat([object]));
+  }
+  
+  Test.BDDMethods = {};   
+  $H(METHODMAP).each(function(pair) { 
+    Test.BDDMethods[pair.key] = function() { 
+       var args = $A(arguments); 
+       var scope = args.shift(); 
+       makeAssertion.apply(scope, [pair.value, args, this]); }; 
+  });
+  
+  [Array.prototype, String.prototype, Number.prototype, Boolean.prototype].each(
+    function(p){ Object.extend(p, Test.BDDMethods) }
+  );
+}
+
+Test.context = function(name, spec, log){
+  Test.setupBDDExtensionMethods();
+  
+  var compiledSpec = {};
+  var titles = {};
+  for(specName in spec) {
+    switch(specName){
+      case "setup":
+      case "teardown":
+        compiledSpec[specName] = spec[specName];
+        break;
+      default:
+        var testName = 'test'+specName.gsub(/\s+/,'-').camelize();
+        var body = spec[specName].toString().split('\n').slice(1);
+        if(/^\{/.test(body[0])) body = body.slice(1);
+        body.pop();
+        body = body.map(function(statement){ 
+          return statement.strip()
+        });
+        compiledSpec[testName] = body.join('\n');
+        titles[testName] = specName;
+    }
+  }
+  new Test.Unit.Runner(compiledSpec, { titles: titles, testLog: log || 'testlog', context: name });
+};
\ No newline at end of file
index 581903e09e0eae62ea912189dfbc044d66334c16..931d8e241893ec66731233dbfb613ed8380b8d33 100644 (file)
@@ -16,8 +16,8 @@ if (window.navigator.userAgent.toLowerCase().match("gecko")) {
 }
 
 var ns6=document.getElementById&&!document.all;
-
-
+Event.observe(window, 'load', ToggleTaskDateOrNoDateActivate);
+Event.observe(window, 'load', taskViewActivate);
 function CtdlRandomString()  {
        return((Math.random()+'').substr(3));
 }
@@ -808,32 +808,6 @@ elem.innerHTML = "<div align=center><br><table border=0 cellpadding=10 bgcolor=\
 }
 
 
-// Show info for a user, basically replaces showuser()
-// matt@comalies is to blame for this poorly coded masterpiece. 
-function CtdlShowUserInfoPopup(Element) {
-       try {
-               // hopefully no one needs to use the class attribute... could be better done 
-               // with xmlns though..
-               var user = Element.getAttribute("class");
-               var updname = "biospace_"+user;
-               if (document.getElementById(updname) == null) {
-                       // insert a space for the bio
-                       var pNode = Element.parentNode;
-                       var newdiv = document.createElement("div");
-                       newdiv.id = updname;
-                       newdiv.innerHTML = "Getting user info....";
-                       pNode.appendChild(newdiv);
-                       CtdlLoadScreen(updname);
-                       new Ajax.Updater(updname, 'showuser_ajax?who='+user, { method: 'get' } );
-               }
-       }
-       catch(err) {
-               return true;
-       }
-       return false;
-}
-
-
 
 // Pop open the address book (target_input is the INPUT field to populate)
 
@@ -893,3 +867,35 @@ function HandleRSVP(question_divname, title_divname, msgnum, cal_partnum, sc) {
        Effect.Fade(question_divname, { duration: 0.5 });
 }
 
+// TODO: Collapse into one function
+function toggleTaskDtStart(event) {
+       var checkBox = $('nodtstart');
+       if (checkBox.checked) {
+               $('dtstart').disabled = true;
+               $('dtstart').value = "";
+       } else {
+               $('dtstart').disabled = false;
+       }
+}
+function toggleTaskDue(event) {
+       var checkBox = $('nodue');
+       if (checkBox.checked) {
+               $('due').disabled = true;
+               $('due').value = "";
+       } else {
+               $('due').disabled = false;
+       }
+}
+function ToggleTaskDateOrNoDateActivate(event) {
+       var dtstart = document.getElementById("nodtstart");
+       if (dtstart != null) {
+               toggleTaskDtStart(null);
+               toggleTaskDue(null);
+               $('nodtstart').observe('click', toggleTaskDtStart);
+               $('nodue').observe('click', toggleTaskDue);
+       } 
+}
+function TaskViewGatherCategoriesFromTable() {
+       var table = $('taskview');
+       
+}
index d95b7ded1bc3b644cb7ae1294a36135b65e7393a..3015ad70eae4126a41e46b4839fb69bdda21ab59 100644 (file)
@@ -1,3 +1,4 @@
+@import url("datepicker.css");
 @media print {
        input#toggler, .toolbar { display: none }
 }
@@ -675,6 +676,9 @@ body, #global {
        vertical-align: middle;
        padding-left: 2px;
        padding-right: 5px;
+       width: 32px;
+       height: 32px;
+       border: none;
 }
 
 #button li a {
@@ -1322,6 +1326,19 @@ li.event_unread span, a.event_read_title {
        padding: 4px;
 }
 
+.roompic {
+      height: 48px;
+      width: 48px;
+      border: none;
+}
+#navbar ul li a img {
+      vertical-align: middle;
+      border: none;
+}
+.table-alt-row {
+      background-color: #DDDDDD;
+}
+
 .stickynote_outer {
        position: absolute;
        width: 200px;
index cd9e08103cbfbe3227341b57986e6f83df074914..7887e0a18d4702f3dc87147adac8bb63e5588701 100644 (file)
@@ -929,6 +929,44 @@ void output_image()
        output_static(blank_gif);
 }
 
+/**
+  * \brief Extract an embedded photo from a vCard for display on the client
+  *
+  * \param msgnum
+  */
+void display_vcard_photo_img(char *msgnum_as_string)
+{
+       long msgnum = 0L;
+       char *vcard;
+       struct vCard *v;
+       char *xferbuf;
+    char *photosrc;
+       int decoded;
+       const char *contentType;
+
+       msgnum = atol(msgnum_as_string);
+       
+       vcard = load_mimepart(msgnum,"1");
+       v = vcard_load(vcard);
+       
+       photosrc = vcard_get_prop(v, "PHOTO", 1,0,0);
+       xferbuf = malloc(strlen(photosrc));
+       if (xferbuf == NULL) {
+               lprintf(5, "xferbuf malloc failed\n");
+               return;
+       }
+       memset(xferbuf, 1, SIZ);
+       decoded = CtdlDecodeBase64(
+               xferbuf,
+               photosrc,
+               strlen(photosrc));
+       contentType = GuessMimeType(xferbuf, decoded);
+       http_transmit_thing(xferbuf, decoded, contentType, 0);
+       free(v);
+       free(photosrc);
+       free(xferbuf);
+}
+
 /**
  * \brief Generic function to output an arbitrary MIME part from an arbitrary
  *        message number on the server.
@@ -1783,6 +1821,8 @@ void session_loop(struct httprequest *req)
                print_message(index[1]);
        } else if (!strcasecmp(action, "msgheaders")) {
                display_headers(index[1]);
+       } else if (!strcasecmp(action, "vcardphoto")) {
+               display_vcard_photo_img(index[1]);      
        } else if (!strcasecmp(action, "wiki")) {
                display_wiki_page();
        } else if (!strcasecmp(action, "display_enter")) {