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