From 3fc2504fcc0a240ea2c933f23deaa57b23a02c53 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Wilfried=20G=C3=B6esgens?= Date: Thu, 1 May 2008 21:32:45 +0000 Subject: [PATCH] * added matts date picker widget * added matts vcard inline picture display * added matts html fixups * corrected BSTR to bstr * removed unneded modifications (blanks) * corrected coding style --- webcit/calendar.c | 311 ++-- webcit/calendar_tools.c | 191 +- webcit/calendar_view.c | 302 ++-- webcit/cookie_conversion.c | 3 +- webcit/iconbar.c | 30 +- webcit/messages.c | 23 +- webcit/roomops.c | 96 +- webcit/static/builder.js | 136 ++ webcit/static/controls.js | 918 ++++++---- webcit/static/datepicker-dev.js | 773 ++++++++ webcit/static/datepicker.css | 78 + webcit/static/dragdrop.js | 999 ++++++++--- webcit/static/effects.js | 1381 +++++++++----- webcit/static/head.html | 7 +- webcit/static/prototype.js | 2960 ++++++++++++++++++++----------- webcit/static/roombanner.html | 4 +- webcit/static/scriptaculous.js | 64 +- webcit/static/slider.js | 275 +++ webcit/static/sound.js | 55 + webcit/static/table.js | 111 ++ webcit/static/unittest.js | 277 ++- webcit/static/wclib.js | 62 +- webcit/static/webcit.css | 17 + webcit/webcit.c | 40 + 24 files changed, 6486 insertions(+), 2627 deletions(-) create mode 100644 webcit/static/builder.js create mode 100644 webcit/static/datepicker-dev.js create mode 100644 webcit/static/datepicker.css create mode 100644 webcit/static/slider.js create mode 100644 webcit/static/sound.js create mode 100644 webcit/static/table.js diff --git a/webcit/calendar.c b/webcit/calendar.c index 7e81620e4..fadadf6c1 100644 --- a/webcit/calendar.c +++ b/webcit/calendar.c @@ -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(""); wprintf(""); 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("
"); wprintf(_("Attendee:")); wprintf("
"); @@ -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("
%s", (is_update ? - _("Update:") : - _("CONFLICT:") - ) - ); + _("Update:") : + _("CONFLICT:") + ) + ); wprintf("
"); escputs(conflict_message); wprintf("
\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("

" @@ -250,10 +252,10 @@ void cal_process_object(icalcomponent *cal, _("Click Update 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("

 

\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 not been entered into your calendar.") - ); + "It has not 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 not been updated.") - ); + "Your calendar has not been updated.") + ); } wprintf("
"); } 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("
\n"); - wprintf(""); - wprintf("

"); + + // TODO: Can we take all this and move it into a template? + output_headers(1, 1, 1, 0, 0, 0); + wprintf(""); + p = icalcomponent_get_first_property(vtodo, ICAL_SUMMARY_PROPERTY); + // Get summary early for title + wprintf("
\n"); + wprintf("
"); wprintf(_("Edit task")); - wprintf("

"); - wprintf("
\n"); - - wprintf("
\n"); - - wprintf("
" - "
"); + wprintf("- "); + if (p != NULL) { + escputs((char *)icalproperty_get_comment(p)); + } + wprintf(""); + wprintf("
\n"); wprintf("
\n"); + wprintf("
\n "); wprintf("\n", WC->nonce); wprintf("\n", msgnum); - - wprintf("\n"); + wprintf(""); + wprintf("
"); + wprintf("\n"); wprintf("\n"); @@ -498,41 +514,70 @@ void display_edit_individual_task(icalcomponent *supplied_vtodo, long msgnum, ch wprintf(_("Due date:")); wprintf("\n"); + icalproperty_status todoStatus = icalcomponent_get_status(vtodo); + wprintf(""); + // start category field + p = icalcomponent_get_first_property(vtodo, ICAL_CATEGORIES_PROPERTY); + wprintf("\n "); + // end category field wprintf("
"); wprintf(_("Summary:")); @@ -485,12 +494,19 @@ void display_edit_individual_task(icalcomponent *supplied_vtodo, long msgnum, ch wprintf(_("Start date:")); wprintf(""); p = icalcomponent_get_first_property(vtodo, ICAL_DTSTART_PROPERTY); + 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("
"); p = icalcomponent_get_first_property(vtodo, ICAL_DUE_PROPERTY); + 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("
\n"); + wprintf(_("Completed:")); + wprintf(""); + wprintf(""); + wprintf("
"); + wprintf(_("Category:")); + wprintf(""); + wprintf(""); + wprintf("
"); wprintf(_("Description:")); wprintf(""); - wprintf("
\n"); - wprintf("
" + wprintf("" "" "  " "\n" "  " "\n" - "
\n", + "\n", _("Save"), _("Delete"), _("Cancel") - ); - + ); + wprintf("
"); wprintf("\n"); - - wprintf("
\n"); + wprintf("
\n"); + wprintf(""); 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); } - diff --git a/webcit/calendar_tools.c b/webcit/calendar_tools.c index d07fd6f38..f06b8e09f 100644 --- a/webcit/calendar_tools.c +++ b/webcit/calendar_tools.c @@ -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("\n"); - - wprintf(_("Day: ")); - wprintf("\n"); - - wprintf(_("Year: ")); - wprintf("\n"); - + wprintf(""); + wprintf(""); wprintf(_("Hour: ")); wprintf("\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; } } diff --git a/webcit/calendar_view.c b/webcit/calendar_view.c index 6ecfa5cd6..0cb0273d1 100644 --- a/webcit/calendar_view.c +++ b/webcit/calendar_view.c @@ -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) "\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("%s: %s
", _("From"), Cal->from); wprintf("%s ", _("Summary:")); @@ -273,7 +273,7 @@ void calendar_month_view_display_events(int year, int month, int day) wprintf("%s ", _("Location:")); escputs((char *)icalproperty_get_comment(q)); wprintf("
"); - } + } /** * 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("%i:%2i" + "" + "", + 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("%i:%2i" - "" - "", - 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("" - "%s%s", - daycolor, - sbuf, - daycolor, - ebuf); - - } + wprintf("" + "%s%s", + daycolor, + sbuf, + daycolor, + ebuf); } + } + } } @@ -566,9 +566,9 @@ void calendar_month_view(int year, int month, int day) { wprintf("
", ((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(""); @@ -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("" - " \n", - _("Week"), - weeknumber, - _("Hours"), - _("Subject"), - _("Start"), - _("End") - ); + " \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("\n", - daycolor, - weekday_name,tm.tm_mday, - daycolor); + "\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("
  • " - "unread)?"_unread":"_read", + wprintf("
  • " + "unread)?"_unread":"_read", Cal->cal_msgnum, year, month, day); wprintf("%s
    ", _("All day event")); wprintf("%s: %s
    ", _("From"), Cal->from); @@ -897,7 +897,7 @@ void calendar_day_view_display_events(time_t thetime, wprintf("%s ", _("Location:")); escputs((char *)icalproperty_get_comment(q)); wprintf("
    "); - } + } 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("
  • " - "unread)?"_unread":"_read", - Cal->cal_msgnum, year, month, day); + Cal->cal_msgnum, year, month, day); wprintf("%s
    ", _("Ongoing event")); wprintf("%s: %s
    ", _("From"), Cal->from); wprintf("%s ", _("Summary:")); @@ -935,7 +935,7 @@ void calendar_day_view_display_events(time_t thetime, wprintf("%s ", _("Location:")); escputs((char *)icalproperty_get_comment(q)); wprintf("
    "); - } + } webcit_fmt_date(buf, event_tt, 1); wprintf("%s %s
    ", _("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("%s ", _("Location:")); escputs((char *)icalproperty_get_comment(q)); wprintf("
    "); - } + } webcit_fmt_date(buf, event_tt, 1); wprintf("%s %s
    ", _("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
    ", hour); @@ -1116,7 +1116,7 @@ void calendar_day_view(int year, int month, int day) { wprintf("%d:00%s ", (hour <= 12 ? hour : hour-12), (hour < 12 ? "am" : "pm") - ); + ); } wprintf(""); @@ -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 ", hour); @@ -1144,7 +1144,7 @@ void calendar_day_view(int year, int month, int day) { wprintf("%d:00%s ", (hour <= 12 ? hour : hour-12), (hour < 12 ? "am" : "pm") - ); + ); } wprintf(""); @@ -1217,7 +1217,7 @@ void calendar_day_view(int year, int month, int day) { "%Y
    " "", &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)
    \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)
    \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("
    " - "
  • %s %s
    %s%s%s%s
    %s%s%s%s
    %s,%i." - "

    \n\n" + "
    \n\n" "\n" - ); + wprintf("\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("\n"); due = get_task_due_date(WC->disp_cal[i].cal); - webcit_fmt_date(buf, due, 0); - wprintf(""); + wprintf("\n", buf); + wprintf(""); + wprintf(""); } - wprintf("
    "); + wprintf(_("Completed?")); + wprintf(""); wprintf(_("Name of task")); wprintf(""); wprintf(_("Date due")); - wprintf("
    "); + wprintf(_("Category")); + wprintf(" ()
    ", - (bg ? "DDDDDD" : "FFFFFF") - ); - + wprintf("
    "); + icalproperty_status todoStatus = icalcomponent_get_status(WC->disp_cal[i].cal); + wprintf("\n"); p = icalcomponent_get_first_property(WC->disp_cal[i].cal, - ICAL_SUMMARY_PROPERTY); - wprintf("disp_cal[i].cal_msgnum ); urlescputs(WC->wc_roomname); wprintf("\">"); - wprintf(" "); + /* wprintf(" "); */ if (p != NULL) { escputs((char *)icalproperty_get_comment(p)); } @@ -1487,15 +1508,26 @@ void do_tasks_view(void) { wprintf(" 0) { + webcit_fmt_date(buf, due, 0); + wprintf(">%s",buf); + } + else { + wprintf(">"); + } + wprintf(""); + p = icalcomponent_get_first_property(WC->disp_cal[i].cal, + ICAL_CATEGORIES_PROPERTY); + if (p != NULL) { + escputs((char *)icalproperty_get_categories(p)); } - wprintf(">%s
    \n"); + wprintf("\n"); /** Free the list */ free_calendar_buffer(); diff --git a/webcit/cookie_conversion.c b/webcit/cookie_conversion.c index 8b0adab6d..328d72fe1 100644 --- a/webcit/cookie_conversion.c +++ b/webcit/cookie_conversion.c @@ -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 " " " + "title=\"%s\"> " "\"%s\" " "\n", @@ -112,7 +112,7 @@ void do_iconbar(void) { ">", _("Your summary page") ); if (ib_displayas != IB_TEXTONLY) { - wprintf("\"\""); } if (ib_displayas != IB_PICONLY) { @@ -129,7 +129,7 @@ void do_iconbar(void) { _("Go to your email inbox") ); if (ib_displayas != IB_TEXTONLY) { - wprintf("\"\""); } if (ib_displayas != IB_PICONLY) { @@ -154,7 +154,7 @@ void do_iconbar(void) { _("Go to your personal calendar") ); if (ib_displayas != IB_TEXTONLY) { - wprintf("\"\""); } 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("\"\""); } if (ib_displayas != IB_PICONLY) { @@ -188,7 +188,7 @@ void do_iconbar(void) { _("Go to your personal notes") ); if (ib_displayas != IB_TEXTONLY) { - wprintf("\"\""); } 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("\"\""); } 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("\"\""); } 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("\"\""); } if (ib_displayas != IB_PICONLY) { @@ -260,7 +260,7 @@ void do_iconbar(void) { ">" ); if (ib_displayas != IB_TEXTONLY) { - wprintf("\"\""); } 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("\"\""); } if (ib_displayas != IB_PICONLY) { @@ -294,7 +294,7 @@ void do_iconbar(void) { _("Room and system administration functions") ); if (ib_displayas != IB_TEXTONLY) { - wprintf("\"\""); } if (ib_displayas != IB_PICONLY) { @@ -311,7 +311,7 @@ void do_iconbar(void) { ); if (ib_displayas != IB_TEXTONLY) { - wprintf("\"\""); } if (ib_displayas != IB_PICONLY) { @@ -390,7 +390,7 @@ void do_iconbar_roomlist(void) { if (ib_citadel) if (ib_displayas != IB_TEXTONLY) wprintf( "\n", @@ -412,7 +412,7 @@ void do_iconbar_roomlist(void) { ); if (ib_displayas != IB_TEXTONLY) { - wprintf("\"\""); } if (ib_displayas != IB_PICONLY) { diff --git a/webcit/messages.c b/webcit/messages.c index 1f6d21abe..63d8cef69 100644 --- a/webcit/messages.c +++ b/webcit/messages.c @@ -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("\n"); } } + else if (!strcasecmp(firsttoken, "photo") && full && pass == 2) { + // Only output on second pass + wprintf(""); + wprintf(_("Photo:")); + wprintf(""); + wprintf("\"Contact",msgnum); + wprintf("\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); } diff --git a/webcit/roomops.c b/webcit/roomops.c index d2bc437ea..73a9b90da 100644 --- a/webcit/roomops.c +++ b/webcit/roomops.c @@ -333,37 +333,37 @@ void embed_room_graphic(void) { serv_getln(buf, sizeof buf); } else if (WC->wc_view == VIEW_ADDRESSBOOK) { - wprintf("\"\"" ); } else if ( (WC->wc_view == VIEW_CALENDAR) || (WC->wc_view == VIEW_CALBRIEF) ) { - wprintf("\"\"" ); } else if (WC->wc_view == VIEW_TASKS) { - wprintf("\"\"" ); } else if (WC->wc_view == VIEW_NOTES) { - wprintf("\"\"" ); } else if (WC->wc_view == VIEW_MAILBOX) { - wprintf("\"\"" ); } else { - wprintf("\"\"" ); @@ -380,7 +380,7 @@ void embed_view_o_matic(void) { int i; wprintf("
    \n"); - wprintf("\n", WC->nonce); + wprintf("\t
    \n\t\n", WC->nonce); wprintf(" " @@ -410,7 +410,7 @@ void embed_view_o_matic(void) { wprintf("\n"); } } - wprintf("\n"); + wprintf("
    \n"); } @@ -419,15 +419,15 @@ void embed_view_o_matic(void) { */ void embed_search_o_matic(void) { wprintf("
    \n"); - wprintf("\n", WC->nonce); + wprintf("
    \n", WC->nonce); wprintf(" \n" ); - wprintf("\n"); + wprintf("
    \n"); } @@ -512,7 +512,7 @@ void embed_room_banner(char *got, int navbar_style) { if (navbar_style == navbar_default) wprintf( "
  • " "" - "" + "\"\"" "%s" "
  • \n", _("Ungoto") ); @@ -521,7 +521,7 @@ void embed_room_banner(char *got, int navbar_style) { wprintf( "
  • " "" - "" + "\"\"" "%s" "
  • \n", _("Read new messages") ); @@ -533,8 +533,8 @@ void embed_room_banner(char *got, int navbar_style) { wprintf( "
  • " "" - "" + "" "" "%s" "
  • \n", _("View contacts") @@ -544,8 +544,8 @@ void embed_room_banner(char *got, int navbar_style) { wprintf( "
  • " "" - "" + "" "" "%s" "
  • \n", _("Day view") @@ -553,8 +553,8 @@ void embed_room_banner(char *got, int navbar_style) { wprintf( "
  • " "" - "" + "" "" "%s" "
  • \n", _("Month view") @@ -564,8 +564,8 @@ void embed_room_banner(char *got, int navbar_style) { wprintf( "
  • " "" - "" + "" "" "%s" "
  • \n", _("Calendar list") @@ -575,8 +575,8 @@ void embed_room_banner(char *got, int navbar_style) { wprintf( "
  • " "" - "" + "" "" "%s" "
  • \n", _("View tasks") @@ -586,8 +586,8 @@ void embed_room_banner(char *got, int navbar_style) { wprintf( "
  • " "" - "" + "" "" "%s" "
  • \n", _("View notes") @@ -597,8 +597,8 @@ void embed_room_banner(char *got, int navbar_style) { wprintf( "
  • " "" - "" + "" "" "%s" "
  • \n", _("View message list") @@ -608,8 +608,8 @@ void embed_room_banner(char *got, int navbar_style) { wprintf( "
  • " "" - "" + "" "" "%s" "
  • \n", _("Wiki home") @@ -619,8 +619,8 @@ void embed_room_banner(char *got, int navbar_style) { wprintf( "
  • " "" - "" + "" "" "%s" "
  • \n", _("Read all messages") @@ -635,8 +635,8 @@ void embed_room_banner(char *got, int navbar_style) { wprintf( "
  • " "" - "" + "" "%s" "
  • \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("\">" - "" + "" "%s" "\n", _("Add new event") ); @@ -658,8 +658,8 @@ void embed_room_banner(char *got, int navbar_style) { wprintf( "
  • " "" - "" + "" "%s" "
  • \n", _("Add new task") ); @@ -668,8 +668,8 @@ void embed_room_banner(char *got, int navbar_style) { wprintf( "
  • " "" - "" + "" "%s" "
  • \n", _("Add new note") ); @@ -680,8 +680,8 @@ void embed_room_banner(char *got, int navbar_style) { wprintf( "
  • " "" - "" + "" "%s" "
  • \n", buf, _("Edit this page") ); @@ -690,8 +690,8 @@ void embed_room_banner(char *got, int navbar_style) { wprintf( "
  • " "" - "" + "" "%s" "
  • \n", _("Write mail") ); @@ -700,8 +700,8 @@ void embed_room_banner(char *got, int navbar_style) { wprintf( "
  • " "" - "" + "" "%s" "
  • \n", _("Enter a message") ); @@ -713,7 +713,7 @@ void embed_room_banner(char *got, int navbar_style) { "
  • " "" - "" + "\"\"" "%s" "
  • \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) { "
  • " "" - "" + "\"\"" "%s" "
  • \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(""); - wprintf("\"\" ", icon); + wprintf("\"\" ", icon); } else { wprintf(""); diff --git a/webcit/static/builder.js b/webcit/static/builder.js new file mode 100644 index 000000000..830199944 --- /dev/null +++ b/webcit/static/builder.js @@ -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 + ">"; + } 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 + ">"; + } 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(/"/,'"') + '"'); + 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))); + } + }); + } +} diff --git a/webcit/static/controls.js b/webcit/static/controls.js index 43cdb3eaa..5012cb812 100644 --- a/webcit/static/controls.js +++ b/webcit/static/controls.js @@ -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 @@ -49,40 +36,50 @@ // 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, ''); 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 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 "
      " + ret.join('') + "
    "; } - }, 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
    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 (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 index 000000000..c558ff82d --- /dev/null +++ b/webcit/static/datepicker-dev.js @@ -0,0 +1,773 @@ +/** + * DatePicker widget using Prototype and Scriptaculous. + * (c) 2007 Mathieu Jondet + * Eulerian Technologies + * + * DatePicker is freely distributable under the same terms as Prototype. + * + */ + +/** + * DatePickerFormatter class for matching and stringifying dates. + * + * By Arturas Slajus . + */ +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évrier', 'Mars', 'Avril', 'Mai', 'Juin', + 'Juillet', 'Aout', 'Septembre', 'Octobre', 'Novembre', 'Dé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ärz', 'April', 'Mai', 'Juni', + 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember' ], + 'pt' : [ 'Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', + 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro' ], + 'hu' : [ 'Január', 'Február', 'Március', 'Április', + 'Május', 'Június', 'Július', 'Augusztus', 'Szeptember', + 'Október', 'November', 'December' ], + 'lt' : [ 'Sausis', 'Vasaris', 'Kovas', 'Balandis', 'Gegužė', + 'Birželis', 'Liepa', 'Rugjūtis', 'Rusė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āris', 'Februāris', 'Marts', 'Aprīlis', 'Maijs', + 'Jūnijs', 'Jūlijs', 'Augusts', 'Septembris', 'Oktobris', + 'Novembris', 'Decemberis' ], + 'ja' : [ '1月', '2月', '3月', '4月', '5月', + '6月', '7月', '8月', '9月', '10月', + '11月', '12月' ], + 'fi' : [ 'Tammikuu', 'Helmikuu', 'Maaliskuu', 'Huhtikuu', 'Toukokuu', + 'Kesäkuu', 'Heinäkuu', 'Elokuu', 'Syyskuu', 'Lokakuu', + 'Marraskuu', 'Joulukuu' ], + 'ro' : [ 'Ianuarie', 'Februarie', 'Martie', 'Aprilie', 'Mai', 'Junie', + 'Julie', 'August', 'Septembrie', 'Octombrie', 'Noiembrie', 'Decembrie' ], + 'zh' : [ '1 月', '2 月', '3 月', + '4 月', '5 月', '6 月', '7 月', + '8 月', '9 月', '10月', '11月', '12月'], + '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á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áb', 'Dom' ], + 'hu' : [ 'Hé', 'Ke', 'Sze', 'Csü', 'Pé', 'Szo', 'Vas' ], + 'lt' : [ 'Pir', 'Ant', 'Tre', 'Ket', 'Pen', 'Šeš', 'Sek' ], + 'nl' : [ 'ma', 'di', 'wo', 'do', 'vr', 'za', 'zo' ], + 'dk' : [ 'Man', 'Tir', 'Ons', 'Tor', 'Fre', 'Lør', 'Søn' ], + 'no' : [ 'Man', 'Tir', 'Ons', 'Tor', 'Fre', 'Lør', 'Sun' ], + 'lv' : [ 'P', 'O', 'T', 'C', 'Pk', 'S', 'Sv' ], + 'ja' : [ '月', '火', '水', '木', '金', + '土', '日' ], + 'fi' : [ 'Ma', 'Ti', 'Ke', 'To', 'Pe', 'La', 'Su' ], + 'ro' : [ 'Lun', 'Mar', 'Mie', 'Joi', 'Vin', 'Sam', 'Dum' ], + 'zh' : [ '周一', '周二', '周三', + '周四', '周五', '周六', + '周日' ], + 'sv' : [ 'Mån', 'Tis', 'Ons', 'Tor', 'Fre', 'Lör', + 'Sön' ] + }), + _language_close : $H({ + 'fr' : 'fermer', + 'en' : 'close', + 'sp' : 'cerrar', + 'it' : 'fine', + 'de' : 'schliessen', + 'pt' : 'fim', + 'hu' : 'bezár', + 'lt' : 'udaryti', + 'nl' : 'sluiten', + 'dk' : 'luk', + 'no' : 'lukk', + 'lv' : 'aizvērt', + 'ja' : '閉じる', + 'fi' : 'sulje', + 'ro' : 'inchide', + 'zh' : '关 闭', + 'sv' : 'stä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 = ''+((this._enableYearBrowse) ? '' : '')+''+((this._enableYearBrowse) ? '' : '')+'
     <  <<  >>  > 
    '; + /* 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 ) ? + "
    " + content + "
    " : 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('   '+this.getMonthLocale(this._current_mon)+' '+this._current_year+'   '); + } +}; diff --git a/webcit/static/datepicker.css b/webcit/static/datepicker.css new file mode 100644 index 000000000..43259516b --- /dev/null +++ b/webcit/static/datepicker.css @@ -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; +} + diff --git a/webcit/static/dragdrop.js b/webcit/static/dragdrop.js index 55461c1b9..bf429c261 100644 --- a/webcit/static/dragdrop.js +++ b/webcit/static/dragdrop.js @@ -1,126 +1,122 @@ -// 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; i0) + 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;i0 ? 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 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= 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.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 = '
    '; + 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); diff --git a/webcit/static/head.html b/webcit/static/head.html index 63c09e56c..ab5690399 100644 --- a/webcit/static/head.html +++ b/webcit/static/head.html @@ -7,13 +7,14 @@ - - + + +
    - + diff --git a/webcit/static/prototype.js b/webcit/static/prototype.js index a3f21ac79..6385503a1 100644 --- a/webcit/static/prototype.js +++ b/webcit/static/prototype.js @@ -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: ']*>([\\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 '#'; } -} +}; 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 '#'; - }, + 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 '#'; + }, -// 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 = '' + html.stripScripts() + '
    '; - depth = 2; - break; - case 'TR': - div.innerHTML = '' + html.stripScripts() + '
    '; - depth = 3; - break; - case 'TD': - div.innerHTML = '
    ' + html.stripScripts() + '
    '; - depth = 4; + Element._attributeTranslations = { + read: { + names: { + 'class': 'className', + 'for': 'htmlFor' + }, + values: { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + _getAttrNode: function(element, attribute) { + var node = element.getAttributeNode(attribute); + return node ? node.value : ""; + }, + _getEv: function(element, attribute) { + 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: ['', '
    ', 1], + TBODY: ['', '
    ', 2], + TR: ['', '
    ', 3], + TD: ['
    ', '
    ', 4], + SELECT: ['', 1] } }; (function() { - Object.extend(this, { - href: this._getAttr, - src: this._getAttr, - type: this._getAttr, - disabled: this._flag, - checked: this._flag, - readonly: this._flag, - multiple: this._flag + 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 = '' + this.content + '
    '; - return $A(div.childNodes[0].childNodes[0].childNodes); - } -} - -var Insertion = new Object(); - -Insertion.Before = Class.create(); -Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { - initializeRange: function() { - this.range.setStartBefore(this.element); - }, - - insertContent: function(fragments) { - fragments.each((function(fragment) { - this.element.parentNode.insertBefore(fragment, this.element); - }).bind(this)); - } -}); - -Insertion.Top = Class.create(); -Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { - initializeRange: function() { - this.range.selectNodeContents(this.element); - this.range.collapse(true); - }, - - insertContent: function(fragments) { - fragments.reverse(false).each((function(fragment) { - this.element.insertBefore(fragment, this.element.firstChild); - }).bind(this)); - } -}); - -Insertion.Bottom = Class.create(); -Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { - initializeRange: function() { - this.range.selectNodeContents(this.element); - this.range.collapse(this.element); - }, - - insertContent: function(fragments) { - fragments.each((function(fragment) { - this.element.appendChild(fragment); - }).bind(this)); - } -}); - -Insertion.After = Class.create(); -Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { - initializeRange: function() { - this.range.setStartAfter(this.element); - }, - - insertContent: function(fragments) { - fragments.each((function(fragment) { - this.element.parentNode.insertBefore(fragment, - this.element.nextSibling); - }).bind(this)); - } -}); - -/*--------------------------------------------------------------------------*/ -Element.ClassNames = Class.create(); -Element.ClassNames.prototype = { - initialize: function(element) { - this.element = $(element); - }, - - _each: function(iterator) { - this.element.className.split(/\s+/).select(function(name) { - return name.length > 0; - })._each(iterator); - }, + if (Element.extend.refresh) Element.extend.refresh(); + Element.cache = { }; +}; - set: function(className) { - this.element.className = className; +document.viewport = { + getDimensions: function() { + var dimensions = { }; + 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 "#"; } -}; +}); 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("'); + document.write('