From f584f83e4c3c3c89ef36fe9f4a27907e60969bee Mon Sep 17 00:00:00 2001 From: Gavin Cornwell Date: Wed, 19 Apr 2006 08:30:47 +0000 Subject: [PATCH] Intial version of property sheet constraints support, no regex or list of values suppport yet though. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@2664 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/messages/webclient.properties | 7 + .../bean/generator/AssociationGenerator.java | 30 +- .../generator/BaseComponentGenerator.java | 717 +++++++++++++++--- .../generator/CategoryPickerGenerator.java | 58 -- .../generator/CategorySelectorGenerator.java | 51 ++ .../web/bean/generator/CheckboxGenerator.java | 72 +- .../generator/ChildAssociationGenerator.java | 33 +- .../bean/generator/DatePickerGenerator.java | 58 +- .../generator/DateTimePickerGenerator.java | 4 +- .../bean/generator/IComponentGenerator.java | 10 +- .../web/bean/generator/LabelGenerator.java | 7 +- .../generator/SpaceIconPickerGenerator.java | 53 +- .../bean/generator/TextFieldGenerator.java | 63 +- .../web/bean/spaces/CreateSpaceWizard.java | 92 ++- .../web/bean/wizard/BaseContentWizard.java | 40 +- .../alfresco/web/ui/repo/RepoConstants.java | 2 +- .../property/BaseAssociationEditor.java | 2 - .../component/property/PropertySheetItem.java | 26 +- .../component/property/UIAssociation.java | 16 +- .../property/UIAssociationEditor.java | 1 + .../property/UIChildAssociation.java | 16 +- .../property/UIChildAssociationEditor.java | 1 + .../repo/component/property/UIProperty.java | 28 +- .../component/property/UIPropertySheet.java | 290 ++++++- .../property/AssociationRenderer.java | 89 +-- .../property/ChildAssociationRenderer.java | 89 +-- .../renderer/property/PropertyRenderer.java | 90 +-- .../property/PropertySheetItemRenderer.java | 116 +++ .../tag/property/PropertySheetGridTag.java | 22 + source/web/WEB-INF/faces-config-beans.xml | 6 +- source/web/WEB-INF/repo.tld | 12 + .../jsp/dialog/edit-document-properties.jsp | 2 +- source/web/scripts/validation.js | 85 +++ 33 files changed, 1419 insertions(+), 769 deletions(-) delete mode 100644 source/java/org/alfresco/web/bean/generator/CategoryPickerGenerator.java create mode 100644 source/java/org/alfresco/web/bean/generator/CategorySelectorGenerator.java create mode 100644 source/java/org/alfresco/web/ui/repo/renderer/property/PropertySheetItemRenderer.java create mode 100644 source/web/scripts/validation.js diff --git a/config/alfresco/messages/webclient.properties b/config/alfresco/messages/webclient.properties index a542ea4083..f0c90c704c 100644 --- a/config/alfresco/messages/webclient.properties +++ b/config/alfresco/messages/webclient.properties @@ -959,3 +959,10 @@ delete_companyroot_confirm=WARNING: This folder is a special folder accessed by status_space_created=Successfully created space ''{0}''. status_space_deleted=Successfully deleted space ''{0}''. status_space_updated=Successfully updated space ''{0}''. + +# Validation Messages +validation_mandatory={0} is a mandatory field. +validation_string_length={0} must be between {1} and {2} characters in length. +validation_regex={0} must match the expression {1}. +validation_regex_not_match={0} must not match the expression {1}. +validation_numeric_range={0} must be between {1} and {2}. diff --git a/source/java/org/alfresco/web/bean/generator/AssociationGenerator.java b/source/java/org/alfresco/web/bean/generator/AssociationGenerator.java index 4184556df3..f7cbafe3b9 100644 --- a/source/java/org/alfresco/web/bean/generator/AssociationGenerator.java +++ b/source/java/org/alfresco/web/bean/generator/AssociationGenerator.java @@ -3,11 +3,9 @@ package org.alfresco.web.bean.generator; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; -import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.web.app.servlet.FacesHelper; import org.alfresco.web.ui.repo.RepoConstants; import org.alfresco.web.ui.repo.component.property.PropertySheetItem; -import org.alfresco.web.ui.repo.component.property.UIAssociationEditor; import org.alfresco.web.ui.repo.component.property.UIPropertySheet; /** @@ -25,26 +23,14 @@ public class AssociationGenerator extends BaseComponentGenerator return component; } - - public UIComponent generate(FacesContext context, UIPropertySheet propertySheet, - PropertySheetItem item) + + @Override + protected void setupMandatoryValidation(FacesContext context, UIPropertySheet propertySheet, + PropertySheetItem item, UIComponent component, boolean realTimeChecking) { - // generate the standard association editor - UIAssociationEditor component = (UIAssociationEditor)generate(context, item.getName()); - - AssociationDefinition assocDef = this.getAssociationDefinition(context, - propertySheet.getNode(), item.getName()); - - // set the association name and set to disabled if appropriate - component.setAssociationName(assocDef.getName().toString()); - if (propertySheet.inEditMode() == false || item.isReadOnly() || assocDef.isProtected()) - { - component.setDisabled(true); - } - - // setup the converter if one was specified - setupConverter(context, propertySheet, item, component); - - return component; + // TODO: the association editor component needs to use the + // 'current_value' hidden field rather than the standard + // 'value' field as this is always null (it's used internally + // by the component) for now disable mandatory checks completely } } diff --git a/source/java/org/alfresco/web/bean/generator/BaseComponentGenerator.java b/source/java/org/alfresco/web/bean/generator/BaseComponentGenerator.java index 0ba8974cca..2499884595 100644 --- a/source/java/org/alfresco/web/bean/generator/BaseComponentGenerator.java +++ b/source/java/org/alfresco/web/bean/generator/BaseComponentGenerator.java @@ -1,5 +1,9 @@ package org.alfresco.web.bean.generator; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; + import javax.faces.FacesException; import javax.faces.component.UIComponent; import javax.faces.component.UIOutput; @@ -7,7 +11,12 @@ import javax.faces.context.FacesContext; import javax.faces.convert.Converter; import javax.faces.el.ValueBinding; +import org.alfresco.repo.dictionary.constraint.NumericRangeConstraint; +import org.alfresco.repo.dictionary.constraint.RegexConstraint; +import org.alfresco.repo.dictionary.constraint.StringLengthConstraint; import org.alfresco.service.cmr.dictionary.AssociationDefinition; +import org.alfresco.service.cmr.dictionary.Constraint; +import org.alfresco.service.cmr.dictionary.ConstraintDefinition; import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.web.app.Application; import org.alfresco.web.app.servlet.FacesHelper; @@ -15,9 +24,11 @@ import org.alfresco.web.bean.repository.DataDictionary; import org.alfresco.web.bean.repository.Node; import org.alfresco.web.ui.common.ComponentConstants; import org.alfresco.web.ui.repo.RepoConstants; +import org.alfresco.web.ui.repo.component.property.BaseAssociationEditor; import org.alfresco.web.ui.repo.component.property.PropertySheetItem; import org.alfresco.web.ui.repo.component.property.UIProperty; import org.alfresco.web.ui.repo.component.property.UIPropertySheet; +import org.alfresco.web.ui.repo.component.property.UIPropertySheet.ClientValidation; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.web.jsf.FacesContextUtils; @@ -26,8 +37,615 @@ public abstract class BaseComponentGenerator implements IComponentGenerator { private static Log logger = LogFactory.getLog(BaseComponentGenerator.class); + protected enum ControlType { FIELD, SELECTOR; } + private DataDictionary dataDictionary; + @SuppressWarnings("unchecked") + public UIComponent generateAndAdd(FacesContext context, UIPropertySheet propertySheet, + PropertySheetItem item) + { + UIComponent component = null; + + if (item instanceof UIProperty) + { + // get the property definition + PropertyDefinition propertyDef = getPropertyDefinition(context, + propertySheet.getNode(), item.getName()); + + // create the component and add it to the property sheet + component = createComponent(context, propertySheet, item); + + // setup the component for multi value editing if necessary + component = setupMultiValuePropertyIfNecessary(context, propertySheet, + item, propertyDef, component); + + // setup common aspects of the property i.e. value binding + setupProperty(context, propertySheet, item, propertyDef, component); + + // add the component now, it needs to be added before the validations + // are setup as we need access to the component id, which in turn needs + // to have a parent to get the correct id + item.getChildren().add(component); + + // setup the component for mandatory validation if necessary + setupMandatoryPropertyIfNecessary(context, propertySheet, item, + propertyDef, component); + + // setup any constraints the property has + setupConstraints(context, propertySheet, item, propertyDef, component); + + // setup any converter the property needs + setupConverter(context, propertySheet, item, propertyDef, component); + } + else + { + // get the association definition + AssociationDefinition assocationDef = this.getAssociationDefinition(context, + propertySheet.getNode(), item.getName()); + + // create the component and add it to the property sheet + component = createComponent(context, propertySheet, item); + + // setup common aspects of the association i.e. value binding + setupAssociation(context, propertySheet, item, assocationDef, component); + + // add the component now, it needs to be added before the validations + // are setup as we need access to the component id, which needs have a + // parent to get the correct id + item.getChildren().add(component); + + // setup the component for mandatory validation if necessary + setupMandatoryAssociationIfNecessary(context, propertySheet, item, + assocationDef, component); + + // setup any converter the association needs + setupConverter(context, propertySheet, item, assocationDef, component); + } + + return component; + } + + + /** + * Creates the component for the given proerty sheet item. + * + * @param context FacesContext + * @param propertySheet The property sheet being generated + * @param item The property or association being generated + * @return The newly created component + */ + @SuppressWarnings("unchecked") + protected UIComponent createComponent(FacesContext context, UIPropertySheet propertySheet, + PropertySheetItem item) + { + UIComponent component = null; + + if (item instanceof UIProperty) + { + if (propertySheet.inEditMode()) + { + // use the standard component in edit mode + component = generate(context, item.getName()); + } + else + { + // create an output text component in view mode + component = createOutputTextComponent(context, item.getName()); + } + } + else + { + // create the standard association component + component = generate(context, item.getName()); + } + + return component; + } + + /** + * Creates the converter with the given id and adds it to the component. + * + * @param context FacesContext + * @param converterId The name of the converter to create + * @param component The component to add the converter to + */ + protected void createAndSetConverter(FacesContext context, String converterId, + UIComponent component) + { + if (converterId != null && component instanceof UIOutput) + { + try + { + Converter conv = context.getApplication().createConverter(converterId); + ((UIOutput)component).setConverter(conv); + } + catch (NullPointerException npe) + { + // workaround a NPE bug in MyFaces + logger.warn("Converter " + converterId + " could not be applied"); + } + catch (FacesException fe) + { + logger.warn("Converter " + converterId + " could not be applied"); + } + } + } + + /** + * Creates an output text component. + * + * @param context FacesContext + * @param id Optional id to set + * @return The new component + */ + protected UIOutput createOutputTextComponent(FacesContext context, String id) + { + UIOutput component = (UIOutput)context.getApplication().createComponent( + ComponentConstants.JAVAX_FACES_OUTPUT); + + component.setRendererType(ComponentConstants.JAVAX_FACES_TEXT); + FacesHelper.setupComponentId(context, component, id); + + return component; + } + + /** + * Sets up the property component i.e. setting the value binding + * + * @param context FacesContext + * @param propertySheet The property sheet + * @param item The parent component + * @param propertyDef The property definition + * @param component The component representing the property + */ + @SuppressWarnings("unchecked") + protected void setupProperty(FacesContext context, UIPropertySheet propertySheet, + PropertySheetItem item, PropertyDefinition propertyDef, UIComponent component) + { + // create and set the value binding + ValueBinding vb = null; + + if (propertyDef != null) + { + vb = context.getApplication().createValueBinding( + "#{" + propertySheet.getVar() + ".properties[\"" + + propertyDef.getName().toString() + "\"]}"); + } + else + { + vb = context.getApplication().createValueBinding( + "#{" + propertySheet.getVar() + ".properties[\"" + + item.getName() + "\"]}"); + } + + component.setValueBinding("value", vb); + + // disable the component if it is read only or protected + // or if the property sheet is in view mode + if (propertySheet.inEditMode() == false || item.isReadOnly() || + (propertyDef != null && propertyDef.isProtected())) + { + component.getAttributes().put("disabled", Boolean.TRUE); + } + } + + /** + * Sets up the association component i.e. setting the value binding + * + * @param context FacesContext + * @param propertySheet The property sheet + * @param item The parent component + * @param associationDef The association definition + * @param component The component representing the association + */ + @SuppressWarnings("unchecked") + protected void setupAssociation(FacesContext context, UIPropertySheet propertySheet, + PropertySheetItem item, AssociationDefinition associationDef, UIComponent component) + { + // create and set the value binding + ValueBinding vb = context.getApplication().createValueBinding( + "#{" + propertySheet.getVar() + "}"); + component.setValueBinding("value", vb); + + // set the association name and set to disabled if appropriate + ((BaseAssociationEditor)component).setAssociationName( + associationDef.getName().toString()); + + // disable the component if it is read only or protected + // or if the property sheet is in view mode + if (propertySheet.inEditMode() == false || item.isReadOnly() || + (associationDef != null && associationDef.isProtected())) + { + component.getAttributes().put("disabled", Boolean.TRUE); + } + } + + /** + * Creates a wrapper component around the given component to enable the user + * to edit multiple values. + * + * @param context FacesContext + * @param propertySheet The property sheet being generated + * @param property The property being generated + * @param propertyDef The data dictionary definition for the property + * @param component The component representing the property + * @return A wrapped component if the property is multi-valued or the + * original component if it is not multi-valued + */ + @SuppressWarnings("unchecked") + protected UIComponent setupMultiValuePropertyIfNecessary(FacesContext context, + UIPropertySheet propertySheet, PropertySheetItem property, + PropertyDefinition propertyDef, UIComponent component) + { + UIComponent multiValueComponent = component; + + if (propertySheet.inEditMode() && property.isReadOnly() == false && + propertyDef != null && propertyDef.isProtected() == false && + propertyDef.isMultiValued()) + { + // if the property is multi-valued create a multi value editor wrapper component + String id = "multi_" + property.getName(); + multiValueComponent = context.getApplication().createComponent( + RepoConstants.ALFRESCO_FACES_MULTIVALUE_EDITOR); + FacesHelper.setupComponentId(context, multiValueComponent, id); + + // set the renderer depending on whether the item is a 'field' or a 'selector' + if (getControlType() == ControlType.FIELD) + { + multiValueComponent.setRendererType(RepoConstants.ALFRESCO_FACES_FIELD_RENDERER); + } + else + { + multiValueComponent.setRendererType(RepoConstants.ALFRESCO_FACES_SELECTOR_RENDERER); + + // set the value binding for the wrapped component and the lastItemAdded attribute of + // the multi select component, needs to point somewhere that can hold any object, it + // will store the item last added by the user. + String expr = "#{MultiValueEditorBean.lastItemsAdded['" + + property.getName() + "']}"; + ValueBinding vb = context.getApplication().createValueBinding(expr); + multiValueComponent.setValueBinding("lastItemAdded", vb); + component.setValueBinding("value", vb); + } + + // add the original component as a child of the wrapper + multiValueComponent.getChildren().add(component); + } + + return multiValueComponent; + } + + /** + * Sets up a mandatory validation rule for the given property. + * + * @param context FacesContext + * @param propertySheet The property sheet being generated + * @param property The property being generated + * @param propertyDef The data dictionary definition of the property + * @param component The component representing the property + */ + @SuppressWarnings("unchecked") + protected void setupMandatoryPropertyIfNecessary(FacesContext context, + UIPropertySheet propertySheet, PropertySheetItem property, + PropertyDefinition propertyDef, UIComponent component) + { + // only setup validations if the property sheet is in edit mode, + // validation is enabled and the property is declared as mandatory + if (propertySheet.inEditMode() && propertySheet.isValidationEnabled() && + propertyDef != null && propertyDef.isMandatory()) + { + setupMandatoryValidation(context, propertySheet, property, component, false); + setupMandatoryMarker(context, property); + } + } + + /** + * Sets up a mandatory validation rule for the given association. + * + * @param context FacesContext + * @param propertySheet The property sheet being generated + * @param association The association being generated + * @param associationDef The data dictionary definition of the association + * @param component The component representing the association + */ + protected void setupMandatoryAssociationIfNecessary(FacesContext context, + UIPropertySheet propertySheet, PropertySheetItem association, + AssociationDefinition associationDef, UIComponent component) + { + // only setup validations if the property sheet is in edit mode, + // validation is enabled and the association is declared as mandatory + if (propertySheet.inEditMode() && propertySheet.isValidationEnabled() && + associationDef != null && associationDef.isTargetMandatory()) + { + setupMandatoryValidation(context, propertySheet, association, component, false); + setupMandatoryMarker(context, association); + } + } + + /** + * Sets up a client mandatory validation rule with the property + * sheet for the given item. + * + * @param context FacesContext + * @param propertySheet The property sheet to add the validation rule to + * @param item The item being generated + * @param component The component representing the item + * @param realTimeChecking true to make the client validate as the user types + */ + protected void setupMandatoryValidation(FacesContext context, + UIPropertySheet propertySheet, PropertySheetItem item, + UIComponent component, boolean realTimeChecking) + { + List params = new ArrayList(3); + + // add the value parameter + String value = "document.getElementById('" + + component.getClientId(context) + "').value"; + params.add(value); + + // add the validation failed message to show (use the value of the + // label component of the given item) + String msg = Application.getMessage(context, "validation_mandatory"); + params.add("'" + MessageFormat.format(msg, new Object[] {item.getResolvedDisplayLabel()}) + "'"); + + // add the validation case to the property sheet + propertySheet.addClientValidation(new ClientValidation("validateMandatory", + params, realTimeChecking)); + } + + /** + * Sets up the marker to show that the item is mandatory. + * + * @param context FacesContext + * @param item The item being generated + */ + @SuppressWarnings("unchecked") + protected void setupMandatoryMarker(FacesContext context, PropertySheetItem item) + { + // create an output text component and set value to "*" + UIOutput component = createOutputTextComponent(context, null); + component.setValue("*"); + + // add marker as child to the property sheet item + item.getChildren().add(component); + } + + /** + * Sets up client validation rules for any constraints the property has. + * + * @param context FacesContext + * propertySheet The property sheet being generated + * @param property The property being generated + * @param propertyDef The data dictionary definition of the property + * @param component The component representing the property + */ + protected void setupConstraints(FacesContext context, + UIPropertySheet propertySheet, PropertySheetItem property, + PropertyDefinition propertyDef, UIComponent component) + { + // only setup constraints if the property sheet is in edit mode, + // validation is enabled + if (propertySheet.inEditMode() && propertySheet.isValidationEnabled() && + propertyDef != null) + { + List constraints = propertyDef.getConstraints(); + for (ConstraintDefinition constraintDef : constraints) + { + Constraint constraint = constraintDef.getConstraint(); + + if (constraint instanceof RegexConstraint) + { + setupRegexConstraint(context, propertySheet, property, component, + (RegexConstraint)constraint, false); + } + else if (constraint instanceof StringLengthConstraint) + { + setupStringLengthConstraint(context, propertySheet, property, component, + (StringLengthConstraint)constraint, false); + } + else if (constraint instanceof NumericRangeConstraint) + { + setupNumericRangeConstraint(context, propertySheet, property, component, + (NumericRangeConstraint)constraint, false); + } + else + { + logger.warn("Unrecognized constaint object: " + constraint.getClass().getName()); + } + } + } + } + + /** + * Sets up a default validation rule for the regular expression constraint + * + * @param context FacesContext + * @param propertySheet The property sheet to add the validation rule to + * @param property The property being generated + * @param component The component representing the property + * @param constraint The constraint to setup + * @param realTimeChecking true to make the client validate as the user types + */ + protected void setupRegexConstraint(FacesContext context, + UIPropertySheet propertySheet, PropertySheetItem property, + UIComponent component, RegexConstraint constraint, + boolean realTimeChecking) + { + String expression = constraint.getExpression(); + boolean requiresMatch = constraint.getRequiresMatch(); + + List params = new ArrayList(3); + + // add the value parameter + String value = "document.getElementById('" + + component.getClientId(context) + "').value"; + params.add(value); + + // add the min parameter + params.add("'" + expression + "'"); + + // add the max parameter + params.add(Boolean.toString(requiresMatch)); + + // add the validation failed message to show + String msg = null; + if (requiresMatch) + { + msg = Application.getMessage(context, "validation_regex"); + } + else + { + msg = Application.getMessage(context, "validation_regex_not_match"); + } + params.add("'" + MessageFormat.format(msg, new Object[] + {property.getResolvedDisplayLabel(), expression}) + "'"); + + // add the validation case to the property sheet + propertySheet.addClientValidation(new ClientValidation("validateRegex", + params, realTimeChecking)); + } + + /** + * Sets up a default validation rule for the string length constraint + * + * @param context FacesContext + * @param propertySheet The property sheet to add the validation rule to + * @param property The property being generated + * @param component The component representing the property + * @param constraint The constraint to setup + * @param realTimeChecking true to make the client validate as the user types + */ + protected void setupStringLengthConstraint(FacesContext context, + UIPropertySheet propertySheet, PropertySheetItem property, + UIComponent component, StringLengthConstraint constraint, + boolean realTimeChecking) + { + int min = constraint.getMinLength(); + int max = constraint.getMaxLength(); + + List params = new ArrayList(3); + + // add the value parameter + String value = "document.getElementById('" + + component.getClientId(context) + "').value"; + params.add(value); + + // add the min parameter + params.add(Integer.toString(min)); + + // add the max parameter + params.add(Integer.toString(max)); + + // add the validation failed message to show + String msg = Application.getMessage(context, "validation_string_length"); + params.add("'" + MessageFormat.format(msg, new Object[] + {property.getResolvedDisplayLabel(), min, max}) + "'"); + + // add the validation case to the property sheet + propertySheet.addClientValidation(new ClientValidation("validateStringLength", + params, realTimeChecking)); + } + + /** + * Sets up a default validation rule for the numeric range constraint + * + * @param context FacesContext + * @param propertySheet The property sheet to add the validation rule to + * @param property The property being generated + * @param component The component representing the property + * @param constraint The constraint to setup + * @param realTimeChecking true to make the client validate as the user types + */ + protected void setupNumericRangeConstraint(FacesContext context, + UIPropertySheet propertySheet, PropertySheetItem property, + UIComponent component, NumericRangeConstraint constraint, + boolean realTimeChecking) + { + double min = constraint.getMinValue(); + double max = constraint.getMaxValue(); + + List params = new ArrayList(3); + + // add the value parameter + String value = "document.getElementById('" + + component.getClientId(context) + "').value"; + params.add(value); + + // add the min parameter + params.add(Double.toString(min)); + + // add the max parameter + params.add(Double.toString(max)); + + // add the validation failed message to show + String msg = Application.getMessage(context, "validation_numeric_range"); + params.add("'" + MessageFormat.format(msg, new Object[] + {property.getResolvedDisplayLabel(), min, max}) + "'"); + + // add the validation case to the property sheet + propertySheet.addClientValidation(new ClientValidation("validateNumberRange", + params, false)); + } + + /** + * Sets up the appropriate converter for the given property + * + * @param context FacesContext + * @param propertySheet The property sheet being generated + * @param property The property being generated + * @param propertyDef The data dictionary definition of the property + * @param component The component representing the property + */ + protected void setupConverter(FacesContext context, + UIPropertySheet propertySheet, PropertySheetItem property, + PropertyDefinition propertyDef, UIComponent component) + { + if (property.getConverter() != null) + { + // create and add the custom converter + createAndSetConverter(context, property.getConverter(), component); + } + else if (propertySheet.inEditMode() == false && + propertyDef != null && propertyDef.isMultiValued()) + { + // if there isn't a custom converter and the property is + // multi-valued add the multi value converter as a default + createAndSetConverter(context, RepoConstants.ALFRESCO_FACES_MULTIVALUE_CONVERTER, + component); + } + } + + /** + * Sets up the appropriate converter for the given association + * + * @param context FacesContext + * @param propertySheet The property sheet being generated + * @param association The association being generated + * @param associationDef The data dictionary definition of the property + * @param component The component representing the association + */ + protected void setupConverter(FacesContext context, + UIPropertySheet propertySheet, PropertySheetItem association, + AssociationDefinition associationDef, UIComponent component) + { + if (association.getConverter() != null) + { + // create and add the custom converter + createAndSetConverter(context, association.getConverter(), component); + } + } + + /** + * Returns the type of the control being generated + * + * @return The type of the control either a FIELD or a SELECTOR + */ + protected ControlType getControlType() + { + return ControlType.FIELD; + } + /** * Retrieve the PropertyDefinition for the given property name on the given node * @@ -54,105 +672,6 @@ public abstract class BaseComponentGenerator implements IComponentGenerator return getDataDictionary(context).getAssociationDefinition(node, associationName); } - /** - * Creates a wrapper component around the given component to enable the user - * to edit multiple values. - * - * @param context FacesContext - * @param propertySheet The property sheet being generated - * @param item The item being generated - * @param component The component to wrap if necessary - * @param field true if the property being enabled is a field style - * component i.e. text field or checkbox. false if the component - * is a selector style component i.e. category selector - */ - protected UIComponent enableForMultiValue(FacesContext context, UIPropertySheet propertySheet, - PropertySheetItem item, UIComponent component, boolean field) - { - UIComponent multiValueComponent = component; - - // NOTE: Associations have built in support for multiple values so we only deal - // with UIProperty instances in here currently - - if (item instanceof UIProperty) - { - // if the property is multi-valued create a multi value editor wrapper component - String id = "multi_" + item.getName(); - multiValueComponent = context.getApplication().createComponent( - RepoConstants.ALFRESCO_FACES_MULTIVALUE_EDITOR); - FacesHelper.setupComponentId(context, multiValueComponent, id); - - // set the renderer depending on whether the item is a 'field' or a 'selector' - if (field) - { - multiValueComponent.setRendererType(RepoConstants.ALFRESCO_FACES_FIELD_RENDERER); - } - else - { - multiValueComponent.setRendererType(RepoConstants.ALFRESCO_FACES_SELECTOR_RENDERER); - - // set the value binding for the wrapped component and the lastItemAdded attribute of - // the multi select component, needs to point somewhere that can hold any object, it - // will store the item last added by the user. - String expr = "#{MultiValueEditorBean.lastItemsAdded['" + - item.getName() + "']}"; - ValueBinding vb = context.getApplication().createValueBinding(expr); - multiValueComponent.setValueBinding("lastItemAdded", vb); - component.setValueBinding("value", vb); - } - - // add the original component as a child of the wrapper - multiValueComponent.getChildren().add(component); - } - - return multiValueComponent; - } - - /** - * Sets up any converters configured for the item - * - * @param context FacesContext - * @param propertySheet The property sheet being generated - * @param item The item being generated - * @param component The component to disable - */ - protected void setupConverter(FacesContext context, UIPropertySheet propertySheet, - PropertySheetItem item, UIComponent component) - { - // if the item has a converter, create it and apply it - String converter = item.getConverter(); - if (converter != null && component instanceof UIOutput) - { - try - { - Converter conv = context.getApplication().createConverter(converter); - ((UIOutput)component).setConverter(conv); - } - catch (FacesException fe) - { - logger.warn("Converter " + converter + " could not be applied"); - } - } - } - - /** - * Creates an output text component - * - * @param context FacesContext - * @param id Optional id to set - * @return The new component - */ - protected UIOutput createOutputTextComponent(FacesContext context, String id) - { - UIOutput component = (UIOutput)context.getApplication().createComponent( - ComponentConstants.JAVAX_FACES_OUTPUT); - - component.setRendererType(ComponentConstants.JAVAX_FACES_TEXT); - FacesHelper.setupComponentId(context, component, id); - - return component; - } - private DataDictionary getDataDictionary(FacesContext context) { if (this.dataDictionary == null) diff --git a/source/java/org/alfresco/web/bean/generator/CategoryPickerGenerator.java b/source/java/org/alfresco/web/bean/generator/CategoryPickerGenerator.java deleted file mode 100644 index 0651c815e0..0000000000 --- a/source/java/org/alfresco/web/bean/generator/CategoryPickerGenerator.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.alfresco.web.bean.generator; - -import javax.faces.component.UIComponent; -import javax.faces.context.FacesContext; - -import org.alfresco.service.cmr.dictionary.PropertyDefinition; -import org.alfresco.web.app.servlet.FacesHelper; -import org.alfresco.web.ui.repo.RepoConstants; -import org.alfresco.web.ui.repo.component.UICategorySelector; -import org.alfresco.web.ui.repo.component.property.PropertySheetItem; -import org.alfresco.web.ui.repo.component.property.UIPropertySheet; - -/** - * Generates a text field component. - * - * @author gavinc - */ -public class CategoryPickerGenerator extends BaseComponentGenerator -{ - public UIComponent generate(FacesContext context, String id) - { - UIComponent component = context.getApplication(). - createComponent(RepoConstants.ALFRESCO_FACES_CATEGORY_SELECTOR); - FacesHelper.setupComponentId(context, component, id); - - return component; - } - - public UIComponent generate(FacesContext context, UIPropertySheet propertySheet, - PropertySheetItem item) - { - // create the standard component - UIComponent component = generate(context, item.getName()); - - // get the property definition - PropertyDefinition propertyDef = getPropertyDefinition(context, - propertySheet.getNode(), item.getName()); - - if (propertySheet.inEditMode() && propertyDef != null && propertyDef.isMultiValued()) - { - // if the item is multi valued we need to wrap the standard component - // but only when the property sheet is in edit mode - component = enableForMultiValue(context, propertySheet, item, component, false); - } - else if (propertySheet.inEditMode() == false || item.isReadOnly() || - (propertyDef != null && propertyDef.isProtected())) - { - // disable the component if it is read only or protected - // or if the property sheet is in view mode - component.getAttributes().put("disabled", Boolean.TRUE); - } - - // setup the converter if one was specified - setupConverter(context, propertySheet, item, component); - - return component; - } -} diff --git a/source/java/org/alfresco/web/bean/generator/CategorySelectorGenerator.java b/source/java/org/alfresco/web/bean/generator/CategorySelectorGenerator.java new file mode 100644 index 0000000000..1c611f5718 --- /dev/null +++ b/source/java/org/alfresco/web/bean/generator/CategorySelectorGenerator.java @@ -0,0 +1,51 @@ +package org.alfresco.web.bean.generator; + +import javax.faces.component.UIComponent; +import javax.faces.context.FacesContext; + +import org.alfresco.web.app.servlet.FacesHelper; +import org.alfresco.web.ui.repo.RepoConstants; +import org.alfresco.web.ui.repo.component.property.PropertySheetItem; +import org.alfresco.web.ui.repo.component.property.UIPropertySheet; + +/** + * Generates a category selector component. + * + * @author gavinc + */ +public class CategorySelectorGenerator extends BaseComponentGenerator +{ + public UIComponent generate(FacesContext context, String id) + { + UIComponent component = context.getApplication(). + createComponent(RepoConstants.ALFRESCO_FACES_CATEGORY_SELECTOR); + FacesHelper.setupComponentId(context, component, id); + + return component; + } + + @Override + @SuppressWarnings("unchecked") + protected UIComponent createComponent(FacesContext context, UIPropertySheet propertySheet, + PropertySheetItem item) + { + // the category selector component is used whatever mode the property sheet is in + return generate(context, item.getName()); + } + + @Override + protected void setupMandatoryValidation(FacesContext context, UIPropertySheet propertySheet, + PropertySheetItem item, UIComponent component, boolean realTimeChecking) + { + // TODO: the category selector component needs to use the + // 'current_value' hidden field rather than the standard + // 'value' field as this is always null (it's used internally + // by the component) for now disable mandatory checks completely + } + + @Override + protected ControlType getControlType() + { + return ControlType.SELECTOR; + } +} diff --git a/source/java/org/alfresco/web/bean/generator/CheckboxGenerator.java b/source/java/org/alfresco/web/bean/generator/CheckboxGenerator.java index 1f861faf5f..d69ce06844 100644 --- a/source/java/org/alfresco/web/bean/generator/CheckboxGenerator.java +++ b/source/java/org/alfresco/web/bean/generator/CheckboxGenerator.java @@ -1,22 +1,18 @@ package org.alfresco.web.bean.generator; import javax.faces.component.UIComponent; -import javax.faces.component.UIOutput; import javax.faces.component.UISelectBoolean; import javax.faces.context.FacesContext; -import javax.faces.convert.Converter; import org.alfresco.service.cmr.dictionary.PropertyDefinition; -import org.alfresco.web.app.Application; import org.alfresco.web.app.servlet.FacesHelper; import org.alfresco.web.ui.common.ComponentConstants; -import org.alfresco.web.ui.common.converter.XMLDateConverter; import org.alfresco.web.ui.repo.RepoConstants; import org.alfresco.web.ui.repo.component.property.PropertySheetItem; import org.alfresco.web.ui.repo.component.property.UIPropertySheet; /** - * Generates a text field component. + * Generates a checkbox component. * * @author gavinc */ @@ -32,58 +28,46 @@ public class CheckboxGenerator extends BaseComponentGenerator return component; } - public UIComponent generate(FacesContext context, UIPropertySheet propertySheet, - PropertySheetItem item) + @Override + protected void setupConverter(FacesContext context, + UIPropertySheet propertySheet, PropertySheetItem property, + PropertyDefinition propertyDef, UIComponent component) { - UIComponent component = null; - - // get the property definition - PropertyDefinition propertyDef = getPropertyDefinition(context, - propertySheet.getNode(), item.getName()); - - if (propertySheet.inEditMode()) + if (property.getConverter() != null) { - // use the standard component in edit mode - component = generate(context, item.getName()); - - // disable the component if it is read only or protected - if (item.isReadOnly() || (propertyDef != null && propertyDef.isProtected())) - { - component.getAttributes().put("disabled", Boolean.TRUE); - } - else - { - // if the item is multi valued we need to wrap the standard component - if (propertyDef != null && propertyDef.isMultiValued()) - { - component = enableForMultiValue(context, propertySheet, item, component, true); - } - } + // create and add the custom converter + createAndSetConverter(context, property.getConverter(), component); } else { - // create an output text component in view mode - component = createOutputTextComponent(context, item.getName()); - - // if there is no overridden converter add a default - if (item.getConverter() == null) + if (propertySheet.inEditMode() == false) { if (propertyDef != null && propertyDef.isMultiValued()) { - // add multi-value converter if property is such - item.setConverter(RepoConstants.ALFRESCO_FACES_MULTIVALUE_CONVERTER); + // if there isn't a custom converter and the property is + // multi-valued add the multi value converter as a default + createAndSetConverter(context, + RepoConstants.ALFRESCO_FACES_MULTIVALUE_CONVERTER, + component); } else { - // add the default boolean label converter - item.setConverter(RepoConstants.ALFRESCO_FACES_BOOLEAN_CONVERTER); + // if there isn't a custom converter and the property is + // not multi-valued add the boolean converter as a default + createAndSetConverter(context, + RepoConstants.ALFRESCO_FACES_BOOLEAN_CONVERTER, + component); } } } - - // setup the converter if one was specified - setupConverter(context, propertySheet, item, component); - - return component; + } + + @Override + protected void setupMandatoryValidation(FacesContext context, + UIPropertySheet propertySheet, PropertySheetItem item, + UIComponent component, boolean realTimeChecking) + { + // a checkbox will always have one value or another so there + // is no need to create a mandatory validation rule } } diff --git a/source/java/org/alfresco/web/bean/generator/ChildAssociationGenerator.java b/source/java/org/alfresco/web/bean/generator/ChildAssociationGenerator.java index 443e661a41..90df1f92fe 100644 --- a/source/java/org/alfresco/web/bean/generator/ChildAssociationGenerator.java +++ b/source/java/org/alfresco/web/bean/generator/ChildAssociationGenerator.java @@ -3,15 +3,13 @@ package org.alfresco.web.bean.generator; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; -import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.web.app.servlet.FacesHelper; import org.alfresco.web.ui.repo.RepoConstants; import org.alfresco.web.ui.repo.component.property.PropertySheetItem; -import org.alfresco.web.ui.repo.component.property.UIChildAssociationEditor; import org.alfresco.web.ui.repo.component.property.UIPropertySheet; /** - * Generates a text field component. + * Generates a component to manage child associations. * * @author gavinc */ @@ -25,27 +23,14 @@ public class ChildAssociationGenerator extends BaseComponentGenerator return component; } - - public UIComponent generate(FacesContext context, UIPropertySheet propertySheet, - PropertySheetItem item) + + @Override + protected void setupMandatoryValidation(FacesContext context, UIPropertySheet propertySheet, + PropertySheetItem item, UIComponent component, boolean realTimeChecking) { - // generate the standard child association editor - UIChildAssociationEditor component = (UIChildAssociationEditor)generate(context, - item.getName()); - - AssociationDefinition assocDef = this.getAssociationDefinition(context, - propertySheet.getNode(), item.getName()); - - // set the association name and set to disabled if appropriate - component.setAssociationName(assocDef.getName().toString()); - if (propertySheet.inEditMode() == false || item.isReadOnly() || assocDef.isProtected()) - { - component.setDisabled(true); - } - - // setup the converter if one was specified - setupConverter(context, propertySheet, item, component); - - return component; + // TODO: the child assocation editor component needs to use the + // 'current_value' hidden field rather than the standard + // 'value' field as this is always null (it's used internally + // by the component) for now disable mandatory checks completely } } diff --git a/source/java/org/alfresco/web/bean/generator/DatePickerGenerator.java b/source/java/org/alfresco/web/bean/generator/DatePickerGenerator.java index 43d3310f29..f6df571217 100644 --- a/source/java/org/alfresco/web/bean/generator/DatePickerGenerator.java +++ b/source/java/org/alfresco/web/bean/generator/DatePickerGenerator.java @@ -1,7 +1,6 @@ package org.alfresco.web.bean.generator; import javax.faces.component.UIComponent; -import javax.faces.component.UIInput; import javax.faces.component.UIOutput; import javax.faces.context.FacesContext; import javax.faces.convert.Converter; @@ -16,7 +15,7 @@ import org.alfresco.web.ui.repo.component.property.PropertySheetItem; import org.alfresco.web.ui.repo.component.property.UIPropertySheet; /** - * Generates a text field component. + * Generates a date picker component. * * @author gavinc */ @@ -24,6 +23,7 @@ public class DatePickerGenerator extends BaseComponentGenerator { private static final String MSG_DATE = "date_pattern"; + @SuppressWarnings("unchecked") public UIComponent generate(FacesContext context, String id) { UIComponent component = context.getApplication(). @@ -36,44 +36,15 @@ public class DatePickerGenerator extends BaseComponentGenerator return component; } - public UIComponent generate(FacesContext context, UIPropertySheet propertySheet, - PropertySheetItem item) + @Override + protected void setupConverter(FacesContext context, + UIPropertySheet propertySheet, PropertySheetItem property, + PropertyDefinition propertyDef, UIComponent component) { - UIComponent component = null; - - if (propertySheet.inEditMode()) + if (property.getConverter() != null) { - // use the standard date picker component - component = generate(context, item.getName()); - - // get the property definition - PropertyDefinition propertyDef = getPropertyDefinition(context, - propertySheet.getNode(), item.getName()); - - // disable the component if it is read only or protected - if (item.isReadOnly() || (propertyDef != null && propertyDef.isProtected())) - { - component.getAttributes().put("disabled", Boolean.TRUE); - } - else - { - // if the item is multi valued we need to wrap the standard component - if (propertyDef != null && propertyDef.isMultiValued()) - { - component = enableForMultiValue(context, propertySheet, item, component, true); - } - } - } - else - { - // create an output text component in view mode - component = createOutputTextComponent(context, item.getName()); - } - - if (item.getConverter() != null) - { - // setup the converter if one was specified - setupConverter(context, propertySheet, item, component); + // create and add the custom converter + createAndSetConverter(context, property.getConverter(), component); } else { @@ -81,10 +52,17 @@ public class DatePickerGenerator extends BaseComponentGenerator // we can cast this as we know it is an UIOutput type ((UIOutput)component).setConverter(getDefaultConverter(context)); } - - return component; } + @Override + protected void setupMandatoryValidation(FacesContext context, + UIPropertySheet propertySheet, PropertySheetItem item, + UIComponent component, boolean realTimeChecking) + { + // a date picker will always have a date value so there + // is no need to create a mandatory validation rule + } + /** * Retrieves the default converter for the date component * diff --git a/source/java/org/alfresco/web/bean/generator/DateTimePickerGenerator.java b/source/java/org/alfresco/web/bean/generator/DateTimePickerGenerator.java index c25fd16bcb..6d9b4eb016 100644 --- a/source/java/org/alfresco/web/bean/generator/DateTimePickerGenerator.java +++ b/source/java/org/alfresco/web/bean/generator/DateTimePickerGenerator.java @@ -1,7 +1,6 @@ package org.alfresco.web.bean.generator; import javax.faces.component.UIComponent; -import javax.faces.component.UIOutput; import javax.faces.context.FacesContext; import javax.faces.convert.Converter; @@ -10,7 +9,7 @@ import org.alfresco.web.ui.common.converter.XMLDateConverter; import org.alfresco.web.ui.repo.RepoConstants; /** - * Generates a text field component. + * Generates a date time picker component. * * @author gavinc */ @@ -18,6 +17,7 @@ public class DateTimePickerGenerator extends DatePickerGenerator { private static final String MSG_DATE_TIME = "date_time_pattern"; + @SuppressWarnings("unchecked") public UIComponent generate(FacesContext context, String id) { UIComponent component = super.generate(context, id); diff --git a/source/java/org/alfresco/web/bean/generator/IComponentGenerator.java b/source/java/org/alfresco/web/bean/generator/IComponentGenerator.java index 17595e9c3a..1def92f27e 100644 --- a/source/java/org/alfresco/web/bean/generator/IComponentGenerator.java +++ b/source/java/org/alfresco/web/bean/generator/IComponentGenerator.java @@ -7,8 +7,7 @@ import org.alfresco.web.ui.repo.component.property.PropertySheetItem; import org.alfresco.web.ui.repo.component.property.UIPropertySheet; /** - * Interface definition for objects that dynamically generate components - * on behalf of the PropertySheet component. + * Interface definition for objects that dynamically generate components. * * @author gavinc */ @@ -25,8 +24,9 @@ public interface IComponentGenerator UIComponent generate(FacesContext context, String id); /** - * Dynamically generates a component for use in the given property sheet - * to represent the given property definition. + * Dynamically generates a component for the given property sheet item. + * The generated component is also setup appropriately for it's model + * definition and added to the given property sheet. * * @param context FacesContext * @param propertySheet The property sheet component @@ -34,6 +34,6 @@ public interface IComponentGenerator * either a property, association or child association * @return The component instance */ - UIComponent generate(FacesContext context, UIPropertySheet propertySheet, + UIComponent generateAndAdd(FacesContext context, UIPropertySheet propertySheet, PropertySheetItem item); } diff --git a/source/java/org/alfresco/web/bean/generator/LabelGenerator.java b/source/java/org/alfresco/web/bean/generator/LabelGenerator.java index 907aeb33ef..69a3c3b50f 100644 --- a/source/java/org/alfresco/web/bean/generator/LabelGenerator.java +++ b/source/java/org/alfresco/web/bean/generator/LabelGenerator.java @@ -18,11 +18,16 @@ public class LabelGenerator extends BaseComponentGenerator return createOutputTextComponent(context, id); } - public UIComponent generate(FacesContext context, UIPropertySheet propertySheet, + @Override + @SuppressWarnings("unchecked") + public UIComponent generateAndAdd(FacesContext context, UIPropertySheet propertySheet, PropertySheetItem item) { UIComponent component = generate(context, "label_" + item.getName()); + // add the component to the property sheet item + item.getChildren().add(component); + // TODO: Turn the label red if the field is required // setup the 'for' attribute to associate with it the control diff --git a/source/java/org/alfresco/web/bean/generator/SpaceIconPickerGenerator.java b/source/java/org/alfresco/web/bean/generator/SpaceIconPickerGenerator.java index 980a60e75e..b7c63f25fe 100644 --- a/source/java/org/alfresco/web/bean/generator/SpaceIconPickerGenerator.java +++ b/source/java/org/alfresco/web/bean/generator/SpaceIconPickerGenerator.java @@ -19,6 +19,7 @@ import org.alfresco.web.ui.repo.component.property.UIPropertySheet; */ public class SpaceIconPickerGenerator extends BaseComponentGenerator { + @SuppressWarnings("unchecked") public UIComponent generate(FacesContext context, String id) { // create the outer component @@ -35,21 +36,20 @@ public class SpaceIconPickerGenerator extends BaseComponentGenerator return component; } - - public UIComponent generate(FacesContext context, UIPropertySheet propertySheet, - PropertySheetItem item) + + @Override + @SuppressWarnings("unchecked") + protected void setupProperty(FacesContext context, + UIPropertySheet propertySheet, PropertySheetItem item, + PropertyDefinition propertyDef, UIComponent component) { - UIComponent component = null; + // do the standard setup + super.setupProperty(context, propertySheet, item, propertyDef, component); - // get the property definition - PropertyDefinition propertyDef = getPropertyDefinition(context, - propertySheet.getNode(), item.getName()); - + // if the property sheet is in edit mode we also need to setup the + // list of icons the user can select from if (propertySheet.inEditMode()) { - // use the standard component in edit mode - component = generate(context, item.getName()); - // create the list items child component UIListItems items = (UIListItems)context.getApplication(). createComponent(RepoConstants.ALFRESCO_FACES_LIST_ITEMS); @@ -76,37 +76,6 @@ public class SpaceIconPickerGenerator extends BaseComponentGenerator // add the list items component to the image picker component component.getChildren().add(items); - - // disable the component if it is read only or protected - if (item.isReadOnly() || (propertyDef != null && propertyDef.isProtected())) - { - component.getAttributes().put("disabled", Boolean.TRUE); - } - else - { - // if the item is multi valued we need to wrap the standard component - if (propertyDef != null && propertyDef.isMultiValued()) - { - component = enableForMultiValue(context, propertySheet, item, component, true); - } - } } - else - { - // create an output text component in view mode - component = createOutputTextComponent(context, item.getName()); - - // if the property is multi-valued and there isn't a custom converter - // specified, add the MultiValue converter as a default - if (propertyDef.isMultiValued() && item.getConverter() == null) - { - item.setConverter(RepoConstants.ALFRESCO_FACES_MULTIVALUE_CONVERTER); - } - } - - // setup the converter if one was specified - setupConverter(context, propertySheet, item, component); - - return component; } } diff --git a/source/java/org/alfresco/web/bean/generator/TextFieldGenerator.java b/source/java/org/alfresco/web/bean/generator/TextFieldGenerator.java index 6f3acff658..253531236d 100644 --- a/source/java/org/alfresco/web/bean/generator/TextFieldGenerator.java +++ b/source/java/org/alfresco/web/bean/generator/TextFieldGenerator.java @@ -1,14 +1,11 @@ package org.alfresco.web.bean.generator; import javax.faces.component.UIComponent; -import javax.faces.component.UIInput; -import javax.faces.component.UIOutput; import javax.faces.context.FacesContext; -import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.web.app.servlet.FacesHelper; import org.alfresco.web.ui.common.ComponentConstants; -import org.alfresco.web.ui.repo.RepoConstants; +import org.alfresco.web.ui.repo.component.UIMultiValueEditor; import org.alfresco.web.ui.repo.component.property.PropertySheetItem; import org.alfresco.web.ui.repo.component.property.UIPropertySheet; @@ -19,6 +16,7 @@ import org.alfresco.web.ui.repo.component.property.UIPropertySheet; */ public class TextFieldGenerator extends BaseComponentGenerator { + @SuppressWarnings("unchecked") public UIComponent generate(FacesContext context, String id) { UIComponent component = context.getApplication(). @@ -31,50 +29,31 @@ public class TextFieldGenerator extends BaseComponentGenerator return component; } - public UIComponent generate(FacesContext context, UIPropertySheet propertySheet, - PropertySheetItem item) + @Override + @SuppressWarnings("unchecked") + protected void setupMandatoryValidation(FacesContext context, + UIPropertySheet propertySheet, PropertySheetItem item, + UIComponent component, boolean realTimeChecking) { - UIComponent component = null; - - // get the property definition - PropertyDefinition propertyDef = getPropertyDefinition(context, - propertySheet.getNode(), item.getName()); - - if (propertySheet.inEditMode()) + if (component instanceof UIMultiValueEditor) { - // use the standard component in edit mode - component = generate(context, item.getName()); + // if the text field has multiple values don't allow real time + // checking of the mandatory status - // disable the component if it is read only or protected - if (item.isReadOnly() || (propertyDef != null && propertyDef.isProtected())) - { - component.getAttributes().put("disabled", Boolean.TRUE); - } - else - { - // if the item is multi valued we need to wrap the standard component - if (propertyDef != null && propertyDef.isMultiValued()) - { - component = enableForMultiValue(context, propertySheet, item, component, true); - } - } + // TODO: the multi-value editor component needs to use the + // 'current_value' hidden field rather than the standard + // 'value' field as this is always null (it's used internally + // by the component) for now disable mandatory checks completely + + //super.setupMandatoryValidation(context, propertySheet, item, component, false); } else { - // create an output text component in view mode - component = createOutputTextComponent(context, item.getName()); - - // if the property is multi-valued and there isn't a custom converter - // specified, add the MultiValue converter as a default - if (propertyDef != null && propertyDef.isMultiValued() && item.getConverter() == null) - { - item.setConverter(RepoConstants.ALFRESCO_FACES_MULTIVALUE_CONVERTER); - } + // setup the client validation rule with real time validation enabled + super.setupMandatoryValidation(context, propertySheet, item, component, true); + + // add event handler to kick off real time checks + component.getAttributes().put("onkeyup", "javascript:processButtonState();"); } - - // setup the converter if one was specified - setupConverter(context, propertySheet, item, component); - - return component; } } diff --git a/source/java/org/alfresco/web/bean/spaces/CreateSpaceWizard.java b/source/java/org/alfresco/web/bean/spaces/CreateSpaceWizard.java index afef53ebcc..1a4508d4f3 100644 --- a/source/java/org/alfresco/web/bean/spaces/CreateSpaceWizard.java +++ b/source/java/org/alfresco/web/bean/spaces/CreateSpaceWizard.java @@ -484,51 +484,63 @@ public class CreateSpaceWizard extends BaseWizardBean QName idQName = Repository.resolveToQName(child.getAttribute("name")); TypeDefinition typeDef = this.dictionaryService.getType(idQName); - if (typeDef != null && - this.dictionaryService.isSubClass(typeDef.getName(), ContentModel.TYPE_FOLDER)) + if (typeDef != null) { - // try and get the label from config - String label = Utils.getDisplayLabel(context, child); - - // if there wasn't a client based label try and get it from the dictionary - if (label == null) + if (this.dictionaryService.isSubClass(typeDef.getName(), ContentModel.TYPE_FOLDER)) { - label = typeDef.getTitle(); + // try and get the label from config + String label = Utils.getDisplayLabel(context, child); + + // if there wasn't a client based label try and get it from the dictionary + if (label == null) + { + label = typeDef.getTitle(); + } + + // finally use the localname if we still haven't found a label + if (label == null) + { + label = idQName.getLocalName(); + } + + // resolve a description string for the type + String description = Utils.getDescription(context, child); + + // if we don't have a local description just use the label + if (description == null) + { + description = label; + } + + // extract the icon to use from the config + String icon = child.getAttribute("icon"); + if (icon == null || icon.length() == 0) + { + icon = DEFAULT_SPACE_TYPE_ICON_PATH; + } + + UIListItem item = new UIListItem(); + item.getAttributes().put("value", idQName.toString()); + item.getAttributes().put("label", label); + item.getAttributes().put("tooltip", label); + item.getAttributes().put("image", icon); + this.folderTypes.add(item); + + UIDescription desc = new UIDescription(); + desc.setControlValue(idQName.toString()); + desc.setText(description); + this.folderTypeDescriptions.add(desc); } - - // finally use the localname if we still haven't found a label - if (label == null) + else { - label = idQName.getLocalName(); + logger.warn("Failed to add '" + child.getAttribute("name") + + "' to the list of folder types as the type is not a subtype of cm:folder"); } - - // resolve a description string for the type - String description = Utils.getDescription(context, child); - - // if we don't have a local description just use the label - if (description == null) - { - description = label; - } - - // extract the icon to use from the config - String icon = child.getAttribute("icon"); - if (icon == null || icon.length() == 0) - { - icon = DEFAULT_SPACE_TYPE_ICON_PATH; - } - - UIListItem item = new UIListItem(); - item.getAttributes().put("value", idQName.toString()); - item.getAttributes().put("label", label); - item.getAttributes().put("tooltip", label); - item.getAttributes().put("image", icon); - this.folderTypes.add(item); - - UIDescription desc = new UIDescription(); - desc.setControlValue(idQName.toString()); - desc.setText(description); - this.folderTypeDescriptions.add(desc); + } + else + { + logger.warn("Failed to add '" + child.getAttribute("name") + + "' to the list of folder types as the type is not recognised"); } } } diff --git a/source/java/org/alfresco/web/bean/wizard/BaseContentWizard.java b/source/java/org/alfresco/web/bean/wizard/BaseContentWizard.java index 0eed6970a6..d91cb04a10 100644 --- a/source/java/org/alfresco/web/bean/wizard/BaseContentWizard.java +++ b/source/java/org/alfresco/web/bean/wizard/BaseContentWizard.java @@ -512,25 +512,37 @@ public abstract class BaseContentWizard extends AbstractWizardBean { TypeDefinition typeDef = this.dictionaryService.getType(idQName); - if (typeDef != null && - this.dictionaryService.isSubClass(typeDef.getName(), ContentModel.TYPE_CONTENT)) + if (typeDef != null) { - // try and get the display label from config - String label = Utils.getDisplayLabel(context, child); - - // if there wasn't a client based label try and get it from the dictionary - if (label == null) + if (this.dictionaryService.isSubClass(typeDef.getName(), ContentModel.TYPE_CONTENT)) { - label = typeDef.getTitle(); + // try and get the display label from config + String label = Utils.getDisplayLabel(context, child); + + // if there wasn't a client based label try and get it from the dictionary + if (label == null) + { + label = typeDef.getTitle(); + } + + // finally, just use the localname + if (label == null) + { + label = idQName.getLocalName(); + } + + this.objectTypes.add(new SelectItem(idQName.toString(), label)); } - - // finally, just use the localname - if (label == null) + else { - label = idQName.getLocalName(); + logger.warn("Failed to add '" + child.getAttribute("name") + + "' to the list of content types as the type is not a subtype of cm:content"); } - - this.objectTypes.add(new SelectItem(idQName.toString(), label)); + } + else + { + logger.warn("Failed to add '" + child.getAttribute("name") + + "' to the list of content types as the type is not recognised"); } } } diff --git a/source/java/org/alfresco/web/ui/repo/RepoConstants.java b/source/java/org/alfresco/web/ui/repo/RepoConstants.java index e2f996b4ed..c2f2597d0d 100644 --- a/source/java/org/alfresco/web/ui/repo/RepoConstants.java +++ b/source/java/org/alfresco/web/ui/repo/RepoConstants.java @@ -46,7 +46,7 @@ public final class RepoConstants public static final String GENERATOR_CHECKBOX = "CheckboxGenerator"; public static final String GENERATOR_DATE_PICKER = "DatePickerGenerator"; public static final String GENERATOR_DATETIME_PICKER = "DateTimePickerGenerator"; - public static final String GENERATOR_CATEGORY_PICKER = "CategoryPickerGenerator"; + public static final String GENERATOR_CATEGORY_SELECTOR = "CategorySelectorGenerator"; public static final String GENERATOR_ASSOCIATION = "AssociationGenerator"; public static final String GENERATOR_CHILD_ASSOCIATION = "ChildAssociationGenerator"; diff --git a/source/java/org/alfresco/web/ui/repo/component/property/BaseAssociationEditor.java b/source/java/org/alfresco/web/ui/repo/component/property/BaseAssociationEditor.java index 0f6dbf2eef..b2e8069314 100644 --- a/source/java/org/alfresco/web/ui/repo/component/property/BaseAssociationEditor.java +++ b/source/java/org/alfresco/web/ui/repo/component/property/BaseAssociationEditor.java @@ -79,7 +79,6 @@ public abstract class BaseAssociationEditor extends UIInput private final static String MSG_SEARCH_SELECT_ITEM = "search_select_item"; private final static String MSG_SELECTED_ITEMS = "selected_items"; private final static String MSG_REMOVE = "remove"; - private final static String MSG_ADD = "add"; private final static String MSG_OK = "ok"; private final static String MSG_CANCEL = "cancel"; private final static String MSG_SEARCH = "search"; @@ -274,7 +273,6 @@ public abstract class BaseAssociationEditor extends UIInput this.highlightedRow = false; ResponseWriter out = context.getResponseWriter(); - String clientId = getClientId(context); // get the child associations currently on the node and any that have been added NodeService nodeService = Repository.getServiceRegistry(context).getNodeService(); diff --git a/source/java/org/alfresco/web/ui/repo/component/property/PropertySheetItem.java b/source/java/org/alfresco/web/ui/repo/component/property/PropertySheetItem.java index fa050f3f60..a84bc4b3c6 100644 --- a/source/java/org/alfresco/web/ui/repo/component/property/PropertySheetItem.java +++ b/source/java/org/alfresco/web/ui/repo/component/property/PropertySheetItem.java @@ -20,7 +20,6 @@ import java.io.IOException; import javax.faces.component.NamingContainer; import javax.faces.component.UIComponent; -import javax.faces.component.UIOutput; import javax.faces.component.UIPanel; import javax.faces.context.FacesContext; import javax.faces.el.ValueBinding; @@ -45,6 +44,8 @@ public abstract class PropertySheetItem extends UIPanel implements NamingContain protected Boolean readOnly; protected String componentGenerator; + protected String resolvedDisplayLabel; + /** * @see javax.faces.component.UIComponentBase#encodeBegin(javax.faces.context.FacesContext) */ @@ -210,6 +211,7 @@ public abstract class PropertySheetItem extends UIPanel implements NamingContain this.readOnly = (Boolean)values[3]; this.converter = (String)values[4]; this.componentGenerator = (String)values[5]; + this.resolvedDisplayLabel = (String)values[6]; } /** @@ -217,7 +219,7 @@ public abstract class PropertySheetItem extends UIPanel implements NamingContain */ public Object saveState(FacesContext context) { - Object values[] = new Object[6]; + Object values[] = new Object[7]; // standard component attributes are saved by the super class values[0] = super.saveState(context); values[1] = this.name; @@ -225,9 +227,20 @@ public abstract class PropertySheetItem extends UIPanel implements NamingContain values[3] = this.readOnly; values[4] = this.converter; values[5] = this.componentGenerator; + values[6] = this.resolvedDisplayLabel; return (values); } + /** + * Returns the resolved display label + * + * @return The display label being used at runtime + */ + public String getResolvedDisplayLabel() + { + return resolvedDisplayLabel; + } + /** * Generates the label and control for the item * @@ -252,13 +265,16 @@ public abstract class PropertySheetItem extends UIPanel implements NamingContain * @param propSheet The property sheet that the item is a child of * @param displayLabel The display label text */ + @SuppressWarnings("unchecked") protected void generateLabel(FacesContext context, UIPropertySheet propSheet, String displayLabel) { UIComponent label = FacesHelper.getComponentGenerator(context, - RepoConstants.GENERATOR_LABEL).generate(context, propSheet, this); - label.getAttributes().put("value", displayLabel + ": "); + RepoConstants.GENERATOR_LABEL).generateAndAdd(context, propSheet, this); - this.getChildren().add(label); + // remember the display label used (without the : separator) + this.resolvedDisplayLabel = displayLabel; + + label.getAttributes().put("value", displayLabel + ":"); if (logger.isDebugEnabled()) logger.debug("Created label " + label.getClientId(context) + diff --git a/source/java/org/alfresco/web/ui/repo/component/property/UIAssociation.java b/source/java/org/alfresco/web/ui/repo/component/property/UIAssociation.java index b244d903e2..3d12405c27 100644 --- a/source/java/org/alfresco/web/ui/repo/component/property/UIAssociation.java +++ b/source/java/org/alfresco/web/ui/repo/component/property/UIAssociation.java @@ -17,20 +17,13 @@ package org.alfresco.web.ui.repo.component.property; import java.io.IOException; -import java.text.MessageFormat; -import javax.faces.FacesException; -import javax.faces.component.UIOutput; import javax.faces.context.FacesContext; -import javax.faces.convert.Converter; -import javax.faces.el.ValueBinding; import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.web.app.Application; import org.alfresco.web.app.servlet.FacesHelper; import org.alfresco.web.bean.repository.DataDictionary; -import org.alfresco.web.bean.repository.Node; -import org.alfresco.web.ui.common.Utils; import org.alfresco.web.ui.repo.RepoConstants; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -121,14 +114,7 @@ public class UIAssociation extends PropertySheetItem AssociationDefinition assocDef) { UIAssociationEditor control = (UIAssociationEditor)FacesHelper.getComponentGenerator( - context, RepoConstants.GENERATOR_ASSOCIATION).generate(context, propSheet, this); - - // set up the value binding - ValueBinding vb = context.getApplication().createValueBinding("#{" + propSheet.getVar() + "}"); - control.setValueBinding("value", vb); - - // add the control itself - this.getChildren().add(control); + context, RepoConstants.GENERATOR_ASSOCIATION).generateAndAdd(context, propSheet, this); if (logger.isDebugEnabled()) logger.debug("Created control " + control + "(" + diff --git a/source/java/org/alfresco/web/ui/repo/component/property/UIAssociationEditor.java b/source/java/org/alfresco/web/ui/repo/component/property/UIAssociationEditor.java index f41bf0d6e7..3f9a10a2bc 100644 --- a/source/java/org/alfresco/web/ui/repo/component/property/UIAssociationEditor.java +++ b/source/java/org/alfresco/web/ui/repo/component/property/UIAssociationEditor.java @@ -60,6 +60,7 @@ public class UIAssociationEditor extends BaseAssociationEditor /** * @see org.alfresco.web.ui.repo.component.property.BaseAssociationEditor#populateAssocationMaps(org.alfresco.web.bean.repository.Node) */ + @SuppressWarnings("unchecked") protected void populateAssocationMaps(Node node) { // we need to remember the original set of associations (if there are any) diff --git a/source/java/org/alfresco/web/ui/repo/component/property/UIChildAssociation.java b/source/java/org/alfresco/web/ui/repo/component/property/UIChildAssociation.java index d71021aa08..622169833b 100644 --- a/source/java/org/alfresco/web/ui/repo/component/property/UIChildAssociation.java +++ b/source/java/org/alfresco/web/ui/repo/component/property/UIChildAssociation.java @@ -17,20 +17,13 @@ package org.alfresco.web.ui.repo.component.property; import java.io.IOException; -import java.text.MessageFormat; -import javax.faces.FacesException; -import javax.faces.component.UIOutput; import javax.faces.context.FacesContext; -import javax.faces.convert.Converter; -import javax.faces.el.ValueBinding; import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.web.app.Application; import org.alfresco.web.app.servlet.FacesHelper; import org.alfresco.web.bean.repository.DataDictionary; -import org.alfresco.web.bean.repository.Node; -import org.alfresco.web.ui.common.Utils; import org.alfresco.web.ui.repo.RepoConstants; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -121,14 +114,7 @@ public class UIChildAssociation extends PropertySheetItem AssociationDefinition assocDef) { UIChildAssociationEditor control = (UIChildAssociationEditor)FacesHelper.getComponentGenerator( - context, RepoConstants.GENERATOR_CHILD_ASSOCIATION).generate(context, propSheet, this); - - // set up the value binding - ValueBinding vb = context.getApplication().createValueBinding("#{" + propSheet.getVar() + "}"); - control.setValueBinding("value", vb); - - // add the control itself - this.getChildren().add(control); + context, RepoConstants.GENERATOR_CHILD_ASSOCIATION).generateAndAdd(context, propSheet, this); if (logger.isDebugEnabled()) logger.debug("Created control " + control + "(" + diff --git a/source/java/org/alfresco/web/ui/repo/component/property/UIChildAssociationEditor.java b/source/java/org/alfresco/web/ui/repo/component/property/UIChildAssociationEditor.java index 78890b62bc..3c0d510a41 100644 --- a/source/java/org/alfresco/web/ui/repo/component/property/UIChildAssociationEditor.java +++ b/source/java/org/alfresco/web/ui/repo/component/property/UIChildAssociationEditor.java @@ -60,6 +60,7 @@ public class UIChildAssociationEditor extends BaseAssociationEditor /** * @see org.alfresco.web.ui.repo.component.property.BaseAssociationEditor#populateAssocationMaps(org.alfresco.web.bean.repository.Node) */ + @SuppressWarnings("unchecked") protected void populateAssocationMaps(Node node) { // we need to remember the original set of associations (if there are any) diff --git a/source/java/org/alfresco/web/ui/repo/component/property/UIProperty.java b/source/java/org/alfresco/web/ui/repo/component/property/UIProperty.java index 4277cf31b9..73bd78bc3b 100644 --- a/source/java/org/alfresco/web/ui/repo/component/property/UIProperty.java +++ b/source/java/org/alfresco/web/ui/repo/component/property/UIProperty.java @@ -20,7 +20,6 @@ import java.io.IOException; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; -import javax.faces.el.ValueBinding; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.PropertyDefinition; @@ -124,6 +123,7 @@ public class UIProperty extends PropertySheetItem * @param propSheet The property sheet this property belongs to * @param propDef The definition of the property to create the control for */ + @SuppressWarnings("unchecked") private void generateControl(FacesContext context, UIPropertySheet propSheet, PropertyDefinition propDef) { @@ -149,7 +149,7 @@ public class UIProperty extends PropertySheetItem } else if (typeName.equals(DataTypeDefinition.CATEGORY)) { - componentGeneratorName = RepoConstants.GENERATOR_CATEGORY_PICKER; + componentGeneratorName = RepoConstants.GENERATOR_CATEGORY_SELECTOR; } else if (typeName.equals(DataTypeDefinition.DATETIME)) { @@ -167,8 +167,8 @@ public class UIProperty extends PropertySheetItem } // retrieve the component generator and generate the control - control = FacesHelper.getComponentGenerator(context, componentGeneratorName).generate( - context, propSheet, this); + control = FacesHelper.getComponentGenerator(context, componentGeneratorName). + generateAndAdd(context, propSheet, this); // if we're in edit mode ensure that we don't allow editing of system properties or scenarios we don't support if (propSheet.inEditMode()) @@ -182,15 +182,6 @@ public class UIProperty extends PropertySheetItem } } - // create and set the value binding - ValueBinding vb = context.getApplication(). - createValueBinding("#{" + propSheet.getVar() + ".properties[\"" + - propDef.getName().toString() + "\"]}"); - control.setValueBinding("value", vb); - - // add the control to this component - this.getChildren().add(control); - if (logger.isDebugEnabled()) logger.debug("Created control " + control + "(" + control.getClientId(context) + @@ -208,16 +199,7 @@ public class UIProperty extends PropertySheetItem private void generateControl(FacesContext context, UIPropertySheet propSheet, String propName) { UIComponent control = FacesHelper.getComponentGenerator(context, RepoConstants.GENERATOR_TEXT_FIELD). - generate(context, propSheet, this); - - // create and set the value binding - ValueBinding vb = context.getApplication(). - createValueBinding("#{" + propSheet.getVar() + ".properties[\"" + - propName + "\"]}"); - control.setValueBinding("value", vb); - - // add the control to this component - this.getChildren().add(control); + generateAndAdd(context, propSheet, this); if (logger.isDebugEnabled()) logger.debug("Created control " + control + "(" + diff --git a/source/java/org/alfresco/web/ui/repo/component/property/UIPropertySheet.java b/source/java/org/alfresco/web/ui/repo/component/property/UIPropertySheet.java index 55466ffe4d..ebc62dc255 100644 --- a/source/java/org/alfresco/web/ui/repo/component/property/UIPropertySheet.java +++ b/source/java/org/alfresco/web/ui/repo/component/property/UIPropertySheet.java @@ -17,13 +17,18 @@ package org.alfresco.web.ui.repo.component.property; import java.io.IOException; +import java.io.Serializable; +import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; +import java.util.List; import java.util.Map; import javax.faces.component.NamingContainer; +import javax.faces.component.UIForm; import javax.faces.component.UIPanel; import javax.faces.context.FacesContext; +import javax.faces.context.ResponseWriter; import javax.faces.el.ValueBinding; import org.alfresco.config.Config; @@ -38,6 +43,8 @@ import org.alfresco.web.config.PropertySheetConfigElement.AssociationConfig; import org.alfresco.web.config.PropertySheetConfigElement.ChildAssociationConfig; import org.alfresco.web.config.PropertySheetConfigElement.ItemConfig; import org.alfresco.web.config.PropertySheetConfigElement.PropertyConfig; +import org.alfresco.web.ui.common.ComponentConstants; +import org.alfresco.web.ui.common.Utils; import org.alfresco.web.ui.repo.RepoConstants; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -57,12 +64,15 @@ public class UIPropertySheet extends UIPanel implements NamingContainer private static String PROP_ID_PREFIX = "prop_"; private static String ASSOC_ID_PREFIX = "assoc_"; + private List validations = new ArrayList(); private String variable; private NodeRef nodeRef; private Node node; private Boolean readOnly; + private Boolean validationEnabled; private String mode; private String configArea; + private String finishButtonId; /** * Default constructor @@ -70,7 +80,7 @@ public class UIPropertySheet extends UIPanel implements NamingContainer public UIPropertySheet() { // set the default renderer for a property sheet - setRendererType("javax.faces.Grid"); + setRendererType(ComponentConstants.JAVAX_FACES_GRID); } /** @@ -78,12 +88,13 @@ public class UIPropertySheet extends UIPanel implements NamingContainer */ public String getFamily() { - return "javax.faces.Panel"; + return UIPanel.COMPONENT_FAMILY; } /** * @see javax.faces.component.UIComponent#encodeBegin(javax.faces.context.FacesContext) */ + @SuppressWarnings("unchecked") public void encodeBegin(FacesContext context) throws IOException { int howManyKids = getChildren().size(); @@ -168,10 +179,29 @@ public class UIPropertySheet extends UIPanel implements NamingContainer super.encodeBegin(context); } - + + /** + * @see javax.faces.component.UIComponent#encodeBegin(javax.faces.context.FacesContext) + */ + public void encodeEnd(FacesContext context) throws IOException + { + super.encodeEnd(context); + + // NOTE: We should really use a renderer to output the JavaScript below but that would + // require extending the MyFaces HtmlGridRenderer class which we should avoid doing. + // Until we support multiple client types this will be OK. + + // output the JavaScript to enforce the required validations (if validation is enabled) + if (isValidationEnabled() && this.validations.size() > 0) + { + renderValidationScript(context); + } + } + /** * @see javax.faces.component.StateHolder#restoreState(javax.faces.context.FacesContext, java.lang.Object) */ + @SuppressWarnings("unchecked") public void restoreState(FacesContext context, Object state) { Object values[] = (Object[])state; @@ -183,6 +213,9 @@ public class UIPropertySheet extends UIPanel implements NamingContainer this.readOnly = (Boolean)values[4]; this.mode = (String)values[5]; this.configArea = (String)values[6]; + this.validationEnabled = (Boolean)values[7]; + this.validations = (List)values[8]; + this.finishButtonId = (String)values[9]; } /** @@ -190,7 +223,7 @@ public class UIPropertySheet extends UIPanel implements NamingContainer */ public Object saveState(FacesContext context) { - Object values[] = new Object[7]; + Object values[] = new Object[10]; // standard component attributes are saved by the super class values[0] = super.saveState(context); values[1] = this.nodeRef; @@ -199,6 +232,9 @@ public class UIPropertySheet extends UIPanel implements NamingContainer values[4] = this.readOnly; values[5] = this.mode; values[6] = this.configArea; + values[7] = this.validationEnabled; + values[8] = this.validations; + values[9] = this.finishButtonId; return (values); } @@ -222,8 +258,7 @@ public class UIPropertySheet extends UIPanel implements NamingContainer value = vb.getValue(getFacesContext()); } } - - // TODO: for now we presume the object is a Node, but we need to support id's too + if (value instanceof Node) { node = (Node)value; @@ -291,6 +326,69 @@ public class UIPropertySheet extends UIPanel implements NamingContainer this.readOnly = Boolean.valueOf(readOnly); } + /** + * @return true if validation is enabled for this property sheet + */ + public boolean isValidationEnabled() + { + // if the property sheet is in "view" mode validation will + // always be disabled + if (inEditMode() == false) + { + return false; + } + + if (this.validationEnabled == null) + { + ValueBinding vb = getValueBinding("validationEnabled"); + if (vb != null) + { + this.validationEnabled = (Boolean)vb.getValue(getFacesContext()); + } + } + + if (this.validationEnabled == null) + { + this.validationEnabled = Boolean.TRUE; + } + + return this.validationEnabled; + } + + /** + * @param validationEnabled Sets the validationEnabled flag + */ + public void setValidationEnabled(boolean validationEnabled) + { + this.validationEnabled = Boolean.valueOf(validationEnabled); + } + + /** + * Returns the id of the finish button + * + * @return The id of the finish button on the page + */ + public String getFinishButtonId() + { + // NOTE: This parameter isn't value binding enabled + if (this.finishButtonId == null) + { + this.finishButtonId = "finish-button"; + } + + return this.finishButtonId; + } + + /** + * Sets the id of the finish button being used on the page + * + * @param finishButtonId The id of the finish button + */ + public void setFinishButtonId(String finishButtonId) + { + this.finishButtonId = finishButtonId; + } + /** * @return Returns the mode */ @@ -356,6 +454,158 @@ public class UIPropertySheet extends UIPanel implements NamingContainer this.configArea = configArea; } + /** + * Adds a validation case to the property sheet + * + * @param validation The validation case to enforce + */ + public void addClientValidation(ClientValidation validation) + { + this.validations.add(validation); + } + + /** + * @return Returns the list of client validations to enforce + */ + public List getClientValidations() + { + return this.validations; + } + + /** + * Renders the necessary JavaScript to enforce any constraints the properties + * have. + * + * @param context FacesContext + */ + @SuppressWarnings("unchecked") + private void renderValidationScript(FacesContext context) throws IOException + { + ResponseWriter out = context.getResponseWriter(); + UIForm form = Utils.getParentForm(context, this); + + // output the validation.js script + // TODO: make sure its only included once per page!! + out.write("\n\n\n"); + } + + private void renderValidationMethod(ResponseWriter out, ClientValidation validation, + boolean lastMethod, boolean showMessage) throws IOException + { + out.write("!"); + out.write(validation.Type); + out.write("("); + + // add the parameters + int numberParams = validation.Params.size(); + for (int p = 0; p < numberParams; p++) + { + out.write(validation.Params.get(p)); + if (p != (numberParams-1)) + { + out.write(", "); + } + } + + // add the parameter to show any validation messages + out.write(", "); + out.write(Boolean.toString(showMessage)); + out.write(")"); + + if (lastMethod) + { + out.write(")"); + } + else + { + out.write(" || "); + } + } + /** * Creates all the property components required to display the properties held by the node. * @@ -363,6 +613,7 @@ public class UIPropertySheet extends UIPanel implements NamingContainer * @param node The Node to show all the properties for * @throws IOException */ + @SuppressWarnings("unchecked") private void createComponentsFromNode(FacesContext context, Node node) throws IOException { @@ -458,6 +709,7 @@ public class UIPropertySheet extends UIPanel implements NamingContainer * @param properties Collection of properties to render (driven from configuration) * @throws IOException */ + @SuppressWarnings("unchecked") private void createComponentsFromConfig(FacesContext context, Collection items) throws IOException { @@ -519,4 +771,30 @@ public class UIPropertySheet extends UIPanel implements NamingContainer "' and added it to property sheet " + this); } } + + /** + * Inner class representing a validation case that must be enforced. + */ + @SuppressWarnings("serial") + public static class ClientValidation implements Serializable + { + public String Type; + public List Params; + public boolean RealTimeChecking; + + /** + * Default constructor + * + * @param type The type of the validation + * @param params A List of String parameters to use for the validation + * @param realTimeChecking true to check the property sheet in real time + * i.e. as the user types or uses the mouse + */ + public ClientValidation(String type, List params, boolean realTimeChecking) + { + this.Type = type; + this.Params = params; + this.RealTimeChecking = realTimeChecking; + } + } } diff --git a/source/java/org/alfresco/web/ui/repo/renderer/property/AssociationRenderer.java b/source/java/org/alfresco/web/ui/repo/renderer/property/AssociationRenderer.java index 1f8c53aaf8..e1280a3132 100644 --- a/source/java/org/alfresco/web/ui/repo/renderer/property/AssociationRenderer.java +++ b/source/java/org/alfresco/web/ui/repo/renderer/property/AssociationRenderer.java @@ -16,98 +16,11 @@ */ package org.alfresco.web.ui.repo.renderer.property; -import java.io.IOException; -import java.util.List; - -import javax.faces.component.UIComponent; -import javax.faces.context.FacesContext; -import javax.faces.context.ResponseWriter; - -import org.alfresco.web.ui.common.Utils; -import org.alfresco.web.ui.common.renderer.BaseRenderer; -import org.alfresco.web.ui.repo.component.property.UIAssociation; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - /** * Renderer for the UIAssociation component * * @author gavinc */ -public class AssociationRenderer extends BaseRenderer +public class AssociationRenderer extends PropertySheetItemRenderer { - private static Log logger = LogFactory.getLog(AssociationRenderer.class); - - /** - * @see javax.faces.render.Renderer#encodeBegin(javax.faces.context.FacesContext, javax.faces.component.UIComponent) - */ - public void encodeBegin(FacesContext context, UIComponent component) throws IOException - { - if (component.isRendered() == false) - { - return; - } - - // NOTE: we close off the first generated by the property sheet's grid renderer - context.getResponseWriter().write(""); - } - - /** - * @see javax.faces.render.Renderer#encodeChildren(javax.faces.context.FacesContext, javax.faces.component.UIComponent) - */ - public void encodeChildren(FacesContext context, UIComponent component) throws IOException - { - if (component.isRendered() == false) - { - return; - } - - UIAssociation association = (UIAssociation)component; - ResponseWriter out = context.getResponseWriter(); - - // make sure there are exactly 2 child components - int count = association.getChildCount(); - - if (count == 2) - { - // get the label and the control - List kids = association.getChildren(); - UIComponent label = kids.get(0); - UIComponent control = kids.get(1); - - // place a style class on the label column if necessary - String labelStylceClass = (String)association.getParent().getAttributes().get("labelStyleClass"); - out.write(" - out.write(">"); - // encode the label - Utils.encodeRecursive(context, label); - // encode the control - context.getResponseWriter().write(""); - Utils.encodeRecursive(context, control); - - // NOTE: we'll allow the property sheet's grid renderer close off the last - } - } - - /** - * @see javax.faces.render.Renderer#encodeEnd(javax.faces.context.FacesContext, javax.faces.component.UIComponent) - */ - public void encodeEnd(FacesContext context, UIComponent component) throws IOException - { - // we don't need to do anything in here - } - - /** - * @see javax.faces.render.Renderer#getRendersChildren() - */ - public boolean getRendersChildren() - { - return true; - } } diff --git a/source/java/org/alfresco/web/ui/repo/renderer/property/ChildAssociationRenderer.java b/source/java/org/alfresco/web/ui/repo/renderer/property/ChildAssociationRenderer.java index 6a594c1fde..b794a3e689 100644 --- a/source/java/org/alfresco/web/ui/repo/renderer/property/ChildAssociationRenderer.java +++ b/source/java/org/alfresco/web/ui/repo/renderer/property/ChildAssociationRenderer.java @@ -16,98 +16,11 @@ */ package org.alfresco.web.ui.repo.renderer.property; -import java.io.IOException; -import java.util.List; - -import javax.faces.component.UIComponent; -import javax.faces.context.FacesContext; -import javax.faces.context.ResponseWriter; - -import org.alfresco.web.ui.common.Utils; -import org.alfresco.web.ui.common.renderer.BaseRenderer; -import org.alfresco.web.ui.repo.component.property.UIChildAssociation; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - /** * Renderer for the UIChildAssociation component * * @author gavinc */ -public class ChildAssociationRenderer extends BaseRenderer +public class ChildAssociationRenderer extends PropertySheetItemRenderer { - private static Log logger = LogFactory.getLog(ChildAssociationRenderer.class); - - /** - * @see javax.faces.render.Renderer#encodeBegin(javax.faces.context.FacesContext, javax.faces.component.UIComponent) - */ - public void encodeBegin(FacesContext context, UIComponent component) throws IOException - { - if (component.isRendered() == false) - { - return; - } - - // NOTE: we close off the first generated by the property sheet's grid renderer - context.getResponseWriter().write(""); - } - - /** - * @see javax.faces.render.Renderer#encodeChildren(javax.faces.context.FacesContext, javax.faces.component.UIComponent) - */ - public void encodeChildren(FacesContext context, UIComponent component) throws IOException - { - if (component.isRendered() == false) - { - return; - } - - UIChildAssociation association = (UIChildAssociation)component; - ResponseWriter out = context.getResponseWriter(); - - // make sure there are exactly 2 child components - int count = association.getChildCount(); - - if (count == 2) - { - // get the label and the control - List kids = association.getChildren(); - UIComponent label = kids.get(0); - UIComponent control = kids.get(1); - - // place a style class on the label column if necessary - String labelStylceClass = (String)association.getParent().getAttributes().get("labelStyleClass"); - out.write(" - out.write(">"); - // encode the label - Utils.encodeRecursive(context, label); - // encode the control - context.getResponseWriter().write(""); - Utils.encodeRecursive(context, control); - - // NOTE: we'll allow the property sheet's grid renderer close off the last - } - } - - /** - * @see javax.faces.render.Renderer#encodeEnd(javax.faces.context.FacesContext, javax.faces.component.UIComponent) - */ - public void encodeEnd(FacesContext context, UIComponent component) throws IOException - { - // we don't need to do anything in here - } - - /** - * @see javax.faces.render.Renderer#getRendersChildren() - */ - public boolean getRendersChildren() - { - return true; - } } diff --git a/source/java/org/alfresco/web/ui/repo/renderer/property/PropertyRenderer.java b/source/java/org/alfresco/web/ui/repo/renderer/property/PropertyRenderer.java index ee3112b78a..02340b1711 100644 --- a/source/java/org/alfresco/web/ui/repo/renderer/property/PropertyRenderer.java +++ b/source/java/org/alfresco/web/ui/repo/renderer/property/PropertyRenderer.java @@ -16,99 +16,11 @@ */ package org.alfresco.web.ui.repo.renderer.property; -import java.io.IOException; -import java.util.List; - -import javax.faces.component.UIComponent; -import javax.faces.context.FacesContext; -import javax.faces.context.ResponseWriter; - -import org.alfresco.web.ui.common.Utils; -import org.alfresco.web.ui.common.renderer.BaseRenderer; -import org.alfresco.web.ui.repo.component.property.UIProperty; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - /** * Renderer for the UIProperty component * * @author gavinc */ -public class PropertyRenderer extends BaseRenderer +public class PropertyRenderer extends PropertySheetItemRenderer { - private static Log logger = LogFactory.getLog(PropertyRenderer.class); - - /** - * @see javax.faces.render.Renderer#encodeBegin(javax.faces.context.FacesContext, javax.faces.component.UIComponent) - */ - public void encodeBegin(FacesContext context, UIComponent component) throws IOException - { - if (component.isRendered() == false) - { - return; - } - - // NOTE: we close off the first generated by the property sheet's grid renderer - context.getResponseWriter().write(""); - } - - /** - * @see javax.faces.render.Renderer#encodeChildren(javax.faces.context.FacesContext, javax.faces.component.UIComponent) - */ - public void encodeChildren(FacesContext context, UIComponent component) throws IOException - { - if (component.isRendered() == false) - { - return; - } - - UIProperty property = (UIProperty)component; - ResponseWriter out = context.getResponseWriter(); - - // make sure there are exactly 2 child components - int count = property.getChildCount(); - - if (count == 2) - { - // get the label and the control - List kids = property.getChildren(); - UIComponent label = kids.get(0); - UIComponent control = kids.get(1); - - // place a style class on the label column if necessary - String labelStylceClass = (String)property.getParent().getAttributes().get("labelStyleClass"); - out.write(" - out.write(">"); - // encode the label - Utils.encodeRecursive(context, label); - // encode the control - out.write(""); - Utils.encodeRecursive(context, control); - - // NOTE: we'll allow the property sheet's grid renderer close off the last - } - } - - /** - * @see javax.faces.render.Renderer#encodeEnd(javax.faces.context.FacesContext, javax.faces.component.UIComponent) - */ - public void encodeEnd(FacesContext context, UIComponent component) throws IOException - { - // we don't need to do anything in here - } - - /** - * @see javax.faces.render.Renderer#getRendersChildren() - */ - public boolean getRendersChildren() - { - return true; - } - } diff --git a/source/java/org/alfresco/web/ui/repo/renderer/property/PropertySheetItemRenderer.java b/source/java/org/alfresco/web/ui/repo/renderer/property/PropertySheetItemRenderer.java new file mode 100644 index 0000000000..9e808dea55 --- /dev/null +++ b/source/java/org/alfresco/web/ui/repo/renderer/property/PropertySheetItemRenderer.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.repo.renderer.property; + +import java.io.IOException; +import java.util.List; + +import javax.faces.component.UIComponent; +import javax.faces.context.FacesContext; +import javax.faces.context.ResponseWriter; + +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.renderer.BaseRenderer; + +/** + * Renderer for a PropertySheetItem component + * + * @author gavinc + */ +public class PropertySheetItemRenderer extends BaseRenderer +{ + /** + * @see javax.faces.render.Renderer#encodeBegin(javax.faces.context.FacesContext, javax.faces.component.UIComponent) + */ + public void encodeBegin(FacesContext context, UIComponent component) throws IOException + { + if (component.isRendered() == false) + { + return; + } + + // NOTE: we close off the first generated by the property sheet's grid renderer + context.getResponseWriter().write(""); + } + + /** + * @see javax.faces.render.Renderer#encodeChildren(javax.faces.context.FacesContext, javax.faces.component.UIComponent) + */ + @SuppressWarnings("unchecked") + public void encodeChildren(FacesContext context, UIComponent component) throws IOException + { + if (component.isRendered() == false) + { + return; + } + + ResponseWriter out = context.getResponseWriter(); + + // make sure there are 2 or 3 child components + int count = component.getChildCount(); + + if (count == 2 || count == 3) + { + // get the label and the control + List children = component.getChildren(); + UIComponent label = children.get(0); + UIComponent control = children.get(1); + + // place a style class on the label column if necessary + String labelStylceClass = (String)component.getParent().getAttributes().get("labelStyleClass"); + out.write(" + out.write(">"); + // encode the label + Utils.encodeRecursive(context, label); + // encode the control + out.write(""); + Utils.encodeRecursive(context, control); + + // encode the mandatory marker component if present + if (count == 3) + { + out.write(""); + UIComponent mandatoryMarker = children.get(2); + Utils.encodeRecursive(context, mandatoryMarker); + } + + // NOTE: we'll allow the property sheet's grid renderer close off the last + } + } + + /** + * @see javax.faces.render.Renderer#encodeEnd(javax.faces.context.FacesContext, javax.faces.component.UIComponent) + */ + public void encodeEnd(FacesContext context, UIComponent component) throws IOException + { + // we don't need to do anything in here + } + + /** + * @see javax.faces.render.Renderer#getRendersChildren() + */ + public boolean getRendersChildren() + { + return true; + } +} diff --git a/source/java/org/alfresco/web/ui/repo/tag/property/PropertySheetGridTag.java b/source/java/org/alfresco/web/ui/repo/tag/property/PropertySheetGridTag.java index 5b08761ed0..c64fa7149c 100644 --- a/source/java/org/alfresco/web/ui/repo/tag/property/PropertySheetGridTag.java +++ b/source/java/org/alfresco/web/ui/repo/tag/property/PropertySheetGridTag.java @@ -34,9 +34,11 @@ public class PropertySheetGridTag extends BaseComponentTag private String configArea; private String readOnly; private String mode; + private String validationEnabled; private String labelStyleClass; private String cellpadding; private String cellspacing; + private String finishButtonId; /** * @see javax.faces.webapp.UIComponentTag#getComponentType() @@ -110,6 +112,14 @@ public class PropertySheetGridTag extends BaseComponentTag this.readOnly = readOnly; } + /** + * @param validationEnabled The validationEnabled to set. + */ + public void setValidationEnabled(String validationEnabled) + { + this.validationEnabled = validationEnabled; + } + /** * @param labelStyleClass Sets the style class for the label column */ @@ -134,6 +144,14 @@ public class PropertySheetGridTag extends BaseComponentTag this.cellspacing = cellspacing; } + /** + * @param finishButtonId Sets the finish button id + */ + public void setFinishButtonId(String finishButtonId) + { + this.finishButtonId = finishButtonId; + } + /** * @see javax.faces.webapp.UIComponentTag#setProperties(javax.faces.component.UIComponent) */ @@ -149,8 +167,10 @@ public class PropertySheetGridTag extends BaseComponentTag setStringStaticProperty(component, "labelStyleClass", this.labelStyleClass); setBooleanProperty(component, "externalConfig", this.externalConfig); setBooleanProperty(component, "readOnly", this.readOnly); + setBooleanProperty(component, "validationEnabled", this.validationEnabled); setStringStaticProperty(component, "cellpadding", this.cellpadding); setStringStaticProperty(component, "cellspacing", this.cellspacing); + setStringStaticProperty(component, "finishButtonId", this.finishButtonId); } /** @@ -165,9 +185,11 @@ public class PropertySheetGridTag extends BaseComponentTag this.configArea = null; this.readOnly = null; this.mode = null; + this.validationEnabled = null; this.labelStyleClass = null; this.cellpadding = null; this.cellspacing = null; + this.finishButtonId = null; super.release(); } diff --git a/source/web/WEB-INF/faces-config-beans.xml b/source/web/WEB-INF/faces-config-beans.xml index d56bfd6ca1..49a735337e 100644 --- a/source/web/WEB-INF/faces-config-beans.xml +++ b/source/web/WEB-INF/faces-config-beans.xml @@ -1458,10 +1458,10 @@ - Bean that generates a category picker component + Bean that generates a category selector component - CategoryPickerGenerator - org.alfresco.web.bean.generator.CategoryPickerGenerator + CategorySelectorGenerator + org.alfresco.web.bean.generator.CategorySelectorGenerator request diff --git a/source/web/WEB-INF/repo.tld b/source/web/WEB-INF/repo.tld index 9b73393cdf..b44dd45207 100644 --- a/source/web/WEB-INF/repo.tld +++ b/source/web/WEB-INF/repo.tld @@ -49,6 +49,18 @@ true + + validationEnabled + false + true + + + + finishButtonId + false + true + + var false diff --git a/source/web/jsp/dialog/edit-document-properties.jsp b/source/web/jsp/dialog/edit-document-properties.jsp index 0c5fc79b0c..67aabdc326 100644 --- a/source/web/jsp/dialog/edit-document-properties.jsp +++ b/source/web/jsp/dialog/edit-document-properties.jsp @@ -156,7 +156,7 @@ + externalConfig="true" finishButtonId="ok-button" /> diff --git a/source/web/scripts/validation.js b/source/web/scripts/validation.js new file mode 100644 index 0000000000..c302343cde --- /dev/null +++ b/source/web/scripts/validation.js @@ -0,0 +1,85 @@ +// +// Validation functions +// Gavin Cornwell 30-11-2005 +// + +/** + * Informs the user of the given 'message', if 'showMessage' is true. + */ +function informUser(message, showMessage) +{ + if (showMessage) + { + alert(message); + } +} + +/** + * Ensures the 'value' is not null or 0. + * + * @return true if the mandatory validation passed + */ +function validateMandatory(value, message, showMessage) +{ + var result = true; + + if (value == null || value.length == 0) + { + informUser(message, showMessage); + result = false; + } + + return result; +} + +/** + * Ensures the 'value' is more than 'min' and less than 'max'. + * + * @return true if the number range validation passed + */ +function validateNumberRange(value, min, max, message, showMessage) +{ + var result = true; + + if (value < min || value > max) + { + informUser(message, showMessage); + result = false; + } + + return result; +} + +/** + * Ensures the 'value' has a string length more than 'min' and less than 'max'. + * + * @return true if the string length validation passed + */ +function validateStringLength(value, min, max, message, showMessage) +{ + var result = true; + + if (value.length < min || value.length > max) + { + informUser(message, showMessage); + result = false; + } + + return result; +} + +/** + * Ensures the 'value' matches the 'expression' if 'requiresMatch' is true. + * Ensures the 'value' does not matche the 'expression' if 'requiresMatch' is false. + * + * @return true if the regex validation passed + */ +function validateRegex(value, expression, requiresMatch, message, showMessage) +{ + var result = true; + + // TODO: implement the regular expression matching + + return result; +} +