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