001 // Copyright 2005 The Apache Software Foundation
002 //
003 // Licensed under the Apache License, Version 2.0 (the "License");
004 // you may not use this file except in compliance with the License.
005 // You may obtain a copy of the License at
006 //
007 // http://www.apache.org/licenses/LICENSE-2.0
008 //
009 // Unless required by applicable law or agreed to in writing, software
010 // distributed under the License is distributed on an "AS IS" BASIS,
011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012 // See the License for the specific language governing permissions and
013 // limitations under the License.
014
015 package org.apache.tapestry.form;
016
017 import org.apache.hivemind.ApplicationRuntimeException;
018 import org.apache.hivemind.HiveMind;
019 import org.apache.hivemind.Location;
020 import org.apache.hivemind.util.Defense;
021 import org.apache.tapestry.*;
022 import org.apache.tapestry.engine.ILink;
023 import org.apache.tapestry.event.BrowserEvent;
024 import org.apache.tapestry.javascript.JavascriptManager;
025 import org.apache.tapestry.json.JSONObject;
026 import org.apache.tapestry.services.DataSqueezer;
027 import org.apache.tapestry.services.ResponseBuilder;
028 import org.apache.tapestry.services.ServiceConstants;
029 import org.apache.tapestry.valid.IValidationDelegate;
030
031 import java.util.*;
032
033 /**
034 * Encapsulates most of the behavior of a Form component.
035 *
036 */
037 public class FormSupportImpl implements FormSupport
038 {
039 /**
040 * Name of query parameter storing the ids alloocated while rendering the form, as a comma
041 * seperated list. This information is used when the form is submitted, to ensure that the
042 * rewind allocates the exact same sequence of ids.
043 */
044
045 public static final String FORM_IDS = "formids";
046
047 /**
048 * Names of additional ids that were pre-reserved, as a comma-sepereated list. These are names
049 * beyond that standard set. Certain engine services include extra parameter values that must be
050 * accounted for, and page properties may be encoded as additional query parameters.
051 */
052
053 public static final String RESERVED_FORM_IDS = "reservedids";
054
055 /**
056 * {@link DataSqueezer} squeezed list of {@link IRequestCycle} id allocation state as it was just before this
057 * form was rendered. Is used to ensure that all generated form ids are globally unique and consistent
058 * between requests.
059 */
060 public static final String SEED_IDS = "seedids";
061
062 /**
063 * Indicates why the form was submitted: whether for normal ("submit"), refresh, or because the
064 * form was canceled.
065 */
066
067 public static final String SUBMIT_MODE = "submitmode";
068
069 /**
070 * Attribute set to true when a field has been focused; used to prevent conflicting JavaScript
071 * for field focusing from being emitted.
072 */
073
074 public static final String FIELD_FOCUS_ATTRIBUTE = "org.apache.tapestry.field-focused";
075
076 private static final Set _standardReservedIds;
077
078 static
079 {
080 Set set = new HashSet();
081
082 set.addAll(Arrays.asList(ServiceConstants.RESERVED_IDS));
083 set.add(FORM_IDS);
084 set.add(RESERVED_FORM_IDS);
085 set.add(SEED_IDS);
086 set.add(SUBMIT_MODE);
087 set.add(FormConstants.SUBMIT_NAME_PARAMETER);
088
089 _standardReservedIds = Collections.unmodifiableSet(set);
090 }
091
092 private static final Set _submitModes;
093
094 static
095 {
096 Set set = new HashSet();
097 set.add(FormConstants.SUBMIT_CANCEL);
098 set.add(FormConstants.SUBMIT_NORMAL);
099 set.add(FormConstants.SUBMIT_REFRESH);
100
101 _submitModes = Collections.unmodifiableSet(set);
102 }
103
104 protected final IRequestCycle _cycle;
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 _allocatedIds = new ArrayList();
119
120 private String _encodingType;
121
122 private final List _deferredRunnables = new ArrayList();
123
124 /**
125 * Map keyed on extended component id, value is the pre-rendered markup for that component.
126 */
127
128 private final Map _prerenderMap = new HashMap();
129
130 /**
131 * {@link Map}, keyed on {@link FormEventType}. Values are either a String (the function name
132 * of a single event handler), or a List of Strings (a sequence of event handler function
133 * names).
134 */
135
136 private Map _events;
137
138 private final IForm _form;
139
140 private final List _hiddenValues = new ArrayList();
141
142 private final boolean _rewinding;
143
144 private final IMarkupWriter _writer;
145
146 private final IValidationDelegate _delegate;
147
148 private final PageRenderSupport _pageRenderSupport;
149
150 /**
151 * Client side validation is built up using a json object syntax structure
152 */
153 private final JSONObject _profile;
154
155 /**
156 * Used to detect whether or not a form component has been updated and will require form sync on ajax requests
157 */
158 private boolean _fieldUpdating;
159
160 private JavascriptManager _javascriptManager;
161
162 private String _idSeed;
163
164 public FormSupportImpl(IMarkupWriter writer, IRequestCycle cycle, IForm form)
165 {
166 this(writer, cycle, form, null);
167 }
168
169 public FormSupportImpl(IMarkupWriter writer, IRequestCycle cycle,
170 IForm form, JavascriptManager javascriptManager)
171 {
172 Defense.notNull(writer, "writer");
173 Defense.notNull(cycle, "cycle");
174 Defense.notNull(form, "form");
175
176 _writer = writer;
177 _cycle = cycle;
178 _form = form;
179 _delegate = form.getDelegate();
180
181 _rewinding = cycle.isRewound(form);
182 _allocatedIdIndex = 0;
183
184 _pageRenderSupport = TapestryUtils.getOptionalPageRenderSupport(cycle);
185 _profile = new JSONObject();
186 _javascriptManager = javascriptManager;
187 }
188
189 /**
190 * Alternate constructor used for testing only.
191 *
192 * @param cycle
193 * The current cycle.
194 */
195 FormSupportImpl(IRequestCycle cycle)
196 {
197 _cycle = cycle;
198 _form = null;
199 _rewinding = false;
200 _writer = null;
201 _delegate = null;
202 _pageRenderSupport = null;
203 _profile = null;
204 }
205
206 /**
207 * {@inheritDoc}
208 */
209 public IForm getForm()
210 {
211 return _form;
212 }
213
214 /**
215 * {@inheritDoc}
216 */
217 public void addEventHandler(FormEventType type, String functionName)
218 {
219 if (_events == null)
220 _events = new HashMap();
221
222 List functionList = (List) _events.get(type);
223
224 // The value can either be a String, or a List of String. Since
225 // it is rare for there to be more than one event handling function,
226 // we start with just a String.
227
228 if (functionList == null)
229 {
230 functionList = new ArrayList();
231
232 _events.put(type, functionList);
233 }
234
235 functionList.add(functionName);
236 }
237
238 /**
239 * Adds hidden fields for parameters provided by the {@link ILink}. These parameters define the
240 * information needed to dispatch the request, plus state information. The names of these
241 * parameters must be reserved so that conflicts don't occur that could disrupt the request
242 * processing. For example, if the id 'page' is not reserved, then a conflict could occur with a
243 * component whose id is 'page'. A certain number of ids are always reserved, and we find any
244 * additional ids beyond that set.
245 */
246
247 private void addHiddenFieldsForLinkParameters(ILink link)
248 {
249 String[] names = link.getParameterNames();
250 int count = Tapestry.size(names);
251
252 StringBuffer extraIds = new StringBuffer();
253 String sep = "";
254 boolean hasExtra = false;
255
256 for (int i = 0; i < count; i++)
257 {
258 String name = names[i];
259
260 // Reserve the name.
261
262 if (!_standardReservedIds.contains(name))
263 {
264 _cycle.getUniqueId(name);
265
266 extraIds.append(sep);
267 extraIds.append(name);
268
269 sep = ",";
270 hasExtra = true;
271 }
272
273 addHiddenFieldsForLinkParameter(link, name);
274 }
275
276 if (hasExtra)
277 addHiddenValue(RESERVED_FORM_IDS, extraIds.toString());
278 }
279
280 public void addHiddenValue(String name, String value)
281 {
282 _hiddenValues.add(new HiddenFieldData(name, value));
283 }
284
285 public void addHiddenValue(String name, String id, String value)
286 {
287 _hiddenValues.add(new HiddenFieldData(name, id, value));
288 }
289
290 /**
291 * Converts the allocateIds property into a string, a comma-separated list of ids. This is
292 * included as a hidden field in the form and is used to identify discrepencies when the form is
293 * submitted.
294 */
295
296 private String buildAllocatedIdList()
297 {
298 StringBuffer buffer = new StringBuffer();
299 int count = _allocatedIds.size();
300
301 for (int i = 0; i < count; i++)
302 {
303 if (i > 0)
304 buffer.append(',');
305
306 buffer.append(_allocatedIds.get(i));
307 }
308
309 return buffer.toString();
310 }
311
312 private void emitEventHandlers(String formId)
313 {
314 if (_events == null || _events.isEmpty())
315 return;
316
317 StringBuffer buffer = new StringBuffer();
318
319 Iterator i = _events.entrySet().iterator();
320
321 while (i.hasNext())
322 {
323 Map.Entry entry = (Map.Entry) i.next();
324 FormEventType type = (FormEventType) entry.getKey();
325 Object value = entry.getValue();
326
327 buffer.append("Tapestry.");
328 buffer.append(type.getAddHandlerFunctionName());
329 buffer.append("('");
330 buffer.append(formId);
331 buffer.append("', function (event)\n{");
332
333 List l = (List) value;
334 int count = l.size();
335
336 for (int j = 0; j < count; j++)
337 {
338 String functionName = (String) l.get(j);
339
340 if (j > 0)
341 {
342 buffer.append(";");
343 }
344
345 buffer.append("\n ");
346 buffer.append(functionName);
347
348 // It's supposed to be function names, but some of Paul's validation code
349 // adds inline code to be executed instead.
350
351 if (!functionName.endsWith(")"))
352 {
353 buffer.append("()");
354 }
355 }
356
357 buffer.append(";\n});\n");
358 }
359
360 // TODO: If PRS is null ...
361
362 _pageRenderSupport.addInitializationScript(_form, buffer.toString());
363 }
364
365 /**
366 * Constructs a unique identifier (within the Form). The identifier consists of the component's
367 * id, with an index number added to ensure uniqueness.
368 * <p>
369 * Simply invokes
370 * {@link #getElementId(org.apache.tapestry.form.IFormComponent, java.lang.String)}with the
371 * component's id.
372 */
373
374 public String getElementId(IFormComponent component)
375 {
376 return getElementId(component, component.getSpecifiedId());
377 }
378
379 /**
380 * Constructs a unique identifier (within the Form). The identifier consists of the component's
381 * id, with an index number added to ensure uniqueness.
382 * <p>
383 * Simply invokes
384 * {@link #getElementId(org.apache.tapestry.form.IFormComponent, java.lang.String)}with the
385 * component's id.
386 */
387
388 public String getElementId(IFormComponent component, String baseId)
389 {
390 // $ is not a valid character in an XML/XHTML id, so convert it to an underscore.
391
392 String filteredId = TapestryUtils.convertTapestryIdToNMToken(baseId);
393
394 String result = _cycle.getUniqueId(filteredId);
395
396 if (_rewinding)
397 {
398 if (_allocatedIdIndex >= _allocatedIds.size())
399 {
400 throw new StaleLinkException(FormMessages.formTooManyIds(_form, _allocatedIds.size(), component), component);
401 }
402
403 String expected = (String) _allocatedIds.get(_allocatedIdIndex);
404
405 if (!result.equals(expected))
406 throw new StaleLinkException(FormMessages.formIdMismatch(
407 _form,
408 _allocatedIdIndex,
409 expected,
410 result,
411 component), component);
412 }
413 else
414 {
415 _allocatedIds.add(result);
416 }
417
418 _allocatedIdIndex++;
419
420 component.setName(result);
421 component.setClientId(result);
422
423 return result;
424 }
425
426 public String peekClientId(IFormComponent comp)
427 {
428 String id = comp.getSpecifiedId();
429
430 if (id == null)
431 return null;
432
433 if (wasPrerendered(comp))
434 return comp.getClientId();
435
436 return _cycle.peekUniqueId(id);
437 }
438
439 public boolean isRewinding()
440 {
441 return _rewinding;
442 }
443
444 /**
445 * Invoked when rewinding a form to re-initialize the _allocatedIds and _elementIdAllocator.
446 * Converts a string passed as a parameter (and containing a comma separated list of ids) back
447 * into the allocateIds property. In addition, return the state of the ID allocater back to
448 * where it was at the start of the render.
449 *
450 * @see #buildAllocatedIdList()
451 * @since 3.0
452 */
453
454 private void reinitializeIdAllocatorForRewind()
455 {
456 _cycle.initializeIdState(_cycle.getParameter(SEED_IDS));
457
458 String allocatedFormIds = _cycle.getParameter(FORM_IDS);
459
460 String[] ids = TapestryUtils.split(allocatedFormIds);
461
462 for (int i = 0; i < ids.length; i++)
463 _allocatedIds.add(ids[i]);
464
465 // Now, reconstruct the initial state of the
466 // id allocator.
467
468 String extraReservedIds = _cycle.getParameter(RESERVED_FORM_IDS);
469
470 ids = TapestryUtils.split(extraReservedIds);
471
472 for (int i = 0; i < ids.length; i++)
473 {
474 _cycle.getUniqueId(ids[i]);
475 }
476 }
477
478 public void render(String method, IRender informalParametersRenderer, ILink link, String scheme, Integer port)
479 {
480 String formId = _form.getName();
481
482 _idSeed = _cycle.encodeIdState();
483
484 emitEventManagerInitialization(formId);
485
486 // Convert the link's query parameters into a series of
487 // hidden field values (that will be rendered later).
488
489 addHiddenFieldsForLinkParameters(link);
490
491 // Create a hidden field to store the submission mode, in case
492 // client-side JavaScript forces an update.
493
494 addHiddenValue(SUBMIT_MODE, null);
495
496 // And another for the name of the component that
497 // triggered the submit.
498
499 addHiddenValue(FormConstants.SUBMIT_NAME_PARAMETER, null);
500
501 IMarkupWriter nested = _writer.getNestedWriter();
502
503 _form.renderBody(nested, _cycle);
504
505 runDeferredRunnables();
506
507 int portI = (port == null) ? 0 : port.intValue();
508
509 writeTag(_writer, method, link.getURL(scheme, null, portI, null, false));
510
511 // For XHTML compatibility
512
513 _writer.attribute("id", formId);
514
515 if (_encodingType != null)
516 _writer.attribute("enctype", _encodingType);
517
518 // Write out event handlers collected during the rendering.
519
520 emitEventHandlers(formId);
521
522 informalParametersRenderer.render(_writer, _cycle);
523
524 // Finish the <form> tag
525
526 _writer.println();
527
528 writeHiddenFields();
529
530 // Close the nested writer, inserting its contents.
531
532 nested.close();
533
534 // Close the <form> tag.
535
536 _writer.end();
537
538 String fieldId = _delegate.getFocusField();
539
540 if (_pageRenderSupport == null)
541 return;
542
543 _pageRenderSupport.addInitializationScript(_form, "dojo.require(\"tapestry.form\");");
544
545 // If the form doesn't support focus, or the focus has already been set by a different form,
546 // then do nothing.
547
548 if (!_cycle.isFocusDisabled() && fieldId != null && _form.getFocus()
549 && _cycle.getAttribute(FIELD_FOCUS_ATTRIBUTE) == null)
550 {
551 // needs to happen last to avoid dialog issues in ie - TAPESTRY-1705
552
553 _pageRenderSupport.addScriptAfterInitialization(_form, "tapestry.form.focusField('" + fieldId + "');");
554
555 _cycle.setAttribute(FIELD_FOCUS_ATTRIBUTE, Boolean.TRUE);
556 }
557
558 // register the validation profile with client side form manager
559
560 if (_form.isClientValidationEnabled())
561 {
562 IPage page = _form.getPage();
563
564 // only include dojo widget layer if it's not already been included
565
566 if (!page.hasWidgets())
567 {
568 if (_javascriptManager != null && _javascriptManager.getFirstWidgetAsset() != null)
569 {
570 _pageRenderSupport.addExternalScript(_form,
571 _javascriptManager.getFirstWidgetAsset().getResourceLocation());
572 }
573 }
574
575 _pageRenderSupport.addInitializationScript(_form, "tapestry.form.clearProfiles('"
576 + formId + "'); tapestry.form.registerProfile('" + formId + "',"
577 + _profile.toString() + ");");
578 }
579 }
580
581 /**
582 * Pre-renders the form, setting up some client-side form support. Returns the name of the
583 * client-side form event manager variable.
584 *
585 * @param formId
586 * The client id of the form.
587 */
588 protected void emitEventManagerInitialization(String formId)
589 {
590 if (_pageRenderSupport == null)
591 return;
592
593 StringBuffer str = new StringBuffer("dojo.require(\"tapestry.form\");");
594 str.append("tapestry.form.registerForm(\"").append(formId).append("\"");
595
596 if (_form.isAsync())
597 {
598 str.append(", true");
599
600 if (_form.isJson())
601 {
602 str.append(", true");
603 }
604 }
605
606 str.append(");");
607
608 _pageRenderSupport.addInitializationScript(_form, str.toString());
609 }
610
611 public String rewind()
612 {
613 _form.getDelegate().clear();
614
615 String mode = _cycle.getParameter(SUBMIT_MODE);
616
617 // On a cancel, don't bother rendering the body or anything else at all.
618
619 if (FormConstants.SUBMIT_CANCEL.equals(mode))
620 return mode;
621
622 reinitializeIdAllocatorForRewind();
623
624 _form.renderBody(_writer, _cycle);
625
626 // New, handles cases where an eventlistener
627 // causes a form submission.
628
629 BrowserEvent event = new BrowserEvent(_cycle);
630
631 _form.getEventInvoker().invokeFormListeners(this, _cycle, event);
632
633 int expected = _allocatedIds.size();
634
635 // The other case, _allocatedIdIndex > expected, is
636 // checked for inside getElementId(). Remember that
637 // _allocatedIdIndex is incremented after allocating.
638
639 if (_allocatedIdIndex < expected)
640 {
641 String nextExpectedId = (String) _allocatedIds.get(_allocatedIdIndex);
642
643 throw new StaleLinkException(FormMessages.formTooFewIds(_form, expected - _allocatedIdIndex, nextExpectedId), _form);
644 }
645
646 runDeferredRunnables();
647
648 if (_submitModes.contains(mode))
649 {
650 // clear errors during refresh
651
652 if (FormConstants.SUBMIT_REFRESH.equals(mode))
653 {
654 _form.getDelegate().clearErrors();
655 }
656
657 return mode;
658 }
659
660 // Either something wacky on the client side, or a client without
661 // javascript enabled.
662
663 return FormConstants.SUBMIT_NORMAL;
664 }
665
666 private void runDeferredRunnables()
667 {
668 Iterator i = _deferredRunnables.iterator();
669 while (i.hasNext())
670 {
671 Runnable r = (Runnable) i.next();
672
673 r.run();
674 }
675 }
676
677 public void setEncodingType(String encodingType)
678 {
679
680 if (_encodingType != null && !_encodingType.equals(encodingType))
681 throw new ApplicationRuntimeException(FormMessages.encodingTypeContention(_form, _encodingType, encodingType),
682 _form, null, null);
683
684 _encodingType = encodingType;
685 }
686
687 /**
688 * Overwridden by {@link org.apache.tapestry.wml.GoFormSupportImpl} (WML).
689 */
690 protected void writeHiddenField(IMarkupWriter writer, String name, String id, String value)
691 {
692 writer.beginEmpty("input");
693 writer.attribute("type", "hidden");
694 writer.attribute("name", name);
695
696 if (HiveMind.isNonBlank(id))
697 writer.attribute("id", id);
698
699 writer.attribute("value", value == null ? "" : value);
700 writer.println();
701 }
702
703 /**
704 * Writes out all hidden values previously added by
705 * {@link #addHiddenValue(String, String, String)}. Writes a <div> tag around
706 * {@link #writeHiddenFieldList(IMarkupWriter)}. Overriden by
707 * {@link org.apache.tapestry.wml.GoFormSupportImpl}.
708 */
709
710 protected void writeHiddenFields()
711 {
712 IMarkupWriter writer = getHiddenFieldWriter();
713
714 writer.begin("div");
715 writer.attribute("style", "display:none;");
716 writer.attribute("id", _form.getName() + "hidden");
717
718 writeHiddenFieldList(writer);
719
720 writer.end();
721 }
722
723 /**
724 * Writes out all hidden values previously added by
725 * {@link #addHiddenValue(String, String, String)}, plus the allocated id list.
726 */
727
728 protected void writeHiddenFieldList(IMarkupWriter writer)
729 {
730 writeHiddenField(writer, FORM_IDS, null, buildAllocatedIdList());
731 writeHiddenField(writer, SEED_IDS, null, _idSeed);
732
733 Iterator i = _hiddenValues.iterator();
734 while (i.hasNext())
735 {
736 HiddenFieldData data = (HiddenFieldData) i.next();
737
738 writeHiddenField(writer, data.getName(), data.getId(), data.getValue());
739 }
740 }
741
742 /**
743 * Determines if a hidden field change has occurred, which would require
744 * that we write hidden form fields using the {@link ResponseBuilder}
745 * writer.
746 *
747 * @return The default {@link IMarkupWriter} if not doing a managed ajax/json
748 * response, else whatever is returned from {@link ResponseBuilder}.
749 */
750 protected IMarkupWriter getHiddenFieldWriter()
751 {
752 if (_cycle.getResponseBuilder().contains(_form)
753 || (!_fieldUpdating || !_cycle.getResponseBuilder().isDynamic()) )
754 {
755 return _writer;
756 }
757
758 return _cycle.getResponseBuilder().getWriter(_form.getName() + "hidden", ResponseBuilder.ELEMENT_TYPE);
759 }
760
761 private void addHiddenFieldsForLinkParameter(ILink link, String parameterName)
762 {
763 String[] values = link.getParameterValues(parameterName);
764
765 // In some cases, there are no values, but a space is "reserved" for the provided name.
766
767 if (values == null)
768 return;
769
770 for (int i = 0; i < values.length; i++)
771 {
772 addHiddenValue(parameterName, values[i]);
773 }
774 }
775
776 protected void writeTag(IMarkupWriter writer, String method, String url)
777 {
778 writer.begin("form");
779 writer.attribute("method", method);
780 writer.attribute("action", url);
781 }
782
783 public void prerenderField(IMarkupWriter writer, IComponent field, Location location)
784 {
785 Defense.notNull(writer, "writer");
786 Defense.notNull(field, "field");
787
788 String key = field.getExtendedId();
789
790 if (_prerenderMap.containsKey(key))
791 throw new ApplicationRuntimeException(FormMessages.fieldAlreadyPrerendered(field), field, location, null);
792
793 NestedMarkupWriter nested = writer.getNestedWriter();
794
795 TapestryUtils.storePrerender(_cycle, field);
796
797 _cycle.getResponseBuilder().render(nested, field, _cycle);
798
799 TapestryUtils.removePrerender(_cycle);
800
801 _prerenderMap.put(key, nested.getBuffer());
802 }
803
804 public boolean wasPrerendered(IMarkupWriter writer, IComponent field)
805 {
806 String key = field.getExtendedId();
807
808 // During a rewind, if the form is pre-rendered, the buffer will be null,
809 // so do the check based on the key, not a non-null value.
810
811 if (!_prerenderMap.containsKey(key))
812 return false;
813
814 String buffer = (String) _prerenderMap.get(key);
815
816 writer.printRaw(buffer);
817
818 _prerenderMap.remove(key);
819
820 return true;
821 }
822
823 public boolean wasPrerendered(IComponent field)
824 {
825 return _prerenderMap.containsKey(field.getExtendedId());
826 }
827
828 public void addDeferredRunnable(Runnable runnable)
829 {
830 Defense.notNull(runnable, "runnable");
831
832 _deferredRunnables.add(runnable);
833 }
834
835 public void registerForFocus(IFormComponent field, int priority)
836 {
837 _delegate.registerForFocus(field, priority);
838 }
839
840 /**
841 * {@inheritDoc}
842 */
843 public JSONObject getProfile()
844 {
845 return _profile;
846 }
847
848 /**
849 * {@inheritDoc}
850 */
851 public boolean isFormFieldUpdating()
852 {
853 return _fieldUpdating;
854 }
855
856 /**
857 * {@inheritDoc}
858 */
859 public void setFormFieldUpdating(boolean value)
860 {
861 _fieldUpdating = value;
862 }
863 }