2 Copyright (C) 2000-2005 SKYRIX Software AG
4 This file is part of SOPE.
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
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.
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
25 A WETreeView is very similiar to a WETableView (eg it can have arbitary
26 columns), but can also show/manage a tree of objects.
28 TODO: we should support a cookie to store the tree hiearchy for stateless
29 servers like the Zope tree. This probably needs to be implemented in
30 takeValues (cookie decoding) and appendToResponse (cookie encoding).
31 Zooming (invokeAction) needs to ignore? the component-id in case the
32 cookie is set and somehow work based on cookie data.
34 TODO: we need to support CSS.
36 WETreeView associations:
37 list & sublist & (item | index | currentPath)
57 WETreeHeader associations:
64 WETreeData associations:
75 TestTree: WETreeView {
79 sublist = item.sublist;
80 zoom = treeState.isExpanded; // take a look at LSWTreeState !!!
81 // if you leave out *zoom*, the tree is rendered full expanded
82 // and without plus and minus icons
84 // if no icons are specified, the tree replaces these icons with
85 // ascii characters (that style is supposed to be ugly :-)
88 iconWidth = "13"; // every icon's width should be equal to "13"
89 plusIcon = "plus.gif";
90 minusIcon = "minus.gif";
91 leafIcon = "leaf.gif";
92 junctionIcon = "junction.gif";
93 cornerIcon = "corner.gif";
94 cornerPlusIcon = "corner_plus.gif";
95 cornerMinusIcon = "corner_miunus.gif";
96 leafCornerIcon = "leaf_corner.gif";
97 lineIcon = "line.gif";
99 TreeDataCell: WETreeData {
100 isTreeElement = YES; // this is a tree cell (that means, it has plus
101 // and minus icons and all that stuff)
103 DataCell: WETreeData {
104 isTreeElement = NO; // this is NOT a tree cell, i.e. it does NOT
105 // have any plus or minus icons. (This is just a
106 // ordinary <td></td>!!!)
109 TreeHeaderCell: WETreeHeader {
112 HeaderCell: WETreeHeader {
121 <!--- tree header --->
122 <#TreeHeaderCell>some title</#TreeHeaderCell>
123 <#HeaderCell">some title</#HeaderCell>
124 <#HeaderCell">some title</#HeaderCell>
126 <!-- tree content -->
128 <#TreeDataCell">some content</#TreeDataCell>
129 <#DataCell">some content</#DataCell>
130 <#DataCell">some content</#DataCell>
136 <var:treeview list="root" sublist="item.sublist" item="item"
137 currentPath="currentPath" zoom="isZoom"
140 <var:tree-header const:isTreeElement="YES">treecell</var:tree-header>
141 <var:tree-header const:isTreeElement="NO" const:bgcolor="#FFDAAA">
148 #include <NGObjWeb/WODynamicElement.h>
150 @class NSMutableArray;
152 @interface WETreeView : WODynamicElement
154 // WODynamicElement: extraAttributes
155 // WODynamicElement: otherTagString
157 WOAssociation *list; // array of objects to iterate through
158 WOAssociation *item; // current item in the array
159 WOAssociation *sublist; // sub list of item
160 WOAssociation *itemIsLeaf; // hh-optimization
161 WOAssociation *index; // current index
162 WOAssociation *zoom; // show sub list of item (BOOL)
163 WOAssociation *currentPath; //
164 WOAssociation *showItem; // show current item
166 WOAssociation *noTable; // render no TABLE (BOOL)
169 WOAssociation *plusIcon;
170 WOAssociation *minusIcon;
171 WOAssociation *leafIcon;
172 WOAssociation *junctionIcon;
173 WOAssociation *cornerIcon;
174 WOAssociation *cornerPlusIcon;
175 WOAssociation *cornerMinusIcon;
176 WOAssociation *leafCornerIcon;
177 WOAssociation *lineIcon;
178 WOAssociation *spaceIcon;
179 WOAssociation *iconWidth;
182 NSMutableArray *matrix;
187 @end /* WETreeView */
189 #include "WETreeContextKeys.h"
190 #include "WETreeMatrixElement.h"
192 #include <NGObjWeb/WEClientCapabilities.h>
194 NSString *WETreeView_HEADER_MODE = @"WETreeView_HEADER_MODE";
195 NSString *WETreeView_ZOOM_ACTION_ID = @"_";
197 NSString *WETreeView_TreeElement = @"WETreeView_TreeElement";
198 NSString *WETreeView_RenderNoTable = @"WETreeView_RenderNoTable";
200 NSString *WETreeView_IconWidth = @"WETreeView_IconWidth";
201 NSString *WETreeView_Plus = @"WETreeView_Plus";
202 NSString *WETreeView_Minus = @"WETreeView_Minus";
203 NSString *WETreeView_Leaf = @"WETreeView_Leaf";
204 NSString *WETreeView_Line = @"WETreeView_Line";
205 NSString *WETreeView_Junction = @"WETreeView_Junction";
206 NSString *WETreeView_Corner = @"WETreeView_Corner";
207 NSString *WETreeView_CornerPlus = @"WETreeView_CornerPlus";
208 NSString *WETreeView_CornerMinus = @"WETreeView_CornerMinus";
209 NSString *WETreeView_CornerLeaf = @"WETreeView_CornerLeaf";
210 NSString *WETreeView_Space = @"WETreeView_Space";
212 @implementation WETreeView
214 - (id)initWithName:(NSString *)_name
215 associations:(NSDictionary *)_config
216 template:(WOElement *)_t
218 if ((self = [super initWithName:_name associations:_config template:_t])) {
219 self->list = WOExtGetProperty(_config, @"list");
220 self->item = WOExtGetProperty(_config, @"item");
221 self->index = WOExtGetProperty(_config, @"index");
222 self->sublist = WOExtGetProperty(_config, @"sublist");
223 self->itemIsLeaf = WOExtGetProperty(_config, @"itemIsLeaf");
224 self->zoom = WOExtGetProperty(_config, @"zoom");
225 self->currentPath = WOExtGetProperty(_config, @"currentPath");
226 self->showItem = WOExtGetProperty(_config, @"showItem");
228 self->noTable = WOExtGetProperty(_config, @"noTable");
231 self->plusIcon = WOExtGetProperty(_config, @"plusIcon");
232 self->minusIcon = WOExtGetProperty(_config, @"minusIcon");
233 self->leafIcon = WOExtGetProperty(_config, @"leafIcon");
234 self->junctionIcon = WOExtGetProperty(_config, @"junctionIcon");
235 self->cornerIcon = WOExtGetProperty(_config, @"cornerIcon");
236 self->cornerPlusIcon = WOExtGetProperty(_config, @"cornerPlusIcon");
237 self->cornerMinusIcon = WOExtGetProperty(_config, @"cornerMinusIcon");
238 self->leafCornerIcon = WOExtGetProperty(_config, @"leafCornerIcon");
239 self->lineIcon = WOExtGetProperty(_config, @"lineIcon");
240 self->spaceIcon = WOExtGetProperty(_config, @"spaceIcon");
241 self->iconWidth = WOExtGetProperty(_config, @"iconWidth");
243 self->template = [_t retain];
249 [self->itemIsLeaf release];
250 [self->sublist release];
251 [self->list release];
252 [self->item release];
253 [self->index release];
254 [self->zoom release];
255 [self->currentPath release];
256 [self->showItem release];
258 [self->noTable release];
260 [self->plusIcon release];
261 [self->minusIcon release];
262 [self->leafIcon release];
263 [self->junctionIcon release];
264 [self->cornerIcon release];
265 [self->cornerPlusIcon release];
266 [self->cornerMinusIcon release];
267 [self->leafCornerIcon release];
268 [self->lineIcon release];
269 [self->spaceIcon release];
270 [self->iconWidth release];
272 [self->template release];
273 [self->matrix release];
278 - (void)updateConfigInContext:(WOContext *)_ctx {
282 cmp = [_ctx component];
284 // TODO: replace the macro with methods?
285 #define SetConfigInContext(_a_, _key_) \
286 if (_a_ && (tmp = [_a_ valueInComponent:cmp])) \
287 [_ctx setObject:tmp forKey:_key_]; \
289 SetConfigInContext(self->plusIcon, WETreeView_Plus);
290 SetConfigInContext(self->minusIcon, WETreeView_Minus);
291 SetConfigInContext(self->leafIcon, WETreeView_Leaf);
292 SetConfigInContext(self->junctionIcon, WETreeView_Junction);
293 SetConfigInContext(self->cornerIcon, WETreeView_Corner);
294 SetConfigInContext(self->cornerPlusIcon, WETreeView_CornerPlus);
295 SetConfigInContext(self->cornerMinusIcon, WETreeView_CornerMinus);
296 SetConfigInContext(self->leafCornerIcon, WETreeView_CornerLeaf);
297 SetConfigInContext(self->lineIcon, WETreeView_Line);
298 SetConfigInContext(self->spaceIcon, WETreeView_Space);
299 SetConfigInContext(self->iconWidth, WETreeView_IconWidth);
301 #undef SetConfigInContext
304 - (void)removeConfigInContext:(WOContext *)_ctx {
305 [_ctx removeObjectForKey:WETreeView_Plus];
306 [_ctx removeObjectForKey:WETreeView_Minus];
307 [_ctx removeObjectForKey:WETreeView_Leaf];
308 [_ctx removeObjectForKey:WETreeView_Junction];
309 [_ctx removeObjectForKey:WETreeView_Corner];
310 [_ctx removeObjectForKey:WETreeView_CornerPlus];
311 [_ctx removeObjectForKey:WETreeView_CornerMinus];
312 [_ctx removeObjectForKey:WETreeView_CornerLeaf];
313 [_ctx removeObjectForKey:WETreeView_Line];
314 [_ctx removeObjectForKey:WETreeView_Space];
315 [_ctx removeObjectForKey:WETreeView_IconWidth];
318 - (id)_toggleZoomInContext:(WOContext *)_ctx {
319 WOComponent *component = [_ctx component];
321 if ([self->zoom isValueSettable]) {
324 isZoom = [self->zoom boolValueInComponent:component];
325 [self->zoom setBoolValue:!isZoom inComponent:component];
332 - (NSArray *)_sublistInContext:(WOContext *)_ctx {
335 if (self->sublist == nil)
337 if (self->itemIsLeaf) {
338 if ([self->itemIsLeaf boolValueInComponent:[_ctx component]])
342 if ((a = [self->sublist valueInComponent:[_ctx component]]) == nil)
345 return ([a count] > 0) ? a : nil;
348 - (void)_takeValuesFromRequest:(WORequest *)_req
349 inContext:(WOContext *)_ctx
350 withArray:(NSArray *)array
358 if (!(_depth <= MAX_TREE_DEPTH-1)) {
359 NSLog(@"ERROR[%s]: WETreeView takeValuesFromRequest: max."
360 @"recursion depth is %d",
361 __PRETTY_FUNCTION__, MAX_TREE_DEPTH-1);
365 NSAssert1((_depth <= MAX_TREE_DEPTH-1),
366 @"WETreeView takeValuesFromRequest: max. recursion depth is %d",
371 cmp = [_ctx component];
374 [_ctx appendZeroElementIDComponent]; // append index
375 for (i = 0; i < cnt; i++) {
378 if ([self->index isValueSettable])
379 [self->index setUnsignedIntValue:i inComponent:cmp];
380 if ([self->item isValueSettable])
381 [self->item setValue:[array objectAtIndex:i] inComponent:cmp];
383 if (self->showItem && ![self->showItem boolValueInComponent:cmp])
386 if (self->zoom == nil || [self->zoom boolValueInComponent:cmp])
387 subArray = [self _sublistInContext:_ctx];
392 [self _takeValuesFromRequest:_req
397 [self->template takeValuesFromRequest:_req inContext:_ctx];
399 [_ctx incrementLastElementIDComponent];
401 [_ctx deleteLastElementIDComponent]; // delete index
404 - (void)takeValuesFromRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
407 array = [self->list valueInComponent:[_ctx component]];
408 [self _takeValuesFromRequest:_req inContext:_ctx withArray:array depth:0];
411 - (id)invokeActionForRequest:(WORequest *)_rq inContext:(WOContext *)_ctx {
412 WOComponent *sComponent;
416 NSMutableArray *stack = nil;
418 unsigned idCount = 0;
420 sComponent = [_ctx component];
421 array = [self->list valueInComponent:sComponent];
422 if ([array count] < 1) return nil;
424 stack = [NSMutableArray arrayWithCapacity:8];
426 idxId = [_ctx currentElementID]; // top level index
429 if ([idxId isEqualToString:@"h"]) {
430 [_ctx setObject:@"YES" forKey:WETreeView_HEADER_MODE];
431 [_ctx appendElementIDComponent:@"h"];
432 [_ctx consumeElementID];
433 result = [self->template invokeActionForRequest:_rq inContext:_ctx];
434 [_ctx deleteLastElementIDComponent];
435 [_ctx removeObjectForKey:WETreeView_HEADER_MODE];
439 while ((![idxId isEqualToString:@"end"]) && (idxId != nil) &&
441 unsigned idx = [idxId unsignedIntValue];
443 object = [array objectAtIndex:idx];
444 [stack addObject:object];
446 if ([self->index isValueSettable])
447 [self->index setUnsignedIntValue:idx inComponent:sComponent];
448 if ([self->item isValueSettable])
449 [self->item setValue:object inComponent:sComponent];
450 if ([self->currentPath isValueSettable])
451 [self->currentPath setValue:stack inComponent:sComponent];
453 array = [self->sublist valueInComponent:sComponent];
455 [_ctx appendElementIDComponent:idxId]; idCount++;
456 idxId = [_ctx consumeElementID]; // sub level index
458 if ([idxId isEqualToString:@"end"]) {
459 [_ctx appendElementIDComponent:idxId]; idCount++;
460 idxId = [_ctx consumeElementID];
463 result = ([[_ctx senderID] hasSuffix:WETreeView_ZOOM_ACTION_ID])
464 ? [self _toggleZoomInContext:_ctx]
465 : [self->template invokeActionForRequest:_rq inContext:_ctx];
467 /* remove element-ids */
468 for (; idCount > 0; idCount--)
469 [_ctx deleteLastElementIDComponent];
474 - (void)appendList:(NSArray *)_list
475 treeElement:(_WETreeMatrixElement *)_element
476 inContext:(WOContext *)_ctx
478 /* TODO: split up this method! */
482 comp = [_ctx component];
487 if (!([_element depth] <= MAX_TREE_DEPTH-1)) {
488 NSLog(@"ERROR[%s]: WETreeView takeValuesFromRequest: max."
489 @"recursion depth is %d",
490 __PRETTY_FUNCTION__, MAX_TREE_DEPTH-1);
494 NSAssert1(([_element depth] <= MAX_TREE_DEPTH-1),
495 @"WETreeView appendToResponse: max. recursion depth is %d",
500 for (i = 0; i < cnt; i++) {
501 id object = [_list objectAtIndex:i];
503 [_element setIndex:i];
504 [_element setItem:object];
506 if ([self->index isValueSettable])
507 [self->index setUnsignedIntValue:i inComponent:comp];
508 if ([self->item isValueSettable])
509 [self->item setValue:object inComponent:comp];
510 if ([self->currentPath isValueSettable])
511 [self->currentPath setValue:[_element currentPath] inComponent:comp];
513 if (self->showItem && ![self->showItem boolValueInComponent:comp])
522 if (self->itemIsLeaf) {
523 isLeaf = [self->itemIsLeaf boolValueInComponent:comp];
524 isZoom = (self->zoom)
525 ? [self->zoom boolValueInComponent:comp]
528 sl = (!isLeaf && isZoom)
529 ? [self _sublistInContext:_ctx]
533 sl = [self _sublistInContext:_ctx];
534 isLeaf = !([sl count] > 0);
537 if (self->showItem) {
545 for (k = (i + 1); k < cnt; k++) {
546 obj = [_list objectAtIndex:k];
548 [_element setIndex:k];
549 [_element setItem:obj];
550 if ([self->index isValueSettable])
551 [self->index setUnsignedIntValue:k inComponent:comp];
552 if ([self->item isValueSettable])
553 [self->item setValue:obj inComponent:comp];
554 if ([self->currentPath isValueSettable])
555 [self->currentPath setValue:[_element currentPath]
558 if ([self->showItem boolValueInComponent:comp]) {
563 [_element setIndex:i];
564 [_element setItem:object];
566 if ([self->index isValueSettable])
567 [self->index setUnsignedIntValue:i inComponent:comp];
568 if ([self->item isValueSettable])
569 [self->item setValue:object inComponent:comp];
570 if ([self->currentPath isValueSettable])
571 [self->currentPath setValue:[_element currentPath]
576 isLast = (i == (cnt-1));
578 if (!isLeaf) { // not a leaf
579 _WETreeMatrixElement *newElement;
581 if (self->zoom == nil) {
582 [_element setElement:(isLast)
584 : WETreeView_Junction];
587 isZoom = [self->zoom boolValueInComponent:comp];
589 [_element setElement:(isLast)
590 ? ([sl count]) ? WETreeView_CornerMinus : WETreeView_Corner
591 : ([sl count]) ? WETreeView_Minus : WETreeView_Junction];
594 [_element setElement:(isLast)
595 ? WETreeView_CornerPlus
598 [_element setLeaf:(isZoom && [sl count])
599 ? WETreeView_CornerLeaf
602 newElement = [[_WETreeMatrixElement alloc] initWithElement:_element];
604 [self->matrix addObject:newElement];
605 [newElement release]; newElement = nil;
608 [_element setElement:(isLast)
612 newElement = [[_WETreeMatrixElement alloc] initWithElement:_element];
614 [self appendList:sl treeElement:newElement inContext:_ctx];
615 [newElement release]; newElement = nil;
619 _WETreeMatrixElement *newElement;
621 [_element setElement: (isLast)
623 : WETreeView_Junction];
625 newElement = [[_WETreeMatrixElement alloc] initWithElement:_element];
627 [newElement setLeaf:WETreeView_Leaf];
629 [self->matrix addObject:newElement];
630 [newElement release]; newElement = nil;
636 - (void)_calcMatrixInContext:(WOContext *)_ctx depth:(int *)_depth {
637 _WETreeMatrixElement *treeElm = nil;
639 int i, cnt, d, maxDepth = 0;
641 top = [self->list valueInComponent:[_ctx component]];
643 [self->matrix release]; self->matrix = nil;
644 self->matrix = [[NSMutableArray allocWithZone:[self zone]]
645 initWithCapacity:64];
647 treeElm = [[_WETreeMatrixElement alloc] init];
649 [self appendList:top treeElement:treeElm inContext:_ctx];
651 [treeElm release]; treeElm = nil;
653 cnt = [self->matrix count];
656 for (i = 0; i < cnt; i++) {
657 d = [[self->matrix objectAtIndex:i] depth];
658 maxDepth = (maxDepth < d) ? d : maxDepth;
662 for (i = 0; i < cnt; i++) {
663 _WETreeMatrixElement *element;
665 element = [self->matrix objectAtIndex:i];
666 [element setColspan:maxDepth-[element depth]+1];
668 *_depth = maxDepth + 2;
671 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
676 [self updateConfigInContext:_ctx];
678 comp = [_ctx component];
680 /* check for browser */
681 if (self->noTable == nil) {
682 WEClientCapabilities *ccaps;
684 ccaps = [[_ctx request] clientCapabilities];
685 doTable = [ccaps isFastTableBrowser];
688 doTable = ![self->noTable boolValueInComponent:comp];
691 [_ctx setObject:@"1" forKey:WETreeView_RenderNoTable];
694 [_response appendContentString:
695 @"<table border='0' cellspacing='0' cellpadding='0'"];
697 [self appendExtraAttributesToResponse:_response inContext:_ctx];
698 if (self->otherTagString) {
699 [_response appendContentCharacter:' '];
700 [_response appendContentString:
701 [self->otherTagString stringValueInComponent:
704 [_response appendContentCharacter:'>'];
707 [self _calcMatrixInContext:_ctx depth:&depth];
709 /* append table title */
711 [_response appendContentString:@"<tr>"];
713 [_ctx setObject:[NSNumber numberWithInt:depth]
714 forKey:WETreeView_HEADER_MODE];
715 [_ctx appendElementIDComponent:@"h"];
716 [self->template appendToResponse:_response inContext:_ctx];
717 [_ctx deleteLastElementIDComponent];
718 [_ctx removeObjectForKey:WETreeView_HEADER_MODE];
720 [_response appendContentString:@"</tr>"];
721 else if (_ctx->wcFlags.xmlStyleEmptyElements)
722 [_response appendContentString:@"<br />"];
724 [_response appendContentString:@"<br>"];
726 cnt = [self->matrix count];
728 for (i = 0; i < cnt; i++) {
729 _WETreeMatrixElement *element;
731 element = [self->matrix objectAtIndex:i];
733 if ([self->index isValueSettable])
734 [self->index setUnsignedIntValue:[element index]
737 if ([self->item isValueSettable])
738 [self->item setValue:[element item]
741 if ([self->currentPath isValueSettable])
742 [self->currentPath setValue:[element currentPath]
745 [_ctx setObject:element forKey:WETreeView_TreeElement];
747 [_ctx appendElementIDComponent:[element elementID]];
749 [_ctx appendElementIDComponent:@"end"];
751 [_response appendContentString:@"<tr>"];
752 [self->template appendToResponse:_response inContext:_ctx];
754 [_response appendContentString:@"</tr>"];
755 else if (_ctx->wcFlags.xmlStyleEmptyElements)
756 [_response appendContentString:@"<br />"];
758 [_response appendContentString:@"<br>"];
760 [_ctx deleteLastElementIDComponent]; // delete "end"
762 [_ctx deleteLastElementIDComponent]; // delete eids
764 [_ctx removeObjectForKey:WETreeView_TreeElement];
767 [_response appendContentString:@"</table>"];
769 [self removeConfigInContext:_ctx];
770 [_ctx removeObjectForKey:WETreeView_RenderNoTable];
771 [self->matrix release]; self->matrix = nil;
774 @end /* WETreeView */