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.hivemind.ApplicationRuntimeException;
018    import org.apache.tapestry.IMarkupWriter;
019    import org.apache.tapestry.IRequestCycle;
020    import org.apache.tapestry.Tapestry;
021    import org.apache.tapestry.valid.ValidatorException;
022    
023    import java.util.HashSet;
024    import java.util.Set;
025    
026    /**
027     * Implements a component that manages an HTML <select> form element. The most common
028     * situation, using a <select> to set a specific property of some object, is best handled
029     * using a {@link PropertySelection}component. [ <a
030     * href="../../../../../ComponentReference/Select.html">Component Reference </a>]
031     * <p>
032     * Otherwise, this component is very similar to {@link RadioGroup}. 
033     * <p>
034     * As of 4.0, this component can be validated.
035     * 
036     * @author Howard Lewis Ship
037     * @author Paul Ferraro
038     */
039    public abstract class Select extends AbstractFormComponent implements ValidatableField
040    {
041        
042        /**
043         * Used by the <code>Select</code> to record itself as a {@link IRequestCycle}attribute, so
044         * that the {@link Option}components it wraps can have access to it.
045         */
046    
047        private static final String ATTRIBUTE_NAME = "org.apache.tapestry.active.Select";
048        
049        private boolean _rewinding;
050    
051        private boolean _rendering;
052    
053        private Set _selections;
054    
055        private int _nextOptionId;
056    
057        public static Select get(IRequestCycle cycle)
058        {
059            return (Select) cycle.getAttribute(ATTRIBUTE_NAME);
060        }
061    
062        public abstract boolean isMultiple();
063    
064        public boolean isRewinding()
065        {
066            if (!_rendering)
067                throw Tapestry.createRenderOnlyPropertyException(this, "rewinding");
068    
069            return _rewinding;
070        }
071    
072        public String getNextOptionId()
073        {
074            if (!_rendering)
075                throw Tapestry.createRenderOnlyPropertyException(this, "nextOptionId");
076    
077            // Return it as a hex value.
078    
079            return Integer.toString(_nextOptionId++);
080        }
081    
082        public boolean isSelected(String value)
083        {
084            if (_selections == null)
085                return false;
086    
087            return _selections.contains(value);
088        }
089    
090        /**
091         * @see org.apache.tapestry.AbstractComponent#prepareForRender(org.apache.tapestry.IRequestCycle)
092         */
093        protected void prepareForRender(IRequestCycle cycle)
094        {
095            super.prepareForRender(cycle);
096            
097            if (cycle.getAttribute(ATTRIBUTE_NAME) != null)
098                throw new ApplicationRuntimeException(Tapestry.getMessage("Select.may-not-nest"), this, null, null);
099    
100            cycle.setAttribute(ATTRIBUTE_NAME, this);
101    
102            _rendering = true;
103            _nextOptionId = 0;      
104        }
105    
106        /**
107         * @see org.apache.tapestry.AbstractComponent#cleanupAfterRender(org.apache.tapestry.IRequestCycle)
108         */
109        protected void cleanupAfterRender(IRequestCycle cycle)
110        {
111            super.cleanupAfterRender(cycle);
112    
113            _rendering = false;
114            _selections = null;        
115            
116            cycle.removeAttribute(ATTRIBUTE_NAME);
117        }
118    
119        /**
120         * @see org.apache.tapestry.form.AbstractFormComponent#renderFormComponent(org.apache.tapestry.IMarkupWriter, org.apache.tapestry.IRequestCycle)
121         */
122        protected void renderFormComponent(IMarkupWriter writer, IRequestCycle cycle)
123        {
124            _rewinding = false;
125    
126            renderDelegatePrefix(writer, cycle);
127    
128            writer.begin("select");
129    
130            writer.attribute("name", getName());
131    
132            if (isMultiple())
133                writer.attribute("multiple", "multiple");
134    
135            if (isDisabled())
136                writer.attribute("disabled", "disabled");
137    
138            renderIdAttribute(writer, cycle);
139    
140            renderDelegateAttributes(writer, cycle);
141    
142            getValidatableFieldSupport().renderContributions(this, writer, cycle);
143            
144            renderInformalParameters(writer, cycle);
145    
146            renderBody(writer, cycle);
147    
148            writer.end();
149    
150            renderDelegateSuffix(writer, cycle);
151        }
152    
153        /**
154         * @see org.apache.tapestry.form.AbstractFormComponent#rewindFormComponent(org.apache.tapestry.IMarkupWriter, org.apache.tapestry.IRequestCycle)
155         */
156        protected void rewindFormComponent(IMarkupWriter writer, IRequestCycle cycle)
157        {
158            _selections = null;
159            _rewinding = true;
160    
161            String[] parameters = cycle.getParameters(getName());
162    
163            try
164            {
165                if (parameters != null)
166                {
167                    int length = parameters.length;
168        
169                    _selections = new HashSet((length > 30) ? 101 : 7);
170        
171                    for (int i = 0; i < length; i++)
172                        _selections.add(parameters[i]);
173                }
174        
175                renderBody(writer, cycle);
176                
177                // This is atypical validation - since this component does not explicitly bind to an object
178                getValidatableFieldSupport().validate(this, writer, cycle, parameters);
179            }
180            catch (ValidatorException e)
181            {
182                getForm().getDelegate().record(e);
183            }
184        }
185    
186        /**
187         * Injected.
188         */
189        public abstract ValidatableFieldSupport getValidatableFieldSupport();
190    
191        /**
192         * @see org.apache.tapestry.form.AbstractFormComponent#isRequired()
193         */
194        public boolean isRequired()
195        {
196            return getValidatableFieldSupport().isRequired(this);
197        }
198    }