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
015package org.apache.tapestry.form;
016
017import java.util.HashSet;
018import java.util.Set;
019
020import org.apache.hivemind.ApplicationRuntimeException;
021import org.apache.tapestry.IMarkupWriter;
022import org.apache.tapestry.IRequestCycle;
023import org.apache.tapestry.Tapestry;
024import 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 */
039public 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}