001    // Copyright 2004, 2005 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    //     http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.tapestry.form;
016    
017    import org.apache.tapestry.IMarkupWriter;
018    import org.apache.tapestry.IRequestCycle;
019    import org.apache.tapestry.Tapestry;
020    import org.apache.tapestry.valid.ValidatorException;
021    
022    /**
023     * A component used to render a drop-down list of options that the user may select. [ <a
024     * href="../../../../../ComponentReference/PropertySelection.html">Component Reference </a>]
025     * <p>
026     * Earlier versions of PropertySelection (through release 2.2) were more flexible, they included a
027     * <b>renderer </b> property that controlled how the selection was rendered. Ultimately, this proved
028     * of little value and this portion of functionality was deprecated in 2.3 and will be removed in
029     * 2.3.
030     * <p>
031     * Typically, the values available to be selected are defined using an
032     * {@link org.apache.commons.lang.enum.Enum}. A PropertySelection is dependent on an
033     * {@link IPropertySelectionModel} to provide the list of possible values.
034     * <p>
035     * Often, this is used to select a particular {@link org.apache.commons.lang.enum.Enum} to assign to
036     * a property; the {@link EnumPropertySelectionModel} class simplifies this.
037     * <p>
038     * Often, a drop-down list will contain an initial option that serves both as a label and to represent 
039     * that nothing is selected. This can behavior can easily be achieved by decorating an existing 
040     * {@link IPropertySelectionModel} with a {@link LabeledPropertySelectionModel}.
041     * <p>
042     * As of 4.0, this component can be validated.
043     * 
044     * @author Howard Lewis Ship
045     * @author Paul Ferraro
046     */
047    public abstract class PropertySelection extends AbstractFormComponent implements ValidatableField
048    {
049        /**
050         * @see org.apache.tapestry.form.AbstractFormComponent#renderFormComponent(org.apache.tapestry.IMarkupWriter, org.apache.tapestry.IRequestCycle)
051         */
052        protected void renderFormComponent(IMarkupWriter writer, IRequestCycle cycle)
053        {
054            renderDelegatePrefix(writer, cycle);
055    
056            writer.begin("select");
057            writer.attribute("name", getName());
058    
059            if (isDisabled())
060                writer.attribute("disabled", "disabled");
061    
062            if (getSubmitOnChange())
063                writer.attribute("onchange", "javascript:   this.form.events.submit();");
064    
065            renderIdAttribute(writer, cycle);
066    
067            renderDelegateAttributes(writer, cycle);
068    
069            getValidatableFieldSupport().renderContributions(this, writer, cycle);
070            
071            // Apply informal attributes.
072            renderInformalParameters(writer, cycle);
073    
074            writer.println();
075    
076            IPropertySelectionModel model = getModel();
077    
078            if (model == null)
079                throw Tapestry.createRequiredParameterException(this, "model");
080    
081            int count = model.getOptionCount();
082            boolean foundSelected = false;
083            Object value = getValue();
084    
085            for (int i = 0; i < count; i++)
086            {
087                Object option = model.getOption(i);
088    
089                writer.begin("option");
090                writer.attribute("value", model.getValue(i));
091    
092                if (!foundSelected && isEqual(option, value))
093                {
094                    writer.attribute("selected", "selected");
095    
096                    foundSelected = true;
097                }
098    
099                writer.print(model.getLabel(i));
100    
101                writer.end();
102    
103                writer.println();
104            }
105    
106            writer.end(); // <select>
107    
108            renderDelegateSuffix(writer, cycle);
109        }
110    
111        /**
112         * @see org.apache.tapestry.form.AbstractFormComponent#rewindFormComponent(org.apache.tapestry.IMarkupWriter, org.apache.tapestry.IRequestCycle)
113         */
114        protected void rewindFormComponent(IMarkupWriter writer, IRequestCycle cycle)
115        {
116            String value = cycle.getParameter(getName());
117            
118            Object object = (value == null) ? null : getModel().translateValue(value);
119            
120            try
121            {
122                getValidatableFieldSupport().validate(this, writer, cycle, object);
123                
124                setValue(object);
125            }
126            catch (ValidatorException e)
127            {
128                getForm().getDelegate().record(e);
129            }
130        }
131    
132        private boolean isEqual(Object left, Object right)
133        {
134            // Both null, or same object, then are equal
135    
136            if (left == right)
137                return true;
138    
139            // If one is null, the other isn't, then not equal.
140    
141            if (left == null || right == null)
142                return false;
143    
144            // Both non-null; use standard comparison.
145    
146            return left.equals(right);
147        }
148    
149        public abstract IPropertySelectionModel getModel();
150    
151        /** @since 2.2 * */
152        public abstract boolean getSubmitOnChange();
153    
154        /** @since 2.2 * */
155        public abstract Object getValue();
156    
157        /** @since 2.2 * */
158        public abstract void setValue(Object value);
159    
160        /**
161         * Injected.
162         */
163        public abstract ValidatableFieldSupport getValidatableFieldSupport();
164    
165        /**
166         * @see org.apache.tapestry.form.AbstractFormComponent#isRequired()
167         */
168        public boolean isRequired()
169        {
170            return getValidatableFieldSupport().isRequired(this);
171        }
172    }