KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > tapestry > form > FormSupportImpl


1 // Copyright 2005 The Apache Software Foundation
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15 package org.apache.tapestry.form;
16
17 import java.util.ArrayList JavaDoc;
18 import java.util.Arrays JavaDoc;
19 import java.util.Collections JavaDoc;
20 import java.util.HashMap JavaDoc;
21 import java.util.HashSet JavaDoc;
22 import java.util.Iterator JavaDoc;
23 import java.util.List JavaDoc;
24 import java.util.Map JavaDoc;
25 import java.util.Set JavaDoc;
26
27 import org.apache.hivemind.ApplicationRuntimeException;
28 import org.apache.hivemind.HiveMind;
29 import org.apache.hivemind.Location;
30 import org.apache.hivemind.Resource;
31 import org.apache.hivemind.util.ClasspathResource;
32 import org.apache.hivemind.util.Defense;
33 import org.apache.tapestry.IComponent;
34 import org.apache.tapestry.IForm;
35 import org.apache.tapestry.IMarkupWriter;
36 import org.apache.tapestry.IRender;
37 import org.apache.tapestry.IRequestCycle;
38 import org.apache.tapestry.NestedMarkupWriter;
39 import org.apache.tapestry.PageRenderSupport;
40 import org.apache.tapestry.StaleLinkException;
41 import org.apache.tapestry.Tapestry;
42 import org.apache.tapestry.TapestryUtils;
43 import org.apache.tapestry.engine.ILink;
44 import org.apache.tapestry.services.ServiceConstants;
45 import org.apache.tapestry.util.IdAllocator;
46
47 /**
48  * Encapsulates most of the behavior of a Form component.
49  *
50  * @author Howard M. Lewis Ship
51  * @since 4.0
52  */

53 public class FormSupportImpl implements FormSupport
54 {
55     /**
56      * Name of query parameter storing the ids alloocated while rendering the form, as a comma
57      * seperated list. This information is used when the form is submitted, to ensure that the
58      * rewind allocates the exact same sequence of ids.
59      */

60
61     public static final String JavaDoc FORM_IDS = "formids";
62
63     /**
64      * Names of additional ids that were pre-reserved, as a comma-sepereated list. These are names
65      * beyond that standard set. Certain engine services include extra parameter values that must be
66      * accounted for, and page properties may be encoded as additional query parameters.
67      */

68
69     public static final String JavaDoc RESERVED_FORM_IDS = "reservedids";
70
71     /**
72      * Indicates why the form was submitted: whether for normal ("submit"), refresh, or because the
73      * form was canceled.
74      */

75
76     public static final String JavaDoc SUBMIT_MODE = "submitmode";
77
78     public static final String JavaDoc SCRIPT = "/org/apache/tapestry/form/Form.js";
79
80     private final static Set JavaDoc _standardReservedIds;
81
82     static
83     {
84         Set JavaDoc set = new HashSet JavaDoc();
85
86         set.addAll(Arrays.asList(ServiceConstants.RESERVED_IDS));
87         set.add(FORM_IDS);
88         set.add(RESERVED_FORM_IDS);
89         set.add(SUBMIT_MODE);
90
91         _standardReservedIds = Collections.unmodifiableSet(set);
92     }
93
94     private final static Set JavaDoc _submitModes;
95
96     static
97     {
98         Set JavaDoc set = new HashSet JavaDoc();
99         set.add(FormConstants.SUBMIT_CANCEL);
100         set.add(FormConstants.SUBMIT_NORMAL);
101         set.add(FormConstants.SUBMIT_REFRESH);
102
103         _submitModes = Collections.unmodifiableSet(set);
104     }
105
106     /**
107      * Used when rewinding the form to figure to match allocated ids (allocated during the rewind)
108      * against expected ids (allocated in the previous request cycle, when the form was rendered).
109      */

110
111     private int _allocatedIdIndex;
112
113     /**
114      * The list of allocated ids for form elements within this form. This list is constructed when a
115      * form renders, and is validated against when the form is rewound.
116      */

117
118     private final List JavaDoc _allocatedIds = new ArrayList JavaDoc();
119
120     private final IRequestCycle _cycle;
121
122     private final IdAllocator _elementIdAllocator = new IdAllocator();
123
124     private String JavaDoc _encodingType;
125
126     private final List JavaDoc _deferredRunnables = new ArrayList JavaDoc();
127
128     /**
129      * Map keyed on extended component id, value is the pre-rendered markup for that component.
130      */

131
132     private final Map JavaDoc _prerenderMap = new HashMap JavaDoc();
133
134     /**
135      * {@link Map}, keyed on {@link FormEventType}. Values are either a String (the function name
136      * of a single event handler), or a List of Strings (a sequence of event handler function
137      * names).
138      */

139
140     private Map JavaDoc _events;
141
142     private final IForm _form;
143
144     private final List JavaDoc _hiddenValues = new ArrayList JavaDoc();
145
146     private boolean _rewinding;
147
148     private final IMarkupWriter _writer;
149
150     private final Resource _script;
151
152     public FormSupportImpl(IMarkupWriter writer, IRequestCycle cycle, IForm form)
153     {
154         Defense.notNull(writer, "writer");
155         Defense.notNull(cycle, "cycle");
156         Defense.notNull(form, "form");
157
158         _writer = writer;
159         _cycle = cycle;
160         _form = form;
161
162         _rewinding = cycle.isRewound(form);
163         _allocatedIdIndex = 0;
164
165         _script = new ClasspathResource(cycle.getEngine().getClassResolver(), SCRIPT);
166     }
167
168     /**
169      * Adds an event handler for the form, of the given type.
170      */

171
172     public void addEventHandler(FormEventType type, String JavaDoc functionName)
173     {
174         if (_events == null)
175             _events = new HashMap JavaDoc();
176
177         List JavaDoc functionList = (List JavaDoc) _events.get(type);
178
179         // The value can either be a String, or a List of String. Since
180
// it is rare for there to be more than one event handling function,
181
// we start with just a String.
182

183         if (functionList == null)
184         {
185             functionList = new ArrayList JavaDoc();
186
187             _events.put(type, functionList);
188         }
189
190         functionList.add(functionName);
191     }
192
193     /**
194      * Adds hidden fields for parameters provided by the {@link ILink}. These parameters define the
195      * information needed to dispatch the request, plus state information. The names of these
196      * parameters must be reserved so that conflicts don't occur that could disrupt the request
197      * processing. For example, if the id 'page' is not reserved, then a conflict could occur with a
198      * component whose id is 'page'. A certain number of ids are always reserved, and we find any
199      * additional ids beyond that set.
200      */

201
202     private void addHiddenFieldsForLinkParameters(ILink link)
203     {
204         String JavaDoc[] names = link.getParameterNames();
205         int count = Tapestry.size(names);
206
207         StringBuffer JavaDoc extraIds = new StringBuffer JavaDoc();
208         String JavaDoc sep = "";
209         boolean hasExtra = false;
210
211         // All the reserved ids, which are essential for
212
// dispatching the request, are automatically reserved.
213
// Thus, if you have a component with an id of 'service', its element id
214
// will likely be 'service$0'.
215

216         preallocateReservedIds();
217
218         for (int i = 0; i < count; i++)
219         {
220             String JavaDoc name = names[i];
221
222             // Reserve the name.
223

224             if (!_standardReservedIds.contains(name))
225             {
226                 _elementIdAllocator.allocateId(name);
227
228                 extraIds.append(sep);
229                 extraIds.append(name);
230
231                 sep = ",";
232                 hasExtra = true;
233             }
234
235             addHiddenFieldsForLinkParameter(link, name);
236         }
237
238         if (hasExtra)
239             addHiddenValue(RESERVED_FORM_IDS, extraIds.toString());
240     }
241
242     public void addHiddenValue(String JavaDoc name, String JavaDoc value)
243     {
244         _hiddenValues.add(new HiddenFieldData(name, value));
245     }
246
247     public void addHiddenValue(String JavaDoc name, String JavaDoc id, String JavaDoc value)
248     {
249         _hiddenValues.add(new HiddenFieldData(name, id, value));
250     }
251
252     /**
253      * Converts the allocateIds property into a string, a comma-separated list of ids. This is
254      * included as a hidden field in the form and is used to identify discrepencies when the form is
255      * submitted.
256      */

257
258     private String JavaDoc buildAllocatedIdList()
259     {
260         StringBuffer JavaDoc buffer = new StringBuffer JavaDoc();
261         int count = _allocatedIds.size();
262
263         for (int i = 0; i < count; i++)
264         {
265             if (i > 0)
266                 buffer.append(',');
267
268             buffer.append(_allocatedIds.get(i));
269         }
270
271         return buffer.toString();
272     }
273
274     private void emitEventHandlers(String JavaDoc eventManager)
275     {
276         if (_events == null || _events.isEmpty())
277             return;
278
279         PageRenderSupport pageRenderSupport = TapestryUtils.getPageRenderSupport(_cycle, _form);
280
281         StringBuffer JavaDoc buffer = new StringBuffer JavaDoc();
282
283         Iterator JavaDoc i = _events.entrySet().iterator();
284
285         while (i.hasNext())
286         {
287             Map.Entry JavaDoc entry = (Map.Entry JavaDoc) i.next();
288             FormEventType type = (FormEventType) entry.getKey();
289             Object JavaDoc value = entry.getValue();
290
291             buffer.append(eventManager);
292             buffer.append(".");
293             buffer.append(type.getAddListenerMethodName());
294
295             // Build a composite function in-place
296

297             buffer.append("(function (event)\n{");
298
299             List JavaDoc l = (List JavaDoc) value;
300             int count = l.size();
301
302             for (int j = 0; j < count; j++)
303             {
304                 String JavaDoc functionName = (String JavaDoc) l.get(j);
305
306                 if (j > 0)
307                 {
308                     buffer.append(";");
309                 }
310
311                 buffer.append("\n ");
312                 buffer.append(functionName);
313
314                 // It's supposed to be function names, but some of Paul's validation code
315
// adds inline code to be executed instead.
316

317                 if (!functionName.endsWith(")"))
318                 {
319                     buffer.append("()");
320                 }
321             }
322
323             buffer.append(";\n});\n");
324         }
325
326         pageRenderSupport.addInitializationScript(buffer.toString());
327     }
328
329     /**
330      * Constructs a unique identifier (within the Form). The identifier consists of the component's
331      * id, with an index number added to ensure uniqueness.
332      * <p>
333      * Simply invokes
334      * {@link #getElementId(org.apache.tapestry.form.IFormComponent, java.lang.String)}with the
335      * component's id.
336      */

337
338     public String JavaDoc getElementId(IFormComponent component)
339     {
340         return getElementId(component, component.getId());
341     }
342
343     /**
344      * Constructs a unique identifier (within the Form). The identifier consists of the component's
345      * id, with an index number added to ensure uniqueness.
346      * <p>
347      * Simply invokes
348      * {@link #getElementId(org.apache.tapestry.form.IFormComponent, java.lang.String)}with the
349      * component's id.
350      */

351
352     public String JavaDoc getElementId(IFormComponent component, String JavaDoc baseId)
353     {
354         String JavaDoc result = _elementIdAllocator.allocateId(baseId);
355
356         if (_rewinding)
357         {
358             if (_allocatedIdIndex >= _allocatedIds.size())
359             {
360                 throw new StaleLinkException(FormMessages.formTooManyIds(_form, _allocatedIds
361                         .size(), component), component);
362             }
363
364             String JavaDoc expected = (String JavaDoc) _allocatedIds.get(_allocatedIdIndex);
365
366             if (!result.equals(expected))
367                 throw new StaleLinkException(FormMessages.formIdMismatch(
368                         _form,
369                         _allocatedIdIndex,
370                         expected,
371                         result,
372                         component), component);
373         }
374         else
375         {
376             _allocatedIds.add(result);
377         }
378
379         _allocatedIdIndex++;
380
381         component.setName(result);
382
383         return result;
384     }
385
386     public boolean isRewinding()
387     {
388         return _rewinding;
389     }
390
391     private void preallocateReservedIds()
392     {
393         for (int i = 0; i < ServiceConstants.RESERVED_IDS.length; i++)
394             _elementIdAllocator.allocateId(ServiceConstants.RESERVED_IDS[i]);
395     }
396
397     /**
398      * Invoked when rewinding a form to re-initialize the _allocatedIds and _elementIdAllocator.
399      * Converts a string passed as a parameter (and containing a comma separated list of ids) back
400      * into the allocateIds property. In addition, return the state of the ID allocater back to
401      * where it was at the start of the render.
402      *
403      * @see #buildAllocatedIdList()
404      * @since 3.0
405      */

406
407     private void reinitializeIdAllocatorForRewind()
408     {
409         String JavaDoc allocatedFormIds = _cycle.getParameter(FORM_IDS);
410
411         String JavaDoc[] ids = TapestryUtils.split(allocatedFormIds);
412
413         for (int i = 0; i < ids.length; i++)
414             _allocatedIds.add(ids[i]);
415
416         // Now, reconstruct the the initial state of the
417
// id allocator.
418

419         preallocateReservedIds();
420
421         String JavaDoc extraReservedIds = _cycle.getParameter(RESERVED_FORM_IDS);
422
423         ids = TapestryUtils.split(extraReservedIds);
424
425         for (int i = 0; i < ids.length; i++)
426             _elementIdAllocator.allocateId(ids[i]);
427     }
428
429     public void render(String JavaDoc method, IRender informalParametersRenderer, ILink link)
430     {
431         String JavaDoc eventManager = emitEventManagerInitialization();
432
433         // Convert the link's query parameters into a series of
434
// hidden field values (that will be rendered later).
435

436         addHiddenFieldsForLinkParameters(link);
437
438         // Create a hidden field to store the submission mode, in case
439
// client-side JavaScript forces an update.
440

441         addHiddenValue(SUBMIT_MODE, null);
442
443         IMarkupWriter nested = _writer.getNestedWriter();
444
445         _form.renderBody(nested, _cycle);
446
447         runDeferredRunnables();
448
449         writeTag(_writer, method, link.getURL(null, false));
450
451         _writer.attribute("name", _form.getName());
452
453         if (_encodingType != null)
454             _writer.attribute("enctype", _encodingType);
455
456         // Write out event handlers collected during the rendering.
457

458         emitEventHandlers(eventManager);
459
460         informalParametersRenderer.render(_writer, _cycle);
461
462         // Finish the <form> tag
463

464         _writer.println();
465
466         writeHiddenField(FORM_IDS, null, buildAllocatedIdList());
467         writeHiddenFields();
468
469         // Close the nested writer, inserting its contents.
470

471         nested.close();
472
473         // Close the <form> tag.
474

475         _writer.end();
476     }
477
478     /**
479      * Pre-renders the form, setting up some client-side form support. Returns the name of the
480      * client-side form event manager variable.
481      */

482     protected String JavaDoc emitEventManagerInitialization()
483     {
484         PageRenderSupport pageRenderSupport = TapestryUtils.getOptionalPageRenderSupport(_cycle);
485
486         if (pageRenderSupport == null)
487             return null;
488
489         pageRenderSupport.addExternalScript(_script);
490
491         String JavaDoc formName = _form.getName();
492
493         String JavaDoc eventManager = formName + "_events";
494
495         pageRenderSupport.addInitializationScript("var " + eventManager
496                 + " = new FormEventManager(document." + formName + ");");
497
498         return eventManager;
499     }
500
501     public String JavaDoc rewind()
502     {
503         _form.getDelegate().clear();
504
505         String JavaDoc mode = _cycle.getParameter(SUBMIT_MODE);
506
507         // On a cancel, don't bother rendering the body or anything else at all.
508

509         if (FormConstants.SUBMIT_CANCEL.equals(mode))
510             return mode;
511
512         reinitializeIdAllocatorForRewind();
513
514         _form.renderBody(_writer, _cycle);
515
516         int expected = _allocatedIds.size();
517
518         // The other case, _allocatedIdIndex > expected, is
519
// checked for inside getElementId(). Remember that
520
// _allocatedIdIndex is incremented after allocating.
521

522         if (_allocatedIdIndex < expected)
523         {
524             String JavaDoc nextExpectedId = (String JavaDoc) _allocatedIds.get(_allocatedIdIndex);
525
526             throw new StaleLinkException(FormMessages.formTooFewIds(_form, expected
527                     - _allocatedIdIndex, nextExpectedId), _form);
528         }
529
530         runDeferredRunnables();
531
532         if (_submitModes.contains(mode))
533             return mode;
534
535         // Either something wacky on the client side, or a client without
536
// javascript enabled.
537

538         return FormConstants.SUBMIT_NORMAL;
539
540     }
541
542     private void runDeferredRunnables()
543     {
544         Iterator JavaDoc i = _deferredRunnables.iterator();
545         while (i.hasNext())
546         {
547             Runnable JavaDoc r = (Runnable JavaDoc) i.next();
548
549             r.run();
550         }
551     }
552
553     public void setEncodingType(String JavaDoc encodingType)
554     {
555
556         if (_encodingType != null && !_encodingType.equals(encodingType))
557             throw new ApplicationRuntimeException(FormMessages.encodingTypeContention(
558                     _form,
559                     _encodingType,
560                     encodingType), _form, null, null);
561
562         _encodingType = encodingType;
563     }
564
565     protected void writeHiddenField(IMarkupWriter writer, String JavaDoc name, String JavaDoc id, String JavaDoc value)
566     {
567         writer.beginEmpty("input");
568         writer.attribute("type", "hidden");
569         writer.attribute("name", name);
570
571         if (HiveMind.isNonBlank(id))
572             writer.attribute("id", id);
573
574         writer.attribute("value", value == null ? "" : value);
575         writer.println();
576     }
577
578     private void writeHiddenField(String JavaDoc name, String JavaDoc id, String JavaDoc value)
579     {
580         writeHiddenField(_writer, name, id, value);
581     }
582
583     /**
584      * Writes out all hidden values previously added by
585      * {@link #addHiddenValue(String, String, String)}.
586      */

587
588     private void writeHiddenFields()
589     {
590         Iterator JavaDoc i = _hiddenValues.iterator();
591         while (i.hasNext())
592         {
593             HiddenFieldData data = (HiddenFieldData) i.next();
594
595             writeHiddenField(data.getName(), data.getId(), data.getValue());
596         }
597     }
598
599     private void addHiddenFieldsForLinkParameter(ILink link, String JavaDoc parameterName)
600     {
601         String JavaDoc[] values = link.getParameterValues(parameterName);
602
603         // In some cases, there are no values, but a space is "reserved" for the provided name.
604

605         if (values == null)
606             return;
607
608         for (int i = 0; i < values.length; i++)
609         {
610             addHiddenValue(parameterName, values[i]);
611         }
612     }
613
614     protected void writeTag(IMarkupWriter writer, String JavaDoc method, String JavaDoc url)
615     {
616         writer.begin("form");
617         writer.attribute("method", method);
618         writer.attribute("action", url);
619     }
620
621     public void prerenderField(IMarkupWriter writer, IComponent field, Location location)
622     {
623         Defense.notNull(writer, "writer");
624         Defense.notNull(field, "field");
625
626         String JavaDoc key = field.getExtendedId();
627
628         if (_prerenderMap.containsKey(key))
629             throw new ApplicationRuntimeException(FormMessages.fieldAlreadyPrerendered(field),
630                     location, null);
631
632         NestedMarkupWriter nested = writer.getNestedWriter();
633
634         field.render(nested, _cycle);
635
636         _prerenderMap.put(key, nested.getBuffer());
637     }
638
639     public boolean wasPrerendered(IMarkupWriter writer, IComponent field)
640     {
641         String JavaDoc key = field.getExtendedId();
642
643         String JavaDoc buffer = (String JavaDoc) _prerenderMap.get(key);
644
645         if (buffer == null)
646             return false;
647
648         writer.printRaw(buffer);
649
650         _prerenderMap.remove(key);
651
652         return true;
653     }
654
655     public void addDeferredRunnable(Runnable JavaDoc runnable)
656     {
657         Defense.notNull(runnable, "runnable");
658
659         _deferredRunnables.add(runnable);
660     }
661 }
Popular Tags