]> err.no Git - sope/blob - sope-appserver/WEExtensions/WEBrowser.m
added strict OSX bundle dependencies
[sope] / sope-appserver / WEExtensions / WEBrowser.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
24 /*
25   WEBrowser
26   
27   TODO: document
28
29   API:
30     - extra attributes are attributes of the <table> tag
31 */
32
33 @interface WEBrowser : WODynamicElement
34 {
35 @protected
36   WOAssociation *list;
37   WOAssociation *item;
38   WOAssociation *sublist;
39   WOAssociation *currentPath;
40   
41   // config
42   WOAssociation *bgColor;
43   WOAssociation *height;
44   WOAssociation *columnWidth;
45   
46   WOElement     *template;
47 }
48 @end
49
50 #include <NGObjWeb/WEClientCapabilities.h>
51 #include "common.h"
52
53 static NSString *WEBrowser_Plus    = @"WEBrowser_Plus";
54 static NSString *WEBrowser_Minus   = @"WEBrowser_Minus";
55
56 @implementation WEBrowser
57
58 #if 0
59 static NSString *retStrForInt(int i) {
60   return [[NSString alloc] initWithFormat:@"%i", i];
61 }
62 #endif
63
64 static inline void
65 _applyPath(WEBrowser *self, NSArray *path, WOComponent *cmp) {
66   [self->currentPath setValue:path              inComponent:cmp];
67   [self->item        setValue:[path lastObject] inComponent:cmp];
68 }
69
70 static inline void
71 _applyPathAppenedByItem(WEBrowser *self, NSArray *path, id obj, id cmp) {
72   [self->currentPath setValue:[path arrayByAddingObject:obj] inComponent:cmp];
73   [self->item        setValue:obj                            inComponent:cmp];
74 }
75
76 - (id)initWithName:(NSString *)_name
77   associations:(NSDictionary *)_config
78   template:(WOElement *)_root
79 {
80   if ((self=[super initWithName:_name associations:_config template:_root])) {
81     self->list        = WOExtGetProperty(_config, @"list");
82     self->item        = WOExtGetProperty(_config, @"item");
83     self->sublist     = WOExtGetProperty(_config, @"sublist");
84     self->currentPath = WOExtGetProperty(_config, @"currentPath");
85
86     // config
87     self->bgColor     = WOExtGetProperty(_config, @"bgColor");
88     self->height      = WOExtGetProperty(_config, @"height");
89     self->columnWidth = WOExtGetProperty(_config, @"columnWidth");
90
91     self->template  = [_root retain];
92   }
93   return self;
94 }
95
96 - (void)dealloc {
97   [self->list        release];
98   [self->item        release];
99   [self->sublist     release];
100   [self->currentPath release];
101
102   [self->columnWidth release];
103   [self->bgColor     release];
104   [self->height      release];
105   
106   [self->template release];
107   [super dealloc];
108 }
109
110 - (void)takeValuesFromRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
111 #if 0
112   [self->template takeValuesFromRequest:_req inContext:_ctx];
113 #endif
114 }
115
116 - (id)invokeActionForRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
117   WOComponent    *cmp;
118   NSMutableArray *stack;
119   NSArray        *selects;
120   NSArray        *subarray;
121   NSString       *cid;
122   id             result;
123   id             obj = nil;
124   int            col, row;
125   BOOL           isScroll = NO;
126
127   cmp     = [_ctx component];
128   selects = [self->currentPath valueInComponent:cmp];
129
130   cid = [_ctx currentElementID];
131   
132   if ([cid isEqualToString:@"scroll"]) {
133     isScroll = YES;
134
135     [_ctx appendElementIDComponent:cid];   // append scroll
136     cid = [_ctx consumeElementID];         // get currentPath index (=column)
137     col = [cid intValue];                  //
138     [_ctx appendElementIDComponent:cid];   // append column
139     cid = [_ctx consumeElementID];         // get row
140     row = [cid intValue];
141     [_ctx appendElementIDComponent:cid];   // append row
142     cid = [_ctx consumeElementID];         // get plus or minus action or else
143   }
144   else {
145     cid = [_ctx currentElementID];         // get row
146     row = [cid  intValue];
147     [_ctx appendElementIDComponent:cid];   // append row
148     cid = [_ctx consumeElementID];         // get currentPath index (=column)
149     col = [cid intValue];
150     [_ctx appendElementIDComponent:cid];   // append index
151     cid = [_ctx consumeElementID];         // get plus or minus action or else
152   }
153
154   stack = [NSMutableArray arrayWithArray:
155                           [selects subarrayWithRange:NSMakeRange(0, col)]];
156
157   if ([cid isEqual:WEBrowser_Minus]) {
158     // last object of current path is the clicked one
159     if (col < (int)[selects count]) {
160       [self->currentPath setValue:stack              inComponent:cmp];
161       [self->item        setValue:[stack lastObject] inComponent:cmp];
162     }
163     result = nil;
164   }
165   else {
166
167     // prepare for getting the clicked object
168     if (col == 0)
169       subarray = [self->list valueInComponent:cmp];
170     else {
171       obj = [selects objectAtIndex:col-1];
172       [self->item        setValue:obj   inComponent:cmp];
173       [self->currentPath setValue:stack inComponent:cmp];
174       
175       subarray = [self->sublist valueInComponent:cmp];
176     }
177     // get clicked object and update currentPath (=stack)
178     if (row < (int)[subarray count]) {
179       obj = [subarray objectAtIndex:row];
180       [stack addObject:obj];
181     }
182     
183     [self->currentPath setValue:stack inComponent:cmp];
184     [self->item        setValue:obj   inComponent:cmp];
185   
186     if ([cid isEqual:WEBrowser_Plus])
187       result = nil;
188     else
189       result = [self->template invokeActionForRequest:_req inContext:_ctx];
190   }
191   [_ctx deleteLastElementIDComponent];   // delete row
192   [_ctx deleteLastElementIDComponent];   // delete index
193
194   return result;
195 }
196
197 - (BOOL)_useScrollingInContext:(WOContext *)_ctx {
198   return [[[_ctx request] clientCapabilities] doesSupportCSSOverflow];
199 }
200
201 - (void)appendWithScrolling:(WOResponse *)_resp inContext:(WOContext *)_ctx {
202   WOComponent *cmp     = nil;
203   NSArray     *selects = nil;
204   NSArray     *path    = nil;
205   int         columns, cnt, i, j;
206   BOOL        useScrolling;
207
208   useScrolling = [self _useScrollingInContext:_ctx];
209   
210   cmp = [_ctx component];
211   
212   if ((selects = [[self->currentPath valueInComponent:cmp] copy]) == nil) {
213     selects = [[NSArray alloc] init];
214     [self->currentPath setValue:selects inComponent:cmp];
215   }
216   
217   columns   = [selects count] + 1;
218
219   [_resp appendContentString:@"<table"];
220   [self appendExtraAttributesToResponse:_resp inContext:_ctx];
221   [_resp appendContentString:@">"];
222   
223   [_resp appendContentString:@"<tr>"];
224   
225   [_ctx appendElementIDComponent:@"scroll"]; // scroll - mode
226   
227   [_ctx appendZeroElementIDComponent];
228   for (i=0; i < columns; i++) {
229     NSArray *array;
230     
231     path = [selects subarrayWithRange:NSMakeRange(0, i)];
232     _applyPath(self, path, cmp);
233     
234     array = (i == 0)
235       ? [self->list valueInComponent:cmp]
236       : [self->sublist valueInComponent:cmp];
237     cnt = [array count];
238       
239     if (cnt) {
240       [_resp appendContentString:@"<td valign=\"top\""];
241       if (self->columnWidth) {
242         [_resp appendContentString:@" width=\""];
243         [_resp appendContentHTMLAttributeValue:
244                  [self->columnWidth stringValueInComponent:cmp]];
245         [_resp appendContentString:@"\""];
246       }
247       [_resp appendContentCharacter:'>'];
248       if (self->height && useScrolling) {
249         [_resp appendContentString:@"<p style=\"width:100%; height="];
250         [_resp appendContentString:[self->height stringValueInComponent:cmp]];
251         [_resp appendContentString:@"; overflow-y: auto;\">"];
252       }
253       [_resp appendContentString:
254                @"<table width=\"100%\" border=\"0\" "
255                @"cellspacing=\"0\" cellpadding=\"2\">"];
256     
257       [_ctx appendZeroElementIDComponent];
258       for (j = 0; j < cnt; j++) {
259         NSString *bg = nil;
260         id       obj = nil;
261
262         obj = [array objectAtIndex:j];
263         _applyPathAppenedByItem(self, path, obj, cmp);
264         bg = [self->bgColor stringValueInComponent:cmp];
265         
266         [_resp appendContentString:@"<tr><td valign=\"center\""];
267         if (bgColor) {
268           [_resp appendContentString:@" bgcolor=\""];
269           [_resp appendContentHTMLAttributeValue:bg];
270           [_resp appendContentCharacter:'"'];
271         }
272         [_resp appendContentString:@">"];
273         
274 #if 0
275         // named ankers for 'jump-to' functionality
276         [_resp appendContentString:@"<a name=\""];
277         s = retStrForInt(j);
278         [_resp appendContentString:s];
279         [s release];
280         [_resp appendContentString:@"\">"];
281         s = retStrForInt(j);
282         [_resp appendContentString:s];
283         [s release];
284         [_resp appendContentString:@"</a>"];
285 #endif
286         
287         [self->template appendToResponse:_resp inContext:_ctx];
288
289         [_resp appendContentString:@"</td></tr>"];
290         [_ctx incrementLastElementIDComponent];
291       }
292       [_ctx deleteLastElementIDComponent];
293
294       [_resp appendContentString:@"</table>"];
295       if (self->height && useScrolling)
296         [_resp appendContentString:@"</p>"];
297       [_resp appendContentString:@"</td>"];
298     }
299     [_ctx incrementLastElementIDComponent];
300   }
301   [_ctx deleteLastElementIDComponent];
302   [_resp appendContentString:@"</tr></table>"];
303
304   [_ctx deleteLastElementIDComponent]; // delete scroll-mode
305   
306   [self->currentPath setValue:selects inComponent:cmp];
307   
308   [selects release]; selects = nil;
309 }
310
311 - (void)appendWithoutScrolling:(WOResponse *)_resp inContext:(WOContext *)_ctx {
312   WOComponent    *cmp     = nil;
313   NSArray        *selects = nil;
314   NSArray        *array   = nil;
315   NSMutableArray *path    = nil;
316   NSString       *bg      = nil;
317   int            selectCnt, cnt, i, j;
318   
319   NSAssert(self->currentPath,
320            @"WEBrowser: missing 'currentPath' association!");
321   
322   cmp       = [_ctx component];
323   array     = [self->list valueInComponent:cmp];
324   selects   = [[self->currentPath valueInComponent:cmp] copy];
325
326   cnt       = [array count];
327   selectCnt = [selects count] + 1;
328   path      = [NSMutableArray arrayWithCapacity:selectCnt];
329   
330   [_resp appendContentString:@"<table"];
331   [self appendExtraAttributesToResponse:_resp inContext:_ctx];
332   [_resp appendContentString:@">"];
333   
334   [_ctx appendZeroElementIDComponent];
335   for (i = 0; i < cnt; i++) {
336     [_resp appendContentString:@"<tr>"];
337     
338     [_ctx appendZeroElementIDComponent];
339     
340     for (j = 0; j < selectCnt; j++) {
341       NSArray *subarray;
342       int     subCount;
343
344       [path removeAllObjects];
345       [path addObjectsFromArray:
346             [selects subarrayWithRange:NSMakeRange(0, j)]];
347
348       // get subarray
349       if (j == 0) {
350         subarray = array;
351       }
352       else {
353         id obj;
354         obj = [selects objectAtIndex:j-1];
355         [self->item        setValue:obj  inComponent:cmp];
356         [self->currentPath setValue:path inComponent:cmp];
357         subarray = [self->sublist valueInComponent:cmp];
358       }
359       // update cnt
360       subCount = [subarray count];
361       cnt      = (subCount > cnt) ? subCount : cnt;
362
363       // append template
364       if (subCount > i) {
365         NSString *k;
366         id       obj;
367
368         obj = [subarray objectAtIndex:i];
369         [self->item        setValue:obj  inComponent:cmp];
370         // [self->currentPath setValue:path inComponent:cmp];
371
372         // is current object in currentPath?
373         
374         if ((j < (int)[selects count]) && 
375             [[selects objectAtIndex:j] isEqual:obj]) {
376           k = (j < selectCnt-1)
377             ? WEBrowser_Minus
378             : WEBrowser_Plus;
379         }
380         else
381           k = WEBrowser_Plus;
382         
383         bg = [self->bgColor stringValueInComponent:cmp];
384
385         WEAppendTD(_resp, nil, nil, bg);
386         [path addObject:obj];
387         [self->currentPath setValue:path inComponent:cmp];
388         [self->template appendToResponse:_resp inContext:_ctx];
389         [_resp appendContentString:@"</td>"];
390       }
391       else {
392         [_resp appendContentString:@"<td colspan=\"2\""];
393         [self->currentPath setValue:nil inComponent:cmp];
394         [self->item        setValue:nil inComponent:cmp];
395         bg = [self->bgColor stringValueInComponent:cmp];
396         if (bg) {
397           [_resp appendContentString:@" bgcolor=\""];
398           [_resp appendContentString:bg];
399         }
400         [_resp appendContentString:@"\">&nbsp;</td>"];
401       }
402       [_ctx incrementLastElementIDComponent];
403     }
404     [_ctx deleteLastElementIDComponent];
405     [_resp appendContentString:@"</tr>"];
406     [_ctx incrementLastElementIDComponent];
407   }
408   [_ctx deleteLastElementIDComponent];
409   [_resp appendContentString:@"</table>"];
410
411   [self->currentPath setValue:selects inComponent:cmp];
412   [selects release];
413 }
414
415 - (void)appendToResponse:(WOResponse *)_resp inContext:(WOContext *)_ctx {
416 #if 1
417   [self appendWithScrolling:_resp inContext:_ctx];
418 #else
419   if ([self _useScrollingInContext:_ctx])
420     [self appendWithScrolling:_resp inContext:_ctx];
421   else
422     [self appendWithoutScrolling:_resp inContext:_ctx];
423 #endif
424 }
425
426 @end /* WEBrowser */