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