/*
* This library is part of OpenCms -
* the Open Source Content Management System
*
* Copyright (c) Alkacon Software GmbH (http://www.alkacon.com)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* For further information about Alkacon Software, please see the
* company website: http://www.alkacon.com
*
* For further information about OpenCms, please see the
* project website: http://www.opencms.org
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.opencms.gwt.client.ui.input.form;
import org.opencms.gwt.client.ui.input.CmsTextBox;
import org.opencms.gwt.client.ui.input.I_CmsFormField;
import org.opencms.gwt.client.ui.input.I_CmsFormWidget;
import org.opencms.gwt.client.ui.input.I_CmsHasBlur;
import org.opencms.gwt.client.ui.input.I_CmsStringModel;
import org.opencms.gwt.client.validation.CmsValidationController;
import org.opencms.gwt.client.validation.I_CmsValidationHandler;
import org.opencms.gwt.shared.CmsValidationResult;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasKeyPressHandlers;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Panel;
/**
*
* This class acts as a container for form fields.<p>
*
* It is also responsible for collecting and validating the values of the form fields.
*
* @since 8.0.0
*
*/
public class CmsForm {
/** The set of fields which have been edited. */
protected Set<String> m_editedFields = new HashSet<String>();
/** A map from field ids to the corresponding widgets. */
protected Map<String, I_CmsFormField> m_fields = new LinkedHashMap<String, I_CmsFormField>();
/** A reference to the dialog this form is contained in. */
protected I_CmsFormDialog m_formDialog;
/** The form handler. */
protected I_CmsFormHandler m_formHandler;
/** A flag which indicates whether the user has pressed enter in a widget. */
protected boolean m_pressedEnter;
/** The tab for advanced form fields. */
private FlowPanel m_advancedTab = new FlowPanel();
/** The tab for basic form fields. */
private FlowPanel m_basicTab = new FlowPanel();
/** A multimap from field groups to fields. */
private Multimap<String, I_CmsFormField> m_fieldsByGroup = ArrayListMultimap.create();
/** The fields indexed by model id. */
private Multimap<String, I_CmsFormField> m_fieldsByModelId = ArrayListMultimap.create();
/** The list of form reset handlers. */
private List<I_CmsFormResetHandler> m_resetHandlers = new ArrayList<I_CmsFormResetHandler>();
/** The server-side form validator class to use. */
private String m_validatorClass;
/** The form widget container. */
private A_CmsFormFieldPanel m_widget;
/**
* Creates a new form with an existing form widget container.<p>
*
* @param panel the form widget container
*/
public CmsForm(A_CmsFormFieldPanel panel) {
m_widget = panel;
}
/**
* Creates a new form and optionally sets the form widget container to a simple form field panel.<p>
*
* @param initPanel if true, initializes the form widget container
*/
public CmsForm(boolean initPanel) {
if (initPanel) {
setWidget(new CmsSimpleFormFieldPanel());
}
}
/**
* Adds a form field.<p>
*
* @param field the field to add
* @param initialValue the initial field value
*/
public void addField(I_CmsFormField field, String initialValue) {
addField("", field, initialValue);
}
/**
* Adds a form field to the form.<p>
*
* @param fieldGroup the form field group key
* @param formField the form field which should be added
*/
public void addField(String fieldGroup, final I_CmsFormField formField) {
initializeFormFieldWidget(formField);
m_fields.put(formField.getId(), formField);
String modelId = formField.getModelId();
m_fieldsByModelId.put(modelId, formField);
formField.getLayoutData().put("group", fieldGroup);
m_fieldsByGroup.put(fieldGroup, formField);
//m_widget.addField(formField);
}
/**
* Adds a form field to the form and sets its initial value.<p>
*
* @param fieldGroup the form field group key
* @param formField the form field which should be added
* @param initialValue the initial value of the form field, or null if the field shouldn't have an initial value
*/
public void addField(String fieldGroup, I_CmsFormField formField, String initialValue) {
if (initialValue != null) {
formField.getWidget().setFormValueAsString(initialValue);
}
addField(fieldGroup, formField);
}
/**
* Adds a new form reset handler to the form.<p>
*
* @param handler the new form reset handler
*/
public void addResetHandler(I_CmsFormResetHandler handler) {
m_resetHandlers.add(handler);
}
/**
* Collects all values from the form fields.<p>
*
* This method omits form fields whose values are null.
*
* @return a map of the form field values
*/
public Map<String, String> collectValues() {
Map<String, String> result = new HashMap<String, String>();
for (Map.Entry<String, I_CmsFormField> entry : m_fields.entrySet()) {
String key = entry.getKey();
String value = null;
I_CmsFormField field = entry.getValue();
value = field.getModelValue();
result.put(key, value);
}
return result;
}
/**
* Returns the set of names of fields which have been edited by the user in the current form.<p>
*
* @return the set of names of fields edited by the user
*/
public Set<String> getEditedFields() {
return m_editedFields;
}
/**
* Returns the form field with a given id.<p>
*
* @param id the id of the form field
*
* @return the form field with the given id, or null if no field was found
*/
public I_CmsFormField getField(String id) {
return m_fields.get(id);
}
/**
* Returns a map of this form's field, indexed by their field name.<p>
*
* @return a map of form fields
*/
public Map<String, I_CmsFormField> getFields() {
return Collections.unmodifiableMap(m_fields);
}
/**
* Returns the form widget container.<p>
*
* @return the form widget container
*/
public A_CmsFormFieldPanel getWidget() {
return m_widget;
}
/**
* Returns true if none of the fields in a collection are marked as invalid.<p>
*
* @param fields the form fields
*
* @return true if none of the fields are invalid
*/
public boolean noFieldsInvalid(Collection<I_CmsFormField> fields) {
for (I_CmsFormField field : fields) {
if (field.getValidationStatus().equals(I_CmsFormField.ValidationStatus.invalid)) {
return false;
}
}
return true;
}
/**
* Removes all fields for the given group.<p>
*
* @param group the group for which the fields should be removed
*/
public void removeGroup(String group) {
List<I_CmsFormField> fieldsToRemove = Lists.newArrayList(m_fieldsByGroup.get(group));
for (I_CmsFormField field : fieldsToRemove) {
removeField(field);
}
m_fieldsByGroup.removeAll(group);
}
/**
* Renders all fields.<p>
*/
public void render() {
m_widget.renderFields(m_fields.values());
}
/**
* Renders the fields of the given group.<p>
*
* @param group the field group
*/
public void renderGroup(String group) {
m_widget.rerenderFields(group, m_fieldsByGroup.get(group));
}
/**
* Sets the form dialog in which this form is being used.<p>
*
* @param dialog the form dialog
*/
public void setFormDialog(I_CmsFormDialog dialog) {
m_formDialog = dialog;
}
/**
* Sets the form handler for this form.<p>
*
* @param handler the form handler
*/
public void setFormHandler(I_CmsFormHandler handler) {
m_formHandler = handler;
}
/**
* Sets the server-side form validator class to use.<p>
*
* @param validatorClass the form validator class name
*/
public void setValidatorClass(String validatorClass) {
m_validatorClass = validatorClass;
}
/**
* Sets the form widget container.
*
* @param widget the form widget container
*/
public void setWidget(A_CmsFormFieldPanel widget) {
assert m_widget == null;
m_widget = widget;
}
/**
* Performs an initial validation of all form fields.<p>
*/
public void validateAllFields() {
CmsValidationController validationController = new CmsValidationController(
m_fields.values(),
createValidationHandler());
validationController.setFormValidator(m_validatorClass);
validationController.setFormValidatorConfig(createValidatorConfig());
startValidation(validationController);
}
/**
* Validates the form fields and submits their values if the validation was successful.<p>
*/
public void validateAndSubmit() {
CmsValidationController validationController = new CmsValidationController(
m_fields.values(),
new I_CmsValidationHandler() {
/**
* @see org.opencms.gwt.client.validation.I_CmsValidationHandler#onValidationFinished(boolean)
*/
public void onValidationFinished(boolean ok) {
if (ok) {
m_formDialog.closeDialog();
Map<String, String> values = collectValues();
Set<String> editedFields = new HashSet<String>(m_editedFields);
editedFields.retainAll(values.keySet());
m_formHandler.onSubmitForm(values, editedFields);
} else {
m_formDialog.setOkButtonEnabled(noFieldsInvalid(m_fields.values()));
}
}
/**
* @see org.opencms.gwt.client.validation.I_CmsValidationHandler#onValidationResult(java.lang.String, org.opencms.gwt.shared.CmsValidationResult)
*/
public void onValidationResult(String field, CmsValidationResult result) {
updateFieldValidationStatus(field, result);
}
});
validationController.setFormValidator(m_validatorClass);
validationController.setFormValidatorConfig(createValidatorConfig());
startValidation(validationController);
}
/**
* Validates a single field.<p>
*
* @param field the field to validate
*/
public void validateField(final I_CmsFormField field) {
CmsValidationController validationController = new CmsValidationController(field, createValidationHandler());
validationController.setFormValidator(m_validatorClass);
validationController.setFormValidatorConfig(createValidatorConfig());
startValidation(validationController);
}
/**
* Returns the configuration string for the server side form validator.<p>
*
* @return the form validator configuration string
*/
protected String createValidatorConfig() {
return "";
}
/**
* The default keypress event handling function for form fields.<p>
*
* @param field the form field for which the event has been fired
*
* @param keyCode the key code
*/
protected void defaultHandleKeyPress(I_CmsFormField field, int keyCode) {
I_CmsFormWidget widget = field.getWidget();
if (keyCode == KeyCodes.KEY_ENTER) {
m_pressedEnter = true;
if (widget instanceof I_CmsHasBlur) {
// force a blur because not all browsers send a change event if the user just presses enter in a field
((I_CmsHasBlur)widget).blur();
}
// make sure that the flag is set to false again after the other events have been processed
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
/**
* @see com.google.gwt.core.client.Scheduler.ScheduledCommand#execute()
*/
public void execute() {
m_pressedEnter = false;
}
});
}
}
/**
* Default handler for value change events of form fields.<p>
*
* @param field the form field for which the event has been fired
*
* @param newValue the new value
*/
protected void defaultHandleValueChange(I_CmsFormField field, String newValue) {
m_editedFields.add(field.getId());
I_CmsStringModel model = field.getModel();
if (model != null) {
model.setValue(newValue, true);
}
field.setValidationStatus(I_CmsFormField.ValidationStatus.unknown);
// if the user presses enter, the keypressed event is fired before the change event,
// so we use a flag to keep track of whether enter was pressed.
if (!m_pressedEnter) {
validateField(field);
} else {
validateAndSubmit();
}
}
/**
* Gets the fields with a given model id.<p>
*
* @param modelId the model id
*
* @return the fields with the given model id
*/
protected Collection<I_CmsFormField> getFieldsByModelId(String modelId) {
return m_fieldsByModelId.get(modelId);
}
/**
* Returns either the basic or advanced tab based on a boolean value.<p>
*
* @param advanced if true, the advanced tab will be returned, else the basic tab
*
* @return the basic or advanced tab
*/
protected Panel getPanel(boolean advanced) {
return advanced ? m_advancedTab : m_basicTab;
}
/**
* Updates the field validation status.<p>
*
* @param field the form field
* @param result the validation result
*/
protected void updateFieldValidationStatus(I_CmsFormField field, CmsValidationResult result) {
if (result.hasNewValue()) {
if (field.getModel() != null) {
field.getModel().setValue(result.getNewValue(), true);
}
field.getWidget().setFormValueAsString(result.getNewValue());
}
String errorMessage = result.getErrorMessage();
field.getWidget().setErrorMessage(result.getErrorMessage());
field.setValidationStatus(errorMessage == null
? I_CmsFormField.ValidationStatus.valid
: I_CmsFormField.ValidationStatus.invalid);
}
/**
* Applies a validation result to a form field.<p>
*
* @param fieldId the field id to which the validation result should be applied
* @param result the result of the validation operation
*/
protected void updateFieldValidationStatus(String fieldId, CmsValidationResult result) {
I_CmsFormField field = m_fields.get(fieldId);
updateFieldValidationStatus(field, result);
}
/**
* Updates the model validation status.<p>
*
* @param modelId the model id
* @param result the validation result
*/
protected void updateModelValidationStatus(String modelId, CmsValidationResult result) {
Collection<I_CmsFormField> fields = getFieldsByModelId(modelId);
for (I_CmsFormField field : fields) {
updateFieldValidationStatus(field, result);
}
}
/**
* Creates a validation handler which updates the OK button state when validation results come in.<p>
*
* @return a validation handler
*/
private I_CmsValidationHandler createValidationHandler() {
return new I_CmsValidationHandler() {
/**
* @see org.opencms.gwt.client.validation.I_CmsValidationHandler#onValidationFinished(boolean)
*/
public void onValidationFinished(boolean ok) {
m_formDialog.setOkButtonEnabled(noFieldsInvalid(m_fields.values()));
}
/**
* @see org.opencms.gwt.client.validation.I_CmsValidationHandler#onValidationResult(java.lang.String, org.opencms.gwt.shared.CmsValidationResult)
*/
public void onValidationResult(String fieldId, CmsValidationResult result) {
I_CmsFormField field = m_fields.get(fieldId);
String modelId = field.getModelId();
updateModelValidationStatus(modelId, result);
}
};
}
/**
* Initializes the widget for a new form field.<p>
*
* @param formField the form field whose widget should be initialized
*/
@SuppressWarnings( {"rawtypes", "unchecked"})
private void initializeFormFieldWidget(final I_CmsFormField formField) {
final I_CmsFormWidget widget = formField.getWidget();
if (widget instanceof HasValueChangeHandlers) {
((HasValueChangeHandlers)widget).addValueChangeHandler(new ValueChangeHandler() {
/**
* @see com.google.gwt.event.logical.shared.ValueChangeHandler#onValueChange(ValueChangeEvent event)
*/
public void onValueChange(ValueChangeEvent event) {
defaultHandleValueChange(formField, widget.getFormValueAsString());
}
});
}
if (widget instanceof HasKeyPressHandlers) {
((HasKeyPressHandlers)widget).addKeyPressHandler(new KeyPressHandler() {
/**
* @see com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google.gwt.event.dom.client.KeyPressEvent)
*/
public void onKeyPress(KeyPressEvent event) {
int keyCode = event.getNativeEvent().getKeyCode();
defaultHandleKeyPress(formField, keyCode);
}
});
}
if (widget instanceof CmsTextBox) {
final CmsTextBox textbox = (CmsTextBox)widget;
textbox.addClickHandler(new ClickHandler() {
/**
* @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent)
*/
public void onClick(ClickEvent event) {
//ghost mode will still be enabled when you click on the field, but the style will be set to normal
textbox.setGhostStyleEnabled(false);
}
});
}
}
/**
* Removes a field from the form's data structure (but not from the form's widget!).<p>
*
* @param field the field to remove
*/
private void removeField(I_CmsFormField field) {
String id = field.getId();
m_fields.remove(id);
// *not* removing the field id from m_editedFields, because a field of the same id may
// be added later
m_fieldsByModelId.remove(field.getModelId(), field);
field.unbind();
}
/**
* Starts the validation of the form.<p>
*
* @param validationController the validation controller to use for the validation
*/
private void startValidation(CmsValidationController validationController) {
validationController.startValidation();
}
}