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 }