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 }