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