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 java.util.HashSet;
018    import java.util.Set;
019    
020    import org.apache.hivemind.ApplicationRuntimeException;
021    import org.apache.tapestry.IMarkupWriter;
022    import org.apache.tapestry.IRequestCycle;
023    import org.apache.tapestry.Tapestry;
024    import org.apache.tapestry.valid.ValidatorException;
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            if (cycle.getAttribute(ATTRIBUTE_NAME) != null)
096                throw new ApplicationRuntimeException(Tapestry.getMessage("Select.may-not-nest"), this,
097                        null, null);
098    
099            cycle.setAttribute(ATTRIBUTE_NAME, this);
100    
101            _rendering = true;
102            _nextOptionId = 0;      
103        }
104    
105        /**
106         * @see org.apache.tapestry.AbstractComponent#cleanupAfterRender(org.apache.tapestry.IRequestCycle)
107         */
108        protected void cleanupAfterRender(IRequestCycle cycle)
109        {
110            _rendering = false;
111            _selections = null;        
112            
113            cycle.removeAttribute(ATTRIBUTE_NAME);           
114        }
115    
116        /**
117         * @see org.apache.tapestry.form.AbstractFormComponent#renderFormComponent(org.apache.tapestry.IMarkupWriter, org.apache.tapestry.IRequestCycle)
118         */
119        protected void renderFormComponent(IMarkupWriter writer, IRequestCycle cycle)
120        {
121            _rewinding = false;
122    
123            renderDelegatePrefix(writer, cycle);
124    
125            writer.begin("select");
126    
127            writer.attribute("name", getName());
128    
129            if (isMultiple())
130                writer.attribute("multiple", "multiple");
131    
132            if (isDisabled())
133                writer.attribute("disabled", "disabled");
134    
135            renderIdAttribute(writer, cycle);
136    
137            renderDelegateAttributes(writer, cycle);
138    
139            getValidatableFieldSupport().renderContributions(this, writer, cycle);
140            
141            renderInformalParameters(writer, cycle);
142    
143            renderBody(writer, cycle);
144    
145            writer.end();
146    
147            renderDelegateSuffix(writer, cycle);
148        }
149    
150        /**
151         * @see org.apache.tapestry.form.AbstractFormComponent#rewindFormComponent(org.apache.tapestry.IMarkupWriter, org.apache.tapestry.IRequestCycle)
152         */
153        protected void rewindFormComponent(IMarkupWriter writer, IRequestCycle cycle)
154        {
155            _selections = null;
156            _rewinding = true;
157    
158            String[] parameters = cycle.getParameters(getName());
159    
160            try
161            {
162                if (parameters != null)
163                {
164                    int length = parameters.length;
165        
166                    _selections = new HashSet((length > 30) ? 101 : 7);
167        
168                    for (int i = 0; i < length; i++)
169                        _selections.add(parameters[i]);
170                }
171        
172                renderBody(writer, cycle);
173                
174                // This is atypical validation - since this component does not explicitly bind to an object
175                getValidatableFieldSupport().validate(this, writer, cycle, parameters);
176            }
177            catch (ValidatorException e)
178            {
179                getForm().getDelegate().record(e);
180            }
181        }
182    
183        /**
184         * Injected.
185         */
186        public abstract ValidatableFieldSupport getValidatableFieldSupport();
187    
188        /**
189         * @see org.apache.tapestry.form.AbstractFormComponent#isRequired()
190         */
191        public boolean isRequired()
192        {
193            return getValidatableFieldSupport().isRequired(this);
194        }
195    }