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     * @author Jesse Kuhnert
047     */
048    public abstract class PropertySelection extends AbstractFormComponent 
049        implements ValidatableField
050    {   
051        /**
052         * @see org.apache.tapestry.form.AbstractFormComponent#renderFormComponent(org.apache.tapestry.IMarkupWriter, org.apache.tapestry.IRequestCycle)
053         */
054        protected void renderFormComponent(IMarkupWriter writer, IRequestCycle cycle)
055        {
056            renderDelegatePrefix(writer, cycle);
057            
058            writer.begin("select");
059            writer.attribute("name", getName());
060            
061            if (isDisabled())
062                writer.attribute("disabled", "disabled");
063            
064            if (getSubmitOnChange())
065                writer.attribute("onchange", "javascript: this.form.events.submit();");
066            
067            renderIdAttribute(writer, cycle);
068            
069            renderDelegateAttributes(writer, cycle);
070            
071            getValidatableFieldSupport().renderContributions(this, writer, cycle);
072            
073            // Apply informal attributes.
074            renderInformalParameters(writer, cycle);
075            
076            writer.println();
077            
078            IPropertySelectionModel model = getModel();
079            
080            if (model == null)
081                throw Tapestry.createRequiredParameterException(this, "model");
082            
083            int count = model.getOptionCount();
084            boolean foundSelected = false;
085            Object value = getValue();
086            
087            for (int i = 0; i < count; i++)
088            {
089                Object option = model.getOption(i);
090    
091                writer.begin("option");
092                writer.attribute("value", model.getValue(i));
093    
094                if (!foundSelected && isEqual(option, value))
095                {
096                    writer.attribute("selected", "selected");
097    
098                    foundSelected = true;
099                }
100    
101                writer.print(model.getLabel(i));
102    
103                writer.end();
104    
105                writer.println();
106            }
107    
108            writer.end(); // <select>
109    
110            renderDelegateSuffix(writer, cycle);
111        }
112    
113        /**
114         * @see org.apache.tapestry.form.AbstractFormComponent#rewindFormComponent(org.apache.tapestry.IMarkupWriter, org.apache.tapestry.IRequestCycle)
115         */
116        protected void rewindFormComponent(IMarkupWriter writer, IRequestCycle cycle)
117        {
118            String value = cycle.getParameter(getName());
119            
120            Object object = getModel().translateValue(value);
121            
122            try
123            {
124                getValidatableFieldSupport().validate(this, writer, cycle, object);
125                
126                setValue(object);
127            }
128            catch (ValidatorException e)
129            {
130                getForm().getDelegate().record(e);
131            }
132        }
133        
134        private boolean isEqual(Object left, Object right)
135        {
136            // Both null, or same object, then are equal
137    
138            if (left == right)
139                return true;
140            
141            // If one is null, the other isn't, then not equal.
142            
143            if (left == null || right == null)
144                return false;
145            
146            // Both non-null; use standard comparison.
147            
148            return left.equals(right);
149        }
150        
151        public abstract IPropertySelectionModel getModel();
152        
153        /** @since 2.2 * */
154        public abstract boolean getSubmitOnChange();
155    
156        /** @since 2.2 * */
157        public abstract Object getValue();
158    
159        /** @since 2.2 * */
160        public abstract void setValue(Object value);
161        
162        /**
163         * Injected.
164         */
165        public abstract ValidatableFieldSupport getValidatableFieldSupport();
166        
167        /**
168         * @see org.apache.tapestry.form.AbstractFormComponent#isRequired()
169         */
170        public boolean isRequired()
171        {
172            return getValidatableFieldSupport().isRequired(this);
173        }
174    }