incubator-xap-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From mtu...@apache.org
Subject svn commit: r434107 [2/2] - /incubator/xap/trunk/dist/zimbra_uncompressed.js
Date Wed, 23 Aug 2006 18:00:43 GMT

Added: incubator/xap/trunk/dist/zimbra_uncompressed.js
URL: http://svn.apache.org/viewvc/incubator/xap/trunk/dist/zimbra_uncompressed.js?rev=434107&view=auto
==============================================================================
--- incubator/xap/trunk/dist/zimbra_uncompressed.js (added)
+++ incubator/xap/trunk/dist/zimbra_uncompressed.js Wed Aug 23 11:00:42 2006
@@ -0,0 +1,36159 @@
+/** auto include file:./xap/peter/src/zimbra/js/msgs/AjxMsg.js **/
+function launch() {
+	// DBG = new AjxDebug( AjxDebug.NONE, null, false );
+}
+// AjxCore.addOnloadListener( launch );
+
+// Basename: /msgs/I18nMsg
+function I18nMsg(){}
+
+I18nMsg["currencyCode"] = "USD";
+I18nMsg["currencySymbol"] = "$";
+I18nMsg["eraAD"] = "AD";
+I18nMsg["eraBC"] = "BC";
+I18nMsg["firstDayOfWeek"] = "1";
+I18nMsg["formatDateFull"] = "EEEE, MMMM d, yyyy";
+I18nMsg["formatDateLong"] = "MMMM d, yyyy";
+I18nMsg["formatDateMedium"] = "MMM d, yyyy";
+I18nMsg["formatDateShort"] = "M/d/yy";
+I18nMsg["formatDateTime"] = "{0} {1}";
+I18nMsg["formatNumber"] = "#,##0.###";
+I18nMsg["formatNumberCurrency"] = "\u00a4#,##0.00;(\u00a4#,##0.00)";
+I18nMsg["formatNumberInteger"] = "#,##0";
+I18nMsg["formatNumberPercent"] = "#,##0%";
+I18nMsg["formatTimeFull"] = "h:mm:ss a z";
+I18nMsg["formatTimeLong"] = "h:mm:ss a z";
+I18nMsg["formatTimeMedium"] = "h:mm:ss a";
+I18nMsg["formatTimeShort"] = "h:mm a";
+I18nMsg["monthAprLong"] = "April";
+I18nMsg["monthAprMedium"] = "Apr";
+I18nMsg["monthAugLong"] = "August";
+I18nMsg["monthAugMedium"] = "Aug";
+I18nMsg["monthDecLong"] = "December";
+I18nMsg["monthDecMedium"] = "Dec";
+I18nMsg["monthFebLong"] = "February";
+I18nMsg["monthFebMedium"] = "Feb";
+I18nMsg["monthJanLong"] = "January";
+I18nMsg["monthJanMedium"] = "Jan";
+I18nMsg["monthJulLong"] = "July";
+I18nMsg["monthJulMedium"] = "Jul";
+I18nMsg["monthJunLong"] = "June";
+I18nMsg["monthJunMedium"] = "Jun";
+I18nMsg["monthMarLong"] = "March";
+I18nMsg["monthMarMedium"] = "Mar";
+I18nMsg["monthMayLong"] = "May";
+I18nMsg["monthMayMedium"] = "May";
+I18nMsg["monthNovLong"] = "November";
+I18nMsg["monthNovMedium"] = "Nov";
+I18nMsg["monthOctLong"] = "October";
+I18nMsg["monthOctMedium"] = "Oct";
+I18nMsg["monthSepLong"] = "September";
+I18nMsg["monthSepMedium"] = "Sep";
+I18nMsg["numberInfinity"] = "\u221e";
+I18nMsg["numberNaN"] = "\ufffd";
+I18nMsg["numberSeparatorDecimal"] = ".";
+I18nMsg["numberSeparatorGrouping"] = ",";
+I18nMsg["numberSeparatorMoneyDecimal"] = ".";
+I18nMsg["numberSignMinus"] = "-";
+I18nMsg["numberSignPerMill"] = "\u2030";
+I18nMsg["numberSignPercent"] = "%";
+I18nMsg["numberZero"] = "0";
+I18nMsg["periodAm"] = "AM";
+I18nMsg["periodPm"] = "PM";
+I18nMsg["timezoneMapAfrica/Algiers"] = "(GMT+01.00) West Central Africa";
+I18nMsg["timezoneMapAfrica/Cairo"] = "(GMT+02.00) Cairo";
+I18nMsg["timezoneMapAfrica/Casablanca"] = "(GMT) Casablanca / Monrovia";
+I18nMsg["timezoneMapAfrica/Harare"] = "(GMT+02.00) Harare / Pretoria";
+I18nMsg["timezoneMapAfrica/Nairobi"] = "(GMT+03.00) Nairobi";
+I18nMsg["timezoneMapAmerica/Bogota"] = "(GMT-05.00) Bogota / Lima / Quito";
+I18nMsg["timezoneMapAmerica/Buenos_Aires"] = "(GMT-03.00) Buenos Aires / Georgetown";
+I18nMsg["timezoneMapAmerica/Caracas"] = "(GMT-04.00) Caracas / La Paz";
+I18nMsg["timezoneMapAmerica/Godthab"] = "(GMT-03.00) Greenland";
+I18nMsg["timezoneMapAmerica/Santiago"] = "(GMT-04.00) Santiago";
+I18nMsg["timezoneMapAsia/Aqtobe"] = "(GMT+05.00) Ekaterinburg";
+I18nMsg["timezoneMapAsia/Baghdad"] = "(GMT+03.00) Baghdad";
+I18nMsg["timezoneMapAsia/Baku"] = "(GMT+04.00) Baku / Tbilisi / Yerevan";
+I18nMsg["timezoneMapAsia/Bangkok"] = "(GMT+07.00) Bangkok / Hanoi / Jakarta";
+I18nMsg["timezoneMapAsia/Calcutta"] = "(GMT+05.30) Chennai / Kolkata / Mumbai / New Delhi";
+I18nMsg["timezoneMapAsia/Colombo"] = "(GMT+06.00) Sri Jayawardenepura";
+I18nMsg["timezoneMapAsia/Dhaka"] = "(GMT+06.00) Astana / Dhaka";
+I18nMsg["timezoneMapAsia/Hong_Kong"] = "(GMT+08.00) Beijing / Chongqing / Hong Kong / Urumqi";
+I18nMsg["timezoneMapAsia/Irkutsk"] = "(GMT+08.00) Irkutsk / Ulaan Bataar";
+I18nMsg["timezoneMapAsia/Jerusalem"] = "(GMT+02.00) Jerusalem";
+I18nMsg["timezoneMapAsia/Kabul"] = "(GMT+04.30) Kabul";
+I18nMsg["timezoneMapAsia/Karachi"] = "(GMT+05.00) Islamabad / Karachi / Tashkent";
+I18nMsg["timezoneMapAsia/Katmandu"] = "(GMT+05.45) Kathmandu";
+I18nMsg["timezoneMapAsia/Krasnoyarsk"] = "(GMT+07.00) Krasnoyarsk";
+I18nMsg["timezoneMapAsia/Kuala_Lumpur"] = "(GMT+08.00) Kuala Lumpur / Singaporev";
+I18nMsg["timezoneMapAsia/Kuwait"] = "(GMT+03.00) Kuwait / Riyadh";
+I18nMsg["timezoneMapAsia/Magadan"] = "(GMT+11.00) Magadan / Solomon Is. / New Calenodia";
+I18nMsg["timezoneMapAsia/Muscat"] = "(GMT+04.00) Abu Dhabi / Muscat";
+I18nMsg["timezoneMapAsia/Novosibirsk"] = "(GMT+06.00) Almaty / Novosibirsk";
+I18nMsg["timezoneMapAsia/Rangoon"] = "(GMT+06.30) Rangoon";
+I18nMsg["timezoneMapAsia/Seoul"] = "(GMT+09.00) Seoul";
+I18nMsg["timezoneMapAsia/Taipei"] = "(GMT+08.00) Taipei";
+I18nMsg["timezoneMapAsia/Tehran"] = "(GMT+03.30) Tehran";
+I18nMsg["timezoneMapAsia/Tokyo"] = "(GMT+09.00) Osaka / Sapporo / Tokyo";
+I18nMsg["timezoneMapAsia/Vladivostok"] = "(GMT+10.00) Vladivostok";
+I18nMsg["timezoneMapAsia/Yakutsk"] = "(GMT+09.00) Yakutsk";
+I18nMsg["timezoneMapAtlantic/Azores"] = "(GMT-01.00) Azores";
+I18nMsg["timezoneMapAtlantic/Cape_Verde"] = "(GMT-01.00) Cape Verde Is.";
+I18nMsg["timezoneMapAustralia/Adelaide"] = "(GMT+09.30) Adelaide";
+I18nMsg["timezoneMapAustralia/Brisbane"] = "(GMT+10.00) Brisbane";
+I18nMsg["timezoneMapAustralia/Darwin"] = "(GMT+09.30) Darwin";
+I18nMsg["timezoneMapAustralia/Hobart"] = "(GMT+10.00) Hobart";
+I18nMsg["timezoneMapAustralia/Perth"] = "(GMT+08.00) Perth";
+I18nMsg["timezoneMapAustralia/Sydney"] = "(GMT+10.00) Canberra / Melbourne / Sydney";
+I18nMsg["timezoneMapBrazil/East"] = "(GMT-03.00) Brasilia";
+I18nMsg["timezoneMapCanada/Eastern"] = "(GMT-04.00) Atlantic Time (Canada)";
+I18nMsg["timezoneMapCanada/Newfoundland"] = "(GMT-03.30) Newfoundlan";
+I18nMsg["timezoneMapCanada/Saskatchewan"] = "(GMT-06.00) Saskatchewan";
+I18nMsg["timezoneMapEurope/Athens"] = "(GMT+02.00) Athens / Beirut / Istanbul / Minsk";
+I18nMsg["timezoneMapEurope/Berlin"] = "(GMT+01.00) Amsterdam / Berlin / Bern / Rome / Stockholm / Vienna";
+I18nMsg["timezoneMapEurope/London"] = "(GMT) Greenwich Mean Time - Dublin / Edinburgh / Lisbon / London";
+I18nMsg["timezoneMapEurope/Moscow"] = "(GMT+03.00) Moscow / St. Petersburg / Volgograd";
+I18nMsg["timezoneMapPacific/Auckland"] = "(GMT+12.00) Auckland / Wellington";
+I18nMsg["timezoneMapPacific/Fiji"] = "(GMT+12.00) Fiji / Kamchatka / Marshall Is.";
+I18nMsg["timezoneMapPacific/Guam"] = "(GMT+10.00) Guam / Port Moresby";
+I18nMsg["timezoneMapPacific/Midway"] = "(GMT-11.00) Midway Island / Samoa";
+I18nMsg["timezoneMapPacific/Tongatapu"] = "(GMT+13.00) Nuku'alofa";
+I18nMsg["timezoneMapUS/Alaska"] = "(GMT-09.00) Alaska";
+I18nMsg["timezoneMapUS/Arizona"] = "(GMT-07.00) Arizona";
+I18nMsg["timezoneMapUS/Central"] = "(GMT-06.00) Central Time (US & Canada)";
+I18nMsg["timezoneMapUS/East-Indiana"] = "(GMT-05.00) Indiana (East)";
+I18nMsg["timezoneMapUS/Eastern"] = "(GMT-05.00) Eastern Time (US & Canada)";
+I18nMsg["timezoneMapUS/Hawaii"] = "(GMT-10.00) Hawaii";
+I18nMsg["timezoneMapUS/Mountain"] = "(GMT-07.00) Mountain Time (US & Canada)";
+I18nMsg["timezoneMapUS/Pacific"] = "(GMT-08.00) Pacific Time (US & Canada) / Tijuana";
+I18nMsg["timezoneNameAfrica/Algiers"] = "Central European Time";
+I18nMsg["timezoneNameAfrica/Cairo"] = "Eastern European Time";
+I18nMsg["timezoneNameAfrica/Casablanca"] = "Western European Time";
+I18nMsg["timezoneNameAfrica/Harare"] = "Central African Time";
+I18nMsg["timezoneNameAfrica/Nairobi"] = "Eastern African Time";
+I18nMsg["timezoneNameAmerica/Bogota"] = "Colombia Time";
+I18nMsg["timezoneNameAmerica/Buenos_Aires"] = "Argentine Time";
+I18nMsg["timezoneNameAmerica/Caracas"] = "Venezuela Time";
+I18nMsg["timezoneNameAmerica/Godthab"] = "Western Greenland Time";
+I18nMsg["timezoneNameAmerica/Santiago"] = "Chile Time";
+I18nMsg["timezoneNameAsia/Aqtobe"] = "Aqtobe Time";
+I18nMsg["timezoneNameAsia/Baghdad"] = "Arabia Standard Time";
+I18nMsg["timezoneNameAsia/Baku"] = "Azerbaijan Time";
+I18nMsg["timezoneNameAsia/Bangkok"] = "Indochina Time";
+I18nMsg["timezoneNameAsia/Calcutta"] = "India Standard Time";
+I18nMsg["timezoneNameAsia/Colombo"] = "Sri Lanka Time";
+I18nMsg["timezoneNameAsia/Dhaka"] = "Bangladesh Time";
+I18nMsg["timezoneNameAsia/Hong_Kong"] = "Hong Kong Time";
+I18nMsg["timezoneNameAsia/Irkutsk"] = "Irkutsk Time";
+I18nMsg["timezoneNameAsia/Jerusalem"] = "Israel Standard Time";
+I18nMsg["timezoneNameAsia/Kabul"] = "Afghanistan Time";
+I18nMsg["timezoneNameAsia/Karachi"] = "Pakistan Time";
+I18nMsg["timezoneNameAsia/Katmandu"] = "Nepal Time";
+I18nMsg["timezoneNameAsia/Krasnoyarsk"] = "Krasnoyarsk Time";
+I18nMsg["timezoneNameAsia/Kuala_Lumpur"] = "Malaysia Time";
+I18nMsg["timezoneNameAsia/Kuwait"] = "Arabia Standard Time";
+I18nMsg["timezoneNameAsia/Magadan"] = "Magadan Time";
+I18nMsg["timezoneNameAsia/Muscat"] = "Gulf Standard Time";
+I18nMsg["timezoneNameAsia/Novosibirsk"] = "Novosibirsk Time";
+I18nMsg["timezoneNameAsia/Rangoon"] = "Myanmar Time";
+I18nMsg["timezoneNameAsia/Seoul"] = "Korea Standard Time";
+I18nMsg["timezoneNameAsia/Taipei"] = "China Standard Time";
+I18nMsg["timezoneNameAsia/Tehran"] = "Iran Standard Time";
+I18nMsg["timezoneNameAsia/Tokyo"] = "Japan Standard Time";
+I18nMsg["timezoneNameAsia/Vladivostok"] = "Vladivostok Time";
+I18nMsg["timezoneNameAsia/Yakutsk"] = "Yakutsk Time";
+I18nMsg["timezoneNameAtlantic/Azores"] = "Azores Time";
+I18nMsg["timezoneNameAtlantic/Cape_Verde"] = "Cape Verde Time";
+I18nMsg["timezoneNameAustralia/Adelaide"] = "Central Standard Time (South Australia)";
+I18nMsg["timezoneNameAustralia/Brisbane"] = "Eastern Standard Time (Queensland)";
+I18nMsg["timezoneNameAustralia/Darwin"] = "Central Standard Time (Northern Territory)";
+I18nMsg["timezoneNameAustralia/Hobart"] = "Eastern Standard Time (Tasmania)";
+I18nMsg["timezoneNameAustralia/Perth"] = "Western Standard Time (Australia)";
+I18nMsg["timezoneNameAustralia/Sydney"] = "Eastern Standard Time (New South Wales)";
+I18nMsg["timezoneNameBrazil/East"] = "Brazil Time";
+I18nMsg["timezoneNameCanada/Eastern"] = "Eastern Standard Time";
+I18nMsg["timezoneNameCanada/Newfoundland"] = "Newfoundland Standard Time";
+I18nMsg["timezoneNameCanada/Saskatchewan"] = "Central Standard Time";
+I18nMsg["timezoneNameEurope/Athens"] = "Eastern European Time";
+I18nMsg["timezoneNameEurope/Berlin"] = "Central European Time";
+I18nMsg["timezoneNameEurope/London"] = "Greenwich Mean Time";
+I18nMsg["timezoneNameEurope/Moscow"] = "Moscow Standard Time";
+I18nMsg["timezoneNamePacific/Auckland"] = "New Zealand Standard Time";
+I18nMsg["timezoneNamePacific/Fiji"] = "Fiji Time";
+I18nMsg["timezoneNamePacific/Guam"] = "Chamorro Standard Time";
+I18nMsg["timezoneNamePacific/Midway"] = "Samoa Standard Time";
+I18nMsg["timezoneNamePacific/Tongatapu"] = "Tonga Time";
+I18nMsg["timezoneNameUS/Alaska"] = "Alaska Standard Time";
+I18nMsg["timezoneNameUS/Arizona"] = "Mountain Standard Time";
+I18nMsg["timezoneNameUS/Central"] = "Central Standard Time";
+I18nMsg["timezoneNameUS/East-Indiana"] = "Eastern Standard Time";
+I18nMsg["timezoneNameUS/Eastern"] = "Eastern Standard Time";
+I18nMsg["timezoneNameUS/Hawaii"] = "Hawaii Standard Time";
+I18nMsg["timezoneNameUS/Mountain"] = "Mountain Standard Time";
+I18nMsg["timezoneNameUS/Pacific"] = "Pacific Standard Time";
+I18nMsg["weekdayFriLong"] = "Friday";
+I18nMsg["weekdayFriMedium"] = "Fri";
+I18nMsg["weekdayMonLong"] = "Monday";
+I18nMsg["weekdayMonMedium"] = "Mon";
+I18nMsg["weekdaySatLong"] = "Saturday";
+I18nMsg["weekdaySatMedium"] = "Sat";
+I18nMsg["weekdaySunLong"] = "Sunday";
+I18nMsg["weekdaySunMedium"] = "Sun";
+I18nMsg["weekdayThuLong"] = "Thursday";
+I18nMsg["weekdayThuMedium"] = "Thu";
+I18nMsg["weekdayTueLong"] = "Tuesday";
+I18nMsg["weekdayTueMedium"] = "Tue";
+I18nMsg["weekdayWedLong"] = "Wednesday";
+I18nMsg["weekdayWedMedium"] = "Wed";
+
+// Basename: /msgs/AjxMsg
+function AjxMsg(){}
+
+AjxMsg["XfnumberLessThanMin"] = "Value must be greater than or equal to {0}.";
+AjxMsg["_close"] = "Close";
+AjxMsg["_finish"] = "Finish";
+AjxMsg["_next"] = "Next";
+AjxMsg["_prev"] = "Previous";
+AjxMsg["add"] = "Add";
+AjxMsg["addAll"] = "Add All";
+AjxMsg["ago"] = "ago";
+AjxMsg["cancel"] = "Cancel";
+AjxMsg["cancelRequest"] = "Cancel Request";
+AjxMsg["confirmTitle"] = "Confirmation";
+AjxMsg["criticalMsg"] = "Critical";
+AjxMsg["dateParsing2DigitStartYear"] = "1936";
+AjxMsg["day"] = "day";
+AjxMsg["days"] = "days";
+AjxMsg["detail"] = "Detail";
+AjxMsg["didNotMatchChoice"] = "Value '$0' did not match any values for this type.";
+AjxMsg["dismiss"] = "Dismiss";
+AjxMsg["first"] = "first";
+AjxMsg["formatCalDate"] = "EEE, MMM d";
+AjxMsg["formatCalDateFull"] = "EEEE, MMMM d, yyyy";
+AjxMsg["formatCalDateLong"] = "EEEE, MMMM d";
+AjxMsg["formatCalDay"] = "M/d ";
+AjxMsg["formatCalMonth"] = "MMMM yyyy";
+AjxMsg["formatDateMediumNoYear"] = "MMM dd ";
+AjxMsg["fourth"] = "fourth";
+AjxMsg["hour"] = "hour";
+AjxMsg["hours"] = "hours";
+AjxMsg["infoMsg"] = "Informational";
+AjxMsg["invalidDateString"] = "Date value must be entered in the form: MM/DD/YYYY or \"today\", \"yesterday\" or \"tomorrow\".";
+AjxMsg["invalidDatetimeString"] = "Date format not understood.";
+AjxMsg["invalidTimeString"] = "Time value must be entered in the form: HH:MM[:SS] [AM|PM]";
+AjxMsg["last"] = "last";
+AjxMsg["minute"] = "minute";
+AjxMsg["minutes"] = "minutes";
+AjxMsg["month"] = "month";
+AjxMsg["monthAprShort"] = "A";
+AjxMsg["monthAugShort"] = "A";
+AjxMsg["monthDecShort"] = "D";
+AjxMsg["monthFebShort"] = "F";
+AjxMsg["monthJanShort"] = "J";
+AjxMsg["monthJulShort"] = "J";
+AjxMsg["monthJunShort"] = "J";
+AjxMsg["monthMarShort"] = "M";
+AjxMsg["monthMayShort"] = "M";
+AjxMsg["monthNovShort"] = "N";
+AjxMsg["monthOctShort"] = "O";
+AjxMsg["monthSepShort"] = "S";
+AjxMsg["months"] = "months";
+AjxMsg["no"] = "No";
+AjxMsg["noResults"] = "No results found.";
+AjxMsg["notANumber"] = "Value must be a number.";
+AjxMsg["notAString"] = "Value must be a text string.";
+AjxMsg["notAnInteger"] = "Value must be an integer.";
+AjxMsg["numberFractionExceeded"] = "Fractional digits exceeds total of {0}.";
+AjxMsg["numberLessThanEqualMin"] = "Value must be greater than {0}.";
+AjxMsg["numberLessThanMin"] = "Value must be greater than or equal to {0}.";
+AjxMsg["numberMoreThanEqualMax"] = "Value must be less than {0}.";
+AjxMsg["numberMoreThanMax"] = "Value must be less than or equal to {0}.";
+AjxMsg["numberMustBeNon0Percent"] = "Value must be a percentage between 1 and 100";
+AjxMsg["numberTotalExceeded"] = "Whole digits exceeds total of {0}.";
+AjxMsg["ok"] = "OK";
+AjxMsg["remove"] = "Remove";
+AjxMsg["removeAll"] = "Remove All";
+AjxMsg["second"] = "second";
+AjxMsg["seconds"] = "seconds";
+AjxMsg["stringLenWrong"] = "Value must be exactly {0} characters long.";
+AjxMsg["stringMismatch"] = "Value did not match valid values.";
+AjxMsg["stringTooLong"] = "Value must be no more than {0} characters long.";
+AjxMsg["stringTooShort"] = "Value must be at least {0} characters long.";
+AjxMsg["third"] = "third";
+AjxMsg["today"] = "today";
+AjxMsg["tomorrow"] = "tomorrow";
+AjxMsg["valueIsRequired"] = "Value is required";
+AjxMsg["warningMsg"] = "Warning";
+AjxMsg["week"] = "week";
+AjxMsg["weekdayFriShort"] = "F";
+AjxMsg["weekdayMonShort"] = "M";
+AjxMsg["weekdaySatShort"] = "S";
+AjxMsg["weekdaySunShort"] = "S";
+AjxMsg["weekdayThuShort"] = "T";
+AjxMsg["weekdayTueShort"] = "T";
+AjxMsg["weekdayWedShort"] = "W";
+AjxMsg["weeks"] = "weeks";
+AjxMsg["workInProgress"] = "Work In Progress";
+AjxMsg["xFnumberMoreThanMax"] = "Value must be less than or equal to {0}.";
+AjxMsg["xformDateTimeFormat"] = "{0,date} at {0,time}";
+AjxMsg["xformRepeatAdd"] = "+";
+AjxMsg["xformRepeatRemove"] = "-";
+AjxMsg["year"] = "year";
+AjxMsg["years"] = "years";
+AjxMsg["yes"] = "Yes";
+AjxMsg["yesterday"] = "yesterday";
+
+/*
+* Conventions
+*
+* - Classes that start with "Dwt" have a corollary Javascript object.
+* - Use one line if there is only one selector and one declaration. Otherwise, indent each declaration on its own line.
+* - Group selectors to avoid repetition of styles, as appropriate (ie group within a section). That means you may have
+*   to look at more than one definition to figure out all the applicable styles, but it lets you give the styles a 
+*   hierarchy. That way, you're more likely to need to change a style in just one place.
+* - Use shorthand styles when possible (font, margin, padding, etc).
+* - When grouping selectors that correspond to our Javascript objects, show inheritance by using a different line for
+*   each level of inheritance. CSS doesn't care about our object hierarchy, but doing that enhances readability.
+* - Use decimal RGB values for colors that don't have names.
+* - Modifiers are lower case, whether they're a class (eg .activated) or tacked on (eg -selected).
+*
+* Classes are laid out in the following general order:
+*	- HTML elements
+*	- General app stuff (shell, overlays, banner)
+*	- Widgets that tend toward abstract (minimal subclassing), such as tool tips and sashes
+*	- Heavier use widgets such as buttons and dialogs
+*		- General widget stuff, then specialized subclasses
+*	- List views, with common stuff first
+*	- Other views (lots of them)
+*	- Miscellaneous stuff that multiple apps might use (objects, autocomplete)
+*	- Modifiers
+*
+* Many of the Dwt widgets don't have corresponding classes here, since they are only subclassed. If you create and
+* display one of those widgets, you'll have to add a class for it.
+*/
+
+/* 
+* HTML elements - use for global declarations
+*/
+
+/* Global ELEMENT declarations */
+
+function ExMsg() {}
+/** auto include file:./xap/peter/src/zimbra/js/core/AjxCore.js **/
+/*
+* ***** BEGIN LICENSE BLOCK *****
+* Version: MPL 1.1
+*
+* The contents of this file are subject to the Mozilla Public
+* License Version 1.1 ("License"); you may not use this file except in
+* compliance with the License. You may obtain a copy of the License at
+* http://www.zimbra.com/license
+*
+* Software distributed under the License is distributed on an "AS IS"
+* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+* the License for the specific language governing rights and limitations
+* under the License.
+*
+* The Original Code is: Zimbra AJAX Toolkit.
+*
+* The Initial Developer of the Original Code is Zimbra, Inc.
+* Portions created by Zimbra are Copyright (C) 2005 Zimbra, Inc.
+* All Rights Reserved.
+*
+* Contributor(s):
+*
+* ***** END LICENSE BLOCK *****
+*/
+
+
+function AjxCore() {}
+
+AjxCore._objectIds = [null];
+
+
+AjxCore.assignId = 
+function(anObject) {
+	var myId = AjxCore._objectIds.length;
+	AjxCore._objectIds[myId]= anObject;
+	return myId;
+};
+
+AjxCore.unassignId = 
+function(anId) {
+	AjxCore._objectIds[anId]= null;
+};
+
+AjxCore.objectWithId = 
+function(anId) {
+	return AjxCore._objectIds[anId];
+};
+
+/**
+ * Adds a listener to an element, for the given event name.
+ */
+AjxCore.addListener = 
+function(eventSource, eventName, action) {
+	eventSource = AjxCore._getEventSource(eventSource);
+	var listenerStruct = AjxCore._getListenerStruct(eventSource, eventName, true);
+	listenerStruct.list[listenerStruct.list.length] = action;
+};
+
+/**
+ * sets a one time event handler for the given eventName.
+ */
+AjxCore.setEventHandler = 
+function(eventSource, eventName, action) {
+	eventSource = AjxCore._getEventSource(eventSource);
+	var listenerStruct = AjxCore._getListenerStruct(eventSource, eventName, true);
+	listenerStruct.single = action;
+};
+
+/**
+ * removes a listener for a given event
+ */
+AjxCore.removeListener = 
+function(eventSource, eventName, action) {
+	eventSource = AjxCore._getEventSource(eventSource);
+	var listenerStruct = AjxCore._getListenerStruct(eventSource, eventName);
+
+	if (listenerStruct) {
+		var listenerList = listenerStruct.list;
+		for (var i = 0; i < listenerList.length; i++) {
+			if (listenerList[i] == action)
+				listenerList[i] = null;
+		}
+	}
+};
+
+/**
+ * removes all listeners for a given eventName, and source
+ */
+AjxCore.removeAllListeners = 
+function(eventSource, eventName) {
+	eventSource = AjxCore._getEventSource(eventSource);
+	var listenerStruct = AjxCore._getListenerStruct(eventSource, eventName);
+
+	if (listenerStruct) {
+		var listenerList = listenerStruct.list;
+		for (var i = 0; i < listenerList.length; i++)
+			listenerList[i] = null;
+	}
+	AjxCore.unassignId(listenerStruct.id);
+};
+
+/**
+ * notifies listeners of the event. This only needs to be called if
+ * the event is not a standard DOM event. Those types of event callbacks
+ * will be triggered by their event handlers
+ */
+AjxCore.notifyListeners = 
+function(eventSource, eventName, arg1) {
+	eventSource = AjxCore._getEventSource(eventSource);
+	var listenerStruct = AjxCore._getListenerStruct(eventSource, eventName)
+	if (listenerStruct)
+		eventSource[eventName](arg1);
+};
+
+AjxCore._getEventSource = 
+function(eventSource) {
+	if (typeof(eventSource) == 'string')
+		eventSource = document.getElementById(eventSource);
+	return eventSource;
+};
+
+AjxCore.getListenerStruct = 
+function (eventSource, eventName) {
+	return AjxCore._getListenerStruct(eventSource, eventName);
+};
+
+/**
+ * gets the existing struct for the eventSource, or creates a new one.
+ */
+AjxCore._getListenerStruct = 
+function(eventSource, eventName, create) {
+	var listenerStruct = null;
+	if (eventSource[eventName]) {
+		var id = eventSource[eventName]._lsListenerStructId;
+		listenerStruct = AjxCore.objectWithId(id);
+	} else if (create) {
+		listenerStruct = AjxCore._setupListener(eventSource, eventName);
+	}
+
+	return listenerStruct;
+};
+    
+/**
+ * Creates a listener struct
+ */
+AjxCore._setupListener = 
+function(eventSource, eventName, id) {
+	var listenerStruct = new Object();
+	listenerStruct.list = new Array();
+	listenerStruct.single = null;
+	var id = listenerStruct.id = AjxCore.assignId(listenerStruct);
+	var handler = AjxCore._createListenerClosure(id);
+	eventSource[eventName] = handler;
+	eventSource[eventName]._lsListenerStructId = id;
+
+	return listenerStruct;
+};
+
+AjxCore._createListenerClosure = 
+function(id) {
+	var closure = function(arg1) {
+		var listenerStruct = AjxCore.objectWithId(id);
+		var listenerList = listenerStruct.list;
+		for (var i = 0; i < listenerList.length; i++) {
+			var callback = listenerList[i];
+			if (callback) {
+				if (typeof(callback) == 'string') {
+					eval(callback);
+				} else {
+					// handle AjxListener callbacks as well as simple functions
+					if (callback.handleEvent) {
+						callback.handleEvent(arg1, this);
+					} else {
+						callback(arg1, this);
+					}
+				}
+			}
+		}
+        if (listenerStruct.single) {
+			var callback = listenerStruct.single;
+			if (typeof(callback) == 'string') {
+				eval(callback);
+			} else {
+				return callback.handleEvent
+					? callback.handleEvent(arg1, this)
+					: callback(arg1, this);
+			}
+		}
+	}
+	return closure;
+};
+
+/**
+ * Convenience method for adding onload listeners
+ */
+AjxCore.addOnloadListener = 
+function(action) {
+	if (window.onload && (!window.onload._lsListenerStructId)) {
+		var priorListener = window.onload;
+		window.onload = null;
+		AjxCore.addListener(window, "onload", priorListener);
+	}
+
+	AjxCore.addListener(window, "onload", action);
+};
+
+/**
+ * Convenience method for adding onunload listeners
+ */    
+AjxCore.addOnunloadListener = 
+function(action) {
+	if (window.onunload && (!window.onunload._lsListenerStructId)) {
+		var priorListener = window.onunload;
+		window.onunload = null;
+		AjxCore.addListener(window, "onunload", priorListener);
+	}
+
+	AjxCore.addListener(window, "onunload", action);
+};
+/** auto include file:./xap/peter/src/zimbra/js/core/AjxEnv.js **/
+/*
+* ***** BEGIN LICENSE BLOCK *****
+* Version: MPL 1.1
+*
+* The contents of this file are subject to the Mozilla Public
+* License Version 1.1 ("License"); you may not use this file except in
+* compliance with the License. You may obtain a copy of the License at
+* http://www.zimbra.com/license
+*
+* Software distributed under the License is distributed on an "AS IS"
+* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+* the License for the specific language governing rights and limitations
+* under the License.
+*
+* The Original Code is: Zimbra AJAX Toolkit.
+*
+* The Initial Developer of the Original Code is Zimbra, Inc.
+* Portions created by Zimbra are Copyright (C) 2005 Zimbra, Inc.
+* All Rights Reserved.
+*
+* Contributor(s):
+*
+* ***** END LICENSE BLOCK *****
+*/
+
+
+function AjxEnv() {
+}
+
+AjxEnv._inited = false;
+
+
+AjxEnv.reset = function () {
+	AjxEnv.browserVersion = -1;
+	AjxEnv.geckoDate = 0;
+	AjxEnv.mozVersion = -1;
+	AjxEnv.isMac = false;
+	AjxEnv.isWindows = false;
+	AjxEnv.isLinux = false;
+	AjxEnv.isNav  = false;
+	AjxEnv.isIE = false;
+	AjxEnv.isNav4 = false;
+	AjxEnv.trueNs = true;
+	AjxEnv.isNav6 = false;
+	AjxEnv.isNav6up = false;
+	AjxEnv.isNav7 = false;
+	AjxEnv.isIE3 = false;
+	AjxEnv.isIE4 = false;
+	AjxEnv.isIE4up = false;
+	AjxEnv.isIE5 = false;
+	AjxEnv.isIE5_5 = false;
+	AjxEnv.isIE5up = false;
+	AjxEnv.isIE5_5up = false;
+	AjxEnv.isIE6  = false;
+	AjxEnv.isIE6up = false;
+	AjxEnv.isNormalResolution = false;
+	AjxEnv.ieScaleFactor = 1;
+	AjxEnv.isFirefox = false;
+	AjxEnv.isFirefox1up = false;
+	AjxEnv.isFirefox1_5up = false;
+	AjxEnv.isMozilla = false;
+	AjxEnv.isMozilla1_4up = false;
+	AjxEnv.isSafari = false;
+	AjxEnv.isGeckoBased = false;
+	AjxEnv.isOpera = false;
+	AjxEnv.useTransparentPNGs = false;
+
+	// screen resolution - ADD MORE RESOLUTION CHECKS AS NEEDED HERE:
+	AjxEnv.is800x600orLower = screen.width <= 800 && screen.height <= 600;
+};
+
+AjxEnv.parseUA = function (userAgent) {
+	var agt = userAgent.toLowerCase();
+	var agtArr = agt.split(" ");
+	var i = 0;
+	var index = -1;
+	var token = null;
+	var isSpoofer = false;
+	var isWebTv = false;
+	var isHotJava = false;
+	var beginsWithMozilla = false;
+	var isCompatible = false;
+	if (agtArr != null) {
+		if ( (index = agtArr[0].search(/^\s*mozilla\//) )!= -1){
+			beginsWithMozilla = true;
+			AjxEnv.browserVersion = parseFloat(agtArr[0].substring(index + 8));
+			AjxEnv.isNav = true;
+		}
+		for ( ; i < agtArr.length; ++i ){
+			token = agtArr[i];
+			if (token.indexOf('compatible') != -1 ) {
+				isCompatible = true;
+				AjxEnv.isNav = false;
+			} else if ((token.indexOf('opera')) != -1){
+				AjxEnv.isOpera = true;
+				AjxEnv.isNav = false;
+				AjxEnv.browserVersion = parseFloat(agtArr[i+1]);
+			} else if ((token.indexOf('spoofer')) != -1){
+				isSpoofer = true;
+				AjxEnv.isNav = false;
+			} else if ((token.indexOf('webtv')) != -1) {
+				isWebTv = true;
+				AjxEnv.isNav = false;
+			} else if ((token.indexOf('hotjava')) != -1) {
+				isHotJava = true;
+				AjxEnv.isNav = false;
+			} else if ((index = token.indexOf('msie')) != -1) {
+				AjxEnv.isIE = true;
+				AjxEnv.browserVersion = parseFloat(agtArr[i+1]);
+			} else if ((index = token.indexOf('gecko/')) != -1){
+				AjxEnv.isGeckoBased = true;
+				AjxEnv.geckoDate = parseFloat(token.substr(index + 6));
+			} else if ((index = token.indexOf('rv:')) != -1){
+				AjxEnv.mozVersion = parseFloat(token.substr(index + 3));
+				AjxEnv.browserVersion = AjxEnv.mozVersion;
+			} else if ((index = token.indexOf('firefox/')) != -1){
+				AjxEnv.isFirefox = true;
+				AjxEnv.browserVersion = parseFloat(token.substr(index + 8));
+			} else if ((index = token.indexOf('netscape6/')) != -1){
+				AjxEnv.trueNs = true;
+				AjxEnv.browserVersion = parseFloat(token.substr(index + 10));
+			} else if ((index = token.indexOf('netscape/')) != -1){
+				AjxEnv.trueNs = true;
+				AjxEnv.browserVersion = parseFloat(token.substr(index + 9));
+			} else if ((index = token.indexOf('safari/')) != -1){
+				AjxEnv.isSafari = true;
+				AjxEnv.browserVersion = parseFloat(token.substr(index + 7));
+			} else if (token.indexOf('windows') != -1){
+				AjxEnv.isWindows = true;
+			} else if ((token.indexOf('macintosh') != -1) ||
+					   (token.indexOf('mac_') != -1)){
+				AjxEnv.isMac = true;
+			} else if (token.indexOf('linux') != -1){
+				AjxEnv.isLinux = true;
+			}
+		}
+		// Note: Opera and WebTV spoof Navigator.  
+		// We do strict client detection.
+		AjxEnv.isNav  = (beginsWithMozilla && !isSpoofer && !isCompatible && 
+						!AjxEnv.isOpera && !isWebTv && !isHotJava &&
+						!AjxEnv.isSafari);
+
+		AjxEnv.isIE = (AjxEnv.isIE && !AjxEnv.isOpera);
+
+		AjxEnv.isNav4 = (AjxEnv.isNav && (AjxEnv.browserVersion  == 4) &&
+						(!AjxEnv.isIE));
+		AjxEnv.isNav6 = (AjxEnv.isNav && AjxEnv.trueNs && 
+						(AjxEnv.browserVersion >=6.0) && 
+						(AjxEnv.browserVersion < 7.0));
+		AjxEnv.isNav6up = (AjxEnv.isNav && AjxEnv.trueNs && 
+						  (AjxEnv.browserVersion >= 6.0));
+		AjxEnv.isNav7 = (AjxEnv.isNav && AjxEnv.trueNs && 
+						(AjxEnv.browserVersion == 7.0));
+
+		AjxEnv.isIE3 = (AjxEnv.isIE && (AjxEnv.browserVersion < 4));
+		AjxEnv.isIE4 = (AjxEnv.isIE && (AjxEnv.browserVersion == 4) && 
+					 (AjxEnv.browserVersion == 4.0));
+		AjxEnv.isIE4up = (AjxEnv.isIE && (AjxEnv.browserVersion >= 4));
+		AjxEnv.isIE5 = (AjxEnv.isIE && (AjxEnv.browserVersion == 4) && 
+					 (AjxEnv.browserVersion == 5.0));
+		AjxEnv.isIE5_5 = (AjxEnv.isIE && (AjxEnv.browserVersion == 4) && 
+						 (AjxEnv.browserVersion == 5.5));
+		AjxEnv.isIE5up = (AjxEnv.isIE && (AjxEnv.browserVersion >= 5.0));
+		AjxEnv.isIE5_5up =(AjxEnv.isIE && (AjxEnv.browserVersion >= 5.5));
+		AjxEnv.isIE6  = (AjxEnv.isIE && (AjxEnv.browserVersion == 6.0));
+		AjxEnv.isIE6up = (AjxEnv.isIE && (AjxEnv.browserVersion >= 6.0));
+
+		AjxEnv.isMozilla = ((AjxEnv.isNav && AjxEnv.mozVersion && 
+							AjxEnv.isGeckoBased && (AjxEnv.geckoDate != 0)));
+		AjxEnv.isMozilla1_4up = (AjxEnv.isMozilla && (AjxEnv.mozVersion >= 1.4));
+		AjxEnv.isFirefox = ((AjxEnv.isMozilla && AjxEnv.isFirefox));
+		AjxEnv.isFirefox1up = (AjxEnv.isFirefox && AjxEnv.browserVersion >= 1.0);
+		AjxEnv.isFirefox1_5up = (AjxEnv.isFirefox && AjxEnv.browserVersion >= 1.5);
+
+	}
+	// setup some global setting we can check for high resolution
+	if (AjxEnv.isIE){
+		AjxEnv.isNormalResolution = true;
+		AjxEnv.ieScaleFactor = screen.deviceXDPI / screen.logicalXDPI;
+		if (AjxEnv.ieScaleFactor > 1) {
+			AjxEnv.isNormalResolution = false;
+		}
+	}
+	// show transparent PNGs on platforms that support them well
+	//	(eg: all but IE and Linux)
+	//	MOW: having trouble getting safari to render transparency for shadows, skipping there, too
+	AjxEnv.useTransparentPNGs = !AjxEnv.isIE && !AjxEnv.isLinux && !AjxEnv.isSafari;
+	AjxEnv._inited = !AjxEnv.isIE;
+};
+
+AjxEnv.reset();
+AjxEnv.parseUA(navigator.userAgent);
+
+// COMPATIBILITY
+
+// Safari doesn't support string.replace(/regexp/, function);
+if (AjxEnv.isSafari) {
+	if (!String.prototype._AjxOldReplace) {
+		String.prototype._AjxOldReplace = String.prototype.replace;
+		String.prototype.replace = function(re, val) {
+			if (typeof val != "function")
+				return this._AjxOldReplace(re, val);
+			else {
+				// TODO: investigate if it's possible to use the array.join approach
+				var str = this.slice(0), v, l, a;
+				while (a = re.exec(str)) {
+					v = val.apply(null, a);
+					l = a[0].length;
+					re.lastIndex -= l - v.length;
+					str = str.substr(0, a.index) + v + str.substr(a.index + l);
+					if (!re.global)
+						break;
+				}
+				return str;
+			}
+		};
+	}
+}
+/** auto include file:./xap/peter/src/zimbra/js/util/AjxUtil.js **/
+/*
+* ***** BEGIN LICENSE BLOCK *****
+* Version: MPL 1.1
+*
+* The contents of this file are subject to the Mozilla Public
+* License Version 1.1 ("License"); you may not use this file except in
+* compliance with the License. You may obtain a copy of the License at
+* http://www.zimbra.com/license
+*
+* Software distributed under the License is distributed on an "AS IS"
+* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+* the License for the specific language governing rights and limitations
+* under the License.
+*
+* The Original Code is: Zimbra AJAX Toolkit.
+*
+* The Initial Developer of the Original Code is Zimbra, Inc.
+* Portions created by Zimbra are Copyright (C) 2005 Zimbra, Inc.
+* All Rights Reserved.
+*
+* Contributor(s):
+*
+* ***** END LICENSE BLOCK *****
+*/
+
+
+/**
+ * AjxUtil - static class with some utility methods. This is where to
+ * put things when no other class wants them.
+ *
+ * 12/3/2004 At this point, it only needs AjxEnv to be loaded.
+ */
+function AjxUtil () {
+};
+
+AjxUtil.FLOAT_RE = /^[+\-]?((\d+(\.\d*)?)|((\d*\.)?\d+))([eE][+\-]?\d+)?$/;
+AjxUtil.NOTFLOAT_RE = /[^\d\.]/;
+AjxUtil.NOTINT_RE = /[^0-9]+/;
+AjxUtil.LIFETIME_FIELD = /^([0-9])+([dhms])?$/;
+
+AjxUtil.isSpecified 		= function(aThing) { return ((aThing !== void 0) && (aThing !== null)); };
+AjxUtil.isUndefined 		= function(aThing) { return (aThing === void 0); };
+AjxUtil.isNull 				= function(aThing) { return (aThing === null); };
+AjxUtil.isBoolean 			= function(aThing) { return (typeof(aThing) == 'boolean'); };
+AjxUtil.isString 			= function(aThing) { return (typeof(aThing) == 'string'); };
+AjxUtil.isNumber 			= function(aThing) { return (typeof(aThing) == 'number'); };
+AjxUtil.isObject 			= function(aThing) { return ((typeof(aThing) == 'object') && (aThing !== null)); };
+AjxUtil.isArray 			= function(aThing) { return AjxUtil.isInstance(aThing, Array); };
+AjxUtil.isFunction 			= function(aThing) { return (typeof(aThing) == 'function'); };
+AjxUtil.isDate 				= function(aThing) { return AjxUtil.isInstance(aThing, Date); };
+AjxUtil.isLifeTime 			= function(aThing) { return AjxUtil.LIFETIME_FIELD.test(aThing); };
+AjxUtil.isNumeric 			= function(aThing) { return (!isNaN(parseFloat(aThing)) && AjxUtil.FLOAT_RE.test(aThing) && !AjxUtil.NOTFLOAT_RE.test(aThing)); };
+AjxUtil.isLong			    = function(aThing) { return (AjxUtil.isNumeric(aThing) && !AjxUtil.NOTINT_RE.test(aThing)); };
+AjxUtil.isNonNegativeLong   = function(aThing) { return (AjxUtil.isNumeric(aThing) && AjxUtil.isLong(aThing) && (parseFloat(aThing) >= 0)); };
+
+
+// REVISIT: Should do more precise checking. However, there are names in
+//			common use that do not follow the RFC patterns (e.g. domain
+//			names that start with digits).
+AjxUtil.IP_ADDRESS_RE = /^\d{1,3}(\.\d{1,3}){3}(\.\d{1,3}\.\d{1,3})?$/;
+AjxUtil.DOMAIN_NAME_SHORT_RE = /^[A-Za-z0-9\-]{2,}$/;
+AjxUtil.DOMAIN_NAME_FULL_RE = /^[A-Za-z0-9\-]{2,}(\.[A-Za-z0-9\-]{2,}){1,}$/;
+AjxUtil.HOST_NAME_RE = /^[A-Za-z0-9\-]{2,}(\.[A-Za-z0-9\-]{2,})*$/;
+AjxUtil.HOST_NAME_WITH_PORT_RE = /^[A-Za-z0-9\-]{2,}(\.[A-Za-z0-9\-]{2,})*:([0-9])+$/;
+AjxUtil.EMAIL_SHORT_RE = /^[^@\s]+$/;
+AjxUtil.EMAIL_FULL_RE = /^[^@\s]+@[A-Za-z0-9\-]{2,}(\.[A-Za-z0-9\-]{2,})*$/;
+AjxUtil.EMAIL_RE = /^([a-zA-Z0-9_\-])+((\.)?([a-zA-Z0-9_\-])+)*@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
+AjxUtil.SHORT_URL_RE = /^[A-Za-z0-9]{2,}:\/\/[A-Za-z0-9\-]{2,}(\.[A-Za-z0-9\-]{2,})*(:([0-9])+)*$/;
+AjxUtil.IP_SHORT_URL_RE = /^[A-Za-z0-9]{2,}:\/\/\d{1,3}(\.\d{1,3}){3}(\.\d{1,3}\.\d{1,3})?(:([0-9])+)*$/;
+
+AjxUtil.isIpAddress 		= function(s) { return AjxUtil.IP_ADDR_RE.test(s); };
+AjxUtil.isDomain 			= function(s) {	return AjxUtil.DOMAIN_RE.test(s); };
+AjxUtil.isHostName 			= function(s) { return AjxUtil.HOST_NAME_RE.test(s); };
+AjxUtil.isDomainName = 
+function(s, shortMatch) {
+	return shortMatch 
+		? AjxUtil.DOMAIN_NAME_SHORT_RE.test(s) 
+		: AjxUtil.DOMAIN_NAME_FULL_RE.test(s);
+};
+
+AjxUtil.isEmailAddress = 
+function(s, nameOnly) {
+	return nameOnly 
+		? AjxUtil.EMAIL_SHORT_RE.test(s) 
+		: AjxUtil.EMAIL_FULL_RE.test(s);
+};
+
+AjxUtil.SIZE_GIGABYTES = "GB";
+AjxUtil.SIZE_MEGABYTES = "MB";
+AjxUtil.SIZE_KILOBYTES = "KB";
+AjxUtil.SIZE_BYTES = "B";
+
+/**
+ * Formats a size (in bytes) to the largest whole unit. For example,
+ * AjxUtil.formatSize(302132199) returns "288 MB".
+ *
+ * @param size      The size (in bytes) to be formatted.
+ * @param round     True to round to nearest integer. Default is true.
+ * @param fractions Number of fractional digits to display, if not rounding.
+ *                  Trailing zeros after the decimal point are trimmed.
+ */
+AjxUtil.formatSize = 
+function(size, round, fractions) {
+	if (round == null) round = true;
+	if (fractions == null) fractions = 20; // max allowed for toFixed is 20
+
+	var units = AjxUtil.SIZE_BYTES;
+	if (size >= 1073741824) {
+		size /= 1073741824;
+		units = AjxUtil.SIZE_GIGABYTES;
+	}
+	else if (size >= 1048576) {
+		size /= 1048576;
+		units = AjxUtil.SIZE_MEGABYTES;
+	}
+	else if (size > 1023) {
+		size /= 1024;
+		units = AjxUtil.SIZE_KILOBYTES;
+	}
+
+	var formattedSize = round ? Math.round(size) : size.toFixed(fractions).replace(/\.?0+$/,"");
+	var formattedUnits = ' '+units;
+	
+	return formattedSize + formattedUnits;
+};
+
+/**
+ * Formats a size (in bytes) to a specific unit. Since the unit size is
+ * known, the unit is not shown in the returned string. For example,
+ * AjxUtil.formatSizeForUnit(302132199, AjxUtil.SIZE_MEGABYTES, false, 2) 
+ * returns "288.13".
+ *
+ * @param size      The size (in bytes) to be formatted.
+ * @param units     The unit of measure.
+ * @param round     True to round to nearest integer. Default is true.
+ * @param fractions Number of fractional digits to display, if not rounding.
+ *                  Trailing zeros after the decimal point are trimmed.
+ */
+AjxUtil.formatSizeForUnits = 
+function(size, units, round, fractions) {
+	if (units == null) units = AjxUtil.SIZE_BYTES;
+	if (round == null) round = true;
+	if (fractions == null) fractions = 20; // max allowed for toFixed is 20
+
+	switch (units) {
+		case AjxUtil.SIZE_GIGABYTES: { size /= 1073741824; break; }
+		case AjxUtil.SIZE_MEGABYTES: { size /= 1048576; break; }
+		case AjxUtil.SIZE_KILOBYTES: { size /= 1024; break; }
+	}
+	
+	var formattedSize = round ? Math.round(size) : size.toFixed(fractions).replace(/\.?0+$/,"");
+	return formattedSize;
+};
+
+/**
+ * Performs the opposite of AjxUtil.formatSize in that this function takes a 
+ * formatted size.
+ *
+ * @param units Unit constant: "GB", "MB", "KB", "B". Must be specified 
+ *              unless the formatted size ends with the size marker, in
+ *				which case the size marker in the formattedSize param
+ *				overrides this parameter.
+ */
+AjxUtil.parseSize = 
+function(formattedSize, units) {
+	// NOTE: Take advantage of fact that parseFloat ignores bad chars
+	//       after numbers
+	var size = parseFloat(formattedSize.replace(/^\s*/,""));
+
+	var marker = /[GMK]?B$/i;
+	var result = marker.exec(formattedSize);
+	if (result) {
+		//alert("units: "+units+", result[0]: '"+result[0]+"'");
+		units = result[0].toUpperCase();
+	}
+	
+	switch (units) {
+		case AjxUtil.SIZE_GIGABYTES: size *= 1073741824; break;
+		case AjxUtil.SIZE_MEGABYTES: size *= 1048576; break; 
+		case AjxUtil.SIZE_KILOBYTES: size *= 1024; break;
+	}
+	
+	//alert("AjxUtil#parseSize: formattedSize="+formattedSize+", size="+size);
+	return size;
+};
+
+AjxUtil.isInstance = 
+function(aThing, aClass) { 
+	return !!(aThing && aThing.constructor && (aThing.constructor === aClass)); 
+};
+
+AjxUtil.assert = 
+function(aCondition, aMessage) {
+	if (!aCondition && AjxUtil.onassert) AjxUtil.onassert(aMessage);
+};
+
+AjxUtil.onassert = 
+function(aMessage) {
+	// Create an exception object and set the message
+	var myException = new Object();
+	myException.message = aMessage;
+	
+	// Compile a stack trace
+	var myStack = new Array();
+	if (AjxEnv.isIE5_5up) {
+		// On IE, the caller chain is on the arguments stack
+		var myTrace = arguments.caller;
+		while (myTrace) {
+		    myStack[myStack.length] = myTrace.callee;
+	    	myTrace = myTrace.caller;
+		}
+	} else {
+		try {
+			var myTrace = arguments.callee.caller;
+			while (myTrace) {
+				myStack[myStack.length] = myTrace;
+				if (myStack.length > 2) break;
+				myTrace = myTrace.caller;
+		    }
+		} catch (e) {
+		}
+	}
+	myException.stack = myStack;
+	
+	// Alert with the message and a description of the stack
+	var stackString = '';
+	var MAX_LEN = 170;
+	for (var i = 1; i < myStack.length; i++) {
+		if (i > 1) stackString += '\n';
+		if (i < 11) {
+			var fs = myStack[i].toString();
+			if (fs.length > MAX_LEN) {
+				fs = fs.substr(0,MAX_LEN) + '...';
+				fs = fs.replace(/\n/g, '');
+			}
+			stackString += i + ': ' + fs;
+		} else {
+			stackString += '(' + (myStack.length - 11) + ' frames follow)';
+			break;
+		}
+	}
+	alert('assertion:\n\n' + aMessage + '\n\n---- Call Stack ---\n' + stackString);
+	
+	// Now throw the exception
+	throw myException;
+};
+
+AjxUtil.NODE_REPEATS = new Object();
+AjxUtil.NODE_REPEATS["folder"]		= true;
+AjxUtil.NODE_REPEATS["search"]		= true;
+AjxUtil.NODE_REPEATS["tag"]			= true;
+AjxUtil.NODE_REPEATS["pref"]		= true;
+AjxUtil.NODE_REPEATS["attr"]		= true;
+AjxUtil.NODE_REPEATS["c"]			= true;
+AjxUtil.NODE_REPEATS["m"]			= true;
+AjxUtil.NODE_REPEATS["cn"]			= true;
+AjxUtil.NODE_REPEATS["e"]			= true;
+AjxUtil.NODE_REPEATS["a"]			= true;
+AjxUtil.NODE_REPEATS["mbx"]			= true;
+//AjxUtil.NODE_REPEATS["mp"]		= true; // only when parent is "mp"
+// these really shouldn't repeat
+AjxUtil.NODE_REPEATS["prefs"]		= true;
+AjxUtil.NODE_REPEATS["attrs"]		= true;
+AjxUtil.NODE_REPEATS["tags"]		= true;
+
+AjxUtil.NODE_IS_ATTR = new Object();
+AjxUtil.NODE_IS_ATTR["authToken"]	= true;
+AjxUtil.NODE_IS_ATTR["lifetime"]	= true;
+AjxUtil.NODE_IS_ATTR["sessionId"]	= true;
+AjxUtil.NODE_IS_ATTR["name"]		= true;
+AjxUtil.NODE_IS_ATTR["quotaUsed"]	= true;
+AjxUtil.NODE_IS_ATTR["su"]			= true;
+AjxUtil.NODE_IS_ATTR["fr"]			= true;
+AjxUtil.NODE_IS_ATTR["mid"]			= true;
+//AjxUtil.NODE_IS_ATTR["content"]	= true; // only when parent is "note"
+
+AjxUtil.NODE_CONTENT = new Object();
+AjxUtil.NODE_CONTENT["pref"]		= true;
+AjxUtil.NODE_CONTENT["attr"]		= true;
+AjxUtil.NODE_CONTENT["a"]			= true;
+
+// IE doesn't define Node type constants
+AjxUtil.ELEMENT_NODE	= 1;
+AjxUtil.TEXT_NODE		= 3;
+
+/**
+* Convert an XML node to the equivalent JS. Traverses the node's
+* children recursively.
+* <p>
+
+* NOTE: This method has not been extensively tested and likely needs
+* work.
+*
+* @param node		[Element]	XML node
+* @param omitName	[boolean]	if true, don't include node name in output
+*/
+AjxUtil.xmlToJs =
+function(node, omitName) {
+
+	if (node.nodeType == AjxUtil.TEXT_NODE)
+		return ['"', node.data, '"'].join("");
+
+	var name = node.name ? node.name : node.localName;
+	if (node.nodeType == AjxUtil.ELEMENT_NODE) {
+		// if only child is text, no need for enclosing {}
+		var hasTextNode = (node.childNodes && node.childNodes.length == 1 && 
+				   (node.childNodes[0].nodeType == AjxUtil.TEXT_NODE));
+		var text;
+		if (omitName) {
+			text = "{";
+		} else if (hasTextNode) {
+			text = [name, ":"].join("");
+		} else {
+			text = [name, ":{"].join("");
+		}
+		var needComma = false;	
+		if (node.attributes) {
+			for (var i = 0; i < node.attributes.length; i++) {
+				var attr = node.attributes[i];
+				if (attr.name == "xmlns") continue;
+				if (needComma) text += ",";
+				var value = AjxUtil.isNumeric(attr.value) ? attr.value : AjxUtil.jsEncode(attr.value);
+				text = [text, attr.name, ':', value].join("");
+				needComma = true;
+			}
+		}
+		if (node.hasChildNodes()) {
+			var cnodes = new Object();
+			var hasChild = false;
+			for (var i = 0; i < node.childNodes.length; i++) {
+				var child = node.childNodes[i];
+				var cname = child.name ? child.name : child.localName;
+				var isAttr = AjxUtil.NODE_IS_ATTR[cname] || 
+							 (name == "content" && parent.name == "note");
+				if (isAttr) {
+					if (needComma) text += ",";
+					text = [text, cname, ':', AjxUtil.jsEncode(child.textContent)].join("");
+					needComma = true;
+				} else {
+					if (!cnodes[cname])
+						cnodes[cname] = new Array();
+					cnodes[cname].push(child);
+					hasChild = true;
+				}
+			}
+			if (hasChild && needComma) {text += ","; needComma = false;}
+			for (var cname in cnodes) {
+				if (needComma) {
+					text += ",";
+					needComma = false;
+				}
+				var repeats = AjxUtil.NODE_REPEATS[cname] ||
+							  (cname == "mp" && name == "mp");
+				if (repeats) text += cname + ":[";
+				var clist = cnodes[cname];
+				for (var i = 0; i < clist.length; i++) {
+					if (needComma) text += ",";
+					text += AjxUtil.xmlToJs(clist[i], repeats);
+					needComma = true;
+				}
+				if (repeats) text += "]";
+			}
+		}
+		if (!hasTextNode) text += "}";
+	}
+
+	return text;
+}
+
+AjxUtil.JS_CHAR_ENCODINGS = [
+	"\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", "\\u0007",
+	"\\b",     "\\t",     "\\n",     "\\u000B", "\\f",     "\\r",     "\\u000E", "\\u000F",
+	"\\u0010", "\\u0011", "\\u0012", "\\u0013", "\\u0014", "\\u0015", "\\u0016", "\\u0017",
+	"\\u0018", "\\u0019", "\\u001A", "\\u001B", "\\u001C", "\\u001D", "\\u001E", "\\u001F"
+];
+
+AjxUtil.jsEncode =
+function(string) {
+
+	if (!string) return "\"\"";
+
+	var text = '"';
+	for (var i = 0; i < string.length; i++) {
+		var c = string.charAt(i);
+		switch (c) {
+			case '\\': case '"': case '/':
+				text += '\\' + c;
+				break;
+			default:
+				var code = string.charCodeAt(i);
+				text += (code < 32) ? AjxUtil.JS_CHAR_ENCODINGS[code] : c;
+		}
+	}
+	text += '"';
+	return text;
+};
+
+AjxUtil.getInnerText = 
+function(node) {
+ 	if (AjxEnv.isIE)
+ 		return node.innerText;
+
+	function f(n) {
+		if (n) {
+			if (n.nodeType == 3 /* TEXT_NODE */)
+				return n.data;
+			if (n.nodeType == 1 /* ELEMENT_NODE */) {
+				if (/^br$/i.test(n.tagName))
+					return "\r\n";
+				var str = "";
+				for (var i = n.firstChild; i; i = i.nextSibling)
+					str += f(i);
+				return str;
+			}
+		}
+		return "";
+	};
+	return f(node);
+};
+
+/**
+ * This method returns a proxy for the specified object. This is useful when
+ * you want to modify properties of an object and want to keep those values
+ * separate from the values in the original object. You can then iterate
+ * over the proxy's properties and use the <code>hasOwnProperty</code>
+
+ * method to determine if the property is a new value in the proxy.
+ * <p>
+ * <strong>Note:</strong>
+ * A reference to the original object is stored in the proxy as the "_object_" 
+ * property.
+ *
+ * @param object [object] The object to proxy.
+ * @param level  [number] The number of property levels deep to proxy.
+ *						  Defaults to zero.
+ */
+AjxUtil.createProxy = 
+function(object, level) {
+	var proxy;
+	var proxyCtor = function(){}; // bug #6517 (Safari doesnt like 'new Function')
+	proxyCtor.prototype = object;
+	if (object instanceof Array) {
+		proxy  = new Array();
+		var cnt  = object.length;
+		for(var ix = 0; ix < cnt; ix++) {
+			proxy[ix] = object[ix];
+		}
+	} else {
+		proxy = new proxyCtor;
+	}
+	
+	if (level) {
+		for (var prop in object) {
+			if (typeof object[prop] == "object")
+				proxy[prop] = AjxUtil.createProxy(object[prop], level - 1);
+		}
+	}	
+	
+	proxy._object_ = object;
+	return proxy;
+};
+
+/**
+* Returns a copy of a list with empty members removed.
+*
+* @param list	[array]		original list
+*/
+AjxUtil.collapseList =
+function(list) {
+	var newList = [];
+	for (var i = 0; i < list.length; i++)
+		if (list[i])
+			newList.push(list[i]);
+	return newList;
+};
+/** auto include file:./xap/peter/src/zimbra/js/util/AjxText.js **/
+/*
+* ***** BEGIN LICENSE BLOCK *****
+* Version: MPL 1.1
+*
+* The contents of this file are subject to the Mozilla Public
+* License Version 1.1 ("License"); you may not use this file except in
+* compliance with the License. You may obtain a copy of the License at
+* http://www.zimbra.com/license
+*
+* Software distributed under the License is distributed on an "AS IS"
+* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+* the License for the specific language governing rights and limitations
+* under the License.
+*
+* The Original Code is: Zimbra AJAX Toolkit.
+*
+* The Initial Developer of the Original Code is Zimbra, Inc.
+* Portions created by Zimbra are Copyright (C) 2005 Zimbra, Inc.
+* All Rights Reserved.
+*
+* Contributor(s):
+*
+* ***** END LICENSE BLOCK *****
+*/
+
+
+// NOTE: The API for the classes in this file are inspired by the Java text
+//		 formatting classes but the implementation was NOT copied or ported
+//		 from the Java sources.
+
+//
+// Format class
+//
+
+/** 
+ * Base class for all formats. To format an object, instantiate the
+ * format of your choice and call the <code>format</code> method which
+ * returns the formatted string.
+ */
+function AjxFormat(pattern) {
+	this._pattern = pattern;
+	this._segments = [];
+}
+
+/** Returns string representation of this object. */
+AjxFormat.prototype.toString = function() { 
+	var s = [];
+	s.push("pattern=\"",this._pattern,'"');
+	if (this._segments.length > 0) {
+		s.push(", segments={ ");
+		for (var i = 0; i < this._segments.length; i++) {
+			if (i > 0) { s.push(", "); }
+			s.push(String(this._segments[i]));
+		}
+		s.push(" }");
+	}
+	return s.join("");
+};
+
+// Data
+
+AjxFormat.prototype._pattern;
+AjxFormat.prototype._segments;
+
+// Public methods
+
+/** 
+ * This method does <em>not</em> need to be overridden unless
+ * the subclass doesn't use format segments and takes complete 
+ * responsibility for formatting.
+ */
+AjxFormat.prototype.format = function(object) { 
+	var s = [];
+	for (var i = 0; i < this._segments.length; i++) {
+		s.push(this._segments[i].format(object));
+	}
+	return s.join("");
+};
+
+/** 
+ * Parses the given string according to this format's pattern and returns
+ * an object.
+ * <p>
+
+ * <strong>Note:</strong>
+ * The default implementation of this method assumes that the sub-class
+ * has implemented the <code>_createParseObject</code> method.
+ */
+AjxFormat.prototype.parse = function(s) {
+	var object = this._createParseObject();
+	var index = 0;
+	for (var i = 0; i < this._segments.length; i++) {
+		var segment = this._segments[i];
+		index = segment.parse(object, s, index);
+	}
+	// REVISIT: Should this return null instead?
+	if (index < s.length) {
+		throw new AjxFormat.ParsingException(this, null, "input too long"); // I18n
+	}
+	return object;
+};
+
+/** 
+ * Returns an array of segments that comprise this format. 
+ * <p>
+ * <strong>Note:</strong>
+ * This method is specific to this implementation and does not follow
+ * the format classes found in the <code>java.text</code> package.
+ */
+AjxFormat.prototype.getSegments = function() {
+	return this._segments;
+};
+
+/** Returns a string pattern for this format. */
+AjxFormat.prototype.toPattern = function() {
+	return this._pattern;
+};
+
+/** Returns a copy of this format. */
+AjxFormat.prototype.clone = function() {
+	return new this.constructor(this._pattern);
+};
+
+// Protected methods
+
+/**
+ * Creates the object that is initialized by parsing
+ * <p>
+
+ * <strong>Note:</strong>
+ * This must be implemented by sub-classes.
+ */
+AjxFormat.prototype._createParseObject = function(s) {
+	throw new AjxFormat.ParsingException(this, null, "not implemented"); // I18n
+};
+
+// Protected static methods
+
+AjxFormat._zeroPad = function(s, length, zeroChar, rightSide) {
+	s = typeof s == "string" ? s : String(s);
+
+	if (s.length >= length) return s;
+
+	zeroChar = zeroChar || '0';
+	
+	var a = [];
+	for (var i = s.length; i < length; i++) {
+		a.push(zeroChar);
+	}
+	a[rightSide ? "unshift" : "push"](s);
+
+	return a.join("");
+};
+
+//
+// Format exception base class
+//
+
+AjxFormat.FormatException = function(format, message) {
+	this._format = format;
+	this._message = message;
+};
+AjxFormat.FormatException.prototype.toString = function() { 
+	return this._message; 
+};
+
+// Data
+
+AjxFormat.FormatException.prototype._format;
+AjxFormat.FormatException.prototype._message;
+
+//
+// Formatting exception class
+//
+
+AjxFormat.FormattingException = function(format, segment, message) {
+	AjxFormat.FormatException.call(this, format, message);
+	this._segment = segment;
+};
+AjxFormat.FormattingException.prototype = new AjxFormat.FormatException;
+AjxFormat.FormattingException.prototype.constructor = AjxFormat.FormattingException;
+
+// Data
+
+AjxFormat.FormattingException.prototype._segment;
+
+//
+// Parsing exception class
+//
+
+AjxFormat.ParsingException = function(format, segment, message) {
+	AjxFormat.FormatException.call(this, format, message);
+	this._segment = segment;
+};
+AjxFormat.ParsingException.prototype = new AjxFormat.FormatException;
+AjxFormat.ParsingException.prototype.constructor = AjxFormat.ParsingException;
+
+// Data
+
+AjxFormat.ParsingException.prototype._segment;
+
+//
+// Segment class
+//
+
+AjxFormat.Segment = function(format, s) {
+	this._parent = format;
+	this._s = s;
+};
+
+AjxFormat.Segment.prototype.toString = function() { 
+	return "segment: \""+this._s+'"'; 
+};
+
+// Data
+
+AjxFormat.Segment.prototype._parent;
+AjxFormat.Segment.prototype._s;
+
+// Public methods
+
+AjxFormat.Segment.prototype.format = function(o) { 
+	return this._s; 
+};
+
+/**
+ * Parses the string at the given index, initializes the parse object
+ * (as appropriate), and returns the new index within the string for
+ * the next parsing step.
+ * <p>
+ * <strong>Note:</strong>
+ * This method must be implemented by sub-classes.
+ *
+ * @param o     [object] The parse object to be initialized.
+ * @param s     [string] The input string to be parsed.
+ * @param index [number] The index within the string to start parsing.
+ */
+AjxFormat.Segment.prototype.parse = function(o, s, i) {
+	throw new AjxFormat.ParsingException(this._parent, this, "not implemented"); // I18n
+};
+
+// Protected static methods
+
+AjxFormat.Segment._parseLiteral = function(literal, s, index) {
+	if (s.length - index < literal.length) {
+		throw new AjxFormat.ParsingException(this._parent, this, "input too short"); // I18n
+	}
+	for (var i = 0; i < literal.length; i++) {
+		if (literal.charAt(i) != s.charAt(index + i)) {
+			throw new AjxFormat.ParsingException(this._parent, this, "input doesn't match"); // I18n
+		}
+	}
+	return index + literal.length;
+};
+AjxFormat.Segment._parseLiterals = function(literals, s, index) {
+	for (var i = 0; i < literals.length; i++) {
+		try {
+			var literal = literals[i];
+			return AjxFormat.Segment._parseLiteral(literal, s, index);
+		}
+		catch (e) {
+			// ignore. keep trying to find a match
+		}
+	}
+	return -1;
+};
+
+/**
+ * Parses an integer at the offset of the given string and calls a
+ * method on the specified object.
+ *
+ * @param o         [object]   The target object.
+ * @param f         [function] The method to call on the target object.
+ * @param adjust    [number]   The numeric adjustment to make on the
+ *                             value before calling the object method.
+ * @param s         [string]   The string to parse.
+ * @param index     [number]   The index within the string to start parsing.
+ * @param fixedlen  [number]   If specified, specifies the required number
+ *                             of digits to be parsed.
+ */
+AjxFormat.Segment._parseInt = function(o, f, adjust, s, index, fixedlen) {
+	var len = fixedlen || s.length - index;
+	var head = index;
+	for (var i = 0; i < len; i++) {
+		if (!s.charAt(index++).match(/\d/)) {
+			index--;
+			break;
+		}
+	}
+	var tail = index;
+	if (head == tail) {
+		throw new AjxFormat.ParsingException(this._parent, this, "number not present"); // I18n
+	}
+	if (fixedlen && tail - head != fixedlen) {
+		throw new AjxFormat.ParsingException(this._parent, this, "number too short"); // I18n
+	}
+	var value = parseInt(s.substring(head, tail), 10);
+	if (f) {
+		var target = o || window;
+		f.call(target, value + adjust);
+	}
+	return tail;
+};
+
+//
+// Date format class
+//
+
+/**
+ * The AjxDateFormat class formats Date objects according to a specified 
+ * pattern. The patterns are defined the same as the SimpleDateFormat
+ * class in the Java libraries. <strong>Note:</strong> <em>Only the
+ * Gregorian Calendar is supported at this time.</em> Supporting other
+ * calendars would require a lot more information downloaded to the
+ * client. Limiting dates to the Gregorian calendar is a trade-off.
+ * <p>
+
+ * <strong>Note:</strong>
+ * The date format differs from the Java patterns a few ways: the pattern
+ * "EEEEE" (5 'E's) denotes a <em>short</em> weekday and the pattern "MMMMM"
+ * (5 'M's) denotes a <em>short</em> month name. This matches the extended 
+ * pattern found in the Common Locale Data Repository (CLDR) found at: 
+ * http://www.unicode.org/cldr/.
+ */
+function AjxDateFormat(pattern) {
+	AjxFormat.call(this, pattern);
+	if (typeof pattern == "number") {
+		switch (pattern) {
+			case AjxDateFormat.SHORT: pattern = I18nMsg.formatDateShort; break;
+			case AjxDateFormat.MEDIUM: pattern = I18nMsg.formatDateMedium; break;
+			case AjxDateFormat.LONG: pattern = I18nMsg.formatDateLong; break;
+			case AjxDateFormat.FULL: pattern = I18nMsg.formatDateFull; break;
+		}
+	}	
+	for (var i = 0; i < pattern.length; i++) {
+		// literal
+		var c = pattern.charAt(i);
+		if (c == "'") {
+			var head = i + 1;
+			for (i++ ; i < pattern.length; i++) {
+				var c = pattern.charAt(i);
+				if (c == "'") {
+					if (i + 1 < pattern.length && pattern.charAt(i + 1) == "'") {
+						pattern = pattern.substr(0, i) + pattern.substr(i + 1);
+					}
+					else {
+						break;
+					}
+				}
+			}
+			if (i == pattern.length) {
+				// TODO: i18n
+				throw new FormatException(this, "unterminated string literal");
+			}
+			var tail = i;
+			var segment = new AjxFormat.TextSegment(this, pattern.substring(head, tail));
+			this._segments.push(segment);
+			continue;
+		}
+
+		// non-meta chars
+		var head = i;
+		while(i < pattern.length) {
+			c = pattern.charAt(i);
+			if (AjxDateFormat._META_CHARS.indexOf(c) != -1 || c == "'") {
+				break;
+			}
+			i++;
+		}
+		var tail = i;
+		if (head != tail) {
+			var segment = new AjxFormat.TextSegment(this, pattern.substring(head, tail));
+			this._segments.push(segment);
+			i--;
+			continue;
+		}
+		
+		// meta char
+		var head = i;
+		while(++i < pattern.length) {
+			if (pattern.charAt(i) != c) {
+				break;
+			}		
+		}
+		var tail = i--;
+		var count = tail - head;
+		var field = pattern.substr(head, count);
+		var segment = null;
+		switch (c) {
+			case 'G': segment = new AjxDateFormat.EraSegment(this, field); break;
+			case 'y': segment = new AjxDateFormat.YearSegment(this, field); break;
+			case 'M': segment = new AjxDateFormat.MonthSegment(this, field); break;
+			case 'w': segment = new AjxDateFormat.WeekSegment(this, field); break;
+			case 'W': segment = new AjxDateFormat.WeekSegment(this, field); break;
+			case 'D': segment = new AjxDateFormat.DaySegment(this, field); break;
+			case 'd': segment = new AjxDateFormat.DaySegment(this, field); break;
+			case 'F': segment = new AjxDateFormat.WeekdaySegment(this, field); break;
+			case 'E': segment = new AjxDateFormat.WeekdaySegment(this, field); break;
+			case 'a': segment = new AjxDateFormat.AmPmSegment(this, field); break;
+			case 'H': segment = new AjxDateFormat.HourSegment(this, field); break;
+			case 'k': segment = new AjxDateFormat.HourSegment(this, field); break;
+			case 'K': segment = new AjxDateFormat.HourSegment(this, field); break;
+			case 'h': segment = new AjxDateFormat.HourSegment(this, field); break;
+			case 'm': segment = new AjxDateFormat.MinuteSegment(this, field); break;
+			case 's': segment = new AjxDateFormat.SecondSegment(this, field); break;
+			case 'S': segment = new AjxDateFormat.SecondSegment(this, field); break;
+			case 'z': segment = new AjxDateFormat.TimezoneSegment(this, field); break;
+			case 'Z': segment = new AjxDateFormat.TimezoneSegment(this, field); break;
+		}
+		if (segment != null) {
+			segment._index = this._segments.length;
+			this._segments.push(segment);
+		}
+	}
+}
+AjxDateFormat.prototype = new AjxFormat;
+AjxDateFormat.prototype.constructor = AjxDateFormat;
+
+AjxDateFormat.prototype.toString = function() {
+	return "[AjxDateFormat: "+AjxFormat.prototype.toString.call(this)+"]";
+};
+
+// Constants
+
+AjxDateFormat.SHORT = 0;
+AjxDateFormat.MEDIUM = 1;
+AjxDateFormat.LONG = 2;
+AjxDateFormat.FULL = 3;
+AjxDateFormat.DEFAULT = AjxDateFormat.MEDIUM;
+
+AjxDateFormat._META_CHARS = "GyMwWDdFEaHkKhmsSzZ";
+
+AjxDateFormat._dateFormats = [
+	I18nMsg.formatDateShort, I18nMsg.formatDateMedium,
+	I18nMsg.formatDateLong, I18nMsg.formatDateFull
+];
+AjxDateFormat._timeFormats = [
+	I18nMsg.formatTimeShort, I18nMsg.formatTimeMedium, 
+	I18nMsg.formatTimeLong, I18nMsg.formatTimeFull
+];
+
+AjxDateFormat._DATE_FORMATTERS = {};
+AjxDateFormat._TIME_FORMATTERS = {};
+AjxDateFormat._DATETIME_FORMATTERS = {};
+
+// Static methods
+
+AjxDateFormat.getDateInstance = function(style) {
+	// lazily create formatters
+	style = style != null ? style : AjxDateFormat.DEFAULT;
+	if (!AjxDateFormat._DATE_FORMATTERS[style]) {
+		AjxDateFormat._DATE_FORMATTERS[style] = new AjxDateFormat(AjxDateFormat._dateFormats[style]);
+	}
+	return AjxDateFormat._DATE_FORMATTERS[style];
+};
+
+AjxDateFormat.getTimeInstance = function(style) {
+	// lazily create formatters
+	style = style != null ? style : AjxDateFormat.DEFAULT;
+	if (!AjxDateFormat._TIME_FORMATTERS[style]) {
+		AjxDateFormat._TIME_FORMATTERS[style] = new AjxDateFormat(AjxDateFormat._timeFormats[style]);
+	}
+	return AjxDateFormat._TIME_FORMATTERS[style];
+};
+
+AjxDateFormat.getDateTimeInstance = function(dateStyle, timeStyle) {
+	// lazily create formatters
+	dateStyle = dateStyle != null ? dateStyle : AjxDateFormat.DEFAULT;
+	timeStyle = timeStyle != null ? timeStyle : AjxDateFormat.DEFAULT;
+	var style = dateStyle * 10 + timeStyle;
+	if (!AjxDateFormat._DATETIME_FORMATTERS[style]) {
+		var pattern = I18nMsg.formatDateTime;
+		var params = [ AjxDateFormat._dateFormats[dateStyle], AjxDateFormat._timeFormats[timeStyle] ];
+		
+		var dateTimePattern = AjxMessageFormat.format(pattern, params);
+		AjxDateFormat._DATETIME_FORMATTERS[style] = new AjxDateFormat(dateTimePattern);
+	}
+	return AjxDateFormat._DATETIME_FORMATTERS[style];
+};
+
+AjxDateFormat.format = function(pattern, date) {
+	return new AjxDateFormat(pattern).format(date);
+};
+AjxDateFormat.parse = function(pattern, dateStr) {
+	return new AjxDateFormat(pattern).parse(dateStr);
+};
+
+// Public methods
+
+/** 
+ * Parses the given string and returns a date. If the string cannot be
+ * parsed as a date, <code>null</code> is returned.
+ */
+AjxDateFormat.prototype.parse = function(s) {
+	var object = null;
+	try {
+		object = AjxFormat.prototype.parse.call(this, s);
+	}
+	catch (e) {
+		// do nothing
+	}
+	return object;
+};
+
+// Protected methods
+
+AjxDateFormat.prototype._createParseObject = function() {
+	return new Date(0, 0, 1, 0, 0, 0, 0);
+};
+
+//
+// Text segment class
+//
+
+AjxFormat.TextSegment = function(format, s) {
+	AjxFormat.Segment.call(this, format, s);
+};
+AjxFormat.TextSegment.prototype = new AjxFormat.Segment;
+AjxFormat.TextSegment.prototype.constructor = AjxFormat.TextSegment;
+
+AjxFormat.TextSegment.prototype.toString = function() { 
+	return "text: \""+this._s+'"'; 
+};
+
+// Public methods
+
+AjxFormat.TextSegment.prototype.parse = function(o, s, index) {
+	return AjxFormat.Segment._parseLiteral(this._s, s, index);
+};
+
+//
+// Date segment class
+//
+
+AjxDateFormat.DateSegment = function(format, s) {
+	AjxFormat.Segment.call(this, format, s);
+}
+AjxDateFormat.DateSegment.prototype = new AjxFormat.Segment;
+AjxDateFormat.DateSegment.prototype.constructor = AjxDateFormat.DateSegment;
+
+// Protected methods
+
+AjxDateFormat.DateSegment.prototype._getFixedLength = function() {
+	var fixedlen;
+	if (this._index + 1 < this._parent._segments.length) {
+		var nextSegment = this._parent._segments[this._index + 1];
+		if (!(nextSegment instanceof AjxFormat.TextSegment)) {
+			fixedlen = this._s.length;
+		}
+	}
+	return fixedlen;
+};
+
+//
+// Date era segment class
+//
+
+AjxDateFormat.EraSegment = function(format, s) {
+	AjxDateFormat.DateSegment.call(this, format, s);
+};
+AjxDateFormat.EraSegment.prototype = new AjxDateFormat.DateSegment;
+AjxDateFormat.EraSegment.prototype.constructor = AjxDateFormat.EraSegment;
+
+AjxDateFormat.EraSegment.prototype.toString = function() { 
+	return "dateEra: \""+this._s+'"'; 
+};
+
+// Public methods
+
+AjxDateFormat.EraSegment.prototype.format = function(date) { 
+	// TODO: Only support current era at the moment...
+	return I18nMsg.eraAD;
+};
+
+//
+// Date year segment class
+//
+
+AjxDateFormat.YearSegment = function(format, s) {
+	AjxDateFormat.DateSegment.call(this, format, s);
+};
+AjxDateFormat.YearSegment.prototype = new AjxDateFormat.DateSegment;
+AjxDateFormat.YearSegment.prototype.constructor = AjxDateFormat.YearSegment;
+
+AjxDateFormat.YearSegment.prototype.toString = function() { 
+	return "dateYear: \""+this._s+'"'; 
+};
+
+// Public methods
+
+AjxDateFormat.YearSegment.prototype.format = function(date) { 
+	var year = String(date.getFullYear());
+	return this._s.length < 4 ? year.substr(year.length - 2) : AjxFormat._zeroPad(year, this._s.length);
+};
+
+AjxDateFormat.YearSegment.prototype.parse = function(date, s, index) {
+	var fixedlen = this._getFixedLength();
+	var nindex = AjxFormat.Segment._parseInt(date, date.setFullYear, 0, s, index, fixedlen);
+	// adjust 2-digit years
+	if (nindex - index == 2) {
+		if (!AjxDateFormat._2digitStartYear) {
+			AjxDateFormat._2digitStartYear = parseInt(AjxMsg.dateParsing2DigitStartYear);
+		}
+		var syear = AjxDateFormat._2digitStartYear;
+		var pyear = parseInt(s.substr(index,2));
+		var century = (Math.floor(syear / 100) + (pyear < (syear % 100) ? 1 : 0)) * 100;
+		var year = century + pyear;
+		date.setFullYear(year);
+	}
+	return nindex;
+};
+
+//
+// Date month segment class
+//
+
+AjxDateFormat.MonthSegment = function(format, s) {
+	AjxDateFormat.DateSegment.call(this, format, s);
+};
+AjxDateFormat.MonthSegment.prototype = new AjxDateFormat.DateSegment;
+AjxDateFormat.MonthSegment.prototype.constructor = AjxDateFormat.MonthSegment;
+
+AjxDateFormat.MonthSegment.prototype.toString = function() { 
+	return "dateMonth: \""+this._s+'"'; 
+};
+
+// Constants
+
+AjxDateFormat.MonthSegment.MONTHS = {};
+AjxDateFormat.MonthSegment.MONTHS[AjxDateFormat.SHORT] = [
+	AjxMsg.monthJanShort, AjxMsg.monthFebShort, AjxMsg.monthMarShort, 
+	AjxMsg.monthAprShort, AjxMsg.monthMayShort, AjxMsg.monthJunShort, 
+	AjxMsg.monthJulShort, AjxMsg.monthAugShort, AjxMsg.monthSepShort, 
+	AjxMsg.monthOctShort, AjxMsg.monthNovShort, AjxMsg.monthDecShort
+];
+AjxDateFormat.MonthSegment.MONTHS[AjxDateFormat.MEDIUM] = [ 
+	I18nMsg.monthJanMedium, I18nMsg.monthFebMedium, I18nMsg.monthMarMedium,
+	I18nMsg.monthAprMedium, I18nMsg.monthMayMedium, I18nMsg.monthJunMedium,
+	I18nMsg.monthJulMedium, I18nMsg.monthAugMedium, I18nMsg.monthSepMedium,
+	I18nMsg.monthOctMedium, I18nMsg.monthNovMedium, I18nMsg.monthDecMedium
+];
+AjxDateFormat.MonthSegment.MONTHS[AjxDateFormat.LONG] = [ 
+	I18nMsg.monthJanLong, I18nMsg.monthFebLong, I18nMsg.monthMarLong,
+	I18nMsg.monthAprLong, I18nMsg.monthMayLong, I18nMsg.monthJunLong,
+	I18nMsg.monthJulLong, I18nMsg.monthAugLong, I18nMsg.monthSepLong,
+	I18nMsg.monthOctLong, I18nMsg.monthNovLong, I18nMsg.monthDecLong
+];
+
+// Public methods
+
+AjxDateFormat.MonthSegment.prototype.format = function(date) {
+	var month = date.getMonth();
+	switch (this._s.length) {
+		case 1: return String(month + 1);
+		case 2: return AjxFormat._zeroPad(month + 1, 2);
+		case 3: return AjxDateFormat.MonthSegment.MONTHS[AjxDateFormat.MEDIUM][month];
+		case 5: return AjxDateFormat.MonthSegment.MONTHS[AjxDateFormat.SHORT][month];
+	}
+	return AjxDateFormat.MonthSegment.MONTHS[AjxDateFormat.LONG][month];
+};
+
+AjxDateFormat.MonthSegment.prototype.parse = function(date, s, index) {
+	var months;
+	switch (this._s.length) {
+		case 3: 
+			months = AjxDateFormat.MonthSegment.MONTHS[AjxDateFormat.SHORT];
+		case 4: 
+			months = months || AjxDateFormat.MonthSegment.MONTHS[AjxDateFormat.MEDIUM];
+		case 5: {
+			months = months || AjxDateFormat.MonthSegment.MONTHS[AjxDateFormat.LONG];
+			var nindex = AjxFormat.Segment._parseLiterals(months, s, index);
+			if (nindex != -1) {
+				return nindex;
+			}
+			throw new AjxFormat.ParsingException(this._parent, this, "no match"); // I18n
+		}
+	}
+	var fixedlen = this._getFixedLength();
+	return AjxFormat.Segment._parseInt(date, date.setMonth, -1, s, index, fixedlen);
+};
+
+//
+// Date week segment class
+//
+
+AjxDateFormat.WeekSegment = function(format, s) {
+	AjxDateFormat.DateSegment.call(this, format, s);
+};
+AjxDateFormat.WeekSegment.prototype = new AjxDateFormat.DateSegment;
+AjxDateFormat.WeekSegment.prototype.constructor = AjxDateFormat.WeekSegment;
+
+AjxDateFormat.WeekSegment.prototype.toString = function() { 
+	return "weekMonth: \""+this._s+'"'; 
+};
+
+// Public methods
+
+AjxDateFormat.WeekSegment.prototype.format = function(date) {
+	var year = date.getYear();
+	var month = date.getMonth();
+	var day = date.getDate();
+	
+	var ofYear = /w/.test(this._s);
+	var date2 = new Date(year, ofYear ? 0 : month, 1);
+
+	var week = 0;
+	while (true) {
+		week++;
+		if (date2.getMonth() > month || (date2.getMonth() == month && date2.getDate() >= day)) {
+			break;
+		}
+		date2.setDate(date2.getDate() + 7);
+	}
+
+	return AjxFormat._zeroPad(week, this._s.length);
+};
+
+//
+// Date day segment class
+//
+
+AjxDateFormat.DaySegment = function(format, s) {
+	AjxDateFormat.DateSegment.call(this, format, s);
+};
+AjxDateFormat.DaySegment.prototype = new AjxDateFormat.DateSegment;
+AjxDateFormat.DaySegment.prototype.constructor = AjxDateFormat.DaySegment;
+
+AjxDateFormat.DaySegment.prototype.toString = function() { 
+	return "dateDay: \""+this._s+'"'; 
+};
+
+// Public methods
+
+AjxDateFormat.DaySegment.prototype.format = function(date) {
+	var month = date.getMonth();
+	var day = date.getDate();
+	if (/D/.test(this._s) && month > 0) {
+		var year = date.getYear();
+		do {
+			// set date to first day of month and then go back one day
+			var date2 = new Date(year, month, 1);
+			date2.setDate(0); 
+			
+			day += date2.getDate();
+			month--;
+		} while (month > 0);
+	}
+	return AjxFormat._zeroPad(day, this._s.length);
+};
+
+AjxDateFormat.DaySegment.prototype.parse = function(date, s, index) {
+	if (/D/.test(this._s)) {
+		date.setMonth(0);
+	}
+	var fixedlen = this._getFixedLength();
+	return AjxFormat.Segment._parseInt(date, date.setDate, 0, s, index, fixedlen);
+};
+
+//
+// Date weekday segment class
+//
+
+AjxDateFormat.WeekdaySegment = function(format, s) {
+	AjxDateFormat.DateSegment.call(this, format, s);
+};
+AjxDateFormat.WeekdaySegment.prototype = new AjxDateFormat.DateSegment;
+AjxDateFormat.WeekdaySegment.prototype.constructor = AjxDateFormat.WeekdaySegment;
+
+AjxDateFormat.DaySegment.prototype.toString = function() { 
+	return "dateDay: \""+this._s+'"'; 
+};
+
+// Constants
+
+AjxDateFormat.WeekdaySegment.WEEKDAYS = {};
+// NOTE: The short names aren't available in Java so we have to define them.
+AjxDateFormat.WeekdaySegment.WEEKDAYS[AjxDateFormat.SHORT] = [ 
+	AjxMsg.weekdaySunShort, AjxMsg.weekdayMonShort, AjxMsg.weekdayTueShort,
+	AjxMsg.weekdayWedShort, AjxMsg.weekdayThuShort, AjxMsg.weekdayFriShort,
+	AjxMsg.weekdaySatShort
+];
+AjxDateFormat.WeekdaySegment.WEEKDAYS[AjxDateFormat.MEDIUM] = [ 
+	I18nMsg.weekdaySunMedium, I18nMsg.weekdayMonMedium, I18nMsg.weekdayTueMedium,
+	I18nMsg.weekdayWedMedium, I18nMsg.weekdayThuMedium, I18nMsg.weekdayFriMedium,
+	I18nMsg.weekdaySatMedium
+];
+AjxDateFormat.WeekdaySegment.WEEKDAYS[AjxDateFormat.LONG] = [ 
+	I18nMsg.weekdaySunLong, I18nMsg.weekdayMonLong, I18nMsg.weekdayTueLong,
+	I18nMsg.weekdayWedLong, I18nMsg.weekdayThuLong, I18nMsg.weekdayFriLong,
+	I18nMsg.weekdaySatLong
+];
+
+// Public methods
+
+AjxDateFormat.WeekdaySegment.prototype.format = function(date) {
+	var weekday = date.getDay();
+	if (/E/.test(this._s)) {
+		var style;
+		switch (this._s.length) {
+			case 4: style = AjxDateFormat.LONG; break;
+			case 5: style = AjxDateFormat.SHORT; break;
+			default: style = AjxDateFormat.MEDIUM;
+		}
+		return AjxDateFormat.WeekdaySegment.WEEKDAYS[style][weekday];
+	}
+	return AjxFormat._zeroPad(weekday, this._s.length);
+};
+
+//
+// Time segment class
+//
+
+AjxDateFormat.TimeSegment = function(format, s) {
+	AjxFormat.Segment.call(this, format, s);
+};
+AjxDateFormat.TimeSegment.prototype = new AjxFormat.Segment;
+AjxDateFormat.TimeSegment.prototype.constructor = AjxDateFormat.TimeSegment;
+
+//
+// Time hour segment class
+//
+
+AjxDateFormat.HourSegment = function(format, s) {
+	AjxFormat.Segment.call(this, format, s);
+};
+AjxDateFormat.HourSegment.prototype = new AjxDateFormat.TimeSegment;
+AjxDateFormat.HourSegment.prototype.constructor = AjxDateFormat.HourSegment;
+
+AjxDateFormat.HourSegment.prototype.toString = function() { 
+	return "timeHour: \""+this._s+'"'; 
+};
+
+// Public methods
+
+AjxDateFormat.HourSegment.prototype.format = function(date) {
+	var hours = date.getHours();
+	if (hours > 12 && /[hK]/.test(this._s)) {
+		hours -= 12;
+	}
+	/***
+	// NOTE: This is commented out to match the Java formatter output
+	//       but from the comments for these meta-chars, it doesn't
+	//       seem right.
+	if (/[Hk]/.test(this._s)) {
+		hours--;
+	}
+	/***/
+	return AjxFormat._zeroPad(hours, this._s.length);
+};
+
+//
+// Time minute segment class
+//
+
+AjxDateFormat.MinuteSegment = function(format, s) {
+	AjxFormat.Segment.call(this, format, s);
+};
+AjxDateFormat.MinuteSegment.prototype = new AjxDateFormat.TimeSegment;
+AjxDateFormat.MinuteSegment.prototype.constructor = AjxDateFormat.MinuteSegment;
+
+AjxDateFormat.MinuteSegment.prototype.toString = function() { 
+	return "timeMinute: \""+this._s+'"'; 
+};
+
+// Public methods
+
+AjxDateFormat.MinuteSegment.prototype.format = function(date) {
+	var minutes = date.getMinutes();
+	return AjxFormat._zeroPad(minutes, this._s.length);
+};
+
+//
+// Time second segment class
+//
+
+AjxDateFormat.SecondSegment = function(format, s) {
+	AjxFormat.Segment.call(this, format, s);
+};
+AjxDateFormat.SecondSegment.prototype = new AjxDateFormat.TimeSegment;
+AjxDateFormat.SecondSegment.prototype.constructor = AjxDateFormat.SecondSegment;
+
+AjxDateFormat.SecondSegment.prototype.toString = function() { 
+	return "timeSecond: \""+this._s+'"'; 
+};
+
+// Public methods
+
+AjxDateFormat.SecondSegment.prototype.format = function(date) {
+	var minutes = /s/.test(this._s) ? date.getSeconds() : date.getMilliseconds();
+	return AjxFormat._zeroPad(minutes, this._s.length);
+};
+
+//
+// Time am/pm segment class
+//
+
+AjxDateFormat.AmPmSegment = function(format, s) {
+	AjxFormat.Segment.call(this, format, s);
+};
+AjxDateFormat.AmPmSegment.prototype = new AjxDateFormat.TimeSegment;
+AjxDateFormat.AmPmSegment.prototype.constructor = AjxDateFormat.AmPmSegment;
+
+AjxDateFormat.AmPmSegment.prototype.toString = function() { 
+	return "timeAmPm: \""+this._s+'"'; 
+};
+
+// Public methods
+
+AjxDateFormat.AmPmSegment.prototype.format = function(date) {
+	var hours = date.getHours();
+	return hours < 12 ? I18nMsg.periodAm : I18nMsg.periodPm;
+};
+
+//
+// Time timezone segment class
+//
+
+AjxDateFormat.TimezoneSegment = function(format, s) {
+	AjxFormat.Segment.call(this, format, s);
+};
+AjxDateFormat.TimezoneSegment.prototype = new AjxDateFormat.TimeSegment;
+AjxDateFormat.TimezoneSegment.prototype.constructor = AjxDateFormat.TimezoneSegment;
+
+AjxDateFormat.TimezoneSegment.prototype.toString = function() { 
+	return "timeTimezone: \""+this._s+'"'; 
+};
+
+// Public methods
+
+AjxDateFormat.TimezoneSegment.prototype.format = function(date) {
+	var clientId = date.timezone || AjxTimezone.DEFAULT;
+	if (/Z/.test(this._s)) {
+		return AjxTimezone.getShortName(clientId);
+	}
+	return this._s.length < 4 ? AjxTimezone.getMediumName(clientId) : AjxTimezone.getLongName(clientId);
+};
+
+//
+// Message format class
+//
+
+function AjxMessageFormat(pattern) {
+	AjxFormat.call(this, pattern);
+	for (var i = 0; i < pattern.length; i++) {
+		// literal
+		var c = pattern.charAt(i);
+		if (c == "'") {
+			if (i + 1 < pattern.length && pattern.charAt(i + 1) == "'") {
+				var segment = new AjxFormat.TextSegment(this, "'");
+				this._segments.push(segment);
+				i++;
+				continue;
+			}
+			var head = i + 1;
+			for (i++ ; i < pattern.length; i++) {
+				var c = pattern.charAt(i);
+				if (c == "'") {
+					if (i + 1 < pattern.length && pattern.charAt(i + 1) == "'") {
+						pattern = pattern.substr(0, i) + pattern.substr(i + 1);
+					}
+					else {
+						break;
+					}
+				}
+			}
+			if (i == pattern.length) {
+				// TODO: i18n
+				throw new AjxFormat.FormatException(this, "unterminated string literal");
+			}
+			var tail = i;
+			var segment = new AjxFormat.TextSegment(this, pattern.substring(head, tail));
+			this._segments.push(segment);
+			continue;
+		}
+		
+		// non-meta chars
+		var head = i;
+		while(i < pattern.length) {
+			c = pattern.charAt(i);
+			if (c == '{' || c == "'") {
+				break;
+			}
+			i++;
+		}
+		var tail = i;
+		if (head != tail) {
+			var segment = new AjxFormat.TextSegment(this, pattern.substring(head, tail));
+			this._segments.push(segment);
+			i--;
+			continue;
+		}
+		
+		// meta char
+		var head = i + 1;
+		while(++i < pattern.length) {
+			if (pattern.charAt(i) == '}') {
+				break;
+			}		
+		}
+		var tail = i;
+		var count = tail - head;
+		var field = pattern.substr(head, count);
+		var segment = new AjxMessageFormat.MessageSegment(this, field);		
+		if (segment != null) {
+			this._segments.push(segment);
+		}
+	}
+}
+AjxMessageFormat.prototype = new AjxFormat;
+AjxMessageFormat.prototype.constructor = AjxMessageFormat;
+
+AjxMessageFormat.prototype.toString = function() {
+	return "[AjxMessageFormat: "+AjxFormat.prototype.toString.call(this)+"]";
+};
+
+// Static methods
+
+AjxMessageFormat.format = function(pattern, params) {
+	return new AjxMessageFormat(pattern).format(params);
+};
+
+// Public methods
+
+AjxMessageFormat.prototype.format = function(params) {
+	if (!(params instanceof Array)) {
+		params = [ params ];
+	}
+	return AjxFormat.prototype.format.call(this, params);
+};
+
+//
+// AjxMessageFormat.MessageSegment class
+//
+
+AjxMessageFormat.MessageSegment = function(format, s) {
+	AjxFormat.Segment.call(this, format, s);
+	var parts = s.split(',');
+	this._index = Number(parts[0]);
+	this._type = parts[1] || "string";
+	this._style = parts[2];
+	switch (this._type) {
+		case "number": {
+			switch (this._style) {
+				case "integer": this._formatter = AjxNumberFormat.getIntegerInstance(); break;
+				case "currency": this._formatter = AjxNumberFormat.getCurrencyInstance(); break;
+				case "percent": this._formatter = AjxNumberFormat.getPercentInstance(); break;
+				default: this._formatter = this._style == null ? AjxNumberFormat.getInstance() : new AjxNumberFormat(this._style);
+			}
+			break;
+		}
+		case "date": case "time": {
+			var func = this._type == "date" ? AjxDateFormat.getDateInstance : AjxDateFormat.getTimeInstance;
+			switch (this._style) {
+				case "short": this._formatter = func(AjxDateFormat.SHORT); break;
+				case "medium": this._formatter = func(AjxDateFormat.MEDIUM); break;
+				case "long": this._formatter = func(AjxDateFormat.LONG); break;
+				case "full": this._formatter = func(AjxDateFormat.FULL); break;
+				default: this._formatter = this._style == null ? func(AjxDateFormat.DEFAULT) : new AjxDateFormat(this._style);
+			}
+			break;
+		}
+		case "choice": /*TODO*/ break;
+	}
+	
+};
+AjxMessageFormat.MessageSegment.prototype = new AjxFormat.Segment;
+AjxMessageFormat.MessageSegment.prototype.constructor = AjxMessageFormat.MessageSegment;
+
+AjxMessageFormat.MessageSegment.prototype.toString = function() {
+	var a = [ "message: \"", this._s, "\", index: ", this.index ];
+	if (this._type) a.push(", type: ", this._type);
+	if (this._style) a.push(", style: ", this._style);
+	if (this._formatter) a.push(", formatter: ", this._formatter.toString());
+	return a.join("");
+};
+
+// Data
+
+AjxMessageFormat.MessageSegment.prototype._index;
+AjxMessageFormat.MessageSegment.prototype._type;
+AjxMessageFormat.MessageSegment.prototype._style;
+
+AjxMessageFormat.MessageSegment.prototype._formatter;
+
+// Public methods
+
+AjxMessageFormat.MessageSegment.prototype.format = function(args) {
+	var object = args[this._index];
+	return this._formatter ? this._formatter.format(object) : String(object);
+};
+
+//
+// AjxNumberFormat class
+//
+
+/**
+ * @param pattern       The number pattern.
+ * @param skipNegFormat Specifies whether to skip the generation of this
+ *                      format's negative value formatter. 
+ *                      <p>
+
+ *                      <strong>Note:</strong> 
+ *                      This parameter is only used by the implementation 
+ *                      and should not be passed by application code 
+ *                      instantiating a custom number format.
+ */
+function AjxNumberFormat(pattern, skipNegFormat) {
+	AjxFormat.call(this, pattern);
+	if (pattern == "") return;
+
+	var patterns = pattern.split(/;/);
+	var pattern = patterns[0];
+	
+	// parse prefix
+	var i = 0;
+	var results = this.__parseStatic(pattern, i);
+	i = results.offset;
+	var hasPrefix = results.text != "";
+	if (hasPrefix) {
+		this._segments.push(new AjxFormat.TextSegment(this, results.text));
+	}
+	
+	// parse number descriptor
+	var start = i;
+	while (i < pattern.length &&
+	       AjxNumberFormat._META_CHARS.indexOf(pattern.charAt(i)) != -1) {
+		i++;
+	}
+	var end = i;
+
+	var numPattern = pattern.substring(start, end);
+	var e = numPattern.indexOf('E');
+	var expon = e != -1 ? numPattern.substring(e + 1) : null;
+	if (expon) {
+		numPattern = numPattern.substring(0, e);
+		this._showExponent = true;
+	}
+	
+	var dot = numPattern.indexOf('.');
+	var whole = dot != -1 ? numPattern.substring(0, dot) : numPattern;
+	if (whole) {
+		var comma = whole.lastIndexOf(',');
+		if (comma != -1) {
+			this._groupingOffset = whole.length - comma - 1;
+		}
+		whole = whole.replace(/[^#0]/g,"");
+		var zero = whole.indexOf('0');
+		if (zero != -1) {
+			this._minIntDigits = whole.length - zero;
+		}
+		this._maxIntDigits = whole.length;
+	}
+	
+	var fract = dot != -1 ? numPattern.substring(dot + 1) : null;
+	if (fract) {
+		var zero = fract.lastIndexOf('0');
+		if (zero != -1) {
+			this._minFracDigits = zero + 1;
+		}
+		this._maxFracDigits = fract.replace(/[^#0]/g,"").length;
+	}
+	
+	this._segments.push(new AjxNumberFormat.NumberSegment(this, numPattern));
+	
+	// parse suffix
+	var results = this.__parseStatic(pattern, i);
+	i = results.offset;
+	if (results.text != "") {
+		this._segments.push(new AjxFormat.TextSegment(this, results.text));
+	}
+	
+	// add negative formatter
+	if (skipNegFormat) return;
+	
+	if (patterns.length > 1) {
+		var pattern = patterns[1];
+		this._negativeFormatter = new AjxNumberFormat(pattern, true);
+	}
+	else {
+		// no negative pattern; insert minus sign before number segment
+		var formatter = new AjxNumberFormat("");
+		formatter._segments = formatter._segments.concat(this._segments);
+
+		var index = hasPrefix ? 1 : 0;
+		var minus = new AjxFormat.TextSegment(formatter, I18nMsg.numberSignMinus);
+		formatter._segments.splice(index, 0, minus);
+		
+		this._negativeFormatter = formatter;
+	}
+}
+AjxNumberFormat.prototype = new AjxFormat;
+AjxNumberFormat.prototype.constructor = AjxNumberFormat;
+
+AjxNumberFormat.prototype.toString = function() {
+	var array = [ 
+		"[AjxNumberFormat: ", 
+		"formatter=", AjxFormat.prototype.toString.call(this) 
+	];
+	if (this._negativeFormatter) {
+		array.push(", negativeFormatter=", this._negativeFormatter.toString());
+	}
+	array.push(']');
+	return array.join("");
+};
+
+// Constants
+
+AjxNumberFormat._NUMBER = "number";
+AjxNumberFormat._INTEGER = "integer";
+AjxNumberFormat._CURRENCY = "currency";
+AjxNumberFormat._PERCENT = "percent";
+
+AjxNumberFormat._META_CHARS = "0#.,E";
+
+AjxNumberFormat._FORMATTERS = {};
+
+// Data
+
+AjxNumberFormat.prototype._groupingOffset = Number.MAX_VALUE;
+AjxNumberFormat.prototype._maxIntDigits;
+AjxNumberFormat.prototype._minIntDigits = 1;
+AjxNumberFormat.prototype._maxFracDigits;
+AjxNumberFormat.prototype._minFracDigits;
+AjxNumberFormat.prototype._isCurrency = false;
+AjxNumberFormat.prototype._isPercent = false;
+AjxNumberFormat.prototype._isPerMille = false;
+AjxNumberFormat.prototype._showExponent = false;
+
+AjxNumberFormat.prototype._negativeFormatter;
+
+// Static functions
+
+AjxNumberFormat.getInstance = function() {
+	if (!AjxNumberFormat._FORMATTERS[AjxNumberFormat._NUMBER]) {
+		AjxNumberFormat._FORMATTERS[AjxNumberFormat._NUMBER] = new AjxNumberFormat(I18nMsg.formatNumber);
+	}
+	return AjxNumberFormat._FORMATTERS[AjxNumberFormat._NUMBER];
+};
+AjxNumberFormat.getNumberInstance = AjxNumberFormat.getInstance;
+AjxNumberFormat.getCurrencyInstance = function() {
+	if (!AjxNumberFormat._FORMATTERS[AjxNumberFormat._CURRENCY]) {
+		AjxNumberFormat._FORMATTERS[AjxNumberFormat._CURRENCY] = new AjxNumberFormat(I18nMsg.formatNumberCurrency);
+	}
+	return AjxNumberFormat._FORMATTERS[AjxNumberFormat._CURRENCY];
+};
+AjxNumberFormat.getIntegerInstance = function() {
+	if (!AjxNumberFormat._FORMATTERS[AjxNumberFormat._INTEGER]) {
+		AjxNumberFormat._FORMATTERS[AjxNumberFormat._INTEGER] = new AjxNumberFormat(I18nMsg.formatNumberInteger);
+	}
+	return AjxNumberFormat._FORMATTERS[AjxNumberFormat._INTEGER];
+};
+AjxNumberFormat.getPercentInstance = function() {
+	if (!AjxNumberFormat._FORMATTERS[AjxNumberFormat._PERCENT]) {
+		AjxNumberFormat._FORMATTERS[AjxNumberFormat._PERCENT] = new AjxNumberFormat(I18nMsg.formatNumberPercent);
+	}
+	return AjxNumberFormat._FORMATTERS[AjxNumberFormat._PERCENT];
+};
+
+AjxNumberFormat.format = function(pattern, number) {
+	return new AjxNumberFormat(pattern).format(number);
+};
+
+// Public methods
+
+AjxNumberFormat.prototype.format = function(number) {
+	if (number < 0 && this._negativeFormatter) {
+		return this._negativeFormatter.format(number);
+	}
+	return AjxFormat.prototype.format.call(this, number);
+};
+
+// Private methods
+
+AjxNumberFormat.prototype.__parseStatic = function(s, i) {
+	var data = [];
+	while (i < s.length) {
+		var c = s.charAt(i++);
+		if (AjxNumberFormat._META_CHARS.indexOf(c) != -1) {
+			i--;
+			break;
+		}
+		switch (c) {
+			case "'": {
+				var start = i;
+				while (i < s.length && s.charAt(i++) != "'") {
+					// do nothing
+				}
+				var end = i;
+				c = end - start == 0 ? "'" : s.substring(start, end);
+				break;
+			}
+			case '%': {
+				c = I18nMsg.numberSignPercent; 
+				this._isPercent = true;
+				break;
+			}
+			case '\u2030': {
+				c = I18nMsg.numberSignPerMill; 
+				this._isPerMille = true;
+				break;
+			}
+			case '\u00a4': {
+				c = s.charAt(i) == '\u00a4'
+				  ? I18nMsg.currencyCode : I18nMsg.currencySymbol;
+				this._isCurrency = true;
+				break;
+			}
+		}
+		data.push(c);
+	}
+	return { text: data.join(""), offset: i };
+};
+
+//
+// AjxNumberFormat.NumberSegment class
+//
+

[... 33587 lines stripped ...]


Mime
View raw message