]> err.no Git - sope/blob - sope-appserver/WEExtensions/WECollapsibleComponentContent.m
Ported "fragmentID" stuff from JOPE to SOPE.
[sope] / sope-appserver / WEExtensions / WECollapsibleComponentContent.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 @class WOAssociation;
25
26 @interface WECollapsibleComponentContent : WODynamicElement
27 {
28 @protected
29   WOAssociation *condition;
30   WOAssociation *visibility;
31   
32   WOAssociation *allowScript; /* perform clicks on browser (use javaScript) */
33   
34   WOElement     *template;
35 }
36 @end
37
38 @interface WECollapsibleAction : WODynamicElement
39 {
40 @protected
41   WOAssociation *openedImageFileName;
42   WOAssociation *closedImageFileName;
43   WOAssociation *framework;
44   WOAssociation *openedLabel;
45   WOAssociation *closedLabel;
46   WOAssociation *submitActionName;
47   WOAssociation *action; // if submit button, use submitActionName instead
48   WOAssociation *fragmentIdentifier;
49   WOAssociation *isClicked;
50
51   WOElement *template;
52 }
53 @end
54
55 #include "WEContextConditional.h"
56 #include <NGObjWeb/WEClientCapabilities.h>
57 #include "common.h"
58
59 static NSString *WECollapsible_TitleMode   = @"WECollapsible_TitleMode";
60 static NSString *WECollapsible_ContentMode = @"WECollapsible_ContentMode";
61 static NSString *WECollapsible_IsCollapsed = @"WECollapsible_IsCollapsed";
62 static NSString *WECollapsible_ScriptId    = @"WECollapsible_ScriptId";
63 static NSString *WECollapsible_HasScript   = @"WECollapsible_HasScript";
64 static NSString *Yes                       = @"YES";
65 static NSString *No                        = @"NO";
66
67 @implementation WECollapsibleComponentContent
68
69 - (id)initWithName:(NSString *)_name
70   associations:(NSDictionary *)_config
71   template:(WOElement *)_tmp
72 {
73   if ((self = [super initWithName:_name associations:_config template:_tmp])) {
74     self->condition   = WOExtGetProperty(_config, @"condition");
75     self->visibility  = WOExtGetProperty(_config, @"visibility");
76     self->allowScript = WOExtGetProperty(_config, @"allowScript");
77
78     if (self->visibility == nil)
79       NSLog(@"WARNING: WECollapsibleComponent 'visibility' not set");
80
81     if (self->visibility && ![self->visibility isValueSettable])
82       NSLog(@"WARNING: WECollapsibleComponent 'visibility' is not settable");
83     
84     self->template = [_tmp retain];
85   }
86   return self;
87 }
88
89 - (void)dealloc {
90   [self->condition   release];
91   [self->visibility  release];
92   [self->allowScript release];
93   [self->template    release];
94   [super dealloc];
95 }
96
97 /* responder */
98
99 - (void)takeValuesFromRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
100   BOOL isCollapsed;
101
102   isCollapsed = ![self->visibility boolValueInComponent:[_ctx component]];
103
104   // content
105   if (!isCollapsed) {
106     [_ctx setObject:Yes forKey:WECollapsible_ContentMode];
107     [self->template takeValuesFromRequest:_req inContext:_ctx];
108     [_ctx removeObjectForKey:WECollapsible_ContentMode];
109   }
110
111   // title
112   [_ctx setObject:Yes forKey:WECollapsible_TitleMode];
113
114   [self->template takeValuesFromRequest:_req inContext:_ctx];
115
116   [_ctx removeObjectForKey:WECollapsible_TitleMode];
117 }
118
119 - (id)invokeActionForRequest:(WORequest *)_request inContext:(WOContext *)_ctx {
120   id   result = nil;
121   BOOL isCollapsed;
122
123   isCollapsed = ![self->visibility boolValueInComponent:[_ctx component]];
124   
125   // title
126   [_ctx setObject:Yes forKey:WECollapsible_TitleMode];
127   [_ctx setObject:Yes forKey:WECollapsible_ContentMode];
128   [_ctx setObject:(isCollapsed) ? Yes : No forKey:WECollapsible_IsCollapsed];
129
130   result = [self->template invokeActionForRequest:_request inContext:_ctx];
131   isCollapsed = [[_ctx objectForKey:WECollapsible_IsCollapsed] boolValue];
132
133   if ([self->visibility isValueSettable])
134     [self->visibility setBoolValue:!isCollapsed inComponent:[_ctx component]];
135
136   [_ctx removeObjectForKey:WECollapsible_IsCollapsed];
137   [_ctx removeObjectForKey:WECollapsible_ContentMode];
138   [_ctx removeObjectForKey:WECollapsible_TitleMode];
139   
140   return result;
141 }
142
143 - (void)appendToResponse:(WOResponse *)_resp inContext:(WOContext *)_ctx {
144   WOComponent *comp;
145   BOOL        isCollapsed;
146   BOOL        doScript;
147   NSString    *scriptId;
148
149   if ([_ctx isRenderingDisabled]) {
150     [self->template appendToResponse:_resp inContext:_ctx];
151     return;
152   }
153   
154   comp     = [_ctx component];
155   doScript = [self->allowScript boolValueInComponent:comp];
156   scriptId = [[[_ctx elementID] componentsSeparatedByString:@"."]
157                      componentsJoinedByString:@"_"];
158
159   if (doScript) {
160     WEClientCapabilities *ccaps;
161     
162     ccaps    = [[_ctx request] clientCapabilities];
163     doScript = [ccaps isInternetExplorer];
164   }
165
166   if (doScript)
167     [_ctx setObject:scriptId forKey:WECollapsible_ScriptId];
168   
169   if ([self->visibility valueInComponent:comp] == nil) {
170     isCollapsed = ![self->condition boolValueInComponent:comp];
171     if ([self->visibility isValueSettable])
172       [self->visibility setBoolValue:!isCollapsed inComponent:comp];
173   }
174   else
175     isCollapsed = ![self->visibility boolValueInComponent:comp];
176
177   // append title
178   [_ctx setObject:Yes forKey:WECollapsible_TitleMode];
179   [_ctx setObject:(isCollapsed) ? Yes : No forKey:WECollapsible_IsCollapsed];
180
181   [self->template appendToResponse:_resp inContext:_ctx];
182   
183   [_ctx removeObjectForKey:WECollapsible_IsCollapsed];
184   [_ctx removeObjectForKey:WECollapsible_TitleMode];
185   
186   // append content
187   if (!isCollapsed || doScript) {
188     [_ctx setObject:Yes forKey:WECollapsible_ContentMode];
189     if (doScript) {
190       [_resp appendContentString:@"<div class=\"collapsible\" "];
191       [_resp appendContentString:@"id=\"collapsible"];
192       [_resp appendContentString:scriptId];
193       [_resp appendContentString:@"\" style=\"display:"];
194       [_resp appendContentString:(isCollapsed) ? @"none" : @"block"];
195       [_resp appendContentString:@";\">"];
196     }
197     
198     [self->template appendToResponse:_resp inContext:_ctx];
199     
200     if (doScript)
201       [_resp appendContentString:@"</div>"];
202     [_ctx removeObjectForKey:WECollapsible_ContentMode];
203   }
204   [_ctx removeObjectForKey:WECollapsible_ScriptId];
205 }
206 @end /* WECollapsibleComponentContent */
207
208 @implementation WECollapsibleAction
209
210 - (id)initWithName:(NSString *)_name
211   associations:(NSDictionary *)_config
212   template:(WOElement *)_temp
213 {
214   if ((self = [super initWithName:_name associations:_config template:_temp])) {
215     self->openedImageFileName = WOExtGetProperty(_config, @"openedImageFileName");
216     self->closedImageFileName = WOExtGetProperty(_config, @"closedImageFileName");
217     self->framework           = WOExtGetProperty(_config, @"framework");
218     self->openedLabel         = WOExtGetProperty(_config, @"openedLabel");
219     self->closedLabel         = WOExtGetProperty(_config, @"closedLabel");
220     self->submitActionName    = WOExtGetProperty(_config, @"submitActionName");
221     self->action              = WOExtGetProperty(_config, @"action");
222     self->fragmentIdentifier  = WOExtGetProperty(_config, @"fragmentIdentifier");
223     self->isClicked           = WOExtGetProperty(_config, @"isClicked");
224
225     self->template = [_temp retain];
226   }
227   return self;
228 }
229
230 - (void)dealloc {
231   [self->openedImageFileName release];
232   [self->closedImageFileName release];
233   [self->framework           release];
234   [self->openedLabel         release];
235   [self->closedLabel         release];
236   [self->submitActionName    release];
237   [self->action              release];
238   [self->fragmentIdentifier  release];
239   [self->isClicked           release];
240   [self->template            release];
241   [super dealloc];
242 }
243
244 /* requests */
245
246 - (void)takeValuesFromRequest:(WORequest *)_request
247   inContext:(WOContext *)_ctx
248 {
249   NSString *eid;
250
251   eid = [_ctx elementID];
252
253   if ([_request formValueForKey:[eid stringByAppendingString:@".c.x"]]) {
254     [_ctx addActiveFormElement:self];
255     [_ctx setRequestSenderID:[[_ctx senderID] stringByAppendingString:@".c"]];
256   }
257   else if ([_request formValueForKey:[eid stringByAppendingString:@".e.x"]]) {
258     [_ctx addActiveFormElement:self];
259     [_ctx setRequestSenderID:[[_ctx senderID] stringByAppendingString:@".e"]];
260   }
261 }
262
263 - (id)invokeActionForRequest:(WORequest *)_request inContext:(WOContext *)_ctx {
264   NSString    *state;
265   BOOL        doForm;
266   
267   state = [[_ctx currentElementID] stringValue];
268   
269   doForm = ([_ctx isInForm] && self->submitActionName &&
270             ([self->submitActionName valueInComponent:[_ctx component]]));
271   
272   [_ctx consumeElementID]; // consume state-id
273     
274   if ([state isEqualToString:@"e"]) {
275     [_ctx setObject:[NSNumber numberWithBool:YES]
276           forKey:WECollapsible_IsCollapsed];
277     
278     if (doForm)
279       [self->submitActionName valueInComponent:[_ctx component]];
280     else if ([self->action valueInComponent:[_ctx component]] != nil)
281       [self->action valueInComponent:[_ctx component]];
282   }
283   else if ([state isEqualToString:@"c"]) {
284     [_ctx setObject:[NSNumber numberWithBool:NO]
285           forKey:WECollapsible_IsCollapsed];
286     
287     if (doForm)
288       [self->submitActionName valueInComponent:[_ctx component]];
289     else if ([self->action valueInComponent:[_ctx component]] != nil)
290       [self->action valueInComponent:[_ctx component]];
291   }
292   if ([self->isClicked isValueSettable])
293     [self->isClicked setBoolValue:YES inComponent:[_ctx component]];
294     
295   return nil; 
296 }
297
298 - (void)_appendScriptWithID:(NSString *)scriptId 
299   label:(NSString *)label imageURL:(NSString *)img
300   toResponse:(WOResponse *)_resp inContext:(WOContext *)_ctx 
301 {
302   WOComponent *comp;
303     NSString *openedImg  = nil;
304     NSString *closedImg  = nil;
305     NSString *openLabel  = nil;
306     NSString *closeLabel = nil;
307
308     comp       = [_ctx component];
309     closedImg  = [self->closedImageFileName stringValueInComponent:comp];
310     openedImg  = [self->openedImageFileName stringValueInComponent:comp];
311     openLabel  = [self->openedLabel         stringValueInComponent:comp];
312     closeLabel = [self->closedLabel         stringValueInComponent:comp];
313
314     closedImg = WEUriOfResource(closedImg, _ctx);
315     openedImg = WEUriOfResource(openedImg, _ctx);
316
317     if (![_ctx objectForKey:WECollapsible_HasScript]) {
318       [_resp appendContentString:
319            @"\n<script language=\"JavaScript\">\n"
320            @"<!--\n"
321            @"function toggleColl(el, img1, img2, alt1, alt2)\n"
322            @"{\n"
323            @"   whichEl = eval(\"collapsible\" + el);\n"
324            @"   whichIm = event.srcElement;\n"
325            @"   if (whichEl.style.display == \"none\") {\n"
326            @"           whichEl.style.display = \"block\";\n"
327            @"           whichIm.src = img1;\n"
328            @"       whichIm.alt = alt1;\n"
329            @"   }\n"
330            @"   else {\n"
331            @"           whichEl.style.display = \"none\";\n"
332            @"       whichIm.src = img2;\n"
333            @"       whichIm.alt = alt2;\n"
334            @"   }\n"
335            @"}\n"
336            @"//-->\n"
337            @"</script>\n"];
338       [_ctx setObject:Yes forKey:WECollapsible_HasScript];
339     }
340     
341     [_resp appendContentString:@"<a href=\"#\" onclick=\"toggleColl('"];
342     [_resp appendContentString:scriptId];
343     [_resp appendContentString:@"','"];
344     [_resp appendContentHTMLString:openedImg];
345     [_resp appendContentString:@"','"];
346     [_resp appendContentHTMLString:closedImg];
347     [_resp appendContentString:@"','"];
348     [_resp appendContentHTMLString:openLabel];
349     [_resp appendContentString:@"','"];
350     [_resp appendContentHTMLString:closeLabel];
351     [_resp appendContentString:@"'); return false\"><img "];
352     if (label) {
353       [_resp appendContentString:@"alt=\""];
354       [_resp appendContentString:label];
355       [_resp appendContentString:@"\" title=\""];
356       [_resp appendContentString:label];
357       [_resp appendContentString:@"\" "];
358     }
359     [_resp appendContentString:@"border=\"0\" name=\"imEx\" src=\""];
360     [_resp appendContentString:img];
361     [_resp appendContentString:@"\" /></a>"];
362 }
363
364 - (void)_appendLinkWithFragmentID:(NSString *)fragId
365   label:(NSString *)label imageURL:(NSString *)img
366   isCollapsed:(BOOL)isCollapsed
367   toResponse:(WOResponse *)_resp inContext:(WOContext *)_ctx 
368 {
369   [_resp appendContentString:@"<a href=\""];
370   [_resp appendContentString:[_ctx componentActionURL]];
371   if ([fragId isNotEmpty]) {
372     [_resp appendContentString:@"#"];
373     [_resp appendContentString:fragId];
374   }
375   [_resp appendContentString:@"\">"];
376   
377   if (img != nil) {
378     [_resp appendContentString:@"<img border=\"0\" src=\""];
379     [_resp appendContentString:img];
380     [_resp appendContentString:@"\""];
381     if (label) {
382       [_resp appendContentString:@" alt=\""];
383       [_resp appendContentString:label];
384       [_resp appendContentString:@"\" title=\""];
385       [_resp appendContentString:label];
386       [_resp appendContentString:@"\""];
387     }
388     [_resp appendContentString:@" />"];
389   }
390   else
391     [_resp appendContentString:(isCollapsed) ? @"[+]" : @"[-]"];
392     
393   [_resp appendContentString:@"</a>"];
394 }
395
396 - (void)appendToResponse:(WOResponse *)_resp inContext:(WOContext *)_ctx {
397   /* TODO: split up this huge method */
398   BOOL        isCollapsed;
399   BOOL        doForm;
400   BOOL        doScript;
401   WOComponent *comp;
402   NSString    *img      = nil;
403   NSString    *label    = nil;
404   NSString    *fragId   = nil;
405   NSString    *scriptId = nil;
406   
407   if ([_ctx isRenderingDisabled]) {
408     [self->template appendToResponse:_resp inContext:_ctx];
409     return;
410   }
411   
412   comp   = [_ctx component];
413   fragId = [self->fragmentIdentifier stringValueInComponent:comp];
414
415   isCollapsed = [[_ctx objectForKey:WECollapsible_IsCollapsed] boolValue];
416   scriptId    = [_ctx objectForKey:WECollapsible_ScriptId];
417   doScript    = [scriptId isNotEmpty] ? YES : NO;
418   
419   img = (isCollapsed)
420     ? [self->closedImageFileName stringValueInComponent:comp]
421     : [self->openedImageFileName stringValueInComponent:comp];
422
423   label = (isCollapsed)
424     ? [self->closedLabel stringValueInComponent:comp]
425     : [self->openedLabel stringValueInComponent:comp];
426
427   img = WEUriOfResource(img, _ctx);
428
429   /*
430   if (isCollapsed)
431     [_resp appendContentString:@"&nbsp;"];
432   */
433
434   doForm = ([_ctx isInForm] && self->submitActionName && img);
435
436   [_ctx appendElementIDComponent:(isCollapsed) ? @"c" : @"e"];
437   if (doScript) {
438     [self _appendScriptWithID:scriptId label:label imageURL:img
439           toResponse:_resp inContext:_ctx];
440   }
441   else if (doForm) {
442     [_resp appendContentString:@"<input type=\"image\" border=\"0\" name=\""];
443     [_resp appendContentString:[_ctx elementID]];
444     [_resp appendContentString:@"\" src=\""];
445     [_resp appendContentString:img];
446     [_resp appendContentString:@"\" />"];
447   }
448   else {
449     [self _appendLinkWithFragmentID:fragId label:label imageURL:img
450           isCollapsed:isCollapsed
451           toResponse:_resp inContext:_ctx];
452   }
453   
454   if (fragId) {
455     WEClientCapabilities *ccaps;
456     
457     ccaps    = [[_ctx request] clientCapabilities];
458     
459     [_resp appendContentString:@"<a name=\""];
460     [_resp appendContentString:fragId];
461     [_resp appendContentString:@"\">&nbsp;</a>"];
462     if ([self->isClicked boolValueInComponent:comp] &&
463         ([ccaps isInternetExplorer] || [ccaps isMozilla] || [ccaps isNetscape6])) {
464       if ([self->isClicked isValueSettable])
465         [self->isClicked setBoolValue:NO inComponent:comp];
466       
467       [_resp appendContentString:
468              [NSString stringWithFormat:
469              @"\n<script language=\"JavaScript\">\n"
470              @"<!--\n"
471              @"  window.location.hash=\"#%@\";\n"
472              @"//-->\n"
473              @"</script>\n",
474              fragId]];
475     }
476   }
477   else
478     [_resp appendContentString:@"&nbsp;"];
479   
480   if (label && !doScript) {
481     if (!doForm) {
482       [_resp appendContentString:@"<a href=\""];
483       [_resp appendContentString:[_ctx componentActionURL]];
484       if (fragId) {
485         [_resp appendContentString:@"#"];
486         [_resp appendContentString:fragId];
487       }
488       [_resp appendContentString:@"\">"];
489     }
490     
491     [_resp appendContentString:label];
492
493     if (!doForm)
494       [_resp appendContentString:@"</a>"];
495   }
496   [_ctx deleteLastElementIDComponent];
497 }
498
499 @end /* WECollapsibleAction */
500
501 @interface WECollapsibleTitleMode: WEContextConditional
502 @end
503
504 @implementation WECollapsibleTitleMode
505
506 - (NSString *)_contextKey {
507   return WECollapsible_TitleMode;
508 }
509
510 @end /* WECollapsibleTitleMode */
511
512 @interface WECollapsibleContentMode: WEContextConditional
513 @end
514
515 @implementation WECollapsibleContentMode
516
517 - (NSString *)_contextKey {
518   return WECollapsible_ContentMode;
519 }
520
521 @end /* WECollapsibleContentMode */