]> err.no Git - sope/blob - sope-appserver/WEExtensions/WEPageView.m
added strict OSX bundle dependencies
[sope] / sope-appserver / WEExtensions / WEPageView.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 <NGObjWeb/WODynamicElement.h>
23 #include <NGObjWeb/WOContext.h>
24
25 /*
26   WEPageView
27   
28   TODO: describe what it does
29 */
30
31 @interface WEPageView : WODynamicElement
32 {
33   WOAssociation *selection;
34
35   /* config: */
36   WOAssociation *titleColor;
37   WOAssociation *contentColor;
38
39   WOAssociation *fontColor;
40   WOAssociation *fontFace;
41   WOAssociation *fontSize;
42   
43   WOAssociation *firstIcon;
44   WOAssociation *firstBlind;    // firstBlindIcon
45   WOAssociation *previousIcon;
46   WOAssociation *previousBlind; // previousBlindIcon
47   WOAssociation *nextIcon;
48   WOAssociation *nextBlind;     // nextBlindIcon
49   WOAssociation *lastIcon;
50   WOAssociation *lastBlind;     // lastBlindIcon
51
52   WOAssociation *firstLabel;
53   WOAssociation *previousLabel;
54   WOAssociation *nextLabel;
55   WOAssociation *lastLabel;
56   
57   id            template;
58 }
59
60 @end
61
62 @interface WEPageItem : WODynamicElement
63 {
64   WOAssociation *key;
65   WOAssociation *title;
66   WOAssociation *action;
67   
68   id            template;
69 }
70
71 @end
72
73 @interface WEPageItemInfo : NSObject
74 {
75 @public
76   NSString *title;
77   NSString *key;
78   NSString *uri;
79 }
80 @end
81
82 #include <NGObjWeb/WEClientCapabilities.h>
83 #include "common.h"
84
85 // #define DEBUG_TAKEVALUES 1
86
87 /* context keys */
88 static NSString *WEPageView_HEAD      = @"WEPageView_head";
89 static NSString *WEPageView_BODY      = @"WEPageView_body";
90 static NSString *WEPageView_KEYS      = @"WEPageView_keys";
91 static NSString *WEPageView_ACTIVEKEY = @"WEPageView_activekey";
92 static NSString *WEPageView_COLLECT   = @"~tv~";
93
94 // navigation icons
95 static NSString *WEPageView_first          = @"WEPageView_first";
96 static NSString *WEPageView_first_blind    = @"WEPageView_first_blind";
97 static NSString *WEPageView_previous       = @"WEPageView_previous";
98 static NSString *WEPageView_previous_blind = @"WEPageView_previous_blind";
99 static NSString *WEPageView_next           = @"WEPageView_next";
100 static NSString *WEPageView_next_blind     = @"WEPageView_next_blind";
101 static NSString *WEPageView_last           = @"WEPageView_last";
102 static NSString *WEPageView_last_blind     = @"WEPageView_last_blind";
103
104 // labels
105 static NSString *WEPageView_firstLabel     = @"WEPageView_firstLabel";
106 static NSString *WEPageView_previousLabel  = @"WEPageView_previousLabel";
107 static NSString *WEPageView_nextLabel      = @"WEPageView_nextLabel";
108 static NSString *WEPageView_lastLabel      = @"WEPageView_lastLabel";
109
110 static NSString *WEPageView_               = @"WEPageView_";
111
112 @implementation WEPageView
113
114 static NSNumber *YesNumber = nil;
115
116 + (void)initialize {
117   if (YesNumber == nil) YesNumber = [[NSNumber numberWithBool:YES] retain];
118 }
119
120 + (int)version {
121   return [super version] + 0;
122 }
123
124 - (id)initWithName:(NSString *)_name
125   associations:(NSDictionary *)_config
126   template:(WOElement *)_subs
127 {
128   if ((self = [super initWithName:_name associations:_config template:_subs])) {
129     self->selection      = WOExtGetProperty(_config, @"selection");
130
131     self->contentColor   = WOExtGetProperty(_config, @"contentColor");
132     self->titleColor     = WOExtGetProperty(_config, @"titleColor");
133     
134     self->firstIcon      = WOExtGetProperty(_config, @"firstIcon");
135     self->firstBlind     = WOExtGetProperty(_config, @"firstBlindIcon");
136     self->previousIcon   = WOExtGetProperty(_config, @"previousIcon");
137     self->previousBlind  = WOExtGetProperty(_config, @"previousBlindIcon");
138     self->nextIcon       = WOExtGetProperty(_config, @"nextIcon");
139     self->nextBlind      = WOExtGetProperty(_config, @"nextBlindIcon");
140     self->lastIcon       = WOExtGetProperty(_config, @"lastIcon");
141     self->lastBlind      = WOExtGetProperty(_config, @"lastBlindIcon");
142
143     self->firstLabel     = WOExtGetProperty(_config, @"firstLabel");
144     self->previousLabel  = WOExtGetProperty(_config, @"previousLabel");
145     self->nextLabel      = WOExtGetProperty(_config, @"nextLabel");
146     self->lastLabel      = WOExtGetProperty(_config, @"lastLabel");
147
148     self->fontColor      = WOExtGetProperty(_config, @"fontColor");
149     self->fontFace       = WOExtGetProperty(_config, @"fontFace");
150     self->fontSize       = WOExtGetProperty(_config, @"fontSize");
151
152 #define SetAssociationValue(_a_, _value_) \
153     if (_a_ == nil) \
154       _a_ = [[WOAssociation associationWithValue:_value_] retain];
155     
156     SetAssociationValue(self->firstLabel,    @"<<");
157     SetAssociationValue(self->previousLabel, @"<");
158     SetAssociationValue(self->nextLabel,     @">");
159     SetAssociationValue(self->lastLabel,     @">>");
160            
161 #undef SetAssociationValue
162     
163     self->template = [_subs retain];
164   }
165   return self;
166 }
167
168 - (void)dealloc {
169   RELEASE(self->selection);
170   
171   RELEASE(self->contentColor);
172   RELEASE(self->titleColor);
173
174   RELEASE(self->firstIcon);
175   RELEASE(self->firstBlind);
176   RELEASE(self->previousIcon);
177   RELEASE(self->previousBlind);
178   RELEASE(self->nextIcon);
179   RELEASE(self->nextBlind);
180   RELEASE(self->lastIcon);
181   RELEASE(self->lastBlind);
182
183   RELEASE(self->firstLabel);
184   RELEASE(self->previousLabel);
185   RELEASE(self->nextLabel);
186   RELEASE(self->lastLabel);
187
188   RELEASE(self->fontColor);
189   RELEASE(self->fontFace);
190   RELEASE(self->fontSize);
191   
192   RELEASE(self->template);
193   [super dealloc];
194 }
195
196
197 - (void)updateConfigInContext:(WOContext *)_ctx {
198   WOComponent *cmp;
199   NSString    *tmp;
200
201   cmp = [_ctx component];
202
203 #define SetConfigInContext(_a_, _key_)                                  \
204       if (_a_ && (tmp = [_a_ valueInComponent:cmp]))                    \
205         [_ctx setObject:tmp forKey:_key_];                              \
206     
207   SetConfigInContext(self->firstIcon,     WEPageView_first);
208   SetConfigInContext(self->firstBlind,    WEPageView_first_blind);
209   SetConfigInContext(self->previousIcon,  WEPageView_previous);
210   SetConfigInContext(self->previousBlind, WEPageView_previous_blind);
211   SetConfigInContext(self->nextIcon,      WEPageView_next);
212   SetConfigInContext(self->nextBlind,     WEPageView_next_blind);
213   SetConfigInContext(self->lastIcon,      WEPageView_last);
214   SetConfigInContext(self->lastBlind,     WEPageView_last_blind);
215
216   SetConfigInContext(self->firstLabel,    WEPageView_firstLabel);
217   SetConfigInContext(self->previousLabel, WEPageView_previousLabel);
218   SetConfigInContext(self->nextLabel,     WEPageView_nextLabel);
219   SetConfigInContext(self->lastLabel,     WEPageView_lastLabel);
220
221 #undef SetConfigInContext
222 }
223
224 - (void)removeConfigInContext:(WOContext *)_ctx {
225   [_ctx removeObjectForKey:WEPageView_first];
226   [_ctx removeObjectForKey:WEPageView_first_blind];
227   [_ctx removeObjectForKey:WEPageView_previous];
228   [_ctx removeObjectForKey:WEPageView_previous_blind];
229   [_ctx removeObjectForKey:WEPageView_next];
230   [_ctx removeObjectForKey:WEPageView_next_blind];
231   [_ctx removeObjectForKey:WEPageView_last];
232   [_ctx removeObjectForKey:WEPageView_last_blind];
233
234   [_ctx removeObjectForKey:WEPageView_firstLabel];
235   [_ctx removeObjectForKey:WEPageView_previousLabel];
236   [_ctx removeObjectForKey:WEPageView_nextLabel];
237   [_ctx removeObjectForKey:WEPageView_lastLabel];
238 }
239
240 static inline NSString *WEPageLabelForKey(NSString *_key, WOContext *_ctx) {
241   NSString *key;
242
243   key = [NSString stringWithFormat:@"WEPageView_%@Label", _key];
244   return [_ctx objectForKey:key];
245 }
246
247 /* nesting */
248
249 - (id)saveNestedStateInContext:(WOContext *)_ctx {
250   return nil;
251 }
252 - (void)restoreNestedState:(id)_state inContext:(WOContext *)_ctx {
253   if (_state == nil) return;
254 }
255
256 - (NSArray *)collectKeysInContext:(WOContext *)_ctx {
257   /* collect mode, collects all keys */
258   [_ctx setObject:WEPageView_COLLECT forKey:WEPageView_HEAD];
259
260   [self->template appendToResponse:nil inContext:_ctx];
261   
262   [_ctx removeObjectForKey:WEPageView_HEAD];
263   return [_ctx objectForKey:WEPageView_KEYS];
264 }
265
266 /* responder */
267
268 - (void)takeValuesFromRequest:(WORequest *)_request inContext:(WOContext *)_ctx {
269   id nestedState;
270   
271   nestedState = [self saveNestedStateInContext:_ctx];
272
273   [_ctx appendElementIDComponent:@"h"];
274   [self->template takeValuesFromRequest:_request inContext:_ctx];
275   [_ctx deleteLastElementIDComponent];
276   
277   [_ctx appendElementIDComponent:@"b"];
278   [_ctx setObject:self->selection forKey:WEPageView_BODY];
279
280 #if DEBUG_TAKEVALUES
281   [[_ctx component] debugWithFormat:@"WEPageView: body takes values, eid='%@'",
282                     [_ctx elementID]];
283 #endif
284   
285   [self->template takeValuesFromRequest:_request inContext:_ctx];
286   
287   [_ctx removeObjectForKey:WEPageView_BODY];
288   [_ctx deleteLastElementIDComponent]; /* 'b' */
289   [self restoreNestedState:nestedState inContext:_ctx];
290 }
291
292 - (id)invokeActionForRequest:(WORequest *)_request inContext:(WOContext *)_ctx {
293   NSString *key;
294   id result;
295
296   result = nil;
297   if ((key = [_ctx currentElementID])) {
298     id nestedState;
299     
300     nestedState = [self saveNestedStateInContext:_ctx];
301     
302     if ([key isEqualToString:@"h"]) {
303       /* header action */
304       
305       [_ctx consumeElementID];
306       [_ctx appendElementIDComponent:key];
307       
308       [_ctx setObject:self->selection forKey:WEPageView_HEAD];
309       result = [self->template invokeActionForRequest:_request inContext:_ctx];
310       [_ctx removeObjectForKey:WEPageView_HEAD];
311       
312       [_ctx deleteLastElementIDComponent];
313     }
314     else if ([key isEqualToString:@"b"]) {
315       /* body action */
316       
317       [_ctx consumeElementID];
318       [_ctx appendElementIDComponent:key];
319
320       [_ctx setObject:self->selection forKey:WEPageView_BODY];
321       
322       result = [self->template invokeActionForRequest:_request inContext:_ctx];
323       
324       [_ctx removeObjectForKey:WEPageView_BODY];
325       
326       [_ctx deleteLastElementIDComponent];
327     }
328     else {
329       [[_ctx application]
330              debugWithFormat:@"unknown page container key '%@'", key];
331     }
332     
333     [self restoreNestedState:nestedState inContext:_ctx];
334   }
335   return result;
336 }
337
338
339
340 - (void)_appendNav:(NSString *)_nav isBlind:(BOOL)_isBlind
341   toResponse:(WOResponse *)_response inContext:(WOContext *)_ctx
342   info:(WEPageItemInfo *)_info
343 {
344   NSString *img;
345   NSString *label;
346   BOOL     doForm;
347   
348   doForm = [_ctx isInForm];
349   img = [WEPageView_ stringByAppendingString:_nav];
350   img = [img stringByAppendingString:(_isBlind) ? @"_blind" : @""];
351   img = [_ctx objectForKey:img];
352   img = WEUriOfResource(img,_ctx);
353   
354   label  = WEPageLabelForKey(_nav, _ctx);
355
356   // append as submit button
357   if (doForm && !_isBlind && img && _info) {
358     NSString *uri;
359
360     uri = [[_info->uri componentsSeparatedByString:@"/"] lastObject];
361     
362     [_ctx appendElementIDComponent:_nav];
363     [_response appendContentString:@"<input type=\"image\" border=\"0\""];
364     [_response appendContentString:@" name=\""];
365     [_response appendContentString:uri];
366     [_response appendContentString:@"\" src=\""];
367     [_response appendContentString:img];
368     [_response appendContentString:@"\" alt=\""];
369     [_response appendContentString:label];
370     if (_ctx->wcFlags.xmlStyleEmptyElements)
371       [_response appendContentString:@"\" />"];
372     else
373       [_response appendContentString:@"\">"];
374     [_ctx deleteLastElementIDComponent];
375     return;
376   }
377
378   /* open anker */
379   if (!_isBlind && _info) {
380     [_ctx appendElementIDComponent:_nav];
381     [_response appendContentString:@"<a href=\""];
382     [_response appendContentString:_info->uri];
383     [_response appendContentString:@"\">"];
384   }
385   if (!img) {
386     [_response appendContentCharacter:'['];
387     [_response appendContentString:label];
388     [_response appendContentCharacter:']'];
389   }
390   else {
391     [_response appendContentString:@"<img border=\"0\" src=\""];
392     [_response appendContentString:img];
393     [_response appendContentString:@"\" alt=\""];
394     [_response appendContentString:label];
395     if (_ctx->wcFlags.xmlStyleEmptyElements)
396       [_response appendContentString:@"\" />"];
397     else
398       [_response appendContentString:@"\">"];
399   }
400   /* close anker */
401   if (!_isBlind && _info) {
402     [_response appendContentString:@"</a>"];
403     [_ctx deleteLastElementIDComponent];
404   }
405 }
406
407 - (void)appendNavToResponse:(WOResponse *)_response
408   inContext:(WOContext *)_ctx
409   isLeft:(BOOL)_isLeft
410   activeKey:(WEPageItemInfo *)_activeKey
411   keys:(NSArray *)_keys
412 {
413   WEPageItemInfo *info;
414   int idx, cnt;
415
416   idx = [_keys indexOfObject:_activeKey];
417   cnt = [_keys count];
418
419   if (idx > cnt) {
420     NSLog(@"Warning! WEPageView: idx is out of range!");
421     return;
422   }
423
424   if (_isLeft) {
425     info = (cnt > 0) ? [_keys objectAtIndex:0] : nil;
426     [self _appendNav:@"first"     isBlind:(idx < 1) ? YES : NO
427           toResponse:_response  inContext:_ctx info:info];
428
429     info = (cnt > 0 && idx > 0) ? [_keys objectAtIndex:idx-1] : nil;
430     [self _appendNav:@"previous"   isBlind:(idx < 1) ? YES : NO
431           toResponse:_response  inContext:_ctx info:info];
432   }
433   else {
434     info = (cnt > idx+1) ? [_keys objectAtIndex:idx+1] : nil;
435     [self _appendNav:@"next"     isBlind:(cnt <= idx+1) ? YES : NO
436           toResponse:_response inContext:_ctx info:info];
437
438     info = (cnt > 0) ? [_keys objectAtIndex:cnt-1] : nil;
439     [self _appendNav:@"last"     isBlind:(cnt <= idx+1) ? YES : NO
440           toResponse:_response inContext:_ctx info:info];
441   }
442 }
443
444 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
445   WOComponent    *cmp;
446   BOOL           indentContent;
447   BOOL           doForm;
448   NSString       *bgcolor;
449   id             nestedState;
450   NSString       *activeKey;
451   WEPageItemInfo *activeInfo = nil;
452   NSArray        *keys;
453   unsigned       i, count;
454   
455   [self updateConfigInContext:_ctx];
456   
457   doForm        = NO;  /* generate form controls ? */
458   indentContent = YES; /* put additional padding table into content */
459   cmp           = [_ctx component];
460   
461   /* check for browser */
462
463   {
464     WEClientCapabilities *ccaps;
465
466     ccaps = [[_ctx request] clientCapabilities];
467     if ([ccaps isFastTableBrowser] && ![ccaps isTextModeBrowser])
468       indentContent = YES;
469   }
470   
471   /* save state */
472   
473   nestedState = [self saveNestedStateInContext:_ctx];
474     
475   /* collect & process keys (= available tabs) */
476
477   [_ctx appendElementIDComponent:@"h"];
478
479   activeKey = [self->selection stringValueInComponent:cmp];
480   keys      = [self collectKeysInContext:_ctx];
481   
482   if (![[keys valueForKey:@"key"] containsObject:activeKey])
483     /* selection is not available in keys */
484     activeKey = nil;
485   
486   if ((activeKey == nil) && ([keys count] > 0)) {
487     /* no or invalid selection, use first key */
488     activeKey = [[keys objectAtIndex:0] key];
489     if ([self->selection isValueSettable])
490       [self->selection setValue:activeKey inComponent:[_ctx component]];
491   }
492   
493   for (i = 0, count = [keys count]; i < count; i++) {
494     WEPageItemInfo *info;
495
496     info     = [keys objectAtIndex:i];
497
498     if ([info->key isEqualToString:activeKey]) {
499       activeInfo = info;
500       break;
501     }
502   }
503
504   /* start appending */
505
506   [_response appendContentString:
507                @"<table border=\"0\" width=\"100%\""
508                @" cellpadding=\"0\" cellspacing=\"0\">"];
509
510   /* generate header row */
511
512   bgcolor = [self->titleColor stringValueInComponent:cmp];
513
514   [_response appendContentString:@"<tr>"];
515
516   /* left navigation */
517   WEAppendTD(_response, @"left", nil, bgcolor);
518   [self appendNavToResponse:_response
519         inContext:_ctx
520         isLeft:YES
521         activeKey:activeInfo
522         keys:keys];
523   [_response appendContentString:@"</td>"];
524
525
526   /* title */
527   WEAppendTD(_response, @"center", nil, bgcolor);
528   {
529     unsigned char buf[256];
530     NSString *tC, *tF, *tS; // text font attrtibutes
531     BOOL     hasFont;
532     
533     cmp = [_ctx component];
534   
535     // TODO: use CSS
536     tC  = [self->fontColor stringValueInComponent:cmp];
537     tF  = [self->fontFace  stringValueInComponent:cmp];
538     tS  = [self->fontSize  stringValueInComponent:cmp];
539
540     hasFont = (tC || tF || tS) ? YES : NO;
541
542     if (hasFont)
543       WEAppendFont(_response, tC, tF, tS);
544   
545     for (i = 0, count = [keys count]; i < count; i++) {
546       WEPageItemInfo *info;
547
548       info = [keys objectAtIndex:i];
549
550       // TODO: use CSS
551       if ([info->key isEqualToString:activeKey]) {
552         [_response appendContentString:@"<b>"];
553         [_response appendContentString:info->title];
554         [_response appendContentString:@"</b>"];
555         break;
556       }
557     }
558     // TODO: use CSS
559     sprintf((char *)buf, " <small>(%d/%d)</small>", (i + 1), count);
560     [_response appendContentCString:buf];
561     
562     if (hasFont)
563       [_response appendContentString:@"</font>"];
564   }
565   [_response appendContentString:@"</td>"];
566
567   /* right navigation */
568
569   WEAppendTD(_response, @"right", nil, bgcolor);
570   [self appendNavToResponse:_response
571         inContext:_ctx
572         isLeft:NO
573         activeKey:activeInfo
574         keys:keys];
575   [_response appendContentString:@"</td>"];
576   
577   [_response appendContentString:@"</tr>"];
578   [_ctx deleteLastElementIDComponent]; // delete "h"
579   [_ctx removeObjectForKey:WEPageView_HEAD];
580   
581   /* body row */
582
583   bgcolor = [self->contentColor stringValueInComponent:cmp];
584   
585   {
586     [_response appendContentString:@"<tr><td colspan=\"3\""];
587     if (bgcolor) {
588       [_response appendContentString:@" bgcolor=\""];
589       [_response appendContentHTMLAttributeValue:bgcolor];
590       [_response appendContentString:@"\""];
591     }
592     [_response appendContentString:@">"];
593     
594     if (indentContent) {
595       /* start padding table */
596       [_response appendContentString:
597                    @"<table border=\"0\" width=\"100%\""
598                    @" cellpadding=\"10\" cellspacing=\"0\">"];
599       [_response appendContentString:@"<tr><td>"];
600     }
601     
602     [_ctx appendElementIDComponent:@"b"];
603
604     /* generate currently active page */
605     
606     {
607       [_ctx setObject:activeKey forKey:WEPageView_BODY];
608       [self->template appendToResponse:_response inContext:_ctx];
609       [_ctx removeObjectForKey:WEPageView_BODY];
610     }
611     
612     [_ctx deleteLastElementIDComponent];
613     
614     if (indentContent)
615       /* close padding table */
616       [_response appendContentString:@"</td></tr></table>"];
617     
618     [_response appendContentString:@"</td></tr>"];
619   }  
620   [_response appendContentString:@"</table>"];
621   
622   [_ctx removeObjectForKey:WEPageView_ACTIVEKEY];
623   [_ctx removeObjectForKey:WEPageView_KEYS];
624   [self restoreNestedState:nestedState inContext:_ctx];
625
626   [self removeConfigInContext:_ctx];
627 }
628
629 @end /* WEPageView */
630
631 @implementation WEPageItem
632
633 + (int)version {
634   return [super version] + 0;
635 }
636
637 - (id)initWithName:(NSString *)_name
638   associations:(NSDictionary *)_config
639   template:(WOElement *)_subs
640 {
641   if ((self = [super initWithName:_name associations:_config template:_subs])) {
642     self->key      = WOExtGetProperty(_config, @"key");
643     self->title    = WOExtGetProperty(_config, @"title");
644     self->action   = WOExtGetProperty(_config, @"action");
645
646     self->template = RETAIN(_subs);
647   }
648   return self;
649 }
650
651 - (void)dealloc {
652   [self->action   release];
653   [self->title    release];
654   [self->key      release];
655   [self->template release];
656   [super dealloc];
657 }
658
659 /* responder */
660
661 - (void)takeValuesFromRequest:(WORequest *)_request inContext:(WOContext *)_ctx {
662   WOAssociation *tmp;
663   
664   if ((tmp = [_ctx objectForKey:WEPageView_BODY])) {
665     NSString *activeTabKey;
666     NSString *myTabKey;
667     
668     activeTabKey = [tmp stringValueInComponent:[_ctx component]];
669     myTabKey     = [self->key stringValueInComponent:[_ctx component]];
670
671     if ([activeTabKey isEqualToString:myTabKey]) {
672       [_ctx appendElementIDComponent:activeTabKey];
673
674 #if DEBUG_TAKEVALUES
675       [[_ctx component] debugWithFormat:
676                           @"WEPageItem: body takes values, eid='%@'",
677                           [_ctx elementID]];
678 #endif
679       
680       [self->template takeValuesFromRequest:_request inContext:_ctx];
681       [_ctx deleteLastElementIDComponent];
682     }
683 #if DEBUG_TAKEVALUES
684     else {
685       [[_ctx component] debugWithFormat:
686                           @"WEPageItem: body takes no values, eid='%@'",
687                           [_ctx elementID]];
688     }
689 #endif
690   }
691   else {
692     NSString *k;
693     NSString *eid;
694
695     k = [self->key stringValueInComponent:[_ctx component]];
696     
697     eid = [[_ctx elementID] stringByAppendingString:@"."];
698     eid = [eid stringByAppendingString:k];
699     
700     if (k && [_request formValueForKey:[eid stringByAppendingString:@".x"]]) {
701       [_ctx addActiveFormElement:self];
702       [_ctx setRequestSenderID:eid];
703     }
704   }
705 }
706
707 - (id)invokeActionForRequest:(WORequest *)_rq inContext:(WOContext *)_ctx {
708   NSString      *tabkey;
709   id            result;
710   WOAssociation *tmp;
711
712   tabkey = [_ctx currentElementID];
713   [_ctx consumeElementID];
714   [_ctx appendElementIDComponent:tabkey];
715   
716   if ((tmp = [_ctx objectForKey:WEPageView_HEAD])) {
717     /* click on tab icon */
718     if ([tmp isValueSettable])
719       [tmp setValue:tabkey inComponent:[_ctx component]];
720     
721     result = [self->action valueInComponent:[_ctx component]];
722   }
723   else if ((tmp = [_ctx objectForKey:WEPageView_BODY])) {
724     /* clicked somewhere in the body */
725     if ([tmp isValueSettable])
726       [tmp setValue:tabkey inComponent:[_ctx component]];
727     
728     result = [self->template invokeActionForRequest:_rq inContext:_ctx];
729   }
730   else {
731     [[_ctx component] debugWithFormat:@"WEPageItem: invalid invoke state"];
732     result = [self->template invokeActionForRequest:_rq inContext:_ctx];
733   }
734   
735   [_ctx deleteLastElementIDComponent];
736   return result;
737 }
738
739 - (void)appendHead:(NSString *)tmp 
740   toResponse:(WOResponse *)_response inContext:(WOContext *)_ctx 
741 {
742   NSMutableArray *keys;
743   WEPageItemInfo *info;
744   WOComponent    *cmp;
745   NSString *k;
746   
747   if (![tmp isEqual:WEPageView_COLLECT])
748     return;
749   
750   /* collect keys */
751
752   cmp  = [_ctx component];
753   keys = [_ctx objectForKey:WEPageView_KEYS];
754   if (keys == nil) {
755     keys = [NSMutableArray arrayWithCapacity:8];
756     [_ctx setObject:keys forKey:WEPageView_KEYS];
757   }
758   
759   if ((k = [self->key stringValueInComponent:[_ctx component]]) == nil) {
760     /* auto-assign a key */
761     char kb[16];
762     sprintf(kb, "%d", [keys count]);
763     k = [NSString stringWithCString:kb];
764   }
765   [_ctx appendElementIDComponent:k];
766       
767   info = [[WEPageItemInfo alloc] init];
768   info->key      = [k copy];
769   info->title    = [[self->title stringValueInComponent:cmp] copy];
770   info->uri      = [[_ctx componentActionURL] copy];
771       
772   [keys addObject:info];
773   [info release];
774   
775   [_ctx deleteLastElementIDComponent];
776 }
777
778 - (void)appendBody:(NSString *)tmp 
779   toResponse:(WOResponse *)_response inContext:(WOContext *)_ctx 
780 {
781   NSString *k;
782   
783   k = [self->key stringValueInComponent:[_ctx component]];
784   if (![tmp isEqualToString:k])
785     return;
786   
787   /* content is active or used as layer*/
788   [_ctx appendElementIDComponent:k];
789 #if DEBUG
790   [self debugWithFormat:@"PAGE: %@", k];
791 #endif
792   [self->template appendToResponse:_response inContext:_ctx];
793   [_ctx deleteLastElementIDComponent];
794 }
795
796 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
797   NSString *tmp;
798   
799   if ((tmp = [_ctx objectForKey:WEPageView_HEAD]))
800     [self appendHead:tmp toResponse:_response inContext:_ctx];
801   else if ((tmp = [_ctx objectForKey:WEPageView_BODY]))
802     [self appendBody:tmp toResponse:_response inContext:_ctx];
803   else
804     [_response appendContentString:@"[invalid pageview state]"];
805 }
806
807 @end /* WEPageItem */
808
809 @implementation WEPageItemInfo
810
811 - (void)dealloc {
812   [self->uri   release];
813   [self->title release];
814   [self->key   release];
815   [super dealloc];
816 }
817
818 - (NSString *)key {
819   return self->key;
820 }
821 - (NSString *)title {
822   return self->title;
823 }
824 - (NSString *)uri {
825   return self->uri;
826 }
827
828 @end /* WEPageItemInfo */