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.json.JSONObject; 025 import org.apache.tapestry.services.ResponseBuilder; 026 import org.apache.tapestry.services.ServiceConstants; 027 import org.apache.tapestry.util.IdAllocator; 028 import org.apache.tapestry.valid.IValidationDelegate; 029 030 import java.util.*; 031 032 /** 033 * Encapsulates most of the behavior of a Form component. 034 * 035 */ 036 public class FormSupportImpl implements FormSupport 037 { 038 /** 039 * Name of query parameter storing the ids alloocated while rendering the form, as a comma 040 * seperated list. This information is used when the form is submitted, to ensure that the 041 * rewind allocates the exact same sequence of ids. 042 */ 043 044 public static final String FORM_IDS = "formids"; 045 046 /** 047 * Names of additional ids that were pre-reserved, as a comma-sepereated list. These are names 048 * beyond that standard set. Certain engine services include extra parameter values that must be 049 * accounted for, and page properties may be encoded as additional query parameters. 050 */ 051 052 public static final String RESERVED_FORM_IDS = "reservedids"; 053 054 /** 055 * Indicates why the form was submitted: whether for normal ("submit"), refresh, or because the 056 * form was canceled. 057 */ 058 059 public static final String SUBMIT_MODE = "submitmode"; 060 061 /** 062 * Attribute set to true when a field has been focused; used to prevent conflicting JavaScript 063 * for field focusing from being emitted. 064 */ 065 066 public static final String FIELD_FOCUS_ATTRIBUTE = "org.apache.tapestry.field-focused"; 067 068 private static final Set _standardReservedIds; 069 070 static 071 { 072 Set set = new HashSet(); 073 074 set.addAll(Arrays.asList(ServiceConstants.RESERVED_IDS)); 075 set.add(FORM_IDS); 076 set.add(RESERVED_FORM_IDS); 077 set.add(SUBMIT_MODE); 078 set.add(FormConstants.SUBMIT_NAME_PARAMETER); 079 080 _standardReservedIds = Collections.unmodifiableSet(set); 081 } 082 083 private static final Set _submitModes; 084 085 static 086 { 087 Set set = new HashSet(); 088 set.add(FormConstants.SUBMIT_CANCEL); 089 set.add(FormConstants.SUBMIT_NORMAL); 090 set.add(FormConstants.SUBMIT_REFRESH); 091 092 _submitModes = Collections.unmodifiableSet(set); 093 } 094 095 protected final IRequestCycle _cycle; 096 097 protected final IdAllocator _elementIdAllocator = new IdAllocator(); 098 099 /** 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 private final List _allocatedIds = new ArrayList(); 112 113 private String _encodingType; 114 115 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 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 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 { 155 Defense.notNull(writer, "writer"); 156 Defense.notNull(cycle, "cycle"); 157 Defense.notNull(form, "form"); 158 159 _writer = writer; 160 _cycle = cycle; 161 _form = form; 162 _delegate = form.getDelegate(); 163 164 _rewinding = cycle.isRewound(form); 165 _allocatedIdIndex = 0; 166 167 _pageRenderSupport = TapestryUtils.getOptionalPageRenderSupport(cycle); 168 _profile = new JSONObject(); 169 } 170 171 /** 172 * Alternate constructor used for testing only. 173 * 174 * @param cycle 175 * The current cycle. 176 */ 177 FormSupportImpl(IRequestCycle cycle) 178 { 179 _cycle = cycle; 180 _form = null; 181 _rewinding = false; 182 _writer = null; 183 _delegate = null; 184 _pageRenderSupport = null; 185 _profile = null; 186 } 187 188 /** 189 * {@inheritDoc} 190 */ 191 public IForm getForm() 192 { 193 return _form; 194 } 195 196 /** 197 * {@inheritDoc} 198 */ 199 public void addEventHandler(FormEventType type, String functionName) 200 { 201 if (_events == null) 202 _events = new HashMap(); 203 204 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 if (functionList == null) 211 { 212 functionList = new ArrayList(); 213 214 _events.put(type, functionList); 215 } 216 217 functionList.add(functionName); 218 } 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 String[] names = link.getParameterNames(); 232 int count = Tapestry.size(names); 233 234 StringBuffer extraIds = new StringBuffer(); 235 String sep = ""; 236 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 preallocateReservedIds(); 244 245 for (int i = 0; i < count; i++) 246 { 247 String name = names[i]; 248 249 // Reserve the name. 250 251 if (!_standardReservedIds.contains(name)) 252 { 253 _elementIdAllocator.allocateId(name); 254 255 extraIds.append(sep); 256 extraIds.append(name); 257 258 sep = ","; 259 hasExtra = true; 260 } 261 262 addHiddenFieldsForLinkParameter(link, name); 263 } 264 265 if (hasExtra) 266 addHiddenValue(RESERVED_FORM_IDS, extraIds.toString()); 267 } 268 269 public void addHiddenValue(String name, String value) 270 { 271 _hiddenValues.add(new HiddenFieldData(name, value)); 272 } 273 274 public void addHiddenValue(String name, String id, String value) 275 { 276 _hiddenValues.add(new HiddenFieldData(name, id, value)); 277 } 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 StringBuffer buffer = new StringBuffer(); 288 int count = _allocatedIds.size(); 289 290 for (int i = 0; i < count; i++) 291 { 292 if (i > 0) 293 buffer.append(','); 294 295 buffer.append(_allocatedIds.get(i)); 296 } 297 298 return buffer.toString(); 299 } 300 301 private void emitEventHandlers(String formId) 302 { 303 if (_events == null || _events.isEmpty()) 304 return; 305 306 StringBuffer buffer = new StringBuffer(); 307 308 Iterator i = _events.entrySet().iterator(); 309 310 while (i.hasNext()) 311 { 312 Map.Entry entry = (Map.Entry) i.next(); 313 FormEventType type = (FormEventType) entry.getKey(); 314 Object value = entry.getValue(); 315 316 buffer.append("Tapestry."); 317 buffer.append(type.getAddHandlerFunctionName()); 318 buffer.append("('"); 319 buffer.append(formId); 320 buffer.append("', function (event)\n{"); 321 322 List l = (List) value; 323 int count = l.size(); 324 325 for (int j = 0; j < count; j++) 326 { 327 String functionName = (String) l.get(j); 328 329 if (j > 0) 330 { 331 buffer.append(";"); 332 } 333 334 buffer.append("\n "); 335 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 if (!functionName.endsWith(")")) 341 { 342 buffer.append("()"); 343 } 344 } 345 346 buffer.append(";\n});\n"); 347 } 348 349 // TODO: If PRS is null ... 350 351 _pageRenderSupport.addInitializationScript(_form, buffer.toString()); 352 } 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 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 String filteredId = TapestryUtils.convertTapestryIdToNMToken(baseId); 382 383 String result = _elementIdAllocator.allocateId(filteredId); 384 385 if (_rewinding) 386 { 387 if (_allocatedIdIndex >= _allocatedIds.size()) 388 { 389 throw new StaleLinkException(FormMessages.formTooManyIds(_form, _allocatedIds.size(), 390 component), component); 391 } 392 393 String expected = (String) _allocatedIds.get(_allocatedIdIndex); 394 395 if (!result.equals(expected)) 396 throw new StaleLinkException(FormMessages.formIdMismatch( 397 _form, 398 _allocatedIdIndex, 399 expected, 400 result, 401 component), component); 402 } 403 else 404 { 405 _allocatedIds.add(result); 406 } 407 408 _allocatedIdIndex++; 409 410 component.setName(result); 411 component.setClientId(result); 412 413 return result; 414 } 415 416 public String peekClientId(IFormComponent comp) 417 { 418 String id = comp.getSpecifiedId(); 419 if (id == null) 420 return null; 421 422 if (wasPrerendered(comp)) 423 return comp.getClientId(); 424 425 return _elementIdAllocator.peekNextId(id); 426 } 427 428 public boolean isRewinding() 429 { 430 return _rewinding; 431 } 432 433 private void preallocateReservedIds() 434 { 435 for (int i = 0; i < ServiceConstants.RESERVED_IDS.length; i++) 436 _elementIdAllocator.allocateId(ServiceConstants.RESERVED_IDS[i]); 437 } 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 String allocatedFormIds = _cycle.getParameter(FORM_IDS); 452 453 String[] ids = TapestryUtils.split(allocatedFormIds); 454 455 for (int i = 0; i < ids.length; i++) 456 _allocatedIds.add(ids[i]); 457 458 // Now, reconstruct the initial state of the 459 // id allocator. 460 461 preallocateReservedIds(); 462 463 String extraReservedIds = _cycle.getParameter(RESERVED_FORM_IDS); 464 465 ids = TapestryUtils.split(extraReservedIds); 466 467 for (int i = 0; i < ids.length; i++) 468 _elementIdAllocator.allocateId(ids[i]); 469 } 470 471 int convertSeedToId(String input) 472 { 473 int index = input.lastIndexOf("_"); 474 475 if (index < 0) 476 throw new ApplicationRuntimeException("Unable to convert seedId of " + input + " to integer."); 477 478 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 String formId = _form.getName(); 484 485 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 addHiddenFieldsForLinkParameters(link); 491 492 // Create a hidden field to store the submission mode, in case 493 // client-side JavaScript forces an update. 494 495 addHiddenValue(SUBMIT_MODE, null); 496 497 // And another for the name of the component that 498 // triggered the submit. 499 500 addHiddenValue(FormConstants.SUBMIT_NAME_PARAMETER, null); 501 502 IMarkupWriter nested = _writer.getNestedWriter(); 503 504 _form.renderBody(nested, _cycle); 505 506 runDeferredRunnables(); 507 508 int portI = (port == null) ? 0 : port.intValue(); 509 510 writeTag(_writer, method, link.getURL(scheme, null, portI, null, false)); 511 512 // For XHTML compatibility 513 514 _writer.attribute("id", formId); 515 516 if (_encodingType != null) 517 _writer.attribute("enctype", _encodingType); 518 519 // Write out event handlers collected during the rendering. 520 521 emitEventHandlers(formId); 522 523 informalParametersRenderer.render(_writer, _cycle); 524 525 // Finish the <form> tag 526 527 _writer.println(); 528 529 writeHiddenFields(); 530 531 // Close the nested writer, inserting its contents. 532 533 nested.close(); 534 535 // Close the <form> tag. 536 537 _writer.end(); 538 539 String fieldId = _delegate.getFocusField(); 540 541 if (_pageRenderSupport == null) 542 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 if (!_cycle.isFocusDisabled() && fieldId != null && _form.getFocus() 548 && _cycle.getAttribute(FIELD_FOCUS_ATTRIBUTE) == null) 549 { 550 _pageRenderSupport.addInitializationScript(_form, "dojo.require(\"tapestry.form\");"); 551 552 // needs to happen last to avoid dialog issues in ie - TAPESTRY-1705 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 IAsset clientScript = _form.getAsset("clientValidationScript"); 569 570 if (clientScript != null) 571 { 572 _pageRenderSupport.addExternalScript(_form, clientScript.getResourceLocation()); 573 } 574 } 575 576 _pageRenderSupport.addInitializationScript(_form, "dojo.require(\"tapestry.form\");tapestry.form.clearProfiles('" 577 + formId + "'); tapestry.form.registerProfile('" + formId + "'," 578 + _profile.toString() + ");"); 579 } 580 } 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 if (_pageRenderSupport == null) 592 return; 593 594 StringBuffer str = new StringBuffer("dojo.require(\"tapestry.form\");"); 595 str.append("tapestry.form.registerForm(\"").append(formId).append("\""); 596 597 if (_form.isAsync()) 598 { 599 str.append(", true"); 600 601 if (_form.isJson()) 602 { 603 str.append(", true"); 604 } 605 } 606 607 str.append(");"); 608 609 _pageRenderSupport.addInitializationScript(_form, str.toString()); 610 } 611 612 public String rewind() 613 { 614 _form.getDelegate().clear(); 615 616 String mode = _cycle.getParameter(SUBMIT_MODE); 617 618 // On a cancel, don't bother rendering the body or anything else at all. 619 620 if (FormConstants.SUBMIT_CANCEL.equals(mode)) 621 return mode; 622 623 reinitializeIdAllocatorForRewind(); 624 625 _form.renderBody(_writer, _cycle); 626 627 // New, handles cases where an eventlistener 628 // causes a form submission. 629 630 BrowserEvent event = new BrowserEvent(_cycle); 631 632 _form.getEventInvoker().invokeFormListeners(this, _cycle, event); 633 634 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 if (_allocatedIdIndex < expected) 641 { 642 String nextExpectedId = (String) _allocatedIds.get(_allocatedIdIndex); 643 644 throw new StaleLinkException(FormMessages.formTooFewIds(_form, expected - _allocatedIdIndex, nextExpectedId), _form); 645 } 646 647 runDeferredRunnables(); 648 649 if (_submitModes.contains(mode)) 650 { 651 // clear errors during refresh 652 653 if (FormConstants.SUBMIT_REFRESH.equals(mode)) 654 { 655 _form.getDelegate().clearErrors(); 656 } 657 658 return mode; 659 } 660 661 // Either something wacky on the client side, or a client without 662 // javascript enabled. 663 664 return FormConstants.SUBMIT_NORMAL; 665 } 666 667 private void runDeferredRunnables() 668 { 669 Iterator i = _deferredRunnables.iterator(); 670 while (i.hasNext()) 671 { 672 Runnable r = (Runnable) i.next(); 673 674 r.run(); 675 } 676 } 677 678 public void setEncodingType(String encodingType) 679 { 680 681 if (_encodingType != null && !_encodingType.equals(encodingType)) 682 throw new ApplicationRuntimeException(FormMessages.encodingTypeContention( 683 _form, 684 _encodingType, 685 encodingType), _form, null, null); 686 687 _encodingType = encodingType; 688 } 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 writer.beginEmpty("input"); 696 writer.attribute("type", "hidden"); 697 writer.attribute("name", name); 698 699 if (HiveMind.isNonBlank(id)) 700 writer.attribute("id", id); 701 702 writer.attribute("value", value == null ? "" : value); 703 writer.println(); 704 } 705 706 /** 707 * Writes out all hidden values previously added by 708 * {@link #addHiddenValue(String, String, String)}. Writes a <div> tag around 709 * {@link #writeHiddenFieldList(IMarkupWriter)}. Overriden by 710 * {@link org.apache.tapestry.wml.GoFormSupportImpl}. 711 */ 712 713 protected void writeHiddenFields() 714 { 715 IMarkupWriter writer = getHiddenFieldWriter(); 716 717 writer.begin("div"); 718 writer.attribute("style", "display:none;"); 719 writer.attribute("id", _form.getName() + "hidden"); 720 721 writeHiddenFieldList(writer); 722 723 writer.end(); 724 } 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 writeHiddenField(writer, FORM_IDS, null, buildAllocatedIdList()); 734 735 Iterator i = _hiddenValues.iterator(); 736 while (i.hasNext()) 737 { 738 HiddenFieldData data = (HiddenFieldData) i.next(); 739 740 writeHiddenField(writer, data.getName(), data.getId(), data.getValue()); 741 } 742 } 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 if (_cycle.getResponseBuilder().contains(_form) 755 || (!_fieldUpdating || !_cycle.getResponseBuilder().isDynamic()) ) 756 return _writer; 757 758 return _cycle.getResponseBuilder().getWriter(_form.getName() + "hidden", 759 ResponseBuilder.ELEMENT_TYPE); 760 } 761 762 private void addHiddenFieldsForLinkParameter(ILink link, String parameterName) 763 { 764 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 if (values == null) 769 return; 770 771 for (int i = 0; i < values.length; i++) 772 addHiddenValue(parameterName, values[i]); 773 } 774 775 protected void writeTag(IMarkupWriter writer, String method, String url) 776 { 777 writer.begin("form"); 778 writer.attribute("method", method); 779 writer.attribute("action", url); 780 } 781 782 public void prerenderField(IMarkupWriter writer, IComponent field, Location location) 783 { 784 Defense.notNull(writer, "writer"); 785 Defense.notNull(field, "field"); 786 787 String key = field.getExtendedId(); 788 789 if (_prerenderMap.containsKey(key)) 790 throw new ApplicationRuntimeException(FormMessages.fieldAlreadyPrerendered(field), 791 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 }