--- /dev/null
+Index: sope-mime/NGImap4/NGImap4Connection.m
+===================================================================
+--- sope-mime/NGImap4/NGImap4Connection.m (revision 1544)
++++ sope-mime/NGImap4/NGImap4Connection.m (working copy)
+@@ -381,7 +381,7 @@
+
+ if (debugCache) [self logWithFormat:@" no folders cached yet .."];
+
+- result = [[self client] list:(onlyFetchInbox ? @"INBOX" : @"*")
++ result = [[self client] list:(onlyFetchInbox ? @"INBOX" : @"")
+ pattern:@"*"];
+ if (![[result valueForKey:@"result"] boolValue]) {
+ [self errorWithFormat:@"Could not list mailbox hierarchy!"];
+Index: sope-mime/NGImap4/NGImap4ResponseParser.m
+===================================================================
+--- sope-mime/NGImap4/NGImap4ResponseParser.m (revision 1544)
++++ sope-mime/NGImap4/NGImap4ResponseParser.m (working copy)
+@@ -84,6 +84,8 @@
+ static NSDictionary *_parseMultipartBody(NGImap4ResponseParser *self,
+ BOOL isBodyStructure);
+
++static NSArray *_parseLanguages();
++
+ static NSString *_parseBodyString(NGImap4ResponseParser *self,
+ BOOL _convertString);
+ static NSString *_parseBodyDecodeString(NGImap4ResponseParser *self,
+@@ -1627,6 +1629,29 @@
+ return _parseBodyDecodeString(self, _convertString, NO /* no decode */);
+ }
+
++static NSArray *_parseLanguages(NGImap4ResponseParser *self) {
++ NSMutableArray *languages;
++ NSString *language;
++
++ languages = [NSMutableArray array];
++ if (_la(self, 0) == '(') {
++ while (_la(self, 0) != ')') {
++ _consume(self,1);
++ language = _parseBodyString(self, YES);
++ if ([language length])
++ [languages addObject: language];
++ }
++ _consume(self,1);
++ }
++ else {
++ language = _parseBodyString(self, YES);
++ if ([language length])
++ [languages addObject: language];
++ }
++
++ return languages;
++}
++
+ static NSDictionary *_parseBodyParameterList(NGImap4ResponseParser *self)
+ {
+ NSMutableDictionary *list;
+@@ -1734,10 +1759,11 @@
+ *encoding, *bodysize;
+ NSDictionary *parameterList;
+ NSMutableDictionary *dict;
++ NSArray *languages;
+
+ type = [_parseBodyString(self, YES) lowercaseString];
+ _consumeIfMatch(self, ' ');
+- subtype = _parseBodyString(self, YES);
++ subtype = [_parseBodyString(self, YES) lowercaseString];
+ _consumeIfMatch(self, ' ');
+ parameterList = _parseBodyParameterList(self);
+ _consumeIfMatch(self, ' ');
+@@ -1762,7 +1788,8 @@
+ _consumeIfMatch(self, ' ');
+ [dict setObject:_parseBodyString(self, YES) forKey:@"lines"];
+ }
+- else if ([type isEqualToString:@"message"]) {
++ else if ([type isEqualToString:@"message"]
++ && [subtype isEqualToString:@"rfc822"]) {
+ if (_la(self, 0) != ')') {
+ _consumeIfMatch(self, ' ');
+ _consumeIfMatch(self, '(');
+@@ -1805,14 +1832,9 @@
+ forKey: @"disposition"];
+ if (_la(self, 0) != ')') {
+ _consume(self,1);
+- if (_la(self, 0) == '(') {
+- [dict setObject: _parseBodyParameterList(self)
+- forKey: @"language"];
+- }
+- else {
+- [dict setObject: _parseBodyString(self, YES)
+- forKey: @"language"];
+- }
++ languages = _parseLanguages(self);
++ if ([languages count])
++ [dict setObject: languages forKey: @"languages"];
+ if (_la(self, 0) != ')') {
+ _consume(self,1);
+ [dict setObject: _parseBodyString(self, YES)
+@@ -1829,6 +1851,7 @@
+ static NSDictionary *_parseMultipartBody(NGImap4ResponseParser *self,
+ BOOL isBodyStructure) {
+ NSMutableArray *parts;
++ NSArray *languages;
+ NSString *kind;
+ NSMutableDictionary *dict;
+
+@@ -1854,14 +1877,9 @@
+ forKey: @"disposition"];
+ if (_la(self, 0) != ')') {
+ _consume(self,1);
+- if (_la(self, 0) == '(') {
+- [dict setObject: _parseBodyParameterList(self)
+- forKey: @"language"];
+- }
+- else {
+- [dict setObject: _parseBodyString(self, YES)
+- forKey: @"language"];
+- }
++ languages = _parseLanguages(self);
++ if ([languages count])
++ [dict setObject: languages forKey: @"languages"];
+ if (_la(self, 0) != ')') {
+ _consume(self,1);
+ [dict setObject: _parseBodyString(self, YES)
+Index: sope-mime/NGMime/NGMimeBodyPart.m
+===================================================================
+--- sope-mime/NGMime/NGMimeBodyPart.m (revision 1544)
++++ sope-mime/NGMime/NGMimeBodyPart.m (working copy)
+@@ -31,18 +31,6 @@
+ return 2;
+ }
+
+-static NGMimeType *defaultType = nil;
+-
+-+ (void)initialize {
+- static BOOL isInitialized = NO;
+- if (!isInitialized) {
+- isInitialized = YES;
+-
+- defaultType =
+- [[NGMimeType mimeType:@"text/plain; charset=us-ascii"] retain];
+- }
+-}
+-
+ + (id)bodyPartWithHeader:(NGHashMap *)_header {
+ return [[[self alloc] initWithHeader:_header] autorelease];
+ }
+@@ -156,13 +144,12 @@
+ if (!Fields)
+ Fields = (NGMimeHeaderNames *)[NGMimePartParser headerFieldNames];
+
+-
+ type = [self->header objectForKey:Fields->contentType];
+
+ if (![type isKindOfClass:[NGMimeType class]])
+ type = [NGMimeType mimeType:[type stringValue]];
+
+- return (type != nil ? type : (id)defaultType);
++ return type;
+ }
+
+ - (NSString *)contentId {
+Index: sope-mime/NGMime/NGMimeBodyParser.m
+===================================================================
+--- sope-mime/NGMime/NGMimeBodyParser.m (revision 1544)
++++ sope-mime/NGMime/NGMimeBodyParser.m (working copy)
+@@ -67,7 +67,10 @@
+ if (_data == nil) return nil;
+
+ ctype = [_part contentType];
+-
++ if (!ctype
++ && [_d respondsToSelector: @selector(parser:contentTypeOfPart:)])
++ ctype = [_d parser: self contentTypeOfPart: _part];
++
+ if (![ctype isKindOfClass:[NGMimeType class]])
+ ctype = [NGMimeType mimeType:[ctype stringValue]];
+
+Index: sope-mime/NGMime/NGMimePartParser.h
+===================================================================
+--- sope-mime/NGMime/NGMimePartParser.h (revision 1544)
++++ sope-mime/NGMime/NGMimePartParser.h (working copy)
+@@ -117,6 +117,7 @@
+ BOOL parserParseRawBodyDataOfPart:1;
+ BOOL parserBodyParserForPart:1;
+ BOOL parserDecodeBodyOfPart:1;
++ BOOL parserContentTypeOfPart:1;
+ } delegateRespondsTo;
+
+
+@@ -275,6 +276,9 @@
+ - (id<NGMimeBodyParser>)parser:(NGMimePartParser *)_parser
+ bodyParserForPart:(id<NGMimePart>)_part;
+
++- (NGMimeType *)parser:(id)_parser
++ contentTypeOfPart:(id<NGMimePart>)_part;
++
+ @end /* NSObject(NGMimePartParserDelegate) */
+
+ @interface NSObject(NGMimePartParser)
+Index: sope-mime/NGMime/NGMimePartParser.m
+===================================================================
+--- sope-mime/NGMime/NGMimePartParser.m (revision 1544)
++++ sope-mime/NGMime/NGMimePartParser.m (working copy)
+@@ -1091,7 +1091,10 @@
+ id<NGMimeBodyParser> bodyParser = nil;
+
+ ctype = [_p contentType];
+-
++ if (!ctype
++ && self->delegateRespondsTo.parserContentTypeOfPart)
++ ctype = [self->delegate parser: self contentTypeOfPart: _p];
++
+ contentType = ([ctype isKindOfClass:[NGMimeType class]])
+ ? ctype
+ : [NGMimeType mimeType:[ctype stringValue]];
+Index: sope-gdl1/PostgreSQL/PostgreSQL72Channel.h
+===================================================================
+--- sope-gdl1/PostgreSQL/PostgreSQL72Channel.h (revision 1544)
++++ sope-gdl1/PostgreSQL/PostgreSQL72Channel.h (working copy)
+@@ -28,6 +28,7 @@
+ #define ___PostgreSQL72_Channel_H___
+
+ #include <GDLAccess/EOAdaptorChannel.h>
++#include <GDLContentStore/EOAdaptorChannel+GCS.h>
+ #include <libpq-fe.h>
+
+ @class NSArray, NSString, NSMutableDictionary;
+@@ -40,7 +41,7 @@
+ int modification;
+ } PostgreSQL72FieldInfo;
+
+-@interface PostgreSQL72Channel : EOAdaptorChannel
++@interface PostgreSQL72Channel : EOAdaptorChannel <GCSEOAdaptorChannel>
+ {
+ // connection is valid after an openChannel call
+ PGConnection *connection;
+Index: sope-gdl1/PostgreSQL/PostgreSQL72Channel.m
+===================================================================
+--- sope-gdl1/PostgreSQL/PostgreSQL72Channel.m (revision 1544)
++++ sope-gdl1/PostgreSQL/PostgreSQL72Channel.m (working copy)
+@@ -713,6 +713,39 @@
+ return ms;
+ }
+
++/* GCSEOAdaptorChannel protocol */
++static NSString *sqlFolderFormat = (@"CREATE TABLE %@ (\n" \
++ @" c_name VARCHAR (256) NOT NULL,\n"
++ @" c_content VARCHAR (100000) NOT NULL,\n"
++ @" c_creationdate INT4 NOT NULL,\n"
++ @" c_lastmodified INT4 NOT NULL,\n"
++ @" c_version INT4 NOT NULL,\n"
++ @" c_deleted INT4 NULL\n"
++ @")");
++static NSString *sqlFolderACLFormat = (@"CREATE TABLE %@ (\n" \
++ @" c_uid VARCHAR (256) NOT NULL,\n"
++ @" c_object VARCHAR (256) NOT NULL,\n"
++ @" c_role VARCHAR (80) NOT NULL\n"
++ @")");
++
++- (NSException *) createGCSFolderTableWithName: (NSString *) tableName
++{
++ NSString *sql;
++
++ sql = [NSString stringWithFormat: sqlFolderFormat, tableName];
++
++ return [self evaluateExpressionX: sql];
++}
++
++- (NSException *) createGCSFolderACLTableWithName: (NSString *) tableName
++{
++ NSString *sql;
++
++ sql = [NSString stringWithFormat: sqlFolderACLFormat, tableName];
++
++ return [self evaluateExpressionX: sql];
++}
++
+ @end /* PostgreSQL72Channel */
+
+ @implementation PostgreSQL72Channel(PrimaryKeyGeneration)
+Index: sope-appserver/NGObjWeb/GNUmakefile.postamble
+===================================================================
+--- sope-appserver/NGObjWeb/GNUmakefile.postamble (revision 1544)
++++ sope-appserver/NGObjWeb/GNUmakefile.postamble (working copy)
+@@ -23,14 +23,20 @@
+
+ # install makefiles
+
+-after-install ::
++after-install :: $(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/Additional/ngobjweb.make
++
++ifneq ($(GNUSTEP_MAKE_VERSION),1.3.0)
++after-install :: $(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/woapp.make $(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/wobundle.make
++endif
++
++$(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/Additional/ngobjweb.make: ngobjweb.make
+ $(MKDIRS) $(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/Additional/
+ $(INSTALL_DATA) ngobjweb.make $(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/Additional/ngobjweb.make
+
+-ifneq ($(GNUSTEP_MAKE_VERSION),1.3.0)
+-after-install ::
++$(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/woapp.make: woapp-gs.make
+ $(INSTALL_DATA) woapp-gs.make \
+ $(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/woapp.make
++
++$(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/wobundle.make: wobundle-gs.make
+ $(INSTALL_DATA) wobundle-gs.make \
+ $(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/wobundle.make
+-endif
+Index: sope-appserver/NGObjWeb/WOContext.m
+===================================================================
+--- sope-appserver/NGObjWeb/WOContext.m (revision 1544)
++++ sope-appserver/NGObjWeb/WOContext.m (working copy)
+@@ -64,11 +64,13 @@
+ static BOOL testNSURLs = NO;
+ static BOOL newCURLStyle = NO;
+ static NSString *WOApplicationSuffix = nil;
++static NSURL *redirectURL = nil;
+
+ + (void)initialize {
+ static BOOL didInit = NO;
+ NSUserDefaults *ud;
+ NSString *cn;
++ NSString *url;
+
+ if (didInit) return;
+
+@@ -91,6 +93,9 @@
+ debugCursor = [ud boolForKey:@"WODebugCursor"] ? 1 : 0;
+ debugComponentAwake = [ud boolForKey:@"WODebugComponentAwake"];
+ WOApplicationSuffix = [[ud stringForKey:@"WOApplicationSuffix"] copy];
++ url = [ud stringForKey:@"WOApplicationRedirectURL"];
++ if (url != nil)
++ redirectURL = [NSURL URLWithString: url];
+ }
+
+ + (id)contextWithRequest:(WORequest *)_r {
+@@ -503,6 +508,11 @@
+ return nil;
+ }
+
++ if (redirectURL) {
++ // Use URL from user defaults (WOApplicationRedirectURL)
++ return redirectURL;
++ }
++
+ if ((serverURL = [rq headerForKey:@"x-webobjects-server-url"]) == nil) {
+ if ((host = [rq headerForKey:@"host"]))
+ serverURL = [@"http://" stringByAppendingString:host];
+Index: sope-appserver/NGObjWeb/DynamicElements/WOHyperlinkInfo.m
+===================================================================
+--- sope-appserver/NGObjWeb/DynamicElements/WOHyperlinkInfo.m (revision 1544)
++++ sope-appserver/NGObjWeb/DynamicElements/WOHyperlinkInfo.m (working copy)
+@@ -216,6 +216,12 @@
+ assocCount++;
+ }
+ }
++ if (count > 0) {
++ if ((self->isAbsolute = OWGetProperty(_config, @"absolute"))) {
++ count--;
++ assocCount++;
++ }
++ }
+
+ self->rest = _config;
+
+Index: sope-appserver/NGObjWeb/DynamicElements/_WOComplexHyperlink.m
+===================================================================
+--- sope-appserver/NGObjWeb/DynamicElements/_WOComplexHyperlink.m (revision 1544)
++++ sope-appserver/NGObjWeb/DynamicElements/_WOComplexHyperlink.m (working copy)
+@@ -40,6 +40,7 @@
+ WOAssociation *string;
+ WOAssociation *target;
+ WOAssociation *disabled;
++ WOAssociation *isAbsolute;
+ WOElement *template;
+
+ /* new in WO4: */
+@@ -359,6 +360,7 @@
+ {
+ if ((self = [super initWithName:_name hyperlinkInfo:_info template:_t])) {
+ self->href = _info->href;
++ self->isAbsolute = _info->isAbsolute;
+ }
+ return self;
+ }
+@@ -374,6 +376,9 @@
+ // TODO: we need a binding to disable rewriting!
+ NSRange r;
+
++ if ([[self->isAbsolute valueInContext:_ctx] boolValue] == YES)
++ return NO;
++
+ r = [_s rangeOfString:@":"];
+ if (r.length == 0)
+ return YES;
+Index: sope-appserver/NGObjWeb/DynamicElements/WOHyperlinkInfo.h
+===================================================================
+--- sope-appserver/NGObjWeb/DynamicElements/WOHyperlinkInfo.h (revision 1544)
++++ sope-appserver/NGObjWeb/DynamicElements/WOHyperlinkInfo.h (working copy)
+@@ -41,7 +41,8 @@
+ WOAssociation *pageName;
+ WOAssociation *actionClass;
+ WOAssociation *directActionName;
+-
++ WOAssociation *isAbsolute;
++
+ BOOL sidInUrl;
+
+ /* 'ivar' associations */
+Index: sope-appserver/NGObjWeb/WOHttpAdaptor/WOHttpTransaction.m
+===================================================================
+--- sope-appserver/NGObjWeb/WOHttpAdaptor/WOHttpTransaction.m (revision 1544)
++++ sope-appserver/NGObjWeb/WOHttpAdaptor/WOHttpTransaction.m (working copy)
+@@ -31,6 +31,7 @@
+ #include <NGObjWeb/WOCookie.h>
+ #include <NGExtensions/NSData+gzip.h>
+ #include <NGHttp/NGHttp.h>
++#include <NGMime/NGMimeType.h>
+ #include "common.h"
+
+ #include <string.h>
+@@ -1016,6 +1017,12 @@
+ - (void)parser:(NGMimePartParser *)_parser didParseHeader:(NGHashMap *)_header {
+ }
+
++- (NGMimeType *)parser:(id)_parser
++ contentTypeOfPart:(id<NGMimePart>)_part
++{
++ return [NGMimeType mimeType: @"text/plain; charset=utf-8"];
++}
++
+ @end /* WOHttpAdaptor */
+
+ @implementation WOCoreApplication(SimpleParserSelection)
--- /dev/null
+/*\r
+*\r
+* Copyright (c) 2007 Andrew Tetlaw & Millstream Web Software\r
+* http://www.millstream.com.au/view/code/tablekit/\r
+* Version: 1.2.1 2007-03-11\r
+* \r
+* Permission is hereby granted, free of charge, to any person\r
+* obtaining a copy of this software and associated documentation\r
+* files (the "Software"), to deal in the Software without\r
+* restriction, including without limitation the rights to use, copy,\r
+* modify, merge, publish, distribute, sublicense, and/or sell copies\r
+* of the Software, and to permit persons to whom the Software is\r
+* furnished to do so, subject to the following conditions:\r
+* \r
+* The above copyright notice and this permission notice shall be\r
+* included in all copies or substantial portions of the Software.\r
+* \r
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\r
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r
+* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\r
+* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\r
+* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\r
+* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r
+* SOFTWARE.\r
+* * \r
+*/\r
+\r
+// Use the TableKit class constructure if you'd prefer to init your tables as JS objects\r
+var TableKit = Class.create();\r
+\r
+TableKit.prototype = {\r
+ initialize : function(elm, options) {\r
+ var table = $(elm);\r
+ if(table.tagName !== "TABLE") {\r
+ return;\r
+ }\r
+ TableKit.register(table,Object.extend(TableKit.options,options || {}));\r
+ this.id = table.id;\r
+ var op = TableKit.option('sortable resizable editable', this.id);\r
+ if(op.sortable) {\r
+ TableKit.Sortable.init(table);\r
+ } \r
+ if(op.resizable) {\r
+ TableKit.Resizable.init(table);\r
+ }\r
+ if(op.editable) {\r
+ TableKit.Editable.init(table);\r
+ }\r
+ },\r
+ sort : function(column, order) {\r
+ TableKit.Sortable.sort(this.id, column, order);\r
+ },\r
+ resizeColumn : function(column, w) {\r
+ TableKit.Resizable.resize(this.id, column, w);\r
+ },\r
+ editCell : function(row, column) {\r
+ TableKit.Editable.editCell(this.id, row, column);\r
+ }\r
+};\r
+\r
+Object.extend(TableKit, {\r
+ getBodyRows : function(table) {\r
+ table = $(table);\r
+ var id = table.id;\r
+ if(!TableKit.rows[id]) {\r
+ TableKit.rows[id] = (table.tHead && table.tHead.rows.length > 0) ? $A(table.tBodies[0].rows) : $A(table.rows).without(table.rows[0]);\r
+ }\r
+ return TableKit.rows[id];\r
+ },\r
+ getHeaderCells : function(table, cell) {\r
+ if(!table) { table = $(cell).up('table'); }\r
+ var id = table.id;\r
+ if(!TableKit.heads[id]) {\r
+ //TableKit.heads[id] = $A((table.tHead && table.tHead.rows.length > 0) ? table.tHead.rows[table.tHead.rows.length-1].cells : table.rows[0].cells);\r
+ TableKit.heads[id] = $A((table.tHead && table.tHead.rows.length > 0) ? table.tHead.rows[0].cells : table.rows[0].cells);\r
+ }\r
+ return TableKit.heads[id];\r
+ },\r
+ getCellIndex : function(cell) {\r
+ return $A(cell.parentNode.cells).indexOf(cell);\r
+ },\r
+ getRowIndex : function(row) {\r
+ return $A(row.parentNode.rows).indexOf(row);\r
+ },\r
+ getCellText : function(cell, refresh) {\r
+ if(!cell) { return ""; }\r
+ TableKit.registerCell(cell);\r
+ var data = TableKit.cells[cell.id];\r
+ if(refresh || data.refresh || !data.textContent) {\r
+ data.textContent = cell.textContent ? cell.textContent : cell.innerText;\r
+ data.refresh = false;\r
+ }\r
+ return data.textContent;\r
+ },\r
+ register : function(table, options) {\r
+ if(!table.id) {\r
+ TableKit._tblcount += 1;\r
+ table.id = "tablekit-table-" + TableKit._tblcount;\r
+ }\r
+ var id = table.id;\r
+ TableKit.tables[id] = TableKit.tables[id] ? Object.extend(TableKit.tables[id], options || {}) : Object.extend({sortable:false,resizable:false,editable:false}, options || {});\r
+ },\r
+ registerCell : function(cell) {\r
+ if(!cell.id) {\r
+ TableKit._cellcount += 1;\r
+ cell.id = "tablekit-cell-" + TableKit._cellcount;\r
+ }\r
+ if(!TableKit.cells[cell.id]) {\r
+ TableKit.cells[cell.id] = {textContent : '', htmlContent : '', active : false};\r
+ }\r
+ },\r
+ isSortable : function(table) {\r
+ return TableKit.tables[table.id] ? TableKit.tables[table.id].sortable : false;\r
+ },\r
+ isResizable : function(table) {\r
+ return TableKit.tables[table.id] ? TableKit.tables[table.id].resizable : false;\r
+ },\r
+ isEditable : function(table) {\r
+ return TableKit.tables[table.id] ? TableKit.tables[table.id].editable : false;\r
+ },\r
+ setup : function(o) {\r
+ Object.extend(TableKit.options, o || {} );\r
+ },\r
+ option : function(s, id, o1, o2) {\r
+ o1 = o1 || TableKit.options;\r
+ o2 = o2 || (id ? (TableKit.tables[id] ? TableKit.tables[id] : {}) : {});\r
+ var key = id + s;\r
+ if(!TableKit._opcache[key]){\r
+ TableKit._opcache[key] = $A($w(s)).inject([],function(a,v){\r
+ a.push(a[v] = o2[v] || o1[v]);\r
+ return a;\r
+ });\r
+ }\r
+ return TableKit._opcache[key];\r
+ },\r
+ e : function(event) {\r
+ return event || window.event;\r
+ },\r
+ tables : {},\r
+ _opcache : {},\r
+ cells : {},\r
+ rows : {},\r
+ heads : {},\r
+ options : {\r
+ autoLoad : true,\r
+ stripe : true,\r
+ sortable : true,\r
+ resizable : true,\r
+ editable : true,\r
+ rowEvenClass : 'roweven',\r
+ rowOddClass : 'rowodd',\r
+ sortableSelector : ['table.sortable'],\r
+ columnClass : 'sortcol',\r
+ descendingClass : 'sortdesc',\r
+ ascendingClass : 'sortasc',\r
+ noSortClass : 'nosort',\r
+ sortFirstAscendingClass : 'sortfirstasc',\r
+ sortFirstDecendingClass : 'sortfirstdesc',\r
+ resizableSelector : ['table.resizable'],\r
+ minWidth : 10,\r
+ showHandle : true,\r
+ resizeOnHandleClass : 'resize-handle-active',\r
+ editableSelector : ['table.editable'],\r
+ formClassName : 'editable-cell-form',\r
+ noEditClass : 'noedit',\r
+ editAjaxURI : '/',\r
+ editAjaxOptions : {}\r
+ },\r
+ _tblcount : 0,\r
+ _cellcount : 0,\r
+ load : function() {\r
+ if(TableKit.options.autoLoad) {\r
+ if(TableKit.options.sortable) {\r
+ $A(TableKit.options.sortableSelector).each(function(s){\r
+ $$(s).each(function(t) {\r
+ TableKit.Sortable.init(t);\r
+ });\r
+ });\r
+ }\r
+ if(TableKit.options.resizable) {\r
+ $A(TableKit.options.resizableSelector).each(function(s){\r
+ $$(s).each(function(t) {\r
+ TableKit.Resizable.init(t);\r
+ });\r
+ });\r
+ }\r
+ if(TableKit.options.editable) {\r
+ $A(TableKit.options.editableSelector).each(function(s){\r
+ $$(s).each(function(t) {\r
+ TableKit.Editable.init(t);\r
+ });\r
+ });\r
+ }\r
+ }\r
+ }\r
+});\r
+\r
+TableKit.Rows = {\r
+ stripe : function(table) {\r
+ var rows = TableKit.getBodyRows(table);\r
+ rows.each(function(r,i) {\r
+ TableKit.Rows.addStripeClass(table,r,i);\r
+ });\r
+ },\r
+ addStripeClass : function(t,r,i) {\r
+ t = t || r.up('table');\r
+ var op = TableKit.option('rowEvenClass rowOddClass', t.id);\r
+ var css = ((i+1)%2 === 0 ? op[0] : op[1]);\r
+ // using prototype's assClassName/RemoveClassName was not efficient for large tables, hence:\r
+ var cn = r.className.split(/\s+/);\r
+ var newCn = [];\r
+ for(var x = 0, l = cn.length; x < l; x += 1) {\r
+ if(cn[x] !== op[0] && cn[x] !== op[1]) { newCn.push(cn[x]); }\r
+ }\r
+ newCn.push(css);\r
+ r.className = newCn.join(" ");\r
+ }\r
+};\r
+\r
+TableKit.Sortable = {\r
+ init : function(elm, options){\r
+ var table = $(elm);\r
+ if(table.tagName !== "TABLE") {\r
+ return;\r
+ }\r
+ TableKit.register(table,Object.extend(options || {},{sortable:true}));\r
+ var sortFirst;\r
+ var cells = TableKit.getHeaderCells(table);\r
+ var op = TableKit.option('noSortClass columnClass sortFirstAscendingClass sortFirstDecendingClass', table.id);\r
+ cells.each(function(c){\r
+ c = $(c);\r
+ if(!c.hasClassName(op.noSortClass)) {\r
+ Event.observe(c, 'mousedown', TableKit.Sortable._sort);\r
+ c.addClassName(op.columnClass);\r
+ if(c.hasClassName(op.sortFirstAscendingClass) || c.hasClassName(op.sortFirstDecendingClass)) {\r
+ sortFirst = c;\r
+ }\r
+ }\r
+ });\r
+\r
+ if(sortFirst) {\r
+ if(sortFirst.hasClassName(op.sortFirstAscendingClass)) {\r
+ TableKit.Sortable.sort(table, sortFirst, 1);\r
+ } else {\r
+ TableKit.Sortable.sort(table, sortFirst, -1);\r
+ }\r
+ } else { // just add row stripe classes\r
+ TableKit.Rows.stripe(table);\r
+ }\r
+ },\r
+ reload : function(table) {\r
+ table = $(table);\r
+ var cells = TableKit.getHeaderCells(table);\r
+ var op = TableKit.option('noSortClass columnClass', table.id);\r
+ cells.each(function(c){\r
+ c = $(c);\r
+ if(!c.hasClassName(op.noSortClass)) {\r
+ Event.stopObserving(c, 'mousedown', TableKit.Sortable._sort);\r
+ c.removeClassName(op.columnClass);\r
+ }\r
+ });\r
+ TableKit.Sortable.init(table);\r
+ },\r
+ _sort : function(e) {\r
+ if(TableKit.Resizable._onHandle) {return;}\r
+ e = TableKit.e(e);\r
+ Event.stop(e);\r
+ var cell = Event.element(e);\r
+ while(!(cell.tagName && cell.tagName.match(/td|th/gi))) {\r
+ cell = cell.parentNode;\r
+ }\r
+ TableKit.Sortable.sort(null, cell);\r
+ },\r
+ sort : function(table, index, order) {\r
+ var cell;\r
+ if(typeof index === 'number') {\r
+ if(!table || (table.tagName && table.tagName !== "TABLE")) {\r
+ return;\r
+ }\r
+ table = $(table);\r
+ index = Math.min(table.rows[0].cells.length, index);\r
+ index = Math.max(1, index);\r
+ index -= 1;\r
+ cell = (table.tHead && table.tHead.rows.length > 0) ? $(table.tHead.rows[table.tHead.rows.length-1].cells[index]) : $(table.rows[0].cells[index]);\r
+ } else {\r
+ cell = $(index);\r
+ table = table ? $(table) : cell.up('table');\r
+ index = TableKit.getCellIndex(cell);\r
+ }\r
+ var op = TableKit.option('noSortClass descendingClass ascendingClass', table.id);\r
+ \r
+ if(cell.hasClassName(op.noSortClass)) {return;} \r
+ \r
+ order = order ? order : (cell.hasClassName(op.descendingClass) ? 1 : -1);\r
+ var rows = TableKit.getBodyRows(table);\r
+\r
+ if(cell.hasClassName(op.ascendingClass) || cell.hasClassName(op.descendingClass)) {\r
+ rows.reverse(); // if it was already sorted we just need to reverse it.\r
+ } else {\r
+ var datatype = TableKit.Sortable.getDataType(cell,index,table);\r
+ var tkst = TableKit.Sortable.types;\r
+ rows.sort(function(a,b) {\r
+ return order * tkst[datatype].compare(TableKit.getCellText(a.cells[index]),TableKit.getCellText(b.cells[index]));\r
+ });\r
+ }\r
+ var tb = table.tBodies[0];\r
+ var tkr = TableKit.Rows;\r
+ rows.each(function(r,i) {\r
+ tb.appendChild(r);\r
+ tkr.addStripeClass(table,r,i);\r
+ });\r
+ var hcells = TableKit.getHeaderCells(null, cell);\r
+ $A(hcells).each(function(c,i){\r
+ c = $(c);\r
+ c.removeClassName(op.ascendingClass);\r
+ c.removeClassName(op.descendingClass);\r
+ if(index === i) {\r
+ if(order === 1) {\r
+ c.removeClassName(op.descendingClass);\r
+ c.addClassName(op.ascendingClass);\r
+ } else {\r
+ c.removeClassName(op.ascendingClass);\r
+ c.addClassName(op.descendingClass);\r
+ }\r
+ }\r
+ });\r
+ },\r
+ types : {},\r
+ detectors : [],\r
+ addSortType : function() {\r
+ $A(arguments).each(function(o){\r
+ TableKit.Sortable.types[o.name] = o;\r
+ });\r
+ },\r
+ getDataType : function(cell,index,table) {\r
+ cell = $(cell);\r
+ index = (index || index === 0) ? index : TableKit.getCellIndex(cell);\r
+ \r
+ var colcache = TableKit.Sortable._coltypecache;\r
+ var cache = colcache[table.id] ? colcache[table.id] : (colcache[table.id] = {});\r
+ \r
+ if(!cache[index]) {\r
+ var t = '';\r
+ // first look for a data type id on the heading row cell\r
+ if(cell.id && TableKit.Sortable.types[cell.id]) {\r
+ t = cell.id;\r
+ }\r
+ t = cell.classNames().detect(function(n){ // then look for a data type classname on the heading row cell\r
+ return (TableKit.Sortable.types[n]) ? true : false;\r
+ });\r
+ if(!t) {\r
+ var rows = TableKit.getBodyRows(table);\r
+ cell = rows[0].cells[index]; // grab same index cell from body row to try and match data type\r
+ t = TableKit.Sortable.detectors.detect(\r
+ function(d){\r
+ return TableKit.Sortable.types[d].detect(TableKit.getCellText(cell));\r
+ });\r
+ }\r
+ cache[index] = t;\r
+ }\r
+ return cache[index];\r
+ },\r
+ _coltypecache : {}\r
+};\r
+\r
+TableKit.Sortable.detectors = $A($w('date-iso date date-eu date-au time currency datasize number casesensitivetext text')); // setting it here because Safari complained when I did it above...\r
+\r
+TableKit.Sortable.Type = Class.create();\r
+TableKit.Sortable.Type.prototype = {\r
+ initialize : function(name, options){\r
+ this.name = name;\r
+ options = Object.extend({\r
+ normal : function(v){\r
+ return v;\r
+ },\r
+ pattern : /.*/\r
+ }, options || {});\r
+ this.normal = options.normal;\r
+ this.pattern = options.pattern;\r
+ if(options.compare) {\r
+ this.compare = options.compare;\r
+ }\r
+ if(options.detect) {\r
+ this.detect = options.detect;\r
+ }\r
+ },\r
+ compare : function(a,b){\r
+ return TableKit.Sortable.Type.compare(this.normal(a), this.normal(b));\r
+ },\r
+ detect : function(v){\r
+ return this.pattern.test(v);\r
+ }\r
+};\r
+\r
+TableKit.Sortable.Type.compare = function(a,b) {\r
+ return a < b ? -1 : a === b ? 0 : 1;\r
+};\r
+\r
+TableKit.Sortable.addSortType(\r
+ new TableKit.Sortable.Type('number', {\r
+ pattern : /^[-+]?[\d]*\.?[\d]+(?:[eE][-+]?[\d]+)?/,\r
+ normal : function(v) {\r
+ // This will grab the first thing that looks like a number from a string, so you can use it to order a column of various srings containing numbers.\r
+ v = parseFloat(v.replace(/^.*?([-+]?[\d]*\.?[\d]+(?:[eE][-+]?[\d]+)?).*$/,"$1"));\r
+ return isNaN(v) ? 0 : v;\r
+ }}),\r
+ new TableKit.Sortable.Type('text',{\r
+ normal : function(v) {\r
+ return v ? v.toLowerCase() : '';\r
+ }}),\r
+ new TableKit.Sortable.Type('casesensitivetext',{pattern : /^[A-Z]+$/}),\r
+ new TableKit.Sortable.Type('datasize',{\r
+ pattern : /^[-+]?[\d]*\.?[\d]+(?:[eE][-+]?[\d]+)?\s?[k|m|g|t]b$/i,\r
+ normal : function(v) {\r
+ var r = v.match(/^([-+]?[\d]*\.?[\d]+([eE][-+]?[\d]+)?)\s?([k|m|g|t]?b)?/i);\r
+ var b = r[1] ? Number(r[1]).valueOf() : 0;\r
+ var m = r[3] ? r[3].substr(0,1).toLowerCase() : '';\r
+ var result = b;\r
+ switch(m) {\r
+ case 'k':\r
+ result = b * 1024;\r
+ break;\r
+ case 'm': \r
+ result = b * 1024 * 1024;\r
+ break;\r
+ case 'g':\r
+ result = b * 1024 * 1024 * 1024;\r
+ break;\r
+ case 't':\r
+ result = b * 1024 * 1024 * 1024 * 1024;\r
+ break;\r
+ }\r
+ return result;\r
+ }}),\r
+ new TableKit.Sortable.Type('date-au',{\r
+ pattern : /^\d{2}\/\d{2}\/\d{4}\s?(?:\d{1,2}\:\d{2}(?:\:\d{2})?\s?[a|p]?m?)?/i,\r
+ normal : function(v) {\r
+ if(!this.pattern.test(v)) {return 0;}\r
+ var r = v.match(/^(\d{2})\/(\d{2})\/(\d{4})\s?(?:(\d{1,2})\:(\d{2})(?:\:(\d{2}))?\s?([a|p]?m?))?/i);\r
+ var yr_num = r[3];\r
+ var mo_num = parseInt(r[2],10)-1;\r
+ var day_num = r[1];\r
+ var hr_num = r[4] ? r[4] : 0;\r
+ if(r[7] && r[7].toLowerCase().indexOf('p') !== -1) {\r
+ hr_num = parseInt(r[4],10) + 12;\r
+ }\r
+ var min_num = r[5] ? r[5] : 0;\r
+ var sec_num = r[6] ? r[6] : 0;\r
+ return new Date(yr_num, mo_num, day_num, hr_num, min_num, sec_num, 0).valueOf();\r
+ }}),\r
+ new TableKit.Sortable.Type('date-us',{\r
+ pattern : /^\d{2}\/\d{2}\/\d{4}\s?(?:\d{1,2}\:\d{2}(?:\:\d{2})?\s?[a|p]?m?)?/i,\r
+ normal : function(v) {\r
+ if(!this.pattern.test(v)) {return 0;}\r
+ var r = v.match(/^(\d{2})\/(\d{2})\/(\d{4})\s?(?:(\d{1,2})\:(\d{2})(?:\:(\d{2}))?\s?([a|p]?m?))?/i);\r
+ var yr_num = r[3];\r
+ var mo_num = parseInt(r[1],10)-1;\r
+ var day_num = r[2];\r
+ var hr_num = r[4] ? r[4] : 0;\r
+ if(r[7] && r[7].toLowerCase().indexOf('p') !== -1) {\r
+ hr_num = parseInt(r[4],10) + 12;\r
+ }\r
+ var min_num = r[5] ? r[5] : 0;\r
+ var sec_num = r[6] ? r[6] : 0;\r
+ return new Date(yr_num, mo_num, day_num, hr_num, min_num, sec_num, 0).valueOf();\r
+ }}),\r
+ new TableKit.Sortable.Type('date-eu',{\r
+ pattern : /^\d{2}-\d{2}-\d{4}/i,\r
+ normal : function(v) {\r
+ if(!this.pattern.test(v)) {return 0;}\r
+ var r = v.match(/^(\d{2})-(\d{2})-(\d{4})/);\r
+ var yr_num = r[3];\r
+ var mo_num = parseInt(r[2],10)-1;\r
+ var day_num = r[1];\r
+ return new Date(yr_num, mo_num, day_num).valueOf();\r
+ }}),\r
+ new TableKit.Sortable.Type('date-iso',{\r
+ pattern : /[\d]{4}-[\d]{2}-[\d]{2}(?:T[\d]{2}\:[\d]{2}(?:\:[\d]{2}(?:\.[\d]+)?)?(Z|([-+][\d]{2}:[\d]{2})?)?)?/, // 2005-03-26T19:51:34Z\r
+ normal : function(v) {\r
+ if(!this.pattern.test(v)) {return 0;}\r
+ var d = v.match(/([\d]{4})(-([\d]{2})(-([\d]{2})(T([\d]{2}):([\d]{2})(:([\d]{2})(\.([\d]+))?)?(Z|(([-+])([\d]{2}):([\d]{2})))?)?)?)?/); \r
+ var offset = 0;\r
+ var date = new Date(d[1], 0, 1);\r
+ if (d[3]) { date.setMonth(d[3] - 1) ;}\r
+ if (d[5]) { date.setDate(d[5]); }\r
+ if (d[7]) { date.setHours(d[7]); }\r
+ if (d[8]) { date.setMinutes(d[8]); }\r
+ if (d[10]) { date.setSeconds(d[10]); }\r
+ if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); }\r
+ if (d[14]) {\r
+ offset = (Number(d[16]) * 60) + Number(d[17]);\r
+ offset *= ((d[15] === '-') ? 1 : -1);\r
+ }\r
+ offset -= date.getTimezoneOffset();\r
+ if(offset !== 0) {\r
+ var time = (Number(date) + (offset * 60 * 1000));\r
+ date.setTime(Number(time));\r
+ }\r
+ return date.valueOf();\r
+ }}),\r
+ new TableKit.Sortable.Type('date',{\r
+ pattern: /^(?:sun|mon|tue|wed|thu|fri|sat)\,\s\d{1,2}\s(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\s\d{4}(?:\s\d{2}\:\d{2}(?:\:\d{2})?(?:\sGMT(?:[+-]\d{4})?)?)?/i, //Mon, 18 Dec 1995 17:28:35 GMT\r
+ compare : function(a,b) { // must be standard javascript date format\r
+ if(a && b) {\r
+ return TableKit.Sortable.Type.compare(new Date(a),new Date(b));\r
+ } else {\r
+ return TableKit.Sortable.Type.compare(a ? 1 : 0, b ? 1 : 0);\r
+ }\r
+ }}),\r
+ new TableKit.Sortable.Type('time',{\r
+ pattern : /^\d{1,2}\:\d{2}(?:\:\d{2})?(?:\s[a|p]m)?$/i,\r
+ compare : function(a,b) {\r
+ var d = new Date();\r
+ var ds = d.getMonth() + "/" + d.getDate() + "/" + d.getFullYear() + " ";\r
+ return TableKit.Sortable.Type.compare(new Date(ds + a),new Date(ds + b));\r
+ }}),\r
+ new TableKit.Sortable.Type('currency',{\r
+ pattern : /^[$£¥\80¤]/, // dollar,pound,yen,euro,generic currency symbol\r
+ normal : function(v) {\r
+ return v ? parseFloat(v.replace(/[^-\d\.]/g,'')) : 0;\r
+ }})\r
+);\r
+\r
+TableKit.Resizable = {\r
+ init : function(elm, options){\r
+ var table = $(elm);\r
+ if(table.tagName !== "TABLE") {return;}\r
+ TableKit.register(table,Object.extend(options || {},{resizable:true})); \r
+ var cells = TableKit.getHeaderCells(table);\r
+ cells.each(function(c){\r
+ c = $(c);\r
+ //log ("init on " + c.firstChild.nodeValue);\r
+ Event.observe(c, 'mouseover', TableKit.Resizable.initDetect);\r
+ Event.observe(c, 'mouseout', TableKit.Resizable.killDetect);\r
+ });\r
+ },\r
+ reload : function(table) {\r
+ table = $(table);\r
+ var cells = TableKit.getHeaderCells(table);\r
+ cells.each(function(c){\r
+ c = $(c);\r
+ Event.stopObserving(c, 'mouseover', TableKit.Resizable.initDetect);\r
+ Event.stopObserving(c, 'mouseout', TableKit.Resizable.killDetect);\r
+ });\r
+ TableKit.Resizable.init(table);\r
+ },\r
+ resize : function(table, index, w) {\r
+ var cell;\r
+ if(typeof index === 'number') {\r
+ if(!table || (table.tagName && table.tagName !== "TABLE")) {return;}\r
+ table = $(table);\r
+ index = Math.min(table.rows[0].cells.length, index);\r
+ index = Math.max(1, index);\r
+ index -= 1;\r
+ cell = (table.tHead && table.tHead.rows.length > 0) ? $(table.tHead.rows[table.tHead.rows.length-1].cells[index]) : $(table.rows[0].cells[index]);\r
+ } else {\r
+ cell = $(index);\r
+ table = table ? $(table) : cell.up('table');\r
+ index = TableKit.getCellIndex(cell);\r
+ }\r
+ var pad = parseInt(cell.getStyle('paddingLeft'),10) + parseInt(cell.getStyle('paddingRight'),10);\r
+ w = Math.max(w-pad, TableKit.option('minWidth', table.id)[0]);\r
+ cell.setStyle({'width' : w + 'px'});\r
+ },\r
+ initDetect : function(e) {\r
+ e = TableKit.e(e);\r
+ var cell = Event.element(e);\r
+ Event.observe(cell, 'mousemove', TableKit.Resizable.detectHandle);\r
+ Event.observe(cell, 'mousedown', TableKit.Resizable.startResize);\r
+ },\r
+ detectHandle : function(e) {\r
+ e = TableKit.e(e);\r
+ var cell = Event.element(e);\r
+ if(TableKit.Resizable.pointerPos(cell,Event.pointerX(e),Event.pointerY(e))){\r
+ cell.addClassName(TableKit.option('resizeOnHandleClass', cell.up('table').id)[0]);\r
+ TableKit.Resizable._onHandle = true;\r
+ } else {\r
+ cell.removeClassName(TableKit.option('resizeOnHandleClass', cell.up('table').id)[0]);\r
+ TableKit.Resizable._onHandle = false;\r
+ }\r
+ },\r
+ killDetect : function(e) {\r
+ e = TableKit.e(e);\r
+ TableKit.Resizable._onHandle = false;\r
+ var cell = Event.element(e);\r
+ if (!cell.tagName || cell.tagName != 'TD') return;\r
+ Event.stopObserving(cell, 'mousemove', TableKit.Resizable.detectHandle);\r
+ Event.stopObserving(cell, 'mousedown', TableKit.Resizable.startResize);\r
+ cell.removeClassName(TableKit.option('resizeOnHandleClass', cell.up('table').id)[0]);\r
+ },\r
+ startResize : function(e) {\r
+ e = TableKit.e(e);\r
+ if(!TableKit.Resizable._onHandle) { return;}\r
+ var cell = Event.element(e);\r
+ Event.stopObserving(cell, 'mousemove', TableKit.Resizable.detectHandle);\r
+ Event.stopObserving(cell, 'mousedown', TableKit.Resizable.startResize);\r
+ Event.stopObserving(cell, 'mouseout', TableKit.Resizable.killDetect);\r
+ TableKit.Resizable._cell = cell;\r
+ var table = cell.up('table');\r
+ TableKit.Resizable._tbl = table;\r
+ if(TableKit.option('showHandle', table.id)[0]) {\r
+ TableKit.Resizable._handle = $(document.createElement('div')).addClassName('resize-handle').setStyle({\r
+ 'top' : Position.cumulativeOffset(cell)[1] + 'px',\r
+ 'left' : Event.pointerX(e) + 'px',\r
+ 'height' : table.getDimensions().height + 'px'\r
+ });\r
+ document.body.appendChild(TableKit.Resizable._handle);\r
+ }\r
+ Event.observe(document, 'mousemove', TableKit.Resizable.drag);\r
+ Event.observe(document, 'mouseup', TableKit.Resizable.endResize);\r
+ Event.stop(e);\r
+ },\r
+ endResize : function(e) {\r
+ e = TableKit.e(e);\r
+ var cell = TableKit.Resizable._cell;\r
+ TableKit.Resizable.resize(null, cell, (Event.pointerX(e) - Position.cumulativeOffset(cell)[0]));\r
+ Event.stopObserving(document, 'mousemove', TableKit.Resizable.drag);\r
+ Event.stopObserving(document, 'mouseup', TableKit.Resizable.endResize);\r
+ if(TableKit.option('showHandle', TableKit.Resizable._tbl.id)[0]) {\r
+ $$('div.resize-handle').each(function(elm){\r
+ document.body.removeChild(elm);\r
+ });\r
+ }\r
+ Event.observe(cell, 'mouseout', TableKit.Resizable.killDetect);\r
+ TableKit.Resizable._tbl = TableKit.Resizable._handle = TableKit.Resizable._cell = null;\r
+ Event.stop(e);\r
+ },\r
+ drag : function(e) {\r
+ e = TableKit.e(e);\r
+ if(TableKit.Resizable._handle === null) {\r
+ try {\r
+ TableKit.Resizable.resize(TableKit.Resizable._tbl, TableKit.Resizable._cell, (Event.pointerX(e) - Position.cumulativeOffset(TableKit.Resizable._cell)[0]));\r
+ } catch(e) {}\r
+ } else {\r
+ TableKit.Resizable._handle.setStyle({'left' : Event.pointerX(e) + 'px'});\r
+ }\r
+ return false;\r
+ },\r
+ pointerPos : function(element, x, y) {\r
+ var offset = Position.cumulativeOffset(element);\r
+ return (y >= offset[1] &&\r
+ y < offset[1] + element.offsetHeight &&\r
+ x >= offset[0] + element.offsetWidth - 5 &&\r
+ x < offset[0] + element.offsetWidth);\r
+ },\r
+ _onHandle : false,\r
+ _cell : null,\r
+ _tbl : null,\r
+ _handle : null\r
+};\r
+\r
+\r
+TableKit.Editable = {\r
+ init : function(elm, options){\r
+ var table = $(elm);\r
+ if(table.tagName !== "TABLE") {return;}\r
+ TableKit.register(table,Object.extend(options || {},{editable:true}));\r
+ Event.observe(table.tBodies[0], 'click', TableKit.Editable._editCell);\r
+ },\r
+ _editCell : function(e) {\r
+ e = TableKit.e(e);\r
+ var cell = Event.findElement(e,'td');\r
+ TableKit.Editable.editCell(null, cell);\r
+ },\r
+ editCell : function(table, index, cindex) {\r
+ var cell, row;\r
+ if(typeof index === 'number') {\r
+ if(!table || (table.tagName && table.tagName !== "TABLE")) {return;}\r
+ table = $(table);\r
+ index = Math.min(table.tBodies[0].rows.length, index);\r
+ index = Math.max(1, index);\r
+ index -= 1;\r
+ cindex = Math.min(table.rows[0].cells.length, cindex);\r
+ cindex = Math.max(1, cindex);\r
+ cindex -= 1;\r
+ row = $(table.tBodies[0].rows[index]);\r
+ cell = $(row.cells[cindex]);\r
+ } else {\r
+ cell = $(index);\r
+ table = (table && table.tagName && table.tagName !== "TABLE") ? $(table) : cell.up('table');\r
+ row = cell.up('tr');\r
+ }\r
+ var op = TableKit.option('noEditClass', table.id);\r
+ if(cell.hasClassName(op.noEditClass)) {return;}\r
+ \r
+ var head = $(TableKit.getHeaderCells(table, cell)[TableKit.getCellIndex(cell)]);\r
+ if(head.hasClassName(op.noEditClass)) {return;}\r
+ \r
+ TableKit.registerCell(cell);\r
+ var data = TableKit.cells[cell.id];\r
+ if(data.active) {return;}\r
+ data.htmlContent = cell.innerHTML;\r
+ var ftype = TableKit.Editable.types['text-input'];\r
+ if(head.id && TableKit.Editable.types[head.id]) {\r
+ ftype = TableKit.Editable.types[head.id];\r
+ } else {\r
+ var n = head.classNames().detect(function(n){\r
+ return (TableKit.Editable.types[n]) ? true : false;\r
+ });\r
+ ftype = n ? TableKit.Editable.types[n] : ftype;\r
+ }\r
+ ftype.edit(cell);\r
+ data.active = true;\r
+ },\r
+ types : {},\r
+ addCellEditor : function(o) {\r
+ if(o && o.name) { TableKit.Editable.types[o.name] = o; }\r
+ }\r
+};\r
+\r
+TableKit.Editable.CellEditor = Class.create();\r
+TableKit.Editable.CellEditor.prototype = {\r
+ initialize : function(name, options){\r
+ this.name = name;\r
+ this.options = Object.extend({\r
+ element : 'input',\r
+ attributes : {name : 'value', type : 'text'},\r
+ selectOptions : [],\r
+ showSubmit : true,\r
+ submitText : 'OK',\r
+ showCancel : true,\r
+ cancelText : 'Cancel',\r
+ ajaxURI : null,\r
+ ajaxOptions : null\r
+ }, options || {});\r
+ },\r
+ edit : function(cell) {\r
+ cell = $(cell);\r
+ var op = this.options;\r
+ var table = cell.up('table');\r
+ \r
+ var form = $(document.createElement("form"));\r
+ form.id = cell.id + '-form';\r
+ form.addClassName(TableKit.option('formClassName', table.id)[0]);\r
+ form.onsubmit = this._submit.bindAsEventListener(this);\r
+ \r
+ var field = document.createElement(op.element);\r
+ $H(op.attributes).each(function(v){\r
+ field[v.key] = v.value;\r
+ });\r
+ switch(op.element) {\r
+ case 'input':\r
+ case 'textarea':\r
+ field.value = TableKit.getCellText(cell);\r
+ break;\r
+ \r
+ case 'select':\r
+ var txt = TableKit.getCellText(cell);\r
+ $A(op.selectOptions).each(function(v){\r
+ field.options[field.options.length] = new Option(v[0], v[1]);\r
+ if(txt === v[1]) {\r
+ field.options[field.options.length-1].selected = 'selected';\r
+ }\r
+ });\r
+ break;\r
+ }\r
+ form.appendChild(field);\r
+ if(op.element === 'textarea') {\r
+ form.appendChild(document.createElement("br"));\r
+ }\r
+ if(op.showSubmit) {\r
+ var okButton = document.createElement("input");\r
+ okButton.type = "submit";\r
+ okButton.value = op.submitText;\r
+ okButton.className = 'editor_ok_button';\r
+ form.appendChild(okButton);\r
+ }\r
+ if(op.showCancel) {\r
+ var cancelLink = document.createElement("a");\r
+ cancelLink.href = "#";\r
+ cancelLink.appendChild(document.createTextNode(op.cancelText));\r
+ cancelLink.onclick = this._cancel.bindAsEventListener(this);\r
+ cancelLink.className = 'editor_cancel'; \r
+ form.appendChild(cancelLink);\r
+ }\r
+ cell.innerHTML = '';\r
+ cell.appendChild(form);\r
+ },\r
+ _submit : function(e) {\r
+ var cell = Event.findElement(e,'td');\r
+ var form = Event.findElement(e,'form');\r
+ Event.stop(e);\r
+ this.submit(cell,form);\r
+ },\r
+ submit : function(cell, form) {\r
+ var op = this.options;\r
+ form = form ? form : cell.down('form');\r
+ var head = $(TableKit.getHeaderCells(null, cell)[TableKit.getCellIndex(cell)]);\r
+ var row = cell.up('tr');\r
+ var table = cell.up('table');\r
+ var s = '&row=' + (TableKit.getRowIndex(row)+1) + '&cell=' + (TableKit.getCellIndex(cell)+1) + '&id=' + row.id + '&field=' + head.id + '&' + Form.serialize(form);\r
+ this.ajax = new Ajax.Updater(cell, op.ajaxURI || TableKit.option('editAjaxURI', table.id)[0], Object.extend(op.ajaxOptions || TableKit.option('editAjaxOptions', table.id)[0], {\r
+ postBody : s,\r
+ onComplete : function() {\r
+ var data = TableKit.cells[cell.id];\r
+ data.active = false;\r
+ data.refresh = true; // mark cell cache for refreshing, in case cell contents has changed and sorting is applied\r
+ }\r
+ }));\r
+ },\r
+ _cancel : function(e) {\r
+ var cell = Event.findElement(e,'td');\r
+ Event.stop(e);\r
+ this.cancel(cell);\r
+ },\r
+ cancel : function(cell) {\r
+ this.ajax = null;\r
+ var data = TableKit.cells[cell.id];\r
+ cell.innerHTML = data.htmlContent;\r
+ data.htmlContent = '';\r
+ data.active = false;\r
+ },\r
+ ajax : null\r
+};\r
+\r
+TableKit.Editable.textInput = function(n,attributes) {\r
+ TableKit.Editable.addCellEditor(new TableKit.Editable.CellEditor(n, {\r
+ element : 'input',\r
+ attributes : Object.extend({name : 'value', type : 'text'}, attributes||{})\r
+ }));\r
+};\r
+TableKit.Editable.textInput('text-input');\r
+\r
+TableKit.Editable.multiLineInput = function(n,attributes) {\r
+ TableKit.Editable.addCellEditor(new TableKit.Editable.CellEditor(n, {\r
+ element : 'textarea',\r
+ attributes : Object.extend({name : 'value', rows : '5', cols : '20'}, attributes||{})\r
+ })); \r
+}; \r
+TableKit.Editable.multiLineInput('multi-line-input');\r
+\r
+TableKit.Editable.selectInput = function(n,attributes,selectOptions) {\r
+ TableKit.Editable.addCellEditor(new TableKit.Editable.CellEditor(n, {\r
+ element : 'select',\r
+ attributes : Object.extend({name : 'value'}, attributes||{}),\r
+ 'selectOptions' : selectOptions\r
+ })); \r
+};\r
+\r
+/*\r
+TableKit.Bench = {\r
+ bench : [],\r
+ start : function(){\r
+ TableKit.Bench.bench[0] = new Date().getTime();\r
+ },\r
+ end : function(s){\r
+ TableKit.Bench.bench[1] = new Date().getTime();\r
+ alert(s + ' ' + ((TableKit.Bench.bench[1]-TableKit.Bench.bench[0])/1000)+' seconds.') //console.log(s + ' ' + ((TableKit.Bench.bench[1]-TableKit.Bench.bench[0])/1000)+' seconds.')\r
+ TableKit.Bench.bench = [];\r
+ }\r
+} */\r
+\r
+if(window.FastInit) {\r
+ FastInit.addOnLoad(TableKit.load);\r
+} else {\r
+ Event.observe(window, 'load', TableKit.load);\r
+}\r