]> err.no Git - sope/blob - sope-appserver/WEExtensions/WETabView.m
added strict OSX bundle dependencies
[sope] / sope-appserver / WEExtensions / WETabView.m
1 /*
2   Copyright (C) 2000-2005 SKYRIX Software AG
3
4   This file is part of SOPE.
5
6   SOPE is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with SOPE; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21
22 #include "WETabView.h"
23 #include <NGObjWeb/WEClientCapabilities.h>
24 #include "common.h"
25
26 #if DEBUG
27 // #  define DEBUG_TAKEVALUES 1
28 #  define DEBUG_JS 1
29 #endif
30
31 /* context keys */
32 NSString *WETabView_HEAD      = @"WETabView_head";
33 NSString *WETabView_BODY      = @"WETabView_body";
34 NSString *WETabView_KEYS      = @"WETabView_keys";
35 NSString *WETabView_SCRIPT    = @"WETabView_script";
36 NSString *WETabView_ACTIVEKEY = @"WETabView_activekey";
37 NSString *WETabView_COLLECT   = @"~tv~";
38
39 @implementation WETabView
40
41 static NSNumber *YesNumber;
42
43 + (void)initialize {
44   if (YesNumber == nil)
45     YesNumber = [[NSNumber numberWithBool:YES] retain];
46 }
47
48 + (int)version {
49   return [super version] + 0;
50 }
51
52 - (id)initWithName:(NSString *)_name
53   associations:(NSDictionary *)_config
54   template:(WOElement *)_t
55 {
56   if ((self = [super initWithName:_name associations:_config template:_t])) {
57     self->selection          = WOExtGetProperty(_config, @"selection");
58     
59     self->bgColor            = WOExtGetProperty(_config, @"bgColor");
60     self->nonSelectedBgColor = WOExtGetProperty(_config,@"nonSelectedBgColor");
61     self->leftCornerIcon     = WOExtGetProperty(_config, @"leftCornerIcon");
62     self->rightCornerIcon    = WOExtGetProperty(_config, @"rightCornerIcon");
63
64     self->tabIcon            = WOExtGetProperty(_config, @"tabIcon");
65     self->leftTabIcon        = WOExtGetProperty(_config, @"leftTabIcon");
66     self->selectedTabIcon    = WOExtGetProperty(_config, @"selectedTabIcon");
67
68     self->asBackground       = WOExtGetProperty(_config, @"asBackground");
69     self->width              = WOExtGetProperty(_config, @"width");
70     self->height             = WOExtGetProperty(_config, @"height");
71     self->activeBgColor      = WOExtGetProperty(_config, @"activeBgColor");
72     self->inactiveBgColor    = WOExtGetProperty(_config, @"inactiveBgColor");
73
74     self->fontColor          = WOExtGetProperty(_config, @"fontColor");
75     self->fontSize           = WOExtGetProperty(_config, @"fontSize");
76     self->fontFace           = WOExtGetProperty(_config, @"fontFace");
77     
78     self->disabledTabKeys = WOExtGetProperty(_config, @"disabledTabKeys");
79
80     self->template = [_t retain];
81   }
82   return self;
83 }
84
85 - (void)dealloc {
86   [self->disabledTabKeys    release];
87   [self->selection          release];
88   [self->bgColor            release];
89   [self->nonSelectedBgColor release];
90   [self->leftCornerIcon  release];
91   [self->rightCornerIcon release];
92   [self->leftTabIcon     release];
93   [self->selectedTabIcon release];
94   [self->tabIcon         release];
95   [self->width           release];
96   [self->height          release];
97   [self->activeBgColor   release];
98   [self->inactiveBgColor release];
99   [self->fontColor       release];
100   [self->fontSize        release];
101   [self->fontFace        release];
102   [self->template        release];
103   [super dealloc];
104 }
105
106 /* nesting */
107
108 - (id)saveNestedStateInContext:(WOContext *)_ctx {
109   return nil;
110 }
111 - (void)restoreNestedState:(id)_state inContext:(WOContext *)_ctx {
112   if (_state == nil) return;
113 }
114
115 - (NSArray *)filterKeys:(NSArray *)_keys inContext:(WOContext *)_ctx {
116   NSMutableArray *ma;
117   NSArray  *rkeys;
118   unsigned i, count;
119   
120   if (self->disabledTabKeys == nil || _keys == nil)
121     return _keys;
122   
123   rkeys = [self->disabledTabKeys valueInComponent:[_ctx component]];
124   if (rkeys == nil || (count = [rkeys count]) == 0)
125     return _keys;
126   
127   ma = [[_keys mutableCopy] autorelease];
128   for (i = 0; i < count; i++) {
129     unsigned j, jc;
130     
131     // TODO: not particulary efficient, but should be OK ... */
132     for (j = 0, jc = [ma count]; j < jc; j++) {
133       WETabItemInfo *info;
134       
135       info = [ma objectAtIndex:j];
136       if (![info->key isEqualToString:[rkeys objectAtIndex:i]])
137         continue;
138       
139       [ma removeObjectAtIndex:j];
140     }
141   }
142   
143   return ma;
144 }
145
146 - (NSArray *)collectKeysInContext:(WOContext *)_ctx {
147   NSArray *keys;
148   
149   /* collect mode, collects all keys */
150   [_ctx setObject:WETabView_COLLECT forKey:WETabView_HEAD];
151   [self->template appendToResponse:nil inContext:_ctx];
152   [_ctx removeObjectForKey:WETabView_HEAD];
153   
154   keys = [_ctx objectForKey:WETabView_KEYS];
155
156   /* filter keys */
157   keys = [self filterKeys:keys inContext:_ctx];
158   
159   return keys;
160 }
161
162 /* responder */
163
164 - (void)takeValuesFromRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
165   id       nestedState;
166   NSString *activeTabKey;
167   
168   activeTabKey = [self->selection stringValueInComponent:[_ctx component]];
169   
170   nestedState = [self saveNestedStateInContext:_ctx];
171   [_ctx appendElementIDComponent:@"b"];
172   [_ctx appendElementIDComponent:activeTabKey];
173   
174   [_ctx setObject:activeTabKey forKey:WETabView_BODY];
175   
176 #if DEBUG_TAKEVALUES
177   [[_ctx component] debugWithFormat:@"WETabView: body takes values, eid='%@'",
178                     [_ctx elementID]];
179 #endif
180   
181   [self->template takeValuesFromRequest:_req inContext:_ctx];
182   
183   [_ctx removeObjectForKey:WETabView_BODY];
184   [_ctx deleteLastElementIDComponent]; // activeKey
185   [_ctx deleteLastElementIDComponent]; /* 'b' */
186   [self restoreNestedState:nestedState inContext:_ctx];
187 }
188
189 - (id)invokeActionForRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
190   NSString *key;
191   id       result;
192   id       nestedState;
193   
194   if ((key = [_ctx currentElementID]) == nil)
195     return nil;
196   
197   result      = nil;
198   nestedState = [self saveNestedStateInContext:_ctx];
199     
200   if ([key isEqualToString:@"h"]) {
201     /* header action */
202     //NSString *urlKey;
203     
204     [_ctx consumeElementID];
205     [_ctx appendElementIDComponent:@"h"];
206 #if 0
207     if ((urlKey = [_ctx currentElementID]) == nil) {
208       [[_ctx application]
209              debugWithFormat:@"missing active head tab key !"];
210     }
211     else {
212       //NSLog(@"clicked: %@", urlKey);
213       [_ctx consumeElementID];
214       [_ctx appendElementIDComponent:urlKey];
215     }
216 #endif
217     
218     [_ctx setObject:self->selection forKey:WETabView_HEAD];
219     result = [self->template invokeActionForRequest:_req inContext:_ctx];
220     [_ctx removeObjectForKey:WETabView_HEAD];
221
222 #if 0
223     if (urlKey)
224       [_ctx deleteLastElementIDComponent]; // active key
225 #endif
226     [_ctx deleteLastElementIDComponent]; // 'h'
227   }
228   else if ([key isEqualToString:@"b"]) {
229     /* body action */
230     NSString *activeTabKey, *urlKey;
231     
232     [_ctx consumeElementID];
233     [_ctx appendElementIDComponent:@"b"];
234       
235     if ((urlKey = [_ctx currentElementID]) == nil) {
236       [[_ctx application]
237              debugWithFormat:@"missing active body tab key !"];
238     }
239     else {
240       //NSLog(@"clicked: %@", urlKey);
241       [_ctx consumeElementID];
242       [_ctx appendElementIDComponent:urlKey];
243     }
244     
245     activeTabKey = [self->selection stringValueInComponent:[_ctx component]];
246     [_ctx setObject:activeTabKey forKey:WETabView_BODY];
247     
248     result = [self->template invokeActionForRequest:_req inContext:_ctx];
249       
250     [_ctx removeObjectForKey:WETabView_BODY];
251
252     if (urlKey)
253       [_ctx deleteLastElementIDComponent]; // active key
254     [_ctx deleteLastElementIDComponent]; // 'b'
255   }
256   else {
257     [[_ctx application]
258            debugWithFormat:@"unknown tab container key '%@'", key];
259   }
260     
261   [self restoreNestedState:nestedState inContext:_ctx];
262   return result;
263 }
264
265 - (NSString *)_tabViewCountInContext:(WOContext *)_ctx {
266   int count;
267   count = [[_ctx valueForKey:@"WETabViewScriptDone"] intValue];
268   return [NSString stringWithFormat:@"%d",count];
269 }
270
271 - (NSString *)scriptHref:(WETabItemInfo *)_info
272   inContext:(WOContext *)_ctx
273   isLeft:(BOOL)_isLeft
274   keys:(NSArray *)_keys
275 {
276   NSMutableString *result = [NSMutableString string];
277   WETabItemInfo *tmp;
278   NSString       *activeKey;
279   int            i, cnt;
280   NSString       *elID;
281   NSString       *tstring;
282   
283   activeKey = [self->selection stringValueInComponent:[_ctx component]];
284   [result appendString:@"JavaScript:showTab("];
285   [result appendString:_info->key];
286   [result appendString:@"Tab);"];
287   
288   [result appendString:@"swapCorners("];
289   tstring = (!_isLeft)
290     ? @"tabCorner%@,tabCornerLeft%@);"
291     : @"tabCornerLeft%@,tabCorner%@);";
292   elID = [self _tabViewCountInContext:_ctx];
293   [result appendString:[NSString stringWithFormat:tstring,elID,elID]];
294   
295   for (i=0, cnt = [_keys count]; i < cnt; i++) {
296     tmp = [_keys objectAtIndex:i];
297
298     if ((tmp->isScript || [tmp->key isEqualToString:activeKey])
299         && ![tmp->key isEqualToString:_info->key]) {
300       [result appendString:@"hideTab("];
301       [result appendString:tmp->key];
302       [result appendString:@"Tab);"];
303     }
304   }
305   return result;
306 }
307
308 - (void)appendLink:(WETabItemInfo *)_info
309   toResponse:(WOResponse *)_response
310   inContext:(WOContext *)_ctx
311   isActive:(BOOL)_isActive isLeft:(BOOL)_isLeft
312   doScript:(BOOL)_doScript keys:(NSArray *)_keys
313 {
314   BOOL        doBgIcon;
315   BOOL        doImages;
316   NSString    *headUri    = nil;
317   NSString    *imgUri     = nil;
318   NSString    *label      = nil;
319   NSString    *bgcolor    = nil;
320   NSString    *w          = nil;
321   NSString    *h          = nil;
322   NSString    *scriptHref = nil;
323   WOComponent *comp;
324
325   doImages = ![[[_ctx request] clientCapabilities] isTextModeBrowser];
326
327   comp = [_ctx component];
328   headUri = _info->uri;
329
330   if (_info->asBackground == 0)
331     doBgIcon = [self->asBackground boolValueInComponent:comp];
332   else
333     doBgIcon = (_info->asBackground == 1) ? YES : NO;
334   
335   if ((label = _info->label) == nil)
336     label = _info->key;
337   
338   if (doImages) {
339     /* lookup image */
340     NSString *imgName = nil;
341     
342     if (_isActive) {
343       imgName = _info->selIcon;  // selectedTabIcon
344       if (imgName == nil)
345         imgName = [self->selectedTabIcon stringValueInComponent:comp];
346     }
347     else if (_isLeft) {
348       imgName = _info->leftIcon; // leftTabIcon
349       if (imgName == nil)
350         imgName = [self->leftTabIcon stringValueInComponent:comp];
351     }
352     else {
353       imgName = _info->tabIcon;  // tabIcon
354       if (imgName == nil)
355         imgName = [self->tabIcon stringValueInComponent:comp];
356     }
357
358     if (imgName == nil) {
359       imgName = _info->icon;
360     }
361
362     imgUri = WEUriOfResource(imgName, _ctx);
363     
364     if ([imgUri length] < 1)
365       doImages = NO;
366   }
367
368   if (_isActive) {
369     bgcolor = (_info->activeBg)
370       ? _info->activeBg
371       : [self->activeBgColor stringValueInComponent:comp];
372   }
373   else {
374     bgcolor = (_info->inactiveBg)
375       ? _info->inactiveBg
376       : [self->inactiveBgColor stringValueInComponent:comp];
377   }
378   
379   w  = (_info->width)
380     ? _info->width
381     : [self->width stringValueInComponent:comp];
382   h  = (_info->height)
383     ? _info->height
384     : [self->height stringValueInComponent:comp];
385   
386   [_response appendContentString:@"<td align='center' valign='middle'"];
387   
388   if (w != nil) {
389     [_response appendContentString:@" width='"];
390     [_response appendContentHTMLAttributeValue:w];
391     [_response appendContentCharacter:'\''];
392   }
393   if (h != nil) {
394     [_response appendContentString:@" height='"];
395     [_response appendContentHTMLAttributeValue:h];
396     [_response appendContentCharacter:'\''];
397   }
398   if (bgcolor != nil) {
399     [_response appendContentString:@" bgcolor='"];
400     [_response appendContentHTMLAttributeValue:bgcolor];
401     [_response appendContentCharacter:'\''];
402   }
403   if (doBgIcon && doImages) {
404     WEClientCapabilities *ccaps;
405   
406     ccaps = [[_ctx request] clientCapabilities];
407     
408     [_response appendContentString:@" background='"];
409     [_response appendContentHTMLAttributeValue:imgUri];
410     [_response appendContentCharacter:'\''];
411
412     // click on td background
413     if ([ccaps isInternetExplorer] && [ccaps isJavaScriptBrowser]) {
414       [_response appendContentString:@" onclick=\"window.location.href='"];
415        [_response appendContentHTMLAttributeValue:headUri];
416       [_response appendContentString:@"'\""];
417     }
418   }
419   
420   [_response appendContentCharacter:'>'];
421   
422   if (!doImages) [_response appendContentString:@"["];
423   
424   [_response appendContentString:@"<a style=\"text-decoration:none;\" href=\""];
425
426   if (_doScript && doImages && (_info->isScript || _isActive)) {
427     scriptHref =
428       [self scriptHref:_info inContext:_ctx isLeft:_isLeft keys:_keys];
429     [_response appendContentHTMLAttributeValue:scriptHref];
430   }
431   else {
432     [_response appendContentHTMLAttributeValue:headUri];
433   }
434   
435   [_response appendContentString:@"\" "];
436   [_response appendContentString:
437                [NSString stringWithFormat:@"name='%@TabLink'", _info->key]];
438   [_response appendContentString:@">"];
439   
440   if (!doImages && _isActive)
441     [_response appendContentString:@"<b>"];
442   
443   if (doImages && !doBgIcon) {
444     [_response appendContentString:@"<img border='0' src='"];
445     [_response appendContentString:imgUri];
446     [_response appendContentString:@"' name='"];
447     [_response appendContentString:_info->key];
448     [_response appendContentString:@"TabImg' alt='"];
449     [_response appendContentHTMLAttributeValue:label];
450     [_response appendContentString:@"' title='"];
451     [_response appendContentHTMLAttributeValue:label];
452     [_response appendContentString:@"'"];
453     if (_ctx->wcFlags.xmlStyleEmptyElements)
454       [_response appendContentString:@" />"];
455     else
456       [_response appendContentString:@">"];
457   }
458   else {
459     NSString *fc     = [self->fontColor stringValueInComponent:comp];
460     NSString *fs     = [self->fontSize  stringValueInComponent:comp];
461     NSString *ff     = [self->fontFace  stringValueInComponent:comp];
462     BOOL     hasFont;
463     
464     hasFont = (fc || fs || ff) ? YES : NO;
465     
466     if ([label length] < 1)
467       label = _info->key;
468     [_response appendContentString:@"<nobr>"];
469     if (hasFont) WEAppendFont(_response, fc, ff, fs);           // <font>
470     
471     if (_isActive) [_response appendContentString:@"<b>"];
472     [_response appendContentHTMLString:label];
473     if (_isActive) [_response appendContentString:@"</b>"];
474     
475     if (hasFont) [_response appendContentString:@"</font>"];    // </font>
476     [_response appendContentString:@"</nobr>"];
477   }
478   
479   if (!doImages && _isActive)
480     [_response appendContentString:@"</b>"];
481   
482   [_response appendContentString:@"</a>"];
483   if (!doImages) [_response appendContentString:@"]"];
484
485
486   [_response appendContentString:@"</td>"];
487   
488   if (_doScript && doImages && (_info->isScript || _isActive)) {
489     NSString *k; // key 
490     NSString *s; // selected   tab icon
491     NSString *u; // unselected tab icon
492     //NSString *out;
493
494     k = _info->key;
495     s = _info->selIcon; // selectedTabIcon
496     u = (_isLeft) ? _info->leftIcon : _info->tabIcon;
497     
498     s = WEUriOfResource(s, _ctx);
499     u = WEUriOfResource(u, _ctx);
500
501     s = ([s length] < 1) ? imgUri : s;
502     u = ([u length] < 1) ? imgUri : u;
503     
504 #if 0
505     out = [NSString alloc];
506     out = [out initWithFormat:
507                     @"<script language=\"JavaScript\">\n"
508                     @"<!--\n"
509                     @"var %@Tab = new Array();\n"
510                     @"%@Tab[\"link\"] = %@TabLink;\n"
511                     @"%@Tab[\"href1\"] = \"%@\";\n"
512                     @"%@Tab[\"href2\"] = \"%@\";\n"
513                     @"%@Tab[\"Img\"] = window.document.%@TabImg;\n"
514                     @"%@Tab[\"Ar\"]  = new Array();\n"
515                     @"%@Tab[\"Ar\"][0] = new Image();\n"
516                     @"%@Tab[\"Ar\"][0].src = \"%@\";\n"
517                     @"%@Tab[\"Ar\"][1] = new Image();\n"
518                     @"%@Tab[\"Ar\"][1].src = \"%@\";\n"
519                     @"//-->\n</script>",
520                     k, k, k, k, scriptHref, k, headUri,
521                     k, k, k, k,
522                     k, u, k, k, s
523                     ];
524  
525     [_response appendContentString:out];
526     RELEASE(out);
527 #else
528 #  define _appendStr_(_str_) [_response appendContentString:_str_]
529     _appendStr_(@"<script language=\"JavaScript\">\n<!--\nvar ");
530     _appendStr_(k); _appendStr_(@"Tab = new Array();\n");
531
532     _appendStr_(k); _appendStr_(@"Tab[\"link\"] = ");
533     _appendStr_(k); _appendStr_(@"TabLink;\n");   // linkName
534       
535     _appendStr_(k); _appendStr_(@"Tab[\"href1\"] = \"");
536     _appendStr_(scriptHref); _appendStr_(@"\";\n"); // scriptHref
537
538     _appendStr_(k); _appendStr_(@"Tab[\"href2\"] = \"");
539     _appendStr_(_info->uri); _appendStr_(@"\";\n"); // actionHref
540       
541     _appendStr_(k); _appendStr_(@"Tab[\"Img\"] = window.document.");
542     _appendStr_(k); _appendStr_(@"TabImg;\n");
543     _appendStr_(k); _appendStr_(@"Tab[\"Ar\"]  = new Array();\n");
544     _appendStr_(k); _appendStr_(@"Tab[\"Ar\"][0] = new Image();\n");
545     _appendStr_(k); _appendStr_(@"Tab[\"Ar\"][0].src = \"");
546     _appendStr_(u); _appendStr_(@"\";\n");  // unselected img
547     _appendStr_(k); _appendStr_(@"Tab[\"Ar\"][1] = new Image();\n");
548     _appendStr_(k); _appendStr_(@"Tab[\"Ar\"][1].src = \"");
549     _appendStr_(s); _appendStr_(@"\";\n");  // selected img
550     _appendStr_(@"//-->\n</script>");
551 #undef _appendStr_
552 #endif
553   }
554 }
555
556 - (void)appendSubmitButton:(WETabItemInfo *)_info
557   toResponse:(WOResponse *)_response
558   inContext:(WOContext *)_ctx
559   isActive:(BOOL)_isActive isLeft:(BOOL)_left
560   doScript:(BOOL)_doScript   keys:(NSArray *)_keys
561 {
562   [self appendLink:_info
563         toResponse:_response
564         inContext:_ctx
565         isActive:_isActive isLeft:_left
566         doScript:_doScript   keys:_keys];
567 }
568
569 - (void)_appendTabViewJSScriptToResponse:(WOResponse *)_response
570   inContext:(WOContext *)_ctx
571 {
572   [_response appendContentString:
573                @"<script language=\"JavaScript\">\n<!--\n\n"
574                @"function showTab(obj) {\n"
575 #if DEBUG_JS
576                @"  if (obj==null) { alert('missing tab obj ..'); return; }\n"
577                @"  if (obj['Div']==null) {"
578                @"    alert('missing div key in ' + obj); return; }\n"
579                @"  if (obj['Div'].style==null) {"
580                @"    alert('missing style key in div ' + obj['Div']);return; }\n"
581 #endif
582                @"  obj['Div'].style.display = \"\";\n"
583                @"  obj['Img'].src = obj[\"Ar\"][1].src;\n"
584                @"  obj['link'].href = obj[\"href2\"];\n"
585                @"}\n"
586                @"function hideTab(obj) {\n"
587 #if DEBUG_JS
588                @"  if (obj==null) { alert('missing tab obj ..'); return; }\n"
589                @"  if (obj['Div']==null) {"
590                @"    alert('missing div key in ' + obj); return; }\n"
591                @"  if (obj['Div'].style==null) {"
592                @"    alert('missing style key in div ' + obj['Div']);return; }\n"
593 #endif
594                @" obj['Div'].style.display = \"none\";\n"
595                @" obj['Img'].src = obj[\"Ar\"][0].src;\n"
596                @" obj['link'].href = obj[\"href1\"];\n"
597                @"}\n"
598                @"function swapCorners(obj1,obj2) {\n"
599                @"   if (obj1==null) { alert('missing corner 1'); return; }\n"
600                @"   if (obj2==null) { alert('missing corner 2'); return; }\n"
601                @"   obj1.style.display = \"none\";\n"
602                @"   obj2.style.display = \"\";\n"
603                @"}\n"
604                @"//-->\n</script>"];
605 }
606
607 - (void)_appendHeaderRowToResponse:(WOResponse *)_response
608   inContext:(WOContext *)_ctx
609   keys:(NSArray *)keys activeKey:(NSString *)activeKey
610   doScript:(BOOL)doScript
611 {
612   unsigned i, count;
613   BOOL doForm;
614   
615   doForm = NO;  /* generate form controls ? */
616   
617   [_response appendContentString:@"<tr><td colspan='2'>"];
618   [_response appendContentString:
619                @"<table border='0' cellpadding='0' cellspacing='0'><tr>"];
620   
621   for (i = 0, count = [keys count]; i < count; i++) {
622     WETabItemInfo *info;
623     NSString       *key;
624     BOOL           isActive;
625     
626     info     = [keys objectAtIndex:i];
627     key      = info->key;
628     isActive = [key isEqualToString:activeKey];
629     
630     [_ctx appendElementIDComponent:key];
631     
632     if (doForm) {
633       /* tab is inside of a FORM, so produce submit buttons */
634       [self appendSubmitButton:info
635             toResponse:_response
636             inContext:_ctx
637             isActive:isActive
638             isLeft:(i == 0) ? YES : NO
639             doScript:doScript
640             keys:keys];
641     }
642     else {
643       /* tab is not in a FORM, generate hyperlinks for tab */
644       [self appendLink:info
645             toResponse:_response
646             inContext:_ctx
647             isActive:isActive
648             isLeft:(i == 0) ? YES : NO
649             doScript:doScript
650             keys:keys];
651     }
652     
653     [_ctx deleteLastElementIDComponent];
654   }
655   //  [_response appendContentString:@"<td></td>"];
656   [_response appendContentString:@"</tr></table>"];
657   [_response appendContentString:@"</td></tr>"];
658 }
659
660 - (void)_appendHeaderFootRowToResponse:(WOResponse *)_response
661   inContext:(WOContext *)_ctx
662   bgcolor:(NSString *)bgcolor
663   doScript:(BOOL)doScript
664   isLeftActive:(BOOL)isLeftActive
665 {
666   [_response appendContentString:@"  <tr"];
667   if (bgcolor) {
668     [_response appendContentString:@" bgcolor=\""];
669     [_response appendContentHTMLAttributeValue:bgcolor];
670     [_response appendContentString:@"\""];
671   }
672   [_response appendContentString:@">"];
673     
674   /* left corner */
675   [_response appendContentString:@"    <td align=\"left\" width=\"10\">"];
676   
677   if (doScript) {
678     [_response appendContentString:@"<div id=\"tabCorner"];
679     [_response appendContentString:[self _tabViewCountInContext:_ctx]];
680     [_response appendContentString:@"\" "];
681     [_response appendContentString:@"style=\"display: "];
682     [_response appendContentString:(isLeftActive) ? @"" : @"none"];
683     [_response appendContentString:@";\">"];
684     [_response appendContentString:@"&nbsp;"];
685     [_response appendContentString:@"</div>"];
686   }
687   else if (isLeftActive)
688     [_response appendContentString:@"&nbsp;"];
689   
690   if (doScript) {
691     [_response appendContentString:@"<div id=\"tabCornerLeft"];
692     [_response appendContentString:[self _tabViewCountInContext:_ctx]];
693     [_response appendContentString:@"\" "];
694     [_response appendContentString:@"style=\"display: "];
695     [_response appendContentString:(!isLeftActive) ? @"visible" : @"none"];
696     [_response appendContentString:@";\">"];
697   }
698   
699   if (!isLeftActive || doScript) {
700     NSString *uri;
701     
702     uri = [self->leftCornerIcon stringValueInComponent:[_ctx component]];
703     if ((uri = WEUriOfResource(uri, _ctx))) {
704       [_response appendContentString:@"<img border=\"0\" alt=\"\" src=\""];
705       [_response appendContentString:uri];
706       [_response appendContentString:@"\" />"];
707     }
708     else
709       [_response appendContentString:@"&nbsp;"];
710   }
711   if (doScript)
712     [_response appendContentString:@"</div>"];
713
714   [_response appendContentString:@"</td>"];
715
716   /* right corner */
717   [_response appendContentString:@"    <td align=\"right\">"];
718   {
719     NSString *uri;
720       
721     uri = [self->rightCornerIcon stringValueInComponent:[_ctx component]];
722     if ((uri = WEUriOfResource(uri, _ctx))) {
723       [_response appendContentString:@"<img border=\"0\" alt=\"\" src=\""];
724       [_response appendContentString:uri];
725       if (_ctx->wcFlags.xmlStyleEmptyElements)
726         [_response appendContentString:@"\" />"];
727       else
728         [_response appendContentString:@"\">"];
729     }
730     else
731       [_response appendContentString:@"&nbsp;"];
732   }
733   [_response appendContentString:@"</td>"];
734     
735   [_response appendContentString:@"  </tr>"];
736 }
737
738 - (void)_appendBodyRowToResponse:(WOResponse *)_response
739   inContext:(WOContext *)_ctx
740   bgcolor:(NSString *)bgcolor
741   activeKey:(NSString *)activeKey
742 {
743   WEClientCapabilities *ccaps;
744   BOOL indentContent;
745   
746   ccaps = [[_ctx request] clientCapabilities];
747   
748   /* put additional padding table into content ??? */
749   indentContent = [ccaps isFastTableBrowser] && ![ccaps isTextModeBrowser];
750   
751   [_response appendContentString:@"<tr><td colspan='2'"];
752   
753   if (bgcolor) {
754     [_response appendContentString:@" bgcolor=\""];
755     [_response appendContentHTMLAttributeValue:bgcolor];
756     [_response appendContentString:@"\""];
757   }
758   [_response appendContentString:@">"];
759     
760   if (indentContent) { // TODO: we can now replace that with CSS padding?
761     /* start padding table */
762     [_response appendContentString:
763                @"<table border='0' width='100%'"
764                @" cellpadding='10' cellspacing='0'>"];
765     [_response appendContentString:@"<tr><td>"];
766   }
767     
768   [_ctx appendElementIDComponent:@"b"];
769   [_ctx appendElementIDComponent:activeKey];
770   
771   /* generate currently active body */
772   {
773     [_ctx setObject:activeKey forKey:WETabView_BODY];
774     [self->template appendToResponse:_response inContext:_ctx];
775     [_ctx removeObjectForKey:WETabView_BODY];
776   }
777   
778   [_ctx deleteLastElementIDComponent]; // activeKey
779   [_ctx deleteLastElementIDComponent]; // 'b'
780     
781   if (indentContent)
782     /* close padding table */
783     [_response appendContentString:@"</td></tr></table>"];
784     
785   [_response appendContentString:@"</td></tr>"];
786 }
787
788 - (BOOL)isLeftActiveInKeys:(NSArray *)keys activeKey:(NSString *)activeKey{
789   unsigned i, count;
790   BOOL isLeftActive;
791   
792   isLeftActive = NO;
793   
794   for (i = 0, count = [keys count]; i < count; i++) {
795     WETabItemInfo *info;
796     
797     info = [keys objectAtIndex:i];
798     
799     if ((i == 0) && [info->key isEqualToString:activeKey])
800       isLeftActive = YES;
801   }
802   
803   return isLeftActive;
804 }
805
806 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
807   /*
808     generates a table with three rows:
809     - a row with the tabs
810     - a row for round edges
811     - a row for the content
812   */
813   WOComponent  *cmp;
814   NSString     *bgcolor;
815   BOOL         isLeftActive;
816   BOOL         doScript;
817   id           nestedState;
818   NSString     *activeKey;
819   NSArray      *keys;
820   int          tabViewCount; /* used for image id's and writing script once */
821   
822   doScript      = NO;  /* perform tab-clicks on browser (use javascript) */
823   tabViewCount  = [[_ctx valueForKey:@"WETabViewScriptDone"] intValue];
824   cmp           = [_ctx component];
825   
826   /* check for browser */
827   {
828     WEClientCapabilities *ccaps;
829     //BOOL isJavaScriptBrowser;
830     
831     ccaps    = [[_ctx request] clientCapabilities];
832     doScript = [ccaps isInternetExplorer] && [ccaps isJavaScriptBrowser]; 
833     if ([_ctx hasSession]) {
834       WOSession *sn;
835       
836       sn = [cmp session];
837       doScript = (doScript &&
838                   [[sn valueForKey:@"isJavaScriptEnabled"] boolValue]);
839     }
840   }
841
842   /* disable javascript */
843   doScript = NO;
844   
845   /* save state */
846   
847   nestedState = [self saveNestedStateInContext:_ctx];
848   
849   /* configure */
850   
851   activeKey = [self->selection stringValueInComponent:cmp];
852   
853   bgcolor = [self->bgColor stringValueInComponent:cmp];
854   bgcolor = [bgcolor stringValue];
855   
856   [_ctx appendElementIDComponent:@"h"];
857   
858   /* collect & process keys (= available tabs) */
859   
860   keys = [self collectKeysInContext:_ctx];
861   
862   if (![[keys valueForKey:@"key"] containsObject:activeKey])
863     /* selection is not available in keys */
864     activeKey = nil;
865   
866   if ((activeKey == nil) && ([keys count] > 0)) {
867     /* no or invalid selection, use first key */
868     activeKey = [[keys objectAtIndex:0] key];
869     if ([self->selection isValueSettable])
870       [self->selection setValue:activeKey inComponent:[_ctx component]];
871   }
872
873   if (doScript) {
874     doScript = [[keys valueForKey:@"isScript"] containsObject:YesNumber];
875     [_ctx setObject:[NSNumber numberWithBool:doScript]
876           forKey:WETabView_SCRIPT];
877   }
878
879   /* start appending */
880   
881   if ((doScript) && (tabViewCount == 0))
882     [self _appendTabViewJSScriptToResponse:_response inContext:_ctx];
883   
884   /* count up for unique tabCorner/tabCornerLeft images */
885   [_ctx takeValue:[NSNumber numberWithInt:(tabViewCount + 1)]
886         forKey:@"WETabViewScriptDone"];
887
888   // TODO: add CSS class for table
889   [_response appendContentString:
890                @"<table border='0' width='100%'"
891                @" cellpadding='0' cellspacing='0'>"];
892   
893   /* find out whether left is active */
894   
895   isLeftActive = [self isLeftActiveInKeys:keys activeKey:activeKey];
896   
897   /* generate header row */
898   
899   [self _appendHeaderRowToResponse:_response inContext:_ctx
900         keys:keys activeKey:activeKey
901         doScript:doScript];
902   
903   [_ctx deleteLastElementIDComponent]; // 'h' for head
904   [_ctx removeObjectForKey:WETabView_HEAD];
905   
906   /* header foot row */
907   
908   [self _appendHeaderFootRowToResponse:_response inContext:_ctx
909         bgcolor:bgcolor
910         doScript:doScript
911         isLeftActive:isLeftActive];
912   
913   /* body row */
914   
915   [self _appendBodyRowToResponse:_response inContext:_ctx
916         bgcolor:bgcolor
917         activeKey:activeKey];
918   
919   /* close table */
920   
921   [_response appendContentString:@"</table>"];
922   [_ctx removeObjectForKey:WETabView_ACTIVEKEY];
923   [_ctx removeObjectForKey:WETabView_KEYS];
924   [self restoreNestedState:nestedState inContext:_ctx];
925 }
926
927 @end /* WETabView */