001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.gwt.client.ui.input.form; 029 030import org.opencms.gwt.client.ui.input.I_CmsFormField; 031import org.opencms.gwt.client.ui.input.I_CmsFormWidget; 032import org.opencms.gwt.client.ui.input.I_CmsHasBlur; 033import org.opencms.gwt.client.ui.input.I_CmsStringModel; 034import org.opencms.gwt.client.util.CmsExtendedValueChangeEvent; 035import org.opencms.gwt.client.validation.CmsValidationController; 036import org.opencms.gwt.client.validation.I_CmsValidationHandler; 037import org.opencms.gwt.shared.CmsValidationResult; 038 039import java.util.ArrayList; 040import java.util.Collection; 041import java.util.Collections; 042import java.util.Date; 043import java.util.HashMap; 044import java.util.HashSet; 045import java.util.LinkedHashMap; 046import java.util.List; 047import java.util.Map; 048import java.util.Set; 049 050import com.google.common.collect.ArrayListMultimap; 051import com.google.common.collect.Multimap; 052import com.google.gwt.core.client.Scheduler; 053import com.google.gwt.core.client.Scheduler.ScheduledCommand; 054import com.google.gwt.event.dom.client.HasKeyPressHandlers; 055import com.google.gwt.event.dom.client.KeyCodes; 056import com.google.gwt.event.dom.client.KeyPressEvent; 057import com.google.gwt.event.dom.client.KeyPressHandler; 058import com.google.gwt.event.logical.shared.HasValueChangeHandlers; 059import com.google.gwt.event.logical.shared.ValueChangeEvent; 060import com.google.gwt.event.logical.shared.ValueChangeHandler; 061 062/** 063 * 064 * This class acts as a container for form fields.<p> 065 * 066 * It is also responsible for collecting and validating the values of the form fields. 067 * 068 * @since 8.0.0 069 * 070 */ 071public class CmsForm { 072 073 /** The set of fields which have been edited. */ 074 protected Set<String> m_editedFields = new HashSet<String>(); 075 076 /** A map from field ids to the corresponding widgets. */ 077 protected Map<String, I_CmsFormField> m_fields = new LinkedHashMap<String, I_CmsFormField>(); 078 079 /** The form handler. */ 080 protected I_CmsFormHandler m_formHandler; 081 082 /** A flag which indicates whether the user has pressed enter in a widget. */ 083 protected boolean m_pressedEnter; 084 085 /** A multimap from field groups to fields. */ 086 private Multimap<String, I_CmsFormField> m_fieldsByGroup = ArrayListMultimap.create(); 087 088 /** The fields indexed by model id. */ 089 private Multimap<String, I_CmsFormField> m_fieldsByModelId = ArrayListMultimap.create(); 090 091 /** The list of form reset handlers. */ 092 private List<I_CmsFormResetHandler> m_resetHandlers = new ArrayList<I_CmsFormResetHandler>(); 093 094 /** The server-side form validator class to use. */ 095 private String m_validatorClass; 096 097 /** The form widget container. */ 098 private A_CmsFormFieldPanel m_widget; 099 100 /** 101 * Creates a new form with an existing form widget container.<p> 102 * 103 * @param panel the form widget container 104 */ 105 public CmsForm(A_CmsFormFieldPanel panel) { 106 107 m_widget = panel; 108 } 109 110 /** 111 * Creates a new form and optionally sets the form widget container to a simple form field panel.<p> 112 * 113 * @param initPanel if true, initializes the form widget container 114 */ 115 public CmsForm(boolean initPanel) { 116 117 if (initPanel) { 118 setWidget(new CmsSimpleFormFieldPanel()); 119 } 120 } 121 122 /** 123 * Adds a form field.<p> 124 * 125 * @param field the field to add 126 * @param initialValue the initial field value 127 */ 128 public void addField(I_CmsFormField field, String initialValue) { 129 130 addField("", field, initialValue); 131 } 132 133 /** 134 * Adds a form field to the form.<p> 135 * 136 * @param fieldGroup the form field group key 137 * @param formField the form field which should be added 138 */ 139 public void addField(String fieldGroup, final I_CmsFormField formField) { 140 141 initializeFormFieldWidget(formField); 142 m_fields.put(formField.getId(), formField); 143 String modelId = formField.getModelId(); 144 m_fieldsByModelId.put(modelId, formField); 145 formField.getLayoutData().put("group", fieldGroup); 146 m_fieldsByGroup.put(fieldGroup, formField); 147 //m_widget.addField(formField); 148 149 } 150 151 /** 152 * Adds a form field to the form and sets its initial value.<p> 153 * 154 * @param fieldGroup the form field group key 155 * @param formField the form field which should be added 156 * @param initialValue the initial value of the form field, or null if the field shouldn't have an initial value 157 */ 158 public void addField(String fieldGroup, I_CmsFormField formField, String initialValue) { 159 160 if (initialValue != null) { 161 formField.getWidget().setFormValueAsString(initialValue); 162 } 163 addField(fieldGroup, formField); 164 } 165 166 /** 167 * Adds a new form reset handler to the form.<p> 168 * 169 * @param handler the new form reset handler 170 */ 171 public void addResetHandler(I_CmsFormResetHandler handler) { 172 173 m_resetHandlers.add(handler); 174 } 175 176 /** 177 * Collects all values from the form fields.<p> 178 * 179 * This method omits form fields whose values are null. 180 * 181 * @return a map of the form field values 182 */ 183 public Map<String, String> collectValues() { 184 185 Map<String, String> result = new HashMap<String, String>(); 186 for (Map.Entry<String, I_CmsFormField> entry : m_fields.entrySet()) { 187 String key = entry.getKey(); 188 String value = null; 189 I_CmsFormField field = entry.getValue(); 190 value = field.getModelValue(); 191 result.put(key, value); 192 } 193 return result; 194 } 195 196 /** 197 * Returns the set of names of fields which have been edited by the user in the current form.<p> 198 * 199 * @return the set of names of fields edited by the user 200 */ 201 public Set<String> getEditedFields() { 202 203 return m_editedFields; 204 205 } 206 207 /** 208 * Returns the form field with a given id.<p> 209 * 210 * @param id the id of the form field 211 * 212 * @return the form field with the given id, or null if no field was found 213 */ 214 public I_CmsFormField getField(String id) { 215 216 return m_fields.get(id); 217 } 218 219 /** 220 * Returns a map of this form's field, indexed by their field name.<p> 221 * 222 * @return a map of form fields 223 */ 224 public Map<String, I_CmsFormField> getFields() { 225 226 return Collections.unmodifiableMap(m_fields); 227 } 228 229 /** 230 * Returns the field group ids of the form.<p> 231 * 232 * @return the field groups 233 */ 234 public Collection<String> getGroups() { 235 236 return m_fieldsByGroup.keySet(); 237 } 238 239 /** 240 * Returns the form widget container.<p> 241 * 242 * @return the form widget container 243 */ 244 public A_CmsFormFieldPanel getWidget() { 245 246 return m_widget; 247 } 248 249 /** 250 * Passes this form's data to a form submit handler.<p> 251 * 252 * @param handler the form submit handler 253 */ 254 public void handleSubmit(I_CmsFormSubmitHandler handler) { 255 256 Map<String, String> values = collectValues(); 257 Set<String> editedFields = new HashSet<String>(getEditedFields()); 258 editedFields.retainAll(values.keySet()); 259 handler.onSubmitForm(this, values, editedFields); 260 } 261 262 /** 263 * Checks that no fields are invalid.<p> 264 * 265 * @return true if no fields are invalid. 266 */ 267 public boolean noFieldsInvalid() { 268 269 return noFieldsInvalid(m_fields.values()); 270 } 271 272 /** 273 * Returns true if none of the fields in a collection are marked as invalid.<p> 274 * 275 * @param fields the form fields 276 * 277 * @return true if none of the fields are invalid 278 */ 279 public boolean noFieldsInvalid(Collection<I_CmsFormField> fields) { 280 281 for (I_CmsFormField field : fields) { 282 if (field.getValidationStatus().equals(I_CmsFormField.ValidationStatus.invalid)) { 283 return false; 284 } 285 } 286 return true; 287 } 288 289 /** 290 * Removes all fields for the given group.<p> 291 * 292 * @param group the group for which the fields should be removed 293 */ 294 public void removeGroup(String group) { 295 296 if (m_fieldsByGroup.get(group) != null) { 297 List<I_CmsFormField> fieldsToRemove = new ArrayList<I_CmsFormField>(m_fieldsByGroup.get(group)); 298 for (I_CmsFormField field : fieldsToRemove) { 299 removeField(field); 300 } 301 } 302 m_fieldsByGroup.removeAll(group); 303 } 304 305 /** 306 * Renders all fields.<p> 307 */ 308 public void render() { 309 310 m_widget.renderFields(m_fields.values()); 311 } 312 313 /** 314 * Renders the fields of the given group.<p> 315 * 316 * @param group the field group 317 */ 318 public void renderGroup(String group) { 319 320 m_widget.rerenderFields(group, m_fieldsByGroup.get(group)); 321 322 } 323 324 /** 325 * Sets the form handler for this form.<p> 326 * 327 * @param handler the form handler 328 */ 329 public void setFormHandler(I_CmsFormHandler handler) { 330 331 m_formHandler = handler; 332 } 333 334 /** 335 * Sets the server-side form validator class to use.<p> 336 * 337 * @param validatorClass the form validator class name 338 */ 339 public void setValidatorClass(String validatorClass) { 340 341 m_validatorClass = validatorClass; 342 } 343 344 /** 345 * Sets the form widget container. 346 * 347 * @param widget the form widget container 348 */ 349 public void setWidget(A_CmsFormFieldPanel widget) { 350 351 assert m_widget == null; 352 m_widget = widget; 353 } 354 355 /** 356 * Performs an initial validation of all form fields.<p> 357 */ 358 public void validateAllFields() { 359 360 CmsValidationController validationController = new CmsValidationController( 361 m_fields.values(), 362 createValidationHandler()); 363 validationController.setFormValidator(m_validatorClass); 364 validationController.setFormValidatorConfig(createValidatorConfig()); 365 startValidation(validationController); 366 } 367 368 /** 369 * Validates the form fields and submits their values if the validation was successful.<p> 370 */ 371 public void validateAndSubmit() { 372 373 CmsValidationController validationController = new CmsValidationController( 374 m_fields.values(), 375 new I_CmsValidationHandler() { 376 377 /** 378 * @see org.opencms.gwt.client.validation.I_CmsValidationHandler#onValidationFinished(boolean) 379 */ 380 public void onValidationFinished(boolean ok) { 381 382 m_formHandler.onSubmitValidationResult(CmsForm.this, ok); 383 } 384 385 /** 386 * @see org.opencms.gwt.client.validation.I_CmsValidationHandler#onValidationResult(java.lang.String, org.opencms.gwt.shared.CmsValidationResult) 387 */ 388 public void onValidationResult(String field, CmsValidationResult result) { 389 390 updateFieldValidationStatus(field, result); 391 392 } 393 394 }); 395 validationController.setFormValidator(m_validatorClass); 396 validationController.setFormValidatorConfig(createValidatorConfig()); 397 startValidation(validationController); 398 } 399 400 /** 401 * Validates a single field.<p> 402 * 403 * @param field the field to validate 404 */ 405 public void validateField(final I_CmsFormField field) { 406 407 CmsValidationController validationController = new CmsValidationController(field, createValidationHandler()); 408 validationController.setFormValidator(m_validatorClass); 409 validationController.setFormValidatorConfig(createValidatorConfig()); 410 startValidation(validationController); 411 } 412 413 /** 414 * Returns the configuration string for the server side form validator.<p> 415 * 416 * @return the form validator configuration string 417 */ 418 protected String createValidatorConfig() { 419 420 return ""; 421 } 422 423 /** 424 * The default keypress event handling function for form fields.<p> 425 * 426 * @param field the form field for which the event has been fired 427 * 428 * @param keyCode the key code 429 */ 430 protected void defaultHandleKeyPress(I_CmsFormField field, int keyCode) { 431 432 I_CmsFormWidget widget = field.getWidget(); 433 if (keyCode == KeyCodes.KEY_ENTER) { 434 m_pressedEnter = true; 435 if (widget instanceof I_CmsHasBlur) { 436 // force a blur because not all browsers send a change event if the user just presses enter in a field 437 ((I_CmsHasBlur)widget).blur(); 438 } 439 // make sure that the flag is set to false again after the other events have been processed 440 Scheduler.get().scheduleDeferred(new ScheduledCommand() { 441 442 /** 443 * @see com.google.gwt.core.client.Scheduler.ScheduledCommand#execute() 444 */ 445 public void execute() { 446 447 m_pressedEnter = false; 448 } 449 }); 450 } 451 } 452 453 /** 454 * Default handler for value change events of form fields.<p> 455 * 456 * @param field the form field for which the event has been fired 457 * @param inhibitValidation prevents validation of the edited field 458 * 459 * @param newValue the new value 460 */ 461 protected void defaultHandleValueChange(I_CmsFormField field, String newValue, boolean inhibitValidation) { 462 463 m_editedFields.add(field.getId()); 464 I_CmsStringModel model = field.getModel(); 465 466 if (model != null) { 467 model.setValue(newValue, true); 468 } 469 field.setValidationStatus(I_CmsFormField.ValidationStatus.unknown); 470 471 // if the user presses enter, the keypressed event is fired before the change event, 472 // so we use a flag to keep track of whether enter was pressed. 473 if (!m_pressedEnter) { 474 if (!inhibitValidation) { 475 validateField(field); 476 } 477 } else { 478 validateAndSubmit(); 479 } 480 } 481 482 /** 483 * Gets the fields with a given model id.<p> 484 * 485 * @param modelId the model id 486 * 487 * @return the fields with the given model id 488 */ 489 protected Collection<I_CmsFormField> getFieldsByModelId(String modelId) { 490 491 return m_fieldsByModelId.get(modelId); 492 } 493 494 /** 495 * Updates the field validation status.<p> 496 * 497 * @param field the form field 498 * @param result the validation result 499 */ 500 protected void updateFieldValidationStatus(I_CmsFormField field, CmsValidationResult result) { 501 502 if (result.hasNewValue()) { 503 if (field.getModel() != null) { 504 field.getModel().setValue(result.getNewValue(), true); 505 } 506 field.getWidget().setFormValueAsString(result.getNewValue()); 507 } 508 String errorMessage = result.getErrorMessage(); 509 field.getWidget().setErrorMessage(result.getErrorMessage()); 510 field.setValidationStatus( 511 errorMessage == null ? I_CmsFormField.ValidationStatus.valid : I_CmsFormField.ValidationStatus.invalid); 512 } 513 514 /** 515 * Applies a validation result to a form field.<p> 516 * 517 * @param fieldId the field id to which the validation result should be applied 518 * @param result the result of the validation operation 519 */ 520 protected void updateFieldValidationStatus(String fieldId, CmsValidationResult result) { 521 522 I_CmsFormField field = m_fields.get(fieldId); 523 updateFieldValidationStatus(field, result); 524 } 525 526 /** 527 * Updates the model validation status.<p> 528 * 529 * @param modelId the model id 530 * @param result the validation result 531 */ 532 protected void updateModelValidationStatus(String modelId, CmsValidationResult result) { 533 534 Collection<I_CmsFormField> fields = getFieldsByModelId(modelId); 535 for (I_CmsFormField field : fields) { 536 updateFieldValidationStatus(field, result); 537 } 538 539 } 540 541 /** 542 * Creates a validation handler which updates the OK button state when validation results come in.<p> 543 * 544 * @return a validation handler 545 */ 546 private I_CmsValidationHandler createValidationHandler() { 547 548 return new I_CmsValidationHandler() { 549 550 /** 551 * @see org.opencms.gwt.client.validation.I_CmsValidationHandler#onValidationFinished(boolean) 552 */ 553 public void onValidationFinished(boolean ok) { 554 555 m_formHandler.onValidationResult(CmsForm.this, noFieldsInvalid(m_fields.values())); 556 } 557 558 /** 559 * @see org.opencms.gwt.client.validation.I_CmsValidationHandler#onValidationResult(java.lang.String, org.opencms.gwt.shared.CmsValidationResult) 560 */ 561 public void onValidationResult(String fieldId, CmsValidationResult result) { 562 563 I_CmsFormField field = m_fields.get(fieldId); 564 String modelId = field.getModelId(); 565 updateModelValidationStatus(modelId, result); 566 } 567 }; 568 } 569 570 /** 571 * Initializes the widget for a new form field.<p> 572 * 573 * @param formField the form field whose widget should be initialized 574 */ 575 @SuppressWarnings({"rawtypes", "unchecked"}) 576 private void initializeFormFieldWidget(final I_CmsFormField formField) { 577 578 final I_CmsFormWidget widget = formField.getWidget(); 579 if (widget instanceof HasValueChangeHandlers) { 580 ((HasValueChangeHandlers)widget).addValueChangeHandler(new ValueChangeHandler() { 581 582 /** 583 * @see com.google.gwt.event.logical.shared.ValueChangeHandler#onValueChange(ValueChangeEvent event) 584 */ 585 public void onValueChange(ValueChangeEvent event) { 586 587 boolean inhibitValidation = false; 588 if (event instanceof CmsExtendedValueChangeEvent) { 589 CmsExtendedValueChangeEvent extEvent = (CmsExtendedValueChangeEvent)event; 590 inhibitValidation = extEvent.isInhibitValidation(); 591 } 592 Object eventValue = event.getValue(); 593 if ((eventValue instanceof String) || (event.getValue() == null)) { 594 // only makes sense for strings 595 defaultHandleValueChange(formField, (String)(event.getValue()), inhibitValidation); 596 } else if (eventValue instanceof Date) { 597 defaultHandleValueChange(formField, "" + ((Date)eventValue).getTime(), inhibitValidation); 598 } else if (eventValue instanceof Boolean) { 599 defaultHandleValueChange(formField, "" + eventValue, inhibitValidation); 600 } 601 602 } 603 }); 604 } 605 606 if (widget instanceof HasKeyPressHandlers) { 607 ((HasKeyPressHandlers)widget).addKeyPressHandler(new KeyPressHandler() { 608 609 /** 610 * @see com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google.gwt.event.dom.client.KeyPressEvent) 611 */ 612 public void onKeyPress(KeyPressEvent event) { 613 614 int keyCode = event.getNativeEvent().getKeyCode(); 615 defaultHandleKeyPress(formField, keyCode); 616 } 617 }); 618 } 619 } 620 621 /** 622 * Removes a field from the form's data structure (but not from the form's widget!).<p> 623 * 624 * @param field the field to remove 625 */ 626 private void removeField(I_CmsFormField field) { 627 628 String id = field.getId(); 629 m_fields.remove(id); 630 // *not* removing the field id from m_editedFields, because a field of the same id may 631 // be added later 632 m_fieldsByModelId.remove(field.getModelId(), field); 633 field.unbind(); 634 } 635 636 /** 637 * Starts the validation of the form.<p> 638 * 639 * @param validationController the validation controller to use for the validation 640 */ 641 private void startValidation(CmsValidationController validationController) { 642 643 validationController.startValidation(); 644 } 645}